Skip to content

Commit efcb23f

Browse files
author
Adrien Boucaud
committed
feat(ImageMapper): When XYZ slicing, display intermediary slices
1 parent 44bbf1a commit efcb23f

File tree

7 files changed

+166
-29
lines changed

7 files changed

+166
-29
lines changed

Sources/Rendering/Core/ImageMapper/index.d.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,11 @@ export interface IImageMapperInitialValues extends IAbstractMapperInitialValues
3434

3535
export interface vtkImageMapper extends vtkAbstractMapper {
3636

37-
/**
38-
*
39-
* @param {Number} pos The position value.
40-
*/
41-
getSliceAtPosition(pos: number): number;
42-
43-
/**
44-
*
45-
* @param {Vector3} pos The position value.
46-
*/
47-
getSliceAtPosition(pos: Vector3): number;
37+
/**
38+
* Returns the IJK slice value from a world position or XYZ slice value
39+
* @param {Vector3 | number} [pos] World point or XYZ slice value
40+
*/
41+
getSliceAtPosition(pos: Vector3 | number): number;
4842

4943
/**
5044
* Get the closest IJK axis

Sources/Rendering/Core/ImageMapper/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,17 @@ function vtkImageMapper(publicAPI, model) {
5151
switch (ijkMode) {
5252
case SlicingMode.I:
5353
slice = vtkMath.clampValue(ijk[0], ex[0], ex[1]);
54-
slice = Math.round(slice);
5554
break;
5655
case SlicingMode.J:
5756
slice = vtkMath.clampValue(ijk[1], ex[2], ex[3]);
58-
slice = Math.round(slice);
5957
break;
6058
case SlicingMode.K:
6159
slice = vtkMath.clampValue(ijk[2], ex[4], ex[5]);
62-
slice = Math.round(slice);
6360
break;
6461
default:
6562
return 0;
6663
}
64+
6765
return slice;
6866
};
6967

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import test from 'tape-catch';
2+
3+
import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper';
4+
import { SlicingMode } from 'vtk.js/Sources/Rendering/Core/ImageMapper/Constants';
5+
import vtkImageSlice from 'vtk.js/Sources/Rendering/Core/ImageSlice';
6+
import vtkRTAnalyticSource from 'vtk.js/Sources/Filters/Sources/RTAnalyticSource';
7+
8+
test('Test slice position differences between XYZ and IJK modes', (t) => {
9+
const source = vtkRTAnalyticSource.newInstance({
10+
wholeExtent: [0, 10, 0, 10, 0, 10],
11+
});
12+
source.update();
13+
const image = source.getOutputData();
14+
image.setSpacing([10, 10, 10]);
15+
global.image = image;
16+
const mapper = vtkImageMapper.newInstance();
17+
const slice = vtkImageSlice.newInstance();
18+
mapper.setInputData(image);
19+
20+
slice.setMapper(mapper);
21+
22+
mapper.setSlicingMode(SlicingMode.Z);
23+
24+
t.equal(3, mapper.getSliceAtPosition(30));
25+
t.equal(3.5, mapper.getSliceAtPosition(35));
26+
27+
t.equal(5, mapper.getSliceAtPosition([0, 0, 50]));
28+
t.equal(5.5, mapper.getSliceAtPosition([0, 0, 55]));
29+
30+
t.equal(0, mapper.getSliceAtPosition(-1));
31+
t.equal(10, mapper.getSliceAtPosition(110));
32+
33+
t.end();
34+
});

Sources/Rendering/OpenGL/ImageMapper/index.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -850,12 +850,14 @@ function vtkOpenGLImageMapper(publicAPI, model) {
850850
const { ijkMode } = model.renderable.getClosestIJKAxis();
851851

852852
// Find the IJK slice
853-
let nSlice = model.renderable.getSlice();
853+
let slice = model.renderable.getSlice();
854854
if (ijkMode !== model.renderable.getSlicingMode()) {
855855
// If not IJK slicing, get the IJK slice from the XYZ position/slice
856-
nSlice = model.renderable.getSliceAtPosition(nSlice);
856+
slice = model.renderable.getSliceAtPosition(slice);
857857
}
858858

859+
const nSlice = Math.round(slice);
860+
859861
// Find sliceOffset
860862
const ext = image.getExtent();
861863
let sliceOffset;
@@ -870,7 +872,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
870872
}
871873

872874
// rebuild the VBO if the data has changed
873-
const toString = `${nSlice}A${image.getMTime()}A${imgScalars.getMTime()}B${publicAPI.getMTime()}C${model.renderable.getSlicingMode()}D${actor
875+
const toString = `${slice}A${image.getMTime()}A${imgScalars.getMTime()}B${publicAPI.getMTime()}C${model.renderable.getSlicingMode()}D${actor
874876
.getProperty()
875877
.getMTime()}`;
876878
if (model.VBOBuildString !== toString) {
@@ -922,16 +924,16 @@ function vtkOpenGLImageMapper(publicAPI, model) {
922924
}
923925
dims[0] = dims[1];
924926
dims[1] = dims[2];
925-
ptsArray[0] = nSlice;
927+
ptsArray[0] = slice;
926928
ptsArray[1] = ext[2];
927929
ptsArray[2] = ext[4];
928-
ptsArray[3] = nSlice;
930+
ptsArray[3] = slice;
929931
ptsArray[4] = ext[3];
930932
ptsArray[5] = ext[4];
931-
ptsArray[6] = nSlice;
933+
ptsArray[6] = slice;
932934
ptsArray[7] = ext[2];
933935
ptsArray[8] = ext[5];
934-
ptsArray[9] = nSlice;
936+
ptsArray[9] = slice;
935937
ptsArray[10] = ext[3];
936938
ptsArray[11] = ext[5];
937939
} else if (ijkMode === SlicingMode.J) {
@@ -947,16 +949,16 @@ function vtkOpenGLImageMapper(publicAPI, model) {
947949
}
948950
dims[1] = dims[2];
949951
ptsArray[0] = ext[0];
950-
ptsArray[1] = nSlice;
952+
ptsArray[1] = slice;
951953
ptsArray[2] = ext[4];
952954
ptsArray[3] = ext[1];
953-
ptsArray[4] = nSlice;
955+
ptsArray[4] = slice;
954956
ptsArray[5] = ext[4];
955957
ptsArray[6] = ext[0];
956-
ptsArray[7] = nSlice;
958+
ptsArray[7] = slice;
957959
ptsArray[8] = ext[5];
958960
ptsArray[9] = ext[1];
959-
ptsArray[10] = nSlice;
961+
ptsArray[10] = slice;
960962
ptsArray[11] = ext[5];
961963
} else if (ijkMode === SlicingMode.K || ijkMode === SlicingMode.NONE) {
962964
scalars = basicScalars.subarray(
@@ -965,16 +967,16 @@ function vtkOpenGLImageMapper(publicAPI, model) {
965967
);
966968
ptsArray[0] = ext[0];
967969
ptsArray[1] = ext[2];
968-
ptsArray[2] = nSlice;
970+
ptsArray[2] = slice;
969971
ptsArray[3] = ext[1];
970972
ptsArray[4] = ext[2];
971-
ptsArray[5] = nSlice;
973+
ptsArray[5] = slice;
972974
ptsArray[6] = ext[0];
973975
ptsArray[7] = ext[3];
974-
ptsArray[8] = nSlice;
976+
ptsArray[8] = slice;
975977
ptsArray[9] = ext[1];
976978
ptsArray[10] = ext[3];
977-
ptsArray[11] = nSlice;
979+
ptsArray[11] = slice;
978980
} else {
979981
vtkErrorMacro('Reformat slicing not yet supported.');
980982
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import test from 'tape-catch';
2+
import testUtils from 'vtk.js/Sources/Testing/testUtils';
3+
4+
import vtkImageGridSource from 'vtk.js/Sources/Filters/Sources/ImageGridSource';
5+
import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper';
6+
import vtkImageSlice from 'vtk.js/Sources/Rendering/Core/ImageSlice';
7+
import vtkOpenGLRenderWindow from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow';
8+
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
9+
import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow';
10+
import vtkColorTransferFunction from 'vtk.js/Sources/Rendering/Core/ColorTransferFunction';
11+
12+
import baseline from './testImageIntermediateZSlice.png';
13+
import { SlicingMode } from '../../../Core/ImageMapper/Constants';
14+
15+
test.onlyIfWebGL('Test ImageMapper intermediate slices', (t) => {
16+
const gc = testUtils.createGarbageCollector(t);
17+
t.ok('rendering', 'vtkOpenGLImageMapper testImage');
18+
19+
// Create some control UI
20+
const container = document.querySelector('body');
21+
const renderWindowContainer = gc.registerDOMElement(
22+
document.createElement('div')
23+
);
24+
container.appendChild(renderWindowContainer);
25+
26+
// create what we will view
27+
const renderWindow = gc.registerResource(vtkRenderWindow.newInstance());
28+
const renderer = gc.registerResource(vtkRenderer.newInstance());
29+
renderWindow.addRenderer(renderer);
30+
renderer.setBackground(0.32, 0.34, 0.43);
31+
32+
// ----------------------------------------------------------------------------
33+
// Test code
34+
// ----------------------------------------------------------------------------
35+
36+
const gridSource = gc.registerResource(vtkImageGridSource.newInstance());
37+
const extent = 200;
38+
const gridSpacing = 32;
39+
const dataSpacing = 4;
40+
const origin = 16;
41+
gridSource.setDataExtent(0, extent, 0, extent, 0, 4);
42+
gridSource.setDataSpacing(dataSpacing, dataSpacing, dataSpacing);
43+
gridSource.setGridSpacing(gridSpacing, gridSpacing, gridSpacing);
44+
gridSource.setGridOrigin(origin, origin, 1);
45+
const direction = [0.866, 0.5, 0, -0.5, 0.866, 0, 0, 0, 1];
46+
gridSource.setDataDirection(...direction);
47+
48+
const slice = 0;
49+
const offset = 1.5;
50+
51+
// mapperAbove should show above mapperBelow
52+
// scalars, however, should be correct
53+
const mapperBelow = gc.registerResource(vtkImageMapper.newInstance());
54+
mapperBelow.setInputConnection(gridSource.getOutputPort());
55+
mapperBelow.setSlicingMode(SlicingMode.Z);
56+
mapperBelow.setSlice(slice * dataSpacing);
57+
58+
const mapperAbove = gc.registerResource(vtkImageMapper.newInstance());
59+
mapperAbove.setInputConnection(gridSource.getOutputPort());
60+
mapperAbove.setSlicingMode(SlicingMode.Z);
61+
mapperAbove.setSlice(slice * dataSpacing + offset);
62+
63+
// make sure that if both mappers were coincident,
64+
// mapperBelow would show above mapperAbove, breaking the test
65+
mapperBelow.setResolveCoincidentTopologyToPolygonOffset();
66+
mapperAbove.setResolveCoincidentTopologyToPolygonOffset();
67+
mapperBelow.setRelativeCoincidentTopologyPolygonOffsetParameters(0, -1);
68+
mapperAbove.setRelativeCoincidentTopologyPolygonOffsetParameters(0, 1);
69+
70+
const actorBelow = gc.registerResource(vtkImageSlice.newInstance());
71+
const rgb = vtkColorTransferFunction.newInstance();
72+
rgb.addRGBPoint(0, 0, 0, 0);
73+
rgb.addRGBPoint(255, 0, 0, 0);
74+
actorBelow.getProperty().setRGBTransferFunction(rgb);
75+
actorBelow.setMapper(mapperBelow);
76+
actorBelow.setPosition(100, 100, 0);
77+
78+
const actorAbove = gc.registerResource(vtkImageSlice.newInstance());
79+
actorAbove.setMapper(mapperAbove);
80+
actorAbove.setPosition(-100, 0, 0);
81+
82+
renderer.addActor(actorBelow);
83+
renderer.addActor(actorAbove);
84+
renderer.resetCamera();
85+
86+
// -----------------------------------------------------------
87+
// Make some variables global so that you can inspect and
88+
// modify objects in your browser's developer console:
89+
// -----------------------------------------------------------
90+
91+
// create something to view it, in this case webgl
92+
const glwindow = gc.registerResource(vtkOpenGLRenderWindow.newInstance());
93+
glwindow.setContainer(renderWindowContainer);
94+
renderWindow.addView(glwindow);
95+
glwindow.setSize(400, 400);
96+
97+
glwindow.captureNextImage().then((image) => {
98+
testUtils.compareImages(
99+
image,
100+
[baseline],
101+
'Rendering/OpenGL/ImageMapper',
102+
t,
103+
0.5,
104+
gc.releaseResources
105+
);
106+
});
107+
renderWindow.render();
108+
});
21.9 KB
Loading

Sources/tests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import './Rendering/Core/VolumeMapper/test/testMaximumIntensityProjection';
7878
import './Rendering/Core/VolumeMapper/test/testMinimumIntensityProjection';
7979
import './Rendering/Misc/GenericRenderWindow/test/testGenericRenderWindowCreateDelete';
8080
import './Rendering/OpenGL/ImageMapper/test/testImageCroppingPlanes';
81+
import './Rendering/OpenGL/ImageMapper/test/testImageIntermediateZSlice';
8182
import './Rendering/OpenGL/PolyDataMapper/test/testAddShaderReplacements';
8283
import './Rendering/OpenGL/PolyDataMapper/test/testClearShaderReplacements';
8384
import './Rendering/OpenGL/PolyDataMapper/test/testClippingPlanes';

0 commit comments

Comments
 (0)