diff --git a/extensions/cornerstone-dicom-pmap/package.json b/extensions/cornerstone-dicom-pmap/package.json index d9347417e..39aba2c9d 100644 --- a/extensions/cornerstone-dicom-pmap/package.json +++ b/extensions/cornerstone-dicom-pmap/package.json @@ -46,8 +46,8 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^2.1.17", - "@cornerstonejs/core": "^2.1.17", + "@cornerstonejs/adapters": "^2.2.1", + "@cornerstonejs/core": "^2.2.1", "@kitware/vtk.js": "32.1.0", "react-color": "^2.19.3" } diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json index 27fc51486..8d5d9fe5e 100644 --- a/extensions/cornerstone-dicom-seg/package.json +++ b/extensions/cornerstone-dicom-seg/package.json @@ -46,8 +46,8 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^2.1.17", - "@cornerstonejs/core": "^2.1.17", + "@cornerstonejs/adapters": "^2.2.1", + "@cornerstonejs/core": "^2.2.1", "@kitware/vtk.js": "32.1.0", "react-color": "^2.19.3" } diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx index bec52996b..21dff244f 100644 --- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx +++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx @@ -4,7 +4,6 @@ import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } f import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup'; import promptHydrateSEG from '../utils/promptHydrateSEG'; import _getStatusComponent from './_getStatusComponent'; -import { useSegmentationPresentationStore } from '@ohif/extension-cornerstone'; import { SegmentationRepresentations } from '@cornerstonejs/tools/enums'; const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup'; @@ -251,6 +250,14 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) { if (toolGroup) { return; } + + // keep the already stored segmentationPresentation for this viewport in memory + // so that we can restore it after hydrating the SEG + commandsManager.runCommand('updateStoredSegmentationPresentation', { + displaySet: segDisplaySet, + type: SegmentationRepresentations.Labelmap, + }); + // always start fresh for this viewport since it is special type of viewport // that should only show one segmentation at a time. segmentationService.clearSegmentationRepresentations(viewportId); diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index 331dd9c95..50220a9f7 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -46,9 +46,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^2.1.17", - "@cornerstonejs/core": "^2.1.17", - "@cornerstonejs/tools": "^2.1.17", + "@cornerstonejs/adapters": "^2.2.1", + "@cornerstonejs/core": "^2.2.1", + "@cornerstonejs/tools": "^2.2.1", "classnames": "^2.3.2" } } diff --git a/extensions/cornerstone-dynamic-volume/package.json b/extensions/cornerstone-dynamic-volume/package.json index 42c1cce9e..42312bf2d 100644 --- a/extensions/cornerstone-dynamic-volume/package.json +++ b/extensions/cornerstone-dynamic-volume/package.json @@ -42,8 +42,8 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/core": "^2.1.17", - "@cornerstonejs/tools": "^2.1.17", + "@cornerstonejs/core": "^2.2.1", + "@cornerstonejs/tools": "^2.2.1", "classnames": "^2.3.2" } } diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index c9fb3f5b5..612c9dc70 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -38,7 +38,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjph": "^2.4.5", - "@cornerstonejs/dicom-image-loader": "^2.1.17", + "@cornerstonejs/dicom-image-loader": "^2.2.1", "@icr/polyseg-wasm": "^0.4.0", "@ohif/core": "3.9.0-beta.109", "@ohif/ui": "3.9.0-beta.109", @@ -55,9 +55,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^2.1.17", - "@cornerstonejs/core": "^2.1.17", - "@cornerstonejs/tools": "^2.1.17", + "@cornerstonejs/adapters": "^2.2.1", + "@cornerstonejs/core": "^2.2.1", + "@cornerstonejs/tools": "^2.2.1", "@icr/polyseg-wasm": "^0.4.0", "@kitware/vtk.js": "32.1.0", "html2canvas": "^1.4.1", diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts index e8b2d0b04..857997dee 100644 --- a/extensions/cornerstone/src/commandsModule.ts +++ b/extensions/cornerstone/src/commandsModule.ts @@ -111,16 +111,14 @@ function commandsModule({ commandsManager.run(options, optionsToUse); }, updateStoredSegmentationPresentation: ({ displaySet, type }) => { - const { setSegmentationPresentation } = useSegmentationPresentationStore.getState(); + const { addSegmentationPresentationItem } = useSegmentationPresentationStore.getState(); const referencedDisplaySetInstanceUID = displaySet.referencedDisplaySetInstanceUID; - setSegmentationPresentation(referencedDisplaySetInstanceUID, [ - { - segmentationId: displaySet.displaySetInstanceUID, - hydrated: true, - type, - }, - ]); + addSegmentationPresentationItem(referencedDisplaySetInstanceUID, { + segmentationId: displaySet.displaySetInstanceUID, + hydrated: true, + type, + }); }, updateStoredPositionPresentation: ({ viewportId, displaySetInstanceUID }) => { const presentations = cornerstoneViewportService.getPresentations(viewportId); @@ -1043,7 +1041,7 @@ function commandsModule({ * @param props.key - The style key to set * @param props.value - The style value */ - setSegmentationStyleCommand: ({ segmentationId, type, key, value }) => { + setSegmentationStyleCommand: ({ type, key, value }) => { const { segmentationService } = servicesManager.services; segmentationService.setStyle({ type }, { [key]: value }); }, @@ -1150,12 +1148,17 @@ function commandsModule({ const segment = segmentation.segments[segmentIndex]; const { label } = segment; - callInputDialog(uiDialogService, label, (label, actionId) => { + const callback = (label, actionId) => { if (label === '') { return; } segmentationService.setSegmentLabel(segmentationId, segmentIndex, label); + }; + + callInputDialog(uiDialogService, label, callback, false, { + dialogTitle: 'Edit Segment Label', + inputLabel: 'Enter new label', }); }, @@ -1169,12 +1172,17 @@ function commandsModule({ const { label } = segmentation; - callInputDialog(uiDialogService, label, (label, actionId) => { + const callback = (label, actionId) => { if (label === '') { return; } segmentationService.addOrUpdateSegmentation({ segmentationId, label }); + }; + + callInputDialog(uiDialogService, label, callback, false, { + dialogTitle: 'Edit Segmentation Label', + inputLabel: 'Enter new label', }); }, diff --git a/extensions/cornerstone/src/panels/PanelSegmentation.tsx b/extensions/cornerstone/src/panels/PanelSegmentation.tsx index b9e9ffcd2..03109fc5c 100644 --- a/extensions/cornerstone/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone/src/panels/PanelSegmentation.tsx @@ -6,8 +6,6 @@ import { metaData } from '@cornerstonejs/core'; export default function PanelSegmentation({ servicesManager, commandsManager, - extensionManager, - configuration, children, }: withAppTypes) { const { customizationService, viewportGridService, displaySetService } = servicesManager.services; @@ -91,23 +89,23 @@ export default function PanelSegmentation({ commandsManager.run('deleteSegmentation', { segmentationId }); }, - setFillAlpha: (type, value) => { + setFillAlpha: ({ type }, value) => { commandsManager.run('setFillAlpha', { type, value }); }, - setOutlineWidth: (type, value) => { + setOutlineWidth: ({ type }, value) => { commandsManager.run('setOutlineWidth', { type, value }); }, - setRenderFill: (type, value) => { + setRenderFill: ({ type }, value) => { commandsManager.run('setRenderFill', { type, value }); }, - setRenderOutline: (type, value) => { + setRenderOutline: ({ type }, value) => { commandsManager.run('setRenderOutline', { type, value }); }, - setFillAlphaInactive: (type, value) => { + setFillAlphaInactive: ({ type }, value) => { commandsManager.run('setFillAlphaInactive', { type, value }); }, @@ -152,6 +150,14 @@ export default function PanelSegmentation({ const exportOptions = segmentationsWithRepresentations.map(({ segmentation }) => { const { representationData, segmentationId } = segmentation; const { Labelmap } = representationData; + + if (!Labelmap) { + return { + segmentationId, + isExportable: true, + }; + } + const referencedImageIds = Labelmap.referencedImageIds; const firstImageId = referencedImageIds[0]; diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts index 40825c208..45b1dbd16 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts @@ -100,10 +100,16 @@ class SegmentationService extends PubSubService { this._segmentationIdToColorLUTIndexMap = new Map(); this.servicesManager = servicesManager; + } + public onModeEnter(): void { this._initSegmentationService(); } + public onModeExit(): void { + this.destroy(); + } + /** * Retrieves a segmentation by its ID. * @@ -1386,7 +1392,11 @@ class SegmentationService extends PubSubService { const frameOfReferenceUID = viewport.getFrameOfReferenceUID(); const segImage = cache.getImage(imageIds[0]); - if (segImage?.FrameOfReferenceUID === frameOfReferenceUID) { + if ( + segImage?.FrameOfReferenceUID && + frameOfReferenceUID && + segImage.FrameOfReferenceUID === frameOfReferenceUID + ) { const isConverted = await this.convertStackToVolumeViewport(viewport); triggerSegmentationRepresentationModified( viewportId, diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts index 75fd18a83..51c7ce3fe 100644 --- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -763,6 +763,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi }); } + if (!volumesNotLoaded.length) { + return; + } + // This returns the async continuation only return this.setVolumesForViewport(viewport, volumeInputArray, presentations); } @@ -1114,8 +1118,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi const { segmentationService } = this.servicesManager.services; - segmentationPresentation.forEach((presentation: SegmentationPresentationItem) => { - const { segmentationId, type, hydrated } = presentation; + segmentationPresentation.forEach((presentationItem: SegmentationPresentationItem) => { + const { segmentationId, type, hydrated } = presentationItem; if (hydrated) { segmentationService.addSegmentationRepresentation(viewport.id, { diff --git a/extensions/cornerstone/src/stores/useSegmentationPresentationStore.ts b/extensions/cornerstone/src/stores/useSegmentationPresentationStore.ts index 60cbb8fe0..8bdbb9e06 100644 --- a/extensions/cornerstone/src/stores/useSegmentationPresentationStore.ts +++ b/extensions/cornerstone/src/stores/useSegmentationPresentationStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; -import { SegmentationPresentation } from '../types/Presentation'; +import { SegmentationPresentation, SegmentationPresentationItem } from '../types/Presentation'; import { JOIN_STR } from './presentationUtils'; import { getViewportOrientationFromImageOrientationPatient } from '../utils/getViewportOrientationFromImageOrientationPatient'; @@ -62,10 +62,9 @@ type SegmentationPresentationStore = { * @param segmentationPresentation - The `SegmentationPresentation` to add. * @param servicesManager - The services manager instance. */ - addSegmentationPresentation: ( + addSegmentationPresentationItem: ( presentationId: string, - segmentationPresentation: SegmentationPresentation, - { servicesManager }: { servicesManager: AppTypes.ServicesManager } + segmentationPresentationItem: SegmentationPresentationItem ) => void; /** @@ -183,33 +182,52 @@ const createSegmentationPresentationStore = set => ({ set({ segmentationPresentationStore: {} }, false, 'clearSegmentationPresentationStore'), /** - * Adds a new segmentation presentation to the store. + * Adds a new segmentation presentation item to the store. + * + * segmentationPresentationItem: { + * segmentationId: string; + * type: SegmentationRepresentations; + * hydrated: boolean | null; + * config?: unknown; + * } */ - addSegmentationPresentation: ( + addSegmentationPresentationItem: ( presentationId: string, - segmentationPresentation: SegmentationPresentation, - { servicesManager }: { servicesManager: AppTypes.ServicesManager } + segmentationPresentationItem: SegmentationPresentationItem ) => set( state => ({ segmentationPresentationStore: { ...state.segmentationPresentationStore, - [presentationId]: segmentationPresentation, + [presentationId]: [ + ...(state.segmentationPresentationStore[presentationId] || []), + segmentationPresentationItem, + ], }, }), false, - 'addSegmentationPresentation' + 'addSegmentationPresentationItem' ), /** - * Sets the segmentation presentation for a given presentation ID. + * Sets the segmentation presentation for a given presentation ID. A segmentation + * presentation is an array of SegmentationPresentationItem. + * + * segmentationPresentationItem: { + * segmentationId: string; + * type: SegmentationRepresentations; + * hydrated: boolean | null; + * config?: unknown; + * } + * + * segmentationPresentation: SegmentationPresentationItem[] */ - setSegmentationPresentation: (presentationId: string, value: SegmentationPresentation) => + setSegmentationPresentation: (presentationId: string, values: SegmentationPresentation) => set( state => ({ segmentationPresentationStore: { ...state.segmentationPresentationStore, - [presentationId]: value, + [presentationId]: values, }, }), false, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getValueDisplayString.js b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getValueDisplayString.js index aa0fe316c..8afadc0f6 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getValueDisplayString.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getValueDisplayString.js @@ -4,9 +4,9 @@ import getDisplayUnit from './getDisplayUnit'; export const getStatisticDisplayString = (numbers, unit, key) => { if (Array.isArray(numbers) && numbers.length > 0) { const results = numbers.map(number => utils.roundNumber(number, 2)); - return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${results.join(', ')} ${getDisplayUnit(unit)}`; + return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${results.join(', ')} ${getDisplayUnit(unit)}`; } const result = utils.roundNumber(numbers, 2); - return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${result} ${getDisplayUnit(unit)}`; + return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${result} ${getDisplayUnit(unit)}`; }; diff --git a/extensions/default/src/Components/SidePanelWithServices.tsx b/extensions/default/src/Components/SidePanelWithServices.tsx index 26bed59ef..c4fd32a1c 100644 --- a/extensions/default/src/Components/SidePanelWithServices.tsx +++ b/extensions/default/src/Components/SidePanelWithServices.tsx @@ -23,16 +23,26 @@ const SidePanelWithServices = ({ // Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM. // Thus going to the Study List page and back to the viewer resets this flag for a SidePanel. - const [hasBeenOpened, setHasBeenOpened] = useState(false); + const [sidePanelOpen, setSidePanelOpen] = useState(activeTabIndexProp !== null); const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp); const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side)); - const handleSidePanelOpen = useCallback(() => { - setHasBeenOpened(true); - }, []); - const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => { setActiveTabIndex(activeTabIndex); + setSidePanelOpen(activeTabIndex !== null); + }, []); + + const handleOpen = useCallback(() => { + setSidePanelOpen(true); + // If panel is being opened but no tab is active, set first tab as active + if (activeTabIndex === null && tabs.length > 0) { + setActiveTabIndex(0); + } + }, [activeTabIndex, tabs]); + + const handleClose = useCallback(() => { + setSidePanelOpen(false); + setActiveTabIndex(null); }, []); /** update the active tab index from outside */ @@ -61,7 +71,7 @@ const SidePanelWithServices = ({ const activatePanelSubscription = panelService.subscribe( panelService.EVENTS.ACTIVATE_PANEL, (activatePanelEvent: Types.ActivatePanelEvent) => { - if (!hasBeenOpened || activatePanelEvent.forceActive) { + if (sidePanelOpen || activatePanelEvent.forceActive) { const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId); if (tabIndex !== -1) { setActiveTabIndex(tabIndex); @@ -73,7 +83,7 @@ const SidePanelWithServices = ({ return () => { activatePanelSubscription.unsubscribe(); }; - }, [tabs, hasBeenOpened, panelService]); + }, [tabs, sidePanelOpen, panelService]); return ( + /> ); }; diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index db06593a3..56aebc011 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -32,8 +32,8 @@ "start": "yarn run dev" }, "peerDependencies": { - "@cornerstonejs/core": "^2.1.17", - "@cornerstonejs/tools": "^2.1.17", + "@cornerstonejs/core": "^2.2.1", + "@cornerstonejs/tools": "^2.2.1", "@ohif/core": "3.9.0-beta.109", "@ohif/extension-cornerstone-dicom-sr": "3.9.0-beta.109", "@ohif/extension-default": "3.9.0-beta.109", diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx index be40e23b5..36ef98631 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx @@ -98,6 +98,9 @@ function TrackedCornerstoneViewport( useEffect(() => { if (isTracked) { annotation.config.style.setViewportToolStyles(viewportId, { + ReferenceLines: { + lineDash: '4,4', + }, global: { lineDash: '', }, diff --git a/modes/basic-dev-mode/src/toolbarButtons.js b/modes/basic-dev-mode/src/toolbarButtons.js index e11c15a25..fd55f7e01 100644 --- a/modes/basic-dev-mode/src/toolbarButtons.js +++ b/modes/basic-dev-mode/src/toolbarButtons.js @@ -140,7 +140,13 @@ const toolbarButtons: Button[] = [ context: 'CORNERSTONE', }, ], - evaluate: 'evaluate.action', + evaluate: [ + 'evaluate.action', + { + name: 'evaluate.viewport.supported', + unsupportedViewportTypes: ['video', 'wholeSlide'], + }, + ], }, }, { diff --git a/modes/basic-test-mode/src/toolbarButtons.ts b/modes/basic-test-mode/src/toolbarButtons.ts index 59e5bf59c..5b5d59290 100644 --- a/modes/basic-test-mode/src/toolbarButtons.ts +++ b/modes/basic-test-mode/src/toolbarButtons.ts @@ -213,7 +213,13 @@ const toolbarButtons: Button[] = [ icon: 'tool-capture', label: 'Capture', commands: 'showDownloadViewportModal', - evaluate: 'evaluate.action', + evaluate: [ + 'evaluate.action', + { + name: 'evaluate.viewport.supported', + unsupportedViewportTypes: ['video', 'wholeSlide'], + }, + ], }, }, { diff --git a/modes/longitudinal/src/index.ts b/modes/longitudinal/src/index.ts index 7ce082c77..1e1617eff 100644 --- a/modes/longitudinal/src/index.ts +++ b/modes/longitudinal/src/index.ts @@ -85,8 +85,14 @@ function modeFactory({ modeConfiguration }) { * Lifecycle hooks */ onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) { - const { measurementService, toolbarService, toolGroupService, customizationService } = - servicesManager.services; + const { + measurementService, + toolbarService, + toolGroupService, + customizationService, + panelService, + segmentationService, + } = servicesManager.services; measurementService.clearMeasurements(); @@ -110,25 +116,23 @@ function modeFactory({ modeConfiguration }) { // // ActivatePanel event trigger for when a segmentation or measurement is added. // // Do not force activation so as to respect the state the user may have left the UI in. - // _activatePanelTriggersSubscriptions = [ - // ...panelService.addActivatePanelTriggers(dicomSeg.panel, [ - // { - // sourcePubSubService: segmentationService, - // sourceEvents: [ - // segmentationService.EVENTS.SEGMENTATION_PIXEL_DATA_CREATED, - // ], - // }, - // ]), - // ...panelService.addActivatePanelTriggers(tracked.measurements, [ - // { - // sourcePubSubService: measurementService, - // sourceEvents: [ - // measurementService.EVENTS.MEASUREMENT_ADDED, - // measurementService.EVENTS.RAW_MEASUREMENT_ADDED, - // ], - // }, - // ]), - // ]; + _activatePanelTriggersSubscriptions = [ + ...panelService.addActivatePanelTriggers(cornerstone.segmentation, [ + { + sourcePubSubService: segmentationService, + sourceEvents: [segmentationService.EVENTS.SEGMENTATION_ADDED], + }, + ]), + ...panelService.addActivatePanelTriggers(tracked.measurements, [ + { + sourcePubSubService: measurementService, + sourceEvents: [ + measurementService.EVENTS.MEASUREMENT_ADDED, + measurementService.EVENTS.RAW_MEASUREMENT_ADDED, + ], + }, + ]), + ]; }, onModeExit: ({ servicesManager }: withAppTypes) => { const { diff --git a/modes/longitudinal/src/toolbarButtons.ts b/modes/longitudinal/src/toolbarButtons.ts index 145e98439..08fe2598c 100644 --- a/modes/longitudinal/src/toolbarButtons.ts +++ b/modes/longitudinal/src/toolbarButtons.ts @@ -172,7 +172,7 @@ const toolbarButtons: Button[] = [ 'evaluate.action', { name: 'evaluate.viewport.supported', - unsupportedViewportTypes: ['video'], + unsupportedViewportTypes: ['video', 'wholeSlide'], }, ], }, diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx index 9ad808bd2..7435dea96 100644 --- a/modes/microscopy/src/index.tsx +++ b/modes/microscopy/src/index.tsx @@ -114,8 +114,7 @@ function modeFactory({ modeConfiguration }) { }, ], extensions: extensionDependencies, - hangingProtocol: ['default'], - + hangingProtocol: 'default', sopClassHandlers: [ '@ohif/extension-cornerstone.sopClassHandlerModule.DicomMicroscopySopClassHandler', '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler', diff --git a/modes/preclinical-4d/src/toolbarButtons.tsx b/modes/preclinical-4d/src/toolbarButtons.tsx index 3c6fc47b5..eaabaea0b 100644 --- a/modes/preclinical-4d/src/toolbarButtons.tsx +++ b/modes/preclinical-4d/src/toolbarButtons.tsx @@ -114,7 +114,13 @@ const toolbarButtons = [ icon: 'tool-capture', label: 'Capture', commands: 'showDownloadViewportModal', - evaluate: 'evaluate.action', + evaluate: [ + 'evaluate.action', + { + name: 'evaluate.viewport.supported', + unsupportedViewportTypes: ['video', 'wholeSlide'], + }, + ], }, }, { diff --git a/modes/segmentation/src/index.tsx b/modes/segmentation/src/index.tsx index 2a53e5998..34e978618 100644 --- a/modes/segmentation/src/index.tsx +++ b/modes/segmentation/src/index.tsx @@ -133,7 +133,7 @@ function modeFactory({ modeConfiguration }) { props: { leftPanels: [ohif.leftPanel], rightPanels: [cornerstone.panelTool], - leftPanelClosed: true, + // leftPanelClosed: true, viewports: [ { namespace: cornerstone.viewport, diff --git a/modes/segmentation/src/toolbarButtons.ts b/modes/segmentation/src/toolbarButtons.ts index 91fc11e20..a59c994eb 100644 --- a/modes/segmentation/src/toolbarButtons.ts +++ b/modes/segmentation/src/toolbarButtons.ts @@ -69,7 +69,13 @@ const toolbarButtons: Button[] = [ icon: 'tool-capture', label: 'Capture', commands: 'showDownloadViewportModal', - evaluate: 'evaluate.action', + evaluate: [ + 'evaluate.action', + { + name: 'evaluate.viewport.supported', + unsupportedViewportTypes: ['video', 'wholeSlide'], + }, + ], }, }, { diff --git a/platform/app/package.json b/platform/app/package.json index d9ff00f32..cd07051ee 100644 --- a/platform/app/package.json +++ b/platform/app/package.json @@ -53,7 +53,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjph": "^2.4.5", - "@cornerstonejs/dicom-image-loader": "^2.1.17", + "@cornerstonejs/dicom-image-loader": "^2.2.1", "@emotion/serialize": "^1.1.3", "@ohif/core": "3.9.0-beta.109", "@ohif/extension-cornerstone": "3.9.0-beta.109", diff --git a/platform/core/package.json b/platform/core/package.json index ecc3bef4f..4f4bcb2c4 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -37,7 +37,7 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjph": "^2.4.5", - "@cornerstonejs/dicom-image-loader": "^2.1.17", + "@cornerstonejs/dicom-image-loader": "^2.2.1", "@ohif/ui": "3.9.0-beta.109", "cornerstone-math": "0.1.9", "dicom-parser": "^1.8.21" diff --git a/platform/core/src/services/CustomizationService/CustomizationService.ts b/platform/core/src/services/CustomizationService/CustomizationService.ts index e4f8a7766..a36104d50 100644 --- a/platform/core/src/services/CustomizationService/CustomizationService.ts +++ b/platform/core/src/services/CustomizationService/CustomizationService.ts @@ -165,6 +165,10 @@ export default class CustomizationService extends PubSubService { this.modeCustomizations.clear(); } + public onModeExit(): void { + this.onModeEnter(); + } + public getModeCustomizations(): Map { return this.modeCustomizations; } diff --git a/platform/core/src/utils/generateAcceptHeader.ts b/platform/core/src/utils/generateAcceptHeader.ts index 7d455441c..c384411a3 100644 --- a/platform/core/src/utils/generateAcceptHeader.ts +++ b/platform/core/src/utils/generateAcceptHeader.ts @@ -9,15 +9,20 @@ const generateAcceptHeader = ( } let acceptHeader = ['multipart/related']; + let hasTransferSyntax = false; if (requestTransferSyntaxUID && typeForTS[requestTransferSyntaxUID]) { const type = typeForTS[requestTransferSyntaxUID]; acceptHeader.push('type=' + type); acceptHeader.push('transfer-syntax=' + requestTransferSyntaxUID); + hasTransferSyntax = true; } else { acceptHeader.push('type=application/octet-stream'); } - acceptHeader.push('transfer-syntax=*'); + if (!hasTransferSyntax) { + acceptHeader.push('transfer-syntax=*'); + } + if (!omitQuotationForMultipartRequest) { //need to add quotation for each mime type of each accept entry acceptHeader = acceptHeader.map(mime => { diff --git a/platform/docs/docs/migration-guide/3p8-to-3p9/0-general.md b/platform/docs/docs/migration-guide/3p8-to-3p9/0-general.md index 8eda1da14..8a96e70e2 100644 --- a/platform/docs/docs/migration-guide/3p8-to-3p9/0-general.md +++ b/platform/docs/docs/migration-guide/3p8-to-3p9/0-general.md @@ -178,6 +178,31 @@ Now: --- +## SidePanel auto switch if open + +In `basic viewer` mode, if the side panel is open and the segmentation panel is active, adding a measurement will automatically switch to the measurement panel. This switch won't occur if the side panel is closed. To enable or disable this feature, adjust your mode configuration accordingly. + +```js +panelService.addActivatePanelTriggers('your.panel.id', [ +{ + sourcePubSubService: segmentationService, + sourceEvents: [segmentationService.EVENTS.SEGMENTATION_ADDED], +}, +]) + +panelService.addActivatePanelTriggers('your.panel.id', [ + { + sourcePubSubService: measurementService, + sourceEvents: [ + measurementService.EVENTS.MEASUREMENT_ADDED, + measurementService.EVENTS.RAW_MEASUREMENT_ADDED, + ], + }, +]) +``` + +--- + ## DicomUpload The DICOM upload functionality in OHIF has been refactored to use the standard customization service pattern. Now you don't need to put diff --git a/platform/ui-next/package.json b/platform/ui-next/package.json index 980c7e765..8d1befd0c 100644 --- a/platform/ui-next/package.json +++ b/platform/ui-next/package.json @@ -53,7 +53,6 @@ "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-shepherd": "6.1.1", - "sanitize-html": "^2.13.1", "shepherd.js": "13.0.3", "sonner": "^1.5.0", "tailwind-merge": "^2.3.0", diff --git a/platform/ui-next/src/components/DataRow/DataRow.tsx b/platform/ui-next/src/components/DataRow/DataRow.tsx index 22ddb98b0..1c83b2f57 100644 --- a/platform/ui-next/src/components/DataRow/DataRow.tsx +++ b/platform/ui-next/src/components/DataRow/DataRow.tsx @@ -8,7 +8,6 @@ import { } from '../../components/DropdownMenu'; import { Icons } from '../../components/Icons/Icons'; import { Tooltip, TooltipTrigger, TooltipContent } from '../../components/Tooltip/Tooltip'; -import sanitizeHtml from 'sanitize-html'; /** * DataRow is a complex UI component that displays a selectable, interactive row with hierarchical data. @@ -131,7 +130,7 @@ const DataRow: React.FC = ({ key={`empty-${indent}`} className="h-2" > - ); // Empty row + ); } const cleanText = decodeHTML(text); return ( @@ -142,11 +141,11 @@ const DataRow: React.FC = ({ {indentation} {cleanText.includes(':') ? ( <> - {sanitizeHtml(cleanText.split(':')[0])}: - {sanitizeHtml(cleanText.split(':')[1])} + {cleanText.split(':')[0]}: + {cleanText.split(':')[1]} > ) : ( - {sanitizeHtml(cleanText)} + {cleanText} )} ); diff --git a/platform/ui-next/src/components/SegmentationTable/AddSegmentRow.tsx b/platform/ui-next/src/components/SegmentationTable/AddSegmentRow.tsx index 5bf9120a8..3da4e253f 100644 --- a/platform/ui-next/src/components/SegmentationTable/AddSegmentRow.tsx +++ b/platform/ui-next/src/components/SegmentationTable/AddSegmentRow.tsx @@ -51,17 +51,19 @@ export const AddSegmentRow: React.FC = ({ children = null, s return ( - {allowAddSegment ? ( - onSegmentAdd(segmentationIdToUse)} - > - - Add Segment - - ) : null} + + {allowAddSegment ? ( + onSegmentAdd(segmentationIdToUse)} + > + + Add Segment + + ) : null} + segmentationId === activeSegmentation.id )?.isExportable; diff --git a/platform/ui-next/src/components/SidePanel/SidePanel.tsx b/platform/ui-next/src/components/SidePanel/SidePanel.tsx index bf35d8e93..08fd63f8c 100644 --- a/platform/ui-next/src/components/SidePanel/SidePanel.tsx +++ b/platform/ui-next/src/components/SidePanel/SidePanel.tsx @@ -149,6 +149,7 @@ const SidePanel = ({ activeTabIndex: activeTabIndexProp = null, tabs, onOpen, + onClose, expandedWidth = 280, onActiveTabIndexChange, }) => { @@ -167,9 +168,11 @@ const SidePanel = ({ setPanelOpen(panelOpen); if (panelOpen && onOpen) { onOpen(); + } else if (onClose && !panelOpen) { + onClose(); } }, - [onOpen] + [onOpen, onClose] ); const updateActiveTabIndex = useCallback( @@ -407,6 +410,7 @@ SidePanel.propTypes = { ), ]), onOpen: PropTypes.func, + onClose: PropTypes.func, onActiveTabIndexChange: PropTypes.func, expandedWidth: PropTypes.number, }; diff --git a/yarn.lock b/yarn.lock index 4bd8d6e6b..b1930a038 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2580,10 +2580,10 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== -"@cornerstonejs/adapters@^2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.1.17.tgz#509e62d500c733ab8babde7a3918548d59d6fa08" - integrity sha512-+eptnnP+C+qKfRh0t0ZoZqQvxtDtwXZzMWk5IzNs/APRPh4XTt/VGh4hWt9UPUwxldG67k/bndjxilQkFUVNkA== +"@cornerstonejs/adapters@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.2.1.tgz#9bed051a7fd9ca5ad5af3ccb97fbb79862feadc1" + integrity sha512-7eIZTN3PO4s95vaLrAAD018bNKsD1w7l0FL+bLv+eqebNHrhBMQQbAfEWuQCueE+tYd1mVDCpNWVDMKMMabHmQ== dependencies: "@babel/runtime-corejs2" "^7.17.8" buffer "^6.0.3" @@ -2616,32 +2616,33 @@ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0" integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg== -"@cornerstonejs/core@^2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.1.17.tgz#3cf4e7b586fccc0cd430947933f69bb08aaa6839" - integrity sha512-rtjRzRlEWk95Q4xlWkUaLitcX9g6bPsGXLASDm2nds8Dd5aEXCnPyEdBo6ow/rhvanohd7I1eC2fK2Wz7WWbJw== +"@cornerstonejs/core@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.2.1.tgz#37cc052d40fffcc093f0f773051787c610d07386" + integrity sha512-5ZZcpa7hU+E45jM4MvEJrIFA+WHFaLWIVh9FO1HksJXtkIeBurdYr6R95aOfk7ujkkft2rsgQA29a5Jl8E6aGg== dependencies: "@kitware/vtk.js" "32.1.0" comlink "^4.4.1" gl-matrix "^3.4.3" -"@cornerstonejs/dicom-image-loader@^2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.1.17.tgz#5429a10b921ed5586f093f51a363524e45cf6ecb" - integrity sha512-UIH4th4+F+a2s/myACpEmMjkPWFljAFbXeoj2Qo0JpL02FYYYGQuO7Ov+gjK/gv90twAh7q8Qg2+yNbHkC9pzA== +"@cornerstonejs/dicom-image-loader@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.2.1.tgz#01f38061cd128c56b00fb5a3c47488880fb0f6bf" + integrity sha512-yFO6K4vFZqG1dkUBpVSsQhOxS6nkb7uKEETwobhroAs8lIIfQ8141R8u2DfxBTIY7o9uJuYpabYzlhbyUYr4eA== dependencies: "@cornerstonejs/codec-charls" "^1.2.3" "@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2" "@cornerstonejs/codec-openjpeg" "^1.2.2" "@cornerstonejs/codec-openjph" "^2.4.5" comlink "^4.4.1" + jpeg-lossless-decoder-js "^2.1.0" pako "^2.0.4" uuid "^9.0.0" -"@cornerstonejs/tools@^2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.1.17.tgz#74e7a18845da779f5bc513902de6dbfe1880add0" - integrity sha512-vHdCVKV0kxYgvbS2ff9lDjd6AOKy6hP2Ohp96ac0enbZBNakUwDhqxWp94SezKqK7vn19fxRdSnPRLIchrqvJA== +"@cornerstonejs/tools@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.2.1.tgz#a6d34c39c330927c485e4af8b4c99026226249e4" + integrity sha512-cxTZAr+zA1atAJjspA4u4yD+mMZsV+H/fqf2Oa8yn3EZU5ONmFF37QbwLiXd3Zo47GYfEgTPDKQwFWG7wIAsxg== dependencies: "@types/offscreencanvas" "2019.7.3" comlink "^4.4.1" @@ -5490,6 +5491,11 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@rollup/rollup-linux-x64-gnu@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz#f672f6508f090fc73f08ba40ff76c20b57424778" + integrity sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA== + "@rspack/binding-darwin-arm64@1.0.14": version "1.0.14" resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.0.14.tgz#b9d99fb71e047f5300a851614f89cb9d7168db3e" @@ -13955,7 +13961,7 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" -htmlparser2@^8.0.0, htmlparser2@^8.0.1: +htmlparser2@^8.0.1: version "8.0.2" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== @@ -15488,6 +15494,13 @@ joi@^17.9.2: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" +jpeg-lossless-decoder-js@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/jpeg-lossless-decoder-js/-/jpeg-lossless-decoder-js-2.1.2.tgz#9ff0ecfcafa6acaee6097c532720bd526278aa3d" + integrity sha512-fYf/plymnuKwVw+s8gJ8O/BPw8y8OLaN11MBvEcT05kuy3m45o7BEC4J0JbxMNUYyO+MdK/I/jq0q1gkVYZm2Q== + optionalDependencies: + "@rollup/rollup-linux-x64-gnu" "4.13.0" + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -18990,11 +19003,6 @@ parse-path@^7.0.0: dependencies: protocols "^2.0.0" -parse-srcset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" - integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== - parse-url@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" @@ -20145,7 +20153,7 @@ postcss@^8.2.1, postcss@^8.3.5, postcss@^8.4.18, postcss@^8.4.23, postcss@^8.4.3 picocolors "^1.0.0" source-map-js "^1.2.0" -postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.38, postcss@^8.4.47: +postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.38, postcss@^8.4.47: version "8.4.47" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== @@ -21860,18 +21868,6 @@ safe-regex-test@^1.0.3: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sanitize-html@^2.13.1: - version "2.13.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.13.1.tgz#b4639b0a09574ab62b1b353cb99b1b87af742834" - integrity sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg== - dependencies: - deepmerge "^4.2.2" - escape-string-regexp "^4.0.0" - htmlparser2 "^8.0.0" - is-plain-object "^5.0.0" - parse-srcset "^1.0.2" - postcss "^8.3.11" - sax@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"