fix(cornerstone-dicom-sr): Freehand SR hydration support (#3996)

Co-authored-by: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com>
Co-authored-by: IbrahimCSAE <ibrahim.mdev@gmail.com>
This commit is contained in:
authored and GitHub committed 2024-04-05 05:42:43 -04:00
1 parent 6e5892d202
commit 5645ac1b27
21 files changed
+366 -237

No files matched your search

@@ -357,6 +357,10 @@ const connectMeasurementServiceToTools = (
imageId = dataSource.getImageIdsForInstance({ instance });
}
/**
* This annotation is used by the cornerstone viewport.
* This is not the read-only annotation rendered by the SR viewport.
*/
const annotationManager = annotation.state.getAnnotationManager();
annotationManager.addAnnotation({
annotationUID: measurement.uid,
@@ -369,11 +373,16 @@ const connectMeasurementServiceToTools = (
referencedImageId: imageId,
},
data: {
/**
* Don't remove this destructuring of data here.
* This is used to pass annotation specific data forward e.g. contour
*/
...(data.annotation.data || {}),
text: data.annotation.data.text,
handles: { ...data.annotation.data.handles },
cachedStats: { ...data.annotation.data.cachedStats },
label: data.annotation.data.label,
frameNumber: frameNumber,
frameNumber,
},
});
}
@@ -1,14 +1,22 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { getDisplayUnit } from './utils';
import { utils } from '@ohif/core';
/**
* Represents a mapping utility for Planar Freehand ROI measurements.
*/
const PlanarFreehandROI = {
toAnnotation: measurement => {},
/**
* Maps cornerstone annotation event data to measurement service format.
*
* @param {Object} cornerstone Cornerstone event data
* @return {Measurement} Measurement instance
* @param {Object} csToolsEventDetail Cornerstone event data
* @param {DisplaySetService} DisplaySetService Service for managing display sets
* @param {CornerstoneViewportService} CornerstoneViewportService Service for managing viewports
* @param {Function} getValueTypeFromToolType Function to get value type from tool type
* @returns {Measurement} Measurement instance
*/
toMeasurement: (
csToolsEventDetail,
@@ -16,7 +24,7 @@ const PlanarFreehandROI = {
CornerstoneViewportService,
getValueTypeFromToolType
) => {
const { annotation, viewportId } = csToolsEventDetail;
const { annotation } = csToolsEventDetail;
const { metadata, data, annotationUID } = annotation;
if (!metadata || !data) {
@@ -26,19 +34,14 @@ const PlanarFreehandROI = {
const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
const validToolType = SUPPORTED_TOOLS.includes(toolName);
if (!validToolType) {
throw new Error('Tool not supported');
throw new Error(`Tool ${toolName} not supported`);
}
const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes(
referencedImageId,
CornerstoneViewportService,
viewportId
);
const { SOPInstanceUID, SeriesInstanceUID, frameNumber, StudyInstanceUID } =
getSOPInstanceAttributes(referencedImageId);
let displaySet;
if (SOPInstanceUID) {
displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
@@ -48,92 +51,151 @@ const PlanarFreehandROI = {
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
}
const { points, textBox } = data.handles;
const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService);
const displayText = getDisplayText(mappedAnnotations);
const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID);
return {
uid: annotationUID,
SOPInstanceUID,
FrameOfReferenceUID,
points,
textBox,
points: data.contour.polyline,
textBox: data.handles.textBox,
metadata,
frameNumber,
referenceSeriesUID: SeriesInstanceUID,
referenceStudyUID: StudyInstanceUID,
toolName: metadata.toolName,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
label: data.label,
displayText: displayText,
data: { ...data, ...data.cachedStats },
displayText: getDisplayText(annotation, displaySet),
data: data.cachedStats,
type: getValueTypeFromToolType(toolName),
getReport,
getReport: () => getColumnValueReport(annotation),
};
},
};
/**
* 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).
* This function is used to convert the measurement data to a
* format that is suitable for 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.
*
* @param {object} annotation
* @returns {object} Report's content from this tool
*/
function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
function getColumnValueReport(annotation) {
const columns = [];
const values = [];
return {
columns,
values,
};
/** Add type */
columns.push('AnnotationType');
values.push('Cornerstone:PlanarFreehandROI');
/** Add cachedStats */
const { metadata, data } = annotation;
const { mean, stdDev, max, area, unit, areaUnit, perimeter } =
data.cachedStats[`imageId:${metadata.referencedImageId}`];
columns.push(`Maximum`, `Mean`, `Std Dev`, 'Pixel Unit', `Area`, 'Unit', 'Perimeter');
values.push(max, mean, stdDev, unit, area, areaUnit, perimeter);
/** Add FOR */
if (metadata.FrameOfReferenceUID) {
columns.push('FrameOfReferenceUID');
values.push(metadata.FrameOfReferenceUID);
}
/** Add points */
if (data.contour.polyline) {
/**
* 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
*/
columns.push('points');
values.push(data.contour.polyline.map(p => p.join(' ')).join(';'));
}
return { columns, values };
}
function getDisplayText(mappedAnnotations) {
return '';
/**
* Retrieves the display text for an annotation in a display set.
*
* @param {Object} annotation - The annotation object.
* @param {Object} displaySet - The display set object.
* @returns {string[]} - An array of display text.
*/
function getDisplayText(annotation, displaySet) {
const { metadata, data } = annotation;
if (!data.cachedStats || !data.cachedStats[`imageId:${metadata.referencedImageId}`]) {
return [];
}
const { mean, stdDev, max, area, modalityUnit, areaUnit, perimeter } =
data.cachedStats[`imageId:${metadata.referencedImageId}`];
const { SOPInstanceUID, frameNumber } = getSOPInstanceAttributes(metadata.referencedImageId);
const displayText = [];
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 { SeriesNumber } = displaySet;
if (SeriesNumber) {
displayText.push(`S: ${SeriesNumber}${instanceText}${frameText}`);
}
if (area) {
/**
* Add Area
* Area sometimes becomes undefined if `preventHandleOutsideImage` is off
*/
const roundedArea = utils.roundNumber(area || 0, 2);
displayText.push(`Area: ${roundedArea} ${getDisplayUnit(areaUnit)}`);
}
if (mean) {
if (Array.isArray(mean)) {
const meanValues = mean.map(value => utils.roundNumber(value));
displayText.push(`Mean: ${meanValues.join(', ')} ${modalityUnit}`);
} else {
displayText.push(`Mean: ${utils.roundNumber(mean)} ${modalityUnit}`);
}
}
if (max) {
if (Array.isArray(max)) {
const maxValues = max.map(value => utils.roundNumber(value, 2));
displayText.push(`Max: ${maxValues.join(', ')} ${modalityUnit}`);
} else {
displayText.push(`Max: ${utils.roundNumber(max, 2)} ${modalityUnit}`);
}
}
if (stdDev) {
if (Array.isArray(stdDev)) {
const stdDevValues = stdDev.map(value => utils.roundNumber(value));
displayText.push(`Std Dev: ${stdDevValues.join(', ')} ${modalityUnit}`);
} else {
displayText.push(`Std Dev: ${utils.roundNumber(stdDev)} ${modalityUnit}`);
}
}
if (perimeter) {
if (Array.isArray(perimeter)) {
const perimeterValues = perimeter.map(value => utils.roundNumber(value));
displayText.push(`Perimeter: ${perimeterValues.join(', ')} ${modalityUnit}`);
} else {
displayText.push(`Perimeter: ${utils.roundNumber(perimeter)} ${modalityUnit}`);
}
}
return displayText;
}
export default PlanarFreehandROI;