Files
ohif-viewer/extensions/dicom-microscopy/src/utils/loadSR.ts
2025-05-27 11:03:57 +07:00

185 lines
6.3 KiB
TypeScript

import dcmjs from 'dcmjs';
import DCM_CODE_VALUES from './dcmCodeValues';
import toArray from './toArray';
const MeasurementReport = dcmjs.adapters.DICOMMicroscopyViewer.MeasurementReport;
// Define as async so that it returns a promise, expected by the ViewportGrid
export default async function loadSR(
microscopyService,
microscopySRDisplaySet,
referencedDisplaySet
) {
const naturalizedDataset = microscopySRDisplaySet.metadata;
const { StudyInstanceUID, FrameOfReferenceUID } = referencedDisplaySet;
const managedViewers = microscopyService.getManagedViewersForStudy(StudyInstanceUID);
if (!managedViewers || !managedViewers.length) {
return;
}
microscopySRDisplaySet.isLoaded = true;
const { rois, labels } = await _getROIsFromToolState(microscopyService, naturalizedDataset, FrameOfReferenceUID);
const managedViewer = managedViewers[0];
for (let i = 0; i < rois.length; i++) {
// NOTE: When saving Microscopy SR, we are attaching identifier property
// to each ROI, and when read for display, it is coming in as "TEXT"
// evaluation.
// As the Dicom Microscopy Viewer will override styles for "Text" evaluations
// to hide all other geometries, we are going to manually remove that
// evaluation item.
const roi = rois[i];
const roiSymbols = Object.getOwnPropertySymbols(roi);
const _properties = roiSymbols.find(s => s.description === 'properties');
const properties = roi[_properties];
properties['evaluations'] = [];
managedViewer.addRoiGraphicWithLabel(roi, labels[i]);
}
}
async function _getROIsFromToolState(microscopyService, naturalizedDataset, FrameOfReferenceUID) {
const toolState = MeasurementReport.generateToolState(naturalizedDataset);
const tools = Object.getOwnPropertyNames(toolState);
// Does a dynamic import to prevent webpack from rebuilding the library
const DICOMMicroscopyViewer = await microscopyService.importDicomMicroscopyViewer();
const measurementGroupContentItems = _getMeasurementGroups(naturalizedDataset);
const rois = [];
const labels = [];
tools.forEach(t => {
const toolSpecificToolState = toolState[t];
let scoord3d;
const capsToolType = t.toUpperCase();
const measurementGroupContentItemsForTool = measurementGroupContentItems.filter(mg => {
const imageRegionContentItem = toArray(mg.ContentSequence).find(
ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.IMAGE_REGION
);
return imageRegionContentItem.GraphicType === capsToolType;
});
toolSpecificToolState.forEach((coordinates, index) => {
const properties = {};
const options = {
coordinates,
frameOfReferenceUID: FrameOfReferenceUID,
};
if (t === 'Polygon') {
scoord3d = new DICOMMicroscopyViewer.scoord3d.Polygon(options);
} else if (t === 'Polyline') {
scoord3d = new DICOMMicroscopyViewer.scoord3d.Polyline(options);
} else if (t === 'Point') {
scoord3d = new DICOMMicroscopyViewer.scoord3d.Point(options);
} else if (t === 'Ellipse') {
scoord3d = new DICOMMicroscopyViewer.scoord3d.Ellipse(options);
} else {
throw new Error('Unsupported tool type');
}
const measurementGroup = measurementGroupContentItemsForTool[index];
const findingGroup = toArray(measurementGroup.ContentSequence).find(
ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.FINDING
);
const trackingGroup = toArray(measurementGroup.ContentSequence).find(
ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.TRACKING_UNIQUE_IDENTIFIER
);
/**
* Extract presentation state from tracking identifier.
* Currently is stored in SR but should be stored in its tags.
*/
if (trackingGroup) {
const regExp = /\(([^)]+)\)/;
const matches = regExp.exec(trackingGroup.TextValue);
if (matches && matches[1]) {
properties.presentationState = JSON.parse(matches[1]);
properties.marker = properties.presentationState.marker;
}
}
let measurements = toArray(measurementGroup.ContentSequence).filter(ci =>
[
DCM_CODE_VALUES.LENGTH,
DCM_CODE_VALUES.AREA,
DCM_CODE_VALUES.SHORT_AXIS,
DCM_CODE_VALUES.LONG_AXIS,
DCM_CODE_VALUES.ELLIPSE_AREA,
].includes(ci.ConceptNameCodeSequence.CodeValue)
);
let evaluations = toArray(measurementGroup.ContentSequence).filter(ci =>
[DCM_CODE_VALUES.TRACKING_UNIQUE_IDENTIFIER].includes(ci.ConceptNameCodeSequence.CodeValue)
);
/**
* TODO: Resolve bug in DCMJS.
* ConceptNameCodeSequence should be a sequence with only one item.
*/
evaluations = evaluations.map(evaluation => {
const e = { ...evaluation };
e.ConceptNameCodeSequence = toArray(e.ConceptNameCodeSequence);
return e;
});
/**
* TODO: Resolve bug in DCMJS.
* ConceptNameCodeSequence should be a sequence with only one item.
*/
measurements = measurements.map(measurement => {
const m = { ...measurement };
m.ConceptNameCodeSequence = toArray(m.ConceptNameCodeSequence);
return m;
});
if (measurements && measurements.length) {
properties.measurements = measurements;
console.log('[SR] retrieving measurements...', measurements);
}
if (evaluations && evaluations.length) {
properties.evaluations = evaluations;
console.log('[SR] retrieving evaluations...', evaluations);
}
const roi = new DICOMMicroscopyViewer.roi.ROI({ scoord3d, properties });
rois.push(roi);
if (findingGroup) {
labels.push(findingGroup.ConceptCodeSequence.CodeValue);
} else {
labels.push('');
}
});
});
return { rois, labels };
}
function _getMeasurementGroups(naturalizedDataset) {
const { ContentSequence } = naturalizedDataset;
const imagingMeasurementsContentItem = ContentSequence.find(
ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.IMAGING_MEASUREMENTS
);
const measurementGroupContentItems = toArray(
imagingMeasurementsContentItem.ContentSequence
).filter(ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.MEASUREMENT_GROUP);
return measurementGroupContentItems;
}