|
@@ -0,0 +1,159 @@
|
|
|
+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
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|