diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json
index a3edff389..a551a7d71 100644
--- a/extensions/vtk/package.json
+++ b/extensions/vtk/package.json
@@ -48,7 +48,7 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
- "react-vtkjs-viewport": "^0.1.6"
+ "react-vtkjs-viewport": "^0.3.0"
},
"devDependencies": {
"@ohif/core": "^1.4.0",
diff --git a/extensions/vtk/src/ConnectedVTKViewport.js b/extensions/vtk/src/ConnectedVTKViewport.js
index ae1dc9807..baf50c2c4 100644
--- a/extensions/vtk/src/ConnectedVTKViewport.js
+++ b/extensions/vtk/src/ConnectedVTKViewport.js
@@ -1,6 +1,6 @@
-import OHIF from "@ohif/core";
-import { View2D } from "react-vtkjs-viewport";
-import { connect } from "react-redux";
+import OHIF from '@ohif/core';
+import { View2D } from 'react-vtkjs-viewport';
+import { connect } from 'react-redux';
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
@@ -24,7 +24,7 @@ const mapStateToProps = (state, ownProps) => {
// Hopefully this doesn't break anything under the hood for this one
// activeTool: activeButton && activeButton.command,
...dataFromStore,
- enableStackPrefetch: isActive
+ enableStackPrefetch: isActive,
};
};
@@ -38,7 +38,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
setViewportSpecificData: data => {
dispatch(setViewportSpecificData(viewportIndex, data));
- }
+ },
};
};
@@ -63,10 +63,10 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
// Store the API details for later
//setViewportSpecificData({ vtkApi: api });
- if (afterCreation && typeof afterCreation === "function") {
+ if (afterCreation && typeof afterCreation === 'function') {
afterCreation(api);
}
- }
+ },
};
return props;
};
diff --git a/extensions/vtk/src/LoadingIndicator.css b/extensions/vtk/src/LoadingIndicator.css
index e722fdab5..7c13f0c28 100644
--- a/extensions/vtk/src/LoadingIndicator.css
+++ b/extensions/vtk/src/LoadingIndicator.css
@@ -3,15 +3,15 @@
}
.loadingIndicator {
- background-color: rgba(0, 0, 0, 0.75);
- font-size: 18px;
- height: 100%;
+ background-color: rgba(0, 0, 0, 0);
+ font-size: 8px;
+ height: 20%;
overflow: hidden;
position: absolute;
top: 0;
width: 100%;
z-index: 1;
- /* Necessary for click-through to cornerstone element below */
+ /* Necessary for click-through to vtkjs element below */
pointer-events: none;
}
diff --git a/extensions/vtk/src/LoadingIndicator.js b/extensions/vtk/src/LoadingIndicator.js
index 06bcbf4db..7cb16399e 100644
--- a/extensions/vtk/src/LoadingIndicator.js
+++ b/extensions/vtk/src/LoadingIndicator.js
@@ -1,7 +1,7 @@
import './LoadingIndicator.css';
import React, { PureComponent } from 'react';
-
+import { withTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
class LoadingIndicator extends PureComponent {
@@ -35,7 +35,8 @@ class LoadingIndicator extends PureComponent {
- Loading... {' '}
+ {this.props.t('Reformatting')}...
+
{percComplete}
@@ -46,4 +47,4 @@ class LoadingIndicator extends PureComponent {
}
}
-export default LoadingIndicator;
+export default withTranslation('Common')(LoadingIndicator);
diff --git a/extensions/vtk/src/OHIFVTKViewport.js b/extensions/vtk/src/OHIFVTKViewport.js
index 5bf3dd2c2..4c7065743 100644
--- a/extensions/vtk/src/OHIFVTKViewport.js
+++ b/extensions/vtk/src/OHIFVTKViewport.js
@@ -172,47 +172,51 @@ class OHIFVTKViewport extends Component {
default:
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
- const loadImageDataPromise = loadImageData(imageDataObject);
-
- return loadImageDataPromise.then(() => {
- return {
- data: imageDataObject.vtkImageData,
- };
- });
+ return imageDataObject;
}
};
- getOrCreateVolume(data, displaySetInstanceUid) {
+ getOrCreateVolume(imageDataObject, displaySetInstanceUid) {
if (volumeCache[displaySetInstanceUid]) {
return volumeCache[displaySetInstanceUid];
}
+ const { vtkImageData, imageMetaData0 } = imageDataObject;
+
+ const { windowWidth, windowCenter, modality } = imageMetaData0;
+
+ let lower;
+ let upper;
+
+ if (modality === 'PT') {
+ // For PET just set the range to 0-5 SUV
+ lower = 0;
+ upper = 5;
+ } else {
+ lower = windowCenter - windowWidth / 2.0;
+ upper = windowCenter + windowWidth / 2.0;
+ }
+
const volumeActor = vtkVolume.newInstance();
const volumeMapper = vtkVolumeMapper.newInstance();
volumeActor.setMapper(volumeMapper);
- volumeMapper.setInputData(data);
+ volumeMapper.setInputData(vtkImageData);
- const range = data
- .getPointData()
- .getScalars()
- .getRange();
-
- // TODO: For PET we might want to just set this to 0-5 SUV
volumeActor
.getProperty()
.getRGBTransferFunction(0)
- .setRange(range[0], range[1]);
+ .setRange(lower, upper);
// TODO: Should look into implementing autoAdjustSampleDistance in vtk
- const sampleDistance =
- 1.2 *
- Math.sqrt(
- data
- .getSpacing()
- .map(v => v * v)
- .reduce((a, b) => a + b, 0)
- );
+ // Manually changing the sample distance here by a couple orders of
+ // Magnitude in either direction makes no difference.
+ // Also setting volumeMapper.setMaximumSamplesPerRay(10000)
+ // Prevents the warning of too many samples, but the resolution isn't improved.
+
+ const spacing = vtkImageData.getSpacing();
+ // https://github.com/Kitware/VTK/blob/6b559c65bb90614fb02eb6d1b9e3f0fca3fe4b0b/Rendering/VolumeOpenGL2/vtkSmartVolumeMapper.cxx#L344
+ const sampleDistance = (spacing[0] + spacing[1] + spacing[2]) / 6.0;
volumeMapper.setSampleDistance(sampleDistance);
@@ -221,7 +225,7 @@ class OHIFVTKViewport extends Component {
return volumeActor;
}
- async setStateFromProps() {
+ setStateFromProps() {
const { studies, displaySet } = this.props.viewportData;
const {
studyInstanceUid,
@@ -238,8 +242,7 @@ class OHIFVTKViewport extends Component {
}
const sopClassUid = sopClassUids[0];
-
- let { data, labelmap } = await this.getViewportData(
+ const imageDataObject = this.getViewportData(
studies,
studyInstanceUid,
displaySetInstanceUid,
@@ -254,13 +257,37 @@ class OHIFVTKViewport extends Component {
labelmap = createLabelMapImageData(data);
}*/
- const volumeActor = this.getOrCreateVolume(data, displaySetInstanceUid);
+ const volumeActor = this.getOrCreateVolume(
+ imageDataObject,
+ displaySetInstanceUid
+ );
this.setState({
- volumes: [volumeActor],
- paintFilterBackgroundImageData: data,
- paintFilterLabelMapImageData: labelmap,
+ paintFilterBackgroundImageData: imageDataObject.vtkImageData,
+ paintFilterLabelMapImageData: null, // TODO
+ percentComplete: 0,
});
+
+ this.setState(
+ {
+ paintFilterBackgroundImageData: imageDataObject.vtkImageData,
+ paintFilterLabelMapImageData: null, // TODO
+ percentComplete: 0,
+ },
+ () => {
+ this.loadProgressively(imageDataObject);
+
+ // TODO: There must be a better way to do this.
+ // We do this so that if all the data is available the react-vtkjs-viewport
+ // Will render _something_ before the volumes are set and the volume
+ // Construction that happens in react-vtkjs-viewport locks up the CPU.
+ setTimeout(() => {
+ this.setState({
+ volumes: [volumeActor],
+ });
+ }, 200);
+ }
+ );
}
componentDidMount() {
@@ -285,6 +312,37 @@ class OHIFVTKViewport extends Component {
}
}
+ loadProgressively(imageDataObject) {
+ loadImageData(imageDataObject);
+
+ const { isLoading, insertPixelDataPromises } = imageDataObject;
+
+ const numberOfFrames = insertPixelDataPromises.length;
+
+ if (!isLoading) {
+ this.setState({ isLoaded: true });
+ return;
+ }
+
+ insertPixelDataPromises.forEach(promise => {
+ promise.then(numberProcessed => {
+ const percentComplete = Math.floor(
+ (numberProcessed * 100) / numberOfFrames
+ );
+
+ if (percentComplete !== this.state.percentComplete) {
+ this.setState({
+ percentComplete,
+ });
+ }
+ });
+ });
+
+ Promise.all(insertPixelDataPromises).then(() => {
+ this.setState({ isLoaded: true });
+ });
+ }
+
render() {
let childrenWithProps = null;
@@ -302,21 +360,23 @@ class OHIFVTKViewport extends Component {
return (
<>
- {this.state.volumes ? (
-
- ) : (
-
-
-
+
+ {!this.state.isLoaded && (
+
+ )}
+ {this.state.volumes && (
+
+ )}
+
)}
{childrenWithProps}
>
diff --git a/extensions/vtk/src/commandsModule.js b/extensions/vtk/src/commandsModule.js
index a44e33b8e..be769b3c0 100644
--- a/extensions/vtk/src/commandsModule.js
+++ b/extensions/vtk/src/commandsModule.js
@@ -116,8 +116,6 @@ function _setView(api, sliceNormal, viewUp) {
function switchMPRInteractors(api, istyle) {
const renderWindow = api.genericRenderWindow.getRenderWindow();
- const renderer = api.genericRenderWindow.getRenderer();
- const camera = renderer.getActiveCamera();
const currentIStyle = renderWindow.getInteractor().getInteractorStyle();
let currentViewport;
@@ -131,10 +129,12 @@ function switchMPRInteractors(api, istyle) {
defaultSlabThickness = currentSlabThickness;
}
- renderWindow.getInteractor().setInteractorStyle(istyle);
+ const interactor = renderWindow.getInteractor();
+
+ interactor.setInteractorStyle(istyle);
// TODO: Not sure why this is required the second time this function is called
- istyle.setInteractor(renderWindow.getInteractor());
+ istyle.setInteractor(interactor);
if (currentViewport) {
istyle.setViewport(currentViewport);
@@ -260,12 +260,61 @@ const actions = {
});
},
mpr2d: async ({ viewports }) => {
+ // TODO push a lot of this backdoor logic lower down to the library level.
const displaySet =
viewports.viewportSpecificData[viewports.activeViewportIndex];
+ // TODO -> Clean this logic up a bit.
+ const cornerstoneElement = cornerstone.getEnabledElement(displaySet.dom);
+
+ let cornerstoneVOI;
+
+ if (cornerstoneElement) {
+ const imageId = cornerstoneElement.image.imageId;
+
+ const { modality } = cornerstone.metaData.get(
+ 'generalSeriesModule',
+ imageId
+ );
+
+ if (modality !== 'PT') {
+ const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
+
+ cornerstoneVOI = {
+ windowWidth,
+ windowCenter,
+ };
+ }
+ }
+
+ const viewportProps = [
+ {
+ //Axial
+ orientation: {
+ sliceNormal: [0, 0, 1],
+ viewUp: [0, -1, 0],
+ },
+ },
+
+ {
+ // Sagital
+ orientation: {
+ sliceNormal: [1, 0, 0],
+ viewUp: [0, 0, 1],
+ },
+ },
+ {
+ // Coronal
+ orientation: {
+ sliceNormal: [0, 1, 0],
+ viewUp: [0, 0, 1],
+ },
+ },
+ ];
+
let apiByViewport;
try {
- apiByViewport = await setMPRLayout(displaySet);
+ apiByViewport = await setMPRLayout(displaySet, viewportProps);
} catch (error) {
throw new Error(error);
}
@@ -276,11 +325,29 @@ const actions = {
.getProperty()
.getRGBTransferFunction(0);
+ if (cornerstoneVOI) {
+ const { windowWidth, windowCenter } = cornerstoneVOI;
+ const lower = windowCenter - windowWidth / 2.0;
+ const upper = windowCenter + windowWidth / 2.0;
+
+ rgbTransferFunction.setRange(lower, upper);
+
+ apiByViewport.forEach(api => {
+ api.updateVOI(windowWidth, windowCenter);
+ });
+ }
+
const onModifiedSubscription = rgbTransferFunction.onModified(() => {
- apiByViewport.forEach(a => {
- const renderWindow = a.genericRenderWindow.getRenderWindow();
+ const range = rgbTransferFunction.getMappingRange();
+ const windowWidth = Math.abs(range[1] - range[0]);
+ const windowCenter = range[0] + windowWidth / 2;
+
+ apiByViewport.forEach(api => {
+ const renderWindow = api.genericRenderWindow.getRenderWindow();
renderWindow.render();
+
+ api.updateVOI(windowWidth, windowCenter);
});
});
@@ -289,7 +356,6 @@ const actions = {
apiByViewport.forEach((api, index) => {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const renderer = api.genericRenderWindow.getRenderer();
- const camera = renderer.getActiveCamera();
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
renderWindow.getInteractor().setInteractorStyle(istyle);
@@ -311,25 +377,10 @@ const actions = {
crosshairsWidget,
};
- switch (index) {
- default:
- case 0:
- //Axial
- istyle.setSliceNormal(0, 0, 1);
- istyle.setViewUp(0, -1, 0);
+ const orientation = api.getOrientation();
- break;
- case 1:
- // sagittal
- istyle.setSliceNormal(1, 0, 0);
- istyle.setViewUp(0, 0, 1);
- break;
- case 2:
- // Coronal
- istyle.setSliceNormal(0, 1, 0);
- istyle.setViewUp(0, 0, 1);
- break;
- }
+ istyle.setViewUp(...orientation.viewUp);
+ istyle.setSliceNormal(...orientation.sliceNormal);
renderWindow.render();
});
diff --git a/extensions/vtk/src/utils/setMPRLayout.js b/extensions/vtk/src/utils/setMPRLayout.js
index 4e14ba799..8de40065a 100644
--- a/extensions/vtk/src/utils/setMPRLayout.js
+++ b/extensions/vtk/src/utils/setMPRLayout.js
@@ -1,11 +1,23 @@
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
-export default function setMPRLayout(displaySet) {
+export default function setMPRLayout(
+ displaySet,
+ viewportPropsArray,
+ numRows = 1,
+ numColumns = 3
+) {
return new Promise((resolve, reject) => {
const viewports = [];
- const numRows = 1;
- const numColumns = 3;
const numViewports = numRows * numColumns;
+
+ if (viewportPropsArray && viewportPropsArray.length !== numViewports) {
+ reject(
+ new Error(
+ 'viewportProps is supplied but its length is not equal to numViewports'
+ )
+ );
+ }
+
const viewportSpecificData = {};
for (let i = 0; i < numViewports; i++) {
@@ -17,8 +29,8 @@ export default function setMPRLayout(displaySet) {
const apis = [];
viewports.forEach((viewport, index) => {
apis[index] = null;
+ const viewportProps = viewportPropsArray[index];
viewports[index] = Object.assign({}, viewports[index], {
- // plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
@@ -28,6 +40,7 @@ export default function setMPRLayout(displaySet) {
resolve(apis);
}
},
+ ...viewportProps,
},
});
});
diff --git a/platform/viewer/package.json b/platform/viewer/package.json
index 6e1f420f5..734a58c47 100644
--- a/platform/viewer/package.json
+++ b/platform/viewer/package.json
@@ -82,7 +82,7 @@
"redux-oidc": "3.1.x",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
- "vtk.js": "^11.0.1"
+ "vtk.js": "^11.6.0"
},
"devDependencies": {
"cypress": "^3.3.1",
diff --git a/yarn.lock b/yarn.lock
index 44adf991d..b03f8fdb8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15478,10 +15478,10 @@ react-transition-group@^4.1.1:
loose-envify "^1.4.0"
prop-types "^15.6.2"
-react-vtkjs-viewport@^0.1.6:
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.1.6.tgz#d9bfb2c27c850c21c07c587405d0d3edcbd31d45"
- integrity sha512-NO5Dx7MXnS6NTG+crIwcGlm1uBFYMTR/tXtLKfTfPwWNakazkqlUuhqpsw2dEcMqEB+v/RmAX6bzrArcjv4M4A==
+react-vtkjs-viewport@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.3.0.tgz#6aa09ce72d9e6f8396388a19222e1e7af230e270"
+ integrity sha512-LzaeUZR4h6BuePMOHZLPadfagnPM6SflSBRqALlBDe2Eii57uxp3lg6+9Qc74i36+H6u3/GwWLY2ftuVOkSDQw==
dependencies:
date-fns "^2.2.1"
gl-matrix "^3.1.0"
@@ -19009,10 +19009,10 @@ void-elements@^2.0.1:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
-vtk.js@^11.0.1:
- version "11.4.2"
- resolved "https://registry.yarnpkg.com/vtk.js/-/vtk.js-11.4.2.tgz#6a967a4522a88704b6982a0d1356f24f77b09989"
- integrity sha512-lr0GFZM5fGVUwM008KhxaSCB9dIeqKeD1ZZJQGcP+4rfcrPgC7Zp2L16kgQkj6rupPTseI+Nsk6UV5Dc8lJN4w==
+vtk.js@^11.6.0:
+ version "11.6.0"
+ resolved "https://registry.yarnpkg.com/vtk.js/-/vtk.js-11.6.0.tgz#a30f95cfb138453890bd85f5bca88c53ed7c2775"
+ integrity sha512-emkpLZVsuaSPLrVsbaosdD5RQQmal4PPfVotDIWb58aBbswXYt4pliaY3EDFNW3nTsKPs9ttadM4f4n8fVWVbQ==
dependencies:
blueimp-md5 "2.10.0"
commander "2.11.0"