160 lines
5.5 KiB
JavaScript
160 lines
5.5 KiB
JavaScript
|
|
class DICOMZero {
|
||
|
|
constructor(options={}) {
|
||
|
|
this.status = options.status || function() {};
|
||
|
|
this.reset();
|
||
|
|
}
|
||
|
|
|
||
|
|
reset() {
|
||
|
|
this.mappingLog = [];
|
||
|
|
this.dataTransfer = undefined;
|
||
|
|
this.datasets = [];
|
||
|
|
this.readers = [];
|
||
|
|
this.arrayBuffers = [];
|
||
|
|
this.files = [];
|
||
|
|
this.fileIndex = 0;
|
||
|
|
this.context = {patients: []};
|
||
|
|
}
|
||
|
|
|
||
|
|
static datasetFromArrayBuffer(arrayBuffer) {
|
||
|
|
let dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
|
||
|
|
let dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
||
|
|
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||
|
|
return(dataset);
|
||
|
|
}
|
||
|
|
|
||
|
|
// return a function to use as the 'onload' callback for the file reader.
|
||
|
|
// The function takes a progress event argument and it knows (from this class instance)
|
||
|
|
// when all files have been read so it can invoke the doneCallback when all
|
||
|
|
// have been read.
|
||
|
|
getReadDICOMFunction(doneCallback, statusCallback) {
|
||
|
|
statusCallback = statusCallback || console.log;
|
||
|
|
return progressEvent => {
|
||
|
|
let reader = progressEvent.target;
|
||
|
|
let arrayBuffer = reader.result;
|
||
|
|
this.arrayBuffers.push(arrayBuffer);
|
||
|
|
|
||
|
|
let dicomData;
|
||
|
|
try {
|
||
|
|
dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
|
||
|
|
let dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
||
|
|
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||
|
|
this.datasets.push(dataset);
|
||
|
|
} catch (error) {
|
||
|
|
console.error(error);
|
||
|
|
statusCallback("skipping non-dicom file");
|
||
|
|
}
|
||
|
|
|
||
|
|
let readerIndex = this.readers.indexOf(reader);
|
||
|
|
if (readerIndex < 0) {
|
||
|
|
reject("Logic error: Unexpected reader!");
|
||
|
|
} else {
|
||
|
|
this.readers.splice(readerIndex, 1); // remove the reader
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.fileIndex === this.dataTransfer.files.length) {
|
||
|
|
statusCallback(`Normalizing...`);
|
||
|
|
try {
|
||
|
|
this.multiframe = dcmjs.normalizers.Normalizer.normalizeToDataset(this.datasets);
|
||
|
|
} catch (e) {
|
||
|
|
console.error('Could not convert to multiframe');
|
||
|
|
console.error(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.multiframe.SOPClassUID == dcmjs.data.DicomMetaDictionary.sopClassUIDsByName['Segmentation']){
|
||
|
|
statusCallback(`Creating segmentation...`);
|
||
|
|
try {
|
||
|
|
this.seg = new dcmjs.derivations.Segmentation([this.multiframe]);
|
||
|
|
statusCallback(`Created ${this.multiframe.NumberOfFrames} frame multiframe object and segmentation.`);
|
||
|
|
} catch (e) {
|
||
|
|
console.error('Could not create segmentation');
|
||
|
|
console.error(e);
|
||
|
|
}
|
||
|
|
} else if (this.multiframe.SOPClassUID == dcmjs.data.DicomMetaDictionary.sopClassUIDsByName['ParametricMapStorage']){
|
||
|
|
statusCallback(`Creating parametric map...`);
|
||
|
|
try {
|
||
|
|
this.pm = new dcmjs.derivations.ParametricMap([this.multiframe]);
|
||
|
|
statusCallback(`Created ${this.multiframe.NumberOfFrames} frame multiframe object and parametric map.`);
|
||
|
|
} catch (e) {
|
||
|
|
console.error('Could not create parametric map');
|
||
|
|
console.error(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
doneCallback();
|
||
|
|
} else {
|
||
|
|
statusCallback(`Reading... (${this.fileIndex+1}).`);
|
||
|
|
this.readOneFile(doneCallback, statusCallback);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Used for file selection button or drop of file list
|
||
|
|
readOneFile(doneCallback, statusCallback) {
|
||
|
|
let file = this.dataTransfer.files[this.fileIndex];
|
||
|
|
this.fileIndex++;
|
||
|
|
|
||
|
|
let reader = new FileReader();
|
||
|
|
reader.onload = this.getReadDICOMFunction(doneCallback, statusCallback);
|
||
|
|
reader.readAsArrayBuffer(file);
|
||
|
|
|
||
|
|
this.files.push(file);
|
||
|
|
this.readers.push(reader);
|
||
|
|
}
|
||
|
|
|
||
|
|
handleDataTransferFileAsDataset(file, options={}) {
|
||
|
|
options.doneCallback = options.doneCallback || function(){};
|
||
|
|
|
||
|
|
let reader = new FileReader();
|
||
|
|
reader.onload = (progressEvent) => {
|
||
|
|
let dataset = DICOMZero.datasetFromArrayBuffer(reader.result);
|
||
|
|
options.doneCallback(dataset);
|
||
|
|
}
|
||
|
|
reader.readAsArrayBuffer(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
extractDatasetFromZipArrayBuffer(arrayBuffer) {
|
||
|
|
this.status(`Extracting ${this.datasets.length} of ${this.expectedDICOMFileCount}...`);
|
||
|
|
this.datasets.push(DICOMZero.datasetFromArrayBuffer(arrayBuffer));
|
||
|
|
if (this.datasets.length == this.expectedDICOMFileCount) {
|
||
|
|
this.status(`Finished extracting`);
|
||
|
|
this.zipFinishCallback();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
handleZip(zip) {
|
||
|
|
this.zip = zip;
|
||
|
|
this.expectedDICOMFileCount = 0;
|
||
|
|
Object.keys(zip.files).forEach(fileKey => {
|
||
|
|
this.status(`Considering ${fileKey}...`);
|
||
|
|
if (fileKey.endsWith('.dcm')) {
|
||
|
|
this.expectedDICOMFileCount += 1;
|
||
|
|
zip.files[fileKey].async('arraybuffer').then(this.extractDatasetFromZipArrayBuffer.bind(this));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
extractFromZipArrayBuffer(arrayBuffer, finishCallback=function(){}) {
|
||
|
|
this.zipFinishCallback = finishCallback;
|
||
|
|
this.status("Extracting from zip...");
|
||
|
|
JSZip.loadAsync(arrayBuffer)
|
||
|
|
.then(this.handleZip.bind(this));
|
||
|
|
}
|
||
|
|
|
||
|
|
organizeDatasets() {
|
||
|
|
this.datasets.forEach(dataset => {
|
||
|
|
let patientName = dataset.PatientName;
|
||
|
|
let studyTag = dataset.StudyDate + ": " + dataset.StudyDescription;
|
||
|
|
let seriesTag = dataset.SeriesNumber + ": " + dataset.SeriesDescription;
|
||
|
|
let patientNames = this.context.patients.map(patient => patient.name);
|
||
|
|
let patientIndex = patientNames.indexOf(dataset.PatientName);
|
||
|
|
if (patientIndex == -1) {
|
||
|
|
this.context.patients.push({
|
||
|
|
name: dataset.PatientName,
|
||
|
|
id: this.context.patients.length,
|
||
|
|
studies: {}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
let studyNames; // TODO - finish organizing
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|