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 ? ( - - ) : null} +
+ {allowAddSegment ? ( + + ) : null} +