diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index f921626e0..27504ed29 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -34,7 +34,7 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", - "dcmjs": "^0.24.5", + "dcmjs": "^0.28.1", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js index a9bb0abd9..0cfebaeda 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js @@ -6,7 +6,7 @@ import { adapters } from 'dcmjs'; const { CodeScheme: Cornerstone3DCodeScheme } = adapters.Cornerstone3D; -const { ImageSet } = classes; +const { ImageSet, MetadataProvider: metadataProvider } = classes; // TODO -> // Add SR thumbnail // Make viewport @@ -214,20 +214,26 @@ function _checkIfCanAddMeasurementsToDisplaySet( newDisplaySet ); - for (let i = 0; i < images.length; i++) { + for (const imageId of imageIdsForDisplaySet) { if (!unloadedMeasurements.length) { // All measurements loaded. - break; + return; } - const image = images[i]; - const { SOPInstanceUID } = image; - if (SOPInstanceUIDs.includes(SOPInstanceUID)) { - const imageId = imageIdsForDisplaySet[i]; + const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID( + imageId + ); + if (SOPInstanceUIDs.includes(SOPInstanceUID)) { for (let j = unloadedMeasurements.length - 1; j >= 0; j--) { const measurement = unloadedMeasurements[j]; - if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID)) { + if ( + _measurementReferencesSOPInstanceUID( + measurement, + SOPInstanceUID, + frameNumber + ) + ) { addMeasurement( measurement, imageId, @@ -241,9 +247,23 @@ function _checkIfCanAddMeasurementsToDisplaySet( } } -function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID) { +function _measurementReferencesSOPInstanceUID( + measurement, + SOPInstanceUID, + frameNumber +) { const { coords } = measurement; + // NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM + // Standard. But for now, we will support only one ReferenceFrameNumber. + const ReferencedFrameNumber = + (measurement.coords[0].ReferencedSOPSequence && + measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) || + 1; + + if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) + return false; + for (let j = 0; j < coords.length; j++) { const coord = coords[j]; const { ReferencedSOPInstanceUID } = coord.ReferencedSOPSequence; diff --git a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts index 9f1cedd39..546dc644d 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts @@ -45,6 +45,12 @@ export default function addMeasurement( const annotationManager = annotation.state.getDefaultAnnotationManager(); + // Create Cornerstone3D Annotation from measurement + const frameNumber = + (measurement.coords[0].ReferencedSOPSequence && + measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) || + 1; + const SRAnnotation: Types.Annotation = { annotationUID: measurement.TrackingUniqueIdentifier, metadata: { @@ -61,6 +67,7 @@ export default function addMeasurement( TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier, renderableData: measurementData.renderableData, }, + frameNumber: frameNumber, }, }; @@ -75,6 +82,7 @@ export default function addMeasurement( // It'd be super weird if it didn't anyway as a SCOORD. measurement.ReferencedSOPInstanceUID = measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID; + measurement.frameNumber = frameNumber; delete measurement.coords; } diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 9b6689f14..50889fe37 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -30,7 +30,7 @@ "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", "cornerstone-wado-image-loader": "^4.2.1", - "dcmjs": "^0.24.5", + "dcmjs": "^0.28.1", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx index c20cea0ba..751105c4c 100644 --- a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx @@ -3,13 +3,23 @@ import ReactResizeDetector from 'react-resize-detector'; import PropTypes from 'prop-types'; import { useViewportGrid } from '@ohif/ui'; import * as cs3DTools from '@cornerstonejs/tools'; -import { Enums, eventTarget, getEnabledElement } from '@cornerstonejs/core'; +import { + Enums, + eventTarget, + getEnabledElement, + StackViewport, +} from '@cornerstonejs/core'; import { setEnabledElement } from '../state'; import CornerstoneCacheService from '../services/ViewportService/CornerstoneCacheService'; import './OHIFCornerstoneViewport.css'; import CornerstoneOverlays from './Overlays/CornerstoneOverlays'; +import { + IStackViewport, + IVolumeViewport, +} from '@cornerstonejs/core/dist/esm/types'; +import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes'; const STACK = 'stack'; @@ -237,7 +247,7 @@ const OHIFCornerstoneViewport = React.memo(props => { displaySets, viewportOptions.viewportType, dataSource, - (viewportDataLoaded) => { + viewportDataLoaded => { CornerstoneViewportService.setViewportDisplaySets( viewportIndex, viewportDataLoaded, @@ -394,7 +404,7 @@ function _jumpToMeasurement( viewportGridService ) { const targetElement = targetElementRef.current; - const { displaySetInstanceUID, SOPInstanceUID } = measurement; + const { displaySetInstanceUID, SOPInstanceUID, frameNumber } = measurement; if (!SOPInstanceUID) { console.warn('cannot jump in a non-acquisition plane measurements yet'); @@ -404,17 +414,37 @@ function _jumpToMeasurement( displaySetInstanceUID ); - const imageIdIndex = referencedDisplaySet.images.findIndex( - i => i.SOPInstanceUID === SOPInstanceUID - ); - // Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager // to set it properly // setCornerstoneMeasurementActive(measurement); viewportGridService.setActiveViewportIndex(viewportIndex); - if (getEnabledElement(targetElement)) { + const enableElement = getEnabledElement(targetElement); + if (enableElement) { + // See how the jumpToSlice() of Cornerstone3D deals with imageIdx param. + const viewport = enableElement.viewport as IStackViewport | IVolumeViewport; + + let imageIdIndex = 0; + + if (viewport instanceof StackViewport) { + const imageIds = viewport.getImageIds(); + imageIdIndex = imageIds.findIndex(imageId => { + const { + SOPInstanceUID: aSOPInstanceUID, + frameNumber: aFrameNumber, + } = getSOPInstanceAttributes(imageId); + return ( + aSOPInstanceUID === SOPInstanceUID && + (!frameNumber || frameNumber === aFrameNumber) + ); + }); + } else { + imageIdIndex = referencedDisplaySet.images.findIndex( + i => i.SOPInstanceUID === SOPInstanceUID + ); + } + cs3DTools.utilities.jumpToSlice(targetElement, { imageIndex: imageIdIndex, }); diff --git a/extensions/cornerstone/src/initMeasurementService.js b/extensions/cornerstone/src/initMeasurementService.js index 9b6d4a414..f97b5007f 100644 --- a/extensions/cornerstone/src/initMeasurementService.js +++ b/extensions/cornerstone/src/initMeasurementService.js @@ -3,6 +3,7 @@ import { Enums, annotation } from '@cornerstonejs/tools'; import { DicomMetadataStore } from '@ohif/core'; import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory'; +import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes'; const { removeAnnotation } = annotation.state; @@ -252,7 +253,18 @@ const connectMeasurementServiceToTools = ( SOPInstanceUID ); - const imageId = dataSource.getImageIdsForInstance({ instance }); + let imageId; + let frameNumber = 1; + + if (measurement?.metadata?.referencedImageId) { + imageId = measurement.metadata.referencedImageId; + frameNumber = getSOPInstanceAttributes( + measurement.metadata.referencedImageId + ).frameNumber; + } else { + imageId = dataSource.getImageIdsForInstance({ instance }); + } + const annotationManager = annotation.state.getDefaultAnnotationManager(); annotationManager.addAnnotation({ annotationUID: measurement.uid, @@ -269,6 +281,7 @@ const connectMeasurementServiceToTools = ( handles: { ...data.annotation.data.handles }, cachedStats: { ...data.annotation.data.cachedStats }, label: data.annotation.data.label, + frameNumber: frameNumber, }, }); } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js index ded3ef076..6562b1ec7 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js @@ -69,6 +69,7 @@ const Length = { metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + frameNumber: mappedAnnotations[0]?.frameNumber || 1, toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, label: data.text, @@ -90,13 +91,16 @@ function getMappedAnnotations(annotation, DisplaySetService) { const annotations = []; - const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes( - referencedImageId - ); + const { + SOPInstanceUID, + SeriesInstanceUID, + frameNumber, + } = getSOPInstanceAttributes(referencedImageId); const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, - SeriesInstanceUID + SeriesInstanceUID, + frameNumber ); const { SeriesNumber } = displaySet; @@ -105,6 +109,7 @@ function getMappedAnnotations(annotation, DisplaySetService) { SeriesInstanceUID, SOPInstanceUID, SeriesNumber, + frameNumber, text, }); @@ -119,7 +124,7 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { SeriesNumber, SOPInstanceUID } = mappedAnnotations[0]; + const { SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; const instance = displaySet.images.find( image => image.SOPInstanceUID === SOPInstanceUID @@ -130,11 +135,10 @@ function getDisplayText(mappedAnnotations, displaySet) { InstanceNumber = instance.InstanceNumber; } - displayText.push( - InstanceNumber - ? `(S: ${SeriesNumber} I: ${InstanceNumber})` - : `(S: ${SeriesNumber})` - ); + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + + displayText.push(`(S: ${SeriesNumber}${instanceText}${frameText})`); return displayText; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js index 82a4d5a45..3a36b7576 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js @@ -67,6 +67,7 @@ const Bidirectional = { metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + frameNumber: mappedAnnotations[0]?.frameNumber || 1, toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, label: data.label, @@ -92,30 +93,33 @@ function getMappedAnnotations(annotation, DisplaySetService) { Object.keys(cachedStats).forEach(targetId => { const targetStats = cachedStats[targetId]; - let displaySet; - - if (referencedImageId) { - const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes( - referencedImageId - ); - - displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID - ); - } else { + if (!referencedImageId) { throw new Error( 'Non-acquisition plane measurement mapping not supported' ); } - const { SeriesNumber, SeriesInstanceUID } = displaySet; + const { + SOPInstanceUID, + SeriesInstanceUID, + frameNumber, + } = getSOPInstanceAttributes(referencedImageId); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID, + frameNumber + ); + + const { SeriesNumber } = displaySet; const { length, width } = targetStats; const unit = 'mm'; annotations.push({ SeriesInstanceUID, + SOPInstanceUID, SeriesNumber, + frameNumber, unit, length, width, @@ -171,7 +175,13 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { length, width, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0]; + const { + length, + width, + SeriesNumber, + SOPInstanceUID, + frameNumber, + } = mappedAnnotations[0]; const roundedLength = utils.roundNumber(length, 2); const roundedWidth = utils.roundNumber(width, 2); @@ -184,10 +194,11 @@ function getDisplayText(mappedAnnotations, displaySet) { InstanceNumber = instance.InstanceNumber; } + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + displayText.push( - InstanceNumber - ? `L: ${roundedLength} mm (S: ${SeriesNumber} I: ${InstanceNumber})` - : `L: ${roundedLength} mm (S: ${SeriesNumber})` + `L: ${roundedLength} mm (S: ${SeriesNumber}${instanceText}${frameText})` ); displayText.push(`W: ${roundedWidth} mm`); diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js index 149dcb644..5f811c0c2 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js @@ -66,6 +66,7 @@ const EllipticalROI = { metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + frameNumber: mappedAnnotations[0]?.frameNumber || 1, toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, label: data.label, @@ -91,31 +92,34 @@ function getMappedAnnotations(annotation, DisplaySetService) { Object.keys(cachedStats).forEach(targetId => { const targetStats = cachedStats[targetId]; - let displaySet; - - if (referencedImageId) { - const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes( - referencedImageId - ); - - displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID - ); - } else { + if (!referencedImageId) { // Todo: Non-acquisition plane measurement mapping not supported yet throw new Error( 'Non-acquisition plane measurement mapping not supported' ); } - const { SeriesNumber, SeriesInstanceUID } = displaySet; + const { + SOPInstanceUID, + SeriesInstanceUID, + frameNumber, + } = getSOPInstanceAttributes(referencedImageId); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID, + frameNumber + ); + + const { SeriesNumber } = displaySet; const { mean, stdDev, max, area, Modality } = targetStats; const unit = getModalityUnit(Modality); annotations.push({ SeriesInstanceUID, + SOPInstanceUID, SeriesNumber, + frameNumber, Modality, unit, mean, @@ -184,7 +188,7 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { area, SOPInstanceUID } = mappedAnnotations[0]; + const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; const instance = displaySet.images.find( image => image.SOPInstanceUID === SOPInstanceUID @@ -195,6 +199,9 @@ function getDisplayText(mappedAnnotations, displaySet) { InstanceNumber = instance.InstanceNumber; } + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + const roundedArea = utils.roundNumber(area, 2); displayText.push(`${roundedArea} mm2`); @@ -202,14 +209,15 @@ function getDisplayText(mappedAnnotations, displaySet) { mappedAnnotations.forEach(mappedAnnotation => { const { unit, max, SeriesNumber } = mappedAnnotation; + let maxStr = ''; if (max) { const roundedMax = utils.roundNumber(max, 2); + maxStr = `Max: ${roundedMax} ${unit} `; + } - displayText.push( - InstanceNumber - ? `Max: ${roundedMax} ${unit} (S:${SeriesNumber} I:${InstanceNumber})` - : `Max: ${roundedMax} ${unit} (S:${SeriesNumber})` - ); + const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`; + if (!displayText.includes(str)) { + displayText.push(str); } }); diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js index 0246c04b0..c3def8782 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js @@ -72,6 +72,7 @@ const Length = { metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + frameNumber: mappedAnnotations[0]?.frameNumber || 1, toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, label: data.label, @@ -97,30 +98,33 @@ function getMappedAnnotations(annotation, DisplaySetService) { Object.keys(cachedStats).forEach(targetId => { const targetStats = cachedStats[targetId]; - let displaySet; - - if (referencedImageId) { - const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes( - referencedImageId - ); - - displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID - ); - } else { + if (!referencedImageId) { throw new Error( 'Non-acquisition plane measurement mapping not supported' ); } - const { SeriesNumber, SeriesInstanceUID } = displaySet; + const { + SOPInstanceUID, + SeriesInstanceUID, + frameNumber, + } = getSOPInstanceAttributes(referencedImageId); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID, + frameNumber + ); + + const { SeriesNumber } = displaySet; const { length } = targetStats; const unit = 'mm'; annotations.push({ SeriesInstanceUID, + SOPInstanceUID, SeriesNumber, + frameNumber, unit, length, }); @@ -175,7 +179,12 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { length, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0]; + const { + length, + SeriesNumber, + SOPInstanceUID, + frameNumber, + } = mappedAnnotations[0]; const instance = displaySet.images.find( image => image.SOPInstanceUID === SOPInstanceUID @@ -186,11 +195,12 @@ function getDisplayText(mappedAnnotations, displaySet) { InstanceNumber = instance.InstanceNumber; } + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + const roundedLength = utils.roundNumber(length, 2); displayText.push( - InstanceNumber - ? `${roundedLength} mm (S: ${SeriesNumber} I: ${InstanceNumber})` - : `${roundedLength} mm (S: ${SeriesNumber})` + `${roundedLength} mm (S: ${SeriesNumber}${instanceText}${frameText})` ); return displayText; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js index 05c66aae2..7cefa4ceb 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js @@ -45,6 +45,7 @@ function _getUIDFromImageID(imageId) { SOPInstanceUID: instance.SOPInstanceUID, SeriesInstanceUID: instance.SeriesInstanceUID, StudyInstanceUID: instance.StudyInstanceUID, + frameNumber: instance.frameNumber || 1, }; } diff --git a/extensions/default/package.json b/extensions/default/package.json index 0c9b59a0c..4d8d5076d 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -32,7 +32,7 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/i18n": "^1.0.0", - "dcmjs": "^0.24.5", + "dcmjs": "^0.28.1", "dicomweb-client": "^0.6.0", "prop-types": "^15.6.2", "react": "^17.0.2", diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index 98e9a19a6..c3fcd3bb9 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -494,10 +494,10 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { const NumberOfFrames = instance.NumberOfFrames; if (NumberOfFrames > 1) { - for (let i = 0; i < NumberOfFrames; i++) { + for (let frame = 1; frame <= NumberOfFrames; frame++) { const imageId = this.getImageIdsForInstance({ instance, - frame: i, + frame, }); imageIds.push(imageId); } diff --git a/extensions/dicom-pdf/package.json b/extensions/dicom-pdf/package.json index 31327fd58..87a9071d7 100644 --- a/extensions/dicom-pdf/package.json +++ b/extensions/dicom-pdf/package.json @@ -30,7 +30,7 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", - "dcmjs": "^0.24.5", + "dcmjs": "^0.28.1", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-video/package.json b/extensions/dicom-video/package.json index c5a13b14a..09fddc733 100644 --- a/extensions/dicom-video/package.json +++ b/extensions/dicom-video/package.json @@ -30,7 +30,7 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", - "dcmjs": "^0.24.5", + "dcmjs": "^0.28.1", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index ea7911dbf..459a26490 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -35,7 +35,7 @@ "@cornerstonejs/core": "^0.16.1", "@cornerstonejs/tools": "^0.24.1", "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", - "dcmjs": "^0.24.5", + "dcmjs": "^0.28.1", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js index b7184449a..2e123b018 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js @@ -44,13 +44,17 @@ export default function _hydrateStructuredReport( ); const sopInstanceUIDToImageId = {}; - let imageIdsForToolState = []; + const imageIdsForToolState = {}; displaySet.measurements.forEach(measurement => { - const { ReferencedSOPInstanceUID, imageId } = measurement; - imageIdsForToolState.push(imageId); + const { ReferencedSOPInstanceUID, imageId, frameNumber } = measurement; + if (!sopInstanceUIDToImageId[ReferencedSOPInstanceUID]) { sopInstanceUIDToImageId[ReferencedSOPInstanceUID] = imageId; + imageIdsForToolState[ReferencedSOPInstanceUID] = []; + } + if (!imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber]) { + imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber] = imageId; } }); @@ -88,7 +92,14 @@ export default function _hydrateStructuredReport( toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState - const imageId = sopInstanceUIDToImageId[toolData.sopInstanceUid]; + // dcmjs and Cornerstone3D has structural defect in supporting multi-frame + // files, and looking up the imageId from sopInstanceUIDToImageId results + // in the wrong value. + const frameNumber = + (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; + const imageId = + imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || + sopInstanceUIDToImageId[toolData.sopInstanceUid]; if (!imageIds.includes(imageId)) { imageIds.push(imageId); @@ -125,7 +136,14 @@ export default function _hydrateStructuredReport( toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState - const imageId = sopInstanceUIDToImageId[toolData.sopInstanceUid]; + // dcmjs and Cornerstone3D has structural defect in supporting multi-frame + // files, and looking up the imageId from sopInstanceUIDToImageId results + // in the wrong value. + const frameNumber = + (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; + const imageId = + imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || + sopInstanceUIDToImageId[toolData.sopInstanceUid]; toolData.uid = guid(); diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx index 2fd99d6ab..d5f728856 100644 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx +++ b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx @@ -17,9 +17,9 @@ const { formatDate } = utils; const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = { key: undefined, // - date: undefined, // '07-Sep-2010', - modality: undefined, // 'CT', - description: undefined, // 'CHEST/ABD/PELVIS W CONTRAST', + date: '', // '07-Sep-2010', + modality: '', // 'CT', + description: '', // 'CHEST/ABD/PELVIS W CONTRAST', }; function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { diff --git a/extensions/tmtv/package.json b/extensions/tmtv/package.json index 85bf6c780..d2c5e4400 100644 --- a/extensions/tmtv/package.json +++ b/extensions/tmtv/package.json @@ -30,7 +30,7 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", - "dcmjs": "0.22.0", + "dcmjs": "0.28.0", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js b/extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js index 4f4124178..ccd594e72 100644 --- a/extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js +++ b/extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js @@ -13,5 +13,6 @@ function _getUIDFromImageID(imageId) { SOPInstanceUID: instance.SOPInstanceUID, SeriesInstanceUID: instance.SeriesInstanceUID, StudyInstanceUID: instance.StudyInstanceUID, + frameNumber: instance.frameNumber || 1, }; } diff --git a/platform/core/package.json b/platform/core/package.json index 0e912e661..21b4a1de7 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@babel/runtime": "7.16.3", - "dcmjs": "^0.24.5", + "dcmjs": "^0.28.1", "dicomweb-client": "^0.6.0", "isomorphic-base64": "^1.0.2", "lodash.merge": "^4.6.1", diff --git a/platform/core/src/classes/MetadataProvider.js b/platform/core/src/classes/MetadataProvider.js index d665d31b9..cc4b75c57 100644 --- a/platform/core/src/classes/MetadataProvider.js +++ b/platform/core/src/classes/MetadataProvider.js @@ -52,7 +52,7 @@ class MetadataProvider { } _getInstance(imageId) { - const uids = this._getUIDsFromImageID(imageId); + const uids = this.getUIDsFromImageID(imageId); if (!uids) { return; @@ -191,14 +191,16 @@ class MetadataProvider { rows: toNumber(instance.Rows), columns: toNumber(instance.Columns), imageOrientationPatient: toNumber(ImageOrientationPatient), - rowCosines: toNumber(rowCosines), - columnCosines: toNumber(columnCosines), - imagePositionPatient: toNumber(instance.ImagePositionPatient), + rowCosines: toNumber(rowCosines || [0, 1, 0]), + columnCosines: toNumber(columnCosines || [0, 0, -1]), + imagePositionPatient: toNumber( + instance.ImagePositionPatient || [0, 0, 0] + ), sliceThickness: toNumber(instance.SliceThickness), sliceLocation: toNumber(instance.SliceLocation), - pixelSpacing: toNumber(PixelSpacing), - rowPixelSpacing: toNumber(rowPixelSpacing), - columnPixelSpacing: toNumber(columnPixelSpacing), + pixelSpacing: toNumber(PixelSpacing || 1), + rowPixelSpacing: toNumber(rowPixelSpacing || 1), + columnPixelSpacing: toNumber(columnPixelSpacing || 1), }; break; case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE: @@ -409,7 +411,7 @@ class MetadataProvider { return metadata; } - _getUIDsFromImageID(imageId) { + getUIDsFromImageID(imageId) { // TODO: adding csiv here is not really correct. Probably need to use // metadataProvider.addImageIdToUIDs(imageId, { // StudyInstanceUID, diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.js b/platform/core/src/services/DisplaySetService/DisplaySetService.js index 7a5ec8158..2f4357bd9 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetService.js +++ b/platform/core/src/services/DisplaySetService/DisplaySetService.js @@ -75,7 +75,11 @@ export default class DisplaySetService { ); }; - getDisplaySetForSOPInstanceUID(SOPInstanceUID, SeriesInstanceUID) { + getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID, + frameNumber + ) { const displaySets = SeriesInstanceUID ? this.getDisplaySetsForSeries(SeriesInstanceUID) : this.getDisplaySetCache(); diff --git a/platform/core/src/services/MeasurementService/MeasurementService.js b/platform/core/src/services/MeasurementService/MeasurementService.js index cfb0a85fb..789218acf 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.js @@ -38,6 +38,7 @@ const MEASUREMENT_SCHEMA_KEYS = [ 'FrameOfReferenceUID', 'referenceStudyUID', 'referenceSeriesUID', + 'frameNumber', 'displaySetInstanceUID', 'label', 'description', diff --git a/platform/core/src/utils/combineFrameInstance.ts b/platform/core/src/utils/combineFrameInstance.ts index 377503b53..b5678a2d6 100644 --- a/platform/core/src/utils/combineFrameInstance.ts +++ b/platform/core/src/utils/combineFrameInstance.ts @@ -12,22 +12,33 @@ const combineFrameInstance = (frame, instance) => { const { PerFrameFunctionalGroupsSequence, SharedFunctionalGroupsSequence, + NumberOfFrames, } = instance; - if (!PerFrameFunctionalGroupsSequence) return instance; - const shared = Object.values(SharedFunctionalGroupsSequence[0]) - .map(it => it[0]) - .filter(it => it !== undefined && typeof it === 'object'); - const perFrame = Object.values( - PerFrameFunctionalGroupsSequence[(frame || 1) - 1] - ) - .map(it => it[0]) - .filter(it => it !== undefined && typeof it === 'object'); - return Object.assign( - {}, - instance, - ...Object.values(shared), - ...Object.values(perFrame) - ); + + if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) { + const frameNumber = Number.parseInt(frame || 1); + const shared = (SharedFunctionalGroupsSequence + ? Object.values(SharedFunctionalGroupsSequence[0]) + : [] + ) + .map(it => it[0]) + .filter(it => it !== undefined && typeof it === 'object'); + const perFrame = (PerFrameFunctionalGroupsSequence + ? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1]) + : [] + ) + .map(it => it[0]) + .filter(it => it !== undefined && typeof it === 'object'); + + return Object.assign( + { frameNumber: frameNumber }, + instance, + ...Object.values(shared), + ...Object.values(perFrame) + ); + } else { + return instance; + } }; export default combineFrameInstance; diff --git a/platform/ui/src/components/Button/Button.tsx b/platform/ui/src/components/Button/Button.tsx index b5649357a..7a2de8f91 100644 --- a/platform/ui/src/components/Button/Button.tsx +++ b/platform/ui/src/components/Button/Button.tsx @@ -235,6 +235,7 @@ Button.propTypes = { ]), border: PropTypes.oneOf([ 'none', + 'light', 'default', 'primary', 'secondary', diff --git a/platform/ui/src/components/MeasurementTable/MeasurementItem.tsx b/platform/ui/src/components/MeasurementTable/MeasurementItem.tsx index 045235cb1..46bf2add9 100644 --- a/platform/ui/src/components/MeasurementTable/MeasurementItem.tsx +++ b/platform/ui/src/components/MeasurementTable/MeasurementItem.tsx @@ -52,9 +52,9 @@ const MeasurementItem = ({
{label} - {displayText.map(line => ( + {displayText.map((line, i) => ( diff --git a/platform/ui/src/components/StudySummary/StudySummary.tsx b/platform/ui/src/components/StudySummary/StudySummary.tsx index 625a1803d..68834928f 100644 --- a/platform/ui/src/components/StudySummary/StudySummary.tsx +++ b/platform/ui/src/components/StudySummary/StudySummary.tsx @@ -11,7 +11,7 @@ const StudySummary = ({ date, modality, description }) => {
- {description} + {description || ''}
); @@ -20,7 +20,7 @@ const StudySummary = ({ date, modality, description }) => { StudySummary.propTypes = { date: PropTypes.string.isRequired, modality: PropTypes.string.isRequired, - description: PropTypes.string.isRequired, + description: PropTypes.string, }; export default StudySummary; diff --git a/platform/viewer/package.json b/platform/viewer/package.json index 0e748c43c..682a145a9 100644 --- a/platform/viewer/package.json +++ b/platform/viewer/package.json @@ -63,7 +63,7 @@ "core-js": "^3.16.1", "cornerstone-math": "^0.1.9", "cornerstone-wado-image-loader": "^4.2.1", - "dcmjs": "^0.24.5", + "dcmjs": "^0.28.1", "detect-gpu": "^4.0.16", "dicom-parser": "^1.8.9", "dotenv-webpack": "^1.7.0", diff --git a/yarn.lock b/yarn.lock index 12f86c5f9..52c0222bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10033,16 +10033,17 @@ dayjs@^1.10.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5" integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw== -dcmjs@^0.24.5: - version "0.24.6" - resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.24.6.tgz#436d00361fb8d4286e68e2b0cab15939fa3acafb" - integrity sha512-ts/DigszrYXMOmYLRVlik4Z6Oq0fb4ykrveFkcIPclTk/uoND0uwbhO4IPMNnuhGGwaVFF6uXQISY+kgbpjn2g== +dcmjs@^0.28.1: + version "0.28.1" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.28.1.tgz#e9b20dddef41fbdbf87c96d0e31531dd6ac93087" + integrity sha512-PzlxfdZazOkv9OCFYtp9jWzMt2GnfSvBOQ2SCqnFlDbzZWAiT6kvrfVHx5ux65Lct0P0IGLGoqRdQChtsjzL5A== dependencies: "@babel/runtime-corejs2" "^7.17.8" gl-matrix "^3.1.0" lodash.clonedeep "^4.5.0" loglevelnext "^3.0.1" ndarray "^1.0.19" + pako "^2.0.4" debug-log@^1.0.0: version "1.0.1"