feat(SR): Measurement adapter mappings and save to same series (#3140)

* feat: Add adapter mappings for RectangleROI and Angle and save same

PR comments

* fix: Rehydrate check on non core measurement adapters
This commit is contained in:
Bill Wallace authored and GitHub committed 2023-02-17 16:44:21 -05:00
1 parent 926290f69e
commit 67fc3f733d
42 files changed
+1277 -218

No files matched your search

+4 -3
View File
@@ -25,6 +25,7 @@ import { registerColormap } from './utils/colormap/transferFunctionHelpers';
import { id } from './id';
import * as csWADOImageLoader from './initWADOImageLoader.js';
import { measurementMappingUtils } from './utils/measurementServiceMappings';
const Component = React.lazy(() => {
return import(
@@ -62,10 +63,9 @@ const cornerstoneExtension: Types.Extensions.Extension = {
},
/**
* Register the Cornerstone 3D services and set them up for use.
*
*
* @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
* @param configuration.csToolsConfig - Passed directly to `initCornerstoneTools`
*/
async preRegistration({
servicesManager,
@@ -151,3 +151,4 @@ const cornerstoneExtension: Types.Extensions.Extension = {
};
export default cornerstoneExtension;
export { measurementMappingUtils };
@@ -14,6 +14,8 @@ import {
DragProbeTool,
ProbeTool,
AngleTool,
CobbAngleTool,
PlanarFreehandROITool,
MagnifyTool,
CrosshairsTool,
SegmentationDisplayTool,
@@ -40,6 +42,8 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(ArrowAnnotateTool);
addTool(DragProbeTool);
addTool(AngleTool);
addTool(CobbAngleTool);
addTool(PlanarFreehandROITool);
addTool(MagnifyTool);
addTool(CrosshairsTool);
addTool(SegmentationDisplayTool);
@@ -76,6 +80,8 @@ const toolNames = {
EllipticalROI: EllipticalROITool.toolName,
Bidirectional: BidirectionalTool.toolName,
Angle: AngleTool.toolName,
CobbAngle: CobbAngleTool.toolName,
PlanarFreehandROI: PlanarFreehandROITool.toolName,
Magnify: MagnifyTool.toolName,
Crosshairs: CrosshairsTool.toolName,
SegmentationDisplay: SegmentationDisplayTool.toolName,
@@ -1,6 +1,6 @@
import { eventTarget } from '@cornerstonejs/core';
import { Enums, annotation } from '@cornerstonejs/tools';
import { DicomMetadataStore } from '@ohif/core';
import { DicomMetadataStore, MeasurementService } from '@ohif/core';
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
@@ -23,6 +23,10 @@ const initMeasurementService = (
Bidirectional,
EllipticalROI,
ArrowAnnotate,
Angle,
CobbAngle,
RectangleROI,
PlanarFreehandROI,
} = measurementServiceMappingsFactory(
measurementService,
displaySetService,
@@ -66,6 +70,38 @@ const initMeasurementService = (
ArrowAnnotate.toMeasurement
);
measurementService.addMapping(
csTools3DVer1MeasurementSource,
'CobbAngle',
CobbAngle.matchingCriteria,
CobbAngle.toAnnotation,
CobbAngle.toMeasurement
);
measurementService.addMapping(
csTools3DVer1MeasurementSource,
'Angle',
Angle.matchingCriteria,
Angle.toAnnotation,
Angle.toMeasurement
);
measurementService.addMapping(
csTools3DVer1MeasurementSource,
'RectangleROI',
RectangleROI.matchingCriteria,
RectangleROI.toAnnotation,
RectangleROI.toMeasurement
);
measurementService.addMapping(
csTools3DVer1MeasurementSource,
'PlanarFreehandROI',
PlanarFreehandROI.matchingCriteria,
PlanarFreehandROI.toAnnotation,
PlanarFreehandROI.toMeasurement
);
return csTools3DVer1MeasurementSource;
};
@@ -0,0 +1,210 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
const Angle = {
toAnnotation: measurement => { },
/**
* Maps cornerstone annotation event data to measurement service format.
*
* @param {Object} cornerstone Cornerstone event data
* @return {Measurement} Measurement instance
*/
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) {
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 { angle } = targetStats;
const unit = '\u00B0';
annotations.push({
SeriesInstanceUID,
SOPInstanceUID,
SeriesNumber,
frameNumber,
unit,
angle,
});
});
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:Angle');
mappedAnnotations.forEach(annotation => {
const { angle, unit } = annotation;
columns.push(`Angle (${unit})`);
values.push(angle);
});
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 {
angle,
unit,
SeriesNumber,
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}` : '';
if (angle === undefined) return displayText;
const roundedAngle = utils.roundNumber(angle, 2);
displayText.push(
`${roundedAngle} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`
);
return displayText;
}
export default Angle;
@@ -0,0 +1,210 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
const CobbAngle = {
toAnnotation: measurement => { },
/**
* Maps cornerstone annotation event data to measurement service format.
*
* @param {Object} cornerstone Cornerstone event data
* @return {Measurement} Measurement instance
*/
toMeasurement: (
csToolsEventDetail,
displaySetService,
CornerstoneViewportService,
getValueTypeFromToolType
) => {
const { annotation, viewportId } = csToolsEventDetail;
const { metadata, data, annotationUID } = annotation;
if (!metadata || !data) {
console.warn('Cobb Angle 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) {
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 { angle } = targetStats;
const unit = '\u00B0';
annotations.push({
SeriesInstanceUID,
SOPInstanceUID,
SeriesNumber,
frameNumber,
unit,
angle,
});
});
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:CobbAngle');
mappedAnnotations.forEach(annotation => {
const { angle, unit } = annotation;
columns.push(`Angle (${unit})`);
values.push(angle);
});
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 {
angle,
unit,
SeriesNumber,
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}` : '';
if (angle === undefined) return displayText;
const roundedAngle = utils.roundNumber(angle, 2);
displayText.push(
`${roundedAngle} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`
);
return displayText;
}
export default CobbAngle;
@@ -0,0 +1,148 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
const PlanarFreehandROI = {
toAnnotation: measurement => { },
/**
* Maps cornerstone annotation event data to measurement service format.
*
* @param {Object} cornerstone Cornerstone event data
* @return {Measurement} Measurement instance
*/
toMeasurement: (
csToolsEventDetail,
DisplaySetService,
CornerstoneViewportService,
getValueTypeFromToolType
) => {
const { annotation, viewportId } = csToolsEventDetail;
const { metadata, data, annotationUID } = annotation;
if (!metadata || !data) {
console.warn('PlanarFreehandROI 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);
const getReport = () =>
_getReport(mappedAnnotations, points, FrameOfReferenceUID);
return {
uid: annotationUID,
SOPInstanceUID,
FrameOfReferenceUID,
points,
metadata,
referenceSeriesUID: SeriesInstanceUID,
referenceStudyUID: StudyInstanceUID,
toolName: metadata.toolName,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
label: data.label,
displayText: displayText,
data: { ...data, ...data.cachedStats },
type: getValueTypeFromToolType(toolName),
getReport,
};
},
};
/**
* It maps an imaging library annotation to a list of simplified annotation properties.
*
* @param {Object} annotationData
* @param {Object} DisplaySetService
* @returns
*/
function getMappedAnnotations(annotationData, DisplaySetService) {
const { metadata, data } = annotationData;
const { label } = data;
const { referencedImageId } = metadata;
const annotations = [];
const {
SOPInstanceUID: _SOPInstanceUID,
SeriesInstanceUID: _SeriesInstanceUID,
} = getSOPInstanceAttributes(referencedImageId) || {};
if (!_SOPInstanceUID || !_SeriesInstanceUID) {
return annotations;
}
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
_SOPInstanceUID,
_SeriesInstanceUID
);
const { SeriesNumber, SeriesInstanceUID } = displaySet;
annotations.push({
SeriesInstanceUID,
SeriesNumber,
label,
data,
});
return annotations;
}
/**
* TBD
* 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.
* @param {*} mappedAnnotations
* @param {*} points
* @param {*} FrameOfReferenceUID
* @returns Object representing the report's content for this tool.
*/
function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
const columns = [];
const values = [];
return {
columns,
values,
};
}
function getDisplayText(mappedAnnotations) {
return '';
}
export default PlanarFreehandROI;
@@ -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 RectangleROI = {
toAnnotation: measurement => { },
toMeasurement: (
csToolsEventDetail,
DisplaySetService,
CornerstoneViewportService,
getValueTypeFromToolType
) => {
const { annotation, viewportId } = csToolsEventDetail;
const { metadata, data, annotationUID } = annotation;
if (!metadata || !data) {
console.warn('Rectangle ROI 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:EllipticalROI');
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 RectangleROI;
@@ -1 +1,11 @@
export default ['Length', 'EllipticalROI', 'Bidirectional', 'ArrowAnnotate'];
export default [
'Length',
'EllipticalROI',
'Bidirectional',
'ArrowAnnotate',
'Angle',
'CobbAngle',
'Probe',
'RectangleROI',
'PlanarFreehandROI',
];
@@ -0,0 +1,3 @@
import * as measurementMappingUtils from './utils';
export { measurementMappingUtils };
@@ -1,19 +1,24 @@
import { MeasurementService } from '@ohif/core';
import Length from './Length';
import Bidirectional from './Bidirectional';
import EllipticalROI from './EllipticalROI';
import ArrowAnnotate from './ArrowAnnotate';
import CobbAngle from './CobbAngle';
import Angle from './Angle';
import PlanarFreehandROI from './PlanarFreehandROI';
import RectangleROI from './RectangleROI';
const measurementServiceMappingsFactory = (
measurementService,
measurementService: MeasurementService,
displaySetService,
cornerstoneViewportService
) => {
/**
* Maps measurement service format object to cornerstone annotation object.
*
* @param {Measurement} measurement The measurement instance
* @param {string} definition The source definition
* @return {Object} Cornerstone annotation data
* @param measurement The measurement instance
* @param definition The source definition
* @return Cornerstone annotation data
*/
const _getValueTypeFromToolType = toolType => {
@@ -23,7 +28,8 @@ const measurementServiceMappingsFactory = (
RECTANGLE,
BIDIRECTIONAL,
POINT,
} = measurementService.VALUE_TYPES;
ANGLE,
} = MeasurementService.VALUE_TYPES;
// TODO -> I get why this was attempted, but its not nearly flexible enough.
// A single measurement may have an ellipse + a bidirectional measurement, for instances.
@@ -32,14 +38,17 @@ const measurementServiceMappingsFactory = (
Length: POLYLINE,
EllipticalROI: ELLIPSE,
RectangleROI: RECTANGLE,
PlanarFreehandROI: POLYLINE,
Bidirectional: BIDIRECTIONAL,
ArrowAnnotate: POINT,
CobbAngle: ANGLE,
Angle: ANGLE,
};
return TOOL_TYPE_TO_VALUE_TYPE[toolType];
};
return {
const factories = {
Length: {
toAnnotation: Length.toAnnotation,
toMeasurement: csToolsAnnotation =>
@@ -51,7 +60,7 @@ const measurementServiceMappingsFactory = (
),
matchingCriteria: [
{
valueType: measurementService.VALUE_TYPES.POLYLINE,
valueType: MeasurementService.VALUE_TYPES.POLYLINE,
points: 2,
},
],
@@ -69,15 +78,16 @@ const measurementServiceMappingsFactory = (
// 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,
},
],
},
EllipticalROI: {
toAnnotation: EllipticalROI.toAnnotation,
toMeasurement: csToolsAnnotation =>
@@ -89,10 +99,43 @@ const measurementServiceMappingsFactory = (
),
matchingCriteria: [
{
valueType: measurementService.VALUE_TYPES.ELLIPSE,
valueType: MeasurementService.VALUE_TYPES.ELLIPSE,
},
],
},
RectangleROI: {
toAnnotation: RectangleROI.toAnnotation,
toMeasurement: csToolsAnnotation =>
RectangleROI.toMeasurement(
csToolsAnnotation,
displaySetService,
cornerstoneViewportService,
_getValueTypeFromToolType
),
matchingCriteria: [
{
valueType: MeasurementService.VALUE_TYPES.POLYLINE,
},
],
},
PlanarFreehandROI: {
toAnnotation: PlanarFreehandROI.toAnnotation,
toMeasurement: csToolsAnnotation =>
PlanarFreehandROI.toMeasurement(
csToolsAnnotation,
displaySetService,
cornerstoneViewportService,
_getValueTypeFromToolType
),
matchingCriteria: [
{
valueType: MeasurementService.VALUE_TYPES.POLYLINE,
},
],
},
ArrowAnnotate: {
toAnnotation: ArrowAnnotate.toAnnotation,
toMeasurement: csToolsAnnotation =>
@@ -104,12 +147,46 @@ const measurementServiceMappingsFactory = (
),
matchingCriteria: [
{
valueType: measurementService.VALUE_TYPES.POINT,
valueType: MeasurementService.VALUE_TYPES.POINT,
points: 1,
},
],
},
CobbAngle: {
toAnnotation: CobbAngle.toAnnotation,
toMeasurement: csToolsAnnotation =>
CobbAngle.toMeasurement(
csToolsAnnotation,
displaySetService,
cornerstoneViewportService,
_getValueTypeFromToolType
),
matchingCriteria: [
{
valueType: MeasurementService.VALUE_TYPES.ANGLE,
},
],
},
Angle: {
toAnnotation: Angle.toAnnotation,
toMeasurement: csToolsAnnotation =>
Angle.toMeasurement(
csToolsAnnotation,
displaySetService,
cornerstoneViewportService,
_getValueTypeFromToolType
),
matchingCriteria: [
{
valueType: MeasurementService.VALUE_TYPES.ANGLE,
},
],
},
};
return factories;
};
export default measurementServiceMappingsFactory;
@@ -0,0 +1,17 @@
import getHandlesFromPoints from './getHandlesFromPoints';
import {
isAnnotationSelected,
setAnnotationSelected,
getFirstAnnotationSelected,
} from './selection';
import getModalityUnit from './getModalityUnit';
import getSOPInstanceAttributes from './getSOPInstanceAttributes';
export {
getModalityUnit,
getHandlesFromPoints,
getSOPInstanceAttributes,
isAnnotationSelected,
setAnnotationSelected,
getFirstAnnotationSelected,
};
@@ -0,0 +1,44 @@
import { annotation as cs3dToolAnnotationUtils } from '@cornerstonejs/tools';
/**
* Check whether an annotation from imaging library is selected or not.
* @param {string} annotationUID uid of imaging library annotation
* @returns boolean
*/
function isAnnotationSelected(annotationUID: string): boolean {
return cs3dToolAnnotationUtils.selection.isAnnotationSelected(annotationUID);
}
/**
* Change an annotation from imaging library's selected property.
* @param annotationUID - uid of imaging library annotation
* @param selected - new value for selected
*/
function setAnnotationSelected(annotationUID: string, selected: boolean): void {
const isCurrentSelected = isAnnotationSelected(annotationUID);
// branch cut, avoid invoking imaging library unnecessarily.
if (isCurrentSelected !== selected) {
cs3dToolAnnotationUtils.selection.setAnnotationSelected(
annotationUID,
selected
);
}
}
function getFirstAnnotationSelected(element) {
const [selectedAnnotationUID] =
cs3dToolAnnotationUtils.selection.getAnnotationsSelected() || [];
if (selectedAnnotationUID) {
return cs3dToolAnnotationUtils.state.getAnnotation(
selectedAnnotationUID,
element
);
}
}
export {
isAnnotationSelected,
setAnnotationSelected,
getFirstAnnotationSelected,
};