Merge pull request #556 from OHIF/vtk-extension-changes

refactor(@ohif/extension-vtk): Update VTK extension to add basic MPR …
This commit is contained in:
Erik Ziegler 2019-06-15 13:44:59 +02:00 committed by GitHub
commit d0d757d468
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 3161 additions and 3900 deletions

View File

@ -1,71 +1,81 @@
{
"name": "@ohif/extension-vtk",
"version": "0.0.3",
"version": "0.0.4",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "dist/index.es.js",
"jsnext:main": "dist/index.es.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build",
"start": "rollup -c -w",
"build": "webpack --progress --colors --mode development",
"build:release": "webpack --progress --colors --mode production",
"start": "webpack --watch --progress --colors --mode development",
"prepare": "yarn run build:release",
"predeploy": "cd example && yarn install && yarn run build:release",
"prepublishOnly": "yarn run build:release",
"lint": "eslint -c .eslintrc --fix src && prettier --single-quote --write src/**/*.{js,jsx,json,css}"
},
"peerDependencies": {
"@ohif/i18n": "0.0.4",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"react-i18next": "^10.11.0",
"cornerstone-core": "^2.2.8",
"cornerstone-wado-image-loader": "^2.2.3",
"dcmjs": "^0.3.6",
"dcmjs": "^0.4.7",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"ohif-core": "^0.3.3",
"@ohif/i18n": "0.0.4",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0",
"react-redux": "^6.0.0",
"react-resize-detector": "^3.4.0",
"react-viewerbase": "^0.6.0",
"ohif-core": "^0.5.9",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.1.0",
"react-resize-detector": "^4.2.0",
"react-viewerbase": "^0.8.1",
"redux": "^4.0.1"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/core": "^7.4.5",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"react-i18next": "^10.11.0",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.6",
"cornerstone-tools": "^3.13.0",
"cornerstone-wado-image-loader": "^2.2.3",
"cross-env": "^5.2.0",
"eslint": "5.13.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-react": "^7.11.1",
"dcmjs": "^0.4.7",
"dicom-parser": "^1.8.3",
"eslint": "5.16.0",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react": "^7.13.0",
"gh-pages": "^2.0.1",
"husky": "^1.3.1",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3",
"husky": "^2.4.1",
"lint-staged": "^8.2.0",
"ohif-core": "^0.5.9",
"prettier": "^1.18.2",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"rollup": "^1.1.2",
"rollup-plugin-babel": "^4.2.0",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-copy": "^2.0.1",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-peer-deps-external": "^2.2.0",
"rollup-plugin-postcss": "^2.0.3",
"rollup-plugin-url": "^2.1.0",
"stylelint": "^9.9.0",
"stylelint-config-recommended": "^2.1.0",
"stylus": "^0.54.5"
"react-redux": "^7.1.0",
"react-viewerbase": "^0.8.1",
"redux": "^4.0.1",
"shader-loader": "^1.3.1",
"stylelint": "^10.1.0",
"stylelint-config-recommended": "^2.2.0",
"stylus": "^0.54.5",
"webpack": "^4.33.0",
"webpack-cli": "^3.3.4",
"worker-loader": "^2.0.0"
},
"husky": {
"hooks": {
@ -92,8 +102,8 @@
"access": "public"
},
"dependencies": {
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.6",
"react-vtkjs-viewport": "^0.0.7"
"@babel/runtime": "^7.4.5",
"@ohif/i18n": "0.0.4",
"react-vtkjs-viewport": "0.0.8"
}
}

View File

@ -5,33 +5,50 @@ import OHIF from 'ohif-core';
const { setToolActive } = OHIF.redux.actions;
const mapStateToProps = state => {
const activeButton = state.tools.buttons.find(tool => tool.active === true);
return {
buttons: [
{
command: 'Crosshairs',
type: 'tool',
text: 'Crosshairs',
icon: 'crosshairs',
active: true,
onClick: () => {
// TODO: Make these use setToolActive instead
window.commandsManager.runCommand('enableCrosshairsTool', {}, 'vtk');
}
},
{
command: 'WWWC',
type: 'tool',
text: 'WWWC',
icon: 'level',
active: true,
onClick: () => {
// TODO: Make these use setToolActive instead
window.commandsManager.runCommand('enableLevelTool', {}, 'vtk');
}
}
/* Disabled temporarily due to issues with the interactor style
{
command: 'Rotate',
type: 'tool',
text: 'Rotate',
icon: '3d-rotate',
active: true
}
active: false,
onClick: () => {
// TODO: Make these use setToolActive instead
window.commandsManager.runCommand('enableRotateTool', {}, 'vtk');
}
},*/
],
activeCommand: 'Rotate'
};
};
const mapDispatchToProps = dispatch => {
return {
setToolActive: tool => {
//dispatch(setToolActive(tool.command))
}
activeCommand: 'Crosshairs'
};
};
const ConnectedToolbarSection = connect(
mapStateToProps,
mapDispatchToProps
null
)(ToolbarSection);
export default ConnectedToolbarSection;

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { VTKMPRViewport } from 'react-vtkjs-viewport';
import { View2D } from 'react-vtkjs-viewport';
import OHIF from 'ohif-core';
const {
@ -17,7 +17,7 @@ const mapStateToProps = (state, ownProps) => {
}
// If this is the active viewport, enable prefetching.
const { viewportIndex } = ownProps; //.viewportData;
const { viewportIndex } = ownProps;
const isActive = viewportIndex === state.viewports.activeViewportIndex;
const viewportSpecificData =
state.viewports.viewportSpecificData[viewportIndex] || {};
@ -31,7 +31,7 @@ const mapStateToProps = (state, ownProps) => {
...pluginDetails,
activeTool: activeButton && activeButton.command,
...dataFromStore,
enableStackPrefetch: isActive,
enableStackPrefetch: isActive
};
};
@ -45,17 +45,44 @@ const mapDispatchToProps = (dispatch, ownProps) => {
setViewportSpecificData: data => {
dispatch(setViewportSpecificData(viewportIndex, data));
},
clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData(viewportIndex));
}
};
};
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
const { afterCreation } = propsFromState;
const { setViewportSpecificData } = propsFromDispatch;
const props = {
...propsFromState,
...propsFromDispatch,
...ownProps,
/**
* Our component sets up the underlying dom element on "componentDidMount"
* for use with VTK.
*
* The onCreated prop passes back an Object containing many of the internal
* components of the VTK scene. We can grab a reference to these here, to
* make playing with VTK's native methods easier.
*
* A similar approach is taken with the Cornerstone extension.
*/
onCreated: api => {
// Store the API details for later
//setViewportSpecificData({ vtkApi: api });
if (afterCreation && typeof afterCreation === 'function') {
afterCreation(api);
}
}
};
return props;
};
const ConnectedVTKViewport = connect(
mapStateToProps,
//mapDispatchToProps
)(VTKMPRViewport);
mapDispatchToProps,
mergeProps
)(View2D);
export default ConnectedVTKViewport;

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import OHIFVTKViewport from './OHIFVTKViewport.js';
import ToolbarModule from './ToolbarModule.js';
import { definitions } from './commands';
/**
* Pass in 'children' to a React component. The purpose of this is to
@ -13,7 +14,7 @@ import ToolbarModule from './ToolbarModule.js';
*/
function withChildren(WrappedComponent, children) {
return function(props) {
return <WrappedComponent children={children} { ...props } />;
return <WrappedComponent children={children} {...props} />;
};
}
@ -21,8 +22,10 @@ function withChildren(WrappedComponent, children) {
// https://github.com/whitecolor/yalc
export default class OHIFVTKExtension {
constructor(children) {
constructor({ children, commandsManager }) {
this.children = children;
_registerCommands(commandsManager, definitions, this.getExtensionId());
}
/**
@ -52,3 +55,21 @@ export default class OHIFVTKExtension {
return ToolbarModule;
}
}
/**
* Register all Viewer commands
*
* @private
*/
function _registerCommands(commandsManager, definitions, commandContext) {
commandsManager.createContext(commandContext);
Object.keys(definitions).forEach(commandName => {
const commandDefinition = definitions[commandName];
commandsManager.registerCommand(
commandContext,
commandName,
commandDefinition
);
});
}

View File

@ -1,10 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import ConnectedVTKViewport from './ConnectedVTKViewport';
import cornerstone from 'cornerstone-core';
import handleSegmentationStorage from './handleSegmentationStorage.js';
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
import ConnectedVTKViewport from './ConnectedVTKViewport';
import handleSegmentationStorage from './handleSegmentationStorage.js';
import LoadingIndicator from './LoadingIndicator.js';
const { StackManager } = OHIF.utils;
@ -27,9 +33,36 @@ specialCaseHandlers[
SOP_CLASSES.SEGMENTATION_STORAGE
] = handleSegmentationStorage;
// TODO: Figure out where we plan to put this long term
const volumeCache = {};
/**
* Create a labelmap image with the same dimensions as our background volume.
*
* @param backgroundImageData vtkImageData
*/
function createLabelMapImageData(backgroundImageData) {
const labelMapData = vtkImageData.newInstance(
backgroundImageData.get('spacing', 'origin', 'direction')
);
labelMapData.setDimensions(backgroundImageData.getDimensions());
labelMapData.computeTransforms();
const values = new Uint8Array(backgroundImageData.getNumberOfPoints());
const dataArray = vtkDataArray.newInstance({
numberOfComponents: 1, // labelmap with single component
values
});
labelMapData.getPointData().setScalars(dataArray);
return labelMapData;
}
class OHIFVTKViewport extends Component {
state = {
viewportData: null
volumes: null,
paintFilterLabelMapImageData: null,
paintFilterBackgroundImageData: null
};
static propTypes = {
@ -39,14 +72,14 @@ class OHIFVTKViewport extends Component {
children: PropTypes.node
};
static id = 'OHIFCornerstoneViewport';
static id = 'OHIFVTKViewport';
static init() {
console.log('OHIFCornerstoneViewport init()');
console.log('OHIFVTKViewport init()');
}
static destroy() {
console.log('OHIFCornerstoneViewport destroy()');
console.log('OHIFVTKViewport destroy()');
StackManager.clearStacks();
}
@ -97,22 +130,6 @@ class OHIFVTKViewport extends Component {
return stack;
}
static getViewportData = (
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
) => {
return OHIFVTKViewport.getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
);
};
getViewportData = (
studies,
studyInstanceUid,
@ -121,63 +138,64 @@ class OHIFVTKViewport extends Component {
sopInstanceUid,
frameIndex
) => {
return new Promise((resolve, reject) => {
const stack = OHIFVTKViewport.getViewportData(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
);
const stack = OHIFVTKViewport.getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
);
let imageDataObject;
let labelmapDataObject;
let doneLoadingCallback;
let callbacks;
let imageDataObject;
let labelmapDataObject;
switch (sopClassUid) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
throw new Error("Not yet implemented");
switch (sopClassUid) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
throw new Error('Not yet implemented');
const data = handleSegmentationStorage(stack.imageIds, displaySetInstanceUid, cornerstone);
imageDataObject = data.referenceDataObject;
labelmapDataObject = data.labelmapDataObject;
const data = handleSegmentationStorage(
stack.imageIds,
displaySetInstanceUid
);
doneLoadingCallback = () => {
resolve({
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject
});
imageDataObject = data.referenceDataObject;
labelmapDataObject = data.labelmapDataObject;
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject
};
});
default:
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
callbacks = [
doneLoadingCallback
];
loadImageData(imageDataObject, callbacks, cornerstone);
break;
default:
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid, cornerstone);
doneLoadingCallback = () => {
resolve({
data: imageDataObject.vtkImageData,
});
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData
};
callbacks = [
doneLoadingCallback
];
loadImageData(imageDataObject, callbacks, cornerstone);
break;
}
});
});
}
};
setStateFromProps() {
getOrCreateVolume(data, displaySetInstanceUid) {
if (volumeCache[displaySetInstanceUid]) {
return volumeCache[displaySetInstanceUid];
}
const volumeActor = vtkVolume.newInstance();
const volumeMapper = vtkVolumeMapper.newInstance();
volumeActor.setMapper(volumeMapper);
volumeMapper.setInputData(data);
volumeCache[displaySetInstanceUid] = volumeActor;
return volumeActor;
}
async setStateFromProps() {
const { studies, displaySet } = this.props.viewportData;
const {
studyInstanceUid,
@ -195,18 +213,25 @@ class OHIFVTKViewport extends Component {
const sopClassUid = sopClassUids[0];
this.getViewportData(
let { data, labelmap } = await this.getViewportData(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
).then(({ data, labelmap })=> {
this.setState({
viewportData: data,
labelmap
});
);
if (!labelmap) {
labelmap = createLabelMapImageData(data);
}
const volumeActor = this.getOrCreateVolume(data, displaySetInstanceUid);
this.setState({
volumes: [volumeActor],
paintFilterBackgroundImageData: data,
paintFilterLabelMapImageData: labelmap
});
}
@ -245,15 +270,22 @@ class OHIFVTKViewport extends Component {
return (
<>
{this.state.viewportData ? (
{this.state.volumes ? (
<ConnectedVTKViewport
data={this.state.viewportData}
labelmap={this.state.labelmap}
volumes={this.state.volumes}
paintFilterLabelMapImageData={
this.state.paintFilterLabelMapImageData
}
paintFilterBackgroundImageData={
this.state.paintFilterBackgroundImageData
}
viewportIndex={this.props.viewportIndex}
/>
) : <div style={style}>
<LoadingIndicator/>
</div>}
) : (
<div style={style}>
<LoadingIndicator />
</div>
)}
{childrenWithProps}
</>
);

View File

@ -0,0 +1,273 @@
import {
vtkInteractorStyleMPRWindowLevel,
vtkInteractorStyleMPRSlice,
vtkInteractorStyleMPRCrosshairs
} from 'react-vtkjs-viewport';
import setViewportToVTK from './utils/setViewportToVTK.js';
import setMPRLayout from './utils/setMPRLayout.js';
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';
// TODO: Should be another way to get this
const commandsManager = window.commandsManager;
// TODO: Put this somewhere else
let apis = {};
function getCrosshairCallbackForIndex(index) {
return worldPos => {
// Set camera focal point to world coordinate for linked views
apis.forEach((api, viewportIndex) => {
if (viewportIndex === index) {
return;
}
// We are basically doing the same as getSlice but with the world coordinate
// that we want to jump to instead of the camera focal point.
// I would rather do the camera adjustment directly but I keep
// doing it wrong and so this is good enough for now.
const renderWindow = api.genericRenderWindow.getRenderWindow();
const istyle = renderWindow.getInteractor().getInteractorStyle();
const sliceNormal = istyle.getSliceNormal();
const transform = vtkMatrixBuilder
.buildFromDegree()
.identity()
.rotateFromDirections(sliceNormal, [1, 0, 0]);
const mutatedWorldPos = worldPos.slice();
transform.apply(mutatedWorldPos);
const slice = mutatedWorldPos[0];
istyle.setSlice(slice);
renderWindow.render();
});
};
}
async function _getActiveViewportVTKApi(viewports) {
const { layout, viewportSpecificData, activeViewportIndex } = viewports;
const currentData = layout.viewports[activeViewportIndex];
if (currentData && currentData.plugin === 'vtk') {
// TODO: I was storing/pulling this from Redux but ran into weird issues
if (apis[activeViewportIndex]) {
return apis[activeViewportIndex];
}
}
const displaySet = viewportSpecificData[activeViewportIndex];
let api;
if (!api) {
try {
api = await setViewportToVTK(
displaySet,
activeViewportIndex,
layout,
viewportSpecificData
);
} catch (error) {
throw new Error(error);
}
}
return api;
}
function _setView(api, sliceNormal, viewUp) {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const renderer = api.genericRenderWindow.getRenderer();
const camera = renderer.getActiveCamera();
const istyle = renderWindow.getInteractor().getInteractorStyle();
istyle.setSliceNormal(...sliceNormal);
camera.setViewUp(...viewUp);
api.volumes[0].getMapper().setSampleDistance(5.0);
api.volumes[0].getMapper().setMaximumSamplesPerRay(2000);
renderWindow.render();
}
function switchMPRInteractors(api, istyle) {
const renderWindow = api.genericRenderWindow.getRenderWindow();
let currentNormal;
if (istyle.getSliceNormal) {
currentNormal = istyle.getSliceNormal();
}
// TODO: This is a hacky workaround because disabling the vtkInteractorStyleMPRSlice is currently
// broken. The camera.onModified is never removed. (https://github.com/Kitware/vtk-js/issues/1110)
renderWindow
.getInteractor()
.getInteractorStyle()
.setInteractor(null);
renderWindow.getInteractor().setInteractorStyle(istyle);
// TODO: Not sure why this is required the second time this function is called
istyle.setInteractor(renderWindow.getInteractor());
if (istyle.getVolumeMapper() !== api.volumes[0]) {
if (currentNormal) {
istyle.setSliceNormal(currentNormal);
}
istyle.setVolumeMapper(api.volumes[0]);
}
}
const actions = {
axial: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [0, 0, 1], [0, -1, 0]);
},
sagittal: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [1, 0, 0], [0, 0, 1]);
},
coronal: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [0, 1, 0], [0, 0, 1]);
},
enableRotateTool: async ({ viewports }) => {
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRSlice.newInstance();
switchMPRInteractors(api, istyle);
});
},
enableCrosshairsTool: async ({ viewports }) => {
apis.forEach((api, index) => {
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
switchMPRInteractors(api, istyle);
istyle.setCallback(getCrosshairCallbackForIndex(index));
});
},
enableLevelTool: async ({ viewports }) => {
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
switchMPRInteractors(api, istyle);
});
},
mpr2d: async ({ viewports }) => {
const displaySet =
viewports.viewportSpecificData[viewports.activeViewportIndex];
let apiByViewport;
try {
apiByViewport = await setMPRLayout(displaySet);
} catch (error) {
throw new Error(error);
}
apis = apiByViewport;
/*const rgbTransferFunction = apiByViewport[0].volumes[0]
.getProperty()
.getRGBTransferFunction(0);
rgbTransferFunction.onModified(() => {
apiByViewport.forEach(a => {
const renderWindow = a.genericRenderWindow.getRenderWindow();
renderWindow.render();
});
});*/
apis[0].volumes[0].getMapper().setSampleDistance(1.5);
apiByViewport.forEach((api, index) => {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const renderer = api.genericRenderWindow.getRenderer();
const camera = renderer.getActiveCamera();
// TODO: This is a hacky workaround because disabling the vtkInteractorStyleMPRSlice is currently
// broken. The camera.onModified is never removed. (https://github.com/Kitware/vtk-js/issues/1110)
renderWindow
.getInteractor()
.getInteractorStyle()
.setInteractor(null);
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
renderWindow.getInteractor().setInteractorStyle(istyle);
istyle.setVolumeMapper(api.volumes[0]);
istyle.setCallback(getCrosshairCallbackForIndex(index));
switch (index) {
default:
case 0:
//Axial
istyle.setSliceNormal(0, 0, 1);
camera.setViewUp(0, -1, 0);
break;
case 1:
// sagittal
istyle.setSliceNormal(1, 0, 0);
camera.setViewUp(0, 0, 1);
break;
case 2:
// Coronal
istyle.setSliceNormal(0, 1, 0);
camera.setViewUp(0, 0, 1);
break;
}
renderWindow.render();
});
}
};
const definitions = {
axial: {
commandFn: actions.axial,
storeContexts: ['viewports'],
options: {}
},
coronal: {
commandFn: actions.coronal,
storeContexts: ['viewports'],
options: {}
},
sagittal: {
commandFn: actions.sagittal,
storeContexts: ['viewports'],
options: {}
},
enableRotateTool: {
commandFn: actions.enableRotateTool,
storeContexts: ['viewports'],
options: {}
},
enableCrosshairsTool: {
commandFn: actions.enableCrosshairsTool,
storeContexts: ['viewports'],
options: {}
},
enableLevelTool: {
commandFn: actions.enableLevelTool,
storeContexts: ['viewports'],
options: {}
},
mpr2d: {
commandFn: actions.mpr2d,
storeContexts: ['viewports'],
options: {}
}
};
export { definitions };

View File

@ -1,5 +1,6 @@
import OHIF from 'ohif-core';
import * as dcmjs from 'dcmjs';
import { api } from 'dicomweb-client';
const { StackManager } = OHIF.utils;
@ -47,12 +48,25 @@ function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) {
return stackClone;
}
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());
function retrieveDicomData(
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
wadoRoot
) {
const config = {
url: wadoRoot,
headers: DICOMWeb.getAuthorizationHeader()
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID
};
return dicomWeb.retrieveInstance(options);
}
async function handleSegmentationStorage(
@ -68,8 +82,22 @@ async function handleSegmentationStorage(
studyInstanceUid,
displaySetInstanceUid
);
const segWadoUri = displaySet.images[0].getData().wadouri;
const arrayBuffer = await retrieveDicomData(segWadoUri);
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
// from google cloud
const wadoRoot = displaySet.images[0].getData().wadoRoot;
const StudyInstanceUID = displaySet.images[0].getStudyInstanceUID();
const SeriesInstanceUID = displaySet.images[0].getSeriesInstanceUID();
const SOPInstanceUID = displaySet.images[0].getSOPInstanceUID();
const arrayBuffer = await retrieveDicomData(
StudyInstanceUID,
SeriesInstanceUID,
SOPInstanceUID,
wadoRoot
);
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
@ -109,7 +137,7 @@ async function handleSegmentationStorage(
return {
referenceDataObject,
labelmapDataObject
}
};
return {
studyInstanceUid,

View File

@ -0,0 +1,10 @@
import { redux } from 'ohif-core';
const { setViewportLayoutAndData } = redux.actions;
// TODO: Should not be getting dispatch from the window, but I'm not sure how else to do it cleanly
export default function setLayoutAndViewportData(layout, viewportSpecificData) {
const action = setViewportLayoutAndData(layout, viewportSpecificData);
window.store.dispatch(action);
}

View File

@ -0,0 +1,61 @@
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
import setSingleLayoutData from './setSingleLayoutData.js';
export default function setMPRLayout(displaySet) {
return new Promise((resolve, reject) => {
let viewports = [];
const rows = 1;
const columns = 3;
const numViewports = rows * columns;
const viewportSpecificData = {};
for (let i = 0; i < numViewports; i++) {
viewports.push({
height: `${100 / rows}%`,
width: `${100 / columns}%`
});
viewportSpecificData[i] = displaySet;
viewportSpecificData[i].plugin = 'vtk';
}
const layout = {
viewports
};
const viewportIndices = [0, 1, 2];
let updatedViewports = layout.viewports;
const apis = [];
viewportIndices.forEach(viewportIndex => {
apis[viewportIndex] = null;
/*const currentData = layout.viewports[viewportIndex];
if (currentData && currentData.plugin === 'vtk') {
reject(new Error('Should not have reached this point??'));
}*/
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
apis[viewportIndex] = api;
if (apis.every(a => !!a)) {
resolve(apis);
}
}
}
};
updatedViewports = setSingleLayoutData(
updatedViewports,
viewportIndex,
data
);
});
setLayoutAndViewportData(
{ viewports: updatedViewports },
viewportSpecificData
);
});
}

View File

@ -0,0 +1,12 @@
export default function setSingleLayoutData(
originalArray,
viewportIndex,
data
) {
const viewports = originalArray.slice();
const layoutData = Object.assign({}, viewports[viewportIndex], data);
viewports[viewportIndex] = layoutData;
return viewports;
}

View File

@ -0,0 +1,39 @@
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
import setSingleLayoutData from './setSingleLayoutData.js';
export default function setViewportToVTK(
displaySet,
viewportIndex,
layout,
viewportSpecificData
) {
return new Promise((resolve, reject) => {
/*const currentData = layout.viewports[viewportIndex];
if (currentData && currentData.plugin === 'vtk') {
reject(new Error('Should not have reached this point??'));
}*/
const data = {
plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
resolve(api);
}
}
};
const updatedViewports = setSingleLayoutData(
layout.viewports,
viewportIndex,
data
);
const updatedViewportData = viewportSpecificData;
setLayoutAndViewportData(
{ viewports: updatedViewports },
updatedViewportData
);
});
}

View File

@ -0,0 +1,108 @@
var path = require('path')
var webpack = require('webpack')
const autoprefixer = require('autoprefixer');
const cssRules = [
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer('last 2 version', 'ie >= 10')],
},
},
],
},
{
test: /\.glsl$/i,
include: /vtk\.js[\/\\]Sources/,
loader: 'shader-loader',
},
{
test: /\.worker\.js$/,
include: /vtk\.js[\/\\]Sources/,
use: [
{
loader: 'worker-loader',
options: { inline: true, fallback: false },
},
],
},
{
test: /\.css$/,
include: /\.module\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
localIdentName: '[name]-[local]_[sha512:hash:base64:5]',
modules: true,
},
},
{
loader: 'postcss-loader',
options: {
plugins: () => [autoprefixer('last 2 version', 'ie >= 10')],
},
},
],
},
];
var entry = path.join(__dirname, './src/index.js')
const sourcePath = path.join(__dirname, './src')
const outputPath = path.join(__dirname, './dist')
module.exports = {
entry,
output: {
path: outputPath,
filename: 'index.umd.js',
library: '@ohif/extension-vtk',
libraryTarget: 'umd',
globalObject: 'this',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
}
].concat(cssRules),
},
resolve: {
modules: [path.resolve(__dirname, 'node_modules'), sourcePath],
},
externals: [{
'cornerstone-core': {
commonjs: 'cornerstone-core',
commonjs2: 'cornerstone-core',
amd: 'cornerstone-core',
root: 'cornerstone',
},
'cornerstone-math': {
commonjs: 'cornerstone-math',
commonjs2: 'cornerstone-math',
amd: 'cornerstone-math',
root: 'cornerstoneMath',
},
},
'ohif-core',
'dcmjs',
'react-viewerbase',
'react',//: 'React',
'react-dom',//: 'ReactDOM',
'react-redux',//: 'ReactRedux',
'react-resize-detector',//: 'ReactResizeDetector',
'react-viewerbase',//: 'reactViewerbase',
'prop-types'//: 'PropTypes'
/*/\b(vtk.js)/*/
],
}

File diff suppressed because it is too large Load Diff