feat(measurement-table) Display support for measurements from DICOM Structured Reports (#225)

This commit is contained in:
Gustavo André Lelis authored and Erik Ziegler committed 2018-07-20 12:40:09 +02:00
1 parent e9832f62f7
commit d94f0eec16
64 files changed
+1797 -78

No files matched your search

@@ -0,0 +1,95 @@
import { OHIF } from 'meteor/ohif:core';
import { cornerstone } from 'meteor/ohif:cornerstone';
function getInstanceMetadata (displaySets, sopInstanceUid) {
let instance;
// Use Array.some so that this loop stops when the internal loop
// has found the correct instance
displaySets.some(displaySet => {
// Search the display set to find the instance metadata for
return displaySet.images.find(instanceMetadata => {
if (instanceMetadata._sopInstanceUID === sopInstanceUid) {
instance = instanceMetadata;
return true;
}
});
});
return instance;
};
export default function getLengthMeasurementData(lengthMeasurementContent, displaySets) {
let lengthStates = [];
lengthMeasurementContent.forEach(groupItemContent => {
const lengthContent = groupItemContent.ContentSequence;
const reference = lengthContent.ContentSequence.ReferencedSOPSequence;
const lengthState = {};
lengthState.measuredValue = groupItemContent.MeasuredValueSequence.NumericValue;
lengthState.handles = {start: {}, end: {}};
[lengthState.handles.start.x,
lengthState.handles.start.y,
lengthState.handles.end.x,
lengthState.handles.end.y] = lengthContent.GraphicData;
// TODO: Save textbox position in GraphicData?
lengthState.handles.textBox = {
hasMoved: false,
movesIndependently: false,
drawnIndependently: true,
allowedOutsideImage: true,
hasBoundingBox: true
}
lengthState.ReferencedInstanceUID = reference.ReferencedSOPInstanceUID;
if (reference.ReferencedFrameNumber && reference.ReferencedFrameNumber !== 'NaN') {
lengthState.ReferencedFrameNumber = reference.ReferencedFrameNumber;
} else {
lengthState.ReferencedFrameNumber = 0;
}
lengthStates.push(lengthState);
});
const lengthMeasurementData = [];
let measurementNumber = 0;
lengthStates.forEach(lengthState => {
const sopInstanceUid = lengthState.ReferencedInstanceUID;
const instanceMetadata = getInstanceMetadata(displaySets, sopInstanceUid);
const imageId = OHIF.viewerbase.getImageId(instanceMetadata);
if (!imageId) {
return;
}
const studyInstanceUid = cornerstone.metaData.get('study', imageId).studyInstanceUid;
const seriesInstanceUid = cornerstone.metaData.get('series', imageId).seriesInstanceUid;
const patientId = instanceMetadata._study.patientId;
const frameIndex = lengthState.ReferencedFrameNumber;
const imagePath = [studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex].join('_');
const measurement = {
handles: lengthState.handles,
length: lengthState.measuredValue,
imageId,
imagePath,
sopInstanceUid,
seriesInstanceUid,
studyInstanceUid,
patientId,
frameIndex,
measurementNumber: ++measurementNumber,
userId: 'UserID',
timepointId: OHIF.viewer.data.currentTimepointId,
toolType: 'length',
_id: imageId + measurementNumber,
};
lengthMeasurementData.push(measurement);
});
return lengthMeasurementData;
};
@@ -0,0 +1,100 @@
import { OHIF } from 'meteor/ohif:core';
import { dcmjs } from 'meteor/ohif:cornerstone';
import getLengthMeasurementData from './getLengthMeasurementData';
const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22'];
const toArray = function(x) {
return (x.constructor.name === "Array" ? x : [x]);
};
const codeMeaningEquals = (codeMeaningName) => {
return (contentItem) => {
return contentItem.ConceptNameCodeSequence.CodeMeaning === codeMeaningName;
};
};
const imagingMeasurementsToMeasurementData = (dataset, displaySets) => {
// Identify the Imaging Measurements
const imagingMeasurementContent = toArray(dataset.ContentSequence).find(codeMeaningEquals("Imaging Measurements"));
// Retrieve the Measurements themselves
const measurementGroupContent = toArray(imagingMeasurementContent.ContentSequence).find(codeMeaningEquals("Measurement Group"));
// For now, bail out if the dataset is not a TID1500 SR with length measurements
// TODO: generalize to the various kinds of report
// TODO: generalize to the kinds of measurements the Viewer supports
if (dataset.ContentTemplateSequence.TemplateIdentifier !== "1500") {
OHIF.log.warn("This package can currently only interpret DICOM SR TID 1500");
return {};
}
// Filter to find Length measurements in the Structured Report
const lengthMeasurementContent = toArray(measurementGroupContent.ContentSequence).filter(codeMeaningEquals("Length"));
// Retrieve Length Measurement Data
return getLengthMeasurementData(lengthMeasurementContent, displaySets);
};
const retrieveDataFromSR = (Part10SRArrayBuffer) => {
const allStudies = OHIF.viewer.Studies.all();
let allDisplaySets = [];
allStudies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
// get the dicom data as an Object
let dicomData = dcmjs.data.DicomMessage.readFile(Part10SRArrayBuffer);
let dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomData.dict);
// Convert the SR into the kind of object the Measurements package is expecting
return imagingMeasurementsToMeasurementData(dataset, allDisplaySets);
};
export const getLatestSRSeries = () => {
const allStudies = OHIF.viewer.StudyMetadataList.all();
let latestSeries;
allStudies.forEach(study => {
study.getSeries().forEach(series => {
const firstInstance = series.getFirstInstance();
const sopClassUid = firstInstance._instance.sopClassUid;
if (supportedSopClassUIDs.includes(sopClassUid)) {
if(!latestSeries) {
latestSeries = series;
} else if (series._data.seriesDate > latestSeries._data.seriesDate ||
(series._data.seriesDate === latestSeries._data.seriesDate && series._data.seriesTime > latestSeries._data.seriesTime)) {
latestSeries = series;
}
}
});
});
return latestSeries;
};
export const handleSR = (series) => {
const instance = series.getFirstInstance();
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.responseType = 'arraybuffer';
request.open('GET', instance.getDataProperty('wadouri'));
request.onload = function (progressEvent) {
const data = retrieveDataFromSR(progressEvent.currentTarget.response);
resolve(data);
};
request.onerror = function(error) {
reject(error);
};
request.send();
});
};