From b2932845003717e4ae79dbc3ccf44d459d3d0208 Mon Sep 17 00:00:00 2001 From: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:07:59 -0500 Subject: [PATCH 1/7] fix(tag-browser): fix dicom tag browser not loading in segmentation mode in study panel (#4601) --- extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx index 80480f18114..f3c2e247398 100644 --- a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx @@ -30,6 +30,7 @@ function WrappedPanelStudyBrowser({ commandsManager, extensionManager, servicesM return ( Date: Tue, 17 Dec 2024 12:34:13 -0500 Subject: [PATCH 2/7] fix(seg): jump to the first slice in SEG and RT that has data (#4605) --- .../viewports/OHIFCornerstonePMAPViewport.tsx | 1 + .../cornerstone-dicom-rt/src/loadRTStruct.js | 10 +++- .../viewports/OHIFCornerstoneRTViewport.tsx | 20 +++++-- .../viewports/OHIFCornerstoneSEGViewport.tsx | 13 +++++ .../OHIFCornerstoneSRMeasurementViewport.tsx | 56 +++++-------------- extensions/cornerstone/src/hps/frameView.ts | 2 +- .../SegmentationService.ts | 24 +++++++- .../CornerstoneViewportService.ts | 36 +++++++++--- .../default/src/getHangingProtocolModule.js | 2 +- .../default/src/hangingprotocols/hpCompare.ts | 4 +- .../default/src/hangingprotocols/hpMNGrid.ts | 2 +- .../default/src/hangingprotocols/hpScale.ts | 2 +- extensions/test-extension/src/hpTestSwitch.ts | 2 +- platform/app/public/config/local_orthanc.js | 1 + 14 files changed, 110 insertions(+), 65 deletions(-) diff --git a/extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx b/extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx index 2133e96ddfd..959d8dbfe37 100644 --- a/extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx +++ b/extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx @@ -97,6 +97,7 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) { viewportType: 'volume', orientation: viewportOptions.orientation, viewportId: viewportOptions.viewportId, + presentationIds: viewportOptions.presentationIds, }} displaySetOptions={[{}, pmapDisplaySetOptions]} > diff --git a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js index 10d9cc99f5b..0e9ab38417e 100644 --- a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js +++ b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js @@ -126,6 +126,7 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet, SeriesInstanceUID: instance.SeriesInstanceUID, ROIContours: [], visible: true, + ReferencedSOPInstanceUIDsSet: new Set(), }; for (let i = 0; i < ROIContourSequence.length; i++) { @@ -142,7 +143,8 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet, const contourPoints = []; for (let c = 0; c < ContourSequenceArray.length; c++) { - const { ContourData, NumberOfContourPoints, ContourGeometricType } = ContourSequenceArray[c]; + const { ContourData, NumberOfContourPoints, ContourGeometricType, ContourImageSequence } = + ContourSequenceArray[c]; let isSupported = false; @@ -172,6 +174,12 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet, type: ContourGeometricType, isSupported, }); + + if (ContourImageSequence?.ReferencedSOPInstanceUID) { + structureSet.ReferencedSOPInstanceUIDsSet.add( + ContourImageSequence?.ReferencedSOPInstanceUID + ); + } } _setROIContourMetadata( diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx index 1c4a9e0e9b3..662541ff1d9 100644 --- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx +++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx @@ -6,7 +6,7 @@ import promptHydrateRT from '../utils/promptHydrateRT'; import _getStatusComponent from './_getStatusComponent'; import createRTToolGroupAndAddTools from '../utils/initRTToolGroup'; -import { SegmentationRepresentations } from '@cornerstonejs/tools/enums'; +import { usePositionPresentationStore } from '@ohif/extension-cornerstone'; const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup'; @@ -43,8 +43,8 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) { const [viewportGrid, viewportGridService] = useViewportGrid(); // States - const [isToolGroupCreated, setToolGroupCreated] = useState(false); const [selectedSegment, setSelectedSegment] = useState(1); + const { setPositionPresentation } = usePositionPresentationStore(); // Hydration means that the RT is opened and segments are loaded into the // segmentation panel, and RT is also rendered on any viewport that is in the @@ -123,6 +123,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) { toolGroupId: toolGroupId, orientation: viewportOptions.orientation, viewportId: viewportOptions.viewportId, + presentationIds: viewportOptions.presentationIds, }} onElementEnabled={evt => { props.onElementEnabled?.(evt); @@ -185,6 +186,19 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) { setRtIsLoading(false); } + if (rtDisplaySet?.firstSegmentedSliceImageId && viewportOptions?.presentationIds) { + const { firstSegmentedSliceImageId } = rtDisplaySet; + const { presentationIds } = viewportOptions; + + setPositionPresentation(presentationIds.positionPresentationId, { + viewportType: 'stack', + viewReference: { + referencedImageId: firstSegmentedSliceImageId, + }, + viewPresentation: {}, + }); + } + if (evt.overlappingSegments) { uiNotificationService.show({ title: 'Overlapping Segments', @@ -247,8 +261,6 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) { toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId); - setToolGroupCreated(true); - return () => { // remove the segmentation representations if seg displayset changed segmentationService.removeSegmentationRepresentations(viewportId); diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx index 21dff244fb9..340afad1662 100644 --- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx +++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx @@ -4,6 +4,7 @@ import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } f import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup'; import promptHydrateSEG from '../utils/promptHydrateSEG'; import _getStatusComponent from './_getStatusComponent'; +import { usePositionPresentationStore } from '@ohif/extension-cornerstone'; import { SegmentationRepresentations } from '@cornerstonejs/tools/enums'; const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup'; @@ -41,6 +42,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) { // States const [selectedSegment, setSelectedSegment] = useState(1); + const { setPositionPresentation } = usePositionPresentationStore(); // Hydration means that the SEG is opened and segments are loaded into the // segmentation panel, and SEG is also rendered on any viewport that is in the @@ -198,6 +200,17 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) { if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) { setSegIsLoading(false); } + + if (segDisplaySet?.firstSegmentedSliceImageId && viewportOptions?.presentationIds) { + const { firstSegmentedSliceImageId } = segDisplaySet; + const { presentationIds } = viewportOptions; + + setPositionPresentation(presentationIds.positionPresentationId, { + viewReference: { + referencedImageId: firstSegmentedSliceImageId, + }, + }); + } } ); diff --git a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx index 7c63f346c7c..847b40ca2a7 100644 --- a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx @@ -15,24 +15,13 @@ const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking' const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup'; function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) { - const { - commandsManager, - children, - dataSource, - displaySets, - viewportOptions, - servicesManager, - extensionManager, - } = props; + const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } = + props; const [appConfig] = useAppConfig(); - const { - displaySetService, - cornerstoneViewportService, - measurementService, - viewportActionCornersService, - } = servicesManager.services; + const { displaySetService, measurementService, viewportActionCornersService } = + servicesManager.services; const viewportId = viewportOptions.viewportId; @@ -43,6 +32,8 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) { const srDisplaySet = displaySets[0]; + const { setPositionPresentation } = usePositionPresentationStore(); + const [viewportGrid, viewportGridService] = useViewportGrid(); const [measurementSelected, setMeasurementSelected] = useState(0); const [measurementCount, setMeasurementCount] = useState(1); @@ -157,30 +148,13 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) { setActiveImageDisplaySetData(referencedDisplaySet); setReferencedDisplaySetMetadata(referencedDisplaySetMetadata); - if ( - referencedDisplaySet.displaySetInstanceUID === - activeImageDisplaySetData?.displaySetInstanceUID - ) { - const { measurements } = srDisplaySet; - - // it means that we have a new referenced display set, and the - // 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 csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); - - if (!csViewport) { - return; - } - - const imageIds = csViewport.getImageIds(); - - const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId); - - if (imageIdIndex !== -1) { - csViewport.setImageIdIndex(imageIdIndex); - } - } + const { presentationIds } = viewportOptions; + const measurement = srDisplaySet.measurements[newMeasurementSelected]; + setPositionPresentation(presentationIds.positionPresentationId, { + viewReference: { + referencedImageId: measurement.imageId, + }, + }); }); }, [dataSource, srDisplaySet, activeImageDisplaySetData, viewportId] @@ -202,10 +176,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) { return null; } - const initialImageIndex = activeImageDisplaySetData.images.findIndex( - image => image.imageId === measurement.imageId - ); - return ( ; const scalarData = voxelManager.getScalarData(); - scalarData.set(volumeScalarData.slice(i * scalarData.length, (i + 1) * scalarData.length)); + const sliceData = volumeScalarData.slice(i * scalarData.length, (i + 1) * scalarData.length); + scalarData.set(sliceData); voxelManager.setScalarData(scalarData); + + // Check if this slice has any non-zero voxels and we haven't found one yet + if (!firstSegmentedSliceImageId && sliceData.some(value => value !== 0)) { + firstSegmentedSliceImageId = derivedSegmentationImages[i].referencedImageId; + } } + // assign the first non zero voxel image id to the segDisplaySet + segDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId; + this._broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, { segmentationId, segDisplaySet, @@ -542,7 +552,19 @@ class SegmentationService extends PubSubService { } const rtDisplaySetUID = rtDisplaySet.displaySetInstanceUID; + const referencedDisplaySet = this.servicesManager.services.displaySetService.getDisplaySetByUID( + rtDisplaySet.referencedDisplaySetInstanceUID + ); + + const referencedImageIdsWithGeometry = Array.from(structureSet.ReferencedSOPInstanceUIDsSet); + + const referencedImageIds = referencedDisplaySet.instances.map(image => image.imageId); + // find the first image id that contains a referenced SOP instance UID + const firstSegmentedSliceImageId = referencedImageIds.find(imageId => + referencedImageIdsWithGeometry.some(referencedId => imageId.includes(referencedId)) + ); + rtDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId; // Map ROI contours to RT Struct Data const allRTStructData = mapROIContoursToRTStructData(structureSet, rtDisplaySetUID); diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts index c07a4de01e2..56d338c48d8 100644 --- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -605,10 +605,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi let initialImageIndexToUse = presentations?.positionPresentation?.initialImageIndex ?? initialImageIndex; - if (initialImageIndexToUse === undefined || initialImageIndexToUse === null) { - initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0; - } - const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions(); const properties = { ...presentations.lutPresentation?.properties }; @@ -637,12 +633,26 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi }); let imageIdsToSet = imageIds; - const res = this._processExtraDisplaySetsForViewport(viewport); - imageIdsToSet = res?.imageIds ?? imageIdsToSet; + const overlayProcessingResult = this._processExtraDisplaySetsForViewport(viewport); + imageIdsToSet = overlayProcessingResult?.imageIds ?? imageIdsToSet; + + const referencedImageId = presentations?.positionPresentation?.viewReference?.referencedImageId; + if (referencedImageId) { + initialImageIndexToUse = imageIdsToSet.indexOf(referencedImageId); + } + + if (initialImageIndexToUse === undefined || initialImageIndexToUse === null) { + initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0; + } return viewport.setStack(imageIdsToSet, initialImageIndexToUse).then(() => { viewport.setProperties({ ...properties }); this.setPresentations(viewport.id, presentations, viewportInfo); + + if (overlayProcessingResult?.addOverlayFn) { + overlayProcessingResult.addOverlayFn(); + } + if (displayArea) { viewport.setDisplayArea(displayArea); } @@ -827,10 +837,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi }); // For SEG and RT viewports - this._processExtraDisplaySetsForViewport(viewport); + const { addOverlayFn } = this._processExtraDisplaySetsForViewport(viewport) || {}; await viewport.setVolumes(volumeInputArray); + if (addOverlayFn) { + addOverlayFn(); + } + volumesProperties.forEach(({ properties, volumeId }) => { viewport.setProperties(properties, volumeId); }); @@ -880,8 +894,12 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi segOrRTSOverlayDisplaySet.referencedDisplaySetInstanceUID ); const imageIds = referenceDisplaySet.images.map(image => image.imageId); - this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport); - return { imageIds }; + + return { + imageIds, + addOverlayFn: () => + this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport), + }; } private addOverlayRepresentationForDisplaySet( diff --git a/extensions/default/src/getHangingProtocolModule.js b/extensions/default/src/getHangingProtocolModule.js index ec0da35794b..a65891ea56b 100644 --- a/extensions/default/src/getHangingProtocolModule.js +++ b/extensions/default/src/getHangingProtocolModule.js @@ -63,7 +63,7 @@ const defaultProtocol = { // It has no affect if nothing is specified in the URL. { attribute: 'isDisplaySetFromUrl', - weight: 10, + weight: 20, constraint: { equals: true, }, diff --git a/extensions/default/src/hangingprotocols/hpCompare.ts b/extensions/default/src/hangingprotocols/hpCompare.ts index 87a4f5e0279..14cb51d93fa 100644 --- a/extensions/default/src/hangingprotocols/hpCompare.ts +++ b/extensions/default/src/hangingprotocols/hpCompare.ts @@ -24,7 +24,7 @@ const defaultDisplaySetSelector = { // It has no affect if nothing is specified in the URL. { attribute: 'isDisplaySetFromUrl', - weight: 10, + weight: 20, constraint: { equals: true, }, @@ -56,7 +56,7 @@ const priorDisplaySetSelector = { // It has no affect if nothing is specified in the URL. { attribute: 'isDisplaySetFromUrl', - weight: 10, + weight: 20, constraint: { equals: true, }, diff --git a/extensions/default/src/hangingprotocols/hpMNGrid.ts b/extensions/default/src/hangingprotocols/hpMNGrid.ts index 1f07242c8a2..0c31815d258 100644 --- a/extensions/default/src/hangingprotocols/hpMNGrid.ts +++ b/extensions/default/src/hangingprotocols/hpMNGrid.ts @@ -50,7 +50,7 @@ const hpMN: Types.HangingProtocol.Protocol = { // It has no affect if nothing is specified in the URL. { attribute: 'isDisplaySetFromUrl', - weight: 10, + weight: 20, constraint: { equals: true, }, diff --git a/extensions/default/src/hangingprotocols/hpScale.ts b/extensions/default/src/hangingprotocols/hpScale.ts index 651413c24eb..e76f5d591b5 100644 --- a/extensions/default/src/hangingprotocols/hpScale.ts +++ b/extensions/default/src/hangingprotocols/hpScale.ts @@ -43,7 +43,7 @@ const hpScale: Types.HangingProtocol.Protocol = { // It has no affect if nothing is specified in the URL. { attribute: 'isDisplaySetFromUrl', - weight: 10, + weight: 20, constraint: { equals: true, }, diff --git a/extensions/test-extension/src/hpTestSwitch.ts b/extensions/test-extension/src/hpTestSwitch.ts index 8b3fda5aa6b..4e497a61ed4 100644 --- a/extensions/test-extension/src/hpTestSwitch.ts +++ b/extensions/test-extension/src/hpTestSwitch.ts @@ -188,7 +188,7 @@ const hpTestSwitch: Types.HangingProtocol.Protocol = { // It has no affect if nothing is specified in the URL. { attribute: 'isDisplaySetFromUrl', - weight: 10, + weight: 20, constraint: { equals: true, }, diff --git a/platform/app/public/config/local_orthanc.js b/platform/app/public/config/local_orthanc.js index b5c287f5f7a..a3bdc8ace19 100644 --- a/platform/app/public/config/local_orthanc.js +++ b/platform/app/public/config/local_orthanc.js @@ -23,6 +23,7 @@ window.config = { wadoRoot: 'http://localhost/dicom-web', qidoSupportsIncludeField: true, supportsReject: true, + dicomUploadEnabled: true, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, From ceaafe4800e859875ee2b2b5bb9419bd5037ea99 Mon Sep 17 00:00:00 2001 From: sedghi Date: Tue, 14 Jan 2025 15:35:34 -0500 Subject: [PATCH 3/7] chore(version): bump version to 3.9.3 and refactor OHIFCornerstoneSRMeasurementViewport component to improve service management and remove unused code --- .../OHIFCornerstoneSRMeasurementViewport.tsx | 14 +++++++++----- version.txt | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx index 847b40ca2a7..592ee486324 100644 --- a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx @@ -9,6 +9,7 @@ import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui'; import hydrateStructuredReport from '../utils/hydrateStructuredReport'; import { useAppConfig } from '@state'; import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet'; +import { usePositionPresentationStore } from '@ohif/extension-cornerstone'; const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking'; @@ -20,8 +21,14 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) { const [appConfig] = useAppConfig(); - const { displaySetService, measurementService, viewportActionCornersService } = - servicesManager.services; + const { setPositionPresentation } = usePositionPresentationStore(); + + const { + displaySetService, + cornerstoneViewportService, + measurementService, + viewportActionCornersService, + } = servicesManager.services; const viewportId = viewportOptions.viewportId; @@ -32,8 +39,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) { const srDisplaySet = displaySets[0]; - const { setPositionPresentation } = usePositionPresentationStore(); - const [viewportGrid, viewportGridService] = useViewportGrid(); const [measurementSelected, setMeasurementSelected] = useState(0); const [measurementCount, setMeasurementCount] = useState(1); @@ -200,7 +205,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) { props.onElementEnabled?.(evt); onElementEnabled(evt); }} - initialImageIndex={initialImageIndex} isJumpToMeasurementDisabled={true} > ); diff --git a/version.txt b/version.txt index 4764627f925..03d70eaebc4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.9.2 \ No newline at end of file +3.9.3 \ No newline at end of file From de84e565562348fce6b67c3417be76aeb923b04e Mon Sep 17 00:00:00 2001 From: Alireza Date: Wed, 15 Jan 2025 22:29:06 -0500 Subject: [PATCH 4/7] fix(multiframe): handling proxies properly (#4693) Co-authored-by: Bill Wallace Co-authored-by: Chao Sui --- .../default/src/DicomLocalDataSource/index.js | 28 +++---- .../default/src/DicomWebDataSource/index.js | 1 - platform/core/src/classes/MetadataProvider.ts | 7 -- .../core/src/utils/combineFrameInstance.ts | 80 +++++++++++++++---- 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/extensions/default/src/DicomLocalDataSource/index.js b/extensions/default/src/DicomLocalDataSource/index.js index a0ea9fb4ffa..57badc06c68 100644 --- a/extensions/default/src/DicomLocalDataSource/index.js +++ b/extensions/default/src/DicomLocalDataSource/index.js @@ -142,7 +142,9 @@ function createDicomLocalApi(dicomLocalConfig) { study.series.forEach(aSeries => { const { SeriesInstanceUID } = aSeries; - aSeries.instances.forEach(instance => { + const isMultiframe = aSeries.instances[0].NumberOfFrames > 1; + + aSeries.instances.forEach((instance, index) => { const { url: imageId, StudyInstanceUID, @@ -151,22 +153,14 @@ function createDicomLocalApi(dicomLocalConfig) { } = instance; instance.imageId = imageId; - const numberOfFrames = instance.NumberOfFrames || 1; - // Process all frames consistently, whether single or multiframe - for (let i = 0; i < numberOfFrames; i++) { - const frameNumber = i + 1; - const frameImageId = implementation.getImageIdsForInstance({ - instance, - frame: frameNumber, - }); - // Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI. - metadataProvider.addImageIdToUIDs(frameImageId, { - StudyInstanceUID, - SeriesInstanceUID, - SOPInstanceUID, - frameNumber: numberOfFrames > 1 ? frameNumber : undefined, - }); - } + + // Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI. + metadataProvider.addImageIdToUIDs(imageId, { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + frameIndex: isMultiframe ? index : 1, + }); }); DicomMetadataStore._broadcastEvent(EVENTS.INSTANCES_ADDED, { diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index 29dbdd32474..c960c17b6dd 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -435,7 +435,6 @@ function createDicomWebApi(dicomWebConfig, servicesManager) { instance.wadoUri = dicomWebConfig.wadoUri; const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance; - const numberOfFrames = instance.NumberOfFrames || 1; // Process all frames consistently, whether single or multiframe for (let i = 0; i < numberOfFrames; i++) { diff --git a/platform/core/src/classes/MetadataProvider.ts b/platform/core/src/classes/MetadataProvider.ts index 08c421fefcd..ddd505a32cb 100644 --- a/platform/core/src/classes/MetadataProvider.ts +++ b/platform/core/src/classes/MetadataProvider.ts @@ -9,7 +9,6 @@ import combineFrameInstance from '../utils/combineFrameInstance'; class MetadataProvider { private readonly imageURIToUIDs: Map = new Map(); - private readonly imageUIDsByImageId: Map = new Map(); // Can be used to store custom metadata for a specific type. // For instance, the scaling metadata for PET can be stored here // as type "scalingModule" @@ -25,7 +24,6 @@ class MetadataProvider { // An example would be dicom hosted at some random site. const imageURI = imageIdToURI(imageId); this.imageURIToUIDs.set(imageURI, uids); - this.imageUIDsByImageId.set(imageId, uids); } addCustomMetadata(imageId, type, metadata) { @@ -462,11 +460,6 @@ class MetadataProvider { } getUIDsFromImageID(imageId) { - const cachedUIDs = this.imageUIDsByImageId.get(imageId); - if (cachedUIDs) { - return cachedUIDs; - } - if (imageId.startsWith('wadors:')) { const strippedImageId = imageId.split('/studies/')[1]; const splitImageId = strippedImageId.split('/'); diff --git a/platform/core/src/utils/combineFrameInstance.ts b/platform/core/src/utils/combineFrameInstance.ts index cf70232cf5d..2487ca5cb76 100644 --- a/platform/core/src/utils/combineFrameInstance.ts +++ b/platform/core/src/utils/combineFrameInstance.ts @@ -73,27 +73,77 @@ const combineFrameInstance = (frame, instance) => { ImagePositionPatientToUse = [position[0], position[1], position[2]]; } } - console.debug('🚀 ~ ImagePositionPatientToUse:', ImagePositionPatientToUse); - const newInstance = Object.assign(instance, { frameNumber: frameNumber }); - - // merge the shared first then the per frame to override - [...shared, ...perFrame].forEach(item => { - Object.entries(item).forEach(([key, value]) => { - newInstance[key] = value; + // Cache the _parentInstance at the top level as a full copy to prevent + // setting values hard. + if (!instance._parentInstance) { + Object.defineProperty(instance, '_parentInstance', { + value: { ...instance }, }); - }); + } + const sharedInstance = createCombinedValue( + instance._parentInstance, + SharedFunctionalGroupsSequence?.[0], + '_shared' + ); + const newInstance = createCombinedValue( + sharedInstance, + PerFrameFunctionalGroupsSequence?.[frameNumber - 1], + frameNumber + ); + + newInstance.ImagePositionPatient = ImagePositionPatientToUse ?? + newInstance.ImagePositionPatient ?? [0, 0, frameNumber]; - // Todo: we should cache this combined instance somewhere, maybe add it - // back to the dicomMetaStore so we don't have to do this again. - return { - ...newInstance, - ImagePositionPatient: ImagePositionPatientToUse ?? - newInstance.ImagePositionPatient ?? [0, 0, frameNumber], - }; + Object.defineProperty(newInstance, 'frameNumber', { + value: frameNumber, + writable: true, + enumerable: true, + configurable: true, + }); + return newInstance; } else { return instance; } }; +/** + * Creates a combined instance stored in the parent object which + * inherits from the parent instance the attributes in the functional groups. + * The storage key in the parent is in key + */ +function createCombinedValue(parent, functionalGroups, key) { + if (parent[key]) { + return parent[key]; + } + // Exclude any proxying values + const newInstance = Object.create(parent); + Object.defineProperty(parent, key, { + value: newInstance, + writable: false, + enumerable: false, + }); + if (!functionalGroups) { + return newInstance; + } + const shared = functionalGroups + ? Object.values(functionalGroups) + .filter(Boolean) + .map(it => it[0]) + .filter(it => typeof it === 'object') + : []; + + // merge the shared first then the per frame to override + [...shared].forEach(item => { + if (item.SOPInstanceUID) { + // This sub-item is a previous value information item, so don't merge it + return; + } + Object.entries(item).forEach(([key, value]) => { + newInstance[key] = value; + }); + }); + return newInstance; +} + export default combineFrameInstance; From 44f5b032d23bb1d2cb249d36c6e7b1472cfd392a Mon Sep 17 00:00:00 2001 From: sedghi Date: Thu, 16 Jan 2025 15:51:02 -0500 Subject: [PATCH 5/7] fix(segmentation): add instance check in PanelSegmentation, disable debug mode in stores, and clean up unused code in layout caching --- .../src/actions/updateSegmentationsChartDisplaySet.ts | 1 - extensions/cornerstone/src/panels/PanelSegmentation.tsx | 8 ++++++++ .../src/stores/usePositionPresentationStore.ts | 2 +- .../default/src/stores/useViewportsByPositionStore.ts | 2 +- extensions/default/src/utils/reuseCachedLayouts.ts | 8 -------- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts b/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts index 78df25a66b6..7e901804bc9 100644 --- a/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts +++ b/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts @@ -262,7 +262,6 @@ function _getInstanceFromSegmentations(segmentations, { servicesManager }) { } function updateSegmentationsChartDisplaySet({ servicesManager }: withAppTypes): void { - debugger; const { segmentationService } = servicesManager.services; const segmentations = segmentationService.getSegmentations(); const { seriesMetadata, instance } = diff --git a/extensions/cornerstone/src/panels/PanelSegmentation.tsx b/extensions/cornerstone/src/panels/PanelSegmentation.tsx index 03109fc5c73..83bbc8bfb0a 100644 --- a/extensions/cornerstone/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone/src/panels/PanelSegmentation.tsx @@ -162,6 +162,14 @@ export default function PanelSegmentation({ const firstImageId = referencedImageIds[0]; const instance = metaData.get('instance', firstImageId); + + if (!instance) { + return { + segmentationId, + isExportable: false, + }; + } + const { SOPInstanceUID, SeriesInstanceUID } = instance; const displaySet = displaySetService.getDisplaySetForSOPInstanceUID( diff --git a/extensions/cornerstone/src/stores/usePositionPresentationStore.ts b/extensions/cornerstone/src/stores/usePositionPresentationStore.ts index 1b06bd8e52d..ac6d94c9f84 100644 --- a/extensions/cornerstone/src/stores/usePositionPresentationStore.ts +++ b/extensions/cornerstone/src/stores/usePositionPresentationStore.ts @@ -4,7 +4,7 @@ import { PositionPresentation } from '../types/Presentation'; import { addUniqueIndex, JOIN_STR } from './presentationUtils'; const PRESENTATION_TYPE_ID = 'positionPresentationId'; -const DEBUG_STORE = true; +const DEBUG_STORE = false; /** * Represents the state and actions for managing position presentations. diff --git a/extensions/default/src/stores/useViewportsByPositionStore.ts b/extensions/default/src/stores/useViewportsByPositionStore.ts index 6466caf7fb2..1dadcacec7b 100644 --- a/extensions/default/src/stores/useViewportsByPositionStore.ts +++ b/extensions/default/src/stores/useViewportsByPositionStore.ts @@ -2,7 +2,7 @@ import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; const PRESENTATION_TYPE_ID = 'viewportsByPositionId'; -const DEBUG_STORE = true; +const DEBUG_STORE = false; /** * Represents the state and actions for managing viewports by position. diff --git a/extensions/default/src/utils/reuseCachedLayouts.ts b/extensions/default/src/utils/reuseCachedLayouts.ts index 3e6f72850cc..b3a0c3e7eff 100644 --- a/extensions/default/src/utils/reuseCachedLayouts.ts +++ b/extensions/default/src/utils/reuseCachedLayouts.ts @@ -61,14 +61,6 @@ const reuseCachedLayout = (state, hangingProtocolService: HangingProtocolService if (viewportId === activeViewportId && i === 0) { setDisplaySetSelector(`${activeStudyUID}:activeDisplaySet:0`, displaySetUID); } - if (displaySetOptions[i]?.id) { - setDisplaySetSelector( - `${activeStudyUID}:${displaySetOptions[i].id}:${ - displaySetOptions[i].matchedDisplaySetsIndex || 0 - }`, - displaySetUID - ); - } } }); From 4bc062693452863629ee9f9510f40f8207eb9e8f Mon Sep 17 00:00:00 2001 From: sedghi Date: Thu, 16 Jan 2025 15:57:15 -0500 Subject: [PATCH 6/7] fix(combineFrameInstance): integrate dicomSplit for ImageType processing and streamline frame instance handling --- .../core/src/utils/combineFrameInstance.ts | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/platform/core/src/utils/combineFrameInstance.ts b/platform/core/src/utils/combineFrameInstance.ts index 2487ca5cb76..4202c5b0fd7 100644 --- a/platform/core/src/utils/combineFrameInstance.ts +++ b/platform/core/src/utils/combineFrameInstance.ts @@ -1,4 +1,5 @@ import { vec3 } from 'gl-matrix'; +import { dicomSplit } from './dicomSplit'; /** * Combine the Per instance frame data, the shared frame data @@ -15,24 +16,13 @@ const combineFrameInstance = (frame, instance) => { PerFrameFunctionalGroupsSequence, SharedFunctionalGroupsSequence, NumberOfFrames, - SpacingBetweenSlices, + ImageType, } = instance; + instance.ImageType = dicomSplit(ImageType); + if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) { const frameNumber = Number.parseInt(frame || 1); - const shared = SharedFunctionalGroupsSequence - ? Object.values(SharedFunctionalGroupsSequence[0]) - .filter(Boolean) - .map(it => it[0]) - .filter(it => typeof it === 'object') - : []; - - const perFrame = PerFrameFunctionalGroupsSequence - ? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1]) - .filter(Boolean) - .map(it => it[0]) - .filter(it => typeof it === 'object') - : []; // this is to fix NM multiframe datasets with position and orientation // information inside DetectorInformationSequence @@ -44,8 +34,12 @@ const combineFrameInstance = (frame, instance) => { let ImagePositionPatientToUse = instance.ImagePositionPatient; if (!instance.ImagePositionPatient && instance.DetectorInformationSequence) { - const imagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient; - const imageOrientationPatient = instance.ImageOrientationPatient; + let imagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient; + let imageOrientationPatient = instance.ImageOrientationPatient; + + imagePositionPatient = imagePositionPatient.map(it => Number(it)); + imageOrientationPatient = imageOrientationPatient.map(it => Number(it)); + const SpacingBetweenSlices = Number(instance.SpacingBetweenSlices); // Calculate the position for the current frame if (imageOrientationPatient && SpacingBetweenSlices) { From cf1f0d8d4b7a1cdf1a5400bd83f8d602bc781701 Mon Sep 17 00:00:00 2001 From: sedghi Date: Thu, 16 Jan 2025 16:08:51 -0500 Subject: [PATCH 7/7] file --- platform/core/src/utils/dicomSplit.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 platform/core/src/utils/dicomSplit.ts diff --git a/platform/core/src/utils/dicomSplit.ts b/platform/core/src/utils/dicomSplit.ts new file mode 100644 index 00000000000..54b20d38f0d --- /dev/null +++ b/platform/core/src/utils/dicomSplit.ts @@ -0,0 +1,5 @@ +export function dicomSplit(value) { + return ( + (Array.isArray(value) && value) || (typeof value === 'string' && value.split('\\')) || value + ); +}