This change makes the OHIF side consistent with the CS3D user calibration settings, and will correctly display px and rounding values consistent with the CS3D display.
213 lines
5.6 KiB
TypeScript
213 lines
5.6 KiB
TypeScript
import SUPPORTED_TOOLS from './constants/supportedTools';
|
|
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
|
import { utils } from '@ohif/core';
|
|
|
|
const EllipticalROI = {
|
|
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, areaUnit, modalityUnit } = targetStats;
|
|
|
|
annotations.push({
|
|
SeriesInstanceUID,
|
|
SOPInstanceUID,
|
|
SeriesNumber,
|
|
frameNumber,
|
|
Modality,
|
|
unit: modalityUnit,
|
|
areaUnit,
|
|
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, areaUnit } = annotation;
|
|
|
|
if (!mean || !unit || !max || !area) {
|
|
return;
|
|
}
|
|
|
|
columns.push(
|
|
`max (${unit})`,
|
|
`mean (${unit})`,
|
|
`std (${unit})`,
|
|
'Area',
|
|
'Unit'
|
|
);
|
|
values.push(max, mean, stdDev, area, areaUnit);
|
|
});
|
|
|
|
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, areaUnit } = 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}` : '';
|
|
|
|
const roundedArea = utils.roundNumber(area, 2);
|
|
displayText.push(`${roundedArea} ${areaUnit}`);
|
|
|
|
// 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 EllipticalROI;
|