WIP Measurements service.
This commit is contained in:
parent
67508f6667
commit
7b7b63de93
@ -90,6 +90,7 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
|
|
||||||
/* Add extension tools configuration here. */
|
/* Add extension tools configuration here. */
|
||||||
const internalToolsConfig = {
|
const internalToolsConfig = {
|
||||||
|
/* TODO ArrowAnnotate input
|
||||||
ArrowAnnotate: {
|
ArrowAnnotate: {
|
||||||
configuration: {
|
configuration: {
|
||||||
getTextCallback: (callback, eventDetails) =>
|
getTextCallback: (callback, eventDetails) =>
|
||||||
@ -98,6 +99,7 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
callInputDialog(data, eventDetails, callback),
|
callInputDialog(data, eventDetails, callback),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Abstract tools configuration using extension configuration. */
|
/* Abstract tools configuration using extension configuration. */
|
||||||
@ -166,27 +168,48 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
|
|
||||||
const _initMeasurementService = measurementService => {
|
const _initMeasurementService = measurementService => {
|
||||||
/* Initialization */
|
/* Initialization */
|
||||||
const { toAnnotation, toMeasurement } = measurementServiceMappingsFactory(
|
const {
|
||||||
measurementService
|
Length,
|
||||||
);
|
Bidirectional,
|
||||||
|
EllipticalRoi,
|
||||||
|
ArrowAnnotate,
|
||||||
|
} = measurementServiceMappingsFactory(measurementService);
|
||||||
const csToolsVer4MeasurementSource = measurementService.createSource(
|
const csToolsVer4MeasurementSource = measurementService.createSource(
|
||||||
'CornerstoneTools',
|
'CornerstoneTools',
|
||||||
'4'
|
'4'
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Matching Criterias */
|
|
||||||
const matchingCriteria = {
|
|
||||||
valueType: measurementService.VALUE_TYPES.POLYLINE,
|
|
||||||
points: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Mappings */
|
/* Mappings */
|
||||||
measurementService.addMapping(
|
measurementService.addMapping(
|
||||||
csToolsVer4MeasurementSource,
|
csToolsVer4MeasurementSource,
|
||||||
'Length',
|
'Length',
|
||||||
matchingCriteria,
|
Length.matchingCriteria,
|
||||||
toAnnotation,
|
Length.toAnnotation,
|
||||||
toMeasurement
|
Length.toMeasurement
|
||||||
|
);
|
||||||
|
|
||||||
|
measurementService.addMapping(
|
||||||
|
csToolsVer4MeasurementSource,
|
||||||
|
'Bidirectional',
|
||||||
|
Bidirectional.matchingCriteria,
|
||||||
|
Bidirectional.toAnnotation,
|
||||||
|
Bidirectional.toMeasurement
|
||||||
|
);
|
||||||
|
|
||||||
|
measurementService.addMapping(
|
||||||
|
csToolsVer4MeasurementSource,
|
||||||
|
'EllipticalRoi',
|
||||||
|
EllipticalRoi.matchingCriteria,
|
||||||
|
EllipticalRoi.toAnnotation,
|
||||||
|
EllipticalRoi.toMeasurement
|
||||||
|
);
|
||||||
|
|
||||||
|
measurementService.addMapping(
|
||||||
|
csToolsVer4MeasurementSource,
|
||||||
|
'ArrowAnnotate',
|
||||||
|
ArrowAnnotate.matchingCriteria,
|
||||||
|
ArrowAnnotate.toAnnotation,
|
||||||
|
ArrowAnnotate.toMeasurement
|
||||||
);
|
);
|
||||||
|
|
||||||
return csToolsVer4MeasurementSource;
|
return csToolsVer4MeasurementSource;
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||||
|
import getHandlesFromPoints from './utils/getHandlesFromPoints';
|
||||||
|
import getPointsFromHandles from './utils/getPointsFromHandles';
|
||||||
|
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||||
|
|
||||||
|
const ArrowAnnotate = {
|
||||||
|
toAnnotation: (measurement, definition) => {
|
||||||
|
// TODO -> Implement when this is needed.
|
||||||
|
},
|
||||||
|
toMeasurement: (csToolsAnnotation, getValueTypeFromToolType) => {
|
||||||
|
const { element, measurementData } = csToolsAnnotation;
|
||||||
|
const tool =
|
||||||
|
csToolsAnnotation.toolType ||
|
||||||
|
csToolsAnnotation.toolName ||
|
||||||
|
measurementData.toolType;
|
||||||
|
|
||||||
|
const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName);
|
||||||
|
|
||||||
|
if (!validToolType(tool)) {
|
||||||
|
throw new Error('Tool not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
} = getSOPInstanceAttributes(element);
|
||||||
|
|
||||||
|
const points = [];
|
||||||
|
points.push(measurementData.handles);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: measurementData._measurementServiceId,
|
||||||
|
SOPInstanceUID: SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
|
referenceStudyUID: StudyInstanceUID,
|
||||||
|
label: measurementData.text,
|
||||||
|
description: measurementData.description,
|
||||||
|
unit: measurementData.unit,
|
||||||
|
text: measurementData.text,
|
||||||
|
type: getValueTypeFromToolType(tool),
|
||||||
|
points: getPointsFromHandles(measurementData.handles),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ArrowAnnotate;
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||||
|
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||||
|
|
||||||
|
const Bidirectional = {
|
||||||
|
toAnnotation: (measurement, definition) => {
|
||||||
|
// TODO -> Implement when this is needed.
|
||||||
|
},
|
||||||
|
toMeasurement: (csToolsAnnotation, getValueTypeFromToolType) => {
|
||||||
|
const { element, measurementData } = csToolsAnnotation;
|
||||||
|
const tool =
|
||||||
|
csToolsAnnotation.toolType ||
|
||||||
|
csToolsAnnotation.toolName ||
|
||||||
|
measurementData.toolType;
|
||||||
|
|
||||||
|
const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName);
|
||||||
|
|
||||||
|
if (!validToolType(tool)) {
|
||||||
|
throw new Error('Tool not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
} = getSOPInstanceAttributes(element);
|
||||||
|
|
||||||
|
const { handles } = measurementData;
|
||||||
|
|
||||||
|
const longAxis = [handles.start, handles.end];
|
||||||
|
const shortAxis = [handles.perpendicularStart, handles.perpendicularEnd];
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: measurementData._measurementServiceId,
|
||||||
|
SOPInstanceUID: SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
|
referenceStudyUID: StudyInstanceUID,
|
||||||
|
label: measurementData.text,
|
||||||
|
description: measurementData.description,
|
||||||
|
unit: measurementData.unit,
|
||||||
|
shortestDiameter: measurementData.shortestDiameter,
|
||||||
|
longestDiameter: measurementData.longestDiameter,
|
||||||
|
type: getValueTypeFromToolType(tool),
|
||||||
|
points: { longAxis, shortAxis },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Bidirectional;
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||||
|
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||||
|
|
||||||
|
const EllipticalRoi = {
|
||||||
|
toAnnotation: (measurement, definition) => {
|
||||||
|
// TODO -> Implement when this is needed.
|
||||||
|
},
|
||||||
|
toMeasurement: (csToolsAnnotation, getValueTypeFromToolType) => {
|
||||||
|
const { element, measurementData } = csToolsAnnotation;
|
||||||
|
const tool =
|
||||||
|
csToolsAnnotation.toolType ||
|
||||||
|
csToolsAnnotation.toolName ||
|
||||||
|
measurementData.toolType;
|
||||||
|
|
||||||
|
const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName);
|
||||||
|
|
||||||
|
if (!validToolType(tool)) {
|
||||||
|
throw new Error('Tool not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
} = getSOPInstanceAttributes(element);
|
||||||
|
|
||||||
|
const { start, end } = measurementData.handles;
|
||||||
|
|
||||||
|
const halfXLength = Math.abs(start.x - end.x) / 2;
|
||||||
|
const halfYLength = Math.abs(start.y - end.y) / 2;
|
||||||
|
|
||||||
|
const points = [];
|
||||||
|
const center = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
||||||
|
|
||||||
|
// To store similar to SR.
|
||||||
|
if (halfXLength > halfYLength) {
|
||||||
|
// X-axis major
|
||||||
|
// Major axis
|
||||||
|
points.push({ x: center.x - halfXLength, y: center.y });
|
||||||
|
points.push({ x: center.x + halfXLength, y: center.y });
|
||||||
|
// Minor axis
|
||||||
|
points.push({ x: center.x, y: center.y - halfYLength });
|
||||||
|
points.push({ x: center.x, y: center.y + halfYLength });
|
||||||
|
} else {
|
||||||
|
// Y-axis major
|
||||||
|
// Major axis
|
||||||
|
points.push({ x: center.x, y: center.y - halfYLength });
|
||||||
|
points.push({ x: center.x, y: center.y + halfYLength });
|
||||||
|
// Minor axis
|
||||||
|
points.push({ x: center.x - halfXLength, y: center.y });
|
||||||
|
points.push({ x: center.x + halfXLength, y: center.y });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: measurementData._measurementServiceId,
|
||||||
|
SOPInstanceUID: SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
|
referenceStudyUID: StudyInstanceUID,
|
||||||
|
label: measurementData.text,
|
||||||
|
description: measurementData.description,
|
||||||
|
unit: measurementData.unit,
|
||||||
|
area:
|
||||||
|
measurementData.cachedStats &&
|
||||||
|
measurementData.cachedStats
|
||||||
|
.area /* TODO: Add concept names instead (descriptor) */,
|
||||||
|
type: getValueTypeFromToolType(tool),
|
||||||
|
points,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EllipticalRoi;
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||||
|
import getHandlesFromPoints from './utils/getHandlesFromPoints';
|
||||||
|
import getPointsFromHandles from './utils/getPointsFromHandles';
|
||||||
|
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||||
|
|
||||||
|
const Length = {
|
||||||
|
toAnnotation: (measurement, definition) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
points,
|
||||||
|
unit,
|
||||||
|
SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
referenceSeriesUID,
|
||||||
|
} = measurement;
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolName: definition,
|
||||||
|
measurementData: {
|
||||||
|
sopInstanceUid: SOPInstanceUID,
|
||||||
|
frameOfReferenceUID: FrameOfReferenceUID,
|
||||||
|
SeriesInstanceUID: referenceSeriesUID,
|
||||||
|
unit,
|
||||||
|
text: label,
|
||||||
|
description,
|
||||||
|
handles: getHandlesFromPoints(points),
|
||||||
|
_measurementServiceId: id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps cornerstone annotation event data to measurement service format.
|
||||||
|
*
|
||||||
|
* @param {Object} cornerstone Cornerstone event data
|
||||||
|
* @return {Measurement} Measurement instance
|
||||||
|
*/
|
||||||
|
toMeasurement: (csToolsAnnotation, getValueTypeFromToolType) => {
|
||||||
|
const { element, measurementData } = csToolsAnnotation;
|
||||||
|
const tool =
|
||||||
|
csToolsAnnotation.toolType ||
|
||||||
|
csToolsAnnotation.toolName ||
|
||||||
|
measurementData.toolType;
|
||||||
|
|
||||||
|
const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName);
|
||||||
|
|
||||||
|
if (!validToolType(tool)) {
|
||||||
|
throw new Error('Tool not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
} = getSOPInstanceAttributes(element);
|
||||||
|
|
||||||
|
const points = [];
|
||||||
|
points.push(measurementData.handles);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: measurementData._measurementServiceId,
|
||||||
|
SOPInstanceUID: SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
|
referenceStudyUID: StudyInstanceUID,
|
||||||
|
label: measurementData.text,
|
||||||
|
description: measurementData.description,
|
||||||
|
unit: measurementData.unit,
|
||||||
|
length: measurementData.length,
|
||||||
|
type: getValueTypeFromToolType(tool),
|
||||||
|
points: getPointsFromHandles(measurementData.handles),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Length;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export default ['Length', 'EllipticalRoi', 'Bidirectional', 'ArrowAnnotate'];
|
||||||
@ -1,11 +1,7 @@
|
|||||||
import cornerstone from 'cornerstone-core';
|
import Length from './Length';
|
||||||
|
import Bidirectional from './Bidirectional';
|
||||||
const SUPPORTED_TOOLS = [
|
import ArrowAnnotate from './ArrowAnnotate';
|
||||||
'Length',
|
import EllipticalRoi from './EllipticalRoi';
|
||||||
'EllipticalRoi',
|
|
||||||
'RectangleRoi',
|
|
||||||
'ArrowAnnotate',
|
|
||||||
];
|
|
||||||
|
|
||||||
const measurementServiceMappingsFactory = measurementService => {
|
const measurementServiceMappingsFactory = measurementService => {
|
||||||
/**
|
/**
|
||||||
@ -15,129 +11,87 @@ const measurementServiceMappingsFactory = measurementService => {
|
|||||||
* @param {string} definition The source definition
|
* @param {string} definition The source definition
|
||||||
* @return {Object} Cornerstone annotation data
|
* @return {Object} Cornerstone annotation data
|
||||||
*/
|
*/
|
||||||
const toAnnotation = (measurement, definition) => {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
label,
|
|
||||||
description,
|
|
||||||
points,
|
|
||||||
unit,
|
|
||||||
SOPInstanceUID,
|
|
||||||
FrameOfReferenceUID,
|
|
||||||
referenceSeriesUID,
|
|
||||||
} = measurement;
|
|
||||||
|
|
||||||
return {
|
|
||||||
toolName: definition,
|
|
||||||
measurementData: {
|
|
||||||
sopInstanceUid: SOPInstanceUID,
|
|
||||||
frameOfReferenceUID: FrameOfReferenceUID,
|
|
||||||
SeriesInstanceUID: referenceSeriesUID,
|
|
||||||
unit,
|
|
||||||
text: label,
|
|
||||||
description,
|
|
||||||
handles: _getHandlesFromPoints(points),
|
|
||||||
_measurementServiceId: id,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps cornerstone annotation event data to measurement service format.
|
|
||||||
*
|
|
||||||
* @param {Object} cornerstone Cornerstone event data
|
|
||||||
* @return {Measurement} Measurement instance
|
|
||||||
*/
|
|
||||||
const toMeasurement = csToolsAnnotation => {
|
|
||||||
const { element, measurementData } = csToolsAnnotation;
|
|
||||||
const tool =
|
|
||||||
csToolsAnnotation.toolType ||
|
|
||||||
csToolsAnnotation.toolName ||
|
|
||||||
measurementData.toolType;
|
|
||||||
|
|
||||||
const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName);
|
|
||||||
|
|
||||||
if (!validToolType(tool)) {
|
|
||||||
throw new Error('Tool not supported');
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
SOPInstanceUID,
|
|
||||||
FrameOfReferenceUID,
|
|
||||||
SeriesInstanceUID,
|
|
||||||
StudyInstanceUID,
|
|
||||||
} = _getAttributes(element);
|
|
||||||
|
|
||||||
const points = [];
|
|
||||||
points.push(measurementData.handles);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: measurementData._measurementServiceId,
|
|
||||||
SOPInstanceUID: SOPInstanceUID,
|
|
||||||
FrameOfReferenceUID,
|
|
||||||
referenceSeriesUID: SeriesInstanceUID,
|
|
||||||
referenceStudyUID: StudyInstanceUID,
|
|
||||||
label: measurementData.text,
|
|
||||||
description: measurementData.description,
|
|
||||||
unit: measurementData.unit,
|
|
||||||
area:
|
|
||||||
measurementData.cachedStats &&
|
|
||||||
measurementData.cachedStats
|
|
||||||
.area /* TODO: Add concept names instead (descriptor) */,
|
|
||||||
type: _getValueTypeFromToolType(tool),
|
|
||||||
points: _getPointsFromHandles(measurementData.handles),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const _getAttributes = element => {
|
|
||||||
const enabledElement = cornerstone.getEnabledElement(element);
|
|
||||||
const imageId = enabledElement.image.imageId;
|
|
||||||
const instance = cornerstone.metaData.get('instance', imageId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
SOPInstanceUID: instance.SOPInstanceUID,
|
|
||||||
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
|
||||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
|
||||||
StudyInstanceUID: instance.StudyInstanceUID,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const _getValueTypeFromToolType = toolType => {
|
const _getValueTypeFromToolType = toolType => {
|
||||||
const { POLYLINE, ELLIPSE, POINT } = measurementService.VALUE_TYPES;
|
const {
|
||||||
|
POLYLINE,
|
||||||
|
ELLIPSE,
|
||||||
|
POINT,
|
||||||
|
BIDIRECTIONAL,
|
||||||
|
} = measurementService.VALUE_TYPES;
|
||||||
|
|
||||||
/* TODO: Relocate static 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.
|
||||||
|
// You can't define a bidirectional tool as a single type..
|
||||||
const TOOL_TYPE_TO_VALUE_TYPE = {
|
const TOOL_TYPE_TO_VALUE_TYPE = {
|
||||||
Length: POLYLINE,
|
Length: POLYLINE,
|
||||||
EllipticalRoi: ELLIPSE,
|
EllipticalRoi: ELLIPSE,
|
||||||
RectangleRoi: POLYLINE,
|
Bidirectional: BIDIRECTIONAL,
|
||||||
ArrowAnnotate: POINT,
|
ArrowAnnotate: POINT,
|
||||||
};
|
};
|
||||||
|
|
||||||
return TOOL_TYPE_TO_VALUE_TYPE[toolType];
|
return TOOL_TYPE_TO_VALUE_TYPE[toolType];
|
||||||
};
|
};
|
||||||
|
|
||||||
const _getPointsFromHandles = handles => {
|
|
||||||
let points = [];
|
|
||||||
Object.keys(handles).map(handle => {
|
|
||||||
if (['start', 'end'].includes(handle)) {
|
|
||||||
let point = {};
|
|
||||||
if (handles[handle].x) point.x = handles[handle].x;
|
|
||||||
if (handles[handle].y) point.y = handles[handle].y;
|
|
||||||
points.push(point);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return points;
|
|
||||||
};
|
|
||||||
|
|
||||||
const _getHandlesFromPoints = points => {
|
|
||||||
return points
|
|
||||||
.map((p, i) => (i % 10 === 0 ? { start: p } : { end: p }))
|
|
||||||
.reduce((obj, item) => Object.assign(obj, { ...item }), {});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toAnnotation,
|
Length: {
|
||||||
toMeasurement,
|
toAnnotation: Length.toAnnotation,
|
||||||
|
toMeasurement: csToolsAnnotation =>
|
||||||
|
Length.toMeasurement(csToolsAnnotation, _getValueTypeFromToolType),
|
||||||
|
matchingCriteria: [
|
||||||
|
{
|
||||||
|
valueType: measurementService.VALUE_TYPES.POLYLINE,
|
||||||
|
points: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Bidirectional: {
|
||||||
|
toAnnotation: Bidirectional.toAnnotation,
|
||||||
|
toMeasurement: csToolsAnnotation =>
|
||||||
|
Bidirectional.toMeasurement(
|
||||||
|
csToolsAnnotation,
|
||||||
|
_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,
|
||||||
|
points: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
valueType: measurementService.VALUE_TYPES.POLYLINE,
|
||||||
|
points: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ArrowAnnotate: {
|
||||||
|
toAnnotation: ArrowAnnotate.toAnnotation,
|
||||||
|
toMeasurement: csToolsAnnotation =>
|
||||||
|
ArrowAnnotate.toMeasurement(
|
||||||
|
csToolsAnnotation,
|
||||||
|
_getValueTypeFromToolType
|
||||||
|
),
|
||||||
|
matchingCriteria: [
|
||||||
|
{
|
||||||
|
valueType: measurementService.VALUE_TYPES.POINT,
|
||||||
|
points: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
EllipticalRoi: {
|
||||||
|
toAnnotation: EllipticalRoi.toAnnotation,
|
||||||
|
toMeasurement: csToolsAnnotation =>
|
||||||
|
EllipticalRoi.toMeasurement(
|
||||||
|
csToolsAnnotation,
|
||||||
|
_getValueTypeFromToolType
|
||||||
|
),
|
||||||
|
matchingCriteria: [
|
||||||
|
{
|
||||||
|
valueType: measurementService.VALUE_TYPES.ELLIPSE,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
export default function getHandlesFromPoints(points) {
|
||||||
|
return points
|
||||||
|
.map((p, i) => (i % 10 === 0 ? { start: p } : { end: p }))
|
||||||
|
.reduce((obj, item) => Object.assign(obj, { ...item }), {});
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
export default function getPointsFromHandles(handles) {
|
||||||
|
let points = [];
|
||||||
|
Object.keys(handles).map(handle => {
|
||||||
|
if (['start', 'end'].includes(handle)) {
|
||||||
|
let point = {};
|
||||||
|
if (handles[handle].x) point.x = handles[handle].x;
|
||||||
|
if (handles[handle].y) point.y = handles[handle].y;
|
||||||
|
points.push(point);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return points;
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
|
||||||
|
export default function getSOPInstanceAttributes(element) {
|
||||||
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
const imageId = enabledElement.image.imageId;
|
||||||
|
const instance = cornerstone.metaData.get('instance', imageId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
SOPInstanceUID: instance.SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||||
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
|
StudyInstanceUID: instance.StudyInstanceUID,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -42,7 +42,7 @@ function PanelMeasurementTableTracking({ servicesManager, commandsManager }) {
|
|||||||
trackedSeries.includes(m.referenceSeriesUID)
|
trackedSeries.includes(m.referenceSeriesUID)
|
||||||
);
|
);
|
||||||
const mappedMeasurements = filteredMeasurements.map((m, index) =>
|
const mappedMeasurements = filteredMeasurements.map((m, index) =>
|
||||||
_mapMeasurementToDisplay(m, index)
|
_mapMeasurementToDisplay(m, index, MeasurementService.VALUE_TYPES)
|
||||||
);
|
);
|
||||||
setDisplayMeasurements(mappedMeasurements);
|
setDisplayMeasurements(mappedMeasurements);
|
||||||
// eslint-ignore-next-line
|
// eslint-ignore-next-line
|
||||||
@ -130,7 +130,7 @@ function PanelMeasurementTableTracking({ servicesManager, commandsManager }) {
|
|||||||
PanelMeasurementTableTracking.propTypes = {};
|
PanelMeasurementTableTracking.propTypes = {};
|
||||||
|
|
||||||
// TODO: This could be a MeasurementService mapper
|
// TODO: This could be a MeasurementService mapper
|
||||||
function _mapMeasurementToDisplay(measurement, index) {
|
function _mapMeasurementToDisplay(measurement, index, types) {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
@ -153,12 +153,14 @@ function _mapMeasurementToDisplay(measurement, index) {
|
|||||||
return {
|
return {
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
label: '(empty)', // 'Label short description',
|
label: '(empty)', // 'Label short description',
|
||||||
displayText: _getDisplayText(
|
displayText:
|
||||||
measurement.points,
|
_getDisplayText(
|
||||||
PixelSpacing,
|
measurement,
|
||||||
SeriesNumber,
|
PixelSpacing,
|
||||||
InstanceNumber
|
SeriesNumber,
|
||||||
),
|
InstanceNumber,
|
||||||
|
types
|
||||||
|
) || [],
|
||||||
// TODO: handle one layer down
|
// TODO: handle one layer down
|
||||||
isActive: false, // activeMeasurementItem === i + 1,
|
isActive: false, // activeMeasurementItem === i + 1,
|
||||||
};
|
};
|
||||||
@ -169,7 +171,13 @@ function _mapMeasurementToDisplay(measurement, index) {
|
|||||||
* @param {*} points
|
* @param {*} points
|
||||||
* @param {*} pixelSpacing
|
* @param {*} pixelSpacing
|
||||||
*/
|
*/
|
||||||
function _getDisplayText(points, pixelSpacing, seriesNumber, instanceNumber) {
|
function _getDisplayText(
|
||||||
|
measurement,
|
||||||
|
pixelSpacing,
|
||||||
|
seriesNumber,
|
||||||
|
instanceNumber,
|
||||||
|
types
|
||||||
|
) {
|
||||||
// TODO: determination of shape influences text
|
// TODO: determination of shape influences text
|
||||||
// Length: 'xx.x unit (S:x, I:x)'
|
// Length: 'xx.x unit (S:x, I:x)'
|
||||||
// Rectangle: 'xx.x x xx.x unit (S:x, I:x)',
|
// Rectangle: 'xx.x x xx.x unit (S:x, I:x)',
|
||||||
@ -177,6 +185,8 @@ function _getDisplayText(points, pixelSpacing, seriesNumber, instanceNumber) {
|
|||||||
// Bidirectional?
|
// Bidirectional?
|
||||||
// Freehand?
|
// Freehand?
|
||||||
|
|
||||||
|
const { type, points } = measurement;
|
||||||
|
|
||||||
const hasPixelSpacing =
|
const hasPixelSpacing =
|
||||||
pixelSpacing !== undefined &&
|
pixelSpacing !== undefined &&
|
||||||
Array.isArray(pixelSpacing) &&
|
Array.isArray(pixelSpacing) &&
|
||||||
@ -186,13 +196,37 @@ function _getDisplayText(points, pixelSpacing, seriesNumber, instanceNumber) {
|
|||||||
: [1, 1];
|
: [1, 1];
|
||||||
const unit = hasPixelSpacing ? 'mm' : 'px';
|
const unit = hasPixelSpacing ? 'mm' : 'px';
|
||||||
|
|
||||||
const { x: x1, y: y1 } = points[0];
|
switch (type) {
|
||||||
const { x: x2, y: y2 } = points[1];
|
case types.POLYLINE:
|
||||||
const dx = (x2 - x1) * colPixelSpacing;
|
const { length } = measurement;
|
||||||
const dy = (y2 - y1) * rowPixelSpacing;
|
|
||||||
const length = _round(Math.sqrt(dx * dx + dy * dy), 1);
|
|
||||||
|
|
||||||
return `${length} ${unit} (S:${seriesNumber}, I:${instanceNumber})`;
|
const roundedLength = _round(length, 1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
`${roundedLength} ${unit} (S:${seriesNumber}, I:${instanceNumber})`,
|
||||||
|
];
|
||||||
|
|
||||||
|
case types.BIDIRECTIONAL:
|
||||||
|
const { shortestDiameter, longestDiameter } = measurement;
|
||||||
|
|
||||||
|
const roundedShortestDiameter = _round(shortestDiameter, 1);
|
||||||
|
const roundedLongestDiameter = _round(longestDiameter, 1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
`l: ${roundedLongestDiameter} ${unit} (S:${seriesNumber}, I:${instanceNumber})`,
|
||||||
|
`s: ${roundedShortestDiameter} ${unit}`,
|
||||||
|
];
|
||||||
|
case types.ELLIPSE:
|
||||||
|
const { area } = measurement;
|
||||||
|
|
||||||
|
const roundedArea = _round(area, 1);
|
||||||
|
return [
|
||||||
|
`${roundedArea} ${unit}2 (S:${seriesNumber}, I:${instanceNumber})`,
|
||||||
|
];
|
||||||
|
case types.POINT:
|
||||||
|
const { text } = measurement;
|
||||||
|
return [`${text} (S:${seriesNumber}, I:${instanceNumber})`];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _round(value, decimals) {
|
function _round(value, decimals) {
|
||||||
|
|||||||
@ -40,6 +40,10 @@ const MEASUREMENT_SCHEMA_KEYS = [
|
|||||||
'type',
|
'type',
|
||||||
'unit',
|
'unit',
|
||||||
'area', // TODO: Add concept names instead (descriptor)
|
'area', // TODO: Add concept names instead (descriptor)
|
||||||
|
'length',
|
||||||
|
'shortestDiameter',
|
||||||
|
'longestDiameter',
|
||||||
|
'text', // NOTE: There is nothing like this in SR.
|
||||||
'points',
|
'points',
|
||||||
'source',
|
'source',
|
||||||
];
|
];
|
||||||
@ -53,6 +57,7 @@ const EVENTS = {
|
|||||||
const VALUE_TYPES = {
|
const VALUE_TYPES = {
|
||||||
POLYLINE: 'value_type::polyline',
|
POLYLINE: 'value_type::polyline',
|
||||||
POINT: 'value_type::point',
|
POINT: 'value_type::point',
|
||||||
|
BIDIRECTIONAL: 'value_type::shortAxisLongAxis', // TODO -> Discuss with Danny. => just using SCOORD values isn't enough here.
|
||||||
ELLIPSE: 'value_type::ellipse',
|
ELLIPSE: 'value_type::ellipse',
|
||||||
MULTIPOINT: 'value_type::multipoint',
|
MULTIPOINT: 'value_type::multipoint',
|
||||||
CIRCLE: 'value_type::circle',
|
CIRCLE: 'value_type::circle',
|
||||||
|
|||||||
@ -14,7 +14,7 @@ const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="overflow-y-auto overflow-x-hidden ohif-scrollbar max-h-112">
|
<div className="overflow-y-auto overflow-x-hidden ohif-scrollbar max-h-112">
|
||||||
{!!data.length &&
|
{!!data.length &&
|
||||||
data.map((measurementItem) => {
|
data.map(measurementItem => {
|
||||||
const { id, label, displayText, isActive } = measurementItem;
|
const { id, label, displayText, isActive } = measurementItem;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -45,9 +45,11 @@ const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => {
|
|||||||
<span className="text-base text-primary-light mb-1">
|
<span className="text-base text-primary-light mb-1">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<span className="pl-2 border-l border-primary-light text-base text-white">
|
{displayText.map(line => (
|
||||||
{displayText}
|
<span className="pl-2 border-l border-primary-light text-base text-white">
|
||||||
</span>
|
{line}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
<Icon
|
<Icon
|
||||||
className={classnames(
|
className={classnames(
|
||||||
'text-white w-4 absolute cursor-pointer transition duration-300',
|
'text-white w-4 absolute cursor-pointer transition duration-300',
|
||||||
@ -61,7 +63,7 @@ const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => {
|
|||||||
right: 4,
|
right: 4,
|
||||||
transform: isActive ? '' : 'translateX(100%)',
|
transform: isActive ? '' : 'translateX(100%)',
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={e => {
|
||||||
// stopPropagation needed to avoid disable the current active item
|
// stopPropagation needed to avoid disable the current active item
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onEdit(id);
|
onEdit(id);
|
||||||
@ -108,7 +110,7 @@ MeasurementTable.propTypes = {
|
|||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
displayText: PropTypes.string,
|
displayText: PropTypes.arrayOf(PropTypes.string),
|
||||||
isActive: PropTypes.bool,
|
isActive: PropTypes.bool,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user