refactor: Viewports state (#1218)
* Organize viewports reducers * Organize viewports actions * Add local state to store dom node and remove hack * Comment usage of dom in vtk * Fix set of enabledElements * Fix warning in html viewport * Update docs for state * Add commandsmanager to commandsmodule
This commit is contained in:
parent
0bb3eeb068
commit
f396b30166
@ -2,6 +2,7 @@ import CornerstoneViewport from 'react-cornerstone-viewport';
|
|||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import throttle from 'lodash.throttle';
|
import throttle from 'lodash.throttle';
|
||||||
|
import { setEnabledElement } from './state';
|
||||||
|
|
||||||
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
||||||
const {
|
const {
|
||||||
@ -80,11 +81,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
|||||||
*/
|
*/
|
||||||
onElementEnabled: event => {
|
onElementEnabled: event => {
|
||||||
const enabledElement = event.detail.element;
|
const enabledElement = event.detail.element;
|
||||||
|
setEnabledElement(viewportIndex, enabledElement);
|
||||||
dispatch(
|
dispatch(
|
||||||
setViewportSpecificData(viewportIndex, {
|
setViewportSpecificData(viewportIndex, {
|
||||||
// TODO: Hack to make sure our plugin info is available from the outset
|
// TODO: Hack to make sure our plugin info is available from the outset
|
||||||
plugin: 'cornerstone',
|
plugin: 'cornerstone',
|
||||||
dom: enabledElement,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
|
import { getEnabledElement } from './state';
|
||||||
const scroll = cornerstoneTools.import('util/scroll');
|
const scroll = cornerstoneTools.import('util/scroll');
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
rotateViewport: ({ viewports, rotation }) => {
|
rotateViewport: ({ viewports, rotation }) => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement(
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (enabledElement) {
|
if (enabledElement) {
|
||||||
let viewport = cornerstone.getViewport(enabledElement);
|
let viewport = cornerstone.getViewport(enabledElement);
|
||||||
@ -17,10 +15,7 @@ const actions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
flipViewportHorizontal: ({ viewports }) => {
|
flipViewportHorizontal: ({ viewports }) => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement(
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (enabledElement) {
|
if (enabledElement) {
|
||||||
let viewport = cornerstone.getViewport(enabledElement);
|
let viewport = cornerstone.getViewport(enabledElement);
|
||||||
@ -29,10 +24,7 @@ const actions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
flipViewportVertical: ({ viewports }) => {
|
flipViewportVertical: ({ viewports }) => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement(
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (enabledElement) {
|
if (enabledElement) {
|
||||||
let viewport = cornerstone.getViewport(enabledElement);
|
let viewport = cornerstone.getViewport(enabledElement);
|
||||||
@ -40,11 +32,8 @@ const actions = {
|
|||||||
cornerstone.setViewport(enabledElement, viewport);
|
cornerstone.setViewport(enabledElement, viewport);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scaleViewport: ({ viewports, direction }) => {
|
scaleViewport: ({ direction, viewports }) => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement(
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
const step = direction * 0.15;
|
const step = direction * 0.15;
|
||||||
|
|
||||||
if (enabledElement) {
|
if (enabledElement) {
|
||||||
@ -58,20 +47,14 @@ const actions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetViewport: ({ viewports }) => {
|
resetViewport: ({ viewports }) => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement(
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (enabledElement) {
|
if (enabledElement) {
|
||||||
cornerstone.reset(enabledElement);
|
cornerstone.reset(enabledElement);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
invertViewport: ({ viewports }) => {
|
invertViewport: ({ viewports }) => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement(
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (enabledElement) {
|
if (enabledElement) {
|
||||||
let viewport = cornerstone.getViewport(enabledElement);
|
let viewport = cornerstone.getViewport(enabledElement);
|
||||||
@ -92,10 +75,7 @@ const actions = {
|
|||||||
console.warn('updateDisplaySet: ', direction);
|
console.warn('updateDisplaySet: ', direction);
|
||||||
},
|
},
|
||||||
clearAnnotations: ({ viewports }) => {
|
clearAnnotations: ({ viewports }) => {
|
||||||
const element = _getActiveViewportEnabledElement(
|
const element = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -147,24 +127,25 @@ const actions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
nextImage: ({ viewports }) => {
|
nextImage: ({ viewports }) => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement(
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
scroll(enabledElement, 1);
|
scroll(enabledElement, 1);
|
||||||
},
|
},
|
||||||
previousImage: ({ viewports }) => {
|
previousImage: ({ viewports }) => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement(
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
viewports.viewportSpecificData,
|
|
||||||
viewports.activeViewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
scroll(enabledElement, -1);
|
scroll(enabledElement, -1);
|
||||||
},
|
},
|
||||||
|
getActiveViewportEnabledElement: ({ viewports }) => {
|
||||||
|
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||||
|
return enabledElement;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
|
getActiveViewportEnabledElement: {
|
||||||
|
commandFn: actions.getActiveViewportEnabledElement,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
rotateViewportCW: {
|
rotateViewportCW: {
|
||||||
commandFn: actions.rotateViewport,
|
commandFn: actions.rotateViewport,
|
||||||
storeContexts: ['viewports'],
|
storeContexts: ['viewports'],
|
||||||
@ -245,15 +226,6 @@ const definitions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Grabs `dom` reference for the enabledElement of
|
|
||||||
* the active viewport
|
|
||||||
*/
|
|
||||||
function _getActiveViewportEnabledElement(viewports, activeIndex) {
|
|
||||||
const activeViewport = viewports[activeIndex] || {};
|
|
||||||
return activeViewport.dom;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
actions,
|
actions,
|
||||||
definitions,
|
definitions,
|
||||||
|
|||||||
20
extensions/cornerstone/src/state.js
Normal file
20
extensions/cornerstone/src/state.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const state = {
|
||||||
|
enabledElements: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the enabled element `dom` reference for an active viewport.
|
||||||
|
* @param {HTMLElement} dom Active viewport element.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
const setEnabledElement = (viewportIndex, element) =>
|
||||||
|
(state.enabledElements[viewportIndex] = element);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grabs the enabled element `dom` reference of an active viewport.
|
||||||
|
*
|
||||||
|
* @return {HTMLElement} Active viewport element.
|
||||||
|
*/
|
||||||
|
const getEnabledElement = viewportIndex => state.enabledElements[viewportIndex];
|
||||||
|
|
||||||
|
export { setEnabledElement, getEnabledElement };
|
||||||
@ -6,7 +6,7 @@ const { setViewportActive } = OHIF.redux.actions;
|
|||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const { viewportIndex, byteArray } = ownProps;
|
const { viewportIndex, byteArray } = ownProps;
|
||||||
const activeViewportIndex = state.viewports;
|
const { activeViewportIndex } = state.viewports;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
viewportIndex,
|
viewportIndex,
|
||||||
|
|||||||
@ -238,7 +238,7 @@ class DicomHtmlViewport extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (viewportIndex !== activeViewportIndex) {
|
if (viewportIndex !== activeViewportIndex) {
|
||||||
setViewportActive();
|
setViewportActive(viewportIndex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import throttle from 'lodash.throttle';
|
||||||
import {
|
import {
|
||||||
vtkInteractorStyleMPRCrosshairs,
|
vtkInteractorStyleMPRCrosshairs,
|
||||||
vtkInteractorStyleMPRWindowLevel,
|
vtkInteractorStyleMPRWindowLevel,
|
||||||
@ -8,354 +9,358 @@ import {
|
|||||||
import setMPRLayout from './utils/setMPRLayout.js';
|
import setMPRLayout from './utils/setMPRLayout.js';
|
||||||
import setViewportToVTK from './utils/setViewportToVTK.js';
|
import setViewportToVTK from './utils/setViewportToVTK.js';
|
||||||
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
|
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
|
||||||
import throttle from 'lodash.throttle';
|
|
||||||
|
|
||||||
const { BlendMode } = Constants;
|
const { BlendMode } = Constants;
|
||||||
|
|
||||||
// TODO: Put this somewhere else
|
const commandsModule = ({ commandsManager }) => {
|
||||||
let apis = {};
|
// TODO: Put this somewhere else
|
||||||
|
let apis = {};
|
||||||
|
|
||||||
async function _getActiveViewportVTKApi(viewports) {
|
async function _getActiveViewportVTKApi(viewports) {
|
||||||
const {
|
const {
|
||||||
numRows,
|
numRows,
|
||||||
numColumns,
|
numColumns,
|
||||||
layout,
|
layout,
|
||||||
viewportSpecificData,
|
viewportSpecificData,
|
||||||
activeViewportIndex,
|
activeViewportIndex,
|
||||||
} = viewports;
|
} = viewports;
|
||||||
|
|
||||||
const currentData = layout.viewports[activeViewportIndex];
|
const currentData = layout.viewports[activeViewportIndex];
|
||||||
if (currentData && currentData.plugin === 'vtk') {
|
if (currentData && currentData.plugin === 'vtk') {
|
||||||
// TODO: I was storing/pulling this from Redux but ran into weird issues
|
// TODO: I was storing/pulling this from Redux but ran into weird issues
|
||||||
if (apis[activeViewportIndex]) {
|
if (apis[activeViewportIndex]) {
|
||||||
return apis[activeViewportIndex];
|
return apis[activeViewportIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const displaySet = viewportSpecificData[activeViewportIndex];
|
||||||
|
|
||||||
|
let api;
|
||||||
|
if (!api) {
|
||||||
|
try {
|
||||||
|
api = await setViewportToVTK(
|
||||||
|
displaySet,
|
||||||
|
activeViewportIndex,
|
||||||
|
numRows,
|
||||||
|
numColumns,
|
||||||
|
layout,
|
||||||
|
viewportSpecificData
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
const displaySet = viewportSpecificData[activeViewportIndex];
|
function _setView(api, sliceNormal, viewUp) {
|
||||||
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
|
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
||||||
|
istyle.setSliceNormal(...sliceNormal);
|
||||||
|
istyle.setViewUp(...viewUp);
|
||||||
|
|
||||||
let api;
|
renderWindow.render();
|
||||||
if (!api) {
|
}
|
||||||
try {
|
|
||||||
api = await setViewportToVTK(
|
function getVOIFromCornerstoneViewport() {
|
||||||
displaySet,
|
const dom = commandsManager.runCommand('getActiveViewportEnabledElement');
|
||||||
activeViewportIndex,
|
const cornerstoneElement = cornerstone.getEnabledElement(dom);
|
||||||
numRows,
|
|
||||||
numColumns,
|
if (cornerstoneElement) {
|
||||||
layout,
|
const imageId = cornerstoneElement.image.imageId;
|
||||||
viewportSpecificData
|
|
||||||
|
const { modality } = cornerstone.metaData.get(
|
||||||
|
'generalSeriesModule',
|
||||||
|
imageId
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error);
|
if (modality !== 'PT') {
|
||||||
|
const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
|
||||||
|
|
||||||
|
return {
|
||||||
|
windowWidth,
|
||||||
|
windowCenter,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return api;
|
function setVOI(voi) {
|
||||||
}
|
const { windowWidth, windowCenter } = voi;
|
||||||
|
const lower = windowCenter - windowWidth / 2.0;
|
||||||
|
const upper = windowCenter + windowWidth / 2.0;
|
||||||
|
|
||||||
function _setView(api, sliceNormal, viewUp) {
|
const rgbTransferFunction = apis[0].volumes[0]
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
.getProperty()
|
||||||
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
.getRGBTransferFunction(0);
|
||||||
istyle.setSliceNormal(...sliceNormal);
|
|
||||||
istyle.setViewUp(...viewUp);
|
|
||||||
|
|
||||||
renderWindow.render();
|
rgbTransferFunction.setRange(lower, upper);
|
||||||
}
|
|
||||||
|
|
||||||
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: () => {
|
|
||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
const istyle = vtkInteractorStyleMPRRotate.newInstance();
|
api.updateVOI(windowWidth, windowCenter);
|
||||||
|
|
||||||
api.setInteractorStyle({ istyle });
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
enableCrosshairsTool: () => {
|
|
||||||
apis.forEach((api, apiIndex) => {
|
|
||||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
|
||||||
|
|
||||||
api.setInteractorStyle({
|
const actions = {
|
||||||
istyle,
|
axial: async ({ viewports }) => {
|
||||||
configuration: { apis, apiIndex },
|
const api = await _getActiveViewportVTKApi(viewports);
|
||||||
});
|
|
||||||
});
|
apis[viewports.activeViewportIndex] = api;
|
||||||
},
|
|
||||||
enableLevelTool: () => {
|
_setView(api, [0, 0, 1], [0, -1, 0]);
|
||||||
function updateVOI(apis, windowWidth, windowCenter) {
|
},
|
||||||
|
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: () => {
|
||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
api.updateVOI(windowWidth, windowCenter);
|
const istyle = vtkInteractorStyleMPRRotate.newInstance();
|
||||||
|
|
||||||
|
api.setInteractorStyle({ istyle });
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
enableCrosshairsTool: () => {
|
||||||
|
apis.forEach((api, apiIndex) => {
|
||||||
|
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||||
|
|
||||||
const throttledUpdateVOIs = throttle(updateVOI, 16, { trailing: true }); // ~ 60 fps
|
api.setInteractorStyle({
|
||||||
|
istyle,
|
||||||
const callbacks = {
|
configuration: { apis, apiIndex },
|
||||||
setOnLevelsChanged: ({ windowCenter, windowWidth }) => {
|
|
||||||
apis.forEach(api => {
|
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
throttledUpdateVOIs(apis, windowWidth, windowCenter);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
apis.forEach(api => {
|
|
||||||
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
|
|
||||||
|
|
||||||
api.setInteractorStyle({ istyle, callbacks });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setSlabThickness: ({ slabThickness }) => {
|
|
||||||
apis.forEach(api => {
|
|
||||||
api.setSlabThickness(slabThickness);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
changeSlabThickness: ({ change }) => {
|
|
||||||
apis.forEach(api => {
|
|
||||||
const slabThickness = Math.max(api.getSlabThickness() + change, 0.1);
|
|
||||||
|
|
||||||
api.setSlabThickness(slabThickness);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setBlendModeToComposite: () => {
|
|
||||||
apis.forEach(api => {
|
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
|
||||||
|
|
||||||
const slabThickness = api.getSlabThickness();
|
|
||||||
|
|
||||||
const mapper = api.volumes[0].getMapper();
|
|
||||||
if (mapper.setBlendModeToComposite) {
|
|
||||||
mapper.setBlendModeToComposite();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (istyle.setSlabThickness) {
|
|
||||||
istyle.setSlabThickness(slabThickness);
|
|
||||||
}
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setBlendModeToMaximumIntensity: () => {
|
|
||||||
apis.forEach(api => {
|
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
const mapper = api.volumes[0].getMapper();
|
|
||||||
if (mapper.setBlendModeToMaximumIntensity) {
|
|
||||||
mapper.setBlendModeToMaximumIntensity();
|
|
||||||
}
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setBlendMode: ({ blendMode }) => {
|
|
||||||
apis.forEach(api => {
|
|
||||||
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
|
||||||
|
|
||||||
api.volumes[0].getMapper().setBlendMode(blendMode);
|
|
||||||
|
|
||||||
renderWindow.render();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
mpr2d: async ({ viewports }) => {
|
|
||||||
// TODO push a lot of this backdoor logic lower down to the library level.
|
|
||||||
const displaySet =
|
|
||||||
viewports.viewportSpecificData[viewports.activeViewportIndex];
|
|
||||||
|
|
||||||
// Get current VOI if cornerstone viewport.
|
|
||||||
const cornerstoneVOI = getVOIFromCornerstoneViewport(displaySet);
|
|
||||||
|
|
||||||
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],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
|
||||||
apis = await setMPRLayout(displaySet, viewportProps, 1, 3);
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cornerstoneVOI) {
|
|
||||||
setVOI(cornerstoneVOI);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add widgets and set default interactorStyle of each viewport.
|
|
||||||
apis.forEach((api, apiIndex) => {
|
|
||||||
api.addSVGWidget(
|
|
||||||
vtkSVGCrosshairsWidget.newInstance(),
|
|
||||||
'crosshairsWidget'
|
|
||||||
);
|
|
||||||
|
|
||||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
|
||||||
|
|
||||||
api.setInteractorStyle({
|
|
||||||
istyle,
|
|
||||||
configuration: { apis, apiIndex },
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
window.vtkActions = actions;
|
|
||||||
|
|
||||||
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: [],
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
enableCrosshairsTool: {
|
|
||||||
commandFn: actions.enableCrosshairsTool,
|
|
||||||
storeContexts: [],
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
enableLevelTool: {
|
|
||||||
commandFn: actions.enableLevelTool,
|
|
||||||
storeContexts: [],
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
setBlendModeToComposite: {
|
|
||||||
commandFn: actions.setBlendModeToComposite,
|
|
||||||
storeContexts: [],
|
|
||||||
options: { blendMode: BlendMode.COMPOSITE_BLEND },
|
|
||||||
},
|
|
||||||
setBlendModeToMaximumIntensity: {
|
|
||||||
commandFn: actions.setBlendModeToMaximumIntensity,
|
|
||||||
storeContexts: [],
|
|
||||||
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
|
|
||||||
},
|
|
||||||
setBlendModeToMinimumIntensity: {
|
|
||||||
commandFn: actions.setBlendMode,
|
|
||||||
storeContexts: [],
|
|
||||||
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
|
|
||||||
},
|
|
||||||
setBlendModeToAverageIntensity: {
|
|
||||||
commandFn: actions.setBlendMode,
|
|
||||||
storeContexts: [],
|
|
||||||
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
|
|
||||||
},
|
|
||||||
setSlabThickness: {
|
|
||||||
// TODO: How do we pass in a function argument?
|
|
||||||
commandFn: actions.setSlabThickness,
|
|
||||||
storeContexts: [],
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
increaseSlabThickness: {
|
|
||||||
commandFn: actions.changeSlabThickness,
|
|
||||||
storeContexts: [],
|
|
||||||
options: {
|
|
||||||
change: 3,
|
|
||||||
},
|
},
|
||||||
},
|
enableLevelTool: () => {
|
||||||
decreaseSlabThickness: {
|
function updateVOI(apis, windowWidth, windowCenter) {
|
||||||
commandFn: actions.changeSlabThickness,
|
apis.forEach(api => {
|
||||||
storeContexts: [],
|
api.updateVOI(windowWidth, windowCenter);
|
||||||
options: {
|
});
|
||||||
change: -3,
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
mpr2d: {
|
|
||||||
commandFn: actions.mpr2d,
|
|
||||||
storeContexts: ['viewports'],
|
|
||||||
options: {},
|
|
||||||
context: 'VIEWER',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
const throttledUpdateVOIs = throttle(updateVOI, 16, { trailing: true }); // ~ 60 fps
|
||||||
definitions,
|
|
||||||
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
|
||||||
};
|
|
||||||
|
|
||||||
function getVOIFromCornerstoneViewport(displaySet) {
|
const callbacks = {
|
||||||
const cornerstoneElement = cornerstone.getEnabledElement(displaySet.dom);
|
setOnLevelsChanged: ({ windowCenter, windowWidth }) => {
|
||||||
|
apis.forEach(api => {
|
||||||
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
|
|
||||||
if (cornerstoneElement) {
|
renderWindow.render();
|
||||||
const imageId = cornerstoneElement.image.imageId;
|
});
|
||||||
|
|
||||||
const { modality } = cornerstone.metaData.get(
|
throttledUpdateVOIs(apis, windowWidth, windowCenter);
|
||||||
'generalSeriesModule',
|
},
|
||||||
imageId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (modality !== 'PT') {
|
|
||||||
const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
|
|
||||||
|
|
||||||
return {
|
|
||||||
windowWidth,
|
|
||||||
windowCenter,
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVOI(voi) {
|
apis.forEach(api => {
|
||||||
const { windowWidth, windowCenter } = voi;
|
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
|
||||||
const lower = windowCenter - windowWidth / 2.0;
|
|
||||||
const upper = windowCenter + windowWidth / 2.0;
|
|
||||||
|
|
||||||
const rgbTransferFunction = apis[0].volumes[0]
|
api.setInteractorStyle({ istyle, callbacks });
|
||||||
.getProperty()
|
});
|
||||||
.getRGBTransferFunction(0);
|
},
|
||||||
|
setSlabThickness: ({ slabThickness }) => {
|
||||||
|
apis.forEach(api => {
|
||||||
|
api.setSlabThickness(slabThickness);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeSlabThickness: ({ change }) => {
|
||||||
|
apis.forEach(api => {
|
||||||
|
const slabThickness = Math.max(api.getSlabThickness() + change, 0.1);
|
||||||
|
|
||||||
rgbTransferFunction.setRange(lower, upper);
|
api.setSlabThickness(slabThickness);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setBlendModeToComposite: () => {
|
||||||
|
apis.forEach(api => {
|
||||||
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
|
const istyle = renderWindow.getInteractor().getInteractorStyle();
|
||||||
|
|
||||||
apis.forEach(api => {
|
const slabThickness = api.getSlabThickness();
|
||||||
api.updateVOI(windowWidth, windowCenter);
|
|
||||||
});
|
const mapper = api.volumes[0].getMapper();
|
||||||
}
|
if (mapper.setBlendModeToComposite) {
|
||||||
|
mapper.setBlendModeToComposite();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (istyle.setSlabThickness) {
|
||||||
|
istyle.setSlabThickness(slabThickness);
|
||||||
|
}
|
||||||
|
renderWindow.render();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setBlendModeToMaximumIntensity: () => {
|
||||||
|
apis.forEach(api => {
|
||||||
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
|
const mapper = api.volumes[0].getMapper();
|
||||||
|
if (mapper.setBlendModeToMaximumIntensity) {
|
||||||
|
mapper.setBlendModeToMaximumIntensity();
|
||||||
|
}
|
||||||
|
renderWindow.render();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setBlendMode: ({ blendMode }) => {
|
||||||
|
apis.forEach(api => {
|
||||||
|
const renderWindow = api.genericRenderWindow.getRenderWindow();
|
||||||
|
|
||||||
|
api.volumes[0].getMapper().setBlendMode(blendMode);
|
||||||
|
|
||||||
|
renderWindow.render();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
mpr2d: async ({ viewports }) => {
|
||||||
|
// TODO push a lot of this backdoor logic lower down to the library level.
|
||||||
|
const displaySet =
|
||||||
|
viewports.viewportSpecificData[viewports.activeViewportIndex];
|
||||||
|
|
||||||
|
// Get current VOI if cornerstone viewport.
|
||||||
|
const cornerstoneVOI = getVOIFromCornerstoneViewport();
|
||||||
|
|
||||||
|
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],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
apis = await setMPRLayout(displaySet, viewportProps, 1, 3);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cornerstoneVOI) {
|
||||||
|
setVOI(cornerstoneVOI);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add widgets and set default interactorStyle of each viewport.
|
||||||
|
apis.forEach((api, apiIndex) => {
|
||||||
|
api.addSVGWidget(
|
||||||
|
vtkSVGCrosshairsWidget.newInstance(),
|
||||||
|
'crosshairsWidget'
|
||||||
|
);
|
||||||
|
|
||||||
|
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||||
|
|
||||||
|
api.setInteractorStyle({
|
||||||
|
istyle,
|
||||||
|
configuration: { apis, apiIndex },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.vtkActions = actions;
|
||||||
|
|
||||||
|
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: [],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
enableCrosshairsTool: {
|
||||||
|
commandFn: actions.enableCrosshairsTool,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
enableLevelTool: {
|
||||||
|
commandFn: actions.enableLevelTool,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
setBlendModeToComposite: {
|
||||||
|
commandFn: actions.setBlendModeToComposite,
|
||||||
|
storeContexts: [],
|
||||||
|
options: { blendMode: BlendMode.COMPOSITE_BLEND },
|
||||||
|
},
|
||||||
|
setBlendModeToMaximumIntensity: {
|
||||||
|
commandFn: actions.setBlendModeToMaximumIntensity,
|
||||||
|
storeContexts: [],
|
||||||
|
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
|
||||||
|
},
|
||||||
|
setBlendModeToMinimumIntensity: {
|
||||||
|
commandFn: actions.setBlendMode,
|
||||||
|
storeContexts: [],
|
||||||
|
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
|
||||||
|
},
|
||||||
|
setBlendModeToAverageIntensity: {
|
||||||
|
commandFn: actions.setBlendMode,
|
||||||
|
storeContexts: [],
|
||||||
|
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
|
||||||
|
},
|
||||||
|
setSlabThickness: {
|
||||||
|
// TODO: How do we pass in a function argument?
|
||||||
|
commandFn: actions.setSlabThickness,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
increaseSlabThickness: {
|
||||||
|
commandFn: actions.changeSlabThickness,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {
|
||||||
|
change: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decreaseSlabThickness: {
|
||||||
|
commandFn: actions.changeSlabThickness,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {
|
||||||
|
change: -3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mpr2d: {
|
||||||
|
commandFn: actions.mpr2d,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: {},
|
||||||
|
context: 'VIEWER',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
definitions,
|
||||||
|
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default commandsModule;
|
||||||
|
|||||||
@ -20,8 +20,8 @@ const vtkExtension = {
|
|||||||
getToolbarModule() {
|
getToolbarModule() {
|
||||||
return toolbarModule;
|
return toolbarModule;
|
||||||
},
|
},
|
||||||
getCommandsModule() {
|
getCommandsModule({ commandsManager }) {
|
||||||
return commandsModule;
|
return commandsModule({ commandsManager });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,7 @@ export default class ExtensionManager {
|
|||||||
if (extension.preRegistration) {
|
if (extension.preRegistration) {
|
||||||
extension.preRegistration({
|
extension.preRegistration({
|
||||||
servicesManager: this._servicesManager,
|
servicesManager: this._servicesManager,
|
||||||
|
commandsManager: this._commandsManager,
|
||||||
configuration,
|
configuration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -112,6 +113,7 @@ export default class ExtensionManager {
|
|||||||
try {
|
try {
|
||||||
const extensionModule = getModuleFn({
|
const extensionModule = getModuleFn({
|
||||||
servicesManager: this._servicesManager,
|
servicesManager: this._servicesManager,
|
||||||
|
commandsManager: this._commandsManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!extensionModule) {
|
if (!extensionModule) {
|
||||||
|
|||||||
@ -49,7 +49,7 @@ describe('ExtensionManager.js', () => {
|
|||||||
expect(fakeExtension.preRegistration.mock.calls.length).toBe(1);
|
expect(fakeExtension.preRegistration.mock.calls.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls preRegistration() passing configuration and servicesManager instance for extension', () => {
|
it('calls preRegistration() passing configuration along with servicesManager and commandsManager instances for extension', () => {
|
||||||
const configuration = { config: 'Some configuration' };
|
const configuration = { config: 'Some configuration' };
|
||||||
extensionManager._servicesManager = { services: { TestService: {} } };
|
extensionManager._servicesManager = { services: { TestService: {} } };
|
||||||
|
|
||||||
@ -60,6 +60,7 @@ describe('ExtensionManager.js', () => {
|
|||||||
// Assert
|
// Assert
|
||||||
expect(fakeExtension.preRegistration.mock.calls[0][0]).toEqual({
|
expect(fakeExtension.preRegistration.mock.calls[0][0]).toEqual({
|
||||||
servicesManager: extensionManager._servicesManager,
|
servicesManager: extensionManager._servicesManager,
|
||||||
|
commandsManager: extensionManager._commandsManager,
|
||||||
configuration,
|
configuration,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -134,7 +135,7 @@ describe('ExtensionManager.js', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('successfully passes a servicesManager instance to each module', () => {
|
it('successfully passes a servicesManager and commandsManager instances to each module', () => {
|
||||||
extensionManager._servicesManager = { services: { TestService: {} } };
|
extensionManager._servicesManager = { services: { TestService: {} } };
|
||||||
|
|
||||||
const extension = {
|
const extension = {
|
||||||
@ -150,6 +151,7 @@ describe('ExtensionManager.js', () => {
|
|||||||
|
|
||||||
expect(extension.getViewportModule.mock.calls[0][0]).toEqual({
|
expect(extension.getViewportModule.mock.calls[0][0]).toEqual({
|
||||||
servicesManager: extensionManager._servicesManager,
|
servicesManager: extensionManager._servicesManager,
|
||||||
|
commandsManager: extensionManager._commandsManager,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -12,13 +12,25 @@ import {
|
|||||||
SET_VIEWPORT_LAYOUT_AND_DATA,
|
SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||||
} from './constants/ActionTypes.js';
|
} from './constants/ActionTypes.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The definition of a viewport layout.
|
||||||
|
*
|
||||||
|
* @typedef {Object} ViewportLayout
|
||||||
|
* @property {number} numRows -
|
||||||
|
* @property {number} numColumns -
|
||||||
|
* @property {array} viewports -
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VIEWPORT
|
* VIEWPORT
|
||||||
*/
|
*/
|
||||||
export const setViewportSpecificData = (viewportIndex, data) => ({
|
export const setViewportSpecificData = (
|
||||||
|
viewportIndex,
|
||||||
|
viewportSpecificData
|
||||||
|
) => ({
|
||||||
type: SET_VIEWPORT,
|
type: SET_VIEWPORT,
|
||||||
viewportIndex,
|
viewportIndex,
|
||||||
data,
|
viewportSpecificData,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setViewportActive = viewportIndex => ({
|
export const setViewportActive = viewportIndex => ({
|
||||||
@ -27,10 +39,7 @@ export const setViewportActive = viewportIndex => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} layout
|
* @param {ViewportLayout} layout
|
||||||
* @param {number} layout.numRows
|
|
||||||
* @param {number} layout.numColumns
|
|
||||||
* @param {array} layout.viewports
|
|
||||||
*/
|
*/
|
||||||
export const setLayout = ({ numRows, numColumns, viewports }) => ({
|
export const setLayout = ({ numRows, numColumns, viewports }) => ({
|
||||||
type: SET_VIEWPORT_LAYOUT,
|
type: SET_VIEWPORT_LAYOUT,
|
||||||
@ -40,10 +49,9 @@ export const setLayout = ({ numRows, numColumns, viewports }) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} layout
|
|
||||||
* @param {number} layout.numRows
|
* @param {number} layout.numRows
|
||||||
* @param {number} layout.numColumns
|
* @param {number} layout.numColumns
|
||||||
* @param {array} layout.viewports
|
* @param {array} viewports
|
||||||
*/
|
*/
|
||||||
export const setViewportLayoutAndData = (
|
export const setViewportLayoutAndData = (
|
||||||
{ numRows, numColumns, viewports },
|
{ numRows, numColumns, viewports },
|
||||||
@ -61,9 +69,9 @@ export const clearViewportSpecificData = viewportIndex => ({
|
|||||||
viewportIndex,
|
viewportIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setActiveViewportSpecificData = data => ({
|
export const setActiveViewportSpecificData = viewportSpecificData => ({
|
||||||
type: SET_ACTIVE_SPECIFIC_DATA,
|
type: SET_ACTIVE_SPECIFIC_DATA,
|
||||||
data,
|
viewportSpecificData,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,13 +121,18 @@ export const setServers = servers => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
// VIEWPORT
|
/**
|
||||||
|
* VIEWPORT
|
||||||
|
*/
|
||||||
setViewportActive,
|
setViewportActive,
|
||||||
setViewportSpecificData,
|
setViewportSpecificData,
|
||||||
setViewportLayoutAndData,
|
setViewportLayoutAndData,
|
||||||
setLayout,
|
setLayout,
|
||||||
clearViewportSpecificData,
|
clearViewportSpecificData,
|
||||||
setActiveViewportSpecificData,
|
setActiveViewportSpecificData,
|
||||||
|
/**
|
||||||
|
* NOT-VIEWPORT
|
||||||
|
*/
|
||||||
setStudyLoadingProgress,
|
setStudyLoadingProgress,
|
||||||
clearStudyLoadingProgress,
|
clearStudyLoadingProgress,
|
||||||
setUserPreferences,
|
setUserPreferences,
|
||||||
|
|||||||
@ -28,7 +28,7 @@ describe('actions', () => {
|
|||||||
|
|
||||||
describe('viewport action creators', () => {
|
describe('viewport action creators', () => {
|
||||||
it('should create an action to set the viewport specific data', () => {
|
it('should create an action to set the viewport specific data', () => {
|
||||||
const data = {
|
const viewportSpecificData = {
|
||||||
displaySetInstanceUid: 'ef859a23-4631-93ab-d26b-7940a822c699',
|
displaySetInstanceUid: 'ef859a23-4631-93ab-d26b-7940a822c699',
|
||||||
seriesDate: '20151026',
|
seriesDate: '20151026',
|
||||||
seriesTime: '082611.370000',
|
seriesTime: '082611.370000',
|
||||||
@ -65,12 +65,12 @@ describe('actions', () => {
|
|||||||
|
|
||||||
const expectedAction = {
|
const expectedAction = {
|
||||||
type: types.SET_ACTIVE_SPECIFIC_DATA,
|
type: types.SET_ACTIVE_SPECIFIC_DATA,
|
||||||
data,
|
viewportSpecificData,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(actions.setActiveViewportSpecificData(data)).toEqual(
|
expect(
|
||||||
expectedAction
|
actions.setActiveViewportSpecificData(viewportSpecificData)
|
||||||
);
|
).toEqual(expectedAction);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create an action to clear clearViewportSpecificData', () => {
|
it('should create an action to clear clearViewportSpecificData', () => {
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
import merge from 'lodash.merge';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CLEAR_VIEWPORT,
|
CLEAR_VIEWPORT,
|
||||||
SET_ACTIVE_SPECIFIC_DATA,
|
SET_ACTIVE_SPECIFIC_DATA,
|
||||||
@ -8,10 +11,7 @@ import {
|
|||||||
SET_VIEWPORT_LAYOUT_AND_DATA,
|
SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||||
} from './../constants/ActionTypes.js';
|
} from './../constants/ActionTypes.js';
|
||||||
|
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
const DEFAULT_STATE = {
|
||||||
import merge from 'lodash.merge';
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
numRows: 1,
|
numRows: 1,
|
||||||
numColumns: 1,
|
numColumns: 1,
|
||||||
activeViewportIndex: 0,
|
activeViewportIndex: 0,
|
||||||
@ -26,94 +26,137 @@ const defaultState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} [state=defaultState]
|
* The definition of a viewport action.
|
||||||
* @param {Object} action
|
*
|
||||||
* @param {string} [action.type]
|
* @typedef {Object} ViewportAction
|
||||||
* @param {number} [action.viewportIndex]
|
* @property {string} type -
|
||||||
* @param {Object} [action.layout]
|
* @property {Object} data -
|
||||||
* @param {Object} [action.viewportSpecificData]
|
* @property {Object} layout -
|
||||||
|
* @property {number} viewportIndex -
|
||||||
|
* @property {Object} viewportSpecificData -
|
||||||
*/
|
*/
|
||||||
const viewports = (state = defaultState, action) => {
|
|
||||||
let viewportSpecificData;
|
/**
|
||||||
|
* @param {Object} [state=DEFAULT_STATE] The current viewport state.
|
||||||
|
* @param {ViewportAction} action A viewport action.
|
||||||
|
*/
|
||||||
|
const viewports = (state = DEFAULT_STATE, action) => {
|
||||||
let useActiveViewport = false;
|
let useActiveViewport = false;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_VIEWPORT_ACTIVE:
|
/**
|
||||||
return Object.assign({}, state, {
|
* Sets the active viewport index.
|
||||||
activeViewportIndex: action.viewportIndex,
|
*
|
||||||
});
|
* @return {Object} New state.
|
||||||
|
*/
|
||||||
|
case SET_VIEWPORT_ACTIVE: {
|
||||||
|
return { ...state, activeViewportIndex: action.viewportIndex };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets viewport layout.
|
||||||
|
*
|
||||||
|
* @return {Object} New state.
|
||||||
|
*/
|
||||||
case SET_VIEWPORT_LAYOUT: {
|
case SET_VIEWPORT_LAYOUT: {
|
||||||
const { numRows, numColumns, viewports } = action;
|
return {
|
||||||
const layout = {
|
...state,
|
||||||
viewports: [...viewports],
|
numRows: action.numRows,
|
||||||
|
numColumns: action.numColumns,
|
||||||
|
layout: { viewports: [...action.viewports] },
|
||||||
};
|
};
|
||||||
|
|
||||||
return Object.assign({}, state, { numRows, numColumns, layout });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets viewport layout and data.
|
||||||
|
*
|
||||||
|
* @return {Object} New state.
|
||||||
|
*/
|
||||||
case SET_VIEWPORT_LAYOUT_AND_DATA: {
|
case SET_VIEWPORT_LAYOUT_AND_DATA: {
|
||||||
const { numRows, numColumns, viewports, viewportSpecificData } = action;
|
return {
|
||||||
const layout = {
|
...state,
|
||||||
viewports: [...viewports],
|
numRows: action.numRows,
|
||||||
|
numColumns: action.numColumns,
|
||||||
|
layout: { viewports: [...action.viewports] },
|
||||||
|
viewportSpecificData: cloneDeep(action.viewportSpecificData),
|
||||||
};
|
};
|
||||||
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
numRows,
|
|
||||||
numColumns,
|
|
||||||
layout,
|
|
||||||
viewportSpecificData: cloneDeep(viewportSpecificData),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets viewport specific data of active viewport.
|
||||||
|
*
|
||||||
|
* @return {Object} New state.
|
||||||
|
*/
|
||||||
case SET_VIEWPORT: {
|
case SET_VIEWPORT: {
|
||||||
const layout = cloneDeep(state.layout);
|
const layout = cloneDeep(state.layout);
|
||||||
const hasPlugin = action.data && action.data.plugin;
|
|
||||||
|
|
||||||
viewportSpecificData = cloneDeep(state.viewportSpecificData);
|
let viewportSpecificData = cloneDeep(state.viewportSpecificData);
|
||||||
viewportSpecificData[action.viewportIndex] = merge(
|
viewportSpecificData[action.viewportIndex] = merge(
|
||||||
{},
|
{},
|
||||||
viewportSpecificData[action.viewportIndex],
|
viewportSpecificData[action.viewportIndex],
|
||||||
action.data
|
action.viewportSpecificData
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasPlugin) {
|
if (action.viewportSpecificData && action.viewportSpecificData.plugin) {
|
||||||
layout.viewports[action.viewportIndex].plugin = action.data.plugin;
|
layout.viewports[action.viewportIndex].plugin =
|
||||||
|
action.viewportSpecificData.plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign({}, state, { layout, viewportSpecificData });
|
return { ...state, layout, viewportSpecificData };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets viewport specific data of active/any viewport.
|
||||||
|
*
|
||||||
|
* @return {Object} New state.
|
||||||
|
*/
|
||||||
case SET_ACTIVE_SPECIFIC_DATA:
|
case SET_ACTIVE_SPECIFIC_DATA:
|
||||||
useActiveViewport = true;
|
useActiveViewport = true;
|
||||||
// Allow fall-through
|
// Allow fall-through
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
case SET_SPECIFIC_DATA: {
|
case SET_SPECIFIC_DATA: {
|
||||||
const layout = cloneDeep(state.layout);
|
const layout = cloneDeep(state.layout);
|
||||||
const hasPlugin = action.data && action.data.plugin;
|
|
||||||
const viewportIndex = useActiveViewport
|
const viewportIndex = useActiveViewport
|
||||||
? state.activeViewportIndex
|
? state.activeViewportIndex
|
||||||
: action.viewportIndex;
|
: action.viewportIndex;
|
||||||
const { dom } = state.viewportSpecificData[viewportIndex];
|
|
||||||
|
|
||||||
viewportSpecificData = cloneDeep(state.viewportSpecificData);
|
let viewportSpecificData = cloneDeep(state.viewportSpecificData);
|
||||||
viewportSpecificData[viewportIndex] = {
|
viewportSpecificData[viewportIndex] = {
|
||||||
dom,
|
...action.viewportSpecificData,
|
||||||
...action.data,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hasPlugin) {
|
if (action.viewportSpecificData && action.viewportSpecificData.plugin) {
|
||||||
layout.viewports[viewportIndex].plugin = action.data.plugin;
|
layout.viewports[viewportIndex].plugin =
|
||||||
|
action.viewportSpecificData.plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign({}, state, { layout, viewportSpecificData });
|
return { ...state, layout, viewportSpecificData };
|
||||||
}
|
}
|
||||||
case CLEAR_VIEWPORT:
|
|
||||||
viewportSpecificData = cloneDeep(state.viewportSpecificData);
|
/**
|
||||||
|
* Clears viewport specific data of any viewport.
|
||||||
|
*
|
||||||
|
* @return {Object} New state.
|
||||||
|
*/
|
||||||
|
case CLEAR_VIEWPORT: {
|
||||||
|
let viewportSpecificData = cloneDeep(state.viewportSpecificData);
|
||||||
|
|
||||||
if (action.viewportIndex) {
|
if (action.viewportIndex) {
|
||||||
viewportSpecificData[action.viewportIndex] = {};
|
viewportSpecificData[action.viewportIndex] = {};
|
||||||
return Object.assign({}, state, { viewportSpecificData });
|
return { ...state, viewportSpecificData };
|
||||||
} else {
|
} else {
|
||||||
return defaultState;
|
return DEFAULT_STATE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
/**
|
||||||
|
* Returns the current application state.
|
||||||
|
*
|
||||||
|
* @return {Object} The current state.
|
||||||
|
*/
|
||||||
|
default: {
|
||||||
return state;
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -60,7 +60,7 @@ describe('viewports reducer', () => {
|
|||||||
const setViewportAction = {
|
const setViewportAction = {
|
||||||
type: types.SET_VIEWPORT,
|
type: types.SET_VIEWPORT,
|
||||||
viewportIndex: viewportToSet,
|
viewportIndex: viewportToSet,
|
||||||
data: {
|
viewportSpecificData: {
|
||||||
hello: 'this is that data for the viewport',
|
hello: 'this is that data for the viewport',
|
||||||
world: 'that will be set for the viewportIndex',
|
world: 'that will be set for the viewportIndex',
|
||||||
},
|
},
|
||||||
@ -69,7 +69,7 @@ describe('viewports reducer', () => {
|
|||||||
const updatedState = reducer(undefined, setViewportAction);
|
const updatedState = reducer(undefined, setViewportAction);
|
||||||
const updatedViewport = updatedState.viewportSpecificData[viewportToSet];
|
const updatedViewport = updatedState.viewportSpecificData[viewportToSet];
|
||||||
|
|
||||||
expect(updatedViewport).toEqual(setViewportAction.data);
|
expect(updatedViewport).toEqual(setViewportAction.viewportSpecificData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle CLEAR_VIEWPORT', () => {
|
it('should handle CLEAR_VIEWPORT', () => {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { connect } from 'react-redux';
|
|||||||
import { CineDialog } from '@ohif/ui';
|
import { CineDialog } from '@ohif/ui';
|
||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import csTools from 'cornerstone-tools';
|
import csTools from 'cornerstone-tools';
|
||||||
|
import { commandsManager } from './../App.js';
|
||||||
// Our target output kills the `as` and "import" throws a keyword error
|
// Our target output kills the `as` and "import" throws a keyword error
|
||||||
// import { import as toolImport, getToolState } from 'cornerstone-tools';
|
// import { import as toolImport, getToolState } from 'cornerstone-tools';
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
@ -16,7 +17,8 @@ const { setViewportSpecificData } = OHIF.redux.actions;
|
|||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
// Get activeViewport's `cine` and `stack`
|
// Get activeViewport's `cine` and `stack`
|
||||||
const { viewportSpecificData, activeViewportIndex } = state.viewports;
|
const { viewportSpecificData, activeViewportIndex } = state.viewports;
|
||||||
const { cine, dom } = viewportSpecificData[activeViewportIndex] || {};
|
const { cine } = viewportSpecificData[activeViewportIndex] || {};
|
||||||
|
const dom = commandsManager.runCommand('getActiveViewportEnabledElement');
|
||||||
|
|
||||||
const cineData = cine || {
|
const cineData = cine || {
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
|
|||||||
@ -3,15 +3,16 @@ import { ViewportDownloadForm } from '@ohif/ui';
|
|||||||
import { utils } from '@ohif/core';
|
import { utils } from '@ohif/core';
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
|
import { commandsManager } from './../App.js';
|
||||||
|
|
||||||
const MINIMUM_SIZE = 100;
|
const MINIMUM_SIZE = 100;
|
||||||
const DEFAULT_SIZE = 512;
|
const DEFAULT_SIZE = 512;
|
||||||
const MAX_TEXTURE_SIZE = 10000;
|
const MAX_TEXTURE_SIZE = 10000;
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const { viewportSpecificData, activeViewportIndex } = state.viewports;
|
const activeEnabledElement = commandsManager.runCommand(
|
||||||
const { dom: activeEnabledElement } =
|
'getActiveViewportEnabledElement'
|
||||||
viewportSpecificData[activeViewportIndex] || {};
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onClose: ownProps.hide,
|
onClose: ownProps.hide,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user