feat: Ultrasound Pleura B-line Annotation Mode & Extension (#5061)

This commit is contained in:
rodrigobasilio2022 authored and GitHub committed 2025-06-24 12:50:48 -04:00
1 parent 9b8eef9474
commit 226f9b0773
46 files changed
+7123 -3660

No files matched your search

@@ -0,0 +1,179 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
import { getIsLocked } from './utils/getIsLocked';
import { getIsVisible } from './utils/getIsVisible';
import { annotation } from '@cornerstonejs/tools';
const UltrasoundPleuraBLine = {
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,
customizationService
) => {
const { annotation } = csToolsEventDetail;
const { metadata, data, annotationUID } = annotation;
const isLocked = getIsLocked(annotationUID);
const isVisible = getIsVisible(annotationUID);
if (!metadata || !data) {
console.warn('UltrasoundPleuraBLine 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);
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, customizationService);
const getReport = () =>
_getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService);
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,
isLocked,
isVisible,
};
},
};
function getMappedAnnotations(annotation, DisplaySetService) {
const { metadata } = annotation;
const { annotationType } = annotation.data;
const { referencedImageId } = metadata;
const annotations = [];
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;
annotations.push({
SeriesInstanceUID,
SOPInstanceUID,
SeriesNumber,
frameNumber,
annotationType,
});
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, customizationService) {
const columns = [];
const values = [];
// Add Type
columns.push('AnnotationType');
values.push('Cornerstone:UltrasoundPleuraBLine');
mappedAnnotations.forEach(annotation => {
const { annotationType } = annotation;
columns.push('AnnotationType');
values.push(annotationType);
});
if (FrameOfReferenceUID) {
columns.push('FrameOfReferenceUID');
values.push(FrameOfReferenceUID);
}
if (points) {
columns.push('points');
values.push(points.map(p => p.join(' ')).join(';'));
}
return {
columns,
values,
};
}
function getDisplayText(mappedAnnotations, displaySet, customizationService) {
const displayText = {
primary: [],
secondary: [],
};
if (!mappedAnnotations || !mappedAnnotations.length) {
return displayText;
}
const { annotationType, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
const instance = displaySet.instances.find(image => image.SOPInstanceUID === SOPInstanceUID);
let InstanceNumber;
if (instance) {
InstanceNumber = instance.InstanceNumber;
}
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
const seriesText = `S: ${SeriesNumber}${instanceText}${frameText}`;
displayText.primary.push(`Annotation : ${annotationType}`);
displayText.secondary.push(seriesText);
return displayText;
}
export default UltrasoundPleuraBLine;
@@ -12,6 +12,7 @@ const supportedTools = [
'SplineROI',
'LivewireContour',
'UltrasoundDirectionalTool',
'UltrasoundPleuraBLineTool',
'SCOORD3DPoint',
'SegmentBidirectional',
];
@@ -13,6 +13,7 @@ import LivewireContour from './LivewireContour';
import Probe from './Probe';
import UltrasoundDirectional from './UltrasoundDirectional';
import SegmentBidirectional from './SegmentBidirectional';
import UltrasoundPleuraBLine from './UltrasoundPleuraBLine';
const measurementServiceMappingsFactory = (
measurementService: MeasurementService,
@@ -296,6 +297,23 @@ const measurementServiceMappingsFactory = (
},
],
},
UltrasoundPleuraBLine: {
toAnnotation: UltrasoundPleuraBLine.toAnnotation,
toMeasurement: csToolsAnnotation =>
UltrasoundPleuraBLine.toMeasurement(
csToolsAnnotation,
displaySetService,
cornerstoneViewportService,
_getValueTypeFromToolType,
customizationService
),
matchingCriteria: [
{
valueType: MeasurementService.VALUE_TYPES.POLYLINE,
points: 2,
},
],
},
};
return factories;