203 lines
5.9 KiB
JavaScript
203 lines
5.9 KiB
JavaScript
import { JSZip } from 'meteor/silentcicero:jszip';
|
|
import { OHIF } from 'ohif-core';
|
|
|
|
const getNumberOfFilesToExport = function(studiesToExport) {
|
|
let numberOfFilesToExport = 0;
|
|
|
|
studiesToExport.forEach(study => {
|
|
numberOfFilesToExport += getNumberOfFilesInStudy(study);
|
|
});
|
|
|
|
return numberOfFilesToExport;
|
|
}
|
|
|
|
const convertSizeToString = size => {
|
|
const measuments = ['B', 'KB', 'MB', 'GB'];
|
|
let totalBytes = size,
|
|
measumentIndex = 0;
|
|
|
|
while(totalBytes > 1024) {
|
|
totalBytes /= 1024;
|
|
measumentIndex++;
|
|
}
|
|
|
|
return `${totalBytes.toFixed(2)} ${measuments[measumentIndex]}`;
|
|
}
|
|
|
|
/**
|
|
* Exports requested studies
|
|
* @param studiesToExport Studies to export
|
|
*/
|
|
OHIF.studylist.exportStudies = studiesToExport => {
|
|
if (studiesToExport.length < 1) {
|
|
return;
|
|
}
|
|
|
|
queryStudiesWithProgress(studiesToExport)
|
|
.then(exportQueriedStudiesWithProgress);
|
|
};
|
|
|
|
const exportQueriedStudiesWithProgress = studiesToExport => {
|
|
const exportFilesCount = getNumberOfFilesToExport(studiesToExport);
|
|
let exportHandler;
|
|
|
|
return OHIF.ui.showDialog('dialogProgress', {
|
|
title: 'Exporting Studies...',
|
|
message: `Exported files: 0 / ${exportFilesCount}`,
|
|
total: getNumberOfFilesToExport(studiesToExport),
|
|
task: {
|
|
run: (dialog) => {
|
|
exportHandler = exportQueriedStudies(studiesToExport, {
|
|
notify: stats => {
|
|
const fileSize = convertSizeToString(stats.totalBytes);
|
|
|
|
dialog.update(stats.processed);
|
|
dialog.setMessage(`Exported files: ${stats.processed} / ${stats.total} (${fileSize})`);
|
|
}
|
|
});
|
|
|
|
exportHandler.promise.then(() => {
|
|
dialog.done();
|
|
}, () => {
|
|
dialog.setMessage('Failed to export studies');
|
|
});
|
|
}
|
|
}
|
|
}).then(null, err => {
|
|
exportHandler.cancel();
|
|
});
|
|
};
|
|
|
|
const exportQueriedStudies = (studiesToExport, options) => {
|
|
const zip = new JSZip(),
|
|
promises = [],
|
|
pendingDownloads = [],
|
|
exportFilesCount = getNumberOfFilesToExport(studiesToExport),
|
|
notify = (options || {}).notify || function() { /* noop */ };
|
|
|
|
const cancelDownloads = () => {
|
|
while(pendingDownloads.length) {
|
|
const download = pendingDownloads.pop();
|
|
download.cancel();
|
|
}
|
|
};
|
|
|
|
let totalBytes = 0;
|
|
|
|
studiesToExport.forEach(study => {
|
|
study.seriesList.forEach(series => {
|
|
series.instances.forEach(instance => {
|
|
if (!instance.wadouri) {
|
|
return;
|
|
}
|
|
|
|
const download = downloadDicomFile(instance);
|
|
pendingDownloads.push(download);
|
|
|
|
const promise = download.promise
|
|
.then(data => {
|
|
const downloadIndex = pendingDownloads.indexOf(download);
|
|
|
|
totalBytes += data && data.size ? data.size : 0;
|
|
|
|
if(downloadIndex > -1) {
|
|
pendingDownloads.splice(downloadIndex, 1);
|
|
}
|
|
|
|
notify({
|
|
total: exportFilesCount,
|
|
processed: exportFilesCount - pendingDownloads.length,
|
|
totalBytes: totalBytes
|
|
})
|
|
|
|
return zipInstance(study, series, instance, zip, data)
|
|
})
|
|
.catch(err => {
|
|
if(!(err instanceof ExportStudyDownloadCanceledError)) {
|
|
OHIF.log.error('Failed to export studies', err);
|
|
}
|
|
|
|
cancelDownloads();
|
|
throw err;
|
|
});
|
|
|
|
promises.push(promise);
|
|
});
|
|
});
|
|
});
|
|
|
|
return {
|
|
cancel: cancelDownloads,
|
|
promise: Promise.all(promises).then(() => {
|
|
const zipContent = zip.generate({ type: 'blob' });
|
|
saveAs(zipContent, 'studies.zip');
|
|
})
|
|
}
|
|
};
|
|
|
|
const downloadDicomFile = instance => {
|
|
let xhr,
|
|
promiseReject;
|
|
|
|
const promise = new Promise((resolve, reject) => {
|
|
promiseReject = reject;
|
|
|
|
xhr = new XMLHttpRequest();
|
|
xhr.open('GET', instance.wadouri, true);
|
|
xhr.responseType = 'blob';
|
|
|
|
xhr.onload = () => {
|
|
if (xhr.readyState === 4 && xhr.status !== 200) {
|
|
return reject(new Error(`File not downloaded: ${instance.wadouri}`));
|
|
}
|
|
|
|
resolve(xhr.response)
|
|
};
|
|
|
|
xhr.onerror = () => {
|
|
reject(new Error(`File not downloaded: ${instance.wadouri}`));
|
|
};
|
|
|
|
xhr.send();
|
|
});
|
|
|
|
return {
|
|
promise: promise,
|
|
cancel: () => {
|
|
xhr.abort();
|
|
promiseReject(new ExportStudyDownloadCanceledError('Download canceled'));
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
const zipInstance = (study, series, instance, zip, data) => {
|
|
const fileReader = new FileReader(),
|
|
blobFile = new Blob([data], { type: 'application/dicom' }),
|
|
zipFolder = zip.folder(study.studyInstanceUid).folder(series.seriesInstanceUid);
|
|
|
|
const promise = new Promise((resolve, reject) => {
|
|
fileReader.onload = () => {
|
|
try {
|
|
zipFolder.file(`${instance.sopInstanceUid}.dcm`, fileReader.result, { binary: true });
|
|
resolve()
|
|
} catch(err) {
|
|
reject(err);
|
|
}
|
|
};
|
|
});
|
|
|
|
fileReader.readAsArrayBuffer(blobFile);
|
|
|
|
return promise;
|
|
};
|
|
|
|
|
|
const ExportStudyDownloadCanceledError = (message) => {
|
|
this.name = 'ExportStudyDownloadCanceledError';
|
|
this.message = message || 'Download canceled';
|
|
this.stack = (new Error()).stack;
|
|
}
|
|
ExportStudyDownloadCanceledError.prototype = Object.create(Error.prototype);
|
|
ExportStudyDownloadCanceledError.prototype.constructor = ExportStudyDownloadCanceledError;
|