From 4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd Mon Sep 17 00:00:00 2001 From: Alireza Date: Wed, 30 Aug 2023 16:46:55 -0400 Subject: [PATCH] feat(grid): remove viewportIndex and only rely on viewportId (#3591) Co-authored-by: Bill Wallace --- extensions/_example/src/index.js | 2 +- .../src/utils/_hydrateRT.ts | 17 +- .../src/utils/promptHydrateRT.ts | 13 +- .../viewports/OHIFCornerstoneRTViewport.tsx | 27 +- .../src/panels/PanelSegmentation.tsx | 1 - .../src/utils/_hydrateSEG.ts | 17 +- .../src/utils/promptHydrateSEG.ts | 13 +- .../viewports/OHIFCornerstoneSEGViewport.tsx | 26 +- .../viewports/OHIFCornerstoneSRViewport.tsx | 31 +- .../src/Viewport/OHIFCornerstoneViewport.tsx | 131 +++--- .../Viewport/Overlays/CornerstoneOverlays.tsx | 16 +- .../Overlays/CustomizableViewportOverlay.tsx | 32 +- .../Overlays/ViewportImageScrollbar.tsx | 21 +- .../Overlays/ViewportOrientationMarkers.tsx | 6 +- .../src/Viewport/Overlays/ViewportOverlay.tsx | 26 +- extensions/cornerstone/src/commandsModule.ts | 96 ++--- .../src/components/CinePlayer/CinePlayer.tsx | 26 +- .../src/getHangingProtocolModule.ts | 3 + extensions/cornerstone/src/init.tsx | 5 +- extensions/cornerstone/src/initContextMenu.ts | 4 +- .../CornerstoneViewportService.ts | 268 +++++-------- .../ViewportService/IViewportService.ts | 14 +- .../src/services/ViewportService/Viewport.ts | 18 +- extensions/cornerstone/src/state.ts | 8 +- .../utils/CornerstoneViewportDownloadForm.tsx | 6 +- .../utils/getActiveViewportEnabledElement.ts | 4 +- .../utils/stackSync/toggleStackImageSync.ts | 40 +- .../src/Panels/PanelMeasurementTable.tsx | 6 +- .../default/src/Panels/PanelStudyBrowser.tsx | 10 +- extensions/default/src/commandsModule.ts | 83 ++-- .../default/src/findViewportsByPosition.ts | 17 +- .../default/src/getHangingProtocolModule.js | 1 + .../default/src/utils/reuseCachedLayouts.ts | 23 +- .../src/DicomMicroscopyViewport.tsx | 22 +- .../MicroscopyPanel/MicroscopyPanel.tsx | 10 +- .../dicom-microscopy/src/getCommandsModule.ts | 25 +- .../dicom-microscopy/src/getPanelModule.tsx | 7 +- extensions/dicom-microscopy/src/index.tsx | 10 +- .../src/services/MicroscopyService.ts | 32 +- .../src/tools/viewerManager.js | 4 +- .../TrackedMeasurementsContext.tsx | 24 +- .../hydrateStructuredReport.tsx | 4 +- .../promptBeginTracking.js | 10 +- .../promptHydrateStructuredReport.js | 14 +- .../promptSaveReport.js | 8 +- .../promptTrackNewSeries.js | 16 +- .../promptTrackNewStudy.js | 16 +- .../PanelMeasurementTableTracking/index.tsx | 6 +- .../PanelStudyBrowserTracking.tsx | 37 +- .../viewports/TrackedCornerstoneViewport.tsx | 33 +- extensions/test-extension/src/hpTestSwitch.ts | 246 ++++++++++++ extensions/test-extension/src/index.tsx | 12 + extensions/tmtv/src/commandsModule.js | 18 +- extensions/tmtv/src/utils/hpViewports.ts | 21 + package.json | 2 +- .../customization/OHIFDoubleClick.spec.js | 22 +- .../cypress/integration/volume/MPR.spec.js | 7 +- platform/app/src/components/ViewportGrid.tsx | 67 ++-- .../HangingProtocolService/HPMatcher.js | 15 +- .../HangingProtocolService.test.js | 2 +- .../HangingProtocolService.ts | 135 ++++--- .../MeasurementService/MeasurementService.ts | 7 +- .../services/UIViewportDialogService/index.js | 12 +- .../ViewportGridService.ts | 30 +- .../ViewportGridService/getPresentationIds.ts | 6 +- platform/core/src/types/HangingProtocol.ts | 4 +- platform/core/src/utils/index.js | 2 + platform/core/src/utils/index.test.js | 1 + .../subscribeToNextViewportGridChange.ts | 6 +- .../platform/extensions/modules/viewport.md | 48 ++- .../services/data/MeasurementService.md | 2 +- .../services/ui/ui-viewport-dialog-service.md | 2 +- .../services/ui/viewport-grid-service.md | 8 +- .../extensions/modules/viewport.md | 4 +- platform/ui/src/components/Dialog/Footer.tsx | 2 +- .../components/Notification/Notification.tsx | 4 +- .../ui/src/components/Viewport/Viewport.tsx | 61 ++- platform/ui/src/components/Viewport/index.js | 2 - platform/ui/src/components/Viewport/index.ts | 5 + .../ViewportActionBar/ViewportActionBar.tsx | 1 - .../ViewportDialogProvider.tsx | 1 - .../contextProviders/ViewportGridProvider.tsx | 378 +++++++++--------- yarn.lock | 10 +- 83 files changed, 1309 insertions(+), 1123 deletions(-) create mode 100644 extensions/test-extension/src/hpTestSwitch.ts delete mode 100644 platform/ui/src/components/Viewport/index.js create mode 100644 platform/ui/src/components/Viewport/index.ts diff --git a/extensions/_example/src/index.js b/extensions/_example/src/index.js index c4fdb9eb1..12c027cb8 100644 --- a/extensions/_example/src/index.js +++ b/extensions/_example/src/index.js @@ -121,7 +121,7 @@ const getSopClassHandlerModule = (/* ... */) => { const getToolbarModule = () => {}; -// displaySet, viewportIndex, dataSource +// displaySet, dataSource const getViewportModule = () => { const wrappedViewport = props => { return ( diff --git a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts index 0668c894b..f1b106c08 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts @@ -1,6 +1,6 @@ async function _hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, + viewportId, servicesManager, }) { const { @@ -27,7 +27,7 @@ async function _hydrateRTDisplaySet({ const { viewports } = viewportGridService.getState(); const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, + viewportId, displaySetInstanceUID ); @@ -39,8 +39,8 @@ async function _hydrateRTDisplaySet({ // is being displayed, if so we need to update the viewport to use volume viewport // (if already is not using it) since Cornerstone3D currently only supports // volume viewport for segmentation - viewports.forEach((viewport, index) => { - if (index === viewportIndex) { + viewports.forEach(viewport => { + if (viewport.viewportId === viewportId) { return; } @@ -51,9 +51,16 @@ async function _hydrateRTDisplaySet({ if (shouldDisplaySeg) { updatedViewports.push({ - viewportIndex: index, + viewportId: viewport.viewportId, displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, viewportOptions: { + // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component + // Used for segmentation hydration right now, since the logic to decide whether + // a viewport needs to render a segmentation lives inside the CornerstoneViewportService + // so we need to re-render (force update via change of the needsRerendering) so that React + // does the diffing and decides we should render this again (although the id and element has not changed) + // so that the CornerstoneViewportService can decide whether to render the segmentation or not. + needsRerendering: true, initialImageOptions: { preset: 'middle', }, diff --git a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts index 55c011074..b48bca363 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts @@ -10,17 +10,14 @@ const RESPONSE = { function promptHydrateRT({ servicesManager, rtDisplaySet, - viewportIndex, + viewportId, toolGroupId = 'default', preHydrateCallbacks, }) { const { uiViewportDialogService } = servicesManager.services; return new Promise(async function(resolve, reject) { - const promptResult = await _askHydrate( - uiViewportDialogService, - viewportIndex - ); + const promptResult = await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { preHydrateCallbacks?.forEach(callback => { @@ -29,7 +26,7 @@ function promptHydrateRT({ const isHydrated = await hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, + viewportId, toolGroupId, servicesManager, }); @@ -39,7 +36,7 @@ function promptHydrateRT({ }); } -function _askHydrate(uiViewportDialogService, viewportIndex) { +function _askHydrate(uiViewportDialogService, viewportId) { return new Promise(function(resolve, reject) { const message = 'Do you want to open this Segmentation?'; const actions = [ @@ -60,7 +57,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx index d203c6769..473d77480 100644 --- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx +++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx @@ -21,7 +21,6 @@ function OHIFCornerstoneRTViewport(props) { children, displaySets, viewportOptions, - viewportIndex, viewportLabel, servicesManager, extensionManager, @@ -36,7 +35,9 @@ function OHIFCornerstoneRTViewport(props) { customizationService, } = servicesManager.services; - const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportIndex}`; + const viewportId = viewportOptions.viewportId; + + const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportId}`; // RT viewport will always have a single display set if (displaySets.length > 1) { @@ -67,7 +68,7 @@ function OHIFCornerstoneRTViewport(props) { // refs const referencedDisplaySetRef = useRef(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet(); const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( @@ -93,9 +94,9 @@ function OHIFCornerstoneRTViewport(props) { }; const storePresentationState = useCallback(() => { - viewportGrid?.viewports.forEach(({ viewportIndex }) => { + viewportGrid?.viewports.forEach(({ viewportId }) => { commandsManager.runCommand('storePresentation', { - viewportIndex, + viewportId, }); }); }, [viewportGrid]); @@ -124,7 +125,7 @@ function OHIFCornerstoneRTViewport(props) { onElementDisabled={onElementDisabled} > ); - }, [viewportIndex, rtDisplaySet, toolGroupId]); + }, [viewportId, rtDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { @@ -162,7 +163,7 @@ function OHIFCornerstoneRTViewport(props) { promptHydrateRT({ servicesManager, - viewportIndex, + viewportId, rtDisplaySet, preHydrateCallbacks: [storePresentationState], }).then(isHydrated => { @@ -170,7 +171,7 @@ function OHIFCornerstoneRTViewport(props) { setIsHydrated(true); } }); - }, [servicesManager, viewportIndex, rtDisplaySet, rtIsLoading]); + }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]); useEffect(() => { const { unsubscribe } = segmentationService.subscribe( @@ -222,12 +223,12 @@ function OHIFCornerstoneRTViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; + const activeViewport = viewports.get(activeViewportId); if ( displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) ) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -292,7 +293,7 @@ function OHIFCornerstoneRTViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -322,7 +323,7 @@ function OHIFCornerstoneRTViewport(props) { storePresentationState(); const isHydrated = await _hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, + viewportId, servicesManager, }); @@ -384,7 +385,7 @@ function OHIFCornerstoneRTViewport(props) { OHIFCornerstoneRTViewport.propTypes = { displaySets: PropTypes.arrayOf(PropTypes.object), - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, dataSource: PropTypes.object, children: PropTypes.node, customProps: PropTypes.object, diff --git a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx index f11858422..368c15991 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx @@ -271,7 +271,6 @@ PanelSegmentation.propTypes = { commandsManager: PropTypes.shape({ runCommand: PropTypes.func.isRequired, }), - appConfig: PropTypes.object.isRequired, servicesManager: PropTypes.shape({ services: PropTypes.shape({ segmentationService: PropTypes.shape({ diff --git a/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts index f6b1522be..f028ce9f3 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts @@ -1,6 +1,6 @@ async function _hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, + viewportId: targetViewportId, servicesManager, }) { const { @@ -27,7 +27,7 @@ async function _hydrateSEGDisplaySet({ const { viewports } = viewportGridService.getState(); const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, + targetViewportId, displaySetInstanceUID ); @@ -37,8 +37,8 @@ async function _hydrateSEGDisplaySet({ // is being displayed, if so we need to update the viewport to use volume viewport // (if already is not using it) since Cornerstone3D currently only supports // volume viewport for segmentation - viewports.forEach((viewport, index) => { - if (index === viewportIndex) { + viewports.forEach((viewport, viewportId) => { + if (targetViewportId === viewportId) { return; } @@ -49,9 +49,16 @@ async function _hydrateSEGDisplaySet({ if (shouldDisplaySeg) { updatedViewports.push({ - viewportIndex: index, + viewportId, displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, viewportOptions: { + // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component + // Used for segmentation hydration right now, since the logic to decide whether + // a viewport needs to render a segmentation lives inside the CornerstoneViewportService + // so we need to re-render (force update via change of the needsRerendering) so that React + // does the diffing and decides we should render this again (although the id and element has not changed) + // so that the CornerstoneViewportService can decide whether to render the segmentation or not. + needsRerendering: true, initialImageOptions: { preset: 'middle', }, diff --git a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts index 76b5ab531..d6cdf23d7 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts @@ -10,16 +10,13 @@ const RESPONSE = { function promptHydrateSEG({ servicesManager, segDisplaySet, - viewportIndex, + viewportId, preHydrateCallbacks, }) { const { uiViewportDialogService } = servicesManager.services; return new Promise(async function(resolve, reject) { - const promptResult = await _askHydrate( - uiViewportDialogService, - viewportIndex - ); + const promptResult = await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { preHydrateCallbacks?.forEach(callback => { @@ -28,7 +25,7 @@ function promptHydrateSEG({ const isHydrated = await hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, + viewportId, servicesManager, }); @@ -37,7 +34,7 @@ function promptHydrateSEG({ }); } -function _askHydrate(uiViewportDialogService, viewportIndex) { +function _askHydrate(uiViewportDialogService, viewportId) { return new Promise(function(resolve, reject) { const message = 'Do you want to open this Segmentation?'; const actions = [ @@ -58,7 +55,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx index c5fb133de..88feed10f 100644 --- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx +++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx @@ -20,7 +20,6 @@ function OHIFCornerstoneSEGViewport(props) { children, displaySets, viewportOptions, - viewportIndex, viewportLabel, servicesManager, extensionManager, @@ -28,6 +27,7 @@ function OHIFCornerstoneSEGViewport(props) { } = props; const { t } = useTranslation('SEGViewport'); + const viewportId = viewportOptions.viewportId; const { displaySetService, @@ -37,7 +37,7 @@ function OHIFCornerstoneSEGViewport(props) { customizationService, } = servicesManager.services; - const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportIndex}`; + const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`; // SEG viewport will always have a single display set if (displaySets.length > 1) { @@ -67,7 +67,7 @@ function OHIFCornerstoneSEGViewport(props) { // refs const referencedDisplaySetRef = useRef(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet(); const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( @@ -94,9 +94,9 @@ function OHIFCornerstoneSEGViewport(props) { }; const storePresentationState = useCallback(() => { - viewportGrid?.viewports.forEach(({ viewportIndex }) => { + viewportGrid?.viewports.forEach(({ viewportId }) => { commandsManager.runCommand('storePresentation', { - viewportIndex, + viewportId, }); }); }, [viewportGrid]); @@ -126,7 +126,7 @@ function OHIFCornerstoneSEGViewport(props) { // initialImageIndex={initialImageIndex} > ); - }, [viewportIndex, segDisplaySet, toolGroupId]); + }, [viewportId, segDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { @@ -165,7 +165,7 @@ function OHIFCornerstoneSEGViewport(props) { promptHydrateSEG({ servicesManager, - viewportIndex, + viewportId, segDisplaySet, preHydrateCallbacks: [storePresentationState], }).then(isHydrated => { @@ -173,7 +173,7 @@ function OHIFCornerstoneSEGViewport(props) { setIsHydrated(true); } }); - }, [servicesManager, viewportIndex, segDisplaySet, segIsLoading]); + }, [servicesManager, viewportId, segDisplaySet, segIsLoading]); useEffect(() => { const { unsubscribe } = segmentationService.subscribe( @@ -225,12 +225,12 @@ function OHIFCornerstoneSEGViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; + const activeViewport = viewports.get(activeViewportId); if ( displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) ) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -296,7 +296,7 @@ function OHIFCornerstoneSEGViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -325,7 +325,7 @@ function OHIFCornerstoneSEGViewport(props) { storePresentationState(); const isHydrated = await hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, + viewportId, servicesManager, }); @@ -388,7 +388,7 @@ function OHIFCornerstoneSEGViewport(props) { OHIFCornerstoneSEGViewport.propTypes = { displaySets: PropTypes.arrayOf(PropTypes.object), - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, dataSource: PropTypes.object, children: PropTypes.node, customProps: PropTypes.object, diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx index b95af260f..0555694d8 100644 --- a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx @@ -21,7 +21,6 @@ function OHIFCornerstoneSRViewport(props) { children, dataSource, displaySets, - viewportIndex, viewportLabel, viewportOptions, servicesManager, @@ -36,6 +35,8 @@ function OHIFCornerstoneSRViewport(props) { measurementService, } = servicesManager.services; + const viewportId = viewportOptions.viewportId; + // SR viewport will always have a single display set if (displaySets.length > 1) { throw new Error('SR viewport should only have a single display set'); @@ -54,7 +55,7 @@ function OHIFCornerstoneSRViewport(props) { setReferencedDisplaySetMetadata, ] = useState(null); const [element, setElement] = useState(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; // Optional hook into tracking extension, if present. let trackedMeasurements; @@ -88,7 +89,7 @@ function OHIFCornerstoneSRViewport(props) { if (displaySets.length) { viewportGridService.setDisplaySetsForViewports([ { - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID], }, ]); @@ -163,12 +164,8 @@ function OHIFCornerstoneSRViewport(props) { // imageIdIndex will handle it by updating the viewport, but if they // are the same we just need to use measurementService to jump to the // new measurement - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - const csViewport = cornerstoneViewportService.getCornerstoneViewport( - viewportInfo.getViewportId() + viewportId ); const imageIds = csViewport.getImageIds(); @@ -183,7 +180,7 @@ function OHIFCornerstoneSRViewport(props) { } }); }, - [dataSource, srDisplaySet, activeImageDisplaySetData, viewportIndex] + [dataSource, srDisplaySet, activeImageDisplaySetData, viewportId] ); const getCornerstoneViewport = useCallback(() => { @@ -231,7 +228,7 @@ function OHIFCornerstoneSRViewport(props) { isJumpToMeasurementDisabled={true} > ); - }, [activeImageDisplaySetData, viewportIndex, measurementSelected]); + }, [activeImageDisplaySetData, viewportId, measurementSelected]); const onMeasurementChange = useCallback( direction => { @@ -269,12 +266,12 @@ function OHIFCornerstoneSRViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; + const activeViewport = viewports[activeViewportId]; if ( displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) ) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -341,7 +338,7 @@ function OHIFCornerstoneSRViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -373,7 +370,7 @@ function OHIFCornerstoneSRViewport(props) { getStatusComponent={() => _getStatusComponent({ srDisplaySet, - viewportIndex, + viewportId, isTracked: false, isRehydratable: srDisplaySet.isRehydratable, isLocked, @@ -413,7 +410,7 @@ function OHIFCornerstoneSRViewport(props) { OHIFCornerstoneSRViewport.propTypes = { displaySets: PropTypes.arrayOf(PropTypes.object), - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, dataSource: PropTypes.object, children: PropTypes.node, viewportLabel: PropTypes.string, @@ -462,7 +459,7 @@ async function _getViewportReferencedDisplaySetData( function _getStatusComponent({ srDisplaySet, - viewportIndex, + viewportId, isRehydratable, isLocked, sendTrackedMeasurementsEvent, @@ -470,7 +467,7 @@ function _getStatusComponent({ const handleMouseUp = () => { sendTrackedMeasurementsEvent('HYDRATE_SR', { displaySetInstanceUID: srDisplaySet.displaySetInstanceUID, - viewportIndex, + viewportId, }); }; diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx index 732752476..d14263425 100644 --- a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx @@ -63,6 +63,10 @@ function areEqual(prevProps, nextProps) { return false; } + if (nextProps.viewportOptions.needsRerendering) { + return false; + } + const prevDisplaySets = prevProps.displaySets; const nextDisplaySets = nextProps.displaySets; @@ -107,7 +111,6 @@ function areEqual(prevProps, nextProps) { // Then we don't need to worry about the re-renders if the props change. const OHIFCornerstoneViewport = React.memo(props => { const { - viewportIndex, displaySets, dataSource, viewportOptions, @@ -123,6 +126,7 @@ const OHIFCornerstoneViewport = React.memo(props => { initialImageIndex, } = props; + const viewportId = viewportOptions.viewportId; const [scrollbarHeight, setScrollbarHeight] = useState('100px'); const [enabledVPElement, setEnabledVPElement] = useState(null); const elementRef = useRef(); @@ -154,27 +158,24 @@ const OHIFCornerstoneViewport = React.memo(props => { } }, [elementRef]); - const cleanUpServices = useCallback(() => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const cleanUpServices = useCallback( + viewportInfo => { + const renderingEngineId = viewportInfo.getRenderingEngineId(); + const syncGroups = viewportInfo.getSyncGroups(); - if (!viewportInfo) { - return; - } + toolGroupService.removeViewportFromToolGroup( + viewportId, + renderingEngineId + ); - const viewportId = viewportInfo.getViewportId(); - const renderingEngineId = viewportInfo.getRenderingEngineId(); - const syncGroups = viewportInfo.getSyncGroups(); - - toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId); - - syncGroupService.removeViewportFromSyncGroup( - viewportId, - renderingEngineId, - syncGroups - ); - }, [viewportIndex, viewportOptions.viewportId]); + syncGroupService.removeViewportFromSyncGroup( + viewportId, + renderingEngineId, + syncGroups + ); + }, + [viewportId] + ); const elementEnabledHandler = useCallback( evt => { @@ -187,9 +188,7 @@ const OHIFCornerstoneViewport = React.memo(props => { const viewportInfo = cornerstoneViewportService.getViewportInfo( viewportId ); - const viewportIndex = viewportInfo.getViewportIndex(); - - setEnabledElement(viewportIndex, element); + setEnabledElement(viewportId, element); setEnabledVPElement(element); const renderingEngineId = viewportInfo.getRenderingEngineId(); @@ -212,16 +211,12 @@ const OHIFCornerstoneViewport = React.memo(props => { onElementEnabled(evt); } }, - [viewportIndex, onElementEnabled, toolGroupService] + [viewportId, onElementEnabled, toolGroupService] ); // disable the element upon unmounting useEffect(() => { - cornerstoneViewportService.enableViewport( - viewportIndex, - viewportOptions, - elementRef.current - ); + cornerstoneViewportService.enableViewport(viewportId, elementRef.current); eventTarget.addEventListener( Enums.Events.ELEMENT_ENABLED, @@ -231,17 +226,16 @@ const OHIFCornerstoneViewport = React.memo(props => { setImageScrollBarHeight(); return () => { - commandsManager.runCommand('storePresentation', { - viewportIndex, - }); - - cleanUpServices(); - - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex + const viewportInfo = cornerstoneViewportService.getViewportInfo( + viewportId ); - cornerstoneViewportService.disableElement(viewportIndex); + if (!viewportInfo) { + return; + } + + cleanUpServices(viewportInfo); + cornerstoneViewportService.storePresentation({ viewportId }); if (onElementDisabled) { onElementDisabled(viewportInfo); @@ -273,8 +267,8 @@ const OHIFCornerstoneViewport = React.memo(props => { return; } - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex + const viewportInfo = cornerstoneViewportService.getViewportInfo( + viewportId ); if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) { @@ -288,7 +282,7 @@ const OHIFCornerstoneViewport = React.memo(props => { const keepCamera = true; cornerstoneViewportService.updateViewport( - viewportIndex, + viewportId, newViewportData, keepCamera ); @@ -298,7 +292,7 @@ const OHIFCornerstoneViewport = React.memo(props => { return () => { unsubscribe(); }; - }, [viewportIndex]); + }, [viewportId]); useEffect(() => { // handle the default viewportType to be stack @@ -329,15 +323,25 @@ const OHIFCornerstoneViewport = React.memo(props => { lutPresentationStore[presentationIds?.lutPresentationId], }; let measurement; - if (cacheJumpToMeasurementEvent?.viewportIndex === viewportIndex) { + if (cacheJumpToMeasurementEvent?.viewportId === viewportId) { measurement = cacheJumpToMeasurementEvent.measurement; // Delete the position presentation so that viewport navigates direct presentations.positionPresentation = null; cacheJumpToMeasurementEvent = null; } + // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component + // Used for segmentation hydration right now, since the logic to decide whether + // a viewport needs to render a segmentation lives inside the CornerstoneViewportService + // so we need to re-render (force update via change of the needsRerendering) so that React + // does the diffing and decides we should render this again (although the id and element has not changed) + // so that the CornerstoneViewportService can decide whether to render the segmentation or not. Not that we reached here we can turn it off. + if (viewportOptions.needsRerendering) { + viewportOptions.needsRerendering = false; + } + cornerstoneViewportService.setViewportData( - viewportIndex, + viewportId, viewportData, viewportOptions, displaySetOptions, @@ -370,7 +374,7 @@ const OHIFCornerstoneViewport = React.memo(props => { measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -380,7 +384,7 @@ const OHIFCornerstoneViewport = React.memo(props => { measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -389,7 +393,7 @@ const OHIFCornerstoneViewport = React.memo(props => { return () => { unsubscribeFromJumpToMeasurementEvents(); }; - }, [displaySets, elementRef, viewportIndex]); + }, [displaySets, elementRef, viewportId]); return ( @@ -408,7 +412,7 @@ const OHIFCornerstoneViewport = React.memo(props => { ref={elementRef} > { />
- {viewportDialogState.viewportIndex === viewportIndex && ( + {viewportDialogState.viewportId === viewportId && ( displaySet.displaySetInstanceUID - ); const { unsubscribe } = measurementService.subscribe( MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, props => { cacheJumpToMeasurementEvent = props; - const { viewportIndex: jumpIndex, measurement, isConsumed } = props; + const { viewportId: jumpId, measurement, isConsumed } = props; if (!measurement || isConsumed) { return; } if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) { // Decide on which viewport should handle this - cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIndexToJump( - jumpIndex, + cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIdToJump( + jumpId, measurement.displaySetInstanceUID, { referencedImageId: measurement.referencedImageId } ); } - if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportIndex) { + if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) { return; } _jumpToMeasurement( measurement, elementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -487,7 +488,7 @@ function _checkForCachedJumpToMeasurementEvents( measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -513,7 +514,7 @@ function _checkForCachedJumpToMeasurementEvents( _jumpToMeasurement( measurement, elementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -526,7 +527,7 @@ function _checkForCachedJumpToMeasurementEvents( function _jumpToMeasurement( measurement, targetElementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -548,13 +549,10 @@ function _jumpToMeasurement( // to set it properly // setCornerstoneMeasurementActive(measurement); - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); const enabledElement = getEnabledElement(targetElement); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); if (enabledElement) { // See how the jumpToSlice() of Cornerstone3D deals with imageIdx param. const viewport = enabledElement.viewport as @@ -621,7 +619,6 @@ OHIFCornerstoneViewport.defaultProps = { }; OHIFCornerstoneViewport.propTypes = { - viewportIndex: PropTypes.number.isRequired, displaySets: PropTypes.array.isRequired, dataSource: PropTypes.object.isRequired, viewportOptions: PropTypes.object, diff --git a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx index a860753d1..69f7b10fa 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx @@ -6,7 +6,7 @@ import ViewportOrientationMarkers from './ViewportOrientationMarkers'; import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator'; function CornerstoneOverlays(props) { - const { viewportIndex, element, scrollbarHeight, servicesManager } = props; + const { viewportId, element, scrollbarHeight, servicesManager } = props; const { cornerstoneViewportService } = servicesManager.services; const [imageSliceData, setImageSliceData] = useState({ imageIndex: 0, @@ -18,7 +18,7 @@ function CornerstoneOverlays(props) { const { unsubscribe } = cornerstoneViewportService.subscribe( cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, props => { - if (props.viewportIndex !== viewportIndex) { + if (props.viewportId !== viewportId) { return; } @@ -29,16 +29,14 @@ function CornerstoneOverlays(props) { return () => { unsubscribe(); }; - }, [viewportIndex]); + }, [viewportId]); if (!element) { return null; } if (viewportData) { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) { return null; @@ -48,7 +46,7 @@ function CornerstoneOverlays(props) { return (
@@ -75,7 +73,7 @@ function CornerstoneOverlays(props) { element={element} viewportData={viewportData} servicesManager={servicesManager} - viewportIndex={viewportIndex} + viewportId={viewportId} />
); diff --git a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx index 3b92c97ef..9e778c1eb 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx @@ -21,7 +21,6 @@ interface OverlayItemProps { element: any; viewportData: any; imageSliceData: ImageSliceData; - viewportIndex: number | null; servicesManager: ServicesManager; instance: InstanceMetadata; customization: any; @@ -114,7 +113,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, }) { const { @@ -152,13 +151,13 @@ function CustomizableViewportOverlay({ if (viewportData != null) { return _getInstanceNumber( viewportData, - viewportIndex, + viewportId, imageIndex, cornerstoneViewportService ); } return null; - }, [viewportData, viewportIndex, imageIndex, cornerstoneViewportService]); + }, [viewportData, viewportId, imageIndex, cornerstoneViewportService]); /** * Initial toolbar state @@ -192,7 +191,7 @@ function CustomizableViewportOverlay({ return () => { element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); }; - }, [viewportIndex, viewportData, voi, element]); + }, [viewportId, viewportData, voi, element]); /** * Updating the scale when the viewport changes its zoom @@ -205,8 +204,8 @@ function CustomizableViewportOverlay({ previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale ) { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex + const viewport = cornerstoneViewportService.getCornerstoneViewport( + viewportId ); if (!viewport) { @@ -237,7 +236,7 @@ function CustomizableViewportOverlay({ return () => { element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); }; - }, [viewportIndex, viewportData, cornerstoneViewportService, element]); + }, [viewportId, viewportData, cornerstoneViewportService, element]); /** * Updating the active tools when the toolbar changes @@ -262,7 +261,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, customization: item, formatters: { @@ -296,7 +295,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, customizationService, instance, @@ -390,7 +389,7 @@ function _getViewportInstance(viewportData, imageIndex) { function _getInstanceNumber( viewportData, - viewportIndex, + viewportId, imageIndex, cornerstoneViewportService ) { @@ -406,7 +405,7 @@ function _getInstanceNumber( instanceNumber = _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ); } @@ -438,8 +437,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) { // Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber function _getInstanceNumberFromVolume( viewportData, - imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ) { const volumes = viewportData.volumes; @@ -452,8 +450,8 @@ function _getInstanceNumberFromVolume( const volume = volumes[0]; const { direction, imageIds } = volume; - const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex + const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport( + viewportId ); if (!cornerstoneViewport) { @@ -486,7 +484,7 @@ function _getInstanceNumberFromVolume( CustomizableViewportOverlay.propTypes = { viewportData: PropTypes.object, imageIndex: PropTypes.number, - viewportIndex: PropTypes.number, + viewportId: PropTypes.string, }; export default CustomizableViewportOverlay; diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx index 074e0a969..26fe32e01 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx @@ -7,7 +7,7 @@ import { ServicesManger } from '@ohif/core'; function CornerstoneImageScrollbar({ viewportData, - viewportIndex, + viewportId, element, imageSliceData, setImageSliceData, @@ -19,12 +19,7 @@ function CornerstoneImageScrollbar({ cornerstoneViewportService, } = (servicesManager as ServicesManger).services; - const onImageScrollbarChange = (imageIndex, viewportIndex) => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - const viewportId = viewportInfo.getViewportId(); + const onImageScrollbarChange = (imageIndex, viewportId) => { const viewport = cornerstoneViewportService.getCornerstoneViewport( viewportId ); @@ -34,7 +29,7 @@ function CornerstoneImageScrollbar({ if (isCineEnabled) { // on image scrollbar change, stop the CINE if it is playing cineService.stopClip(element); - cineService.setCine({ id: viewportIndex, isPlaying: false }); + cineService.setCine({ id: viewportId, isPlaying: false }); } csToolsUtils.jumpToSlice(viewport.element, { @@ -48,8 +43,8 @@ function CornerstoneImageScrollbar({ return; } - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex + const viewport = cornerstoneViewportService.getCornerstoneViewport( + viewportId ); if (!viewport) { @@ -79,7 +74,7 @@ function CornerstoneImageScrollbar({ const { imageIndex, numberOfSlices } = sliceData; setImageSliceData({ imageIndex, numberOfSlices }); } - }, [viewportIndex, viewportData]); + }, [viewportId, viewportData]); useEffect(() => { if (viewportData?.viewportType !== Enums.ViewportType.STACK) { @@ -131,7 +126,7 @@ function CornerstoneImageScrollbar({ return ( onImageScrollbarChange(evt, viewportIndex)} + onChange={evt => onImageScrollbarChange(evt, viewportId)} max={ imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0 } @@ -143,7 +138,7 @@ function CornerstoneImageScrollbar({ CornerstoneImageScrollbar.propTypes = { viewportData: PropTypes.object, - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, element: PropTypes.instanceOf(Element), scrollbarHeight: PropTypes.string, imageSliceData: PropTypes.object.isRequired, diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx index 8e431ac52..d0ba9550a 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx @@ -22,7 +22,7 @@ function ViewportOrientationMarkers({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, orientationMarkers = ['top', 'left'], }) { @@ -114,9 +114,7 @@ function ViewportOrientationMarkers({ flipHorizontal ); - const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); if (!ohifViewport) { console.log('ViewportOrientationMarkers::No viewport'); diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportOverlay.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportOverlay.tsx index b3ecf6e2c..fcc686f2c 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportOverlay.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportOverlay.tsx @@ -11,7 +11,7 @@ function CornerstoneViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, }) { const { @@ -73,7 +73,7 @@ function CornerstoneViewportOverlay({ return () => { element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); }; - }, [viewportIndex, viewportData, voi, element]); + }, [viewportId, viewportData, voi, element]); /** * Updating the scale when the viewport changes its zoom @@ -86,8 +86,8 @@ function CornerstoneViewportOverlay({ previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale ) { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex + const viewport = cornerstoneViewportService.getCornerstoneViewport( + viewportId ); if (!viewport) { @@ -118,7 +118,7 @@ function CornerstoneViewportOverlay({ return () => { element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); }; - }, [viewportIndex, viewportData]); + }, [viewportId, viewportData]); const getTopLeftContent = useCallback(() => { const { windowWidth, windowCenter } = voi; @@ -168,7 +168,7 @@ function CornerstoneViewportOverlay({ instanceNumber = _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ); } @@ -183,15 +183,13 @@ function CornerstoneViewportOverlay({
); - }, [imageSliceData, viewportData, viewportIndex]); + }, [imageSliceData, viewportData, viewportId]); if (!viewportData) { return null; } - const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); if (!ohifViewport) { return null; @@ -240,7 +238,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) { function _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ) { const volumes = viewportData.volumes; @@ -253,8 +251,8 @@ function _getInstanceNumberFromVolume( const volume = volumes[0]; const { direction, imageIds } = volume; - const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex + const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport( + viewportId ); if (!cornerstoneViewport) { @@ -287,7 +285,7 @@ function _getInstanceNumberFromVolume( CornerstoneViewportOverlay.propTypes = { viewportData: PropTypes.object, imageIndex: PropTypes.number, - viewportIndex: PropTypes.number, + viewportId: PropTypes.string, servicesManager: PropTypes.instanceOf(ServicesManager), }; diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts index cf1b219a3..884515db5 100644 --- a/extensions/cornerstone/src/commandsModule.ts +++ b/extensions/cornerstone/src/commandsModule.ts @@ -239,8 +239,7 @@ function commandsModule({ return; } - const viewportIndex = viewportInfo.getViewportIndex(); - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); }, arrowTextCallback: ({ callback, data }) => { callInputDialog(uiDialogService, data, callback); @@ -310,9 +309,11 @@ function commandsModule({ } } - const { viewports } = viewportGridService.getState() || { - viewports: [], - }; + const { viewports } = viewportGridService.getState(); + + if (!viewports.size) { + return; + } const toolGroup = toolGroupService.getToolGroup(toolGroupId); const toolGroupViewportIds = toolGroup?.getViewportIds?.(); @@ -322,15 +323,11 @@ function commandsModule({ return; } - const filteredViewports = viewports.filter(viewport => { - if (!viewport.viewportOptions) { - return false; + const filteredViewports = Array.from(viewports.values()).filter( + viewport => { + return toolGroupViewportIds.includes(viewport.viewportId); } - - return toolGroupViewportIds.includes( - viewport.viewportOptions.viewportId - ); - }); + ); if (!filteredViewports.length) { return; @@ -368,12 +365,10 @@ function commandsModule({ }); }, showDownloadViewportModal: () => { - const { activeViewportIndex } = viewportGridService.getState(); + const { activeViewportId } = viewportGridService.getState(); if ( - !cornerstoneViewportService.getCornerstoneViewportByIndex( - activeViewportIndex - ) + !cornerstoneViewportService.getCornerstoneViewport(activeViewportId) ) { // Cannot download a non-cornerstone viewport (image). uiNotificationService.show({ @@ -391,7 +386,7 @@ function commandsModule({ content: CornerstoneViewportDownloadForm, title: 'Download High Quality Image', contentProps: { - activeViewportIndex, + activeViewportId, onClose: uiModalService.hide, cornerstoneViewportService, }, @@ -554,13 +549,13 @@ function commandsModule({ cstUtils.scroll(viewport, options); }, setViewportColormap: ({ - viewportIndex, + viewportId, displaySetInstanceUID, colormap, immediate = false, }) => { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex + const viewport = cornerstoneViewportService.getCornerstoneViewport( + viewportId ); const actorEntries = viewport.getActors(); @@ -577,17 +572,17 @@ function commandsModule({ viewport.render(); } }, - incrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const nextViewportIndex = (activeViewportIndex + 1) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); - }, - decrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); + changeActiveViewport: ({ direction = 1 }) => { + const { activeViewportId, viewports } = viewportGridService.getState(); + const viewportIds = Array.from(viewports.keys()); + const currentIndex = viewportIds.indexOf(activeViewportId); const nextViewportIndex = - (activeViewportIndex - 1 + viewports.length) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); + (currentIndex + direction + viewportIds.length) % viewportIds.length; + viewportGridService.setActiveViewportId( + viewportIds[nextViewportIndex] as string + ); }, + toggleStackImageSync: ({ toggledState }) => { toggleStackImageSync({ getEnabledElement, @@ -596,9 +591,9 @@ function commandsModule({ }); }, toggleReferenceLines: ({ toggledState }) => { - const { activeViewportIndex } = viewportGridService.getState(); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - activeViewportIndex + const { activeViewportId } = viewportGridService.getState(); + const viewportInfo = cornerstoneViewportService.getViewportInfo( + activeViewportId ); const viewportId = viewportInfo.getViewportId(); @@ -617,34 +612,8 @@ function commandsModule({ ); toolGroup.setToolEnabled(ReferenceLinesTool.toolName); }, - storePresentation: ({ viewportIndex }) => { - const presentation = cornerstoneViewportService.getPresentation( - viewportIndex - ); - if (!presentation || !presentation.presentationIds) { - return; - } - const { - lutPresentationStore, - positionPresentationStore, - } = stateSyncService.getState(); - const { presentationIds } = presentation; - const { lutPresentationId, positionPresentationId } = - presentationIds || {}; - const storeState = {}; - if (lutPresentationId) { - storeState.lutPresentationStore = { - ...lutPresentationStore, - [lutPresentationId]: presentation, - }; - } - if (positionPresentationId) { - storeState.positionPresentationStore = { - ...positionPresentationStore, - [positionPresentationId]: presentation, - }; - } - stateSyncService.store(storeState); + storePresentation: ({ viewportId }) => { + cornerstoneViewportService.storePresentation({ viewportId }); }, }; @@ -701,10 +670,11 @@ function commandsModule({ options: { rotation: -90 }, }, incrementActiveViewport: { - commandFn: actions.incrementActiveViewport, + commandFn: actions.changeActiveViewport, }, decrementActiveViewport: { - commandFn: actions.decrementActiveViewport, + commandFn: actions.changeActiveViewport, + options: { direction: -1 }, }, flipViewportHorizontal: { commandFn: actions.flipViewportHorizontal, diff --git a/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx b/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx index ef54108b6..d39f3bab0 100644 --- a/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx +++ b/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx @@ -2,14 +2,10 @@ import React, { useEffect } from 'react'; import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui'; import { Enums, eventTarget } from '@cornerstonejs/core'; -function WrappedCinePlayer({ - enabledVPElement, - viewportIndex, - servicesManager, -}) { +function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) { const { toolbarService, customizationService } = servicesManager.services; const [{ isCineEnabled, cines }, cineService] = useCine(); - const [{ activeViewportIndex }] = useViewportGrid(); + const [{ activeViewportId }] = useViewportGrid(); const { component: CinePlayerComponent = CinePlayer } = customizationService.get('cinePlayer') ?? {}; @@ -30,11 +26,11 @@ function WrappedCinePlayer({ }; const cineHandler = () => { - if (!cines || !cines[viewportIndex] || !enabledVPElement) { + if (!cines || !cines[viewportId] || !enabledVPElement) { return; } - const cine = cines[viewportIndex]; + const cine = cines[viewportId]; const isPlaying = cine.isPlaying || false; const frameRate = cine.frameRate || 24; @@ -56,7 +52,7 @@ function WrappedCinePlayer({ ); return () => { - cineService.setCine({ id: viewportIndex, isPlaying: false }); + cineService.setCine({ id: viewportId, isPlaying: false }); eventTarget.removeEventListener( Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler @@ -65,20 +61,20 @@ function WrappedCinePlayer({ }, [enabledVPElement]); useEffect(() => { - if (!cines || !cines[viewportIndex] || !enabledVPElement) { + if (!cines || !cines[viewportId] || !enabledVPElement) { return; } cineHandler(); return () => { - if (enabledVPElement && cines?.[viewportIndex]?.isPlaying) { + if (enabledVPElement && cines?.[viewportId]?.isPlaying) { cineService.stopClip(enabledVPElement); } }; - }, [cines, viewportIndex, cineService, enabledVPElement, cineHandler]); + }, [cines, viewportId, cineService, enabledVPElement, cineHandler]); - const cine = cines[viewportIndex]; + const cine = cines[viewportId]; const isPlaying = (cine && cine.isPlaying) || false; return ( @@ -89,13 +85,13 @@ function WrappedCinePlayer({ onClose={handleCineClose} onPlayPauseChange={isPlaying => cineService.setCine({ - id: activeViewportIndex, + id: activeViewportId, isPlaying, }) } onFrameRateChange={frameRate => cineService.setCine({ - id: activeViewportIndex, + id: activeViewportId, frameRate, }) } diff --git a/extensions/cornerstone/src/getHangingProtocolModule.ts b/extensions/cornerstone/src/getHangingProtocolModule.ts index e4bf99628..15bfaa44c 100644 --- a/extensions/cornerstone/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone/src/getHangingProtocolModule.ts @@ -88,6 +88,7 @@ const mpr: Types.HangingProtocol.Protocol = { viewports: [ { viewportOptions: { + viewportId: 'mpr-axial', toolGroupId: 'mpr', viewportType: 'volume', orientation: 'axial', @@ -111,6 +112,7 @@ const mpr: Types.HangingProtocol.Protocol = { }, { viewportOptions: { + viewportId: 'mpr-sagittal', toolGroupId: 'mpr', viewportType: 'volume', orientation: 'sagittal', @@ -134,6 +136,7 @@ const mpr: Types.HangingProtocol.Protocol = { }, { viewportOptions: { + viewportId: 'mpr-coronal', toolGroupId: 'mpr', viewportType: 'volume', orientation: 'coronal', diff --git a/extensions/cornerstone/src/init.tsx b/extensions/cornerstone/src/init.tsx index 40e318da0..3938189b2 100644 --- a/extensions/cornerstone/src/init.tsx +++ b/extensions/cornerstone/src/init.tsx @@ -282,9 +282,8 @@ export default async function init({ ); viewportGridService.subscribe( - viewportGridService.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, - ({ viewportIndex, viewportId }) => { - viewportId = viewportId || `viewport-${viewportIndex}`; + viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, + ({ viewportId }) => { const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) { diff --git a/extensions/cornerstone/src/initContextMenu.ts b/extensions/cornerstone/src/initContextMenu.ts index bd11dfa71..48a5c19a3 100644 --- a/extensions/cornerstone/src/initContextMenu.ts +++ b/extensions/cornerstone/src/initContextMenu.ts @@ -63,7 +63,6 @@ function initContextMenu({ customizationService.get('cornerstoneViewportClickCommands') || DEFAULT_CONTEXT_MENU_CLICKS; const toRun = customizations[name]; - console.log('initContextMenu::cornerstoneViewportHandleEvent', name, toRun); const options = { nearbyToolData: findNearbyToolData(commandsManager, evt), event: evt, @@ -82,9 +81,8 @@ function initContextMenu({ if (!viewportInfo) { return; } - const viewportIndex = viewportInfo.getViewportIndex(); // TODO check update upstream - setEnabledElement(viewportIndex, element); + setEnabledElement(viewportId, element); element.addEventListener( cs3DToolsEvents.MOUSE_CLICK, diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts index 56ed36197..6ee00b8ec 100644 --- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -9,8 +9,6 @@ import { VolumeViewport, VolumeViewport3D, cache, - utilities, - CONSTANTS, Enums as csEnums, } from '@cornerstonejs/core'; @@ -55,7 +53,6 @@ class CornerstoneViewportService extends PubSubService }; renderingEngine: Types.IRenderingEngine | null; - viewportsInfo: Map = new Map(); viewportsById: Map = new Map(); viewportGridResizeObserver: ResizeObserver | null; viewportsDisplaySets: Map = new Map(); @@ -75,45 +72,17 @@ class CornerstoneViewportService extends PubSubService /** * Adds the HTML element to the viewportService - * @param {*} viewportIndex + * @param {*} viewportId * @param {*} elementRef */ - public enableViewport( - viewportIndex: number, - viewportOptions: PublicViewportOptions, - elementRef: HTMLDivElement - ): void { - // Use the provided viewportId - // Not providing a viewportId is frowned upon because it does weird things - // on moving them around, but it does mostly work. - if (!viewportOptions.viewportId) { - console.warn('Should provide viewport id externally', viewportOptions); - viewportOptions.viewportId = - this.getViewportId(viewportIndex) || `viewport-${viewportIndex}`; - } - const { viewportId } = viewportOptions; - const viewportInfo = new ViewportInfo(viewportIndex, viewportId); - if (!viewportInfo.viewportId) { - throw new Error('Should have viewport ID afterwards'); - } - + public enableViewport(viewportId: string, elementRef: HTMLDivElement): void { + const viewportInfo = new ViewportInfo(viewportId); viewportInfo.setElement(elementRef); - this.viewportsInfo.set(viewportIndex, viewportInfo); this.viewportsById.set(viewportId, viewportInfo); } public getViewportIds(): string[] { - const viewportIds = []; - - this.viewportsInfo.forEach(viewportInfo => { - viewportIds.push(viewportInfo.getViewportId()); - }); - - return viewportIds; - } - - public getViewportId(viewportIndex: number): string { - return this.viewportsInfo[viewportIndex]?.viewportId; + return Array.from(this.viewportsById.keys()); } /** @@ -171,21 +140,14 @@ class CornerstoneViewportService extends PubSubService * created for every new viewport, this will be called whenever the set of * viewports is changed, but NOT when the viewport position changes only. * - * @param viewportIndex + * @param viewportId - The viewportId to disable */ - public disableElement(viewportIndex: number): void { - const viewportInfo = this.viewportsInfo.get(viewportIndex); - if (!viewportInfo) { - return; - } + public disableElement(viewportId: string): void { + this.renderingEngine?.disableElement(viewportId); - const viewportId = viewportInfo.getViewportId(); - - this.renderingEngine && this.renderingEngine.disableElement(viewportId); - - this.viewportsInfo.get(viewportIndex).destroy(); - this.viewportsInfo.delete(viewportIndex); + // clean up this.viewportsById.delete(viewportId); + this.viewportsDisplaySets.delete(viewportId); } public setPresentations(viewport, presentations?: Presentations): void { @@ -199,14 +161,14 @@ class CornerstoneViewportService extends PubSubService } } - public getPresentation(viewportIndex: number): Presentation { - const viewportInfo = this.viewportsInfo.get(viewportIndex); + public getPresentation(viewportId: string): Presentation { + const viewportInfo = this.viewportsById.get(viewportId); if (!viewportInfo) { return; } const { viewportType, presentationIds } = viewportInfo.getViewportOptions(); - const csViewport = this.getCornerstoneViewportByIndex(viewportIndex); + const csViewport = this.getCornerstoneViewport(viewportId); if (!csViewport) { return; } @@ -228,55 +190,80 @@ class CornerstoneViewportService extends PubSubService }; } + public storePresentation({ viewportId }) { + const stateSyncService = this.servicesManager.services.stateSyncService; + let presentation; + try { + presentation = this.getPresentation(viewportId); + } catch (error) { + console.warn(error); + } + + if (!presentation || !presentation.presentationIds) { + return; + } + const { + lutPresentationStore, + positionPresentationStore, + } = stateSyncService.getState(); + const { presentationIds } = presentation; + const { lutPresentationId, positionPresentationId } = presentationIds || {}; + const storeState = {}; + if (lutPresentationId) { + storeState.lutPresentationStore = { + ...lutPresentationStore, + [lutPresentationId]: presentation, + }; + } + if (positionPresentationId) { + storeState.positionPresentationStore = { + ...positionPresentationStore, + [positionPresentationId]: presentation, + }; + } + stateSyncService.store(storeState); + } + /** - * Uses the renderingEngine to enable the element for the given viewport index - * and sets the displaySet data to the viewport - * @param {*} viewportIndex - * @param {*} displaySet - * @param {*} dataSource - * @returns + * Sets the viewport data for a viewport. + * @param viewportId - The ID of the viewport to set the data for. + * @param viewportData - The viewport data to set. + * @param publicViewportOptions - The public viewport options. + * @param publicDisplaySetOptions - The public display set options. + * @param presentations - The presentations to set. */ public setViewportData( - viewportIndex: number, + viewportId: string, viewportData: StackViewportData | VolumeViewportData, publicViewportOptions: PublicViewportOptions, publicDisplaySetOptions: DisplaySetOptions[], presentations?: Presentations ): void { const renderingEngine = this.getRenderingEngine(); - const viewportId = - publicViewportOptions.viewportId || this.getViewportId(viewportIndex); - if (!viewportId) { - throw new Error('Must define viewportId externally'); - } + // This is the old viewportInfo, which may have old options but we might be + // using its viewport (same viewportId as the new viewportInfo) const viewportInfo = this.viewportsById.get(viewportId); + // We should store the presentation for the current viewport since we can't only + // rely to store it WHEN the viewport is disabled since we might keep around the + // same viewport/element and just change the viewportData for it (drag and drop etc.) + // the disableElement storePresentation handle would not be called in this case + // and we would lose the presentation. + this.storePresentation({ viewportId: viewportInfo.getViewportId() }); + if (!viewportInfo) { - throw new Error('Viewport info not defined'); + throw new Error('element is not enabled for the given viewportId'); } - // If the viewport has moved index, then record the new index - if (viewportInfo.viewportIndex !== viewportIndex) { - this.viewportsInfo.delete(viewportInfo.viewportIndex); - this.viewportsInfo.set(viewportIndex, viewportInfo); - viewportInfo.viewportIndex = viewportIndex; - } - - viewportInfo.setRenderingEngineId(renderingEngine.id); - - const { - viewportOptions, - displaySetOptions, - } = this._getViewportAndDisplaySetOptions( - publicViewportOptions, - publicDisplaySetOptions, - viewportInfo + // override the viewportOptions and displaySetOptions with the public ones + // since those are the newly set ones, we set them here so that it handles defaults + const displaySetOptions = viewportInfo.setPublicDisplaySetOptions( + publicDisplaySetOptions + ); + const viewportOptions = viewportInfo.setPublicViewportOptions( + publicViewportOptions ); - - viewportInfo.setViewportOptions(viewportOptions); - viewportInfo.setDisplaySetOptions(displaySetOptions); - viewportInfo.setViewportData(viewportData); const element = viewportInfo.getElement(); const type = viewportInfo.getViewportType(); @@ -293,12 +280,26 @@ class CornerstoneViewportService extends PubSubService }, }; + // Rendering Engine Id set should happen before enabling the element + // since there are callbacks that depend on the renderingEngine id + // Todo: however, this is a limitation which means that we can't change + // the rendering engine id for a given viewport which might be a super edge + // case + viewportInfo.setRenderingEngineId(renderingEngine.id); + // Todo: this is not optimal at all, we are re-enabling the already enabled // element which is not what we want. But enabledElement as part of the // renderingEngine is designed to be used like this. This will trigger // ENABLED_ELEMENT again and again, which will run onEnableElement callbacks renderingEngine.enableElement(viewportInput); + viewportInfo.setViewportOptions(viewportOptions); + viewportInfo.setDisplaySetOptions(displaySetOptions); + viewportInfo.setViewportData(viewportData); + viewportInfo.setViewportId(viewportId); + + this.viewportsById.set(viewportId, viewportInfo); + const viewport = renderingEngine.getViewport(viewportId); this._setDisplaySets(viewport, viewportData, viewportInfo, presentations); @@ -307,7 +308,6 @@ class CornerstoneViewportService extends PubSubService // invalid data. this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { viewportData, - viewportIndex, viewportId, }); } @@ -330,43 +330,8 @@ class CornerstoneViewportService extends PubSubService return viewport; } - public getCornerstoneViewportByIndex( - viewportIndex: number - ): Types.IStackViewport | Types.IVolumeViewport | null { - const viewportInfo = this.getViewportInfoByIndex(viewportIndex); - - if ( - !viewportInfo || - !this.renderingEngine || - this.renderingEngine.hasBeenDestroyed - ) { - return null; - } - - const viewport = this.renderingEngine.getViewport( - viewportInfo.getViewportId() - ); - - return viewport; - } - - /** - * Returns the viewportIndex for the provided viewportId - * @param {string} viewportId - the viewportId - * @returns {number} - the viewportIndex - */ - public getViewportInfoByIndex(viewportIndex: number): ViewportInfo { - return this.viewportsInfo.get(viewportIndex); - } - public getViewportInfo(viewportId: string): ViewportInfo { - // @ts-ignore - for (const [index, viewport] of this.viewportsInfo.entries()) { - if (viewport.getViewportId() === viewportId) { - return viewport; - } - } - return null; + return this.viewportsById.get(viewportId); } _setStackViewport( @@ -414,7 +379,7 @@ class CornerstoneViewportService extends PubSubService } viewport.setStack(imageIds, initialImageIndexToUse).then(() => { - viewport.setProperties(properties); + viewport.setProperties({ ...properties }); const camera = presentations.positionPresentation?.camera; if (camera) { viewport.setCamera(camera); @@ -541,10 +506,7 @@ class CornerstoneViewportService extends PubSubService this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs); - if ( - hangingProtocolService.hasCustomImageLoadStrategy() && - !hangingProtocolService.customImageLoadPerformed - ) { + if (hangingProtocolService.getShouldPerformCustomImageLoad()) { // delegate the volume loading to the hanging protocol service if it has a custom image load strategy return hangingProtocolService.runImageLoadStrategy({ viewportId: viewport.id, @@ -744,14 +706,8 @@ class CornerstoneViewportService extends PubSubService // Todo: keepCamera is an interim solution until we have a better solution for // keeping the camera position when the viewport data is changed - public updateViewport( - viewportIndex: number, - viewportData, - keepCamera = false - ) { - const viewportInfo = this.getViewportInfoByIndex(viewportIndex); - - const viewportId = viewportInfo.getViewportId(); + public updateViewport(viewportId: string, viewportData, keepCamera = false) { + const viewportInfo = this.getViewportInfo(viewportId); const viewport = this.getCornerstoneViewport(viewportId); const viewportCamera = viewport.getCamera(); @@ -841,36 +797,6 @@ class CornerstoneViewportService extends PubSubService } } - _getViewportAndDisplaySetOptions( - publicViewportOptions: PublicViewportOptions, - publicDisplaySetOptions: DisplaySetOptions[], - viewportInfo: ViewportInfo - ): { - viewportOptions: ViewportOptions; - displaySetOptions: DisplaySetOptions[]; - } { - const viewportIndex = viewportInfo.getViewportIndex(); - - // Creating a temporary viewportInfo to handle defaults - const newViewportInfo = new ViewportInfo( - viewportIndex, - viewportInfo.getViewportId() - ); - - // To handle setting the default values if missing for the viewportOptions and - // displaySetOptions - newViewportInfo.setPublicViewportOptions(publicViewportOptions); - newViewportInfo.setPublicDisplaySetOptions(publicDisplaySetOptions); - - const newViewportOptions = newViewportInfo.getViewportOptions(); - const newDisplaySetOptions = newViewportInfo.getDisplaySetOptions(); - - return { - viewportOptions: newViewportOptions, - displaySetOptions: newDisplaySetOptions, - }; - } - _getFrameOfReferenceUID(displaySetInstanceUID) { const { displaySetService } = this.servicesManager.services; const displaySet = displaySetService.getDisplaySetByUID( @@ -907,25 +833,25 @@ class CornerstoneViewportService extends PubSubService * * @param measurement * The measurement that is desired to view. - * @param activeViewportIndex - the index that was active at the time the jump + * @param activeViewportId - the index that was active at the time the jump * was initiated. - * @return the viewportIndex to display the given measurement + * @return the viewportId that the measurement should be displayed in. */ - public getViewportIndexToJump( - activeViewportIndex: number, + public getViewportIdToJump( + activeViewportId: string, displaySetInstanceUID: string, cameraProps: unknown - ): number { - const viewportInfo = this.viewportsInfo.get(activeViewportIndex); + ): string { + const viewportInfo = this.getViewportInfo(activeViewportId); const { referencedImageId } = cameraProps; if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) { - return activeViewportIndex; + return activeViewportId; } return ( [...this.viewportsById.values()].find(viewportInfo => viewportInfo.contains(displaySetInstanceUID, referencedImageId) - )?.viewportIndex ?? -1 + )?.viewportId ?? null ); } } diff --git a/extensions/cornerstone/src/services/ViewportService/IViewportService.ts b/extensions/cornerstone/src/services/ViewportService/IViewportService.ts index 87168cb7f..82f6d2058 100644 --- a/extensions/cornerstone/src/services/ViewportService/IViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/IViewportService.ts @@ -17,7 +17,6 @@ export interface IViewportService { viewportGridResizeObserver: unknown; viewportsInfo: unknown; sceneVolumeInputs: unknown; - viewportIndexUIDMap: unknown; viewportDivElements: unknown; ViewportPropertiesMap: unknown; volumeUIDs: unknown; @@ -28,14 +27,9 @@ export interface IViewportService { _broadcastEvent: unknown; /** * Adds the HTML element to the viewportService - * @param {*} viewportIndex * @param {*} elementRef */ - enableViewport( - viewportIndex: number, - viewportOptions: ViewportOptions, - elementRef: HTMLDivElement - ): void; + enableViewport(viewportId: string, elementRef: HTMLDivElement): void; /** * It retrieves the renderingEngine if it does exist, or creates one otherwise * @returns {RenderingEngine} rendering engine @@ -54,19 +48,17 @@ export interface IViewportService { /** * Disables the viewport inside the renderingEngine, if no viewport is left * it destroys the renderingEngine. - * @param viewportIndex + * @param viewportId */ - disableElement(viewportIndex: number): void; + disableElement(viewportId: string): void; /** * Uses the renderingEngine to enable the element for the given viewport index * and sets the displaySet data to the viewport - * @param {*} viewportIndex * @param {*} displaySet * @param {*} dataSource * @returns */ setViewportData( - viewportIndex: number, viewportData: StackData | VolumeData, publicViewportOptions: PublicViewportOptions, publicDisplaySetOptions: DisplaySetOptions[] diff --git a/extensions/cornerstone/src/services/ViewportService/Viewport.ts b/extensions/cornerstone/src/services/ViewportService/Viewport.ts index 75e976d4e..d27f2129f 100644 --- a/extensions/cornerstone/src/services/ViewportService/Viewport.ts +++ b/extensions/cornerstone/src/services/ViewportService/Viewport.ts @@ -106,15 +106,13 @@ const dataContains = ( class ViewportInfo { private viewportId = ''; - private viewportIndex: number; private element: HTMLDivElement; private viewportOptions: ViewportOptions; private displaySetOptions: Array; private viewportData: StackViewportData | VolumeViewportData; private renderingEngineId: string; - constructor(viewportIndex: number, viewportId: string) { - this.viewportIndex = viewportIndex; + constructor(viewportId: string) { this.viewportId = viewportId; this.setPublicViewportOptions({}); this.setPublicDisplaySetOptions([{}]); @@ -155,9 +153,6 @@ class ViewportInfo { public setViewportId(viewportId: string): void { this.viewportId = viewportId; } - public setViewportIndex(viewportIndex: number): void { - this.viewportIndex = viewportIndex; - } public setElement(element: HTMLDivElement): void { this.element = element; @@ -173,9 +168,6 @@ class ViewportInfo { return this.viewportData; } - public getViewportIndex(): number { - return this.viewportIndex; - } public getElement(): HTMLDivElement { return this.element; @@ -187,13 +179,15 @@ class ViewportInfo { public setPublicDisplaySetOptions( publicDisplaySetOptions: PublicDisplaySetOptions[] | DisplaySetSelector[] - ): void { + ): Array { // map the displaySetOptions and check if they are undefined then set them to default values const displaySetOptions = this.mapDisplaySetOptions( publicDisplaySetOptions ); this.setDisplaySetOptions(displaySetOptions); + + return this.displaySetOptions } public hasDisplaySet(displaySetInstanceUID: string): boolean { @@ -217,7 +211,7 @@ class ViewportInfo { public setPublicViewportOptions( viewportOptionsEntry: PublicViewportOptions - ): void { + ): ViewportOptions { let viewportType = viewportOptionsEntry.viewportType; const { toolGroupId = DEFAULT_TOOLGROUP_ID, @@ -250,6 +244,8 @@ class ViewportInfo { toolGroupId, presentationIds, }); + + return this.viewportOptions; } public setViewportOptions(viewportOptions: ViewportOptions): void { diff --git a/extensions/cornerstone/src/state.ts b/extensions/cornerstone/src/state.ts index e1d67a561..821078c1d 100644 --- a/extensions/cornerstone/src/state.ts +++ b/extensions/cornerstone/src/state.ts @@ -10,13 +10,13 @@ const state = { * @return void */ const setEnabledElement = ( - viewportIndex: number, + viewportId: string, element: HTMLElement, context?: string ): void => { const targetContext = context || state.DEFAULT_CONTEXT; - state.enabledElements[viewportIndex] = { + state.enabledElements[viewportId] = { element, context: targetContext, }; @@ -27,8 +27,8 @@ const setEnabledElement = ( * * @return {HTMLElement} Active viewport element. */ -const getEnabledElement = viewportIndex => { - return state.enabledElements[viewportIndex]; +const getEnabledElement = viewportId => { + return state.enabledElements[viewportId]; }; const reset = () => { diff --git a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx index cfab216e7..b9971059b 100644 --- a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx +++ b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx @@ -20,10 +20,10 @@ const VIEWPORT_ID = 'cornerstone-viewport-download-form'; const CornerstoneViewportDownloadForm = ({ onClose, - activeViewportIndex, + activeViewportId: activeViewportIdProp, cornerstoneViewportService, }) => { - const enabledElement = OHIFgetEnabledElement(activeViewportIndex); + const enabledElement = OHIFgetEnabledElement(activeViewportIdProp); const activeViewportElement = enabledElement?.element; const activeViewportEnabledElement = getEnabledElement(activeViewportElement); @@ -268,7 +268,7 @@ const CornerstoneViewportDownloadForm = ({ CornerstoneViewportDownloadForm.propTypes = { onClose: PropTypes.func, - activeViewportIndex: PropTypes.number.isRequired, + activeViewportId: PropTypes.string.isRequired, }; export default CornerstoneViewportDownloadForm; diff --git a/extensions/cornerstone/src/utils/getActiveViewportEnabledElement.ts b/extensions/cornerstone/src/utils/getActiveViewportEnabledElement.ts index d188ece9a..d067622e0 100644 --- a/extensions/cornerstone/src/utils/getActiveViewportEnabledElement.ts +++ b/extensions/cornerstone/src/utils/getActiveViewportEnabledElement.ts @@ -6,8 +6,8 @@ import { getEnabledElement as OHIFgetEnabledElement } from '../state'; export default function getActiveViewportEnabledElement( viewportGridService ): IEnabledElement { - const { activeViewportIndex } = viewportGridService.getState(); - const { element } = OHIFgetEnabledElement(activeViewportIndex) || {}; + const { activeViewportId } = viewportGridService.getState(); + const { element } = OHIFgetEnabledElement(activeViewportId) || {}; const enabledElement = getEnabledElement(element); return enabledElement; } diff --git a/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts b/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts index 5a0de4677..7f7ce32cc 100644 --- a/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts +++ b/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts @@ -2,7 +2,7 @@ import calculateViewportRegistrations from './calculateViewportRegistrations'; // [ { // synchronizerId: string, -// viewports: [ { viewportId: number, renderingEngineId: string, index: number } , ...] +// viewports: [ { viewportId: string, renderingEngineId: string, index: number } , ...] // ]} let STACK_IMAGE_SYNC_GROUPS_INFO = []; @@ -37,32 +37,25 @@ export default function toggleStackImageSync({ STACK_IMAGE_SYNC_GROUPS_INFO = []; // create synchronization groups and add viewports - let { viewports } = viewportGridService.getState(); + const { viewports } = viewportGridService.getState(); // filter empty viewports - viewports = viewports.filter( - viewport => - viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length - ); + const viewportsArray = Array.from(viewports.values()) + .filter(viewport => viewport.displaySetInstanceUIDs?.length) + // filter reconstructable viewports + .filter(viewport => { + const { displaySetInstanceUIDs } = viewport; - // filter reconstructable viewports - viewports = viewports.filter(viewport => { - const { displaySetInstanceUIDs } = viewport; + for (const displaySetInstanceUID of displaySetInstanceUIDs) { + const displaySet = displaySetService.getDisplaySetByUID( + displaySetInstanceUID + ); - for (const displaySetInstanceUID of displaySetInstanceUIDs) { - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); - - if (displaySet && displaySet.isReconstructable) { - return true; + return !!displaySet?.isReconstructable; } + }); - return false; - } - }); - - const viewportsByOrientation = viewports.reduce((acc, viewport) => { + const viewportsByOrientation = viewportsArray.reduce((acc, viewport) => { const { viewportId, viewportType } = viewport.viewportOptions; if (viewportType !== 'stack') { @@ -71,9 +64,8 @@ export default function toggleStackImageSync({ } const { element } = cornerstoneViewportService.getViewportInfo(viewportId); - const { viewport: csViewport, renderingEngineId } = getEnabledElement( - element - ); + const { viewport: csViewport, renderingEngineId } = + getEnabledElement(element); const { viewPlaneNormal } = csViewport.getCamera(); // Should we round here? I guess so, but not sure how much precision we need diff --git a/extensions/default/src/Panels/PanelMeasurementTable.tsx b/extensions/default/src/Panels/PanelMeasurementTable.tsx index 7d6be4aab..49063e566 100644 --- a/extensions/default/src/Panels/PanelMeasurementTable.tsx +++ b/extensions/default/src/Panels/PanelMeasurementTable.tsx @@ -25,7 +25,7 @@ export default function PanelMeasurementTable({ extensionManager, }): React.FunctionComponent { const [viewportGrid, viewportGridService] = useViewportGrid(); - const { activeViewportIndex, viewports } = viewportGrid; + const { activeViewportId, viewports } = viewportGrid; const { measurementService, uiDialogService, @@ -80,7 +80,7 @@ export default function PanelMeasurementTable({ async function createReport(): Promise { // filter measurements that are added to the active study - const activeViewport = viewports[activeViewportIndex]; + const activeViewport = viewports.get(activeViewportId); const measurements = measurementService.getMeasurements(); const displaySet = displaySetService.getDisplaySetByUID( activeViewport.displaySetInstanceUIDs[0] @@ -133,7 +133,7 @@ export default function PanelMeasurementTable({ } const jumpToImage = ({ uid, isActive }) => { - measurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, uid); + measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid); onMeasurementItemClickHandler({ uid, isActive }); }; diff --git a/extensions/default/src/Panels/PanelStudyBrowser.tsx b/extensions/default/src/Panels/PanelStudyBrowser.tsx index 512b47d05..98a765479 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.tsx @@ -29,7 +29,7 @@ function PanelStudyBrowser({ // Tabs --> Studies --> DisplaySets --> Thumbnails const { StudyInstanceUIDs } = useImageViewer(); const [ - { activeViewportIndex, viewports }, + { activeViewportId, viewports }, viewportGridService, ] = useViewportGrid(); const [activeTabName, setActiveTabName] = useState('primary'); @@ -43,10 +43,10 @@ function PanelStudyBrowser({ const onDoubleClickThumbnailHandler = displaySetInstanceUID => { let updatedViewports = []; - const viewportIndex = activeViewportIndex; + const viewportId = activeViewportId; try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, + viewportId, displaySetInstanceUID ); } catch (error) { @@ -262,8 +262,8 @@ function PanelStudyBrowser({ } } - const activeDisplaySetInstanceUIDs = - viewports[activeViewportIndex]?.displaySetInstanceUIDs; + const activeDisplaySetInstanceUIDs = viewports.get(activeViewportId) + ?.displaySetInstanceUIDs; return ( 1 ? [] : displaySetInstanceUIDs .map(displaySetInstanceUID => hangingProtocolService.getViewportsRequireUpdate( - viewportIndexToUpdate, + viewportIdToUpdate, displaySetInstanceUID ) ) @@ -432,14 +428,37 @@ const commandsModule = ({ // This findOrCreateViewport returns either one of the updatedViewports // returned from the HP service OR if there is not one from the HP service then // simply returns what was in the previous state. - const findOrCreateViewport = (viewportIndex: number) => { - const viewport = updatedViewports.find( - viewport => viewport.viewportIndex === viewportIndex - ); + const findOrCreateViewport = (position: number) => { + // get the viewportId in the current state (since we are in the one-up layout) + const currentOneUpViewport = Array.from(viewports.values())[0]; - return viewport - ? { viewportOptions, displaySetOptions, ...viewport } - : toggleOneUpViewportGridStore.viewports[viewportIndex]; + // we should restore the previous layout but take into the account the fact that + // the current one up viewport might have a new displaySet dragged and dropped on it + // so we should prioritize the current one in the old grid store layout viewports + + const newViewports = Array.from( + toggleOneUpViewportGridStore.viewports.values() + ).map(viewport => { + if (viewport.viewportId === currentOneUpViewport.viewportId) { + return { + ...currentOneUpViewport, + }; + } + + return viewport; + }); + + // However, we also need to take into account that the current one up viewport + // might have been part of a bigger hanging protocol layout, so going back + // from one up we should apply those viewports as well. + return updatedViewportsViaHP.length > 1 && + updatedViewportsViaHP[position] + ? { + viewportOptions, + displaySetOptions, + ...updatedViewportsViaHP[position], + } + : newViewports[position]; }; const layoutOptions = viewportGridService.getLayoutOptionsFromState( @@ -450,7 +469,7 @@ const commandsModule = ({ viewportGridService.setLayout({ numRows: toggleOneUpViewportGridStore.layout.numRows, numCols: toggleOneUpViewportGridStore.layout.numCols, - activeViewportIndex: viewportIndexToUpdate, + activeViewportId: viewportIdToUpdate, layoutOptions, findOrCreateViewport, }); @@ -520,8 +539,8 @@ const commandsModule = ({ }, openDICOMTagViewer() { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const activeViewportSpecificData = viewports[activeViewportIndex]; + const { activeViewportId, viewports } = viewportGridService.getState(); + const activeViewportSpecificData = viewports.get(activeViewportId); const { displaySetInstanceUIDs } = activeViewportSpecificData; const displaySets = displaySetService.activeDisplaySets; @@ -552,17 +571,9 @@ const commandsModule = ({ }, scrollActiveThumbnailIntoView: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); + const { activeViewportId, viewports } = viewportGridService.getState(); - if ( - !viewports || - activeViewportIndex < 0 || - activeViewportIndex > viewports.length - 1 - ) { - return; - } - - const activeViewport = viewports[activeViewportIndex]; + const activeViewport = viewports.get(activeViewportId); const activeDisplaySetInstanceUID = activeViewport.displaySetInstanceUIDs[0]; @@ -615,9 +626,9 @@ const commandsModule = ({ currentDisplaySets.sort(dsSortFn); - const { activeViewportIndex, viewports } = viewportGridService.getState(); + const { activeViewportId, viewports } = viewportGridService.getState(); - const { displaySetInstanceUIDs } = viewports[activeViewportIndex]; + const { displaySetInstanceUIDs } = viewports.get(activeViewportId); const activeDisplaySetIndex = currentDisplaySets.findIndex(displaySet => displaySetInstanceUIDs.includes(displaySet.displaySetInstanceUID) @@ -656,7 +667,7 @@ const commandsModule = ({ try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - activeViewportIndex, + activeViewportId, displaySetInstanceUID ); } catch (error) { diff --git a/extensions/default/src/findViewportsByPosition.ts b/extensions/default/src/findViewportsByPosition.ts index 5132290a4..c7319bd4a 100644 --- a/extensions/default/src/findViewportsByPosition.ts +++ b/extensions/default/src/findViewportsByPosition.ts @@ -1,4 +1,4 @@ -import { StateSyncService, Types } from '@ohif/core'; +import { StateSyncService } from '@ohif/core'; /** * This find or create viewport is paired with the reduce results from @@ -7,7 +7,7 @@ import { StateSyncService, Types } from '@ohif/core'; * then one can be re-used from the display set if it isn't going to be displayed. * @param hangingProtocolService - bound parameter supplied before using this * @param viewportsByPosition - bound parameter supplied before using this - * @param viewportIndex - the index to retrieve + * @param position - the position in the grid to retrieve * @param positionId - the current position on screen to retrieve * @param options - the set of options used, so that subsequent calls can * store state that is reset by the setLayout. @@ -17,7 +17,7 @@ import { StateSyncService, Types } from '@ohif/core'; export const findOrCreateViewport = ( hangingProtocolService, viewportsByPosition, - viewportIndex: number, + position: number, positionId: string, options: Record ) => { @@ -74,24 +74,19 @@ const findViewportsByPosition = ( const viewportsByPosition = { ...syncState.viewportsByPosition }; const initialInDisplay = []; - for (const viewport of viewports) { + viewports.forEach(viewport => { if (viewport.positionId) { const storedViewport = { ...viewport, viewportOptions: { ...viewport.viewportOptions }, }; viewportsByPosition[viewport.positionId] = storedViewport; - // The cache doesn't store the viewport options - it is only useful - // for remembering the type of viewport and UIDs - delete storedViewport.viewportId; - delete storedViewport.viewportOptions.viewportId; } - } + }); for (let row = 0; row < numRows; row++) { for (let col = 0; col < numCols; col++) { - const pos = col + row * numCols; - const positionId = viewports?.[pos]?.positionId || `${col}-${row}`; + const positionId = `${col}-${row}`; const viewport = viewportsByPosition[positionId]; if (viewport?.displaySetInstanceUIDs) { initialInDisplay.push(...viewport.displaySetInstanceUIDs); diff --git a/extensions/default/src/getHangingProtocolModule.js b/extensions/default/src/getHangingProtocolModule.js index 2df134f48..596e3031d 100644 --- a/extensions/default/src/getHangingProtocolModule.js +++ b/extensions/default/src/getHangingProtocolModule.js @@ -73,6 +73,7 @@ const defaultProtocol = { { viewportOptions: { viewportType: 'stack', + viewportId: 'default', toolGroupId: 'default', // This will specify the initial image options index if it matches in the URL // and will otherwise not specify anything. diff --git a/extensions/default/src/utils/reuseCachedLayouts.ts b/extensions/default/src/utils/reuseCachedLayouts.ts index 35bb1774f..f84c24d3d 100644 --- a/extensions/default/src/utils/reuseCachedLayouts.ts +++ b/extensions/default/src/utils/reuseCachedLayouts.ts @@ -19,22 +19,24 @@ const reuseCachedLayout = ( hangingProtocolService: HangingProtocolService, syncService: StateSyncService ): ReturnType => { - const { activeViewportIndex, viewports, layout } = state; + const { activeViewportId } = state; + const { protocol } = hangingProtocolService.getActiveProtocol(); const hpInfo = hangingProtocolService.getState(); const { protocolId, stageIndex, activeStudyUID } = hpInfo; - const { protocol } = hangingProtocolService.getActiveProtocol(); + + const syncState = syncService.getState(); + const viewportGridStore = { ...syncState.viewportGridStore }; + const displaySetSelectorMap = { ...syncState.displaySetSelectorMap }; + const stage = protocol.stages[stageIndex]; const storeId = `${activeStudyUID}:${protocolId}:${stageIndex}`; - const syncState = syncService.getState(); const cacheId = `${activeStudyUID}:${protocolId}`; - const viewportGridStore = { ...syncState.viewportGridStore }; const hangingProtocolStageIndexMap = { ...syncState.hangingProtocolStageIndexMap, }; - const displaySetSelectorMap = { ...syncState.displaySetSelectorMap }; const { rows, columns } = stage.viewportStructure.properties; const custom = - stage.viewports.length !== state.viewports.length || + stage.viewports.length !== state.viewports.size || state.layout.numRows !== rows || state.layout.numCols !== columns; @@ -44,18 +46,17 @@ const reuseCachedLayout = ( viewportGridStore[storeId] = { ...state }; } - for (let idx = 0; idx < state.viewports.length; idx++) { - const viewport = state.viewports[idx]; + state.viewports.forEach((viewport, viewportId) => { const { displaySetOptions, displaySetInstanceUIDs } = viewport; if (!displaySetOptions) { - continue; + return; } for (let i = 0; i < displaySetOptions.length; i++) { const displaySetUID = displaySetInstanceUIDs[i]; if (!displaySetUID) { continue; } - if (idx === activeViewportIndex && i === 0) { + if (viewportId === activeViewportId && i === 0) { displaySetSelectorMap[ `${activeStudyUID}:activeDisplaySet:0` ] = displaySetUID; @@ -67,7 +68,7 @@ const reuseCachedLayout = ( ] = displaySetUID; } } - } + }); return { hangingProtocolStageIndexMap, diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx index de2220593..3be440040 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx +++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx @@ -49,12 +49,12 @@ class DicomMicroscopyViewport extends Component { static propTypes = { viewportData: PropTypes.object, - activeViewportIndex: PropTypes.number, + activeViewportId: PropTypes.string, setViewportActive: PropTypes.func, // props from OHIF Viewport Grid displaySets: PropTypes.array, - viewportIndex: PropTypes.number, + viewportId: PropTypes.string, viewportLabel: PropTypes.string, dataSource: PropTypes.object, viewportOptions: PropTypes.object, @@ -230,7 +230,7 @@ class DicomMicroscopyViewport extends Component { this.managedViewer = this.microscopyService.addViewer( this.viewer, - this.props.viewportIndex, + this.props.viewportId, container, StudyInstanceUID, SeriesInstanceUID @@ -259,8 +259,10 @@ class DicomMicroscopyViewport extends Component { } componentDidMount() { - const { displaySets, viewportIndex } = this.props; - const displaySet = displaySets[viewportIndex]; + const { displaySets, viewportOptions } = this.props; + const { viewportId } = viewportOptions; + // Todo-rename: this is always getting the 0 + const displaySet = displaySets[0]; this.installOpenLayersRenderer(this.container.current, displaySet).then( () => { this.setState({ isLoaded: true }); @@ -295,14 +297,10 @@ class DicomMicroscopyViewport extends Component { } setViewportActiveHandler = () => { - const { - setViewportActive, - viewportIndex, - activeViewportIndex, - } = this.props; + const { setViewportActive, viewportId, activeViewportId } = this.props; - if (viewportIndex !== activeViewportIndex) { - setViewportActive(viewportIndex); + if (viewportId !== activeViewportId) { + setViewportActive(viewportId); } }; diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx index 88d5f4392..96dc158e0 100644 --- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx +++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx @@ -52,7 +52,7 @@ const formatLength = (length, unit) => { interface IMicroscopyPanelProps extends WithTranslation { viewports: PropTypes.array; - activeViewportIndex: PropTypes.number; + activeViewportId: PropTypes.string; // onSaveComplete?: PropTypes.func; // callback when successfully saved annotations @@ -83,7 +83,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { const { uiDialogService, displaySetService } = servicesManager.services; useEffect(() => { - const viewport = props.viewports[props.activeViewportIndex]; + const viewport = props.viewports[props.activeViewportId]; if (viewport.displaySetInstanceUIDs[0]) { const displaySet = displaySetService.getDisplaySetByUID( viewport.displaySetInstanceUIDs[0] @@ -92,7 +92,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { setStudyInstanceUID(displaySet.StudyInstanceUID); } } - }, [props.viewports, props.activeViewportIndex]); + }, [props.viewports, props.activeViewportId]); useEffect(() => { const onAnnotationUpdated = () => { @@ -265,7 +265,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { */ const onDeleteCurrentSRHandler = async () => { try { - const activeViewport = props.viewports[props.activeViewportIndex]; + const activeViewport = props.viewports[props.activeViewportId]; const { StudyInstanceUID } = activeViewport; // TODO: studies? @@ -307,7 +307,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { const onMeasurementItemClickHandler = ({ uid }: { uid: string }) => { const roiAnnotation = microscopyService.getAnnotation(uid); microscopyService.selectAnnotation(roiAnnotation); - microscopyService.focusAnnotation(roiAnnotation, props.activeViewportIndex); + microscopyService.focusAnnotation(roiAnnotation, props.activeViewportId); }; /** diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts index 1a7a42187..cc2133102 100644 --- a/extensions/dicom-microscopy/src/getCommandsModule.ts +++ b/extensions/dicom-microscopy/src/getCommandsModule.ts @@ -122,19 +122,6 @@ export default function getCommandsModule({ ]); } }, - - incrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const nextViewportIndex = (activeViewportIndex + 1) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); - }, - decrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const nextViewportIndex = - (activeViewportIndex - 1 + viewports.length) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); - }, - toggleOverlays: () => { // overlay const overlays = document.getElementsByClassName( @@ -149,8 +136,8 @@ export default function getCommandsModule({ } // overview - const { activeViewportIndex, viewports } = viewportGridService.getState(); - microscopyService.toggleOverviewMap(activeViewportIndex); + const { activeViewportId, viewports } = viewportGridService.getState(); + microscopyService.toggleOverviewMap(activeViewportId); }, toggleAnnotations: () => { microscopyService.toggleROIsVisibility(); @@ -173,14 +160,6 @@ export default function getCommandsModule({ storeContexts: [] as any[], options: {}, }, - incrementActiveViewport: { - commandFn: actions.incrementActiveViewport, - storeContexts: [] as any[], - }, - decrementActiveViewport: { - commandFn: actions.decrementActiveViewport, - storeContexts: [] as any[], - }, toggleOverlays: { commandFn: actions.toggleOverlays, storeContexts: [] as any[], diff --git a/extensions/dicom-microscopy/src/getPanelModule.tsx b/extensions/dicom-microscopy/src/getPanelModule.tsx index ef49c9331..58e4f2f09 100644 --- a/extensions/dicom-microscopy/src/getPanelModule.tsx +++ b/extensions/dicom-microscopy/src/getPanelModule.tsx @@ -18,15 +18,12 @@ export default function getPanelModule({ extensionManager: ExtensionManager; }) { const wrappedMeasurementPanel = () => { - const [ - { activeViewportIndex, viewports }, - viewportGridService, - ] = useViewportGrid(); + const [{ activeViewportId, viewports }] = useViewportGrid(); return ( {}} onRejectComplete={() => {}} commandsManager={commandsManager} diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx index 66dfeacd9..98406fe52 100644 --- a/extensions/dicom-microscopy/src/index.tsx +++ b/extensions/dicom-microscopy/src/index.tsx @@ -52,7 +52,7 @@ export default { * * @param props {*} * @param props.displaySets - * @param props.viewportIndex + * @param props.viewportId * @param props.viewportLabel * @param props.dataSource * @param props.viewportOptions @@ -63,16 +63,16 @@ export default { const { viewportOptions } = props; const [viewportGrid, viewportGridService] = useViewportGrid(); - const { viewports, activeViewportIndex } = viewportGrid; + const { activeViewportId } = viewportGrid; return ( { - viewportGridService.setActiveViewportIndex(viewportIndex); + activeViewportId={activeViewportId} + setViewportActive={(viewportId: string) => { + viewportGridService.setActiveViewportId(viewportId); }} viewportData={viewportOptions} {...props} diff --git a/extensions/dicom-microscopy/src/services/MicroscopyService.ts b/extensions/dicom-microscopy/src/services/MicroscopyService.ts index a6730511c..74d3d84a6 100644 --- a/extensions/dicom-microscopy/src/services/MicroscopyService.ts +++ b/extensions/dicom-microscopy/src/services/MicroscopyService.ts @@ -286,31 +286,31 @@ export default class MicroscopyService extends PubSubService { } /** - * Creates a managed viewer instance for the given thrid-party API's viewer. + * Creates a managed viewer instance for the given third-party API's viewer. * Restores existing annotations for the given study/series. * Adds event subscriptions for the viewer being added. * Focuses the selected annotation when the viewer is being loaded into the * active viewport. * - * @param {Object} viewer Third-party viewer API's object to be managed - * @param {Number} viewportIndex The index of the viewport to load the viewer - * @param {HTMLElement} container The DOM element where it will be renderd - * @param {String} studyInstanceUID The study UID of the loaded image - * @param {String} seriesInstanceUID The series UID of the loaded image - * @param {Array} displaySets All displaySets related to the same StudyInstanceUID + * @param viewer - Third-party viewer API's object to be managed + * @param viewportId - The viewport Id where the viewer will be loaded + * @param container - The DOM element where it will be rendered + * @param studyInstanceUID - The study UID of the loaded image + * @param seriesInstanceUID - The series UID of the loaded image + * @param displaySets - All displaySets related to the same StudyInstanceUID * * @returns {ViewerManager} managed viewer */ addViewer( viewer, - viewportIndex, + viewportId, container, studyInstanceUID, seriesInstanceUID ) { const managedViewer = new ViewerManager( viewer, - viewportIndex, + viewportId, container, studyInstanceUID, seriesInstanceUID @@ -325,7 +325,7 @@ export default class MicroscopyService extends PubSubService { if (this.pendingFocus) { this.pendingFocus = false; - this.focusAnnotation(this.selectedAnnotation, viewportIndex); + this.focusAnnotation(this.selectedAnnotation, viewportId); } return managedViewer; @@ -502,13 +502,13 @@ export default class MicroscopyService extends PubSubService { /** * Toggles overview map * - * @param viewportIndex The active viewport index + * @param viewportId The active viewport index * @returns {void} */ - toggleOverviewMap(viewportIndex) { + toggleOverviewMap(viewportId) { const managedViewers = Array.from(this.managedViewers); const managedViewer = managedViewers.find( - mv => mv.viewportIndex === viewportIndex + mv => mv.viewportId === viewportId ); if (managedViewer) { managedViewer.toggleOverviewMap(); @@ -550,10 +550,10 @@ export default class MicroscopyService extends PubSubService { * the managed viewer instance is created. * * @param {RoiAnnotation} roiAnnotation RoiAnnotation instance to be focused - * @param {Number} viewportIndex Index of the viewport to focus + * @param {string} viewportId Index of the viewport to focus */ - focusAnnotation(roiAnnotation, viewportIndex) { - const filter = mv => mv.viewportIndex === viewportIndex; + focusAnnotation(roiAnnotation, viewportId) { + const filter = mv => mv.viewportId === viewportId; const managedViewer = Array.from(this.managedViewers).find(filter); if (managedViewer) { managedViewer.setViewStateByExtent(roiAnnotation); diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js index bff7318d7..2777affcf 100644 --- a/extensions/dicom-microscopy/src/tools/viewerManager.js +++ b/extensions/dicom-microscopy/src/tools/viewerManager.js @@ -48,14 +48,14 @@ const EVENTS = { class ViewerManager extends PubSubService { constructor( viewer, - viewportIndex, + viewportId, container, studyInstanceUID, seriesInstanceUID ) { super(EVENTS); this.viewer = viewer; - this.viewportIndex = viewportIndex; + this.viewportId = viewportId; this.container = container; this.studyInstanceUID = studyInstanceUID; this.seriesInstanceUID = seriesInstanceUID; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx index c260cf7d8..e32d0cb88 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx @@ -33,7 +33,7 @@ function TrackedMeasurementsContextProvider( const [appConfig] = useAppConfig(); const [viewportGrid, viewportGridService] = useViewportGrid(); - const { activeViewportIndex, viewports } = viewportGrid; + const { activeViewportId, viewports } = viewportGrid; const { measurementService, displaySetService } = servicesManager.services; const machineOptions = Object.assign({}, defaultOptions); @@ -49,7 +49,7 @@ function TrackedMeasurementsContextProvider( console.log( 'jumping to measurement reset viewport', - viewportGrid.activeViewportIndex, + viewportGrid.activeViewportId, trackedMeasurements[0] ); @@ -84,7 +84,7 @@ function TrackedMeasurementsContextProvider( } viewportGridService.setDisplaySetsForViewport({ - viewportIndex: viewportGrid.activeViewportIndex, + viewportId: viewportGrid.activeViewportId, displaySetInstanceUIDs: [referencedDisplaySetUID], viewportOptions: { initialImageOptions: { @@ -99,7 +99,7 @@ function TrackedMeasurementsContextProvider( evt.data.createdDisplaySetInstanceUIDs[0].displaySetInstanceUID; viewportGridService.setDisplaySetsForViewport({ - viewportIndex: evt.data.viewportIndex, + viewportId: evt.data.viewportId, displaySetInstanceUIDs: [StructuredReportDisplaySetInstanceUID], }); } @@ -172,16 +172,14 @@ function TrackedMeasurementsContextProvider( machineOptions ); - const [ - trackedMeasurements, - sendTrackedMeasurementsEvent, - trackedMeasurementsService, - ] = useMachine(measurementTrackingMachine); + const [trackedMeasurements, sendTrackedMeasurementsEvent] = useMachine( + measurementTrackingMachine + ); // ~~ Listen for changes to ViewportGrid for potential SRs hung in panes when idle useEffect(() => { - if (viewports.length > 0) { - const activeViewport = viewports[activeViewportIndex]; + if (viewports.size > 0) { + const activeViewport = viewports.get(activeViewportId); if (!activeViewport || !activeViewport?.displaySetInstanceUIDs?.length) { return; @@ -229,12 +227,12 @@ function TrackedMeasurementsContextProvider( sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', { displaySetInstanceUID: displaySet.displaySetInstanceUID, SeriesInstanceUID: displaySet.SeriesInstanceUID, - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, }); } } }, [ - activeViewportIndex, + activeViewportId, sendTrackedMeasurementsEvent, servicesManager.services, viewports, diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/hydrateStructuredReport.tsx b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/hydrateStructuredReport.tsx index 5a1be430b..b3f363427 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/hydrateStructuredReport.tsx +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/hydrateStructuredReport.tsx @@ -6,7 +6,7 @@ function hydrateStructuredReport( evt ) { const { displaySetService } = servicesManager.services; - const { viewportIndex, displaySetInstanceUID } = evt; + const { viewportId, displaySetInstanceUID } = evt; const srDisplaySet = displaySetService.getDisplaySetByUID( displaySetInstanceUID ); @@ -23,7 +23,7 @@ function hydrateStructuredReport( resolve({ displaySetInstanceUID: evt.displaySetInstanceUID, srSeriesInstanceUID: srDisplaySet.SeriesInstanceUID, - viewportIndex, + viewportId, StudyInstanceUID, SeriesInstanceUIDs, }); diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js index 4197dd7f1..ae3fb917b 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js @@ -10,24 +10,24 @@ const RESPONSE = { function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) { const { uiViewportDialogService } = servicesManager.services; - const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; + const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt; return new Promise(async function(resolve, reject) { let promptResult = await _askTrackMeasurements( uiViewportDialogService, - viewportIndex + viewportId ); resolve({ userResponse: promptResult, StudyInstanceUID, SeriesInstanceUID, - viewportIndex, + viewportId, }); }); } -function _askTrackMeasurements(uiViewportDialogService, viewportIndex) { +function _askTrackMeasurements(uiViewportDialogService, viewportId) { return new Promise(function(resolve, reject) { const message = 'Track measurements for this series?'; const actions = [ @@ -56,7 +56,7 @@ function _askTrackMeasurements(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, id: 'measurement-tracking-prompt-begin-tracking', type: 'info', message, diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js index 925396b9a..8b3d04688 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js @@ -20,15 +20,15 @@ function promptHydrateStructuredReport( uiViewportDialogService, displaySetService, } = servicesManager.services; - const { viewportIndex, displaySetInstanceUID } = evt; + const { viewportId, displaySetInstanceUID } = evt; const srDisplaySet = displaySetService.getDisplaySetByUID( displaySetInstanceUID ); - return new Promise(async function (resolve, reject) { + return new Promise(async function(resolve, reject) { const promptResult = await _askTrackMeasurements( uiViewportDialogService, - viewportIndex + viewportId ); // Need to do action here... So we can set state... @@ -49,15 +49,15 @@ function promptHydrateStructuredReport( userResponse: promptResult, displaySetInstanceUID: evt.displaySetInstanceUID, srSeriesInstanceUID: srDisplaySet.SeriesInstanceUID, - viewportIndex, + viewportId, StudyInstanceUID, SeriesInstanceUIDs, }); }); } -function _askTrackMeasurements(uiViewportDialogService, viewportIndex) { - return new Promise(function (resolve, reject) { +function _askTrackMeasurements(uiViewportDialogService, viewportId) { + return new Promise(function(resolve, reject) { const message = 'Do you want to continue tracking measurements for this study?'; const actions = [ @@ -78,7 +78,7 @@ function _askTrackMeasurements(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js index 2aa54db80..33c211ee4 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js @@ -13,10 +13,8 @@ function promptSaveReport( measurementService, displaySetService, } = servicesManager.services; - const viewportIndex = - evt.viewportIndex === undefined - ? evt.data.viewportIndex - : evt.viewportIndex; + const viewportId = + evt.viewportId === undefined ? evt.data.viewportId : evt.viewportId; const isBackupSave = evt.isBackupSave === undefined ? evt.data.isBackupSave : evt.isBackupSave; const StudyInstanceUID = evt?.data?.StudyInstanceUID; @@ -66,7 +64,7 @@ function promptSaveReport( createdDisplaySetInstanceUIDs: displaySetInstanceUIDs, StudyInstanceUID, SeriesInstanceUID, - viewportIndex, + viewportId, isBackupSave, }); }); diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js index 005c329a5..e71574169 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js @@ -11,17 +11,17 @@ const RESPONSE = { function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) { const { UIViewportDialogService } = servicesManager.services; - const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; + const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt; return new Promise(async function(resolve, reject) { let promptResult = await _askShouldAddMeasurements( UIViewportDialogService, - viewportIndex + viewportId ); if (promptResult === RESPONSE.CREATE_REPORT) { promptResult = ctx.isDirty - ? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) + ? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportId) : RESPONSE.SET_STUDY_AND_SERIES; } @@ -29,13 +29,13 @@ function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) { userResponse: promptResult, StudyInstanceUID, SeriesInstanceUID, - viewportIndex, + viewportId, isBackupSave: false, }); }); } -function _askShouldAddMeasurements(uiViewportDialogService, viewportIndex) { +function _askShouldAddMeasurements(uiViewportDialogService, viewportId) { return new Promise(function(resolve, reject) { const message = 'Do you want to add this measurement to the existing report?'; @@ -62,7 +62,7 @@ function _askShouldAddMeasurements(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, @@ -75,7 +75,7 @@ function _askShouldAddMeasurements(uiViewportDialogService, viewportIndex) { }); } -function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) { +function _askSaveDiscardOrCancel(UIViewportDialogService, viewportId) { return new Promise(function(resolve, reject) { const message = 'You have existing tracked measurements. What would you like to do with your existing tracked measurements?'; @@ -98,7 +98,7 @@ function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) { }; UIViewportDialogService.show({ - viewportIndex, + viewportId, type: 'warning', message, actions, diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js index 9e1456424..752edaf71 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js @@ -9,17 +9,17 @@ const RESPONSE = { function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) { const { UIViewportDialogService } = servicesManager.services; - const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; + const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt; return new Promise(async function(resolve, reject) { let promptResult = await _askTrackMeasurements( UIViewportDialogService, - viewportIndex + viewportId ); if (promptResult === RESPONSE.SET_STUDY_AND_SERIES) { promptResult = ctx.isDirty - ? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) + ? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportId) : RESPONSE.SET_STUDY_AND_SERIES; } @@ -27,13 +27,13 @@ function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) { userResponse: promptResult, StudyInstanceUID, SeriesInstanceUID, - viewportIndex, + viewportId, isBackupSave: false, }); }); } -function _askTrackMeasurements(UIViewportDialogService, viewportIndex) { +function _askTrackMeasurements(UIViewportDialogService, viewportId) { return new Promise(function(resolve, reject) { const message = 'Track measurements for this series?'; const actions = [ @@ -55,7 +55,7 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) { }; UIViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, @@ -68,7 +68,7 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) { }); } -function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) { +function _askSaveDiscardOrCancel(UIViewportDialogService, viewportId) { return new Promise(function(resolve, reject) { const message = 'Measurements cannot span across multiple studies. Do you want to save your tracked measurements?'; @@ -91,7 +91,7 @@ function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) { }; UIViewportDialogService.show({ - viewportIndex, + viewportId, type: 'warning', message, actions, diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx index ac73a5810..eb7ef360f 100644 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx +++ b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx @@ -25,7 +25,7 @@ const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = { }; function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { - const [viewportGrid, viewportGridService] = useViewportGrid(); + const [viewportGrid] = useViewportGrid(); const [measurementChangeTimestamp, setMeasurementsUpdated] = useState( Date.now().toString() ); @@ -157,7 +157,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { } const jumpToImage = ({ uid, isActive }) => { - measurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, uid); + measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid); onMeasurementItemClickHandler({ uid, isActive }); }; @@ -280,7 +280,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { onExportClick={exportReport} onCreateReportClick={() => { sendTrackedMeasurementsEvent('SAVE_REPORT', { - viewportIndex: viewportGrid.activeViewportIndex, + viewportId: viewportGrid.activeViewportId, isBackupSave: true, }); }} diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx index 46c74526d..0d40774b3 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx @@ -40,7 +40,7 @@ function PanelStudyBrowserTracking({ // Tabs --> Studies --> DisplaySets --> Thumbnails const { StudyInstanceUIDs } = useImageViewer(); const [ - { activeViewportIndex, viewports }, + { activeViewportId, viewports }, viewportGridService, ] = useViewportGrid(); const [ @@ -58,10 +58,10 @@ function PanelStudyBrowserTracking({ const onDoubleClickThumbnailHandler = displaySetInstanceUID => { let updatedViewports = []; - const viewportIndex = activeViewportIndex; + const viewportId = activeViewportId; try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, + viewportId, displaySetInstanceUID ); } catch (error) { @@ -78,8 +78,8 @@ function PanelStudyBrowserTracking({ viewportGridService.setDisplaySetsForViewports(updatedViewports); }; - const activeViewportDisplaySetInstanceUIDs = - viewports[activeViewportIndex]?.displaySetInstanceUIDs; + const activeViewportDisplaySetInstanceUIDs = viewports.get(activeViewportId) + ?.displaySetInstanceUIDs; const { trackedSeries } = trackedMeasurements.context; @@ -447,20 +447,19 @@ function _mapDisplaySets( const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID]; const componentType = _getComponentType(ds); const numPanes = viewportGridService.getNumViewportPanes(); - const viewportIdentificator = - numPanes === 1 - ? [] - : viewports.reduce((acc, viewportData, index) => { - if ( - index < numPanes && - viewportData?.displaySetInstanceUIDs?.includes( - ds.displaySetInstanceUID - ) - ) { - acc.push(viewportData.viewportLabel); - } - return acc; - }, []); + const viewportIdentificator = []; + + if (numPanes !== 1) { + viewports.forEach(viewportData => { + if ( + viewportData?.displaySetInstanceUIDs?.includes( + ds.displaySetInstanceUID + ) + ) { + viewportIdentificator.push(viewportData.viewportLabel); + } + }); + } const array = componentType === 'thumbnailTracked' diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx index 66d9e2aa9..bd3f227d2 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx @@ -15,11 +15,10 @@ const { formatDate } = utils; function TrackedCornerstoneViewport(props) { const { displaySets, - viewportIndex, + viewportId, viewportLabel, servicesManager, extensionManager, - viewportOptions, } = props; const { t } = useTranslation('Common'); @@ -43,7 +42,6 @@ function TrackedCornerstoneViewport(props) { const [viewportElem, setViewportElem] = useState(null); const { trackedSeries } = trackedMeasurements.context; - const viewportId = viewportOptions.viewportId; const { SeriesDate, @@ -64,8 +62,8 @@ function TrackedCornerstoneViewport(props) { } = displaySet.images[0]; const updateIsTracked = useCallback(() => { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex + const viewport = cornerstoneViewportService.getCornerstoneViewport( + viewportId ); if (viewport instanceof BaseVolumeViewport) { @@ -84,7 +82,7 @@ function TrackedCornerstoneViewport(props) { if (trackedSeries.includes(SeriesInstanceUID) !== isTracked) { setIsTracked(!isTracked); } - }, [isTracked, trackedMeasurements, viewportIndex, SeriesInstanceUID]); + }, [isTracked, trackedMeasurements, viewportId, SeriesInstanceUID]); const onElementEnabled = useCallback( evt => { @@ -113,7 +111,7 @@ function TrackedCornerstoneViewport(props) { const { unsubscribe } = cornerstoneViewportService.subscribe( cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, props => { - if (props.viewportIndex !== viewportIndex) { + if (props.viewportId !== viewportId) { return; } @@ -124,7 +122,7 @@ function TrackedCornerstoneViewport(props) { return () => { unsubscribe(); }; - }, [updateIsTracked, viewportIndex]); + }, [updateIsTracked, viewportId]); useEffect(() => { if (isTracked) { @@ -169,12 +167,12 @@ function TrackedCornerstoneViewport(props) { [added, addedRaw].forEach(evt => { subscriptions.push( measurementService.subscribe(evt, ({ source, measurement }) => { - const { activeViewportIndex } = viewportGridService.getState(); + const { activeViewportId } = viewportGridService.getState(); // Each TrackedCornerstoneViewport receives the MeasurementService's events. // Only send the tracked measurements event for the active viewport to avoid // sending it more than once. - if (viewportIndex === activeViewportIndex) { + if (viewportId === activeViewportId) { const { referenceStudyUID: StudyInstanceUID, referenceSeriesUID: SeriesInstanceUID, @@ -182,7 +180,7 @@ function TrackedCornerstoneViewport(props) { sendTrackedMeasurementsEvent('SET_DIRTY', { SeriesInstanceUID }); sendTrackedMeasurementsEvent('TRACK_SERIES', { - viewportIndex, + viewportId, StudyInstanceUID, SeriesInstanceUID, }); @@ -199,7 +197,7 @@ function TrackedCornerstoneViewport(props) { }, [ measurementService, sendTrackedMeasurementsEvent, - viewportIndex, + viewportId, viewportGridService, ]); @@ -217,10 +215,7 @@ function TrackedCornerstoneViewport(props) { setTrackedMeasurementUID(newTrackedMeasurementUID); - measurementService.jumpToMeasurement( - viewportIndex, - newTrackedMeasurementUID - ); + measurementService.jumpToMeasurement(viewportId, newTrackedMeasurementUID); } const getCornerstoneViewport = () => { @@ -280,7 +275,7 @@ function TrackedCornerstoneViewport(props) { TrackedCornerstoneViewport.propTypes = { displaySets: PropTypes.arrayOf(PropTypes.object.isRequired).isRequired, - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, dataSource: PropTypes.object, children: PropTypes.node, customProps: PropTypes.object, @@ -299,10 +294,10 @@ function _getNextMeasurementUID( const { measurementService, viewportGridService } = servicesManager.services; const measurements = measurementService.getMeasurements(); - const { activeViewportIndex, viewports } = viewportGridService.getState(); + const { activeViewportId, viewports } = viewportGridService.getState(); const { displaySetInstanceUIDs: activeViewportDisplaySetInstanceUIDs, - } = viewports[activeViewportIndex]; + } = viewports.get(activeViewportId); const { trackedSeries } = trackedMeasurements.context; diff --git a/extensions/test-extension/src/hpTestSwitch.ts b/extensions/test-extension/src/hpTestSwitch.ts new file mode 100644 index 000000000..27a7bde55 --- /dev/null +++ b/extensions/test-extension/src/hpTestSwitch.ts @@ -0,0 +1,246 @@ +import { Types } from '@ohif/core'; + +const viewport0a = { + viewportOptions: { + viewportId: 'viewportA', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewport1b = { + viewportOptions: { + viewportId: 'viewportB', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 1, + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewport2c = { + viewportOptions: { + viewportId: 'viewportC', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 2, + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewport3d = { + viewportOptions: { + viewportId: 'viewportD', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 3, + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewport4e = { + viewportOptions: { + viewportId: 'viewportE', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 4, + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewport5f = { + viewportOptions: { + viewportId: 'viewportF', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 5, + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewport3a = { + viewportOptions: { + viewportId: 'viewportA', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 3, + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewport2b = { + viewportOptions: { + viewportId: 'viewportB', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 2, + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewport1c = { + viewportOptions: { + viewportId: 'viewportC', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 1, + id: 'defaultDisplaySetId', + }, + ], +}; +const viewport0d = { + viewportOptions: { + viewportId: 'viewportD', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + matchedDisplaySetsIndex: 0, + id: 'defaultDisplaySetId', + }, + ], +}; + +const viewportStructure = { + layoutType: 'grid', + properties: { + rows: 2, + columns: 2, + }, +}; + +const viewportStructure32 = { + layoutType: 'grid', + properties: { + rows: 2, + columns: 3, + }, +}; + +/** + * This hanging protocol is a test hanging protocol used to apply various + * layouts in different positions for display, re-using earlier names in + * various orders. + */ +const hpTestSwitch: Types.HangingProtocol.Protocol = { + hasUpdatedPriorsInformation: false, + id: '@ohif/mnTestSwitch', + description: 'Has various hanging protocol grid layouts', + name: 'Test Switch', + protocolMatchingRules: [ + { + id: 'OneOrMoreSeries', + weight: 25, + attribute: 'numberOfDisplaySetsWithImages', + constraint: { + greaterThan: 0, + }, + }, + ], + toolGroupIds: ['default'], + displaySetSelectors: { + defaultDisplaySetId: { + seriesMatchingRules: [ + { + attribute: 'numImageFrames', + constraint: { + greaterThan: { value: 0 }, + }, + }, + // This display set will select the specified items by preference + // It has no affect if nothing is specified in the URL. + { + attribute: 'isDisplaySetFromUrl', + weight: 10, + constraint: { + equals: true, + }, + }, + ], + }, + }, + defaultViewport: { + viewportOptions: { + viewportType: 'stack', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + id: 'defaultDisplaySetId', + matchedDisplaySetsIndex: -1, + }, + ], + }, + stages: [ + { + name: '2x2 0a1b2c3d', + viewportStructure, + viewports: [viewport0a, viewport1b, viewport2c, viewport3d], + }, + { + name: '3x2 0a1b4e2c3d5f', + viewportStructure: viewportStructure32, + // Note the following structure simply preserves the viewportId for + // a given screen position + viewports: [ + viewport0a, + viewport1b, + viewport4e, + viewport2c, + viewport3d, + viewport5f, + ], + }, + { + name: '2x2 1c0d3a2b', + viewportStructure, + viewports: [viewport1c, viewport0d, viewport3a, viewport2b], + }, + { + name: '2x2 3a2b1c0d', + viewportStructure, + viewports: [viewport3a, viewport2b, viewport1c, viewport0d], + }, + ], + numberOfPriorsReferenced: -1, +}; + +export default hpTestSwitch; diff --git a/extensions/test-extension/src/index.tsx b/extensions/test-extension/src/index.tsx index 16fe5dd2b..475415bd5 100644 --- a/extensions/test-extension/src/index.tsx +++ b/extensions/test-extension/src/index.tsx @@ -2,6 +2,8 @@ import { Types } from '@ohif/core'; import { id } from './id'; +import hpTestSwitch from './hpTestSwitch'; + import getCustomizationModule from './getCustomizationModule'; // import {setViewportZoomPan, storeViewportZoomPan } from './custom-viewport/setViewportZoomPan'; import sameAs from './custom-attribute/sameAs'; @@ -47,6 +49,16 @@ const testExtension: Types.Extensions.Extension = { /** Registers some customizations */ getCustomizationModule, + + getHangingProtocolModule: () => { + return [ + // Create a MxN hanging protocol available by default + { + name: hpTestSwitch.id, + protocol: hpTestSwitch, + }, + ]; + }, }; export default testExtension; diff --git a/extensions/tmtv/src/commandsModule.js b/extensions/tmtv/src/commandsModule.js index 03227e794..7c0e4e457 100644 --- a/extensions/tmtv/src/commandsModule.js +++ b/extensions/tmtv/src/commandsModule.js @@ -36,8 +36,8 @@ const commandsModule = ({ const { getEnabledElement } = utilityModule.exports; function _getActiveViewportsEnabledElement() { - const { activeViewportIndex } = viewportGridService.getState(); - const { element } = getEnabledElement(activeViewportIndex) || {}; + const { activeViewportId } = viewportGridService.getState(); + const { element } = getEnabledElement(activeViewportId) || {}; const enabledElement = cs.getEnabledElement(element); return enabledElement; } @@ -45,8 +45,8 @@ const commandsModule = ({ function _getMatchedViewportsToolGroupIds() { const { viewportMatchDetails } = hangingProtocolService.getMatchDetails(); const toolGroupIds = []; - viewportMatchDetails.forEach((value, key) => { - const { viewportOptions } = value; + viewportMatchDetails.forEach(viewport => { + const { viewportOptions } = viewport; const { toolGroupId } = viewportOptions; if (toolGroupIds.indexOf(toolGroupId) === -1) { toolGroupIds.push(toolGroupId); @@ -64,7 +64,7 @@ const commandsModule = ({ // corrected PT vs the non-attenuation correct PT) let ptDisplaySet = null; - for (const [viewportIndex, viewportDetails] of viewportMatchDetails) { + for (const [viewportId, viewportDetails] of viewportMatchDetails) { const { displaySetsInfo } = viewportDetails; const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) => displaySetService.getDisplaySetByUID(displaySetInstanceUID) @@ -137,7 +137,6 @@ const commandsModule = ({ // Add Segmentation to all toolGroupIds in the viewer const toolGroupIds = _getMatchedViewportsToolGroupIds(); - const representationType = LABELMAP; for (const toolGroupId of toolGroupIds) { @@ -545,13 +544,8 @@ const commandsModule = ({ let viewports = []; fusionViewportIds.forEach(viewportId => { - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); - - const viewportIndex = viewportInfo.getViewportIndex(); commandsManager.runCommand('setViewportColormap', { - viewportIndex, + viewportId, displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID, colormap: { name: colormap, diff --git a/extensions/tmtv/src/utils/hpViewports.ts b/extensions/tmtv/src/utils/hpViewports.ts index 370b4907f..b716a9ef1 100644 --- a/extensions/tmtv/src/utils/hpViewports.ts +++ b/extensions/tmtv/src/utils/hpViewports.ts @@ -114,6 +114,9 @@ const ptAXIAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -155,6 +158,9 @@ const ptSAGITTAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -196,6 +202,9 @@ const ptCORONAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -246,6 +255,9 @@ const fusionAXIAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -306,6 +318,9 @@ const fusionSAGITTAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -366,6 +381,9 @@ const fusionCORONAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -411,6 +429,9 @@ const mipSAGITTAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], diff --git a/package.json b/package.json index aa12b1d78..db8e85850 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "postcss-import": "^14.0.2", "postcss-loader": "^6.1.1", "postcss-preset-env": "^7.4.3", - "prettier": "^1.18.2", + "prettier": "^3.0.3", "react-hot-loader": "^4.13.0", "semver": "^7.5.1", "serve": "^14.2.0", diff --git a/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js b/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js index 7b46387fa..3eab093cd 100644 --- a/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js +++ b/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js @@ -16,12 +16,18 @@ describe('OHIF Double Click', () => { .should('be.eq', numExpectedViewports); for (let i = 0; i < numExpectedViewports; i += 1) { + cy.wait(2000); + // For whatever reason, with Cypress tests, we have to activate the // viewport we are double clicking first. cy.get('[data-cy="viewport-pane"]') .eq(i) - .trigger('mousedown', 'center', { force: true }) - .trigger('mouseup', 'center', { force: true }); + .trigger('mousedown', 'center', { + force: true, + }) + .trigger('mouseup', 'center', { + force: true, + }); // Wait for the viewport to be 'active'. // TODO Is there a better way to do this? @@ -41,12 +47,16 @@ describe('OHIF Double Click', () => { .should('be.eq', 1); cy.get('[data-cy="viewport-pane"]') - .eq(0) - .trigger('dblclick', 'center'); + .trigger('mousedown', 'center', { + force: true, + }) + .trigger('mouseup', 'center', { + force: true, + }); cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', numExpectedViewports); + .eq(0) + .trigger('dblclick', 'center'); } }); }); diff --git a/platform/app/cypress/integration/volume/MPR.spec.js b/platform/app/cypress/integration/volume/MPR.spec.js index 080f8100a..1e40a3f15 100644 --- a/platform/app/cypress/integration/volume/MPR.spec.js +++ b/platform/app/cypress/integration/volume/MPR.spec.js @@ -66,9 +66,10 @@ describe('OHIF MPR', () => { .then(cornerstone => { const viewports = cornerstone.getRenderingEngines()[0].getViewports(); - const imageData1 = viewports[0].getImageData(); - const imageData2 = viewports[1].getImageData(); - const imageData3 = viewports[2].getImageData(); + // The stack viewport still exists after the changes to viewportId and inde + const imageData1 = viewports[1].getImageData(); + const imageData2 = viewports[2].getImageData(); + const imageData3 = viewports[3].getImageData(); // for some reason map doesn't work here cy.wrap(imageData1).should('not.be', undefined); diff --git a/platform/app/src/components/ViewportGrid.tsx b/platform/app/src/components/ViewportGrid.tsx index 1d8bbacaa..c0e311928 100644 --- a/platform/app/src/components/ViewportGrid.tsx +++ b/platform/app/src/components/ViewportGrid.tsx @@ -10,7 +10,7 @@ function ViewerViewportGrid(props) { const { servicesManager, viewportComponents, dataSource } = props; const [viewportGrid, viewportGridService] = useViewportGrid(); - const { layout, activeViewportIndex, viewports } = viewportGrid; + const { layout, activeViewportId, viewports } = viewportGrid; const { numCols, numRows } = layout; // TODO -> Need some way of selecting which displaySets hit the viewports. @@ -51,10 +51,11 @@ function ViewerViewportGrid(props) { * specify the viewport match details, which specifies the size and * setup of the various viewports. */ - const findOrCreateViewport = viewportIndex => { - const details = viewportMatchDetails.get(viewportIndex); + const findOrCreateViewport = pos => { + const viewportId = Array.from(viewportMatchDetails.keys())[pos]; + const details = viewportMatchDetails.get(viewportId); if (!details) { - console.log('No match details for viewport', viewportIndex); + console.log('No match details for viewport', viewportId); return; } @@ -99,11 +100,11 @@ function ViewerViewportGrid(props) { }; const _getUpdatedViewports = useCallback( - (viewportIndex, displaySetInstanceUID) => { + (viewportId, displaySetInstanceUID) => { let updatedViewports = []; try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, + viewportId, displaySetInstanceUID ); } catch (error) { @@ -144,7 +145,7 @@ function ViewerViewportGrid(props) { useEffect(() => { const { unsubscribe } = measurementService.subscribe( MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT, - ({ viewportIndex, measurement, isConsumed }) => { + ({ viewportId, measurement, isConsumed }) => { if (isConsumed) { return; } @@ -155,7 +156,7 @@ function ViewerViewportGrid(props) { measurement; const updatedViewports = _getUpdatedViewports( - viewportIndex, + viewportId, referencedDisplaySetInstanceUID ); // Arbitrarily assign the viewport to element 0 @@ -197,7 +198,7 @@ function ViewerViewportGrid(props) { }, [viewports]); /** - const onDoubleClick = viewportIndex => { + const onDoubleClick = viewportId => { // TODO -> Disabled for now. // onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport. // Which it really really shouldn't. We need a larger fix for jump to @@ -206,7 +207,7 @@ function ViewerViewportGrid(props) { viewportGridService.set({ numCols: cachedLayout.numCols, numRows: cachedLayout.numRows, - activeViewportIndex: cachedLayout.activeViewportIndex, + activeViewportId: cachedLayout.activeViewportId, viewports: cachedLayout.viewports, cachedLayout: null, }); @@ -223,10 +224,10 @@ function ViewerViewportGrid(props) { viewportGridService.set({ numCols: 1, numRows: 1, - activeViewportIndex: 0, + activeViewportId: 0, viewports: [ { - displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID, + displaySetInstanceUID: viewports[viewportId].displaySetInstanceUID, imageIndex: undefined, }, ], @@ -234,15 +235,15 @@ function ViewerViewportGrid(props) { numCols, numRows, viewports: cachedViewports, - activeViewportIndex: viewportIndex, + activeViewportId: viewportId, }, }); }; */ - const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => { + const onDropHandler = (viewportId, { displaySetInstanceUID }) => { const updatedViewports = _getUpdatedViewports( - viewportIndex, + viewportId, displaySetInstanceUID ); viewportGridService.setDisplaySetsForViewports(updatedViewports); @@ -253,13 +254,7 @@ function ViewerViewportGrid(props) { const numViewportPanes = viewportGridService.getNumViewportPanes(); for (let i = 0; i < numViewportPanes; i++) { - const viewportIndex = i; - const isActive = activeViewportIndex === viewportIndex; - const paneMetadata = viewports[i] || {}; - const viewportId = paneMetadata.viewportId || `viewport-${i}`; - if (!paneMetadata.viewportId) { - paneMetadata.viewportId = viewportId; - } + const paneMetadata = Array.from(viewports.values())[i] || {}; const { displaySetInstanceUIDs, viewportOptions, @@ -271,9 +266,13 @@ function ViewerViewportGrid(props) { viewportLabel, } = paneMetadata; + const viewportId = viewportOptions.viewportId + const isActive = activeViewportId === viewportId; + + const displaySetInstanceUIDsToUse = displaySetInstanceUIDs || []; - // This is causing the viewport components re-render when the activeViewportIndex changes + // This is causing the viewport components re-render when the activeViewportId changes const displaySets = displaySetInstanceUIDsToUse.map( displaySetInstanceUID => { return ( @@ -307,17 +306,23 @@ function ViewerViewportGrid(props) { event.stopPropagation(); } - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); }; - // TEMP -> Double click disabled for now - // onDoubleClick={() => onDoubleClick(viewportIndex)} - viewportPanes[i] = ( 1 ? viewportLabel : ''} + viewportLabel={viewports.size > 1 ? viewportLabel : ''} + viewportId={viewportId} dataSource={dataSource} viewportOptions={viewportOptions} displaySetOptions={displaySetOptions} @@ -349,7 +354,7 @@ function ViewerViewportGrid(props) { } return viewportPanes; - }, [viewports, activeViewportIndex, viewportComponents, dataSource]); + }, [viewports, activeViewportId, viewportComponents, dataSource]); /** * Loading indicator until numCols and numRows are gotten from the HangingProtocolService diff --git a/platform/core/src/services/HangingProtocolService/HPMatcher.js b/platform/core/src/services/HangingProtocolService/HPMatcher.js index b87c31e84..ef7546425 100644 --- a/platform/core/src/services/HangingProtocolService/HPMatcher.js +++ b/platform/core/src/services/HangingProtocolService/HPMatcher.js @@ -74,13 +74,14 @@ const match = ( errorMessages = ['Something went wrong during validation.', e]; } - console.log( - 'Test', - `${from}.${attribute}`, - readValues[attribute], - JSON.stringify(rule.constraint), - !errorMessages - ); + // TODO: move to a logger + // console.log( + // 'Test', + // `${from}.${attribute}`, + // readValues[attribute], + // JSON.stringify(rule.constraint), + // !errorMessages + // ); if (!errorMessages) { // If no errorMessages were returned, then validation passed. diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js b/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js index 6dff6a190..f51c49951 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js @@ -118,7 +118,7 @@ function checkHpsBestMatch(hps) { hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets }); const { viewportMatchDetails } = hps.getMatchDetails(); expect(viewportMatchDetails.size).toBe(1); - expect(viewportMatchDetails.get(0)).toMatchObject({ + expect(viewportMatchDetails.get('ctAXIAL')).toMatchObject({ viewportOptions: { viewportId: 'ctAXIAL', viewportType: 'volume', diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts index ddf5ac807..717059579 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts @@ -14,6 +14,7 @@ import { } from './custom-attribute/isDisplaySetFromUrl'; import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages'; import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets'; +import uuidv4 from '../../utils/uuidv4'; type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator; @@ -132,11 +133,11 @@ export default class HangingProtocolService extends PubSubService { > = new Map(); /** - * An array that contains for each viewport (viewportIndex) specified in the + * An array that contains for each viewport (viewportId) specified in the * hanging protocol, an object of the form */ viewportMatchDetails: Map< - number, // viewportIndex + string, // viewportId HangingProtocol.ViewportMatchDetails > = new Map(); @@ -185,7 +186,7 @@ export default class HangingProtocolService extends PubSubService { stage: HangingProtocol.ProtocolStage; stageIndex: number; activeStudy?: StudyMetadata; - viewportMatchDetails: Map; + viewportMatchDetails: Map; displaySetMatchDetails: Map; activeImageLoadStrategyName: string; } { @@ -404,7 +405,7 @@ export default class HangingProtocolService extends PubSubService { /** * Returns true, if the hangingProtocol has a custom loading strategy for the images * and its callback has been added to the HangingProtocolService - * @returns {boolean} true + * @returns A boolean indicating whether a custom image load strategy has been added or not. */ public hasCustomImageLoadStrategy(): boolean { return ( @@ -415,10 +416,31 @@ export default class HangingProtocolService extends PubSubService { ); } + /** + * Returns a boolean indicating whether a custom image load has been performed or not. + * A custom image load is performed when a custom image load strategy is used to load images. + * This method is used internally by the HangingProtocolService to determine whether to perform + * a custom image load or not. + * + * @returns A boolean indicating whether a custom image load has been performed or not. + */ public getCustomImageLoadPerformed(): boolean { return this.customImageLoadPerformed; } + /** + * Returns a boolean indicating whether a custom image load should be performed or not. + * A custom image load should be performed if a custom image load strategy has been added to the HangingProtocolService + * and it has not been performed yet. + * + * @returns A boolean indicating whether a custom image load should be performed or not. + */ + public getShouldPerformCustomImageLoad(): boolean { + return ( + this.hasCustomImageLoadStrategy() && !this.getCustomImageLoadPerformed() + ); + } + /** * Set the strategy callback for loading images to the HangingProtocolService * @param {string} name strategy name @@ -519,21 +541,28 @@ export default class HangingProtocolService extends PubSubService { for (let i = 0; i < rows * columns; i++) { stage.viewports.push({ - viewportOptions: defaultViewportOptions, + viewportOptions: { + ...defaultViewportOptions, + viewportId: uuidv4(), + }, displaySets: [], }); } } else { + // Clone each viewport to ensure independent objects + stage.viewports = stage.viewports.map(viewport => ({ + ...viewport, + viewportOptions: { + ...(viewport.viewportOptions || defaultViewportOptions), + viewportId: viewport.viewportOptions?.viewportId || uuidv4(), + }, + displaySets: viewport.displaySets || [], + })); + stage.viewports.forEach(viewport => { - viewport.viewportOptions = - viewport.viewportOptions || defaultViewportOptions; - if (!viewport.displaySets) { - viewport.displaySets = []; - } else { - viewport.displaySets.forEach(displaySet => { - displaySet.options = displaySet.options || {}; - }); - } + viewport.displaySets.forEach(displaySet => { + displaySet.options = displaySet.options || {}; + }); }); } }); @@ -558,7 +587,7 @@ export default class HangingProtocolService extends PubSubService { }; } - getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) { + getViewportsRequireUpdate(viewportId, displaySetInstanceUID) { const { displaySetService } = this._servicesManager.services; const displaySet = displaySetService.getDisplaySetByUID( displaySetInstanceUID @@ -570,15 +599,22 @@ export default class HangingProtocolService extends PubSubService { const protocol = this.protocol; const protocolStage = protocol.stages[this.stageIndex]; const protocolViewports = protocolStage.viewports; - const protocolViewport = protocolViewports[viewportIndex]; const defaultReturn = [ { - viewportIndex, + viewportId, displaySetInstanceUIDs: [newDisplaySetInstanceUID], }, ]; + if (!protocolViewports) { + return defaultReturn; + } + + const protocolViewport = protocolViewports.find( + pv => pv.viewportOptions.viewportId === viewportId + ); + // if no viewport, then we can assume there is no predefined set of // rules that should be applied to this viewport while matching if (!protocolViewport) { @@ -644,13 +680,13 @@ export default class HangingProtocolService extends PubSubService { } // if we reach here, it means that the displaySetInstanceUIDs to be dropped - // in the viewportIndex are valid, and we can proceed with the update. However + // for the desired viewportId are valid, and we can proceed with the update. However // we need to check if the displaySets that the viewport were showing // was also referenced by other viewports, and if so, we need to update those // viewports as well // check if displaySetSelectors are used by other viewports, and - // store the viewportIndex and displaySetInstanceUIDs that need to be updated + // store the viewportId and displaySetInstanceUIDs that need to be updated const viewportsToUpdate = []; protocolViewports.forEach((viewport, index) => { const viewportNeedsUpdate = viewport.displaySets.some( @@ -661,7 +697,7 @@ export default class HangingProtocolService extends PubSubService { if (viewportNeedsUpdate) { // Try to recompute the viewport options based on the current - // viewportIndex that needs update but from its old/original un-computed + // viewportId that needs update but from its old/original un-computed // viewport & displaySet options if (originalProtocolStage) { const originalViewport = originalProtocolStage.viewports[index]; @@ -692,7 +728,7 @@ export default class HangingProtocolService extends PubSubService { ); viewportsToUpdate.push({ - viewportIndex: index, + viewportId: viewport.viewportOptions.viewportId, displaySetInstanceUIDs, viewportOptions: viewport.viewportOptions, displaySetOptions, @@ -799,7 +835,7 @@ export default class HangingProtocolService extends PubSubService { * @param protocolId - name of the registered protocol to be set * @param options - options to be passed to the protocol, this is either an array * of the displaySetInstanceUIDs to be set on ALL VIEWPORTS OF THE PROTOCOL or an object - * that contains viewportIndex as the key and displaySetInstanceUIDs as the value + * that contains viewportId as the key and displaySetInstanceUIDs as the value * for each viewport that needs to be set. * @param errorCallback - callback to be called if there is an error * during the protocol application @@ -918,7 +954,7 @@ export default class HangingProtocolService extends PubSubService { const stage = stages[i]; if (stage.id === stageId && stage.status !== 'disabled') { return i; - } + } } return; } @@ -1098,7 +1134,6 @@ export default class HangingProtocolService extends PubSubService { // matching applied this.viewportMatchDetails = new Map(); this.displaySetMatchDetails = new Map(); - this.customImageLoadPerformed = false; // Retrieve the current stage const stageModel = this._getCurrentStageModel(); @@ -1148,7 +1183,11 @@ export default class HangingProtocolService extends PubSubService { displaySetMatchDetails: Map; } { let matchedViewports = 0; - stageModel.viewports.forEach((viewport, viewportIndex) => { + stageModel.viewports.forEach(viewport => { + // Todo: we should probably assign a random viewportId if not defined + // below, but it feels odd since viewportGrid should handle this kind + // of thing + const viewportId = viewport.viewportOptions.viewportId; const matchDetails = this._matchViewport( viewport, options, @@ -1169,7 +1208,7 @@ export default class HangingProtocolService extends PubSubService { displaySetOptions: it, })); } - viewportMatchDetails.set(viewportIndex, matchDetails); + viewportMatchDetails.set(viewportId, matchDetails); } }); return { @@ -1231,7 +1270,7 @@ export default class HangingProtocolService extends PubSubService { for (const subMatch of match.matchingScores) { if (subMatch.displaySetInstanceUID === displaySetUID) { return; - } + } } throw new Error( `Reused viewport details ${id} with ds ${displaySetUID} not valid` @@ -1442,15 +1481,15 @@ export default class HangingProtocolService extends PubSubService { ); const studyMatchDetails = this.protocolEngine.findMatch( - study, - studyMatchingRules, - { - studies: this.studies, - displaySets: studyDisplaySets, - allDisplaySets: this.displaySets, - displaySetMatchDetails: this.displaySetMatchDetails, + study, + studyMatchingRules, + { + studies: this.studies, + displaySets: studyDisplaySets, + allDisplaySets: this.displaySets, + displaySetMatchDetails: this.displaySetMatchDetails, studyInstanceUIDsIndex, - } + } ); // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed @@ -1459,10 +1498,10 @@ export default class HangingProtocolService extends PubSubService { } this.debug( - 'study', - study.StudyInstanceUID, - 'display sets #', - studyDisplaySets.length + 'study', + study.StudyInstanceUID, + 'display sets #', + studyDisplaySets.length ); studyDisplaySets.forEach(displaySet => { const { @@ -1471,15 +1510,15 @@ export default class HangingProtocolService extends PubSubService { displaySetInstanceUID, } = displaySet; const seriesMatchDetails = this.protocolEngine.findMatch( - displaySet, - seriesMatchingRules, - // Todo: why we have images here since the matching type does not have it - { - studies: this.studies, - instance: displaySet.images?.[0], - displaySetMatchDetails: this.displaySetMatchDetails, - displaySets: studyDisplaySets, - } + displaySet, + seriesMatchingRules, + // Todo: why we have images here since the matching type does not have it + { + studies: this.studies, + instance: displaySet.images?.[0], + displaySetMatchDetails: this.displaySetMatchDetails, + displaySets: studyDisplaySets, + } ); // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed diff --git a/platform/core/src/services/MeasurementService/MeasurementService.ts b/platform/core/src/services/MeasurementService/MeasurementService.ts index 66ef23d6a..e425fd50b 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.ts +++ b/platform/core/src/services/MeasurementService/MeasurementService.ts @@ -649,10 +649,7 @@ class MeasurementService extends PubSubService { * merely causes it to fire the event with the isConsumed set to true. */ - public jumpToMeasurement( - viewportIndex: number, - measurementUID: string - ): void { + public jumpToMeasurement(viewportId: string, measurementUID: string): void { const measurement = this.measurements.get(measurementUID); if (!measurement) { @@ -660,7 +657,7 @@ class MeasurementService extends PubSubService { return; } const consumableEvent = this.createConsumableEvent({ - viewportIndex, + viewportId, measurement, }); diff --git a/platform/core/src/services/UIViewportDialogService/index.js b/platform/core/src/services/UIViewportDialogService/index.js index 770fccc05..dd715a342 100644 --- a/platform/core/src/services/UIViewportDialogService/index.js +++ b/platform/core/src/services/UIViewportDialogService/index.js @@ -4,7 +4,7 @@ * @typedef {Object} ViewportDialogProps * @property {ReactElement|HTMLElement} [content=null] Modal content. * @property {Object} [contentProps=null] Modal content props. - * @property {boolean} [viewportIndex=false] Modal is dismissible via the esc key. + * @property {boolean} [viewportId=false] Modal is dismissible via the esc key. */ const name = 'uiViewportDialogService'; @@ -22,12 +22,12 @@ const serviceImplementation = { }; /** - * Show a new UI viewport dialog on the specified viewportIndex; + * Show a new UI viewport dialog on the specified viewportId; * - * @param {ViewportDialogProps} props { content, contentProps, viewportIndex } + * @param {ViewportDialogProps} props { content, contentProps, viewportId } */ function _show({ - viewportIndex, + viewportId, id, type, message, @@ -36,7 +36,7 @@ function _show({ onOutsideClick, }) { return serviceImplementation._show({ - viewportIndex, + viewportId, id, type, message, @@ -59,7 +59,7 @@ function _hide() { * @param {*} { * hide: hideImplementation, * show: showImplementation, - * viewportIndex, + * viewportId, * } */ function setServiceImplementation({ diff --git a/platform/core/src/services/ViewportGridService/ViewportGridService.ts b/platform/core/src/services/ViewportGridService/ViewportGridService.ts index a5fca6e6a..2a5e305fd 100644 --- a/platform/core/src/services/ViewportGridService/ViewportGridService.ts +++ b/platform/core/src/services/ViewportGridService/ViewportGridService.ts @@ -3,7 +3,7 @@ import { getPresentationIds, PresentationIds } from './getPresentationIds'; class ViewportGridService extends PubSubService { public static readonly EVENTS = { - ACTIVE_VIEWPORT_INDEX_CHANGED: 'event::activeviewportindexchanged', + ACTIVE_VIEWPORT_ID_CHANGED: 'event::activeviewportidchanged', LAYOUT_CHANGED: 'event::layoutChanged', GRID_STATE_CHANGED: 'event::gridStateChanged', }; @@ -27,7 +27,7 @@ class ViewportGridService extends PubSubService { public setServiceImplementation({ getState: getStateImplementation, - setActiveViewportIndex: setActiveViewportIndexImplementation, + setActiveViewportId: setActiveViewportIdImplementation, setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation, setLayout: setLayoutImplementation, reset: resetImplementation, @@ -38,9 +38,9 @@ class ViewportGridService extends PubSubService { if (getStateImplementation) { this.serviceImplementation._getState = getStateImplementation; } - if (setActiveViewportIndexImplementation) { - this.serviceImplementation._setActiveViewportIndex = - setActiveViewportIndexImplementation; + if (setActiveViewportIdImplementation) { + this.serviceImplementation._setActiveViewport = + setActiveViewportIdImplementation; } if (setDisplaySetsForViewportsImplementation) { this.serviceImplementation._setDisplaySetsForViewports = @@ -64,13 +64,11 @@ class ViewportGridService extends PubSubService { } } - public setActiveViewportIndex(index) { - this.serviceImplementation._setActiveViewportIndex(index); + public setActiveViewportId(id: string) { + this.serviceImplementation._setActiveViewport(id); const state = this.getState(); - const viewportId = state.viewports[index]?.viewportOptions?.viewportId; - this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, { - viewportIndex: index, - viewportId, + this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, { + viewportId: id, }); } @@ -89,7 +87,7 @@ class ViewportGridService extends PubSubService { const viewports = []; for (const viewport of props) { - const updatedViewport = state.viewports[viewport.viewportIndex]; + const updatedViewport = state.viewports.get(viewport.viewportId); if (updatedViewport) { viewports.push(updatedViewport); } else { @@ -118,7 +116,7 @@ class ViewportGridService extends PubSubService { numRows, layoutOptions, layoutType = 'grid', - activeViewportIndex = undefined, + activeViewportId = undefined, findOrCreateViewport = undefined, }) { this.serviceImplementation._setLayout({ @@ -126,7 +124,7 @@ class ViewportGridService extends PubSubService { numRows, layoutOptions, layoutType, - activeViewportIndex, + activeViewportId, findOrCreateViewport, }); this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, { @@ -160,8 +158,8 @@ class ViewportGridService extends PubSubService { return this.serviceImplementation._getNumViewportPanes(); } - public getLayoutOptionsFromState(state) { - return state.viewports.map(viewport => { + public getLayoutOptionsFromState(state: any): { x: number; y: number; width: number; height: number }[] { + return Array.from(state.viewports.entries()).map(([_, viewport]) => { return { x: viewport.x, y: viewport.y, diff --git a/platform/core/src/services/ViewportGridService/getPresentationIds.ts b/platform/core/src/services/ViewportGridService/getPresentationIds.ts index e832b800a..7d2f1465a 100644 --- a/platform/core/src/services/ViewportGridService/getPresentationIds.ts +++ b/platform/core/src/services/ViewportGridService/getPresentationIds.ts @@ -25,7 +25,7 @@ const addUniqueIndex = (arr, key, viewports, isUpdatingSameViewport) => { arr[arr.length - 1] = displayInstance; const testId = arr.join(JOIN_STR); if ( - !viewports.find( + !Array.from(viewports.values()).find( viewport => viewport.viewportOptions?.presentationIds?.[key] === testId ) ) { @@ -126,11 +126,11 @@ const getPresentationIds = (viewport, viewports): PresentationIds => { } // only add unique index if the viewport is getting inserted and not updated - const isUpdatingSameViewport = viewports.some(v => { + const isUpdatingSameViewport = Array.from(viewports.values()).some(v => { return ( v.displaySetInstanceUIDs.toString() === viewport.displaySetInstanceUIDs.toString() && - v.viewportIndex === viewport.viewportIndex + v.viewportId === viewport.viewportId ); }); diff --git a/platform/core/src/types/HangingProtocol.ts b/platform/core/src/types/HangingProtocol.ts index f8f82ef08..b21dcff33 100644 --- a/platform/core/src/types/HangingProtocol.ts +++ b/platform/core/src/types/HangingProtocol.ts @@ -61,7 +61,7 @@ export type SetProtocolOptions = { export type HangingProtocolMatchDetails = { displaySetMatchDetails: Map; - viewportMatchDetails: Map; + viewportMatchDetails: Map; }; export type ConstraintValue = @@ -263,6 +263,8 @@ export type ProtocolNotifications = { export type Protocol = { // Mandatory id: string; + /** A description of this protocol. Used as a tool tip for the user. */ + description?: string; /** Maps ids to display set selectors to choose display sets */ displaySetSelectors: Record; /** A default viewport to use for any stage to select new viewport layouts. */ diff --git a/platform/core/src/utils/index.js b/platform/core/src/utils/index.js index 4b801d054..3f5d66e41 100644 --- a/platform/core/src/utils/index.js +++ b/platform/core/src/utils/index.js @@ -1,6 +1,7 @@ import ObjectPath from './objectPath'; import absoluteUrl from './absoluteUrl'; import guid from './guid'; +import uuidv4 from './uuidv4'; import sortBy from './sortBy.js'; import writeScript from './writeScript.js'; import b64toBlob from './b64toBlob.js'; @@ -41,6 +42,7 @@ import { splitComma, getSplitParam } from './splitComma'; const utils = { guid, + uuidv4, ObjectPath, absoluteUrl, sortBy, diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js index 5620a0fa2..5ac07149c 100644 --- a/platform/core/src/utils/index.test.js +++ b/platform/core/src/utils/index.test.js @@ -39,6 +39,7 @@ describe('Top level exports', () => { 'hierarchicalListUtils', 'progressTrackingUtils', 'subscribeToNextViewportGridChange', + 'uuidv4', ].sort(); const exports = Object.keys(utils.default).sort(); diff --git a/platform/core/src/utils/subscribeToNextViewportGridChange.ts b/platform/core/src/utils/subscribeToNextViewportGridChange.ts index e6c72d048..8c1339120 100644 --- a/platform/core/src/utils/subscribeToNextViewportGridChange.ts +++ b/platform/core/src/utils/subscribeToNextViewportGridChange.ts @@ -1,7 +1,7 @@ import { ViewportGridService } from '../services'; /** - * Subscribes to the very next LAYOUT_CHANGED or GRID_STATE_CHANGED event that + * Subscribes to the very next LAYOUT_CHANGED event that * is not currently on the event queue. The subscriptions are made on a 'zero' * timeout so as to avoid responding to any of those events currently on the event queue. * The subscription persists only for a single invocation of either event. @@ -24,10 +24,6 @@ function subscribeToNextViewportGridChange( viewportGridService.EVENTS.LAYOUT_CHANGED, callback ), - viewportGridService.subscribe( - viewportGridService.EVENTS.GRID_STATE_CHANGED, - callback - ), ]; }; diff --git a/platform/docs/docs/platform/extensions/modules/viewport.md b/platform/docs/docs/platform/extensions/modules/viewport.md index 4db2561ed..7ede0bf1b 100644 --- a/platform/docs/docs/platform/extensions/modules/viewport.md +++ b/platform/docs/docs/platform/extensions/modules/viewport.md @@ -43,20 +43,13 @@ const getViewportModule = () => { A simplified version of the tracked `OHIFCornerstoneViewport` is shown below, which creates a cornerstone viewport: -:::note Tip - -Not in OHIF version 3.1 we use `displaySets` in the props which is new compared to -the previous version (3.0) which uses `displaySet`. This is due to the fact that -we are moving to a new data model that can render fused images in a single viewport. - -::: ```jsx function TrackedCornerstoneViewport({ children, dataSource, displaySets, - viewportIndex, + viewportId, servicesManager, extensionManager, commandsManager, @@ -87,6 +80,45 @@ function TrackedCornerstoneViewport({ } ``` +### Viewport re-rendering optimizations + +We make use of the React memoization pattern to prevent unnecessary re-renders +for the viewport unless certain aspects of the Viewport props change. You can take +a look into the `areEqual` function in the `OHIFCornerstoneViewport` component to +see how this is done. + +```js +function areEqual(prevProps, nextProps) { + if (prevProps.displaySets.length !== nextProps.displaySets.length) { + return false; + } + + if ( + prevProps.viewportOptions.orientation !== + nextProps.viewportOptions.orientation + ) { + return false; + } + + // rest of the code +``` + +as you see, we check if the `needsRerendering` prop is true, and if so, we will +re-render the viewport if the `displaySets` prop changes or the orientation +changes. + + +We use viewportId to identify a viewport and we use it as a key in React +rendering. This is important because it allows us to keep track of the viewport +and its state, and also let React optimize and move the viewport around in the +grid without re-rendering it. However, there are some cases where we need to +force re-render the viewport, for example, when the viewport is hydrated +with a new Segmentation. For these cases, we use the `needsRerendering` prop +to force re-render the viewport. You can add it to the `viewportOptions` + + + + ### `@ohif/app` diff --git a/platform/docs/docs/platform/services/data/MeasurementService.md b/platform/docs/docs/platform/services/data/MeasurementService.md index 21bd4589f..f4ecd51f6 100644 --- a/platform/docs/docs/platform/services/data/MeasurementService.md +++ b/platform/docs/docs/platform/services/data/MeasurementService.md @@ -68,7 +68,7 @@ There are seven events that get publish in `MeasurementService`: - `toMeasurementSchema`: A function to get the `data` into the same shape as the source definition. -- `jumpToMeasurement(viewportIndex, id)`: calls the listeners who have +- `jumpToMeasurement(viewportId, id)`: calls the listeners who have subscribed to `JUMP_TO_MEASUREMENT`. ## Source / Mappers diff --git a/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md b/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md index 737042845..b50e3e7e4 100644 --- a/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md +++ b/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md @@ -48,7 +48,7 @@ is expected to support, [check out it's interface in `@ohif/core`][interface] ```js const DEFAULT_STATE = { - viewportIndex: null, + viewportId: null, message: undefined, type: 'info', // "error" | "warning" | "info" | "success" actions: undefined, // array of { type, text, value } diff --git a/platform/docs/docs/platform/services/ui/viewport-grid-service.md b/platform/docs/docs/platform/services/ui/viewport-grid-service.md index d523a7415..9ad86a69f 100644 --- a/platform/docs/docs/platform/services/ui/viewport-grid-service.md +++ b/platform/docs/docs/platform/services/ui/viewport-grid-service.md @@ -15,7 +15,7 @@ There are seven events that get publish in `ViewportGridService `: | Event | Description | | ----------------------------- | --------------------------------------------------| -| ACTIVE_VIEWPORT_INDEX_CHANGED | Fires the index of the active viewport is changed | +| ACTIVE_VIEWPORT_ID_CHANGED | Fires the Id of the active viewport is changed | | LAYOUT_CHANGED | Fires the layout is changed | | GRID_STATE_CHANGED | Fires when the entire grid state is changed | ## Interface @@ -25,9 +25,9 @@ is expected to support, [check out it's interface in `@ohif/core`][interface] | API Member | Description | | --------------------------------------------------------------------- | --------------------------------------------------- | -| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | +| `setActiveViewportId(viewportId)` | Sets the active viewport Id in the app | | `getState()` | Gets the states of the viewport (see below) | -| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | +| `setDisplaySetsForViewport({ viewportId, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | | `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. | | `reset()` | Resets the default states | | `getNumViewportPanes()` | Gets the number of visible viewport panes | @@ -56,6 +56,6 @@ const DEFAULT_STATE = { * } */ ], - activeViewportIndex: 0, + activeViewportId: null, }; ``` diff --git a/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md index 4291c99f3..00e244d24 100644 --- a/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md @@ -25,7 +25,7 @@ Each `ViewportComponent` will receive the following props: ```html ``` @@ -34,7 +34,7 @@ Each `ViewportComponent` will receive the following props: | --------------- | --------------- | --------------------------------- | | `children` | React.element[] | | | `viewportData` | object | `viewportSpecificData` (probably) | -| `viewportIndex` | number | | +| `viewportId` | string | | ### `@ohif/viewer` diff --git a/platform/ui/src/components/Dialog/Footer.tsx b/platform/ui/src/components/Dialog/Footer.tsx index 5f571345c..0c43e7b9b 100644 --- a/platform/ui/src/components/Dialog/Footer.tsx +++ b/platform/ui/src/components/Dialog/Footer.tsx @@ -10,7 +10,7 @@ const Footer = ({ actions, className, onSubmit, value }) => { return (
- {actions.map((action, index) => { + {actions?.map((action, index) => { const isFirst = index === 0; const onClickHandler = event => onSubmit({ action, value, event }); diff --git a/platform/ui/src/components/Notification/Notification.tsx b/platform/ui/src/components/Notification/Notification.tsx index cd9cdef6a..edee4c4a8 100644 --- a/platform/ui/src/components/Notification/Notification.tsx +++ b/platform/ui/src/components/Notification/Notification.tsx @@ -73,9 +73,7 @@ const Notification = ({ {message}
- {actions.map((action, index) => { - const isFirst = index === 0; - + {actions?.map((action, index) => { return (