From 47c25f4364bf76babe5ab99b4a2535a60703f66f Mon Sep 17 00:00:00 2001 From: "M.D" <26860200+md-prog@users.noreply.github.com> Date: Mon, 17 Apr 2023 08:23:03 -0400 Subject: [PATCH] feat(measurements): add CircleROI Tool (#3211) * [refactor] measurement service mapping files - rename to .ts files * [fix] RectangleROI - measurement service mapping _getReport() function - tool name fix * [feat] added CircleROI tool from updated cornerstone3D * [refactor] fix for typescript strong typing warnings * [feat] cornerstone-dicom-sr extension - added CircleROI tool * [feat] added toolbar button for CircleROI tool * [doc] doc updates for CircleROI tool * [update] library - dcmjs to 0.29.5 * [fix] roundNumber() function when given strings * [fix] refactor after upgrading conerstonejs library 0.49.0 * yarn.lock file change --- extensions/cornerstone-dicom-sr/package.json | 2 +- extensions/cornerstone-dicom-sr/src/init.ts | 3 + .../src/tools/toolNames.ts | 1 + extensions/cornerstone/package.json | 2 +- .../cornerstone/src/initCornerstoneTools.js | 3 + .../cornerstone/src/initMeasurementService.js | 9 + .../{ArrowAnnotate.js => ArrowAnnotate.ts} | 0 .../{Bidirectional.js => Bidirectional.ts} | 0 .../measurementServiceMappings/CircleROI.ts | 228 ++++++++++++++++++ .../{EllipticalROI.js => EllipticalROI.ts} | 0 .../{Length.js => Length.ts} | 32 +-- .../RectangleROI.ts | 26 +- .../constants/supportedTools.js | 1 + .../measurementServiceMappingsFactory.ts | 19 ++ .../utils/getSOPInstanceAttributes.js | 4 +- extensions/default/package.json | 2 +- extensions/dicom-pdf/package.json | 2 +- extensions/dicom-video/package.json | 2 +- extensions/measurement-tracking/package.json | 2 +- extensions/tmtv/package.json | 2 +- modes/basic-dev-mode/src/index.js | 8 +- modes/basic-dev-mode/src/toolbarButtons.js | 15 ++ modes/basic-test-mode/src/initToolGroups.js | 3 + modes/basic-test-mode/src/toolbarButtons.js | 23 ++ modes/longitudinal/src/initToolGroups.js | 3 + modes/longitudinal/src/toolbarButtons.js | 23 ++ modes/tmtv/src/toolbarButtons.js | 17 +- platform/core/package.json | 2 +- platform/core/src/utils/roundNumber.js | 6 +- .../platform/extensions/modules/toolbar.md | 10 + .../platform/services/data/ToolbarService.md | 10 + platform/ui/src/assets/icons/tool-circle.svg | 3 + platform/ui/src/components/Icon/getIcon.js | 2 + platform/viewer/package.json | 2 +- yarn.lock | 14 +- 35 files changed, 412 insertions(+), 69 deletions(-) rename extensions/cornerstone/src/utils/measurementServiceMappings/{ArrowAnnotate.js => ArrowAnnotate.ts} (100%) rename extensions/cornerstone/src/utils/measurementServiceMappings/{Bidirectional.js => Bidirectional.ts} (100%) create mode 100644 extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts rename extensions/cornerstone/src/utils/measurementServiceMappings/{EllipticalROI.js => EllipticalROI.ts} (100%) rename extensions/cornerstone/src/utils/measurementServiceMappings/{Length.js => Length.ts} (90%) create mode 100644 platform/ui/src/assets/icons/tool-circle.svg diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index 923385eda..76e8fe82b 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -36,7 +36,7 @@ "@ohif/extension-cornerstone": "^3.0.0", "@ohif/extension-measurement-tracking": "^3.0.0", "@ohif/ui": "^2.0.0", - "dcmjs": "^0.29.4", + "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/cornerstone-dicom-sr/src/init.ts b/extensions/cornerstone-dicom-sr/src/init.ts index ec24b5c48..84cbcaf37 100644 --- a/extensions/cornerstone-dicom-sr/src/init.ts +++ b/extensions/cornerstone-dicom-sr/src/init.ts @@ -6,6 +6,7 @@ import { BidirectionalTool, CobbAngleTool, EllipticalROITool, + CircleROITool, LengthTool, PlanarFreehandROITool, } from '@cornerstonejs/tools'; @@ -24,6 +25,7 @@ export default function init({ addToolInstance(toolNames.SRLength, LengthTool, {}); addToolInstance(toolNames.SRBidirectional, BidirectionalTool); addToolInstance(toolNames.SREllipticalROI, EllipticalROITool); + addToolInstance(toolNames.SRCircleROI, CircleROITool); addToolInstance(toolNames.SRArrowAnnotate, ArrowAnnotateTool); addToolInstance(toolNames.SRAngle, AngleTool); // TODO - fix the SR display of Cobb Angle, as it joins the two lines @@ -40,6 +42,7 @@ export default function init({ SRLength: dashedLine, SRBidirectional: dashedLine, SREllipticalROI: dashedLine, + SRCircleROI: dashedLine, SRArrowAnnotate: dashedLine, SRCobbAngle: dashedLine, SRAngle: dashedLine, diff --git a/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts b/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts index 48375f4cb..2e52ec111 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts +++ b/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts @@ -5,6 +5,7 @@ const toolNames = { SRLength: 'SRLength', SRBidirectional: 'SRBidirectional', SREllipticalROI: 'SREllipticalROI', + SRCircleROI: 'SRCircleROI', SRArrowAnnotate: 'SRArrowAnnotate', SRAngle: 'SRAngle', SRCobbAngle: 'SRCobbAngle', diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 2eab62700..cae1befd0 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.13.0", - "dcmjs": "^0.29.4", + "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js index 2d305af8a..5df8f8983 100644 --- a/extensions/cornerstone/src/initCornerstoneTools.js +++ b/extensions/cornerstone/src/initCornerstoneTools.js @@ -9,6 +9,7 @@ import { LengthTool, RectangleROITool, EllipticalROITool, + CircleROITool, BidirectionalTool, ArrowAnnotateTool, DragProbeTool, @@ -41,6 +42,7 @@ export default function initCornerstoneTools(configuration = {}) { addTool(LengthTool); addTool(RectangleROITool); addTool(EllipticalROITool); + addTool(CircleROITool); addTool(BidirectionalTool); addTool(ArrowAnnotateTool); addTool(DragProbeTool); @@ -83,6 +85,7 @@ const toolNames = { Probe: ProbeTool.toolName, RectangleROI: RectangleROITool.toolName, EllipticalROI: EllipticalROITool.toolName, + CircleROI: CircleROITool.toolName, Bidirectional: BidirectionalTool.toolName, Angle: AngleTool.toolName, CobbAngle: CobbAngleTool.toolName, diff --git a/extensions/cornerstone/src/initMeasurementService.js b/extensions/cornerstone/src/initMeasurementService.js index 36b7229d2..c11a53111 100644 --- a/extensions/cornerstone/src/initMeasurementService.js +++ b/extensions/cornerstone/src/initMeasurementService.js @@ -24,6 +24,7 @@ const initMeasurementService = ( Length, Bidirectional, EllipticalROI, + CircleROI, ArrowAnnotate, Angle, CobbAngle, @@ -78,6 +79,14 @@ const initMeasurementService = ( EllipticalROI.toMeasurement ); + measurementService.addMapping( + csTools3DVer1MeasurementSource, + 'CircleROI', + CircleROI.matchingCriteria, + CircleROI.toAnnotation, + CircleROI.toMeasurement + ); + measurementService.addMapping( csTools3DVer1MeasurementSource, 'ArrowAnnotate', diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts similarity index 100% rename from extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js rename to extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts similarity index 100% rename from extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js rename to extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts new file mode 100644 index 000000000..c55d7da4b --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts @@ -0,0 +1,228 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import getModalityUnit from './utils/getModalityUnit'; +import { utils } from '@ohif/core'; + +const CircleROI = { + toAnnotation: measurement => {}, + toMeasurement: ( + csToolsEventDetail, + DisplaySetService, + CornerstoneViewportService, + getValueTypeFromToolType + ) => { + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + + if (!validToolType) { + throw new Error('Tool not supported'); + } + + const { + SOPInstanceUID, + SeriesInstanceUID, + StudyInstanceUID, + } = getSOPInstanceAttributes( + referencedImageId, + CornerstoneViewportService, + viewportId + ); + + let displaySet; + + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { points } = data.handles; + + const mappedAnnotations = getMappedAnnotations( + annotation, + DisplaySetService + ); + + const displayText = getDisplayText(mappedAnnotations, displaySet); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID); + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + points, + metadata, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + frameNumber: mappedAnnotations[0]?.frameNumber || 1, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: data.label, + displayText: displayText, + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport, + }; + }, +}; + +function getMappedAnnotations(annotation, DisplaySetService) { + const { metadata, data } = annotation; + const { cachedStats } = data; + const { referencedImageId } = metadata; + const targets = Object.keys(cachedStats); + + if (!targets.length) { + return []; + } + + const annotations = []; + Object.keys(cachedStats).forEach(targetId => { + const targetStats = cachedStats[targetId]; + + if (!referencedImageId) { + // Todo: Non-acquisition plane measurement mapping not supported yet + throw new Error( + 'Non-acquisition plane measurement mapping not supported' + ); + } + + 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, + stdDev, + max, + area, + }); + }); + + return annotations; +} + +/* +This function is used to convert the measurement data to a format that is +suitable for the report generation (e.g. for the csv report). The report +returns a list of columns and corresponding values. +*/ +function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { + const columns = []; + const values = []; + + // Add Type + columns.push('AnnotationType'); + values.push('Cornerstone:CircleROI'); + + mappedAnnotations.forEach(annotation => { + const { mean, stdDev, max, area, unit } = annotation; + + if (!mean || !unit || !max || !area) { + return; + } + + columns.push( + `max (${unit})`, + `mean (${unit})`, + `std (${unit})`, + `area (mm2)` + ); + values.push(max, mean, stdDev, area); + }); + + if (FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(FrameOfReferenceUID); + } + + if (points) { + columns.push('points'); + // points has the form of [[x1, y1, z1], [x2, y2, z2], ...] + // convert it to string of [[x1 y1 z1];[x2 y2 z2];...] + // so that it can be used in the csv report + values.push(points.map(p => p.join(' ')).join(';')); + } + + return { + columns, + values, + }; +} + +function getDisplayText(mappedAnnotations, displaySet) { + if (!mappedAnnotations || !mappedAnnotations.length) { + return ''; + } + + const displayText = []; + + // Area is the same for all series + const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; + + const instance = displaySet.images.find( + image => image.SOPInstanceUID === SOPInstanceUID + ); + + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; + const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; + + // Area sometimes becomes undefined if `preventHandleOutsideImage` is off. + const roundedArea = utils.roundNumber(area || 0, 2); + displayText.push(`${roundedArea} mm2`); + + // Todo: we need a better UI for displaying all these information + mappedAnnotations.forEach(mappedAnnotation => { + const { unit, max, SeriesNumber } = mappedAnnotation; + + let maxStr = ''; + if (max) { + const roundedMax = utils.roundNumber(max, 2); + maxStr = `Max: ${roundedMax} ${unit} `; + } + + const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`; + if (!displayText.includes(str)) { + displayText.push(str); + } + }); + + return displayText; +} + +export default CircleROI; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts similarity index 100% rename from extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js rename to extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts similarity index 90% rename from extensions/cornerstone/src/utils/measurementServiceMappings/Length.js rename to extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts index 206208257..8dad5a1c1 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts @@ -1,6 +1,6 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; -import { displaySetService, utils } from '@ohif/core'; +import { utils } from '@ohif/core'; const Length = { toAnnotation: measurement => {}, @@ -32,15 +32,12 @@ const Length = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( - referencedImageId, - cornerstoneViewportService, - viewportId - ); + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = + getSOPInstanceAttributes( + referencedImageId, + cornerstoneViewportService, + viewportId + ); let displaySet; @@ -104,11 +101,8 @@ function getMappedAnnotations(annotation, displaySetService) { ); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = displaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -179,12 +173,8 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { - length, - SeriesNumber, - SOPInstanceUID, - frameNumber, - } = mappedAnnotations[0]; + const { length, SeriesNumber, SOPInstanceUID, frameNumber } = + mappedAnnotations[0]; const instance = displaySet.images.find( image => image.SOPInstanceUID === SOPInstanceUID diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts index 7560dc137..01f1178b9 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts @@ -4,7 +4,7 @@ import getModalityUnit from './utils/getModalityUnit'; import { utils } from '@ohif/core'; const RectangleROI = { - toAnnotation: measurement => { }, + toAnnotation: measurement => {}, toMeasurement: ( csToolsEventDetail, DisplaySetService, @@ -26,15 +26,12 @@ const RectangleROI = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( - referencedImageId, - CornerstoneViewportService, - viewportId - ); + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = + getSOPInstanceAttributes( + referencedImageId, + CornerstoneViewportService, + viewportId + ); let displaySet; @@ -99,11 +96,8 @@ function getMappedAnnotations(annotation, DisplaySetService) { ); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -143,7 +137,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { // Add Type columns.push('AnnotationType'); - values.push('Cornerstone:EllipticalROI'); + values.push('Cornerstone:RectangleROI'); mappedAnnotations.forEach(annotation => { const { mean, stdDev, max, area, unit } = annotation; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js index 00be9ae14..e1e8a1755 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js @@ -1,6 +1,7 @@ export default [ 'Length', 'EllipticalROI', + 'CircleROI', 'Bidirectional', 'ArrowAnnotate', 'Angle', diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts index 4d5c77f17..2a4562e7d 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts @@ -2,6 +2,7 @@ import { MeasurementService } from '@ohif/core'; import Length from './Length'; import Bidirectional from './Bidirectional'; import EllipticalROI from './EllipticalROI'; +import CircleROI from './CircleROI'; import ArrowAnnotate from './ArrowAnnotate'; import CobbAngle from './CobbAngle'; import Angle from './Angle'; @@ -25,6 +26,7 @@ const measurementServiceMappingsFactory = ( const { POLYLINE, ELLIPSE, + CIRCLE, RECTANGLE, BIDIRECTIONAL, POINT, @@ -37,6 +39,7 @@ const measurementServiceMappingsFactory = ( const TOOL_TYPE_TO_VALUE_TYPE = { Length: POLYLINE, EllipticalROI: ELLIPSE, + CircleROI: CIRCLE, RectangleROI: RECTANGLE, PlanarFreehandROI: POLYLINE, Bidirectional: BIDIRECTIONAL, @@ -104,6 +107,22 @@ const measurementServiceMappingsFactory = ( ], }, + CircleROI: { + toAnnotation: CircleROI.toAnnotation, + toMeasurement: csToolsAnnotation => + CircleROI.toMeasurement( + csToolsAnnotation, + displaySetService, + cornerstoneViewportService, + _getValueTypeFromToolType + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.CIRCLE, + }, + ], + }, + RectangleROI: { toAnnotation: RectangleROI.toAnnotation, toMeasurement: csToolsAnnotation => diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js index b93226bb1..c0707dd4f 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js @@ -13,8 +13,8 @@ import * as cornerstone from '@cornerstonejs/core'; */ export default function getSOPInstanceAttributes( imageId, - cornerstoneViewportService, - viewportId + cornerstoneViewportService = undefined, + viewportId = undefined ) { if (imageId) { return _getUIDFromImageID(imageId); diff --git a/extensions/default/package.json b/extensions/default/package.json index 0ca309f62..81ca66393 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.29.4", + "dcmjs": "^0.29.5", "dicomweb-client": "^0.8.4", "prop-types": "^15.6.2", "react": "^17.0.2", diff --git a/extensions/dicom-pdf/package.json b/extensions/dicom-pdf/package.json index 85684879c..1de3bce06 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.29.4", + "dcmjs": "^0.29.5", "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 83f4e060b..6447fa568 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.29.4", + "dcmjs": "^0.29.5", "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 332d84253..59ab9e3b9 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -35,7 +35,7 @@ "@cornerstonejs/core": "^0.40.0", "@cornerstonejs/tools": "^0.60.1", "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", - "dcmjs": "^0.29.4", + "dcmjs": "^0.29.5", "lodash.debounce": "^4.17.21", "prop-types": "^15.6.2", "react": "^17.0.2", diff --git a/extensions/tmtv/package.json b/extensions/tmtv/package.json index 9210d7cfa..89d25ba3b 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.29.4", + "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/modes/basic-dev-mode/src/index.js b/modes/basic-dev-mode/src/index.js index d74ee60a4..229feb643 100644 --- a/modes/basic-dev-mode/src/index.js +++ b/modes/basic-dev-mode/src/index.js @@ -81,6 +81,7 @@ function modeFactory({ modeConfiguration }) { { toolName: toolNames.Bidirectional }, { toolName: toolNames.Probe }, { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, { toolName: toolNames.StackScroll }, { toolName: toolNames.CalibrationLine }, @@ -134,11 +135,8 @@ function modeFactory({ modeConfiguration }) { ]); }, onModeExit: ({ servicesManager }) => { - const { - toolGroupService, - measurementService, - toolbarService, - } = servicesManager.services; + const { toolGroupService, measurementService, toolbarService } = + servicesManager.services; toolGroupService.destroy(); }, diff --git a/modes/basic-dev-mode/src/toolbarButtons.js b/modes/basic-dev-mode/src/toolbarButtons.js index 4a08edbf0..995e4dea6 100644 --- a/modes/basic-dev-mode/src/toolbarButtons.js +++ b/modes/basic-dev-mode/src/toolbarButtons.js @@ -130,6 +130,21 @@ const toolbarButtons = [ ], 'Ellipse Tool' ), + _createToolButton( + 'CircleROI', + 'tool-circle', + 'Circle', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'CircleROI', + }, + context: 'CORNERSTONE', + }, + ], + 'Circle Tool' + ), ], }, }, diff --git a/modes/basic-test-mode/src/initToolGroups.js b/modes/basic-test-mode/src/initToolGroups.js index 105b1a2f8..ccc5c1a6d 100644 --- a/modes/basic-test-mode/src/initToolGroups.js +++ b/modes/basic-test-mode/src/initToolGroups.js @@ -32,6 +32,7 @@ function initDefaultToolGroup( { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, { toolName: toolNames.StackScroll }, { toolName: toolNames.Angle }, @@ -110,6 +111,7 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: SRToolNames.SRArrowAnnotate }, { toolName: SRToolNames.SRBidirectional }, { toolName: SRToolNames.SREllipticalROI }, + { toolName: SRToolNames.SRCircleROI }, ], enabled: [ { @@ -170,6 +172,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, { toolName: toolNames.StackScroll }, { toolName: toolNames.Angle }, diff --git a/modes/basic-test-mode/src/toolbarButtons.js b/modes/basic-test-mode/src/toolbarButtons.js index 9a04581c5..2d0492f6d 100644 --- a/modes/basic-test-mode/src/toolbarButtons.js +++ b/modes/basic-test-mode/src/toolbarButtons.js @@ -200,6 +200,29 @@ const toolbarButtons = [ ], 'Ellipse Tool' ), + _createToolButton( + 'CircleROI', + 'tool-circle', + 'Circle', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'CircleROI', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRCircleROI', + toolGroupId: 'SRToolGroup', + }, + context: 'CORNERSTONE', + }, + ], + 'Circle Tool' + ), ], }, }, diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js index 6bbe668d2..b8e88db90 100644 --- a/modes/longitudinal/src/initToolGroups.js +++ b/modes/longitudinal/src/initToolGroups.js @@ -32,6 +32,7 @@ function initDefaultToolGroup( { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, { toolName: toolNames.StackScroll }, { toolName: toolNames.Angle }, @@ -113,6 +114,7 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: SRToolNames.SRArrowAnnotate }, { toolName: SRToolNames.SRBidirectional }, { toolName: SRToolNames.SREllipticalROI }, + { toolName: SRToolNames.SRCircleROI }, ], enabled: [ { @@ -173,6 +175,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, { toolName: toolNames.StackScroll }, { toolName: toolNames.Angle }, diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js index a2b72cbf1..92de37e89 100644 --- a/modes/longitudinal/src/toolbarButtons.js +++ b/modes/longitudinal/src/toolbarButtons.js @@ -208,6 +208,29 @@ const toolbarButtons = [ ], 'Ellipse Tool' ), + _createToolButton( + 'CircleROI', + 'tool-circle', + 'Circle', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'CircleROI', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRCircleROI', + toolGroupId: 'SRToolGroup', + }, + context: 'CORNERSTONE', + }, + ], + 'Circle Tool' + ), ], }, }, diff --git a/modes/tmtv/src/toolbarButtons.js b/modes/tmtv/src/toolbarButtons.js index 8fd347737..f6f707221 100644 --- a/modes/tmtv/src/toolbarButtons.js +++ b/modes/tmtv/src/toolbarButtons.js @@ -173,6 +173,20 @@ const toolbarButtons = [ ], 'Ellipse Tool' ), + _createToolButton( + 'CircleROI', + 'tool-circle', + 'Circle', + [ + ..._createCommands('setToolActive', 'CircleROI', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + // toolGroupIds.MPR, + ]), + ], + 'Circle Tool' + ), ], }, }, @@ -299,8 +313,7 @@ const toolbarButtons = [ commandName: 'displayNotification', commandOptions: { title: 'RectangleROI Threshold Tip', - text: - 'RectangleROI Threshold tool should be used on PT Axial Viewport', + text: 'RectangleROI Threshold tool should be used on PT Axial Viewport', type: 'info', }, }, diff --git a/platform/core/package.json b/platform/core/package.json index 42c973065..cb97a9eab 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "dcmjs": "^0.29.4", + "dcmjs": "^0.29.5", "dicomweb-client": "^0.8.4", "isomorphic-base64": "^1.0.2", "lodash.merge": "^4.6.1", diff --git a/platform/core/src/utils/roundNumber.js b/platform/core/src/utils/roundNumber.js index b62e5df42..a441225da 100644 --- a/platform/core/src/utils/roundNumber.js +++ b/platform/core/src/utils/roundNumber.js @@ -1,5 +1,9 @@ +/** + * @param {string | number} value + * @param {number} decimals + */ function _round(value, decimals) { - return Number(value.toFixed(decimals)); + return Number(value).toFixed(decimals); } export default _round; diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md index f340e0c4e..98e0d91a7 100644 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md @@ -184,6 +184,16 @@ to create `MeasurementTools` nested button toolName: 'EllipticalRoi', } }, + // Circle tool + { + id: 'CircleROI', + icon: 'tool-circle', + label: 'Circle', + type: 'tool', + commandOptions: { + toolName: 'CircleROI', + } + }, ], }, } diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md index b080c6ef4..92ab50048 100644 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md +++ b/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md @@ -210,6 +210,16 @@ to create `MeasurementTools` nested button toolName: 'EllipticalRoi', } }, + // Circle tool + { + id: 'CircleROI', + icon: 'tool-circle', + label: 'Circle', + type: 'tool', + commandOptions: { + toolName: 'CircleROI', + } + }, ], }, } diff --git a/platform/ui/src/assets/icons/tool-circle.svg b/platform/ui/src/assets/icons/tool-circle.svg new file mode 100644 index 000000000..e94375564 --- /dev/null +++ b/platform/ui/src/assets/icons/tool-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js index e6c3b986a..b8df5463d 100644 --- a/platform/ui/src/components/Icon/getIcon.js +++ b/platform/ui/src/components/Icon/getIcon.js @@ -81,6 +81,7 @@ import toolWindow from './../../assets/icons/tool-window-level.svg'; import toolAnnotate from './../../assets/icons/tool-annotate.svg'; import toolBidirectional from './../../assets/icons/tool-bidirectional.svg'; import toolElipse from './../../assets/icons/tool-elipse.svg'; +import toolCircle from './../../assets/icons/tool-circle.svg'; import toolLength from './../../assets/icons/tool-length.svg'; import toolStackScroll from './../../assets/icons/tool-stack-scroll.svg'; import toolMagnify from './../../assets/icons/tool-magnify.svg'; @@ -199,6 +200,7 @@ const ICONS = { 'tool-annotate': toolAnnotate, 'tool-bidirectional': toolBidirectional, 'tool-elipse': toolElipse, + 'tool-circle': toolCircle, 'tool-length': toolLength, 'tool-stack-scroll': toolStackScroll, 'tool-magnify': toolMagnify, diff --git a/platform/viewer/package.json b/platform/viewer/package.json index 60bbf2f42..2c1c7325b 100644 --- a/platform/viewer/package.json +++ b/platform/viewer/package.json @@ -66,7 +66,7 @@ "core-js": "^3.16.1", "cornerstone-math": "^0.1.9", "cornerstone-wado-image-loader": "^4.13.0", - "dcmjs": "^0.29.4", + "dcmjs": "^0.29.5", "detect-gpu": "^4.0.16", "dicom-parser": "^1.8.9", "dotenv-webpack": "^1.7.0", diff --git a/yarn.lock b/yarn.lock index e5d1f8dde..5611ea7a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9247,21 +9247,9 @@ dayjs@^1.10.4: resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== -dcmjs@^0.29.4: - version "0.29.4" - resolved "https://registry.npmjs.org/dcmjs/-/dcmjs-0.29.4.tgz#3fb13945611979f756bc91b3f199fd87b4eabdb4" - integrity sha512-PzD6C4oB7v/AVTIPbkS6D6hjgVcSW7+T+DsxFpkD1xcEgU4qjGM16WGEHH3ZAPDBF0Xgm2d9FXUu/lXZIyfmow== - 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" - dcmjs@^0.29.5: version "0.29.5" - resolved "https://registry.npmjs.org/dcmjs/-/dcmjs-0.29.5.tgz#3e2311fc47aafc70f21e236ad89e2ff9ba6589fd" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.29.5.tgz#3e2311fc47aafc70f21e236ad89e2ff9ba6589fd" integrity sha512-CcLo3pwitf9JhhvW3/icCraxUhaIKJRyOys9XJlBBDn5TQskyJzxgveLKejcryHn4DooqlJPeMuM4ixdPFEERQ== dependencies: "@babel/runtime-corejs2" "^7.17.8"