- )}
+ )}
);
}
diff --git a/extensions/vtk/src/OHIFVTKViewport.js b/extensions/vtk/src/OHIFVTKViewport.js
index 69c89c1648e..5f864ba2b80 100644
--- a/extensions/vtk/src/OHIFVTKViewport.js
+++ b/extensions/vtk/src/OHIFVTKViewport.js
@@ -49,7 +49,7 @@ class OHIFVTKViewport extends Component {
state = {
volumes: null,
paintFilterLabelMapImageData: null,
- paintFilterBackgroundImageData: null,
+ paintFilterBackgroundImageData: null
};
static propTypes = {
@@ -69,7 +69,7 @@ class OHIFVTKViewport extends Component {
};
static defaultProps = {
- onScroll: () => {},
+ onScroll: () => { },
};
static id = 'OHIFVTKViewport';
@@ -156,6 +156,10 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
+ this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(isHidden => {
+ return { visible: !isHidden };
+ });
+
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
if (labelmapCache[vtkLabelmapID]) {
@@ -339,13 +343,13 @@ class OHIFVTKViewport extends Component {
this.setStateFromProps();
}
- componentDidUpdate(prevProps) {
+ componentDidUpdate(prevProps, prevState) {
const { displaySet } = this.props.viewportData;
const prevDisplaySet = prevProps.viewportData.displaySet;
if (
displaySet.displaySetInstanceUID !==
- prevDisplaySet.displaySetInstanceUID ||
+ prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
@@ -405,10 +409,6 @@ class OHIFVTKViewport extends Component {
const style = { width: '100%', height: '100%', position: 'relative' };
- const visible = configuration.renderFill || configuration.renderOutline;
- const opacity = configuration.fillAlpha;
- const outlineThickness = configuration.outlineThickness;
-
return (
<>
@@ -428,10 +428,14 @@ class OHIFVTKViewport extends Component {
dataDetails={this.state.dataDetails}
labelmapRenderingOptions={{
colorLUT: this.state.labelmapColorLUT,
- globalOpacity: opacity,
- visible,
- outlineThickness,
- renderOutline: true,
+ globalOpacity: configuration.fillAlpha,
+ visible: configuration.renderFill,
+ outlineThickness: configuration.outlineWidth,
+ renderOutline: configuration.renderOutline,
+ segmentsDefaultProperties: this.segmentsDefaultProperties,
+ onNewSegmentationRequested: () => {
+ this.setStateFromProps();
+ }
}}
onScroll={this.props.onScroll}
/>
diff --git a/extensions/vtk/src/commandsModule.js b/extensions/vtk/src/commandsModule.js
index 7702b565634..e2b110c426e 100644
--- a/extensions/vtk/src/commandsModule.js
+++ b/extensions/vtk/src/commandsModule.js
@@ -5,10 +5,13 @@ import {
vtkInteractorStyleMPRRotate,
vtkSVGCrosshairsWidget,
} from 'react-vtkjs-viewport';
-
+import { getImageData } from 'react-vtkjs-viewport';
+import { vec3 } from 'gl-matrix';
import setMPRLayout from './utils/setMPRLayout.js';
import setViewportToVTK from './utils/setViewportToVTK.js';
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
+import OHIFVTKViewport from './OHIFVTKViewport';
+import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
const { BlendMode } = Constants;
@@ -34,7 +37,6 @@ const commandsModule = ({ commandsManager }) => {
}
const displaySet = viewportSpecificData[activeViewportIndex];
-
let api;
if (!api) {
try {
@@ -99,6 +101,20 @@ const commandsModule = ({ commandsManager }) => {
});
}
+ const _convertModelToWorldSpace = (position, vtkImageData) => {
+ const indexToWorld = vtkImageData.getIndexToWorld();
+ const pos = vec3.create();
+
+ position[0] += 0.5; /* Move to the centre of the voxel. */
+ position[1] += 0.5; /* Move to the centre of the voxel. */
+ position[2] += 0.5; /* Move to the centre of the voxel. */
+
+ vec3.set(pos, position[0], position[1], position[2]);
+ vec3.transformMat4(pos, pos, indexToWorld);
+
+ return pos;
+ };
+
const actions = {
getVtkApis: ({ index }) => {
return apis[index];
@@ -124,6 +140,119 @@ const commandsModule = ({ commandsManager }) => {
_setView(api, [0, 1, 0], [0, 0, 1]);
},
+ requestNewSegmentation: async ({ viewports }) => {
+ const allViewports = Object.values(viewports.viewportSpecificData);
+ const promises = allViewports.map(async (viewport, viewportIndex) => {
+ let api = apis[viewportIndex];
+
+ if (!api) {
+ api = await _getActiveViewportVTKApi(viewports);
+ apis[viewportIndex] = api;
+ }
+
+ api.requestNewSegmentation();
+ api.updateImage();
+ });
+ await Promise.all(promises);
+ },
+ jumpToSlice: async ({
+ viewports,
+ studies,
+ StudyInstanceUID,
+ displaySetInstanceUID,
+ SOPClassUID,
+ SOPInstanceUID,
+ segmentNumber,
+ frameIndex,
+ frame,
+ done = () => { }
+ }) => {
+ let api = apis[viewports.activeViewportIndex];
+
+ if (!api) {
+ api = await _getActiveViewportVTKApi(viewports);
+ apis[viewports.activeViewportIndex] = api;
+ }
+
+ const stack = OHIFVTKViewport.getCornerstoneStack(
+ studies,
+ StudyInstanceUID,
+ displaySetInstanceUID,
+ SOPClassUID,
+ SOPInstanceUID,
+ frameIndex,
+ );
+
+ const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
+
+ let pixelIndex = 0;
+ let x = 0;
+ let y = 0;
+ let count = 0;
+
+ const rows = imageDataObject.dimensions[1];
+ const cols = imageDataObject.dimensions[0];
+
+ for (let j = 0; j < rows; j++) {
+ for (let i = 0; i < cols; i++) {
+ // [i, j] =
+ const pixel = frame.pixelData[pixelIndex];
+ if (pixel === segmentNumber) {
+ x += i;
+ y += j;
+ count++;
+ }
+ pixelIndex++;
+ }
+ }
+ x /= count;
+ y /= count;
+
+ const position = [x, y, frameIndex];
+ const worldPos = _convertModelToWorldSpace(position, imageDataObject.vtkImageData);
+
+ api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
+ done();
+ },
+ setSegmentationConfiguration: async ({
+ viewports,
+ globalOpacity,
+ visible,
+ renderOutline,
+ outlineThickness,
+ }) => {
+ const allViewports = Object.values(viewports.viewportSpecificData);
+ const promises = allViewports.map(async (viewport, viewportIndex) => {
+ let api = apis[viewportIndex];
+
+ if (!api) {
+ api = await _getActiveViewportVTKApi(viewports);
+ apis[viewportIndex] = api;
+ }
+
+ api.setGlobalOpacity(globalOpacity);
+ api.setVisibility(visible);
+ api.setOutlineThickness(outlineThickness);
+ api.setOutlineRendering(renderOutline);
+ api.updateImage();
+ });
+ await Promise.all(promises);
+ },
+ setSegmentConfiguration: async ({ viewports, visible, segmentNumber }) => {
+ const allViewports = Object.values(viewports.viewportSpecificData);
+ const promises = allViewports.map(async (viewport, viewportIndex) => {
+ let api = apis[viewportIndex];
+
+ if (!api) {
+ api = await _getActiveViewportVTKApi(viewports);
+ apis[viewportIndex] = api;
+ }
+
+ api.setSegmentVisibility(segmentNumber, visible);
+ api.updateImage();
+ });
+ await Promise.all(promises);
+ },
enableRotateTool: () => {
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRRotate.newInstance();
@@ -280,6 +409,26 @@ const commandsModule = ({ commandsManager }) => {
window.vtkActions = actions;
const definitions = {
+ requestNewSegmentation: {
+ commandFn: actions.requestNewSegmentation,
+ storeContexts: ['viewports'],
+ options: {},
+ },
+ jumpToSlice: {
+ commandFn: actions.jumpToSlice,
+ storeContexts: ['viewports'],
+ options: {},
+ },
+ setSegmentationConfiguration: {
+ commandFn: actions.setSegmentationConfiguration,
+ storeContexts: ['viewports'],
+ options: {},
+ },
+ setSegmentConfiguration: {
+ commandFn: actions.setSegmentConfiguration,
+ storeContexts: ['viewports'],
+ options: {},
+ },
axial: {
commandFn: actions.axial,
storeContexts: ['viewports'],
diff --git a/platform/core/src/classes/metadata/StudyMetadata.js b/platform/core/src/classes/metadata/StudyMetadata.js
index 911427f6778..a33c3dfe5ab 100644
--- a/platform/core/src/classes/metadata/StudyMetadata.js
+++ b/platform/core/src/classes/metadata/StudyMetadata.js
@@ -79,7 +79,7 @@ export class StudyMetadata extends Metadata {
Object.defineProperty(this, 'studyInstanceUID', {
configurable: false,
enumerable: false,
- get: function() {
+ get: function () {
return this.getStudyInstanceUID();
},
});
diff --git a/platform/core/src/extensions/ExtensionManager.js b/platform/core/src/extensions/ExtensionManager.js
index 7bb5a30b299..bb32ab553f3 100644
--- a/platform/core/src/extensions/ExtensionManager.js
+++ b/platform/core/src/extensions/ExtensionManager.js
@@ -2,7 +2,7 @@ import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js';
export default class ExtensionManager {
- constructor({ commandsManager, servicesManager, appConfig = {} }) {
+ constructor({ commandsManager, servicesManager, api, appConfig = {} }) {
this.modules = {};
this.registeredExtensionIds = [];
this.moduleTypeNames = Object.values(MODULE_TYPES);
@@ -10,6 +10,7 @@ export default class ExtensionManager {
this._commandsManager = commandsManager;
this._servicesManager = servicesManager;
this._appConfig = appConfig;
+ this._api = api;
this.moduleTypeNames.forEach(moduleType => {
this.modules[moduleType] = [];
@@ -119,6 +120,7 @@ export default class ExtensionManager {
commandsManager: this._commandsManager,
appConfig: this._appConfig,
configuration,
+ api: this._api
});
if (!extensionModule) {
diff --git a/platform/viewer/cypress/integration/pwa/OHIFExtensionVTK.spec.js b/platform/viewer/cypress/integration/pwa/OHIFExtensionVTK.spec.js
index edf7b028376..935f5175987 100644
--- a/platform/viewer/cypress/integration/pwa/OHIFExtensionVTK.spec.js
+++ b/platform/viewer/cypress/integration/pwa/OHIFExtensionVTK.spec.js
@@ -21,8 +21,8 @@ describe('OHIF VTK Extension', () => {
//Select 2D MPR button
cy.get('[data-cy="2d mpr"]').click();
- //Wait Reformatting Images
- cy.waitVTKReformatting();
+ //Wait waitVTKLoading Images
+ cy.waitVTKLoading();
});
beforeEach(() => {
diff --git a/platform/viewer/cypress/integration/visual-regression/PercyCheckOHIFExtensionVTK.spec.js b/platform/viewer/cypress/integration/visual-regression/PercyCheckOHIFExtensionVTK.spec.js
index 231e817e32c..b40d30b3dad 100644
--- a/platform/viewer/cypress/integration/visual-regression/PercyCheckOHIFExtensionVTK.spec.js
+++ b/platform/viewer/cypress/integration/visual-regression/PercyCheckOHIFExtensionVTK.spec.js
@@ -18,8 +18,8 @@ describe('Visual Regression - OHIF VTK Extension', () => {
//Select 2D MPR button
cy.get('[data-cy="2d mpr"]').click();
- //Wait Reformatting Images
- cy.waitVTKReformatting();
+ //Wait waitVTKLoading Images
+ cy.waitVTKwaitVTKLoading();
});
beforeEach(() => {
diff --git a/platform/viewer/cypress/support/commands.js b/platform/viewer/cypress/support/commands.js
index 487b56f2df6..68e9eb873fb 100644
--- a/platform/viewer/cypress/support/commands.js
+++ b/platform/viewer/cypress/support/commands.js
@@ -107,15 +107,15 @@ Cypress.Commands.add('waitStudyList', () => {
});
});
-Cypress.Commands.add('waitVTKReformatting', () => {
- // Wait for start reformatting
+Cypress.Commands.add('waitVTKLoading', () => {
+ // Wait for start loading
cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => {
- expect($grid).to.contain.text('Reform');
+ expect($grid).to.contain.text('Loading');
});
- // Wait for finish reformatting
+ // Wait for finish loading
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
- expect($grid).not.to.contain.text('Reform');
+ expect($grid).not.to.contain.text('Loading');
});
});
diff --git a/platform/viewer/src/App.js b/platform/viewer/src/App.js
index f41ff394bc0..4c33735008c 100644
--- a/platform/viewer/src/App.js
+++ b/platform/viewer/src/App.js
@@ -53,7 +53,7 @@ import store from './store';
/** Contexts */
import WhiteLabelingContext from './context/WhiteLabelingContext';
import UserManagerContext from './context/UserManagerContext';
-import AppContext from './context/AppContext';
+import { AppProvider, useAppContext, CONTEXTS } from './context/AppContext';
/** ~~~~~~~~~~~~~ Application Setup */
const commandsManagerConfig = {
@@ -159,8 +159,8 @@ class App extends Component {
if (this._userManager) {
return (
-
-
+
+
@@ -183,14 +183,14 @@ class App extends Component {
-
-
+
+
);
}
return (
-
-
+
+
@@ -204,8 +204,8 @@ class App extends Component {
-
-
+
+
);
}
@@ -255,6 +255,12 @@ function _initExtensions(extensions, cornerstoneExtensionConfig, appConfig) {
commandsManager,
servicesManager,
appConfig,
+ api: {
+ contexts: CONTEXTS,
+ hooks: {
+ useAppContext
+ }
+ }
});
const requiredExtensions = [
diff --git a/platform/viewer/src/OHIFStandaloneViewer.js b/platform/viewer/src/OHIFStandaloneViewer.js
index 9d0aedac113..baad5d759f2 100644
--- a/platform/viewer/src/OHIFStandaloneViewer.js
+++ b/platform/viewer/src/OHIFStandaloneViewer.js
@@ -191,8 +191,8 @@ class OHIFStandaloneViewer extends Component {
{match === null ? (
<>>
) : (
-
- )}
+
+ )}
)}
diff --git a/platform/viewer/src/connectedComponents/ConnectedToolbarRow.js b/platform/viewer/src/connectedComponents/ConnectedToolbarRow.js
deleted file mode 100644
index 3df035c3ebd..00000000000
--- a/platform/viewer/src/connectedComponents/ConnectedToolbarRow.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// TODO: REPLACE THIS WITH A CONTEXT PROVIDER
-// EVERYTHING IN `VIEWER.JS` COULD USE THIS FOR APPROPRIATE CONTEXT
-import ToolbarRow from './ToolbarRow';
-import { connect } from 'react-redux';
-import { getActiveContexts } from './../store/layout/selectors.js';
-
-const mapStateToProps = state => {
- return {
- activeContexts: getActiveContexts(state),
- };
-};
-
-const ConnectedToolbarRow = connect(mapStateToProps)(ToolbarRow);
-
-export default ConnectedToolbarRow;
diff --git a/platform/viewer/src/connectedComponents/ToolbarRow.js b/platform/viewer/src/connectedComponents/ToolbarRow.js
index a1729a4fc2a..b03f778bc8c 100644
--- a/platform/viewer/src/connectedComponents/ToolbarRow.js
+++ b/platform/viewer/src/connectedComponents/ToolbarRow.js
@@ -16,6 +16,7 @@ import { commandsManager, extensionManager } from './../App.js';
import ConnectedCineDialog from './ConnectedCineDialog';
import ConnectedLayoutButton from './ConnectedLayoutButton';
+import { withAppContext } from '../context/AppContext';
class ToolbarRow extends Component {
// TODO: Simplify these? isOpen can be computed if we say "any" value for selected,
@@ -381,5 +382,5 @@ function _handleBuiltIn(button) {
}
export default withTranslation(['Common', 'ViewportDownloadForm'])(
- withModal(withDialog(ToolbarRow))
+ withModal(withDialog(withAppContext(ToolbarRow)))
);
diff --git a/platform/viewer/src/connectedComponents/Viewer.js b/platform/viewer/src/connectedComponents/Viewer.js
index 851d9462fb1..f2acea44c9e 100644
--- a/platform/viewer/src/connectedComponents/Viewer.js
+++ b/platform/viewer/src/connectedComponents/Viewer.js
@@ -7,7 +7,7 @@ import OHIF, { DICOMSR } from '@ohif/core';
import { withDialog } from '@ohif/ui';
import moment from 'moment';
import ConnectedHeader from './ConnectedHeader.js';
-import ConnectedToolbarRow from './ConnectedToolbarRow.js';
+import ToolbarRow from './ToolbarRow.js';
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
import ConnectedViewerMain from './ConnectedViewerMain.js';
import SidePanel from './../components/SidePanel.js';
@@ -256,7 +256,7 @@ class Viewer extends Component {
{/* TOOLBAR */}
-
) : (
-
- )}
+
+ )}
{/* MAIN */}
@@ -349,7 +349,7 @@ export default withDialog(Viewer);
* @param {Study[]} studies
* @param {DisplaySet[]} studies[].displaySets
*/
-const _mapStudiesToThumbnails = function(studies) {
+const _mapStudiesToThumbnails = function (studies) {
return studies.map(study => {
const { StudyInstanceUID } = study;
diff --git a/platform/viewer/src/context/AppContext.js b/platform/viewer/src/context/AppContext.js
index 82bec40c40e..1bd8e817952 100644
--- a/platform/viewer/src/context/AppContext.js
+++ b/platform/viewer/src/context/AppContext.js
@@ -1,5 +1,33 @@
-import React from 'react';
+import React, { useContext } from 'react';
+import { useSelector } from 'react-redux';
+import { getActiveContexts } from '../store/layout/selectors.js';
let AppContext = React.createContext({});
+export const CONTEXTS = {
+ CORNERSTONE: 'ACTIVE_VIEWPORT::CORNERSTONE',
+ VTK: 'ACTIVE_VIEWPORT::VTK'
+};
+
+export const useAppContext = () => useContext(AppContext);
+
+export const AppProvider = ({ children, config }) => {
+ const activeContexts = useSelector(state => getActiveContexts(state));
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const withAppContext = Component => {
+ return function WrappedComponent(props) {
+ const { appConfig, activeContexts } = useAppContext();
+ return (
+
+ );
+ };
+};
+
export default AppContext;
diff --git a/yarn.lock b/yarn.lock
index 4a06d84e476..ed51294ddd7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9110,6 +9110,11 @@ gl-matrix@^3.1.0:
resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.1.0.tgz#f5b2de17d8fed95a79e5025b10cded0ab9ccbed0"
integrity sha512-526NA+3EA+ztAQi0IZpSWiM0fyQXIp7IbRvfJ4wS/TjjQD0uv0fVybXwwqqSOlq33UckivI0yMDlVtboWm3k7A==
+gl-matrix@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b"
+ integrity sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA==
+
gl-preserve-state@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/gl-preserve-state/-/gl-preserve-state-1.0.0.tgz#4ef710d62873f1470ed015c6546c37dacddd4198"
@@ -15598,7 +15603,7 @@ react-codemirror2@^6.0.0:
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-6.0.0.tgz#180065df57a64026026cde569a9708fdf7656525"
integrity sha512-D7y9qZ05FbUh9blqECaJMdDwKluQiO3A9xB+fssd5jKM7YAXucRuEOlX32mJQumUvHUkHRHqXIPBjm6g0FW0Ag==
-react-cornerstone-viewport@^2.3.8:
+react-cornerstone-viewport@2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-2.3.8.tgz#7af8360f29bca986ae4e36b4e503269b88ddc52f"
integrity sha512-aiG2uVNrDY6SQx4t/HBxIA3zsMsCwT+6TpcXK9qSSoXhs+X6OTmYEKncWUqL0jtxU1yfh6JTUz8ARTg03gtF+A==
@@ -16024,10 +16029,10 @@ react-transition-group@^4.1.1:
loose-envify "^1.4.0"
prop-types "^15.6.2"
-react-vtkjs-viewport@^0.8.3:
- version "0.8.4"
- resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.8.4.tgz#6bd9837d7762b845e77f053fb8fbf4469428d594"
- integrity sha512-b90ENJzmStiahxVox+7mMwZij3NVUu//jE07PV2Qtp6E7q/eLXn6ZUwcEt9w5YHN36xLZVcx9b53ztLcXVgh/Q==
+react-vtkjs-viewport@^0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/react-vtkjs-viewport/-/react-vtkjs-viewport-0.9.0.tgz#5a7890643e511946f960db6b05142318be2dfe80"
+ integrity sha512-kjpNr1n+eW0nU736HH07IsSYQXphxfn/RBvKtpBer9tNsl+FyLH8h+uY8KWdv8kVBHkEoaH6/srxB0JBa8l+jA==
dependencies:
date-fns "^2.2.1"
gl-matrix "^3.1.0"