Segmentation UI for VTKjs (#1685)

* Add single viewport configuration

* Multiple viewport configuration

* Improve performance by using independent set methods

* Add jump to slice command

* Add context configuration

* Cache panel visibility

* Fix sync between vtk and cornerstone

* Remove apis index

* Add approach

* Add loading to update volumes

* Fix broken configuration

* Bump vtk version

* Use loading label

* Update cy tests after vtk loading label changed

* Remove loading for segs
This commit is contained in:
Igor Octaviano authored and GitHub committed 2020-05-06 19:17:44 +02:00
1 parent 19a8e71317
commit 42c22df1b6
22 files changed
+516 -173

No files matched your search

+151 -2
View File
@@ -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'],