185 lines
6.3 KiB
TypeScript
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;
|
|
}
|