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
This commit is contained in:
M.D 2023-04-17 08:23:03 -04:00 committed by GitHub
parent 83b4606357
commit 47c25f4364
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 412 additions and 69 deletions

View File

@ -36,7 +36,7 @@
"@ohif/extension-cornerstone": "^3.0.0", "@ohif/extension-cornerstone": "^3.0.0",
"@ohif/extension-measurement-tracking": "^3.0.0", "@ohif/extension-measurement-tracking": "^3.0.0",
"@ohif/ui": "^2.0.0", "@ohif/ui": "^2.0.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",

View File

@ -6,6 +6,7 @@ import {
BidirectionalTool, BidirectionalTool,
CobbAngleTool, CobbAngleTool,
EllipticalROITool, EllipticalROITool,
CircleROITool,
LengthTool, LengthTool,
PlanarFreehandROITool, PlanarFreehandROITool,
} from '@cornerstonejs/tools'; } from '@cornerstonejs/tools';
@ -24,6 +25,7 @@ export default function init({
addToolInstance(toolNames.SRLength, LengthTool, {}); addToolInstance(toolNames.SRLength, LengthTool, {});
addToolInstance(toolNames.SRBidirectional, BidirectionalTool); addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
addToolInstance(toolNames.SREllipticalROI, EllipticalROITool); addToolInstance(toolNames.SREllipticalROI, EllipticalROITool);
addToolInstance(toolNames.SRCircleROI, CircleROITool);
addToolInstance(toolNames.SRArrowAnnotate, ArrowAnnotateTool); addToolInstance(toolNames.SRArrowAnnotate, ArrowAnnotateTool);
addToolInstance(toolNames.SRAngle, AngleTool); addToolInstance(toolNames.SRAngle, AngleTool);
// TODO - fix the SR display of Cobb Angle, as it joins the two lines // TODO - fix the SR display of Cobb Angle, as it joins the two lines
@ -40,6 +42,7 @@ export default function init({
SRLength: dashedLine, SRLength: dashedLine,
SRBidirectional: dashedLine, SRBidirectional: dashedLine,
SREllipticalROI: dashedLine, SREllipticalROI: dashedLine,
SRCircleROI: dashedLine,
SRArrowAnnotate: dashedLine, SRArrowAnnotate: dashedLine,
SRCobbAngle: dashedLine, SRCobbAngle: dashedLine,
SRAngle: dashedLine, SRAngle: dashedLine,

View File

@ -5,6 +5,7 @@ const toolNames = {
SRLength: 'SRLength', SRLength: 'SRLength',
SRBidirectional: 'SRBidirectional', SRBidirectional: 'SRBidirectional',
SREllipticalROI: 'SREllipticalROI', SREllipticalROI: 'SREllipticalROI',
SRCircleROI: 'SRCircleROI',
SRArrowAnnotate: 'SRArrowAnnotate', SRArrowAnnotate: 'SRArrowAnnotate',
SRAngle: 'SRAngle', SRAngle: 'SRAngle',
SRCobbAngle: 'SRCobbAngle', SRCobbAngle: 'SRCobbAngle',

View File

@ -30,7 +30,7 @@
"@ohif/core": "^3.0.0", "@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0", "@ohif/ui": "^2.0.0",
"cornerstone-wado-image-loader": "^4.13.0", "cornerstone-wado-image-loader": "^4.13.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",

View File

@ -9,6 +9,7 @@ import {
LengthTool, LengthTool,
RectangleROITool, RectangleROITool,
EllipticalROITool, EllipticalROITool,
CircleROITool,
BidirectionalTool, BidirectionalTool,
ArrowAnnotateTool, ArrowAnnotateTool,
DragProbeTool, DragProbeTool,
@ -41,6 +42,7 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(LengthTool); addTool(LengthTool);
addTool(RectangleROITool); addTool(RectangleROITool);
addTool(EllipticalROITool); addTool(EllipticalROITool);
addTool(CircleROITool);
addTool(BidirectionalTool); addTool(BidirectionalTool);
addTool(ArrowAnnotateTool); addTool(ArrowAnnotateTool);
addTool(DragProbeTool); addTool(DragProbeTool);
@ -83,6 +85,7 @@ const toolNames = {
Probe: ProbeTool.toolName, Probe: ProbeTool.toolName,
RectangleROI: RectangleROITool.toolName, RectangleROI: RectangleROITool.toolName,
EllipticalROI: EllipticalROITool.toolName, EllipticalROI: EllipticalROITool.toolName,
CircleROI: CircleROITool.toolName,
Bidirectional: BidirectionalTool.toolName, Bidirectional: BidirectionalTool.toolName,
Angle: AngleTool.toolName, Angle: AngleTool.toolName,
CobbAngle: CobbAngleTool.toolName, CobbAngle: CobbAngleTool.toolName,

View File

@ -24,6 +24,7 @@ const initMeasurementService = (
Length, Length,
Bidirectional, Bidirectional,
EllipticalROI, EllipticalROI,
CircleROI,
ArrowAnnotate, ArrowAnnotate,
Angle, Angle,
CobbAngle, CobbAngle,
@ -78,6 +79,14 @@ const initMeasurementService = (
EllipticalROI.toMeasurement EllipticalROI.toMeasurement
); );
measurementService.addMapping(
csTools3DVer1MeasurementSource,
'CircleROI',
CircleROI.matchingCriteria,
CircleROI.toAnnotation,
CircleROI.toMeasurement
);
measurementService.addMapping( measurementService.addMapping(
csTools3DVer1MeasurementSource, csTools3DVer1MeasurementSource,
'ArrowAnnotate', 'ArrowAnnotate',

View File

@ -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} mm<sup>2</sup>`);
// 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} <small>${unit}</small> `;
}
const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`;
if (!displayText.includes(str)) {
displayText.push(str);
}
});
return displayText;
}
export default CircleROI;

View File

@ -1,6 +1,6 @@
import SUPPORTED_TOOLS from './constants/supportedTools'; import SUPPORTED_TOOLS from './constants/supportedTools';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { displaySetService, utils } from '@ohif/core'; import { utils } from '@ohif/core';
const Length = { const Length = {
toAnnotation: measurement => {}, toAnnotation: measurement => {},
@ -32,15 +32,12 @@ const Length = {
throw new Error('Tool not supported'); throw new Error('Tool not supported');
} }
const { const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } =
SOPInstanceUID, getSOPInstanceAttributes(
SeriesInstanceUID, referencedImageId,
StudyInstanceUID, cornerstoneViewportService,
} = getSOPInstanceAttributes( viewportId
referencedImageId, );
cornerstoneViewportService,
viewportId
);
let displaySet; let displaySet;
@ -104,11 +101,8 @@ function getMappedAnnotations(annotation, displaySetService) {
); );
} }
const { const { SOPInstanceUID, SeriesInstanceUID, frameNumber } =
SOPInstanceUID, getSOPInstanceAttributes(referencedImageId);
SeriesInstanceUID,
frameNumber,
} = getSOPInstanceAttributes(referencedImageId);
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID( const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID, SOPInstanceUID,
@ -179,12 +173,8 @@ function getDisplayText(mappedAnnotations, displaySet) {
const displayText = []; const displayText = [];
// Area is the same for all series // Area is the same for all series
const { const { length, SeriesNumber, SOPInstanceUID, frameNumber } =
length, mappedAnnotations[0];
SeriesNumber,
SOPInstanceUID,
frameNumber,
} = mappedAnnotations[0];
const instance = displaySet.images.find( const instance = displaySet.images.find(
image => image.SOPInstanceUID === SOPInstanceUID image => image.SOPInstanceUID === SOPInstanceUID

View File

@ -4,7 +4,7 @@ import getModalityUnit from './utils/getModalityUnit';
import { utils } from '@ohif/core'; import { utils } from '@ohif/core';
const RectangleROI = { const RectangleROI = {
toAnnotation: measurement => { }, toAnnotation: measurement => {},
toMeasurement: ( toMeasurement: (
csToolsEventDetail, csToolsEventDetail,
DisplaySetService, DisplaySetService,
@ -26,15 +26,12 @@ const RectangleROI = {
throw new Error('Tool not supported'); throw new Error('Tool not supported');
} }
const { const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } =
SOPInstanceUID, getSOPInstanceAttributes(
SeriesInstanceUID, referencedImageId,
StudyInstanceUID, CornerstoneViewportService,
} = getSOPInstanceAttributes( viewportId
referencedImageId, );
CornerstoneViewportService,
viewportId
);
let displaySet; let displaySet;
@ -99,11 +96,8 @@ function getMappedAnnotations(annotation, DisplaySetService) {
); );
} }
const { const { SOPInstanceUID, SeriesInstanceUID, frameNumber } =
SOPInstanceUID, getSOPInstanceAttributes(referencedImageId);
SeriesInstanceUID,
frameNumber,
} = getSOPInstanceAttributes(referencedImageId);
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID, SOPInstanceUID,
@ -143,7 +137,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
// Add Type // Add Type
columns.push('AnnotationType'); columns.push('AnnotationType');
values.push('Cornerstone:EllipticalROI'); values.push('Cornerstone:RectangleROI');
mappedAnnotations.forEach(annotation => { mappedAnnotations.forEach(annotation => {
const { mean, stdDev, max, area, unit } = annotation; const { mean, stdDev, max, area, unit } = annotation;

View File

@ -1,6 +1,7 @@
export default [ export default [
'Length', 'Length',
'EllipticalROI', 'EllipticalROI',
'CircleROI',
'Bidirectional', 'Bidirectional',
'ArrowAnnotate', 'ArrowAnnotate',
'Angle', 'Angle',

View File

@ -2,6 +2,7 @@ import { MeasurementService } from '@ohif/core';
import Length from './Length'; import Length from './Length';
import Bidirectional from './Bidirectional'; import Bidirectional from './Bidirectional';
import EllipticalROI from './EllipticalROI'; import EllipticalROI from './EllipticalROI';
import CircleROI from './CircleROI';
import ArrowAnnotate from './ArrowAnnotate'; import ArrowAnnotate from './ArrowAnnotate';
import CobbAngle from './CobbAngle'; import CobbAngle from './CobbAngle';
import Angle from './Angle'; import Angle from './Angle';
@ -25,6 +26,7 @@ const measurementServiceMappingsFactory = (
const { const {
POLYLINE, POLYLINE,
ELLIPSE, ELLIPSE,
CIRCLE,
RECTANGLE, RECTANGLE,
BIDIRECTIONAL, BIDIRECTIONAL,
POINT, POINT,
@ -37,6 +39,7 @@ const measurementServiceMappingsFactory = (
const TOOL_TYPE_TO_VALUE_TYPE = { const TOOL_TYPE_TO_VALUE_TYPE = {
Length: POLYLINE, Length: POLYLINE,
EllipticalROI: ELLIPSE, EllipticalROI: ELLIPSE,
CircleROI: CIRCLE,
RectangleROI: RECTANGLE, RectangleROI: RECTANGLE,
PlanarFreehandROI: POLYLINE, PlanarFreehandROI: POLYLINE,
Bidirectional: BIDIRECTIONAL, 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: { RectangleROI: {
toAnnotation: RectangleROI.toAnnotation, toAnnotation: RectangleROI.toAnnotation,
toMeasurement: csToolsAnnotation => toMeasurement: csToolsAnnotation =>

View File

@ -13,8 +13,8 @@ import * as cornerstone from '@cornerstonejs/core';
*/ */
export default function getSOPInstanceAttributes( export default function getSOPInstanceAttributes(
imageId, imageId,
cornerstoneViewportService, cornerstoneViewportService = undefined,
viewportId viewportId = undefined
) { ) {
if (imageId) { if (imageId) {
return _getUIDFromImageID(imageId); return _getUIDFromImageID(imageId);

View File

@ -32,7 +32,7 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^3.0.0", "@ohif/core": "^3.0.0",
"@ohif/i18n": "^1.0.0", "@ohif/i18n": "^1.0.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"dicomweb-client": "^0.8.4", "dicomweb-client": "^0.8.4",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^17.0.2", "react": "^17.0.2",

View File

@ -30,7 +30,7 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^3.0.0", "@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0", "@ohif/ui": "^2.0.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",

View File

@ -30,7 +30,7 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^3.0.0", "@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0", "@ohif/ui": "^2.0.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",

View File

@ -35,7 +35,7 @@
"@cornerstonejs/core": "^0.40.0", "@cornerstonejs/core": "^0.40.0",
"@cornerstonejs/tools": "^0.60.1", "@cornerstonejs/tools": "^0.60.1",
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0", "@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"lodash.debounce": "^4.17.21", "lodash.debounce": "^4.17.21",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^17.0.2", "react": "^17.0.2",

View File

@ -30,7 +30,7 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^3.0.0", "@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0", "@ohif/ui": "^2.0.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",

View File

@ -81,6 +81,7 @@ function modeFactory({ modeConfiguration }) {
{ toolName: toolNames.Bidirectional }, { toolName: toolNames.Bidirectional },
{ toolName: toolNames.Probe }, { toolName: toolNames.Probe },
{ toolName: toolNames.EllipticalROI }, { toolName: toolNames.EllipticalROI },
{ toolName: toolNames.CircleROI },
{ toolName: toolNames.RectangleROI }, { toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll }, { toolName: toolNames.StackScroll },
{ toolName: toolNames.CalibrationLine }, { toolName: toolNames.CalibrationLine },
@ -134,11 +135,8 @@ function modeFactory({ modeConfiguration }) {
]); ]);
}, },
onModeExit: ({ servicesManager }) => { onModeExit: ({ servicesManager }) => {
const { const { toolGroupService, measurementService, toolbarService } =
toolGroupService, servicesManager.services;
measurementService,
toolbarService,
} = servicesManager.services;
toolGroupService.destroy(); toolGroupService.destroy();
}, },

View File

@ -130,6 +130,21 @@ const toolbarButtons = [
], ],
'Ellipse Tool' 'Ellipse Tool'
), ),
_createToolButton(
'CircleROI',
'tool-circle',
'Circle',
[
{
commandName: 'setToolActive',
commandOptions: {
toolName: 'CircleROI',
},
context: 'CORNERSTONE',
},
],
'Circle Tool'
),
], ],
}, },
}, },

View File

@ -32,6 +32,7 @@ function initDefaultToolGroup(
{ toolName: toolNames.Bidirectional }, { toolName: toolNames.Bidirectional },
{ toolName: toolNames.DragProbe }, { toolName: toolNames.DragProbe },
{ toolName: toolNames.EllipticalROI }, { toolName: toolNames.EllipticalROI },
{ toolName: toolNames.CircleROI },
{ toolName: toolNames.RectangleROI }, { toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll }, { toolName: toolNames.StackScroll },
{ toolName: toolNames.Angle }, { toolName: toolNames.Angle },
@ -110,6 +111,7 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) {
{ toolName: SRToolNames.SRArrowAnnotate }, { toolName: SRToolNames.SRArrowAnnotate },
{ toolName: SRToolNames.SRBidirectional }, { toolName: SRToolNames.SRBidirectional },
{ toolName: SRToolNames.SREllipticalROI }, { toolName: SRToolNames.SREllipticalROI },
{ toolName: SRToolNames.SRCircleROI },
], ],
enabled: [ enabled: [
{ {
@ -170,6 +172,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) {
{ toolName: toolNames.Bidirectional }, { toolName: toolNames.Bidirectional },
{ toolName: toolNames.DragProbe }, { toolName: toolNames.DragProbe },
{ toolName: toolNames.EllipticalROI }, { toolName: toolNames.EllipticalROI },
{ toolName: toolNames.CircleROI },
{ toolName: toolNames.RectangleROI }, { toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll }, { toolName: toolNames.StackScroll },
{ toolName: toolNames.Angle }, { toolName: toolNames.Angle },

View File

@ -200,6 +200,29 @@ const toolbarButtons = [
], ],
'Ellipse Tool' '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'
),
], ],
}, },
}, },

View File

@ -32,6 +32,7 @@ function initDefaultToolGroup(
{ toolName: toolNames.Bidirectional }, { toolName: toolNames.Bidirectional },
{ toolName: toolNames.DragProbe }, { toolName: toolNames.DragProbe },
{ toolName: toolNames.EllipticalROI }, { toolName: toolNames.EllipticalROI },
{ toolName: toolNames.CircleROI },
{ toolName: toolNames.RectangleROI }, { toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll }, { toolName: toolNames.StackScroll },
{ toolName: toolNames.Angle }, { toolName: toolNames.Angle },
@ -113,6 +114,7 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) {
{ toolName: SRToolNames.SRArrowAnnotate }, { toolName: SRToolNames.SRArrowAnnotate },
{ toolName: SRToolNames.SRBidirectional }, { toolName: SRToolNames.SRBidirectional },
{ toolName: SRToolNames.SREllipticalROI }, { toolName: SRToolNames.SREllipticalROI },
{ toolName: SRToolNames.SRCircleROI },
], ],
enabled: [ enabled: [
{ {
@ -173,6 +175,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) {
{ toolName: toolNames.Bidirectional }, { toolName: toolNames.Bidirectional },
{ toolName: toolNames.DragProbe }, { toolName: toolNames.DragProbe },
{ toolName: toolNames.EllipticalROI }, { toolName: toolNames.EllipticalROI },
{ toolName: toolNames.CircleROI },
{ toolName: toolNames.RectangleROI }, { toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll }, { toolName: toolNames.StackScroll },
{ toolName: toolNames.Angle }, { toolName: toolNames.Angle },

View File

@ -208,6 +208,29 @@ const toolbarButtons = [
], ],
'Ellipse Tool' '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'
),
], ],
}, },
}, },

View File

@ -173,6 +173,20 @@ const toolbarButtons = [
], ],
'Ellipse Tool' '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', commandName: 'displayNotification',
commandOptions: { commandOptions: {
title: 'RectangleROI Threshold Tip', title: 'RectangleROI Threshold Tip',
text: text: 'RectangleROI Threshold tool should be used on PT Axial Viewport',
'RectangleROI Threshold tool should be used on PT Axial Viewport',
type: 'info', type: 'info',
}, },
}, },

View File

@ -37,7 +37,7 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"dicomweb-client": "^0.8.4", "dicomweb-client": "^0.8.4",
"isomorphic-base64": "^1.0.2", "isomorphic-base64": "^1.0.2",
"lodash.merge": "^4.6.1", "lodash.merge": "^4.6.1",

View File

@ -1,5 +1,9 @@
/**
* @param {string | number} value
* @param {number} decimals
*/
function _round(value, decimals) { function _round(value, decimals) {
return Number(value.toFixed(decimals)); return Number(value).toFixed(decimals);
} }
export default _round; export default _round;

View File

@ -184,6 +184,16 @@ to create `MeasurementTools` nested button
toolName: 'EllipticalRoi', toolName: 'EllipticalRoi',
} }
}, },
// Circle tool
{
id: 'CircleROI',
icon: 'tool-circle',
label: 'Circle',
type: 'tool',
commandOptions: {
toolName: 'CircleROI',
}
},
], ],
}, },
} }

View File

@ -210,6 +210,16 @@ to create `MeasurementTools` nested button
toolName: 'EllipticalRoi', toolName: 'EllipticalRoi',
} }
}, },
// Circle tool
{
id: 'CircleROI',
icon: 'tool-circle',
label: 'Circle',
type: 'tool',
commandOptions: {
toolName: 'CircleROI',
}
},
], ],
}, },
} }

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<circle stroke="currentColor" fill="none" stroke-width="2" cx="16" cy="16" r="14" />
</svg>

After

Width:  |  Height:  |  Size: 157 B

View File

@ -81,6 +81,7 @@ import toolWindow from './../../assets/icons/tool-window-level.svg';
import toolAnnotate from './../../assets/icons/tool-annotate.svg'; import toolAnnotate from './../../assets/icons/tool-annotate.svg';
import toolBidirectional from './../../assets/icons/tool-bidirectional.svg'; import toolBidirectional from './../../assets/icons/tool-bidirectional.svg';
import toolElipse from './../../assets/icons/tool-elipse.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 toolLength from './../../assets/icons/tool-length.svg';
import toolStackScroll from './../../assets/icons/tool-stack-scroll.svg'; import toolStackScroll from './../../assets/icons/tool-stack-scroll.svg';
import toolMagnify from './../../assets/icons/tool-magnify.svg'; import toolMagnify from './../../assets/icons/tool-magnify.svg';
@ -199,6 +200,7 @@ const ICONS = {
'tool-annotate': toolAnnotate, 'tool-annotate': toolAnnotate,
'tool-bidirectional': toolBidirectional, 'tool-bidirectional': toolBidirectional,
'tool-elipse': toolElipse, 'tool-elipse': toolElipse,
'tool-circle': toolCircle,
'tool-length': toolLength, 'tool-length': toolLength,
'tool-stack-scroll': toolStackScroll, 'tool-stack-scroll': toolStackScroll,
'tool-magnify': toolMagnify, 'tool-magnify': toolMagnify,

View File

@ -66,7 +66,7 @@
"core-js": "^3.16.1", "core-js": "^3.16.1",
"cornerstone-math": "^0.1.9", "cornerstone-math": "^0.1.9",
"cornerstone-wado-image-loader": "^4.13.0", "cornerstone-wado-image-loader": "^4.13.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.5",
"detect-gpu": "^4.0.16", "detect-gpu": "^4.0.16",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"dotenv-webpack": "^1.7.0", "dotenv-webpack": "^1.7.0",

View File

@ -9247,21 +9247,9 @@ dayjs@^1.10.4:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== 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: dcmjs@^0.29.5:
version "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== integrity sha512-CcLo3pwitf9JhhvW3/icCraxUhaIKJRyOys9XJlBBDn5TQskyJzxgveLKejcryHn4DooqlJPeMuM4ixdPFEERQ==
dependencies: dependencies:
"@babel/runtime-corejs2" "^7.17.8" "@babel/runtime-corejs2" "^7.17.8"