From a4f286feee3e8cd34b3fb709cb764e0dc2d14016 Mon Sep 17 00:00:00 2001 From: James Petts Date: Wed, 22 Jul 2020 18:40:40 +0100 Subject: [PATCH] [OHIF 284] - JumpToMeasurement + prevent layout change on scroll Prevent layout change on scroll (#1889) * No more setting on debounce, jumpToMeasurement service set up. * Fix bug causing a race condition sometimes if you click super fast. * Optimise rendering of Tracking viewport slightly. * element => targetElement * Remove complete TODO * Respond to reviewer comments. * Fix e2e workflow. * Fix regression that removed highlighting on jump to. * Make sure jump to works from panel as well as viewport. * Fix typo and remove debugger. Co-authored-by: Danny Brown --- extensions/cornerstone/package.json | 2 +- extensions/cornerstone/src/commandsModule.js | 28 --- extensions/cornerstone/src/init.js | 50 ++--- .../ArrowAnnotate.js | 12 +- .../Bidirectional.js | 12 +- .../EllipticalRoi.js | 12 +- .../measurementServiceMappings/Length.js | 12 +- .../measurementServiceMappingsFactory.js | 26 ++- extensions/default/package.json | 2 +- extensions/dicom-html/package.json | 2 +- extensions/dicom-rt/package.json | 2 +- extensions/dicom-segmentation/package.json | 2 +- extensions/dicom-sr/package.json | 2 +- .../dicom-sr/src/OHIFCornerstoneSRViewport.js | 5 +- ...ToolStateToCornerstoneMeasurementSchema.js | 100 ++++++++-- extensions/measurement-tracking/package.json | 2 +- .../PanelMeasurementTableTracking/index.js | 24 +-- .../viewports/TrackedCornerstoneViewport.js | 177 +++++++++++++----- extensions/vtk/package.json | 2 +- platform/core/package.json | 2 +- .../DisplaySetService/DisplaySetService.js | 14 ++ .../MeasurementService/MeasurementService.js | 36 ++++ .../contextProviders/ViewportGridProvider.jsx | 13 +- platform/viewer/package.json | 2 +- .../viewer/src/components/ViewportGrid.jsx | 87 ++++++--- yarn.lock | 8 +- 26 files changed, 439 insertions(+), 197 deletions(-) diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 9a19c0562a7..8c98a3233cd 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -35,7 +35,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "4.18.1", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/cornerstone/src/commandsModule.js b/extensions/cornerstone/src/commandsModule.js index 7dfecd9120e..00d4098cdaa 100644 --- a/extensions/cornerstone/src/commandsModule.js +++ b/extensions/cornerstone/src/commandsModule.js @@ -83,8 +83,6 @@ const commandsModule = ({ servicesManager }) => { const enabledElement = _getActiveViewportsEnabledElement(); if (enabledElement) { - debugger; - const cancelActiveManipulatorsForElement = cornerstoneTools.getModule( 'manipulatorState' ).setters.cancelActiveManipulatorsForElement; @@ -284,32 +282,6 @@ const commandsModule = ({ servicesManager }) => { cornerstone.setViewport(enabledElement, viewport); } }, - jumpToImage: ({ - StudyInstanceUID, - SOPInstanceUID, - imageIndex, - activeViewportIndex, - }) => { - const study = studyMetadataManager.get(StudyInstanceUID); - - const displaySet = study.findDisplaySet(ds => { - return ( - ds.images && - ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID) - ); - }); - - displaySet.SOPInstanceUID = SOPInstanceUID; - displaySet.imageIndex = imageIndex; - - window.store.dispatch( - setViewportSpecificData(activeViewportIndex, displaySet) - ); - - cornerstone.getEnabledElements().forEach(enabledElement => { - cornerstone.updateImage(enabledElement.element); - }); - }, }; const definitions = { diff --git a/extensions/cornerstone/src/init.js b/extensions/cornerstone/src/init.js index 057d1fe6be7..be6a528df84 100644 --- a/extensions/cornerstone/src/init.js +++ b/extensions/cornerstone/src/init.js @@ -18,7 +18,11 @@ import { setEnabledElement } from './state'; * @param {Object|Array} configuration.csToolsConfig */ export default function init({ servicesManager, configuration }) { - const { UIDialogService, MeasurementService } = servicesManager.services; + const { + UIDialogService, + MeasurementService, + DisplaySetService, + } = servicesManager.services; const callInputDialog = (data, event, callback) => { if (UIDialogService) { @@ -137,7 +141,7 @@ export default function init({ servicesManager, configuration }) { ); /* Measurement Service */ - _connectToolsToMeasurementService(MeasurementService); + _connectToolsToMeasurementService(MeasurementService, DisplaySetService); /* Add extension tools configuration here. */ const internalToolsConfig = { @@ -220,21 +224,21 @@ export default function init({ servicesManager, configuration }) { csTools.setToolEnabled('Overlay', {}); } -const _initMeasurementService = measurementService => { +const _initMeasurementService = (MeasurementService, DisplaySetService) => { /* Initialization */ const { Length, Bidirectional, EllipticalRoi, ArrowAnnotate, - } = measurementServiceMappingsFactory(measurementService); - const csToolsVer4MeasurementSource = measurementService.createSource( + } = measurementServiceMappingsFactory(MeasurementService, DisplaySetService); + const csToolsVer4MeasurementSource = MeasurementService.createSource( 'CornerstoneTools', '4' ); /* Mappings */ - measurementService.addMapping( + MeasurementService.addMapping( csToolsVer4MeasurementSource, 'Length', Length.matchingCriteria, @@ -242,7 +246,7 @@ const _initMeasurementService = measurementService => { Length.toMeasurement ); - measurementService.addMapping( + MeasurementService.addMapping( csToolsVer4MeasurementSource, 'Bidirectional', Bidirectional.matchingCriteria, @@ -250,7 +254,7 @@ const _initMeasurementService = measurementService => { Bidirectional.toMeasurement ); - measurementService.addMapping( + MeasurementService.addMapping( csToolsVer4MeasurementSource, 'EllipticalRoi', EllipticalRoi.matchingCriteria, @@ -258,7 +262,7 @@ const _initMeasurementService = measurementService => { EllipticalRoi.toMeasurement ); - measurementService.addMapping( + MeasurementService.addMapping( csToolsVer4MeasurementSource, 'ArrowAnnotate', ArrowAnnotate.matchingCriteria, @@ -269,12 +273,16 @@ const _initMeasurementService = measurementService => { return csToolsVer4MeasurementSource; }; -const _connectToolsToMeasurementService = measurementService => { +const _connectToolsToMeasurementService = ( + MeasurementService, + DisplaySetService +) => { const csToolsVer4MeasurementSource = _initMeasurementService( - measurementService + MeasurementService, + DisplaySetService ); _connectMeasurementServiceToTools( - measurementService, + MeasurementService, csToolsVer4MeasurementSource ); const { addOrUpdate, remove } = csToolsVer4MeasurementSource; @@ -330,9 +338,9 @@ const _connectToolsToMeasurementService = measurementService => { } } - const { MEASUREMENTS_CLEARED } = measurementService.EVENTS; + const { MEASUREMENTS_CLEARED } = MeasurementService.EVENTS; - measurementService.subscribe(MEASUREMENTS_CLEARED, () => { + MeasurementService.subscribe(MEASUREMENTS_CLEARED, () => { cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState( {} ); @@ -350,16 +358,16 @@ const _connectToolsToMeasurementService = measurementService => { }; const _connectMeasurementServiceToTools = ( - measurementService, + MeasurementService, measurementSource ) => { const { MEASUREMENTS_CLEARED, MEASUREMENT_REMOVED, - } = measurementService.EVENTS; + } = MeasurementService.EVENTS; const sourceId = measurementSource.id; - measurementService.subscribe(MEASUREMENTS_CLEARED, () => { + MeasurementService.subscribe(MEASUREMENTS_CLEARED, () => { cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState({}); cornerstone.getEnabledElements().forEach(enabledElement => { cornerstone.updateImage(enabledElement.element); @@ -367,7 +375,7 @@ const _connectMeasurementServiceToTools = ( }); /* TODO: Remove per measurement - measurementService.subscribe(MEASUREMENT_REMOVED, + MeasurementService.subscribe(MEASUREMENT_REMOVED, ({ source, measurement }) => { if ([sourceId].includes(source.id)) { // const annotation = getAnnotation('Length', measurement.id); @@ -380,9 +388,9 @@ const _connectMeasurementServiceToTools = ( // const { // MEASUREMENT_ADDED, // MEASUREMENT_UPDATED, -// } = measurementService.EVENTS; +// } = MeasurementService.EVENTS; -// measurementService.subscribe( +// MeasurementService.subscribe( // MEASUREMENT_ADDED, // ({ source, measurement }) => { // if (![sourceId].includes(source.id)) { @@ -397,7 +405,7 @@ const _connectMeasurementServiceToTools = ( // } // ); -// measurementService.subscribe( +// MeasurementService.subscribe( // MEASUREMENT_UPDATED, // ({ source, measurement }) => { // if (![sourceId].includes(source.id)) { diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js index f36c64ad869..8a081e2e4b1 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js @@ -6,7 +6,11 @@ const ArrowAnnotate = { toAnnotation: (measurement, definition) => { // TODO -> Implement when this is needed. }, - toMeasurement: (csToolsAnnotation, getValueTypeFromToolType) => { + toMeasurement: ( + csToolsAnnotation, + DisplaySetService, + getValueTypeFromToolType + ) => { const { element, measurementData } = csToolsAnnotation; const tool = csToolsAnnotation.toolType || @@ -26,6 +30,11 @@ const ArrowAnnotate = { StudyInstanceUID, } = getSOPInstanceAttributes(element); + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + const points = []; points.push(measurementData.handles); @@ -35,6 +44,7 @@ const ArrowAnnotate = { FrameOfReferenceUID, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + displaySetInstanceUID: displaySet.displaySetInstanceUID, label: measurementData.text, description: measurementData.description, unit: measurementData.unit, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js index 25ca59b12c3..e31cc7849de 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js @@ -5,7 +5,11 @@ const Bidirectional = { toAnnotation: (measurement, definition) => { // TODO -> Implement when this is needed. }, - toMeasurement: (csToolsAnnotation, getValueTypeFromToolType) => { + toMeasurement: ( + csToolsAnnotation, + DisplaySetService, + getValueTypeFromToolType + ) => { const { element, measurementData } = csToolsAnnotation; const tool = csToolsAnnotation.toolType || @@ -25,6 +29,11 @@ const Bidirectional = { StudyInstanceUID, } = getSOPInstanceAttributes(element); + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + const { handles } = measurementData; const longAxis = [handles.start, handles.end]; @@ -36,6 +45,7 @@ const Bidirectional = { FrameOfReferenceUID, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + displaySetInstanceUID: displaySet.displaySetInstanceUID, label: measurementData.text, description: measurementData.description, unit: measurementData.unit, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalRoi.js b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalRoi.js index b2a24187ef8..19c2803b59e 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalRoi.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalRoi.js @@ -5,7 +5,11 @@ const EllipticalRoi = { toAnnotation: (measurement, definition) => { // TODO -> Implement when this is needed. }, - toMeasurement: (csToolsAnnotation, getValueTypeFromToolType) => { + toMeasurement: ( + csToolsAnnotation, + DisplaySetService, + getValueTypeFromToolType + ) => { const { element, measurementData } = csToolsAnnotation; const tool = csToolsAnnotation.toolType || @@ -25,6 +29,11 @@ const EllipticalRoi = { StudyInstanceUID, } = getSOPInstanceAttributes(element); + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + const { start, end } = measurementData.handles; const halfXLength = Math.abs(start.x - end.x) / 2; @@ -58,6 +67,7 @@ const EllipticalRoi = { FrameOfReferenceUID, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + displaySetInstanceUID: displaySet.displaySetInstanceUID, label: measurementData.text, description: measurementData.description, unit: measurementData.unit, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js index a5b7c1e9d55..777e98a0afa 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js @@ -37,7 +37,11 @@ const Length = { * @param {Object} cornerstone Cornerstone event data * @return {Measurement} Measurement instance */ - toMeasurement: (csToolsAnnotation, getValueTypeFromToolType) => { + toMeasurement: ( + csToolsAnnotation, + DisplaySetService, + getValueTypeFromToolType + ) => { const { element, measurementData } = csToolsAnnotation; const tool = csToolsAnnotation.toolType || @@ -57,12 +61,18 @@ const Length = { StudyInstanceUID, } = getSOPInstanceAttributes(element); + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + return { id: measurementData.id, SOPInstanceUID: SOPInstanceUID, FrameOfReferenceUID, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + displaySetInstanceUID: displaySet.displaySetInstanceUID, label: measurementData.text, description: measurementData.description, unit: measurementData.unit, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js index 6e325fb19ed..069267907fa 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js @@ -3,7 +3,10 @@ import Bidirectional from './Bidirectional'; import ArrowAnnotate from './ArrowAnnotate'; import EllipticalRoi from './EllipticalRoi'; -const measurementServiceMappingsFactory = measurementService => { +const measurementServiceMappingsFactory = ( + MeasurementService, + DisplaySetService +) => { /** * Maps measurement service format object to cornerstone annotation object. * @@ -18,7 +21,7 @@ const measurementServiceMappingsFactory = measurementService => { ELLIPSE, POINT, BIDIRECTIONAL, - } = measurementService.VALUE_TYPES; + } = MeasurementService.VALUE_TYPES; // TODO -> I get why this was attemped, but its not nearly flexible enough. // A single measurement may have an ellipse + a bidirectional measurement, for instances. @@ -37,10 +40,14 @@ const measurementServiceMappingsFactory = measurementService => { Length: { toAnnotation: Length.toAnnotation, toMeasurement: csToolsAnnotation => - Length.toMeasurement(csToolsAnnotation, _getValueTypeFromToolType), + Length.toMeasurement( + csToolsAnnotation, + DisplaySetService, + _getValueTypeFromToolType + ), matchingCriteria: [ { - valueType: measurementService.VALUE_TYPES.POLYLINE, + valueType: MeasurementService.VALUE_TYPES.POLYLINE, points: 2, }, ], @@ -50,17 +57,18 @@ const measurementServiceMappingsFactory = measurementService => { toMeasurement: csToolsAnnotation => Bidirectional.toMeasurement( csToolsAnnotation, + DisplaySetService, _getValueTypeFromToolType ), matchingCriteria: [ // TODO -> We should eventually do something like shortAxis + longAxis, // But its still a little unclear how these automatic interpretations will work. { - valueType: measurementService.VALUE_TYPES.POLYLINE, + valueType: MeasurementService.VALUE_TYPES.POLYLINE, points: 2, }, { - valueType: measurementService.VALUE_TYPES.POLYLINE, + valueType: MeasurementService.VALUE_TYPES.POLYLINE, points: 2, }, ], @@ -70,11 +78,12 @@ const measurementServiceMappingsFactory = measurementService => { toMeasurement: csToolsAnnotation => ArrowAnnotate.toMeasurement( csToolsAnnotation, + DisplaySetService, _getValueTypeFromToolType ), matchingCriteria: [ { - valueType: measurementService.VALUE_TYPES.POINT, + valueType: MeasurementService.VALUE_TYPES.POINT, points: 1, }, ], @@ -84,11 +93,12 @@ const measurementServiceMappingsFactory = measurementService => { toMeasurement: csToolsAnnotation => EllipticalRoi.toMeasurement( csToolsAnnotation, + DisplaySetService, _getValueTypeFromToolType ), matchingCriteria: [ { - valueType: measurementService.VALUE_TYPES.ELLIPSE, + valueType: MeasurementService.VALUE_TYPES.ELLIPSE, }, ], }, diff --git a/extensions/default/package.json b/extensions/default/package.json index 4663a943c00..01dd403c687 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -34,7 +34,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "webpack": "^4.0.0", - "dcmjs": "0.16.0" + "dcmjs": "0.16.1" }, "dependencies": { "@babel/runtime": "7.7.6" diff --git a/extensions/dicom-html/package.json b/extensions/dicom-html/package.json index 0ce0817f90d..d7731f5793f 100644 --- a/extensions/dicom-html/package.json +++ b/extensions/dicom-html/package.json @@ -28,7 +28,7 @@ }, "peerDependencies": { "@ohif/core": "^0.50.0", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "prop-types": "^15.6.2", "react": "^16.11.0", "react-dom": "^16.11.0" diff --git a/extensions/dicom-rt/package.json b/extensions/dicom-rt/package.json index 1a100d03db4..fb3de31b0fb 100644 --- a/extensions/dicom-rt/package.json +++ b/extensions/dicom-rt/package.json @@ -31,7 +31,7 @@ "@ohif/core": "^0.50.0", "cornerstone-core": "^2.2.8", "cornerstone-tools": "4.18.1", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-segmentation/package.json b/extensions/dicom-segmentation/package.json index 4f86096065c..c61c52bf2cb 100644 --- a/extensions/dicom-segmentation/package.json +++ b/extensions/dicom-segmentation/package.json @@ -31,7 +31,7 @@ "@ohif/core": "^0.50.0", "cornerstone-core": "^2.2.8", "cornerstone-tools": "4.18.1", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6" diff --git a/extensions/dicom-sr/package.json b/extensions/dicom-sr/package.json index a84facd9fa1..1b19f8b4821 100644 --- a/extensions/dicom-sr/package.json +++ b/extensions/dicom-sr/package.json @@ -35,7 +35,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "4.18.1", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js b/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js index 049b579f0ad..7663549c011 100644 --- a/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js +++ b/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js @@ -372,6 +372,7 @@ function OHIFCornerstoneSRViewport({ const toMeasurementSchema = getToolStateToCornerstoneMeasurementSchema( toolType, MeasurementService, + DisplaySetService, imageId ); @@ -436,8 +437,8 @@ function OHIFCornerstoneSRViewport({ spacing: PixelSpacing && PixelSpacing.length ? `${PixelSpacing[0].toFixed(2)}mm x ${PixelSpacing[1].toFixed( - 2 - )}mm` + 2 + )}mm` : '', scanner: ManufacturerModelName || '', }, diff --git a/extensions/dicom-sr/src/utils/getToolStateToCornerstoneMeasurementSchema.js b/extensions/dicom-sr/src/utils/getToolStateToCornerstoneMeasurementSchema.js index 21f2c2dc1d1..da8f12030f1 100644 --- a/extensions/dicom-sr/src/utils/getToolStateToCornerstoneMeasurementSchema.js +++ b/extensions/dicom-sr/src/utils/getToolStateToCornerstoneMeasurementSchema.js @@ -1,6 +1,7 @@ export default function getToolStateToCornerstoneMeasurementSchema( toolType, MeasurementService, + DisplaySetService, imageId ) { const _getValueTypeFromToolType = toolType => { @@ -28,20 +29,45 @@ export default function getToolStateToCornerstoneMeasurementSchema( switch (toolType) { case 'Length': return measurementData => - Length(measurementData, imageId, _getValueTypeFromToolType); + Length( + measurementData, + imageId, + DisplaySetService, + _getValueTypeFromToolType + ); case 'Bidirectional': return measurementData => - Bidirectional(measurementData, imageId, _getValueTypeFromToolType); + Bidirectional( + measurementData, + imageId, + DisplaySetService, + _getValueTypeFromToolType + ); case 'EllipticalRoi': return measurementData => - EllipticalRoi(measurementData, imageId, _getValueTypeFromToolType); + EllipticalRoi( + measurementData, + imageId, + DisplaySetService, + _getValueTypeFromToolType + ); case 'ArrowAnnotate': return measurementData => - ArrowAnnotate(measurementData, imageId, _getValueTypeFromToolType); + ArrowAnnotate( + measurementData, + imageId, + DisplaySetService, + _getValueTypeFromToolType + ); } } -function Length(measurementData, imageId, _getValueTypeFromToolType) { +function Length( + measurementData, + imageId, + DisplaySetService, + _getValueTypeFromToolType +) { const tool = measurementData.toolType || measurementData.toolName; const instance = cornerstone.metaData.get('instance', imageId); const { @@ -51,6 +77,12 @@ function Length(measurementData, imageId, _getValueTypeFromToolType) { StudyInstanceUID, } = instance; + const displaySetInstanceUID = _getDisplaySetInstanceUID( + DisplaySetService, + SeriesInstanceUID, + SOPInstanceUID + ); + const { handles, label } = measurementData; const points = []; @@ -69,7 +101,7 @@ function Length(measurementData, imageId, _getValueTypeFromToolType) { FrameOfReferenceUID, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, - label: measurementData.text, + displaySetInstanceUID, description: measurementData.description, unit: measurementData.unit, length: measurementData.length, @@ -79,7 +111,12 @@ function Length(measurementData, imageId, _getValueTypeFromToolType) { }; } -function Bidirectional(measurementData, imageId, _getValueTypeFromToolType) { +function Bidirectional( + measurementData, + imageId, + DisplaySetService, + _getValueTypeFromToolType +) { const tool = measurementData.toolType || measurementData.toolName; const instance = cornerstone.metaData.get('instance', imageId); const { @@ -88,6 +125,11 @@ function Bidirectional(measurementData, imageId, _getValueTypeFromToolType) { SeriesInstanceUID, StudyInstanceUID, } = instance; + const displaySetInstanceUID = _getDisplaySetInstanceUID( + DisplaySetService, + SeriesInstanceUID, + SOPInstanceUID + ); const { handles, label } = measurementData; @@ -100,7 +142,7 @@ function Bidirectional(measurementData, imageId, _getValueTypeFromToolType) { FrameOfReferenceUID, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, - label: measurementData.text, + displaySetInstanceUID, description: measurementData.description, unit: measurementData.unit, shortestDiameter: measurementData.shortestDiameter, @@ -111,7 +153,12 @@ function Bidirectional(measurementData, imageId, _getValueTypeFromToolType) { }; } -function EllipticalRoi(measurementData, imageId, _getValueTypeFromToolType) { +function EllipticalRoi( + measurementData, + imageId, + DisplaySetService, + _getValueTypeFromToolType +) { const tool = measurementData.toolType || measurementData.toolName; const instance = cornerstone.metaData.get('instance', imageId); const { @@ -121,6 +168,11 @@ function EllipticalRoi(measurementData, imageId, _getValueTypeFromToolType) { StudyInstanceUID, } = instance; + const displaySetInstanceUID = _getDisplaySetInstanceUID( + DisplaySetService, + SeriesInstanceUID, + SOPInstanceUID + ); const { handles, label } = measurementData; const { start, end } = handles; @@ -155,7 +207,7 @@ function EllipticalRoi(measurementData, imageId, _getValueTypeFromToolType) { FrameOfReferenceUID, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, - label: measurementData.text, + displaySetInstanceUID, description: measurementData.description, unit: measurementData.unit, area: @@ -168,7 +220,12 @@ function EllipticalRoi(measurementData, imageId, _getValueTypeFromToolType) { }; } -function ArrowAnnotate(measurementData, imageId, _getValueTypeFromToolType) { +function ArrowAnnotate( + measurementData, + imageId, + DisplaySetService, + _getValueTypeFromToolType +) { const tool = measurementData.toolType || measurementData.toolName; const instance = cornerstone.metaData.get('instance', imageId); const { @@ -178,6 +235,12 @@ function ArrowAnnotate(measurementData, imageId, _getValueTypeFromToolType) { StudyInstanceUID, } = instance; + const displaySetInstanceUID = _getDisplaySetInstanceUID( + DisplaySetService, + SeriesInstanceUID, + SOPInstanceUID + ); + const { handles } = measurementData; const points = []; @@ -196,12 +259,25 @@ function ArrowAnnotate(measurementData, imageId, _getValueTypeFromToolType) { FrameOfReferenceUID, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + displaySetInstanceUID, label: measurementData.text, description: measurementData.description, unit: measurementData.unit, text: measurementData.text, type: _getValueTypeFromToolType(tool), points, - label, }; } + +function _getDisplaySetInstanceUID( + DisplaySetService, + SeriesInstanceUID, + SOPInstanceUID +) { + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + + return displaySet.displaySetInstanceUID; +} diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index 9400de9f898..7aa6c3fddff 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -29,7 +29,7 @@ "peerDependencies": { "@ohif/core": "^0.50.0", "cornerstone-tools": "4.18.1", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "prop-types": "^15.6.2", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js index c1f72b232f0..af89f39c7f3 100644 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js +++ b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js @@ -161,29 +161,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { } const jumpToImage = ({ id, isActive }) => { - const measurement = MeasurementService.getMeasurement(id); - const { referenceSeriesUID, SOPInstanceUID } = measurement; - - setCornerstoneMeasurementActive(measurement); - - const displaySets = DisplaySetService.getDisplaySetsForSeries( - referenceSeriesUID - ); - const displaySet = displaySets.find(ds => { - return ( - ds.images && ds.images.some(i => i.SOPInstanceUID === SOPInstanceUID) - ); - }); - - const imageIndex = displaySet.images - .map(i => i.SOPInstanceUID) - .indexOf(SOPInstanceUID); - - viewportGridService.setDisplaysetForViewport({ - viewportIndex: viewportGrid.activeViewportIndex, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - imageIndex, - }); + MeasurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, id); onMeasurementItemClickHandler({ id, isActive }); }; diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js index 6a226992ab2..8c3ca308cc2 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js @@ -16,6 +16,7 @@ import ViewportOverlay from './ViewportOverlay'; import ViewportLoadingIndicator from './ViewportLoadingIndicator'; import setCornerstoneMeasurementActive from '../_shared/setCornerstoneMeasurementActive'; +const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex'); const { formatDate } = utils; // TODO -> Get this list from the list of tracked measurements. @@ -47,12 +48,13 @@ function TrackedCornerstoneViewport({ viewportIndex, servicesManager, }) { - const { ToolBarService, DisplaySetService } = servicesManager.services; + const { + ToolBarService, + DisplaySetService, + MeasurementService, + } = servicesManager.services; const [trackedMeasurements] = useTrackedMeasurements(); - const [ - { activeViewportIndex, viewports }, - viewportGridService, - ] = useViewportGrid(); + const [{ activeViewportIndex, viewports }] = useViewportGrid(); // viewportIndex, onSubmit const [viewportDialogState, viewportDialogApi] = useViewportDialog(); const [viewportData, setViewportData] = useState(null); @@ -69,6 +71,28 @@ function TrackedCornerstoneViewport({ }; }, []); + useEffect(() => { + const unsubcribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents( + MeasurementService, + DisplaySetService, + element, + viewportIndex, + displaySet.displaySetInstanceUID + ); + + _checkForCachedJumpToMeasurementEvents( + MeasurementService, + DisplaySetService, + element, + viewportIndex, + displaySet.displaySetInstanceUID + ); + + return () => { + unsubcribeFromJumpToMeasurementEvents(); + }; + }, [element, displaySet]); + useEffect(() => { if (!element) { return; @@ -167,13 +191,6 @@ function TrackedCornerstoneViewport({ ); } - /* - * This grabs `imageIndex from first matching - * We actually want whichever is at our `viewportIndex` - */ - const { imageIndex } = viewports[viewportIndex]; - displaySet.imageIndex = imageIndex; - _getViewportData(dataSource, displaySet).then(setViewportData); }, [dataSource, displaySet, viewports, viewportIndex]); @@ -275,19 +292,10 @@ function TrackedCornerstoneViewport({ setCornerstoneMeasurementActive(measurement); - const { - displaySetInstanceUID, - imageIndex, - } = _getViewportDataFromTrackedMeasurementId( - measurement, - DisplaySetService - ); - - viewportGridService.setDisplaysetForViewport({ + MeasurementService.jumpToMeasurement( viewportIndex, - displaySetInstanceUID, - imageIndex, - }); + newTrackedMeasurementId + ); } const showNavArrows = isTracked && viewportIndex === activeViewportIndex; @@ -336,14 +344,6 @@ function TrackedCornerstoneViewport({ viewportIndex={viewportIndex} imageIds={imageIds} imageIdIndex={currentImageIdIndex} - onNewImageDebounceTime={700} - onNewImageDebounced={({ currentImageIdIndex }) => { - viewportGridService.setDisplaysetForViewport({ - viewportIndex: activeViewportIndex, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - imageIndex: currentImageIdIndex, - }); - }} // Sync resize throttle w/ sidepanel animation duration to prevent // seizure inducing strobe blinking effect resizeRefreshRateMs={150} @@ -402,16 +402,12 @@ const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']; * @return {Object} CornerstoneTools Stack */ function _getCornerstoneStack(displaySet, dataSource) { - const { imageIndex } = displaySet; - // Get stack from Stack Manager const storedStack = StackManager.findOrCreateStack(displaySet, dataSource); // Clone the stack here so we don't mutate it const stack = Object.assign({}, storedStack); - stack.currentImageIdIndex = imageIndex; - return stack; } @@ -498,26 +494,111 @@ function _getNextMeasurementId( return newTrackedMeasurementId; } -function _getViewportDataFromTrackedMeasurementId( +function _subscribeToJumpToMeasurementEvents( + MeasurementService, + DisplaySetService, + element, + viewportIndex, + displaySetInstanceUID +) { + const { unsubscribe } = MeasurementService.subscribe( + MeasurementService.EVENTS.JUMP_TO_MEASUREMENT, + ({ viewportIndex: jumpToMeasurementViewportIndex, measurement }) => { + // check if the correct viewport index. + if (viewportIndex !== jumpToMeasurementViewportIndex) { + // Event for a different viewport. + return; + } + + if (measurement.displaySetInstanceUID !== displaySetInstanceUID) { + // Not for this displaySet. + return; + } + + _jumpToMeasurement( + measurement, + element, + viewportIndex, + MeasurementService, + DisplaySetService + ); + } + ); + + return unsubscribe; +} + +function _checkForCachedJumpToMeasurementEvents( + MeasurementService, + DisplaySetService, + element, + viewportIndex, + displaySetInstanceUID +) { + // Check if there is a queued jumpToMeasurement event + const measurementIdToJumpTo = MeasurementService.getJumpToMeasurement( + viewportIndex + ); + + if (measurementIdToJumpTo && element) { + // Jump to measurement if the measurement exists + const measurement = MeasurementService.getMeasurement( + measurementIdToJumpTo + ); + + if (measurement.displaySetInstanceUID === displaySetInstanceUID) { + _jumpToMeasurement( + measurement, + element, + viewportIndex, + MeasurementService, + DisplaySetService + ); + } + } +} + +function _jumpToMeasurement( measurement, + targetElement, + viewportIndex, + MeasurementService, DisplaySetService ) { - const { referenceSeriesUID: SeriesInstanceUID, SOPInstanceUID } = measurement; + const { displaySetInstanceUID, SOPInstanceUID } = measurement; - const activeDisplaySets = DisplaySetService.getActiveDisplaySets(); - - const displaySet = activeDisplaySets.find( - ds => ds.SeriesInstanceUID === SeriesInstanceUID + const referencedDisplaySet = DisplaySetService.getDisplaySetByUID( + displaySetInstanceUID ); - const imageIndex = displaySet.images.findIndex( - image => image.SOPInstanceUID === SOPInstanceUID + const imageIndex = referencedDisplaySet.images.findIndex( + i => i.SOPInstanceUID === SOPInstanceUID ); - return { - imageIndex, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - }; + setCornerstoneMeasurementActive(measurement); + + if (targetElement !== null) { + const enabledElement = cornerstone.getEnabledElement(targetElement); + + if (enabledElement.image) { + // Wait for the image to update or we get a race condition when the element has only just been enabled. + const scrollToHandler = evt => { + scrollToIndex(targetElement, imageIndex); + targetElement.removeEventListener( + 'cornerstoneimagerendered', + scrollToHandler + ); + }; + targetElement.addEventListener( + 'cornerstoneimagerendered', + scrollToHandler + ); + cornerstone.updateImage(targetElement); + } + + // Jump to measurement consumed, remove. + MeasurementService.removeJumpToMeasurement(viewportIndex); + } } export default TrackedCornerstoneViewport; diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json index 52104c2304b..b16d7eb6019 100644 --- a/extensions/vtk/package.json +++ b/extensions/vtk/package.json @@ -33,7 +33,7 @@ "@ohif/ui": "^2.0.0", "cornerstone-core": "^2.3.0", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "dicom-parser": "^1.8.3", "i18next": "^17.0.3", "i18next-browser-languagedetector": "^3.0.1", diff --git a/platform/core/package.json b/platform/core/package.json index bcb7a85412d..a4031b38670 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -38,7 +38,7 @@ "dependencies": { "@babel/runtime": "7.7.6", "ajv": "^6.10.0", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "dicomweb-client": "^0.6.0", "immer": "6.0.2", "isomorphic-base64": "^1.0.2", diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.js b/platform/core/src/services/DisplaySetService/DisplaySetService.js index bd81d0325f1..08026fe4692 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetService.js +++ b/platform/core/src/services/DisplaySetService/DisplaySetService.js @@ -46,6 +46,20 @@ export default class DisplaySetService { ); }; + getDisplaySetForSOPInstanceUID(SOPInstanceUID, SeriesInstanceUID) { + const displaySets = SeriesInstanceUID + ? this.getDisplaySetsForSeries(SeriesInstanceUID) + : this.getDisplaySetCache(); + + const displaySet = displaySets.find(ds => { + return ( + ds.images && ds.images.some(i => i.SOPInstanceUID === SOPInstanceUID) + ); + }); + + return displaySet; + } + /** * @param {string} displaySetInstanceUID * @returns {object} displaySet diff --git a/platform/core/src/services/MeasurementService/MeasurementService.js b/platform/core/src/services/MeasurementService/MeasurementService.js index fde47eb0df1..82baa8e8c72 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.js @@ -53,6 +53,7 @@ const EVENTS = { MEASUREMENT_ADDED: 'event::measurement_added', MEASUREMENT_REMOVED: 'event::measurement_removed', MEASUREMENTS_CLEARED: 'event::measurements_cleared', + JUMP_TO_MEASUREMENT: 'event:jump_to_measurement', }; const VALUE_TYPES = { @@ -70,6 +71,7 @@ class MeasurementService { this.mappings = {}; this.measurements = {}; this.listeners = {}; + this._jumpToMeasurementCache = {}; Object.defineProperty(this, 'EVENTS', { value: EVENTS, writable: false, @@ -498,6 +500,40 @@ class MeasurementService { this._broadcastChange(this.EVENTS.MEASUREMENTS_CLEARED); } + jumpToMeasurement(viewportIndex, id) { + const measurement = this.measurements[id]; + + if (!measurement) { + log.warn(`No id provided, or unable to find measurement by id.`); + return; + } + + this._addJumpToMeasurement(viewportIndex, id); + + const eventName = this.EVENTS.JUMP_TO_MEASUREMENT; + + const hasListeners = Object.keys(this.listeners).length > 0; + const hasCallbacks = Array.isArray(this.listeners[eventName]); + + if (hasListeners && hasCallbacks) { + this.listeners[eventName].forEach(listener => { + listener.callback({ viewportIndex, measurement }); + }); + } + } + + _addJumpToMeasurement(viewportIndex, id) { + this._jumpToMeasurementCache[viewportIndex] = id; + } + + getJumpToMeasurement(viewportIndex) { + return this._jumpToMeasurementCache[viewportIndex]; + } + + removeJumpToMeasurement(viewportIndex) { + delete this._jumpToMeasurementCache[viewportIndex]; + } + _getMappingByMeasurementSource(measurementId, definition) { const measurement = this.getMeasurement(measurementId); if (this._isValidSource(measurement.source)) { diff --git a/platform/ui/src/contextProviders/ViewportGridProvider.jsx b/platform/ui/src/contextProviders/ViewportGridProvider.jsx index f2bb8a04bf1..04c5d7ab6df 100644 --- a/platform/ui/src/contextProviders/ViewportGridProvider.jsx +++ b/platform/ui/src/contextProviders/ViewportGridProvider.jsx @@ -27,14 +27,10 @@ export function ViewportGridProvider({ children, service }) { return { ...state, ...{ activeViewportIndex: action.payload } }; } case 'SET_DISPLAYSET_FOR_VIEWPORT': { - const { - viewportIndex, - displaySetInstanceUID, - imageIndex, - } = action.payload; + const { viewportIndex, displaySetInstanceUID } = action.payload; const viewports = state.viewports.slice(); - viewports[viewportIndex] = { displaySetInstanceUID, imageIndex }; + viewports[viewportIndex] = { displaySetInstanceUID }; return { ...state, ...{ viewports }, cachedLayout: null }; } @@ -63,7 +59,7 @@ export function ViewportGridProvider({ children, service }) { numCols: 1, numRows: 1, activeViewportIndex: 0, - viewports: [{ displaySetInstanceUID: null, imageIndex: null }], + viewports: [{ displaySetInstanceUID: null }], cachedLayout: null, }; } @@ -96,13 +92,12 @@ export function ViewportGridProvider({ children, service }) { [dispatch] ); const setDisplaysetForViewport = useCallback( - ({ viewportIndex, displaySetInstanceUID, imageIndex }) => + ({ viewportIndex, displaySetInstanceUID }) => dispatch({ type: 'SET_DISPLAYSET_FOR_VIEWPORT', payload: { viewportIndex, displaySetInstanceUID, - imageIndex, }, }), [dispatch] diff --git a/platform/viewer/package.json b/platform/viewer/package.json index 621cbc0c303..b3e41229650 100644 --- a/platform/viewer/package.json +++ b/platform/viewer/package.json @@ -70,7 +70,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "4.18.1", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.16.0", + "dcmjs": "0.16.1", "dicom-parser": "^1.8.3", "dicomweb-client": "^0.4.4", "dotenv-webpack": "^1.7.0", diff --git a/platform/viewer/src/components/ViewportGrid.jsx b/platform/viewer/src/components/ViewportGrid.jsx index 48ef18a30e6..8de6dcc876a 100644 --- a/platform/viewer/src/components/ViewportGrid.jsx +++ b/platform/viewer/src/components/ViewportGrid.jsx @@ -1,7 +1,7 @@ /** * CSS Grid Reference: http://grid.malven.co/ */ -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui'; import EmptyViewport from './EmptyViewport'; @@ -11,13 +11,18 @@ import classNames from 'classnames'; function ViewerViewportGrid(props) { const { servicesManager, viewportComponents, dataSource } = props; - const [ - { numCols, numRows, activeViewportIndex, viewports, cachedLayout }, - viewportGridService, - ] = useViewportGrid(); + const [viewportGrid, viewportGridService] = useViewportGrid(); + + const { + numCols, + numRows, + activeViewportIndex, + viewports, + cachedLayout, + } = viewportGrid; // TODO -> Need some way of selecting which displaySets hit the viewports. - const { DisplaySetService } = servicesManager.services; + const { DisplaySetService, MeasurementService } = servicesManager.services; useEffect(() => { const { unsubscribe } = DisplaySetService.subscribe( @@ -40,30 +45,56 @@ function ViewerViewportGrid(props) { }; }, []); - // TODO -> Make a HangingProtocolService - const HangingProtocolService = displaySets => { - let displaySetInstanceUID; - - // Fallback - if (!displaySets || !displaySets.length) { - const displaySet = DisplaySetService.activeDisplaySets[0]; - displaySetInstanceUID = displaySet.displaySetInstanceUID; - } else { - const displaySet = displaySets[0]; - displaySetInstanceUID = displaySet.displaySetInstanceUID; - } + useEffect(() => { + const { unsubscribe } = MeasurementService.subscribe( + MeasurementService.EVENTS.JUMP_TO_MEASUREMENT, + ({ viewportIndex, measurement }) => { + const referencedDisplaySetInstanceUID = + measurement.displaySetInstanceUID; + + // If the viewport does not contain the displaySet, then hang that displaySet. + if ( + viewports[viewportIndex].displaySetInstanceUID !== + referencedDisplaySetInstanceUID + ) { + viewportGridService.setDisplaysetForViewport({ + viewportIndex, + displaySetInstanceUID: referencedDisplaySetInstanceUID, + }); + } + } + ); - return { - numRows: 1, - numCols: 1, - activeViewportIndex: 0, - viewports: [ - { - displaySetInstanceUID, - }, - ], + return () => { + unsubscribe(); }; - }; + }, [viewports]); + + // TODO -> Make a HangingProtocolService + // Commented out whilst not in use to avoid pointlessly regenerating this function. + // const HangingProtocolService = displaySets => { + // let displaySetInstanceUID; + + // // Fallback + // if (!displaySets || !displaySets.length) { + // const displaySet = DisplaySetService.activeDisplaySets[0]; + // displaySetInstanceUID = displaySet.displaySetInstanceUID; + // } else { + // const displaySet = displaySets[0]; + // displaySetInstanceUID = displaySet.displaySetInstanceUID; + // } + + // return { + // numRows: 1, + // numCols: 1, + // activeViewportIndex: 0, + // viewports: [ + // { + // displaySetInstanceUID, + // }, + // ], + // }; + // }; const onDoubleClick = viewportIndex => { // TODO -> Disabled for now. diff --git a/yarn.lock b/yarn.lock index 5a2d2bd98bf..59b4d5445b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7364,10 +7364,10 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dcmjs@0.16.0: - version "0.16.0" - resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.0.tgz#714755d006c3a49cf65ef13e627ff7a23151d35a" - integrity sha512-41nY3Isg5i5FoSd1s67aUgUHi146r+fb8JuiRvDsxvm7MEkkdjIzMYMtxjsrVwKRW1xRH+x34nxbV9+8cc7WQw== +dcmjs@0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.1.tgz#76bc61cbdf2c58a2f54990080729ca2d7ee19543" + integrity sha512-t6vsKi5QXzwX1fwnHY2hdU99FfyzmK8aO0OSBDH6XvJNrBp2A6HpoVWbucybOy8eStIPDqs4v0FGP/m39cTCRA== dependencies: "@babel/polyfill" "^7.8.3" "@babel/runtime" "^7.8.4"