fix: 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases (#862)
* fix: 🐛 Local file: failing when retrieving segmentation data Fix segmentation data retrieval issues for local file. Changed from fecthing to use cornerstone loadAndCache method BREAKING CHANGE: DICOM Seg Closes: part of #838 * Switch SEG retrieval to WADO-RS * Forgot a debugger * refactor: 💡 Code refactor. Minor changes into methods * fix: 🐛 Load local files: PDF Items: 1. FileLoaderService: used for serveral operations on local files(load it, get list of studies, group them, accepting dicom and pdf) 2. DicomLoaderService: used for loading dicom based on dataset and studies. Depending on type of dicom loader might change. WIP 3. Refactor PDF and handleSegmentationStorage to use DicomLoaderService * fix: 🐛 Code review * fix: 🐛 Code review. Changed:Folder organization and dicom file Move fileLoaderService and others to a specific folder. When loading dicom file change to only retrieve the file(not use cornerstone to cache or anything else). * fix: 🐛 Code review. Move dicomLoaderService to core Moved dicomLoaderService to ohif/core and localFileLoaders to a specific folder. * fix: 🐛 Code review Simplified method to get study for dicom file. Added error handling on file loading. DicomLoaderService to be exposed on ohif/core/utils instead. * fix: 🐛 Reduce local load to one method only Reduced local file load to one method only * fix: 🐛 HTML to use dicomLoaderService. Prefer wadors than (uri) * fix: 🐛 Code implementation for multiframe files * fix: 🐛 Code review. Default local loader to dicom Closes: 838 * fix: 🐛 Code review. Use relative path to require DICOMWeb Closes: 838 * fix: 🐛 Code review. Fix unit test. Added DicomLoaderService mod Closes: 838 * fix: 🐛 Code review. Add 'Seg' on left thumb When getting/creating dataset get modality for file/image read Closes: 838
This commit is contained in:
parent
e66c27affb
commit
e7e1a8a6cd
@ -4,7 +4,7 @@ import OHIF from '@ohif/core';
|
|||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
|
|
||||||
const { StackManager } = OHIF.utils;
|
const { StackManager, DicomLoaderService } = OHIF.utils;
|
||||||
|
|
||||||
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
@ -52,14 +52,6 @@ function addSegMetadataToCornerstoneToolState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function retrieveDicomData(wadoUri) {
|
|
||||||
// TODO: Authorization header depends on the server. If we ever have multiple servers
|
|
||||||
// we will need to figure out how / when to pass this information in.
|
|
||||||
return fetch(wadoUri, {
|
|
||||||
headers: OHIF.DICOMWeb.getAuthorizationHeader(),
|
|
||||||
}).then(response => response.arrayBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSegmentationStorage(
|
async function handleSegmentationStorage(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
@ -73,8 +65,11 @@ async function handleSegmentationStorage(
|
|||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUid
|
||||||
);
|
);
|
||||||
const segWadoUri = displaySet.images[0].getData().wadouri;
|
|
||||||
const arrayBuffer = await retrieveDicomData(segWadoUri);
|
const arrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||||
|
displaySet,
|
||||||
|
studies
|
||||||
|
);
|
||||||
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
|
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
|
||||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||||
dicomData.dict
|
dicomData.dict
|
||||||
|
|||||||
@ -1,46 +1,34 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DicomHtmlViewport from './DicomHtmlViewport';
|
import DicomHtmlViewport from './DicomHtmlViewport';
|
||||||
|
import OHIF from '@ohif/core';
|
||||||
|
const { DicomLoaderService } = OHIF.utils;
|
||||||
|
|
||||||
class OHIFDicomHtmlViewport extends Component {
|
class OHIFDicomHtmlViewport extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
studies: PropTypes.object,
|
studies: PropTypes.object,
|
||||||
displaySet: PropTypes.object,
|
displaySet: PropTypes.object,
|
||||||
viewportIndex: PropTypes.number
|
viewportIndex: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
byteArray: null,
|
byteArray: null,
|
||||||
error: null
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { displaySet } = this.props.viewportData;
|
const { displaySet, studies } = this.props.viewportData;
|
||||||
const {
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid,
|
|
||||||
wadoRoot,
|
|
||||||
wadoUri,
|
|
||||||
authorizationHeaders
|
|
||||||
} = displaySet;
|
|
||||||
|
|
||||||
this.retrieveDicomData(
|
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
|
||||||
studyInstanceUid,
|
data => {
|
||||||
seriesInstanceUid,
|
const byteArray = new Uint8Array(data);
|
||||||
sopInstanceUid,
|
|
||||||
wadoRoot,
|
|
||||||
wadoUri,
|
|
||||||
authorizationHeaders
|
|
||||||
).then(
|
|
||||||
byteArray => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
byteArray
|
byteArray: byteArray,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
@ -48,27 +36,6 @@ class OHIFDicomHtmlViewport extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveDicomData(
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid,
|
|
||||||
wadoRoot,
|
|
||||||
wadoUri,
|
|
||||||
authorizationHeaders
|
|
||||||
) {
|
|
||||||
// TODO: Passing in a lot of data we aren't using
|
|
||||||
|
|
||||||
// TODO: Authorization header depends on the server. If we ever have multiple servers
|
|
||||||
// we will need to figure out how / when to pass this information in.
|
|
||||||
return fetch(wadoUri, {
|
|
||||||
headers: authorizationHeaders
|
|
||||||
})
|
|
||||||
.then(response => response.arrayBuffer())
|
|
||||||
.then(arraybuffer => {
|
|
||||||
return new Uint8Array(arraybuffer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -4,41 +4,46 @@ import TypedArrayProp from './TypedArrayProp';
|
|||||||
|
|
||||||
// TODO: Should probably use dcmjs for this
|
// TODO: Should probably use dcmjs for this
|
||||||
const SOP_CLASS_UIDS = {
|
const SOP_CLASS_UIDS = {
|
||||||
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1'
|
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1',
|
||||||
};
|
};
|
||||||
|
|
||||||
class DicomPDFViewport extends Component {
|
class DicomPDFViewport extends Component {
|
||||||
state = {
|
state = {
|
||||||
fileURL: null,
|
fileURL: null,
|
||||||
error: null
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
byteArray: TypedArrayProp.uint8
|
byteArray: TypedArrayProp.uint8,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPDF = dataSet => {
|
renderPDF = (dataSet, byteArray) => {
|
||||||
const sopClassUid = dataSet.string('x00080016');
|
let pdfByteArray = byteArray;
|
||||||
|
|
||||||
if (sopClassUid !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
|
if (dataSet) {
|
||||||
throw new Error('This is not a DICOM-encapsulated PDF');
|
const sopClassUid = dataSet.string('x00080016');
|
||||||
|
|
||||||
|
if (sopClassUid !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
|
||||||
|
throw new Error('This is not a DICOM-encapsulated PDF');
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileTag = dataSet.elements.x00420011;
|
||||||
|
const offset = fileTag.dataOffset;
|
||||||
|
const remainder = offset + fileTag.length;
|
||||||
|
pdfByteArray = dataSet.byteArray.slice(offset, remainder);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileTag = dataSet.elements.x00420011;
|
|
||||||
const offset = fileTag.dataOffset;
|
|
||||||
const remainder = offset + fileTag.length;
|
|
||||||
const pdfByteArray = dataSet.byteArray.slice(offset, remainder);
|
|
||||||
const PDF = new Blob([pdfByteArray], { type: 'application/pdf' });
|
const PDF = new Blob([pdfByteArray], { type: 'application/pdf' });
|
||||||
const fileURL = URL.createObjectURL(PDF);
|
const fileURL = URL.createObjectURL(PDF);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
fileURL
|
fileURL,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
parseByteArray = byteArray => {
|
parseByteArray = byteArray => {
|
||||||
const options = {
|
const options = {
|
||||||
untilTag: ''
|
untilTag: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
let dataSet;
|
let dataSet;
|
||||||
@ -47,7 +52,7 @@ class DicomPDFViewport extends Component {
|
|||||||
dataSet = dicomParser.parseDicom(byteArray, options);
|
dataSet = dicomParser.parseDicom(byteArray, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error
|
error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +62,7 @@ class DicomPDFViewport extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const dataSet = this.parseByteArray(this.props.byteArray);
|
const dataSet = this.parseByteArray(this.props.byteArray);
|
||||||
|
|
||||||
this.renderPDF(dataSet);
|
this.renderPDF(dataSet, this.props.byteArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -1,60 +1,54 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from 'react';
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from 'prop-types';
|
||||||
import OHIF from "@ohif/core";
|
import OHIF from '@ohif/core';
|
||||||
import OHIFComponentPlugin from "./OHIFComponentPlugin.js";
|
import OHIFComponentPlugin from './OHIFComponentPlugin.js';
|
||||||
import DicomPDFViewport from "./DicomPDFViewport";
|
import DicomPDFViewport from './DicomPDFViewport';
|
||||||
|
|
||||||
const { DICOMWeb } = OHIF;
|
const { DicomLoaderService } = OHIF.utils;
|
||||||
|
|
||||||
class OHIFDicomPDFViewport extends Component {
|
class OHIFDicomPDFViewport extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
studies: PropTypes.object,
|
studies: PropTypes.object,
|
||||||
displaySet: PropTypes.object,
|
displaySet: PropTypes.object,
|
||||||
viewportIndex: PropTypes.number
|
viewportIndex: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
byteArray: null,
|
byteArray: null,
|
||||||
error: null
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
static id = "DicomPDFViewportPDF";
|
static id = 'DicomPDFViewportPDF';
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
console.log("DicomPDFViewport init()");
|
console.log('DicomPDFViewport init()');
|
||||||
}
|
}
|
||||||
|
|
||||||
static destroy() {
|
static destroy() {
|
||||||
console.log("DicomPDFViewport destroy()");
|
console.log('DicomPDFViewport destroy()');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { displaySet } = this.props.viewportData;
|
const { displaySet, studies } = this.props.viewportData;
|
||||||
const {
|
const {
|
||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
seriesInstanceUid,
|
seriesInstanceUid,
|
||||||
sopInstanceUid,
|
sopInstanceUid,
|
||||||
wadoRoot,
|
wadoRoot,
|
||||||
wadoUri,
|
wadoUri,
|
||||||
authorizationHeaders
|
authorizationHeaders,
|
||||||
} = displaySet;
|
} = displaySet;
|
||||||
|
|
||||||
this.retrieveDicomData(
|
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
|
||||||
studyInstanceUid,
|
data => {
|
||||||
seriesInstanceUid,
|
const byteArray = new Uint8Array(data);
|
||||||
sopInstanceUid,
|
|
||||||
wadoRoot,
|
|
||||||
wadoUri,
|
|
||||||
authorizationHeaders
|
|
||||||
).then(
|
|
||||||
byteArray => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
byteArray
|
byteArray: byteArray,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
@ -62,27 +56,6 @@ class OHIFDicomPDFViewport extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveDicomData(
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid,
|
|
||||||
wadoRoot,
|
|
||||||
wadoUri,
|
|
||||||
authorizationHeaders
|
|
||||||
) {
|
|
||||||
// TODO: Passing in a lot of data we aren't using
|
|
||||||
|
|
||||||
// TODO: Authorization header depends on the server. If we ever have multiple servers
|
|
||||||
// we will need to figure out how / when to pass this information in.
|
|
||||||
return fetch(wadoUri, {
|
|
||||||
headers: authorizationHeaders
|
|
||||||
})
|
|
||||||
.then(response => response.arrayBuffer())
|
|
||||||
.then(arraybuffer => {
|
|
||||||
return new Uint8Array(arraybuffer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { id, init, destroy } = OHIFDicomPDFViewport;
|
const { id, init, destroy } = OHIFDicomPDFViewport;
|
||||||
const pluginProps = { id, init, destroy };
|
const pluginProps = { id, init, destroy };
|
||||||
|
|||||||
@ -129,6 +129,7 @@ export class StudyMetadata extends Metadata {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (displaySet) {
|
if (displaySet) {
|
||||||
|
displaySet.sopClassModule = true;
|
||||||
displaySets.push(displaySet);
|
displaySets.push(displaySet);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -214,7 +215,7 @@ export class StudyMetadata extends Metadata {
|
|||||||
* @returns {boolean} True on success, false on failure.
|
* @returns {boolean} True on success, false on failure.
|
||||||
*/
|
*/
|
||||||
addDisplaySet(displaySet) {
|
addDisplaySet(displaySet) {
|
||||||
if (displaySet instanceof ImageSet) {
|
if (displaySet instanceof ImageSet || displaySet.sopClassModule) {
|
||||||
this._displaySets.push(displaySet);
|
this._displaySets.push(displaySet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -402,6 +402,7 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
|||||||
baseWadoRsUri: baseWadoRsUri,
|
baseWadoRsUri: baseWadoRsUri,
|
||||||
wadouri: WADOProxy.convertURL(wadouri, server),
|
wadouri: WADOProxy.convertURL(wadouri, server),
|
||||||
wadorsuri: WADOProxy.convertURL(wadorsuri, server),
|
wadorsuri: WADOProxy.convertURL(wadorsuri, server),
|
||||||
|
wadoRoot: server.wadoRoot,
|
||||||
imageRendering: server.imageRendering,
|
imageRendering: server.imageRendering,
|
||||||
thumbnailRendering: server.thumbnailRendering,
|
thumbnailRendering: server.thumbnailRendering,
|
||||||
};
|
};
|
||||||
|
|||||||
199
platform/core/src/utils/dicomLoaderService.js
Normal file
199
platform/core/src/utils/dicomLoaderService.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
|
import { api } from 'dicomweb-client';
|
||||||
|
import DICOMWeb from '../DICOMWeb';
|
||||||
|
|
||||||
|
const getImageId = imageObj => {
|
||||||
|
if (!imageObj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof imageObj.getImageId === 'function'
|
||||||
|
? imageObj.getImageId()
|
||||||
|
: imageObj.url;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findImageIdOnStudies = (studies, displaySetInstanceUid) => {
|
||||||
|
const study = studies.find(study => {
|
||||||
|
const displaySet = study.displaySets.some(
|
||||||
|
displaySet => displaySet.displaySetInstanceUid === displaySetInstanceUid
|
||||||
|
);
|
||||||
|
return displaySet;
|
||||||
|
});
|
||||||
|
const { seriesList = [] } = study;
|
||||||
|
const { instances = [] } = seriesList[0] || {};
|
||||||
|
const instance = instances[0];
|
||||||
|
|
||||||
|
return getImageId(instance);
|
||||||
|
};
|
||||||
|
|
||||||
|
const someInvalidStrings = strings => {
|
||||||
|
const stringsArray = Array.isArray(strings) ? strings : [strings];
|
||||||
|
const emptyString = string => !string;
|
||||||
|
let invalid = stringsArray.some(emptyString);
|
||||||
|
return invalid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageInstance = dataset => {
|
||||||
|
return dataset && dataset.images && dataset.images[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageInstanceId = imageInstance => {
|
||||||
|
return getImageId(imageInstance);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchIt = (url, headers = DICOMWeb.getAuthorizationHeader()) => {
|
||||||
|
return fetch(url, headers).then(response => response.arrayBuffer());
|
||||||
|
};
|
||||||
|
|
||||||
|
const cornerstoneRetriever = imageId => {
|
||||||
|
return cornerstone.loadAndCacheImage(imageId).then(image => {
|
||||||
|
return image && image.data && image.data.byteArray.buffer;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const wadorsRetriever = (
|
||||||
|
url,
|
||||||
|
studyInstanceUID,
|
||||||
|
seriesInstanceUID,
|
||||||
|
sopInstanceUID,
|
||||||
|
headers = DICOMWeb.getAuthorizationHeader()
|
||||||
|
) => {
|
||||||
|
const config = {
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
};
|
||||||
|
const dicomWeb = new api.DICOMwebClient(config);
|
||||||
|
|
||||||
|
return dicomWeb.retrieveInstance({
|
||||||
|
studyInstanceUID,
|
||||||
|
seriesInstanceUID,
|
||||||
|
sopInstanceUID,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageLoaderType = imageId => {
|
||||||
|
const loaderRegExp = /^\w+\:/;
|
||||||
|
const loaderType = loaderRegExp.exec(imageId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
(loaderRegExp.lastIndex === 0 &&
|
||||||
|
loaderType &&
|
||||||
|
loaderType[0] &&
|
||||||
|
loaderType[0].replace(':', '')) ||
|
||||||
|
''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DicomLoaderService = new (class {
|
||||||
|
getLocalData(dataset, studies) {
|
||||||
|
if (dataset && dataset.localFile) {
|
||||||
|
// Use referenced imageInstance
|
||||||
|
const imageInstance = getImageInstance(dataset);
|
||||||
|
let imageId = getImageInstanceId(imageInstance);
|
||||||
|
|
||||||
|
// or Try to get it from studies
|
||||||
|
if (someInvalidStrings(imageId)) {
|
||||||
|
imageId = findImageIdOnStudies(studies, dataset.displaySetInstanceUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!someInvalidStrings(imageId)) {
|
||||||
|
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataByImageType(dataset) {
|
||||||
|
const imageInstance = getImageInstance(dataset);
|
||||||
|
|
||||||
|
if (imageInstance) {
|
||||||
|
const imageId = getImageInstanceId(imageInstance);
|
||||||
|
let getDicomDataMethod = fetchIt;
|
||||||
|
const loaderType = getImageLoaderType(imageId);
|
||||||
|
|
||||||
|
switch (loaderType) {
|
||||||
|
case 'dicomfile':
|
||||||
|
getDicomDataMethod = cornerstoneRetriever.bind(this, imageId);
|
||||||
|
break;
|
||||||
|
case 'wadors':
|
||||||
|
const url = imageInstance.getData().wadoRoot;
|
||||||
|
const studyInstanceUID = imageInstance.getStudyInstanceUID();
|
||||||
|
const seriesInstanceUID = imageInstance.getSeriesInstanceUID();
|
||||||
|
const sopInstanceUID = imageInstance.getSOPInstanceUID();
|
||||||
|
const invalidParams = someInvalidStrings([
|
||||||
|
url,
|
||||||
|
studyInstanceUID,
|
||||||
|
seriesInstanceUID,
|
||||||
|
sopInstanceUID,
|
||||||
|
]);
|
||||||
|
if (invalidParams) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDicomDataMethod = wadorsRetriever.bind(
|
||||||
|
this,
|
||||||
|
url,
|
||||||
|
studyInstanceUID,
|
||||||
|
seriesInstanceUID,
|
||||||
|
sopInstanceUID
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'wadouri':
|
||||||
|
// Strip out the image loader specifier
|
||||||
|
imageId = imageId.substring(imageId.indexOf(':') + 1);
|
||||||
|
|
||||||
|
if (someInvalidStrings(imageId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getDicomDataMethod = fetchIt.bind(this, imageId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDicomDataMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataByDatasetType(dataset) {
|
||||||
|
const {
|
||||||
|
studyInstanceUid,
|
||||||
|
seriesInstanceUid,
|
||||||
|
sopInstanceUid,
|
||||||
|
authorizationHeaders,
|
||||||
|
wadoRoot,
|
||||||
|
wadoUri,
|
||||||
|
} = dataset;
|
||||||
|
// Retrieve wadors or just try to fetch wadouri
|
||||||
|
if (!someInvalidStrings(wadoRoot)) {
|
||||||
|
return wadorsRetriever(
|
||||||
|
wadoRoot,
|
||||||
|
studyInstanceUid,
|
||||||
|
seriesInstanceUid,
|
||||||
|
sopInstanceUid,
|
||||||
|
authorizationHeaders
|
||||||
|
);
|
||||||
|
} else if (!someInvalidStrings(wadoUri)) {
|
||||||
|
return fetchIt(wadoUri, { headers: authorizationHeaders });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*getLoaderIterator(dataset, studies) {
|
||||||
|
yield this.getLocalData(dataset, studies);
|
||||||
|
yield this.getDataByImageType(dataset);
|
||||||
|
yield this.getDataByDatasetType(dataset);
|
||||||
|
}
|
||||||
|
|
||||||
|
findDicomDataPromise(dataset, studies) {
|
||||||
|
const loaderIterator = this.getLoaderIterator(dataset, studies);
|
||||||
|
// it returns first valid retriever method.
|
||||||
|
for (const loader of loaderIterator) {
|
||||||
|
if (loader) {
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case of no valid loader
|
||||||
|
throw new Error('Invalid dicom data loader');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default DicomLoaderService;
|
||||||
@ -5,11 +5,16 @@ function getWADORSImageUrl(instance, frame) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to sum 1 because WADO-RS frame number is 1-based
|
// Use null to obtain an imageId which represents the instance
|
||||||
frame = frame ? parseInt(frame) + 1 : 1;
|
if (frame === null) {
|
||||||
|
wadorsuri = wadorsuri.replace(/frames\/(\d+)/, '');
|
||||||
|
} else {
|
||||||
|
// We need to sum 1 because WADO-RS frame number is 1-based
|
||||||
|
frame = frame ? parseInt(frame) + 1 : 1;
|
||||||
|
|
||||||
// Replaces /frame/1 by /frame/{frame}
|
// Replaces /frame/1 by /frame/{frame}
|
||||||
wadorsuri = wadorsuri.replace(/frames\/(\d+)/, `frames/${frame}`);
|
wadorsuri = wadorsuri.replace(/frames\/(\d+)/, `frames/${frame}`);
|
||||||
|
}
|
||||||
|
|
||||||
return wadorsuri;
|
return wadorsuri;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import sortBy from './sortBy.js';
|
|||||||
import studyMetadataManager from './studyMetadataManager';
|
import studyMetadataManager from './studyMetadataManager';
|
||||||
import updateMetaDataManager from './updateMetaDataManager.js';
|
import updateMetaDataManager from './updateMetaDataManager.js';
|
||||||
import writeScript from './writeScript.js';
|
import writeScript from './writeScript.js';
|
||||||
|
import DicomLoaderService from './dicomLoaderService.js';
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
guid,
|
guid,
|
||||||
@ -21,6 +22,7 @@ const utils = {
|
|||||||
// Updates WADO-RS metaDataManager
|
// Updates WADO-RS metaDataManager
|
||||||
updateMetaDataManager,
|
updateMetaDataManager,
|
||||||
DICOMTagDescriptions,
|
DICOMTagDescriptions,
|
||||||
|
DicomLoaderService,
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -35,6 +37,7 @@ export {
|
|||||||
// Updates WADO-RS metaDataManager
|
// Updates WADO-RS metaDataManager
|
||||||
updateMetaDataManager,
|
updateMetaDataManager,
|
||||||
DICOMTagDescriptions,
|
DICOMTagDescriptions,
|
||||||
|
DicomLoaderService,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default utils;
|
export default utils;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ describe('Top level exports', () => {
|
|||||||
// Updates WADO-RS metaDataManager
|
// Updates WADO-RS metaDataManager
|
||||||
'updateMetaDataManager',
|
'updateMetaDataManager',
|
||||||
'DICOMTagDescriptions',
|
'DICOMTagDescriptions',
|
||||||
|
'DicomLoaderService',
|
||||||
].sort();
|
].sort();
|
||||||
|
|
||||||
const exports = Object.keys(utils.default).sort();
|
const exports = Object.keys(utils.default).sort();
|
||||||
|
|||||||
@ -79,6 +79,9 @@ class ViewerLocalFileData extends Component {
|
|||||||
studyMetadata.createDisplaySets(sopClassHandlerModules);
|
studyMetadata.createDisplaySets(sopClassHandlerModules);
|
||||||
studyMetadata.setDisplaySets(study.displaySets);
|
studyMetadata.setDisplaySets(study.displaySets);
|
||||||
|
|
||||||
|
studyMetadata.forEachDisplaySet(displayset => {
|
||||||
|
displayset.localFile = true;
|
||||||
|
});
|
||||||
// Updates WADO-RS metaDataManager
|
// Updates WADO-RS metaDataManager
|
||||||
updateMetaDataManager(study);
|
updateMetaDataManager(study);
|
||||||
|
|
||||||
|
|||||||
@ -1,160 +1,26 @@
|
|||||||
import cornerstone from 'cornerstone-core';
|
import FileLoaderService from './localFileLoaders/fileLoaderService';
|
||||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
|
||||||
import * as dcmjs from 'dcmjs';
|
|
||||||
|
|
||||||
function datasetsToStudies(datasets) {
|
const processFile = async file => {
|
||||||
const StudyInstanceUIDs = new Set();
|
try {
|
||||||
datasets.forEach(ds => {
|
const fileLoaderService = new FileLoaderService(file);
|
||||||
StudyInstanceUIDs.add(ds.StudyInstanceUID);
|
const imageId = fileLoaderService.addFile(file);
|
||||||
});
|
const image = await fileLoaderService.loadFile(file, imageId);
|
||||||
|
const dataset = await fileLoaderService.getDataset(image, imageId);
|
||||||
|
const studies = await fileLoaderService.getStudies(dataset, imageId);
|
||||||
|
|
||||||
const studies = [];
|
return studies;
|
||||||
Array.from(StudyInstanceUIDs).forEach(studyInstanceUid => {
|
} catch (error) {
|
||||||
const studyDatasets = datasets.filter(
|
console.log(
|
||||||
ds => ds.StudyInstanceUID === studyInstanceUid
|
error.name,
|
||||||
|
':Error when trying to load and process local files:',
|
||||||
|
error.message
|
||||||
);
|
);
|
||||||
const firstDataset = studyDatasets[0];
|
}
|
||||||
const study = {
|
};
|
||||||
studyInstanceUid: firstDataset.StudyInstanceUID,
|
|
||||||
studyDate: firstDataset.StudyDate,
|
|
||||||
studyTime: firstDataset.StudyTime,
|
|
||||||
accessionNumber: firstDataset.AccessionNumber,
|
|
||||||
referringPhysicianName: firstDataset.ReferringPhysicianName,
|
|
||||||
patientName: firstDataset.PatientName,
|
|
||||||
patientId: firstDataset.PatientID,
|
|
||||||
patientBirthdate: firstDataset.PatientBirthDate,
|
|
||||||
patientSex: firstDataset.PatientSex,
|
|
||||||
studyId: firstDataset.StudyID,
|
|
||||||
studyDescription: firstDataset.StudyDescription,
|
|
||||||
//numberOfStudyRelatedSeries: DICOMWeb.getString(study['00201206']),
|
|
||||||
//numberOfStudyRelatedInstances: DICOMWeb.getString(study['00201208']),
|
|
||||||
// modality: DICOMWeb.getString(study['00080060']),
|
|
||||||
// modalitiesInStudy: DICOMWeb.getString(study['00080061']),
|
|
||||||
//modalities:
|
|
||||||
seriesList: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const SeriesInstanceUIDs = new Set();
|
|
||||||
studyDatasets.forEach(ds => {
|
|
||||||
SeriesInstanceUIDs.add(ds.SeriesInstanceUID);
|
|
||||||
});
|
|
||||||
|
|
||||||
Array.from(SeriesInstanceUIDs).forEach(seriesInstanceUid => {
|
|
||||||
const seriesDatasets = studyDatasets.filter(
|
|
||||||
ds => ds.SeriesInstanceUID === seriesInstanceUid
|
|
||||||
);
|
|
||||||
|
|
||||||
const SOPInstanceUIDs = new Set();
|
|
||||||
seriesDatasets.forEach(ds => {
|
|
||||||
SOPInstanceUIDs.add(ds.SOPInstanceUID);
|
|
||||||
|
|
||||||
study.seriesList.push({
|
|
||||||
seriesInstanceUid: ds.SeriesInstanceUID,
|
|
||||||
seriesDescription: ds.SeriesDescription,
|
|
||||||
seriesNumber: ds.SeriesNumber,
|
|
||||||
instances: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Array.from(SOPInstanceUIDs).forEach(sopInstanceUid => {
|
|
||||||
const instance = seriesDatasets.find(
|
|
||||||
a => a.SOPInstanceUID === sopInstanceUid
|
|
||||||
);
|
|
||||||
const series = study.seriesList.find(
|
|
||||||
a => a.seriesInstanceUid === seriesInstanceUid
|
|
||||||
);
|
|
||||||
|
|
||||||
series.instances.push({
|
|
||||||
sopInstanceUid: instance.SOPInstanceUID,
|
|
||||||
sopClassUid: instance.SOPClassUID,
|
|
||||||
rows: instance.Rows,
|
|
||||||
columns: instance.Columns,
|
|
||||||
numberOfFrames: instance.NumberOfFrames,
|
|
||||||
instanceNumber: instance.InstanceNumber,
|
|
||||||
getImageId: () => instance.imageId, // TODO: Change getImageId to check for instance.imageId property first.
|
|
||||||
/*imageType: DICOMWeb.getString(instance['00080008']),
|
|
||||||
modality: DICOMWeb.getString(instance['00080060']),
|
|
||||||
instanceNumber: DICOMWeb.getNumber(instance['00200013']),
|
|
||||||
imagePositionPatient: DICOMWeb.getString(instance['00200032']),
|
|
||||||
imageOrientationPatient: DICOMWeb.getString(instance['00200037']),
|
|
||||||
frameOfReferenceUID: DICOMWeb.getString(instance['00200052']),
|
|
||||||
sliceLocation: DICOMWeb.getNumber(instance['00201041']),
|
|
||||||
samplesPerPixel: DICOMWeb.getNumber(instance['00280002']),
|
|
||||||
photometricInterpretation: DICOMWeb.getString(instance['00280004']),
|
|
||||||
planarConfiguration: DICOMWeb.getNumber(instance['00280006']),
|
|
||||||
pixelSpacing: DICOMWeb.getString(instance['00280030']),
|
|
||||||
pixelAspectRatio: DICOMWeb.getString(instance['00280034']),
|
|
||||||
bitsAllocated: DICOMWeb.getNumber(instance['00280100']),
|
|
||||||
bitsStored: DICOMWeb.getNumber(instance['00280101']),
|
|
||||||
highBit: DICOMWeb.getNumber(instance['00280102']),
|
|
||||||
pixelRepresentation: DICOMWeb.getNumber(instance['00280103']),
|
|
||||||
smallestPixelValue: DICOMWeb.getNumber(instance['00280106']),
|
|
||||||
largestPixelValue: DICOMWeb.getNumber(instance['00280107']),
|
|
||||||
windowCenter: DICOMWeb.getString(instance['00281050']),
|
|
||||||
windowWidth: DICOMWeb.getString(instance['00281051']),
|
|
||||||
rescaleIntercept: DICOMWeb.getNumber(instance['00281052']),
|
|
||||||
rescaleSlope: DICOMWeb.getNumber(instance['00281053']),
|
|
||||||
rescaleType: DICOMWeb.getNumber(instance['00281054']),
|
|
||||||
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
|
|
||||||
laterality: DICOMWeb.getString(instance['00200062']),
|
|
||||||
viewPosition: DICOMWeb.getString(instance['00185101']),
|
|
||||||
acquisitionDateTime: DICOMWeb.getString(instance['0008002A']),
|
|
||||||
frameIncrementPointer: getFrameIncrementPointer(instance['00280009']),
|
|
||||||
frameTime: DICOMWeb.getNumber(instance['00181063']),
|
|
||||||
frameTimeVector: parseFloatArray(
|
|
||||||
DICOMWeb.getString(instance['00181065'])
|
|
||||||
),
|
|
||||||
sliceThickness: DICOMWeb.getNumber(instance['00180050']),
|
|
||||||
spacingBetweenSlices: DICOMWeb.getString(instance['00180088']),
|
|
||||||
lossyImageCompression: DICOMWeb.getString(instance['00282110']),
|
|
||||||
derivationDescription: DICOMWeb.getString(instance['00282111']),
|
|
||||||
lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']),
|
|
||||||
lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']),
|
|
||||||
echoNumber: DICOMWeb.getString(instance['00180086']),
|
|
||||||
contrastBolusAgent: DICOMWeb.getString(instance['00180010']),
|
|
||||||
radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance),
|
|
||||||
wadouri: WADOProxy.convertURL(wadouri, server),
|
|
||||||
wadorsuri: WADOProxy.convertURL(wadorsuri, server),*/
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
studies.push(study);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.warn(studies);
|
|
||||||
|
|
||||||
return studies;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function filesToStudies(files) {
|
export default async function filesToStudies(files) {
|
||||||
const imagePromises = files.map(file => {
|
const processFilesPromises = files.map(processFile);
|
||||||
const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
|
const studies = await Promise.all(processFilesPromises);
|
||||||
return cornerstone
|
|
||||||
.loadAndCacheImage(imageId)
|
|
||||||
.catch(error => console.warn(error));
|
|
||||||
});
|
|
||||||
|
|
||||||
const images = await Promise.all(imagePromises);
|
return FileLoaderService.groupSeries(studies.flat());
|
||||||
const datasets = [];
|
|
||||||
|
|
||||||
images.forEach(image => {
|
|
||||||
if (!image || !image.data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = image.data.byteArray.buffer;
|
|
||||||
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
|
|
||||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
|
||||||
dicomData.dict
|
|
||||||
);
|
|
||||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(
|
|
||||||
dicomData.meta
|
|
||||||
);
|
|
||||||
dataset.imageId = image.imageId;
|
|
||||||
|
|
||||||
datasets.push(dataset);
|
|
||||||
});
|
|
||||||
|
|
||||||
return datasetsToStudies(datasets);
|
|
||||||
}
|
}
|
||||||
|
|||||||
192
platform/viewer/src/lib/localFileLoaders/dicomFileLoader.js
Normal file
192
platform/viewer/src/lib/localFileLoaders/dicomFileLoader.js
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import * as dcmjs from 'dcmjs';
|
||||||
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
|
import FileLoader from './fileLoader';
|
||||||
|
|
||||||
|
const DICOMFileLoader = new (class extends FileLoader {
|
||||||
|
fileType = 'application/dicom';
|
||||||
|
loadFile(file, imageId) {
|
||||||
|
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataset(image, imageId) {
|
||||||
|
let dataset = {};
|
||||||
|
try {
|
||||||
|
const dicomData = dcmjs.data.DicomMessage.readFile(image);
|
||||||
|
dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||||
|
dicomData.dict
|
||||||
|
);
|
||||||
|
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(
|
||||||
|
dicomData.meta
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error on getting dicom file dataset. It defaults to empty');
|
||||||
|
}
|
||||||
|
// Set imageId on dataset to be consumed later on
|
||||||
|
dataset.imageId = imageId;
|
||||||
|
|
||||||
|
return dataset;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStudies(dataset, imageId) {
|
||||||
|
return this.getStudyFromDataset(dataset);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStudyFromDataset(dataset = {}) {
|
||||||
|
const {
|
||||||
|
StudyInstanceUID,
|
||||||
|
StudyDate,
|
||||||
|
StudyTime,
|
||||||
|
AccessionNumber,
|
||||||
|
ReferringPhysicianName,
|
||||||
|
PatientName,
|
||||||
|
PatientID,
|
||||||
|
PatientBirthDate,
|
||||||
|
PatientSex,
|
||||||
|
StudyID,
|
||||||
|
StudyDescription,
|
||||||
|
/*
|
||||||
|
NumberOfStudyRelatedSeries,
|
||||||
|
NumberOfStudyRelatedInstances,
|
||||||
|
Modality,
|
||||||
|
ModalitiesInStudy,
|
||||||
|
*/
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SOPClassUID,
|
||||||
|
Rows,
|
||||||
|
Columns,
|
||||||
|
NumberOfFrames,
|
||||||
|
InstanceNumber,
|
||||||
|
imageId,
|
||||||
|
Modality,
|
||||||
|
/*ImageType,
|
||||||
|
InstanceNumber,
|
||||||
|
ImagePositionPatient,
|
||||||
|
ImageOrientationPatient,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
SliceLocation,
|
||||||
|
SamplesPerPixel,
|
||||||
|
PhotometricInterpretation,
|
||||||
|
PlanarConfiguration,
|
||||||
|
PixelSpacing,
|
||||||
|
PixelAspectRatio,
|
||||||
|
BitsAllocated,
|
||||||
|
BitsStored,
|
||||||
|
HighBit,
|
||||||
|
PixelRepresentation,
|
||||||
|
SmallestPixelValue,
|
||||||
|
LargestPixelValue,
|
||||||
|
WindowCenter,
|
||||||
|
WindowWidth,
|
||||||
|
RescaleIntercept,
|
||||||
|
RescaleSlope,
|
||||||
|
RescaleType,
|
||||||
|
Laterality,
|
||||||
|
ViewPosition,
|
||||||
|
AcquisitionDateTime,
|
||||||
|
FrameIncrementPointer,
|
||||||
|
FrameTime,
|
||||||
|
FrameTimeVector,
|
||||||
|
SliceThickness,
|
||||||
|
SpacingBetweenSlices,
|
||||||
|
LossyImageCompression,
|
||||||
|
DerivationDescription,
|
||||||
|
LossyImageCompressionRatio,
|
||||||
|
LossyImageCompressionMethod,
|
||||||
|
EchoNumber,
|
||||||
|
ContrastBolusAgent,
|
||||||
|
*/
|
||||||
|
} = dataset;
|
||||||
|
|
||||||
|
const instance = {
|
||||||
|
sopInstanceUid: SOPInstanceUID,
|
||||||
|
sopClassUid: SOPClassUID,
|
||||||
|
rows: Rows,
|
||||||
|
columns: Columns,
|
||||||
|
numberOfFrames: NumberOfFrames,
|
||||||
|
instanceNumber: InstanceNumber,
|
||||||
|
url: imageId,
|
||||||
|
modality: Modality,
|
||||||
|
/*
|
||||||
|
TODO: in case necessary to uncoment this block, double check every property
|
||||||
|
imageType: ImageType || DICOMWeb.getString(dataset['00080008']),
|
||||||
|
instanceNumber: InstanceNumber || DICOMWeb.getNumber(dataset['00200013']),
|
||||||
|
imagePositionPatient: ImagePositionPatient || DICOMWeb.getString(dataset['00200032']),
|
||||||
|
imageOrientationPatient: ImageOrientationPatient || DICOMWeb.getString(dataset['00200037']),
|
||||||
|
frameOfReferenceUID: FrameOfReferenceUID || DICOMWeb.getString(dataset['00200052']),
|
||||||
|
sliceLocation: SliceLocation || DICOMWeb.getNumber(dataset['00201041']),
|
||||||
|
samplesPerPixel: SamplesPerPixel || DICOMWeb.getNumber(dataset['00280002']),
|
||||||
|
photometricInterpretation: PhotometricInterpretation || DICOMWeb.getString(dataset['00280004']),
|
||||||
|
planarConfiguration: PlanarConfiguration || DICOMWeb.getNumber(dataset['00280006']),
|
||||||
|
pixelSpacing: PixelSpacing || DICOMWeb.getString(dataset['00280030']),
|
||||||
|
pixelAspectRatio: PixelAspectRatio || DICOMWeb.getString(dataset['00280034']),
|
||||||
|
bitsAllocated: BitsAllocated || DICOMWeb.getNumber(dataset['00280100']),
|
||||||
|
bitsStored: BitsStored || DICOMWeb.getNumber(dataset['00280101']),
|
||||||
|
highBit: HighBit || DICOMWeb.getNumber(dataset['00280102']),
|
||||||
|
pixelRepresentation: PixelRepresentation || DICOMWeb.getNumber(dataset['00280103']),
|
||||||
|
smallestPixelValue: SmallestPixelValue || DICOMWeb.getNumber(dataset['00280106']),
|
||||||
|
largestPixelValue: LargestPixelValue || DICOMWeb.getNumber(dataset['00280107']),
|
||||||
|
windowCenter: WindowCenter || DICOMWeb.getString(dataset['00281050']),
|
||||||
|
windowWidth: WindowWidth || DICOMWeb.getString(dataset['00281051']),
|
||||||
|
rescaleIntercept: RescaleIntercept || DICOMWeb.getNumber(dataset['00281052']),
|
||||||
|
rescaleSlope: RescaleSlope || DICOMWeb.getNumber(dataset['00281053']),
|
||||||
|
rescaleType: RescaleType || DICOMWeb.getNumber(dataset['00281054']),
|
||||||
|
sourceImageInstanceUid: getSourceImageInstanceUid(dataset),
|
||||||
|
laterality: Laterality || DICOMWeb.getString(dataset['00200062']),
|
||||||
|
viewPosition: ViewPosition || DICOMWeb.getString(dataset['00185101']),
|
||||||
|
acquisitionDateTime: AcquisitionDateTime || DICOMWeb.getString(dataset['0008002A']),
|
||||||
|
frameIncrementPointer: FrameIncrementPointer || getFrameIncrementPointer(dataset['00280009']),
|
||||||
|
frameTime: FrameTime || DICOMWeb.getNumber(dataset['00181063']),
|
||||||
|
frameTimeVector: FrameTimeVector || parseFloatArray(
|
||||||
|
DICOMWeb.getString(dataset['00181065'])
|
||||||
|
),
|
||||||
|
sliceThickness: SliceThickness || DICOMWeb.getNumber(dataset['00180050']),
|
||||||
|
spacingBetweenSlices: SpacingBetweenSlices || DICOMWeb.getString(dataset['00180088']),
|
||||||
|
lossyImageCompression: LossyImageCompression || DICOMWeb.getString(dataset['00282110']),
|
||||||
|
derivationDescription: DerivationDescription || DICOMWeb.getString(dataset['00282111']),
|
||||||
|
lossyImageCompressionRatio: LossyImageCompressionRatio || DICOMWeb.getString(dataset['00282112']),
|
||||||
|
lossyImageCompressionMethod: LossyImageCompressionMethod || DICOMWeb.getString(dataset['00282114']),
|
||||||
|
echoNumber: EchoNumber || DICOMWeb.getString(dataset['00180086']),
|
||||||
|
contrastBolusAgent: ContrastBolusAgent || DICOMWeb.getString(dataset['00180010']),
|
||||||
|
radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(dataset),
|
||||||
|
wadouri: WADOProxy.convertURL(wadouri, server),
|
||||||
|
wadorsuri: WADOProxy.convertURL(wadorsuri, server),*/
|
||||||
|
};
|
||||||
|
|
||||||
|
const series = {
|
||||||
|
seriesInstanceUid: SeriesInstanceUID,
|
||||||
|
seriesDescription: SeriesDescription,
|
||||||
|
seriesNumber: SeriesNumber,
|
||||||
|
instances: [instance],
|
||||||
|
};
|
||||||
|
|
||||||
|
const study = {
|
||||||
|
studyInstanceUid: StudyInstanceUID,
|
||||||
|
studyDate: StudyDate,
|
||||||
|
studyTime: StudyTime,
|
||||||
|
accessionNumber: AccessionNumber,
|
||||||
|
referringPhysicianName: ReferringPhysicianName,
|
||||||
|
patientName: PatientName,
|
||||||
|
patientId: PatientID,
|
||||||
|
patientBirthdate: PatientBirthDate,
|
||||||
|
patientSex: PatientSex,
|
||||||
|
studyId: StudyID,
|
||||||
|
studyDescription: StudyDescription,
|
||||||
|
/*
|
||||||
|
TODO: in case necessary to uncomment this block, double check every property
|
||||||
|
numberOfStudyRelatedSeries: NumberOfStudyRelatedSeries || DICOMWeb.getString(dataset['00201206']),
|
||||||
|
numberOfStudyRelatedInstances: NumberOfStudyRelatedInstances || DICOMWeb.getString(dataset['00201208']),
|
||||||
|
modality: Modality || DICOMWeb.getString(dataset['00080060']),
|
||||||
|
modalitiesInStudy: ModalitiesInStudy || DICOMWeb.getString(dataset['00080061']),
|
||||||
|
modalities:
|
||||||
|
*/
|
||||||
|
seriesList: [series],
|
||||||
|
};
|
||||||
|
|
||||||
|
return study;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default DICOMFileLoader;
|
||||||
6
platform/viewer/src/lib/localFileLoaders/fileLoader.js
Normal file
6
platform/viewer/src/lib/localFileLoaders/fileLoader.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default class FileLoader {
|
||||||
|
fileType;
|
||||||
|
loadFile(file, imageId) { }
|
||||||
|
getDataset(image, imageId) { }
|
||||||
|
getStudies(dataset, imageId) { }
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
|
import FileLoader from './fileLoader';
|
||||||
|
import PDFFileLoader from './pdfFileLoader';
|
||||||
|
import DICOMFileLoader from './dicomFileLoader';
|
||||||
|
|
||||||
|
class FileLoaderService extends FileLoader {
|
||||||
|
fileType;
|
||||||
|
loader;
|
||||||
|
constructor(file) {
|
||||||
|
super();
|
||||||
|
const fileType = file && file.type;
|
||||||
|
this.loader = this.getLoader(fileType);
|
||||||
|
this.fileType = this.loader.fileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
static groupSeries(studies) {
|
||||||
|
const groupBy = (list, groupByKey, listKey) => {
|
||||||
|
let nonKeyCounter = 1;
|
||||||
|
|
||||||
|
return list.reduce((acc, obj) => {
|
||||||
|
let key = obj[groupByKey];
|
||||||
|
const list = obj[listKey];
|
||||||
|
|
||||||
|
// in case key not found, group it using counter
|
||||||
|
key = !!key ? key : '' + nonKeyCounter++;
|
||||||
|
|
||||||
|
if (!acc[key]) {
|
||||||
|
acc[key] = { ...obj };
|
||||||
|
acc[key][listKey] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[key][listKey].push(...list);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const studiesGrouped = Object.values(
|
||||||
|
groupBy(studies, 'studyInstanceUid', 'seriesList')
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = studiesGrouped.map(studyGroup => {
|
||||||
|
const seriesGrouped = groupBy(
|
||||||
|
studyGroup.seriesList,
|
||||||
|
'seriesInstanceUid',
|
||||||
|
'instances'
|
||||||
|
);
|
||||||
|
studyGroup.seriesList = Object.values(seriesGrouped);
|
||||||
|
|
||||||
|
return studyGroup;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
addFile(file) {
|
||||||
|
return cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFile(file, imageId) {
|
||||||
|
return this.loader.loadFile(file, imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataset(image, imageId) {
|
||||||
|
return this.loader.getDataset(image, imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStudies(dataset, imageId) {
|
||||||
|
return this.loader.getStudies(dataset, imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoader(fileType) {
|
||||||
|
if (fileType === 'application/pdf') {
|
||||||
|
return PDFFileLoader;
|
||||||
|
} else {
|
||||||
|
// Default to dicom loader
|
||||||
|
return DICOMFileLoader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileLoaderService;
|
||||||
58
platform/viewer/src/lib/localFileLoaders/pdfFileLoader.js
Normal file
58
platform/viewer/src/lib/localFileLoaders/pdfFileLoader.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
|
import FileLoader from './fileLoader';
|
||||||
|
|
||||||
|
const PDFFileLoader = new (class extends FileLoader {
|
||||||
|
fileType = 'application/pdf';
|
||||||
|
loadFile(file, imageId) {
|
||||||
|
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataset(image, imageId) {
|
||||||
|
const dataset = {};
|
||||||
|
dataset.imageId = image.imageId || imageId;
|
||||||
|
return dataset;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStudies(dataset, imageId) {
|
||||||
|
return this.getDefaultStudy(imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultStudy(imageId) {
|
||||||
|
const study = {
|
||||||
|
studyInstanceUid: '',
|
||||||
|
studyDate: '',
|
||||||
|
studyTime: '',
|
||||||
|
accessionNumber: '',
|
||||||
|
referringPhysicianName: '',
|
||||||
|
patientName: '',
|
||||||
|
patientId: '',
|
||||||
|
patientBirthdate: '',
|
||||||
|
patientSex: '',
|
||||||
|
studyId: '',
|
||||||
|
studyDescription: '',
|
||||||
|
seriesList: [
|
||||||
|
{
|
||||||
|
seriesInstanceUid: '',
|
||||||
|
seriesDescription: '',
|
||||||
|
seriesNumber: '',
|
||||||
|
instances: [
|
||||||
|
{
|
||||||
|
sopInstanceUid: '',
|
||||||
|
sopClassUid: '1.2.840.10008.5.1.4.1.1.104.1',
|
||||||
|
rows: '',
|
||||||
|
columns: '',
|
||||||
|
numberOfFrames: 0,
|
||||||
|
instanceNumber: 1,
|
||||||
|
getImageId: () => imageId,
|
||||||
|
isLocalFile: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return study;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default PDFFileLoader;
|
||||||
Loading…
Reference in New Issue
Block a user