Add DICOM PDF plugin
This commit is contained in:
parent
fe68719eca
commit
4d0822e47e
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -7,3 +7,6 @@
|
||||
[submodule "Packages-react/react-cornerstone-viewport"]
|
||||
path = Packages-react/react-cornerstone-viewport
|
||||
url = git@github.com:cornerstonejs/react-cornerstone-viewport.git
|
||||
[submodule "Packages-react/plugins/VTKPlugin"]
|
||||
path = Packages-react/plugins/VTKPlugin
|
||||
url = git@github.com:OHIF/VTKPlugin.git
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"cornerstone-tools": "3.0.0-b.1512",
|
||||
"cornerstone-wado-image-loader": "2.2.3",
|
||||
"dicom-parser": "1.8.3",
|
||||
"dicomweb-client": "^0.4.1",
|
||||
"hammerjs": "^2.0.8",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -27,6 +28,7 @@
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-scripts": "^2.1.3",
|
||||
"react-viewerbase": "^0.2.1",
|
||||
"react-vtkjs-viewport": "^0.0.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-oidc": "^3.1.0"
|
||||
},
|
||||
|
||||
@ -44,6 +44,12 @@ class App extends Component {
|
||||
|
||||
const userNotLoggedIn = userManager && (!user || user.expired);
|
||||
if (userNotLoggedIn) {
|
||||
const pathname = this.props.location.pathname;
|
||||
|
||||
if (pathname !== '/callback') {
|
||||
sessionStorage.setItem('ohif-redirect-to', pathname);
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
||||
|
||||
@ -14,7 +14,9 @@ class CallbackPage extends Component {
|
||||
<CallbackComponent
|
||||
userManager={this.props.userManager}
|
||||
successCallback={() => {
|
||||
this.props.history.push("/");
|
||||
const pathname = sessionStorage.getItem('ohif-redirect-to');
|
||||
|
||||
this.props.history.push(pathname);
|
||||
}}
|
||||
errorCallback={error => {
|
||||
//this.props.history.push("/");
|
||||
|
||||
@ -35,7 +35,13 @@ class FlexboxLayout extends Component {
|
||||
|
||||
const thumbnails = study.displaySets.map((displaySet) => {
|
||||
const { displaySetInstanceUid, seriesDescription, seriesNumber, instanceNumber, numImageFrames } = displaySet;
|
||||
const imageId = displaySet.images[0].getImageId();
|
||||
|
||||
|
||||
let imageId;
|
||||
|
||||
if (displaySet.images && displaySet.images.length) {
|
||||
imageId = displaySet.images[0].getImageId();
|
||||
}
|
||||
|
||||
return {
|
||||
imageId,
|
||||
|
||||
@ -9,7 +9,7 @@ OHIFComponentPlugin.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
init: PropTypes.func.isRequired,
|
||||
destroy: PropTypes.func.isRequired,
|
||||
children: PropTypes.node.isRequired
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export default OHIFComponentPlugin;
|
||||
@ -2,7 +2,7 @@ import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import OHIF from 'ohif-core';
|
||||
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
|
||||
import OHIFComponentPlugin from './OHIFComponentPlugin.js';
|
||||
import OHIFComponentPlugin from '../OHIFComponentPlugin.js';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
import React, { Component } from "react";
|
||||
import dicomParser from 'dicom-parser';
|
||||
import TypedArrayProp from './TypedArrayProp';
|
||||
|
||||
// TODO: Should probably use dcmjs for this
|
||||
const SOP_CLASS_UIDS = {
|
||||
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1'
|
||||
};
|
||||
|
||||
class DicomPDFViewport extends Component {
|
||||
state = {
|
||||
fileURL: null,
|
||||
error: null
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
byteArray: TypedArrayProp.uint8,
|
||||
};
|
||||
|
||||
renderPDF = (dataSet) => {
|
||||
debugger;
|
||||
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;
|
||||
const pdfByteArray = dataSet.byteArray.slice(offset, remainder);
|
||||
const PDF = new Blob([pdfByteArray], {type: 'application/pdf'});
|
||||
const fileURL = URL.createObjectURL(PDF);
|
||||
|
||||
this.setState({
|
||||
fileURL
|
||||
});
|
||||
}
|
||||
|
||||
parseByteArray = (byteArray) => {
|
||||
const options = {
|
||||
untilTag: ''
|
||||
};
|
||||
|
||||
let dataSet;
|
||||
|
||||
try {
|
||||
dataSet = dicomParser.parseDicom(byteArray, options);
|
||||
} catch(error) {
|
||||
this.setState({
|
||||
error
|
||||
});
|
||||
}
|
||||
|
||||
return dataSet
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const dataSet = this.parseByteArray(this.props.byteArray);
|
||||
|
||||
this.renderPDF(dataSet);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={'DicomPDFViewport'} style={{width: '100%', height: '100%'}}>
|
||||
{this.state.fileURL &&
|
||||
<object data={this.state.fileURL} type="application/pdf" width="100%" height="100%">
|
||||
</object>
|
||||
}
|
||||
{this.state.error &&
|
||||
<h2>{JSON.stringify(this.state.error)}</h2>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default DicomPDFViewport;
|
||||
@ -0,0 +1,32 @@
|
||||
import OHIF from "ohif-core";
|
||||
|
||||
const { plugins, utils } = OHIF;
|
||||
const { PLUGIN_TYPES } = plugins;
|
||||
|
||||
// TODO: Should probably use dcmjs for this
|
||||
const SOP_CLASS_UIDS = {
|
||||
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1'
|
||||
};
|
||||
|
||||
const OHIFDicomPDFSopClassHandlerPlugin = {
|
||||
id: 'OHIFDicomPDFSopClassHandlerPlugin',
|
||||
type: PLUGIN_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUids: [
|
||||
SOP_CLASS_UIDS.ENCAPSULATED_PDF
|
||||
],
|
||||
getDisplaySetFromSeries(series, study) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
return {
|
||||
plugin: 'pdf',
|
||||
displaySetInstanceUid: utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default OHIFDicomPDFSopClassHandlerPlugin;
|
||||
@ -0,0 +1,77 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import OHIF from 'ohif-core';
|
||||
import OHIFComponentPlugin from '../OHIFComponentPlugin.js';
|
||||
import DicomPDFViewport from './DicomPDFViewport';
|
||||
|
||||
const { DICOMWeb } = OHIF;
|
||||
|
||||
class OHIFDicomPDFViewportPlugin extends Component {
|
||||
static propTypes = {
|
||||
studies: PropTypes.object,
|
||||
displaySet: PropTypes.object,
|
||||
viewportIndex: PropTypes.number,
|
||||
};
|
||||
|
||||
state = {
|
||||
byteArray: null,
|
||||
error: null
|
||||
}
|
||||
|
||||
static id = 'DicomPDFViewportPlugin';
|
||||
|
||||
static init() {
|
||||
console.log('DicomPDFViewportPlugin init()');
|
||||
}
|
||||
|
||||
static destroy() {
|
||||
console.log('DicomPDFViewportPlugin destroy()');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { displaySet } = this.props.viewportData;
|
||||
const { studyInstanceUid, seriesInstanceUid, sopInstanceUid, wadoRoot, wadoUri } = displaySet;
|
||||
|
||||
this.retrieveDicomData(studyInstanceUid, seriesInstanceUid, sopInstanceUid, wadoRoot, wadoUri).then(byteArray => {
|
||||
this.setState({
|
||||
byteArray
|
||||
});
|
||||
}, error => {
|
||||
this.setState({
|
||||
error
|
||||
});
|
||||
|
||||
throw new Error(error);
|
||||
});
|
||||
}
|
||||
|
||||
retrieveDicomData(studyInstanceUid, seriesInstanceUid, sopInstanceUid, wadoRoot, wadoUri) {
|
||||
// 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: DICOMWeb.getAuthorizationHeader()
|
||||
}).then(response => response.arrayBuffer()).then(arraybuffer => {
|
||||
debugger;
|
||||
return new Uint8Array(arraybuffer);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { id, init, destroy } = OHIFDicomPDFViewportPlugin;
|
||||
const pluginProps = { id, init, destroy };
|
||||
|
||||
return (
|
||||
<OHIFComponentPlugin {...pluginProps}>
|
||||
{this.state.byteArray && <DicomPDFViewport byteArray={this.state.byteArray}/>}
|
||||
{this.state.error &&
|
||||
<h2>{JSON.stringify(this.state.error)}</h2>
|
||||
}
|
||||
</OHIFComponentPlugin>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default OHIFDicomPDFViewportPlugin;
|
||||
@ -0,0 +1,137 @@
|
||||
// https://github.com/facebook/prop-types/issues/69
|
||||
|
||||
export const TypedArrayProp = {
|
||||
any: (props, propName, componentName) => {
|
||||
let obj = props[propName];
|
||||
if (!(obj instanceof Float64Array ||
|
||||
obj instanceof Float32Array ||
|
||||
obj instanceof Int32Array ||
|
||||
obj instanceof Int16Array ||
|
||||
obj instanceof Int8Array ||
|
||||
obj instanceof Uint32Array ||
|
||||
obj instanceof Uint16Array ||
|
||||
obj instanceof Uint8Array ||
|
||||
obj instanceof Uint8ClampedArray)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a typed array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
float64: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Float64Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a Float64Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
float32: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Float32Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a Float32Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
float: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Float64Array ||
|
||||
props[propName] instanceof Float32Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a Float32Array or Float64Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
int32: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Int32Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected an Int32Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
int16: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Int16Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected an In16Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
int8: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Int8Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected an Int8Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
int: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Int32Array ||
|
||||
props[propName] instanceof Int16Array ||
|
||||
props[propName] instanceof Int8Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected an Int32Array, In16Array, or Int8Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
uint32: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Uint32Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a Uint32Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
uint16: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Uint16Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a Uint16Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
uint8: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Uint8Array)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a Uint8Array.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
uint8clamped: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Uint8ClampedArray)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a Uint8ClampedArray.'
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
uint: (props, propName, componentName) => {
|
||||
if (!(props[propName] instanceof Uint32Array ||
|
||||
props[propName] instanceof Uint16Array ||
|
||||
props[propName] instanceof Uint8Array ||
|
||||
props[propName] instanceof Uint8ClampedArray)) {
|
||||
return new Error(
|
||||
'Invalid prop `' + propName + '` supplied to' +
|
||||
' `' + componentName + '`. Expected a Uint32Array, Uint16Array, Uint8Array, or Uint8ClampedArray.'
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export { TypedArrayProp as default };
|
||||
@ -21,13 +21,11 @@ class ViewerMain extends Component {
|
||||
studies: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
getDisplaySetsWithImages(studies) {
|
||||
getDisplaySets(studies) {
|
||||
const displaySets = [];
|
||||
studies.forEach((study) => {
|
||||
study.displaySets.forEach(dSet => {
|
||||
if (dSet.images.length) {
|
||||
displaySets.push(dSet);
|
||||
}
|
||||
displaySets.push(dSet);
|
||||
});
|
||||
});
|
||||
|
||||
@ -53,7 +51,7 @@ class ViewerMain extends Component {
|
||||
//window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
|
||||
|
||||
// Get all the display sets for the viewer studies
|
||||
const displaySets = this.getDisplaySetsWithImages(this.props.studies);
|
||||
const displaySets = this.getDisplaySets(this.props.studies);
|
||||
|
||||
this.setState({
|
||||
viewportData: displaySets
|
||||
@ -66,14 +64,10 @@ class ViewerMain extends Component {
|
||||
|
||||
// Note: Use Slice because React does a shallow equality check. Mutating the array
|
||||
// would not trigger a re-render. We have to create a copy.
|
||||
debugger;
|
||||
console.warn(item);
|
||||
const updatedViewportData = this.state.viewportData.slice(0);
|
||||
|
||||
const displaySet = this.findDisplaySet(this.props.studies, item.studyInstanceUid, item.displaySetInstanceUid);
|
||||
|
||||
console.warn(displaySet);
|
||||
|
||||
updatedViewportData[viewportIndex] = Object.assign({}, displaySet);
|
||||
|
||||
this.setState({
|
||||
@ -101,9 +95,6 @@ class ViewerMain extends Component {
|
||||
OHIF.viewer.updateImageSynchronizer.destroy();
|
||||
|
||||
// TODO: Instruct all plugins to clean up themselves
|
||||
//
|
||||
// Clear references to all stacks in the StackManager
|
||||
//StackManager.clearStacks();
|
||||
|
||||
// @TypeSafeStudies
|
||||
// Clears OHIF.viewer.Studies collection
|
||||
|
||||
@ -11,6 +11,9 @@ import App from './App.js';
|
||||
|
||||
import OHIFCornerstoneViewportPlugin from "./connectedComponents/OHIFCornerstoneViewportPlugin/OHIFCornerstoneViewportPlugin.js";
|
||||
//import ConnectedExampleViewportPlugin from './components/ConnectedExampleViewportPlugin.js';
|
||||
//import OHIFVTKViewportPlugin from './connectedComponents/OHIFVTKViewportPlugin/OHIFVTKViewportPlugin.js';
|
||||
import OHIFDicomPDFViewportPlugin from './connectedComponents/OHIFDicomPDFViewportPlugin/OHIFDicomPDFViewportPlugin.js';
|
||||
import OHIFDicomPDFSopClassHandlerPlugin from './connectedComponents/OHIFDicomPDFViewportPlugin/OHIFDicomPDFSopClassHandlerPlugin.js';
|
||||
|
||||
const reducers = OHIF.redux.reducers;
|
||||
reducers.ui = ui;
|
||||
@ -127,21 +130,44 @@ const buttonsAction = OHIF.redux.actions.setAvailableButtons(defaultButtons);
|
||||
|
||||
store.dispatch(buttonsAction);
|
||||
|
||||
const { plugins } = OHIF;
|
||||
const { PLUGIN_TYPES } = plugins;
|
||||
|
||||
// Uncomment this and comment the Cornerstone version to see how the
|
||||
// example plugin works
|
||||
/*const pluginAction = OHIF.redux.actions.addPlugin({
|
||||
id: 'example',
|
||||
type: 'viewport',
|
||||
type: PLUGIN_TYPES.VIEWPORT,
|
||||
component: ConnectedExampleViewportPlugin
|
||||
});*/
|
||||
|
||||
const pluginAction = OHIF.redux.actions.addPlugin({
|
||||
const cornerstonePluginAction = OHIF.redux.actions.addPlugin({
|
||||
id: 'cornerstone',
|
||||
type: 'viewport',
|
||||
type: PLUGIN_TYPES.VIEWPORT,
|
||||
component: OHIFCornerstoneViewportPlugin
|
||||
});
|
||||
|
||||
store.dispatch(pluginAction);
|
||||
/*const pluginAction = OHIF.redux.actions.addPlugin({
|
||||
id: 'vtk',
|
||||
type: PLUGIN_TYPES.VIEWPORT,
|
||||
component: OHIFVTKViewportPlugin
|
||||
});*/
|
||||
|
||||
const pdfPluginAction = OHIF.redux.actions.addPlugin({
|
||||
id: 'pdf',
|
||||
type: PLUGIN_TYPES.VIEWPORT,
|
||||
component: OHIFDicomPDFViewportPlugin
|
||||
});
|
||||
|
||||
const pdfPluginActionSopClass = OHIF.redux.actions.addPlugin({
|
||||
id: 'pdf_sopClassHandler',
|
||||
type: PLUGIN_TYPES.SOP_CLASS_HANDLER,
|
||||
component: OHIFDicomPDFSopClassHandlerPlugin
|
||||
});
|
||||
|
||||
store.dispatch(cornerstonePluginAction);
|
||||
store.dispatch(pdfPluginAction);
|
||||
store.dispatch(pdfPluginActionSopClass);
|
||||
|
||||
// TODO[react] Use a provider when the whole tree is React
|
||||
window.store = store;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit 305643a1c9d355888643163e8080f06701cb59ed
|
||||
Subproject commit 7ab55fe8f5778def698282d9e6381835897c392d
|
||||
@ -15,7 +15,6 @@ class CallbackPage extends Component {
|
||||
userManager={this.props.userManager}
|
||||
successCallback={() => {
|
||||
const pathname = sessionStorage.getItem('ohif-redirect-to');
|
||||
debugger;
|
||||
|
||||
this.props.history.push(pathname);
|
||||
}}
|
||||
|
||||
@ -38,7 +38,6 @@ class OHIFStandaloneViewer extends Component {
|
||||
|
||||
const userNotLoggedIn = userManager && (!user || user.expired);
|
||||
if (userNotLoggedIn) {
|
||||
debugger;
|
||||
const pathname = this.props.location.pathname;
|
||||
|
||||
if (pathname !== '/callback') {
|
||||
|
||||
1
Packages-react/plugins/VTKPlugin
Submodule
1
Packages-react/plugins/VTKPlugin
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit dd8b3a7edddc5bc97af49bd4ceeb89c5f7d56dfb
|
||||
@ -1 +1 @@
|
||||
Subproject commit 34b0df571866dcd374d6189944c16a863b79e0bd
|
||||
Subproject commit 6baa2cc97f224cc2baef62436014385b9cc6ac4d
|
||||
Loading…
Reference in New Issue
Block a user