Skip to content

Commit

Permalink
fix(ImageCroppingRegionsWidget): Prettifier and arch changes
Browse files Browse the repository at this point in the history
Arch changes include: adding slice and sliceOrientation setters on the
widget, and requiring a VolumeMapper rather than an ImageMapper, since
this is inherently a 3D crop tool with UI only for 2D slices.
  • Loading branch information
floryst committed Jan 10, 2018
1 parent 61e0dec commit e3ae6a4
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 178 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import macro from 'vtk.js/Sources/macro';
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
import vtkCellPicker from 'vtk.js/Sources/Rendering/Core/CellPicker';
import macro from 'vtk.js/Sources/macro';
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
import vtkCellPicker from 'vtk.js/Sources/Rendering/Core/CellPicker';
import vtkWidgetRepresentation from 'vtk.js/Sources/Interaction/Widgets/WidgetRepresentation';
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
import Constants from 'vtk.js/Sources/Interaction/Widgets/ImageCroppingRegionsRepresentation/Constants';
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
import Constants from 'vtk.js/Sources/Interaction/Widgets/ImageCroppingRegionsRepresentation/Constants';

const { vtkErrorMacro } = macro;
const { Orientation } = Constants;
Expand Down Expand Up @@ -42,16 +42,23 @@ function vtkImageCroppingRegionsRepresentation(publicAPI, model) {
0--1------4--5
*/
// set polys
model.regionPolyData.getPolys().setData(new Uint32Array([
4, 0, 1, 2, 3,
4, 1, 4, 7, 2,
4, 4, 5, 6, 7,
4, 7, 6, 9, 8,
4, 8, 9, 10, 11,
4, 13, 8, 11, 14,
4, 12, 13, 14, 15,
4, 3, 2, 13, 12,
]), 1);

// prettier-ignore
model.regionPolyData
.getPolys()
.setData(
new Uint32Array([
4, 0, 1, 2, 3,
4, 1, 4, 7, 2,
4, 4, 5, 6, 7,
4, 7, 6, 9, 8,
4, 8, 9, 10, 11,
4, 13, 8, 11, 14,
4, 12, 13, 14, 15,
4, 3, 2, 13, 12,
]),
1,
);

model.mapper = vtkMapper.newInstance();
model.actor = vtkActor.newInstance();
Expand Down Expand Up @@ -104,18 +111,27 @@ function vtkImageCroppingRegionsRepresentation(publicAPI, model) {
}

model.initialLength = Math.sqrt(
((newBounds[1] - newBounds[0]) * (newBounds[1] - newBounds[0])) +
((newBounds[3] - newBounds[2]) * (newBounds[3] - newBounds[2])) +
((newBounds[5] - newBounds[4]) * (newBounds[5] - newBounds[4])));
(newBounds[1] - newBounds[0]) * (newBounds[1] - newBounds[0]) +
(newBounds[3] - newBounds[2]) * (newBounds[3] - newBounds[2]) +
(newBounds[5] - newBounds[4]) * (newBounds[5] - newBounds[4])
);

// set plane positions
publicAPI.setPlanePositions(...model.initialBounds);
};

publicAPI.setSlice = (slice, sliceOrientation) => {
model.slice = slice;
model.sliceOrientation = sliceOrientation;
publicAPI.updateGeometry();
publicAPI.setSlice = (slice) => {
if (slice !== model.slice) {
model.slice = slice;
publicAPI.updateGeometry();
}
};

publicAPI.setSliceOrientation = (sliceOrientation) => {
if (sliceOrientation !== model.sliceOrientation) {
model.sliceOrientation = sliceOrientation;
publicAPI.updateGeometry();
}
};

publicAPI.setOpacity = (opacityValue) => {
Expand All @@ -134,10 +150,16 @@ function vtkImageCroppingRegionsRepresentation(publicAPI, model) {

// make sure each position is within the bounds
for (let i = 0; i < 6; i += 2) {
if (positions[i] < model.initialBounds[i] || positions[i] > model.initialBounds[i + 1]) {
if (
positions[i] < model.initialBounds[i] ||
positions[i] > model.initialBounds[i + 1]
) {
positions[i] = model.initialBounds[i];
}
if (positions[i + 1] < model.initialBounds[i] || positions[i + 1] > model.initialBounds[i + 1]) {
if (
positions[i + 1] < model.initialBounds[i] ||
positions[i + 1] > model.initialBounds[i + 1]
) {
positions[i + 1] = model.initialBounds[i + 1];
}
}
Expand All @@ -151,68 +173,81 @@ function vtkImageCroppingRegionsRepresentation(publicAPI, model) {
const slicePos = model.slice;
const verts = model.regionPolyData.getPoints().getData();
const [xBMin, xBMax, yBMin, yBMax, zBMin, zBMax] = model.initialBounds;
const [xPLower, xPUpper, yPLower, yPUpper, zPLower, zPUpper] = model.planePositions;
const [
xPLower,
xPUpper,
yPLower,
yPUpper,
zPLower,
zPUpper,
] = model.planePositions;

// set vert coordinates depending on slice orientation
// slicePos offsets are a hack to get polydata edges to render correctly.
switch (model.sliceOrientation) {
case Orientation.YZ:
verts.set([slicePos, yBMin, zBMin], 0);
verts.set([slicePos, yPLower, zBMin], 3);
verts.set([slicePos, yPLower, zPLower], 6);
verts.set([slicePos + 0.01, yPLower, zPLower], 6);
verts.set([slicePos, yBMin, zPLower], 9);
verts.set([slicePos, yPUpper, zBMin], 12);
verts.set([slicePos + 0.01, yPUpper, zBMin], 12);
verts.set([slicePos, yBMax, zBMin], 15);
verts.set([slicePos, yBMax, zPLower], 18);
verts.set([slicePos + 0.01, yBMax, zPLower], 18);
verts.set([slicePos, yPUpper, zPLower], 21);
verts.set([slicePos, yPUpper, zPUpper], 24);
verts.set([slicePos + 0.01, yPUpper, zPUpper], 24);
verts.set([slicePos, yBMax, zPUpper], 27);
verts.set([slicePos, yBMax, zBMax], 30);
verts.set([slicePos, yPUpper, zBMax], 33);
verts.set([slicePos, yBMin, zPUpper], 36);
verts.set([slicePos + 0.01, yBMin, zPUpper], 36);
verts.set([slicePos, yPLower, zPUpper], 39);
verts.set([slicePos, yPLower, zBMax], 42);
verts.set([slicePos + 0.01, yPLower, zBMax], 42);
verts.set([slicePos, yBMin, zBMax], 45);
break;
case Orientation.XZ:
verts.set([xBMin, slicePos, zBMin], 0);
verts.set([xPLower, slicePos, zBMin], 3);
verts.set([xPLower, slicePos, zPLower], 6);
verts.set([xPLower, slicePos + 0.01, zPLower], 6);
verts.set([xBMin, slicePos, zPLower], 9);
verts.set([xPUpper, slicePos, zBMin], 12);
verts.set([xPUpper, slicePos + 0.01, zBMin], 12);
verts.set([xBMax, slicePos, zBMin], 15);
verts.set([xBMax, slicePos, zPLower], 18);
verts.set([xBMax, slicePos + 0.01, zPLower], 18);
verts.set([xPUpper, slicePos, zPLower], 21);
verts.set([xPUpper, slicePos, zPUpper], 24);
verts.set([xPUpper, slicePos + 0.01, zPUpper], 24);
verts.set([xBMax, slicePos, zPUpper], 27);
verts.set([xBMax, slicePos, zBMax], 30);
verts.set([xPUpper, slicePos, zBMax], 33);
verts.set([xBMin, slicePos, zPUpper], 36);
verts.set([xPLower, slicePos, zPUpper], 39);
verts.set([xPLower, slicePos, zBMax], 42);
verts.set([xPLower, slicePos + 0.01, zBMax], 42);
verts.set([xBMin, slicePos, zBMax], 45);
break;
case Orientation.XY:
verts.set([xBMin, yBMin, slicePos], 0);
verts.set([xPLower, yBMin, slicePos], 3);
verts.set([xPLower, yPLower, slicePos], 6);
verts.set([xPLower, yPLower, slicePos + 0.01], 6);
verts.set([xBMin, yPLower, slicePos], 9);
verts.set([xPUpper, yBMin, slicePos], 12);
verts.set([xPUpper, yBMin, slicePos + 0.01], 12);
verts.set([xBMax, yBMin, slicePos], 15);
verts.set([xBMax, yPLower, slicePos], 18);
verts.set([xBMax, yPLower, slicePos + 0.01], 18);
verts.set([xPUpper, yPLower, slicePos], 21);
verts.set([xPUpper, yPUpper, slicePos], 24);
verts.set([xPUpper, yPUpper, slicePos + 0.01], 24);
verts.set([xBMax, yPUpper, slicePos], 27);
verts.set([xBMax, yBMax, slicePos], 30);
verts.set([xPUpper, yBMax, slicePos], 33);
verts.set([xBMin, yPUpper, slicePos], 36);
verts.set([xBMin, yPUpper, slicePos + 0.01], 36);
verts.set([xPLower, yPUpper, slicePos], 39);
verts.set([xPLower, yBMax, slicePos], 42);
verts.set([xPLower, yBMax, slicePos + 0.01], 42);
verts.set([xBMin, yBMax, slicePos], 45);
break;
default:
// noop
// noop
}
// TODO set center polydata

model.regionPolyData.modified();

console.log('slice position:', model.slice);
console.log('plane positions:', model.planePositions);
};

publicAPI.getBounds = () => model.initialBounds;
Expand Down Expand Up @@ -265,7 +300,10 @@ export function extend(publicAPI, model, initialValues = {}) {

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkImageCroppingRegionsRepresentation');
export const newInstance = macro.newInstance(
extend,
'vtkImageCroppingRegionsRepresentation'
);

// ----------------------------------------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@ const WidgetState = {
MOVE_RIGHT_TOP: 8,
};

const SliceNormals = ['X', 'Y', 'Z'];

export default { WidgetState, SliceNormals };
export default { WidgetState };
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'vtk.js/Sources/favicon';

import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
import vtkHttpDataSetReader from 'vtk.js/Sources/IO/Core/HttpDataSetReader';
import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper';
import vtkImageSlice from 'vtk.js/Sources/Rendering/Core/ImageSlice';
import vtkInteractorStyleImage from 'vtk.js/Sources/Interaction/Style/InteractorStyleImage';
import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
import vtkHttpDataSetReader from 'vtk.js/Sources/IO/Core/HttpDataSetReader';
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper';
import vtkImageSlice from 'vtk.js/Sources/Rendering/Core/ImageSlice';
import vtkInteractorStyleImage from 'vtk.js/Sources/Interaction/Style/InteractorStyleImage';
import vtkImageCroppingRegionsWidget from 'vtk.js/Sources/Interaction/Widgets/ImageCroppingRegionsWidget';

// ----------------------------------------------------------------------------
Expand All @@ -29,47 +30,70 @@ const widget = vtkImageCroppingRegionsWidget.newInstance();
widget.setInteractor(renderWindow.getInteractor());

// called when the volume is loaded
function setupWidget(imageMapper) {
widget.setImageMapper(imageMapper);
function setupWidget(volumeMapper, imageMapper) {
widget.setVolumeMapper(volumeMapper);
widget.setEnable(true);
// getWidgetRep() returns a widget AFTER setEnable(true).
widget.getWidgetRep().setOpacity(0.8);
// TODO widget.getWidgetRep().setLineColor(1.0, 0.0, 0.0);

imageMapper.onModified(() => {
// update slice and slice orientation
const sliceMode = imageMapper.getCurrentSlicingMode();
const sliceNormal = ['X', 'Y', 'Z'][sliceMode];
const slice = imageMapper[`get${sliceNormal}Slice`]();
widget.setSlice(slice);
widget.setSliceOrientation(sliceMode);
});

renderWindow.render();
}

// ----------------------------------------------------------------------------
// Set up volume
// ----------------------------------------------------------------------------
const mapper = vtkImageMapper.newInstance();
const volumeMapper = vtkVolumeMapper.newInstance();
const imageMapper = vtkImageMapper.newInstance();
const actor = vtkImageSlice.newInstance();
actor.setMapper(mapper);
actor.setMapper(imageMapper);
renderer.addViewProp(actor);

const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
reader.setUrl(`${__BASE_PATH__}/data/volume/headsq.vti`, { loadData: true }).then(() => {
const data = reader.getOutputData();
// we don't care about image direction here
data.setDirection(1, 0, 0, 0, 1, 0, 0, 0, 1);

mapper.setInputData(data);
// change me!
const sliceMode = 0;
const sliceNormal = ['X', 'Y', 'Z'][sliceMode];
mapper.setCurrentSlicingMode(sliceMode);
mapper[`set${sliceNormal}Slice`](32);

const camPosition = renderer.getActiveCamera().getFocalPoint().map((v, idx) => (idx === sliceMode ? (v + 1) : v));
const viewUp = [0, 0, 0];
// viewUp[(sliceMode + 2) % 3] = 1;
viewUp[2] = 1;
renderer.getActiveCamera().set({ position: camPosition, viewUp });
console.log(renderer.getActiveCamera().getViewUp());

// create our cropping widget
setupWidget(mapper);

renderer.resetCamera();
renderWindow.render();
});
reader
.setUrl(`${__BASE_PATH__}/data/volume/headsq.vti`, { loadData: true })
.then(() => {
const data = reader.getOutputData();
// NOTE we don't care about image direction here
data.setDirection(1, 0, 0, 0, 1, 0, 0, 0, 1);

volumeMapper.setInputData(data);
imageMapper.setInputData(data);

// create our cropping widget
setupWidget(volumeMapper, imageMapper);

// After creating our cropping widget, we can now update our image mapper

// These values can be tweaked
const sliceMode = 2;
const viewUp = [0, 1, 0];

const sliceNormal = ['X', 'Y', 'Z'][sliceMode];
imageMapper.setCurrentSlicingMode(sliceMode);
imageMapper[`set${sliceNormal}Slice`](30);

const camPosition = renderer
.getActiveCamera()
.getFocalPoint()
.map((v, idx) => (idx === sliceMode ? v + 1 : v));
renderer.getActiveCamera().set({ position: camPosition, viewUp });

console.log('position', renderer.getActiveCamera().getPosition());
console.log('camera', camPosition);

renderer.resetCamera();
renderWindow.render();
});

renderWindow.render();

Expand Down
Loading

0 comments on commit e3ae6a4

Please sign in to comment.