import { annotation } from '@cornerstonejs/tools'; import SUPPORTED_TOOLS from './constants/supportedTools'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; import { getDisplayUnit } from './utils'; const Bidirectional = { 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, textBox } = data.handles; const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, textBox, 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, referencedSeriesInstanceUID } = 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 { length, width, unit } = targetStats; annotations.push({ SeriesInstanceUID, SOPInstanceUID, SeriesNumber, frameNumber, unit, length, width, }); }); 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:Bidirectional'); mappedAnnotations.forEach(annotation => { const { length, width, unit } = annotation; columns.push(`Length`, `Width`, 'Unit'); values.push(length, width, unit); }); 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 { length, width, unit, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; const roundedLength = utils.roundNumber(length, 2); const roundedWidth = utils.roundNumber(width, 2); 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}` : ''; displayText.push( `L: ${roundedLength} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})` ); displayText.push(`W: ${roundedWidth} ${getDisplayUnit(unit)}`); return displayText; } export default Bidirectional;