fix: ohif tests to run with cornerstone 3d 5.0 (#6043)
* chore(tests): Update multiple screenshot test images for various specs * feat(screenshot-reviewer): Add screenshot review tool and update package.json scripts * fix(DICOMSRDisplayTool): Improve actor presence check in viewport * chore(tests): Update multiple screenshot assets for various specs * chore(tests): Integrate waitForPaintToSettle and waitForViewportsRendered in multiple specs for improved rendering stability * chore(tests): Update screenshot assets for SEGHydration and SEGNoHydration specs * test: update progressive loading screenshots * jest 30 test fixes for compatibility with pnpm cs3d * Use correct setDisplaySets instead of setDataId * fix: Naming change for LegacyVolumeViewport3D * Update to allow tolerance for contour tests * update * fix * refactor: Replace instanceof checks with utility functions for viewport type validation * fix: Update createSegmentationForViewport to handle undefined displaySetInstanceUID gracefully * bun lock * fix: Install cs3d with pnpm instead of bun * Update node version for playwright * Update to v5.0.0 of cs3d * fix: Build dependency * audit * Change to a web await retry assert * Fix timing related test failures * fix: Freehand close --------- Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
1 parent
a27f779f0b
commit
6dd150d401
172 files changed
+3081
-1866
No files matched your search
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.29.7",
|
||||
"@cornerstonejs/adapters": "4.22.10",
|
||||
"@cornerstonejs/core": "4.22.10",
|
||||
"@kitware/vtk.js": "34.15.1"
|
||||
"@cornerstonejs/adapters": "5.0.0",
|
||||
"@cornerstonejs/core": "5.0.0",
|
||||
"@kitware/vtk.js": "35.5.3"
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.29.7",
|
||||
"@cornerstonejs/adapters": "4.22.10",
|
||||
"@cornerstonejs/core": "4.22.10",
|
||||
"@kitware/vtk.js": "34.15.1"
|
||||
"@cornerstonejs/adapters": "5.0.0",
|
||||
"@cornerstonejs/core": "5.0.0",
|
||||
"@kitware/vtk.js": "35.5.3"
|
||||
}
|
||||
}
|
||||
@@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.29.7",
|
||||
"@cornerstonejs/adapters": "4.22.10",
|
||||
"@cornerstonejs/core": "4.22.10",
|
||||
"@cornerstonejs/tools": "4.22.10",
|
||||
"@cornerstonejs/adapters": "5.0.0",
|
||||
"@cornerstonejs/core": "5.0.0",
|
||||
"@cornerstonejs/tools": "5.0.0",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,12 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
trackingUniqueIdentifiers.includes(annotation.data?.TrackingUniqueIdentifier)
|
||||
);
|
||||
|
||||
if (!viewport._actors?.size) {
|
||||
const hasActors =
|
||||
typeof viewport.getActors === 'function'
|
||||
? viewport.getActors().length > 0
|
||||
: Boolean(viewport._actors?.size);
|
||||
|
||||
if (!hasActors) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.29.7",
|
||||
"@cornerstonejs/core": "4.22.10",
|
||||
"@cornerstonejs/tools": "4.22.10",
|
||||
"@cornerstonejs/core": "5.0.0",
|
||||
"@cornerstonejs/tools": "5.0.0",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "1.3.0",
|
||||
"@cornerstonejs/codec-openjph": "2.4.7",
|
||||
"@cornerstonejs/dicom-image-loader": "4.22.10",
|
||||
"@cornerstonejs/dicom-image-loader": "5.0.0",
|
||||
"@ohif/core": "3.13.0-beta.88",
|
||||
"@ohif/ui": "3.13.0-beta.88",
|
||||
"dcmjs": "0.49.4",
|
||||
@@ -53,15 +53,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.29.7",
|
||||
"@cornerstonejs/adapters": "4.22.10",
|
||||
"@cornerstonejs/ai": "4.22.10",
|
||||
"@cornerstonejs/core": "4.22.10",
|
||||
"@cornerstonejs/labelmap-interpolation": "4.22.10",
|
||||
"@cornerstonejs/polymorphic-segmentation": "4.22.10",
|
||||
"@cornerstonejs/tools": "4.22.10",
|
||||
"@cornerstonejs/adapters": "5.0.0",
|
||||
"@cornerstonejs/ai": "5.0.0",
|
||||
"@cornerstonejs/core": "5.0.0",
|
||||
"@cornerstonejs/labelmap-interpolation": "5.0.0",
|
||||
"@cornerstonejs/metadata": "5.0.0",
|
||||
"@cornerstonejs/polymorphic-segmentation": "5.0.0",
|
||||
"@cornerstonejs/tools": "5.0.0",
|
||||
"@icr/polyseg-wasm": "0.4.0",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "34.15.1",
|
||||
"@kitware/vtk.js": "35.5.3",
|
||||
"html2canvas": "1.4.1",
|
||||
"immutability-helper": "3.1.1",
|
||||
"lodash.compact": "3.0.1",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Enums, VolumeViewport3D, utilities as csUtils } from '@cornerstonejs/core';
|
||||
import { Enums, utilities as csUtils } from '@cornerstonejs/core';
|
||||
import { ImageScrollbar } from '@ohif/ui-next';
|
||||
import { isVolume3DViewportType } from '../../utils/getLegacyViewportType';
|
||||
|
||||
function CornerstoneImageScrollbar({
|
||||
viewportData,
|
||||
@@ -40,7 +41,7 @@ function CornerstoneImageScrollbar({
|
||||
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport || viewport instanceof VolumeViewport3D) {
|
||||
if (!viewport || isVolume3DViewportType(viewport)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ function CornerstoneImageScrollbar({
|
||||
|
||||
const updateIndex = event => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (!viewport || viewport instanceof VolumeViewport3D) {
|
||||
if (!viewport || isVolume3DViewportType(viewport)) {
|
||||
return;
|
||||
}
|
||||
const { imageIndex, newImageIdIndex = imageIndex, imageIdIndex } = event.detail;
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { VolumeViewport3D, utilities as csUtils } from '@cornerstonejs/core';
|
||||
import { utilities as csUtils } from '@cornerstonejs/core';
|
||||
import { isVolume3DViewportType } from '../../../utils/getLegacyViewportType';
|
||||
import {
|
||||
SmartScrollbar,
|
||||
SmartScrollbarTrack,
|
||||
@@ -102,7 +103,7 @@ function ViewportSliceProgressScrollbar({
|
||||
const onScrollbarValueChange = targetImageIndex => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport || viewport instanceof VolumeViewport3D) {
|
||||
if (!viewport || isVolume3DViewportType(viewport)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
import { Enums, VolumeViewport3D } from '@cornerstonejs/core';
|
||||
import { Enums } from '@cornerstonejs/core';
|
||||
import { ViewportData } from './types';
|
||||
import { isVolume3DViewportType } from '../../../utils/getLegacyViewportType';
|
||||
|
||||
export function getImageIndexFromEvent(event): number | undefined {
|
||||
const { imageIndex, newImageIdIndex = imageIndex, imageIdIndex } = event.detail;
|
||||
@@ -19,7 +20,7 @@ export function getViewportImageIds(viewportData: ViewportData): string[] {
|
||||
}
|
||||
|
||||
export function isProgressFullMode(viewportData: ViewportData, viewport): boolean {
|
||||
if (!viewportData || !viewport || viewport instanceof VolumeViewport3D) {
|
||||
if (!viewportData || !viewport || isVolume3DViewportType(viewport)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ import {
|
||||
Enums,
|
||||
eventTarget,
|
||||
utilities,
|
||||
VolumeViewport3D,
|
||||
} from '@cornerstonejs/core';
|
||||
import { useByteArray } from '@ohif/ui-next';
|
||||
import { isVolume3DViewportType } from '../../../utils/getLegacyViewportType';
|
||||
import { getImageIdFromCacheEvent, getImageIndexFromEvent, isProgressFullMode } from './helpers';
|
||||
import { ImageSliceData, ViewportData } from './types';
|
||||
|
||||
@@ -98,7 +98,7 @@ export function useViewportSliceSync({
|
||||
}
|
||||
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (viewport && !(viewport instanceof VolumeViewport3D)) {
|
||||
if (viewport && !isVolume3DViewportType(viewport)) {
|
||||
try {
|
||||
const currentImageIndex = viewport.getCurrentImageIdIndex();
|
||||
const currentNumberOfSlices = viewport.getNumberOfSlices();
|
||||
@@ -120,7 +120,7 @@ export function useViewportSliceSync({
|
||||
|
||||
const updateIndex = event => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (!viewport || viewport instanceof VolumeViewport3D) {
|
||||
if (!viewport || isVolume3DViewportType(viewport)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import {
|
||||
getEnabledElement,
|
||||
StackViewport,
|
||||
VolumeViewport,
|
||||
utilities as csUtils,
|
||||
Enums as CoreEnums,
|
||||
Types as CoreTypes,
|
||||
BaseVolumeViewport,
|
||||
getRenderingEngines,
|
||||
} from '@cornerstonejs/core';
|
||||
import {
|
||||
@@ -38,6 +34,12 @@ import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/u
|
||||
import { getViewportEnabledElement } from './utils/getViewportEnabledElement';
|
||||
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
||||
import toggleVOISliceSync from './utils/toggleVOISliceSync';
|
||||
import {
|
||||
isStackViewportType,
|
||||
isOrthographicViewportType,
|
||||
isVolume3DViewportType,
|
||||
isVolumeViewportType,
|
||||
} from './utils/getLegacyViewportType';
|
||||
import {
|
||||
usePositionPresentationStore,
|
||||
useSegmentationPresentationStore,
|
||||
@@ -324,7 +326,7 @@ function commandsModule({
|
||||
// Todo: check if PMAP modality should be handled such as SEG
|
||||
displaySet.Modality !== 'SEG'
|
||||
? SegmentationRepresentations.Contour
|
||||
: viewport.type === CoreEnums.ViewportType.VOLUME_3D
|
||||
: isVolume3DViewportType(viewport)
|
||||
? SegmentationRepresentations.Surface
|
||||
: SegmentationRepresentations.Labelmap;
|
||||
|
||||
@@ -910,7 +912,7 @@ function commandsModule({
|
||||
|
||||
const { lower, upper } = csUtils.windowLevel.toLowHighRange(windowWidthNum, windowCenterNum);
|
||||
|
||||
if (viewport instanceof BaseVolumeViewport) {
|
||||
if (isVolumeViewportType(viewport)) {
|
||||
const volumeId = actions.getVolumeIdForDisplaySet({
|
||||
viewportId,
|
||||
displaySetInstanceUID,
|
||||
@@ -986,8 +988,12 @@ function commandsModule({
|
||||
},
|
||||
getVolumeIdForDisplaySet: ({ viewportId, displaySetInstanceUID }) => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (viewport instanceof BaseVolumeViewport) {
|
||||
const volumeIds = viewport.getAllVolumeIds();
|
||||
// `instanceof BaseVolumeViewport` is false for GenericViewport compat adapters,
|
||||
// so use the capability guard instead (cornerstone 5.x generic-viewport guide).
|
||||
// A stack-mode adapter passes the guard but getAllVolumeIds() returns [], so the
|
||||
// result is the same null as before on stacks.
|
||||
if (csUtils.viewportSupportsVolumeId(viewport)) {
|
||||
const volumeIds = (viewport as CoreTypes.IVolumeViewport).getAllVolumeIds();
|
||||
const volumeId = volumeIds.find(id => id.includes(displaySetInstanceUID));
|
||||
return volumeId;
|
||||
}
|
||||
@@ -1256,7 +1262,7 @@ function commandsModule({
|
||||
}
|
||||
const { viewport } = enabledElement;
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
if (isStackViewportType(viewport)) {
|
||||
if (direction) {
|
||||
const { parallelScale } = viewport.getCamera();
|
||||
viewport.setCamera({ parallelScale: parallelScale * scaleFactor });
|
||||
@@ -1286,9 +1292,9 @@ function commandsModule({
|
||||
// -> Copied from cornerstone3D jumpToSlice\_getImageSliceData()
|
||||
let numberOfSlices = 0;
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
if (isStackViewportType(viewport)) {
|
||||
numberOfSlices = viewport.getImageIds().length;
|
||||
} else if (viewport instanceof VolumeViewport) {
|
||||
} else if (isOrthographicViewportType(viewport)) {
|
||||
numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport).numberOfSlices;
|
||||
} else {
|
||||
throw new Error('Unsupported viewport type');
|
||||
@@ -1344,11 +1350,11 @@ function commandsModule({
|
||||
// HP takes priority over the default opacity
|
||||
colormap = { ...colormap, opacity: hpOpacity || opacity };
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
if (isStackViewportType(viewport)) {
|
||||
viewport.setProperties({ colormap });
|
||||
}
|
||||
|
||||
if (viewport instanceof VolumeViewport) {
|
||||
if (isOrthographicViewportType(viewport)) {
|
||||
if (!displaySetInstanceUID) {
|
||||
const { viewports } = viewportGridService.getState();
|
||||
displaySetInstanceUID = viewports.get(viewportId)?.displaySetInstanceUIDs[0];
|
||||
@@ -2145,7 +2151,7 @@ function commandsModule({
|
||||
setViewportOrientation: ({ viewportId, orientation }) => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport || viewport.type !== CoreEnums.ViewportType.ORTHOGRAPHIC) {
|
||||
if (!viewport || !isOrthographicViewportType(viewport)) {
|
||||
console.warn('Orientation can only be set on volume viewports');
|
||||
return;
|
||||
}
|
||||
@@ -2227,7 +2233,7 @@ function commandsModule({
|
||||
|
||||
const { viewport } = enabledElement;
|
||||
|
||||
if (viewport instanceof BaseVolumeViewport) {
|
||||
if (isVolumeViewportType(viewport)) {
|
||||
const camera = viewport.getCamera();
|
||||
const rotAngle = (rotation * Math.PI) / 180;
|
||||
const rotMat = mat4.identity(new Float32Array(16));
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useCallback, useState, ReactElement, useMemo } from '
|
||||
import PropTypes from 'prop-types';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { PanelSection, WindowLevel } from '@ohif/ui-next';
|
||||
import { BaseVolumeViewport, Enums, eventTarget } from '@cornerstonejs/core';
|
||||
import { Enums, eventTarget, utilities as csUtils, Types } from '@cornerstonejs/core';
|
||||
import { useActiveViewportDisplaySets } from '@ohif/core';
|
||||
import {
|
||||
getNodeOpacity,
|
||||
@@ -27,10 +27,16 @@ const ViewportWindowLevel = ({
|
||||
const getViewportsWithVolumeIds = useCallback(
|
||||
(volumeIds: string[]) => {
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
const viewports = renderingEngine.getVolumeViewports();
|
||||
// getVolumeViewports() was removed in the GenericViewport architecture
|
||||
// (a PLANAR_NEXT viewport can be volume-capable without being a VolumeViewport).
|
||||
// Official replacement: getViewports() + the viewportSupportsVolumeCompatibility
|
||||
// capability guard (cornerstone codemod cornerstone3d/5/generic-viewport).
|
||||
const viewports = renderingEngine
|
||||
.getViewports()
|
||||
.filter(csUtils.viewportSupportsVolumeCompatibility);
|
||||
|
||||
return viewports.filter(vp => {
|
||||
const viewportVolumeIds = vp instanceof BaseVolumeViewport ? vp.getAllVolumeIds() : [];
|
||||
const viewportVolumeIds = (vp as Types.IVolumeViewport).getAllVolumeIds();
|
||||
return (
|
||||
volumeIds.length === viewportVolumeIds.length &&
|
||||
volumeIds.every(volumeId => viewportVolumeIds.includes(volumeId))
|
||||
@@ -124,8 +130,9 @@ const ViewportWindowLevel = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportVolumeIds =
|
||||
viewport instanceof BaseVolumeViewport ? viewport.getAllVolumeIds() : [];
|
||||
const viewportVolumeIds = csUtils.viewportSupportsVolumeId(viewport)
|
||||
? (viewport as Types.IVolumeViewport).getAllVolumeIds()
|
||||
: [];
|
||||
const viewports = getViewportsWithVolumeIds(viewportVolumeIds);
|
||||
|
||||
viewports.forEach(vp => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
import { useViewportDisplaySets } from './useViewportDisplaySets';
|
||||
import { BaseVolumeViewport } from '@cornerstonejs/core';
|
||||
import { isVolumeViewportType } from '../utils/getLegacyViewportType';
|
||||
|
||||
/**
|
||||
* Hook that provides measurement tracking information for a viewport
|
||||
@@ -54,7 +54,7 @@ export function useMeasurementTracking({ viewportId }: { viewportId: string }) {
|
||||
const viewport = cornerstoneViewportService?.getCornerstoneViewport(viewportId);
|
||||
const SeriesInstanceUID = backgroundDisplaySet.SeriesInstanceUID;
|
||||
|
||||
if (viewport instanceof BaseVolumeViewport) {
|
||||
if (isVolumeViewportType(viewport)) {
|
||||
const currentImageId = viewport?.getCurrentImageId();
|
||||
|
||||
if (!currentImageId) {
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useViewportDisplaySets } from './useViewportDisplaySets';
|
||||
import {
|
||||
StackViewport,
|
||||
Types,
|
||||
VolumeViewport3D,
|
||||
utilities,
|
||||
Enums,
|
||||
BaseVolumeViewport,
|
||||
cache,
|
||||
} from '@cornerstonejs/core';
|
||||
import { Types, utilities, Enums, cache } from '@cornerstonejs/core';
|
||||
import { getDataIdForViewport } from '../utils/getDataIdForViewport';
|
||||
import {
|
||||
isStackViewportType,
|
||||
isVolumeViewportType,
|
||||
isVolume3DViewportType,
|
||||
} from '../utils/getLegacyViewportType';
|
||||
import { WindowLevelPreset } from '../types/WindowLevel';
|
||||
import { ColorbarPositionType, ColorbarOptions, ColorbarProperties } from '../types/Colorbar';
|
||||
import { VolumeRenderingConfig } from '../types/VolumeRenderingConfig';
|
||||
@@ -130,7 +127,7 @@ export function useViewportRendering(
|
||||
const [viewport, setViewport] = useState<Types.IViewport | null>(() =>
|
||||
viewportId ? (cornerstoneViewportService.getCornerstoneViewport(viewportId) ?? null) : null
|
||||
);
|
||||
const [is3DVolume, setIs3DVolume] = useState(viewport instanceof VolumeViewport3D);
|
||||
const [is3DVolume, setIs3DVolume] = useState(isVolume3DViewportType(viewport));
|
||||
const [opacity, setOpacityState] = useState<number | undefined>();
|
||||
const [opacityLinear, setOpacityLinearState] = useState<number | undefined>();
|
||||
const [threshold, setThresholdState] = useState<number | undefined>();
|
||||
@@ -219,7 +216,7 @@ export function useViewportRendering(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(viewport instanceof BaseVolumeViewport)) {
|
||||
if (!isVolumeViewportType(viewport)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -264,7 +261,7 @@ export function useViewportRendering(
|
||||
}, [allWindowLevelPresets, activeDisplaySetInstanceUID]);
|
||||
|
||||
useEffect(() => {
|
||||
setIs3DVolume(viewport instanceof VolumeViewport3D);
|
||||
setIs3DVolume(isVolume3DViewportType(viewport));
|
||||
|
||||
if (!viewport || !activeDisplaySetInstanceUID) {
|
||||
return;
|
||||
@@ -573,7 +570,7 @@ export function useViewportRendering(
|
||||
|
||||
const setOpacity = useCallback(
|
||||
(opacityValue: number) => {
|
||||
if (!viewport || !(viewport instanceof BaseVolumeViewport)) {
|
||||
if (!viewport || !isVolumeViewportType(viewport)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -624,7 +621,7 @@ export function useViewportRendering(
|
||||
|
||||
const setThreshold = useCallback(
|
||||
(thresholdValue: number) => {
|
||||
if (!viewport || !(viewport instanceof BaseVolumeViewport)) {
|
||||
if (!viewport || !isVolumeViewportType(viewport)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -665,7 +662,7 @@ export function useViewportRendering(
|
||||
return null;
|
||||
}
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
if (isStackViewportType(viewport)) {
|
||||
const { colormap } = viewport.getProperties();
|
||||
if (!colormap) {
|
||||
return (
|
||||
|
||||
@@ -74,6 +74,10 @@ export default async function init({
|
||||
rendering: {
|
||||
...cornerstone.getConfiguration().rendering,
|
||||
strictZSpacingForVolumeViewport: appConfig.strictZSpacingForVolumeViewport,
|
||||
// Opt-in: route legacy viewport types through the new GenericViewport render
|
||||
// paths while keeping the legacy public API via compatibility adapters.
|
||||
// No-op on cornerstone builds that predate the GenericViewport architecture.
|
||||
useGenericViewport: Boolean(appConfig.useGenericViewport),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ import {
|
||||
metaData,
|
||||
} from '@cornerstonejs/core';
|
||||
import { ViewportType } from '@cornerstonejs/core/enums';
|
||||
import {
|
||||
isVolume3DViewportType,
|
||||
isVolumeViewportType,
|
||||
} from '../../utils/getLegacyViewportType';
|
||||
|
||||
import {
|
||||
Enums as csToolsEnums,
|
||||
@@ -312,7 +316,7 @@ class SegmentationService extends PubSubService {
|
||||
let isConverted = false;
|
||||
|
||||
const defaultRepresentationType: csToolsEnums.SegmentationRepresentations =
|
||||
csViewport.type === ViewportType.VOLUME_3D ? SURFACE : LABELMAP;
|
||||
isVolume3DViewportType(csViewport) ? SURFACE : LABELMAP;
|
||||
let representationTypeToUse = type || defaultRepresentationType;
|
||||
|
||||
if (representationTypeToUse === LABELMAP) {
|
||||
@@ -1591,8 +1595,7 @@ class SegmentationService extends PubSubService {
|
||||
}
|
||||
|
||||
private determineViewportAndSegmentationType(csViewport, segmentation) {
|
||||
const isVolumeViewport =
|
||||
csViewport.type === ViewportType.ORTHOGRAPHIC || csViewport.type === ViewportType.VOLUME_3D;
|
||||
const isVolumeViewport = isVolumeViewportType(csViewport);
|
||||
const isVolumeSegmentation = 'volumeId' in segmentation.representationData[LABELMAP];
|
||||
return { isVolumeViewport, isVolumeSegmentation };
|
||||
}
|
||||
@@ -1623,7 +1626,7 @@ class SegmentationService extends PubSubService {
|
||||
}
|
||||
|
||||
private async handleVolumeViewportCase(csViewport, segmentation, isVolumeSegmentation) {
|
||||
if (csViewport.type === ViewportType.VOLUME_3D) {
|
||||
if (isVolume3DViewportType(csViewport)) {
|
||||
return {
|
||||
representationTypeToUse: SURFACE,
|
||||
isConverted: false,
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import {
|
||||
} from '@cornerstonejs/tools';
|
||||
|
||||
import { isAnyDisplaySetCommon } from '../../utils/isAnyDisplaySetCommon';
|
||||
import { isVolume3DViewportType } from '../../utils/getLegacyViewportType';
|
||||
|
||||
const { createSynchronizer } = SynchronizerManager;
|
||||
const { SEGMENTATION_REPRESENTATION_MODIFIED } = Enums.Events;
|
||||
@@ -89,7 +90,7 @@ const segmentationRepresentationModifiedCallback = async (
|
||||
|
||||
// Ensure the segmentation representation aligns with the target viewport type.
|
||||
const type: Enums.SegmentationRepresentations =
|
||||
viewport.type === CoreEnums.ViewportType.VOLUME_3D
|
||||
isVolume3DViewportType(viewport)
|
||||
? Enums.SegmentationRepresentations.Surface
|
||||
: ((segmentationRepresentationType as Enums.SegmentationRepresentations) ??
|
||||
Enums.SegmentationRepresentations.Labelmap);
|
||||
|
||||
@@ -3,16 +3,11 @@ import { PubSubService } from '@ohif/core';
|
||||
import { Types as OhifTypes } from '@ohif/core';
|
||||
import {
|
||||
RenderingEngine,
|
||||
StackViewport,
|
||||
Types,
|
||||
getRenderingEngine,
|
||||
utilities as csUtils,
|
||||
VolumeViewport,
|
||||
VolumeViewport3D,
|
||||
ECGViewport,
|
||||
cache,
|
||||
Enums as csEnums,
|
||||
BaseVolumeViewport,
|
||||
} from '@cornerstonejs/core';
|
||||
|
||||
import { utilities as csToolsUtils, Enums as csToolsEnums } from '@cornerstonejs/tools';
|
||||
@@ -39,6 +34,12 @@ import { usePositionPresentationStore } from '../../stores/usePositionPresentati
|
||||
import { useSynchronizersStore } from '../../stores/useSynchronizersStore';
|
||||
import { useSegmentationPresentationStore } from '../../stores/useSegmentationPresentationStore';
|
||||
import getClosestOrientationFromIOP from '../../utils/isReferenceViewable';
|
||||
import {
|
||||
getLegacyViewportType,
|
||||
isStackViewportType,
|
||||
isVolume3DViewportType,
|
||||
isVolumeViewportType,
|
||||
} from '../../utils/getLegacyViewportType';
|
||||
import { BlendModes } from '@cornerstonejs/core/enums';
|
||||
|
||||
const EVENTS = {
|
||||
@@ -49,10 +50,19 @@ const EVENTS = {
|
||||
const MIN_STACK_VIEWPORTS_TO_ENQUEUE_RESIZE = 12;
|
||||
const MIN_VOLUME_VIEWPORTS_TO_ENQUEUE_RESIZE = 6;
|
||||
|
||||
// Actor class names that represent a primary volume binding in a viewport.
|
||||
// Legacy ORTHOGRAPHIC / VOLUME_3D render the source volume as a 'vtkVolume'
|
||||
// actor. Under the GenericViewport-backed compat path (useGenericViewport),
|
||||
// the MPR slice render path emits 'vtkImageSlice' actors instead, so the
|
||||
// volume-set-vs-add optimization below must recognize both. (3D still uses
|
||||
// 'vtkVolume'.) Without this, the optimization silently no-ops and a full
|
||||
// setVolumes() teardown blanks MPR during SEG hydration.
|
||||
const VOLUME_ACTOR_CLASS_NAMES = new Set(['vtkVolume', 'vtkImageSlice']);
|
||||
|
||||
function getVolumeActorReferencedIds(viewport: Types.IVolumeViewport): string[] {
|
||||
const actors = viewport.getActors?.() ?? [];
|
||||
return actors
|
||||
.filter(ac => ac.actor?.getClassName?.() === 'vtkVolume')
|
||||
.filter(ac => VOLUME_ACTOR_CLASS_NAMES.has(ac.actor?.getClassName?.() ?? ''))
|
||||
.map(ac => ac.referencedId)
|
||||
.filter(Boolean) as string[];
|
||||
}
|
||||
@@ -81,7 +91,7 @@ function viewportMatchesDesiredVolumePresentation(
|
||||
desiredViewportInfo: ViewportInfo
|
||||
): boolean {
|
||||
const desiredType = desiredViewportInfo.getViewportType();
|
||||
if (viewport.type !== desiredType) {
|
||||
if (getLegacyViewportType(viewport) !== desiredType) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
@@ -370,7 +380,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
|
||||
return {
|
||||
viewportType: viewportInfo.getViewportType(),
|
||||
viewReference: csViewport instanceof VolumeViewport3D ? null : csViewport.getViewReference(),
|
||||
viewReference: isVolume3DViewportType(csViewport) ? null : csViewport.getViewReference(),
|
||||
viewPresentation: csViewport.getViewPresentation({ pan: true, zoom: true }),
|
||||
viewportId,
|
||||
};
|
||||
@@ -398,10 +408,9 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return properties;
|
||||
};
|
||||
|
||||
const properties =
|
||||
csViewport instanceof BaseVolumeViewport
|
||||
? new Map()
|
||||
: cleanProperties(csViewport.getProperties());
|
||||
const properties = isVolumeViewportType(csViewport)
|
||||
? new Map()
|
||||
: cleanProperties(csViewport.getProperties());
|
||||
|
||||
if (properties instanceof Map) {
|
||||
const volumeIds = (csViewport as Types.IBaseVolumeViewport).getAllVolumeIds();
|
||||
@@ -840,10 +849,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
_presentations: Presentations = {}
|
||||
): Promise<void> {
|
||||
const [displaySet] = viewportData.data;
|
||||
return viewport.setDataIds(displaySet.imageIds, {
|
||||
groupId: displaySet.displaySetInstanceUID,
|
||||
viewReference: viewportInfo.getViewReference(),
|
||||
});
|
||||
// CS3D's "redo viewports" replaced setDataIds with the generic
|
||||
// setDisplaySets({ displaySetId }) API; the legacy adapters key off
|
||||
// imageIds[0] as the displaySetId, so do the same here.
|
||||
await viewport.setDisplaySets({ displaySetId: displaySet.imageIds[0] });
|
||||
const viewReference = viewportInfo.getViewReference();
|
||||
if (viewReference) {
|
||||
viewport.setViewReference(viewReference);
|
||||
}
|
||||
}
|
||||
|
||||
private async _setStackViewport(
|
||||
@@ -1141,7 +1154,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
throw new Error('Background display set not found');
|
||||
}
|
||||
|
||||
if (viewport.type === csEnums.ViewportType.VOLUME_3D) {
|
||||
if (isVolume3DViewportType(viewport)) {
|
||||
timeoutViewportCallback(() => {
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId: viewport.id,
|
||||
@@ -1311,7 +1324,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
|
||||
let displaySetPromise;
|
||||
|
||||
if (viewport instanceof VolumeViewport || viewport instanceof VolumeViewport3D) {
|
||||
if (isVolumeViewportType(viewport)) {
|
||||
displaySetPromise = this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => {
|
||||
if (keepCamera) {
|
||||
viewport.setCamera(viewportCamera);
|
||||
@@ -1320,7 +1333,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
});
|
||||
}
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
if (isStackViewportType(viewport)) {
|
||||
displaySetPromise = this._setStackViewport(viewport, viewportData, viewportInfo);
|
||||
}
|
||||
|
||||
@@ -1338,7 +1351,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
viewportInfo: ViewportInfo,
|
||||
presentations: Presentations = {}
|
||||
): Promise<void> {
|
||||
if (viewport instanceof StackViewport) {
|
||||
if (isStackViewportType(viewport)) {
|
||||
return this._setStackViewport(
|
||||
viewport,
|
||||
viewportData as StackViewportData,
|
||||
@@ -1347,7 +1360,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
);
|
||||
}
|
||||
|
||||
if ([VolumeViewport, VolumeViewport3D].some(type => viewport instanceof type)) {
|
||||
if (isVolumeViewportType(viewport)) {
|
||||
return this._setVolumeViewport(
|
||||
viewport as Types.IVolumeViewport,
|
||||
viewportData as VolumeViewportData,
|
||||
@@ -1356,7 +1369,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
);
|
||||
}
|
||||
|
||||
if (viewport instanceof ECGViewport) {
|
||||
if (getLegacyViewportType(viewport) === csEnums.ViewportType.ECG) {
|
||||
return this._setEcgViewport(
|
||||
viewport as unknown as Types.IECGViewport,
|
||||
viewportData as StackViewportData
|
||||
@@ -1508,7 +1521,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
const { properties } = lutPresentation;
|
||||
if (viewport instanceof BaseVolumeViewport) {
|
||||
if (isVolumeViewportType(viewport)) {
|
||||
if (properties instanceof Map) {
|
||||
properties.forEach((propertiesEntry, volumeId) => {
|
||||
viewport.setProperties(propertiesEntry, volumeId);
|
||||
@@ -1557,7 +1570,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const { segmentationId, type, hydrated } = presentationItem;
|
||||
|
||||
const { Labelmap, Surface } = csToolsEnums.SegmentationRepresentations;
|
||||
const isVolume3D = viewport.type === csEnums.ViewportType.VOLUME_3D;
|
||||
const isVolume3D = isVolume3DViewportType(viewport);
|
||||
|
||||
// Determine the appropriate segmentation representation for the viewport.
|
||||
// If the current type is Surface but the viewport is not 3D, fallback to Labelmap.
|
||||
|
||||
@@ -2,10 +2,10 @@ import {
|
||||
Types,
|
||||
Enums,
|
||||
getEnabledElementByViewportId,
|
||||
VolumeViewport,
|
||||
utilities,
|
||||
} from '@cornerstonejs/core';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
import { isOrthographicViewportType } from '../../utils/getLegacyViewportType';
|
||||
import getCornerstoneBlendMode from '../../utils/getCornerstoneBlendMode';
|
||||
import getCornerstoneOrientation from '../../utils/getCornerstoneOrientation';
|
||||
import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType';
|
||||
@@ -105,7 +105,7 @@ const dataContains = ({ data, displaySetUID, imageId, viewport }): boolean => {
|
||||
return !!data.imageIds.find(dataId => dataId === imageId);
|
||||
}
|
||||
|
||||
if (imageId && (data.volumeId || viewport instanceof VolumeViewport)) {
|
||||
if (imageId && (data.volumeId || isOrthographicViewportType(viewport))) {
|
||||
const isAcquisition = !!viewport.getCurrentImageId();
|
||||
|
||||
if (!isAcquisition) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { VolumeViewport, metaData, utilities } from '@cornerstonejs/core';
|
||||
import { metaData, utilities } from '@cornerstonejs/core';
|
||||
import { IStackViewport, IVolumeViewport } from '@cornerstonejs/core/types';
|
||||
import { AnnotationDisplayTool, drawing } from '@cornerstonejs/tools';
|
||||
import { guid, b64toBlob } from '@ohif/core/src/utils';
|
||||
import OverlayPlaneModuleProvider from './OverlayPlaneModuleProvider';
|
||||
import { isOrthographicViewportType } from '../utils/getLegacyViewportType';
|
||||
|
||||
interface CachedStat {
|
||||
color: number[]; // [r, g, b, a]
|
||||
@@ -49,7 +50,7 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
|
||||
onSetToolDisabled = (): void => {};
|
||||
|
||||
protected getReferencedImageId(viewport: IStackViewport | IVolumeViewport): string {
|
||||
if (viewport instanceof VolumeViewport) {
|
||||
if (isOrthographicViewportType(viewport)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { utils } from '@ohif/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import html2canvas from 'html2canvas';
|
||||
import { getEnabledElement, StackViewport, BaseVolumeViewport } from '@cornerstonejs/core';
|
||||
import { getEnabledElement } from '@cornerstonejs/core';
|
||||
import { ToolGroupManager, segmentation, Enums } from '@cornerstonejs/tools';
|
||||
import { getEnabledElement as OHIFgetEnabledElement } from '../state';
|
||||
import { isStackViewportType, isVolumeViewportType } from './getLegacyViewportType';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
const { downloadUrl } = utils;
|
||||
@@ -127,10 +128,10 @@ const CornerstoneViewportDownloadForm = ({
|
||||
const viewPresentation = viewport.getViewPresentation?.();
|
||||
const viewRef = viewport.getViewReference?.();
|
||||
|
||||
if (downloadViewport instanceof StackViewport) {
|
||||
if (isStackViewportType(downloadViewport)) {
|
||||
const imageId = viewport.getCurrentImageId();
|
||||
await downloadViewport.setStack([imageId]);
|
||||
} else if (downloadViewport instanceof BaseVolumeViewport) {
|
||||
} else if (isVolumeViewportType(downloadViewport)) {
|
||||
const volumeIds = viewport.getAllVolumeIds();
|
||||
await downloadViewport.setVolumes([{ volumeId: volumeIds[0] }]);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ type CreateSegmentationForViewportParams = {
|
||||
export async function createSegmentationForViewport(
|
||||
servicesManager: ServicesManager,
|
||||
{ viewportId, options = {}, segmentationType }: CreateSegmentationForViewportParams
|
||||
): Promise<string> {
|
||||
): Promise<string | undefined> {
|
||||
const { viewportGridService, displaySetService, segmentationService } = servicesManager.services;
|
||||
const { viewports } = viewportGridService.getState();
|
||||
const targetViewportId = viewportId;
|
||||
@@ -44,7 +44,15 @@ export async function createSegmentationForViewport(
|
||||
const viewport = viewports.get(targetViewportId);
|
||||
|
||||
// Todo: add support for multiple display sets
|
||||
const displaySetInstanceUID = options.displaySetInstanceUID || viewport.displaySetInstanceUIDs[0];
|
||||
// Guard against the grid not having this viewport yet (a transient race when
|
||||
// "Add segmentation" runs before the viewport is registered in the grid). Bail
|
||||
// gracefully instead of throwing, which would surface an uncaught error overlay.
|
||||
const displaySetInstanceUID =
|
||||
options.displaySetInstanceUID || viewport?.displaySetInstanceUIDs?.[0];
|
||||
|
||||
if (!displaySetInstanceUID) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const segs = segmentationService.getSegmentations();
|
||||
|
||||
|
||||
@@ -30,10 +30,14 @@ const mockDocument = {
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(global, 'document', {
|
||||
writable: true,
|
||||
value: mockDocument,
|
||||
});
|
||||
// jsdom (jest 30) makes the global `document` non-configurable, so it can't be
|
||||
// replaced wholesale. Route the methods the code under test uses onto the real
|
||||
// jsdom document; assertions still target mockDocument's jest.fn() references.
|
||||
const doc = document as unknown as typeof mockDocument;
|
||||
doc.addEventListener = mockDocument.addEventListener;
|
||||
doc.createElement = mockDocument.createElement;
|
||||
document.body.appendChild = mockDocument.body.appendChild as typeof document.body.appendChild;
|
||||
document.body.removeChild = mockDocument.body.removeChild as typeof document.body.removeChild;
|
||||
|
||||
describe('generateSegmentationCSVReport', () => {
|
||||
const mockInfo = {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Enums as csEnums, Types as csTypes } from '@cornerstonejs/core';
|
||||
|
||||
const { ViewportType } = csEnums;
|
||||
|
||||
type ViewportLike = {
|
||||
requestedType?: csEnums.ViewportType;
|
||||
type?: csEnums.ViewportType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the legacy/requested viewport type, transparent across GenericViewport
|
||||
* compatibility remapping.
|
||||
*
|
||||
* When `rendering.useGenericViewport` is enabled, a viewport requested as
|
||||
* `STACK` or `ORTHOGRAPHIC` has runtime `viewport.type === PLANAR_NEXT`, and a
|
||||
* `VOLUME_3D` request has `VOLUME_3D_NEXT`. The rendering engine records the
|
||||
* original request on `viewport.requestedType`, which this reads. Falls back to
|
||||
* `viewport.type` for legacy/non-remapped viewports (and older cornerstone builds
|
||||
* that do not populate `requestedType`).
|
||||
*
|
||||
* Use this instead of `viewport instanceof StackViewport/VolumeViewport/...` or
|
||||
* raw `viewport.type === ViewportType.X` when branching on the legacy viewport
|
||||
* type. For "does this viewport support operation X" questions, prefer the
|
||||
* cornerstone capability guards (`utilities.viewportSupports*`) instead.
|
||||
*/
|
||||
export function getLegacyViewportType(
|
||||
viewport: unknown
|
||||
): csEnums.ViewportType | undefined {
|
||||
const vp = viewport as ViewportLike | null | undefined;
|
||||
return vp?.requestedType ?? vp?.type;
|
||||
}
|
||||
|
||||
/** Legacy STACK viewport (image stack). Replaces `instanceof StackViewport`. */
|
||||
export function isStackViewportType(
|
||||
viewport: unknown
|
||||
): viewport is csTypes.IStackViewport {
|
||||
return getLegacyViewportType(viewport) === ViewportType.STACK;
|
||||
}
|
||||
|
||||
/** Legacy ORTHOGRAPHIC (MPR) viewport. Replaces `instanceof VolumeViewport`. */
|
||||
export function isOrthographicViewportType(
|
||||
viewport: unknown
|
||||
): viewport is csTypes.IVolumeViewport {
|
||||
return getLegacyViewportType(viewport) === ViewportType.ORTHOGRAPHIC;
|
||||
}
|
||||
|
||||
/** Legacy VOLUME_3D viewport. Replaces `instanceof VolumeViewport3D`. */
|
||||
export function isVolume3DViewportType(
|
||||
viewport: unknown
|
||||
): viewport is csTypes.IVolumeViewport {
|
||||
return getLegacyViewportType(viewport) === ViewportType.VOLUME_3D;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy ORTHOGRAPHIC or VOLUME_3D viewport (i.e. a `BaseVolumeViewport`).
|
||||
* Replaces `instanceof BaseVolumeViewport`.
|
||||
*/
|
||||
export function isVolumeViewportType(
|
||||
viewport: unknown
|
||||
): viewport is csTypes.IVolumeViewport {
|
||||
const legacyType = getLegacyViewportType(viewport);
|
||||
return (
|
||||
legacyType === ViewportType.ORTHOGRAPHIC ||
|
||||
legacyType === ViewportType.VOLUME_3D
|
||||
);
|
||||
}
|
||||
@@ -32,8 +32,8 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "4.22.10",
|
||||
"@cornerstonejs/tools": "4.22.10",
|
||||
"@cornerstonejs/core": "5.0.0",
|
||||
"@cornerstonejs/tools": "5.0.0",
|
||||
"@ohif/core": "3.13.0-beta.88",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.88",
|
||||
"@ohif/extension-default": "3.13.0-beta.88",
|
||||
|
||||
@@ -362,9 +362,7 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
|
||||
}
|
||||
|
||||
const referencedVolume =
|
||||
csTools.utilities.segmentation.getReferenceVolumeForSegmentationVolume(
|
||||
labelmapVolume.volumeId
|
||||
);
|
||||
csTools.utilities.segmentation.getReferenceVolumeForSegmentation(segmentationId);
|
||||
|
||||
if (!referencedVolume) {
|
||||
report[id] = segReport;
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.29.7",
|
||||
"@cornerstonejs/core": "4.22.10",
|
||||
"@cornerstonejs/tools": "4.22.10",
|
||||
"@cornerstonejs/core": "5.0.0",
|
||||
"@cornerstonejs/tools": "5.0.0",
|
||||
"@ohif/core": "3.13.0-beta.88",
|
||||
"@ohif/extension-cornerstone": "3.13.0-beta.88",
|
||||
"@ohif/extension-default": "3.13.0-beta.88",
|
||||
|
||||
Reference in new issue
Block a user