Skip to content

Commit 22a4948

Browse files
authored
Merge pull request #106 from Kitware/datasets-and-image-array-mapper
Datasets and image array mapper
2 parents a55e689 + a1fe1ea commit 22a4948

File tree

8 files changed

+377
-83
lines changed

8 files changed

+377
-83
lines changed

package-lock.json

Lines changed: 221 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"dev": "rollup -c --watch"
3333
},
3434
"peerDependencies": {
35-
"@kitware/vtk.js": "^26.5.3",
35+
"@kitware/vtk.js": "^26.5.5",
3636
"react": "^16.0.0"
3737
},
3838
"devDependencies": {
@@ -41,7 +41,7 @@
4141
"@babel/preset-env": "^7.19.1",
4242
"@babel/preset-react": "^7.18.6",
4343
"@babel/preset-typescript": "^7.18.6",
44-
"@kitware/vtk.js": "^26.5.5",
44+
"@kitware/vtk.js": "^26.9.9",
4545
"@rollup/plugin-babel": "^6.0.3",
4646
"@rollup/plugin-commonjs": "24.0.1",
4747
"@rollup/plugin-eslint": "^9.0.3",

src/core/Dataset.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { vtkObject } from '@kitware/vtk.js/interfaces';
2+
import { useEffect } from 'react';
3+
import { useDownstream, useRepresentation } from './contexts';
4+
5+
export interface DatasetProps {
6+
dataset: vtkObject | null;
7+
}
8+
9+
export default function Dataset(props: DatasetProps) {
10+
const representation = useRepresentation();
11+
const downstream = useDownstream();
12+
13+
const { dataset } = props;
14+
15+
useEffect(() => {
16+
if (!dataset) {
17+
return;
18+
}
19+
20+
downstream.setInputData(dataset);
21+
representation.dataAvailable();
22+
representation.dataChanged();
23+
}, [dataset, downstream, representation]);
24+
25+
return null;
26+
}

src/core/SliceRepresentation.tsx

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import AbstractImageMapper, {
2+
vtkAbstractImageMapper,
3+
} from '@kitware/vtk.js/Rendering/Core/AbstractImageMapper';
4+
import vtkImageArrayMapper from '@kitware/vtk.js/Rendering/Core/ImageArrayMapper';
15
import vtkImageMapper, {
26
IImageMapperInitialValues,
37
} from '@kitware/vtk.js/Rendering/Core/ImageMapper';
@@ -9,6 +13,7 @@ import { Vector2 } from '@kitware/vtk.js/types';
913
import {
1014
forwardRef,
1115
PropsWithChildren,
16+
useCallback,
1217
useEffect,
1318
useImperativeHandle,
1419
useMemo,
@@ -38,6 +43,11 @@ export interface SliceRepresentationProps extends PropsWithChildren {
3843
*/
3944
mapper?: IImageMapperInitialValues;
4045

46+
/**
47+
* An opational mapper instanc
48+
*/
49+
mapperInstance?: AbstractImageMapper;
50+
4151
/**
4252
* Properties to set to the slice/actor
4353
*/
@@ -94,6 +104,18 @@ const DefaultProps = {
94104
colorDataRange: 'auto' as const,
95105
};
96106

107+
function isVtkImageMapper(
108+
mapper: vtkAbstractImageMapper
109+
): mapper is vtkImageMapper {
110+
return mapper.isA('vtkImageMapper');
111+
}
112+
113+
function isVtkImageArrayMapper(
114+
mapper: vtkAbstractImageMapper
115+
): mapper is vtkImageArrayMapper {
116+
return mapper.isA('vtkImageArrayMapper');
117+
}
118+
97119
export default forwardRef(function SliceRepresentation(
98120
props: SliceRepresentationProps,
99121
fwdRef
@@ -117,12 +139,20 @@ export default forwardRef(function SliceRepresentation(
117139

118140
// --- mapper --- //
119141

120-
const getMapper = useMapper(
142+
const getInternalMapper = useMapper(
121143
() => vtkImageMapper.newInstance(),
122144
props.mapper,
123145
trackModified
124146
);
125147

148+
const { mapperInstance } = props;
149+
const getMapper = useCallback<() => vtkAbstractImageMapper>(() => {
150+
if (mapperInstance) {
151+
return mapperInstance;
152+
}
153+
return getInternalMapper();
154+
}, [mapperInstance, getInternalMapper]);
155+
126156
// --- actor --- //
127157

128158
const actorProps = {
@@ -137,7 +167,8 @@ export default forwardRef(function SliceRepresentation(
137167
});
138168

139169
useEffect(() => {
140-
getActor().setMapper(getMapper());
170+
// workaround for vtkImageSlice.setMapper only taking vtkImageMapper
171+
getActor().setMapper(getMapper() as vtkImageMapper);
141172
}, [getActor, getMapper]);
142173

143174
useEffect(() => {
@@ -160,30 +191,58 @@ export default forwardRef(function SliceRepresentation(
160191

161192
const { iSlice, jSlice, kSlice, xSlice, ySlice, zSlice } = props;
162193

194+
// --- vtkImageMapper setSlice --- //
195+
163196
useEffect(() => {
164-
if (iSlice != null) trackModified(getMapper().setISlice(iSlice));
197+
const mapper = getMapper();
198+
if (isVtkImageMapper(mapper) && iSlice != null)
199+
trackModified(mapper.setISlice(iSlice));
165200
}, [iSlice, getMapper, trackModified]);
166201

167202
useEffect(() => {
168-
if (jSlice != null) trackModified(getMapper().setJSlice(jSlice));
203+
const mapper = getMapper();
204+
if (isVtkImageMapper(mapper) && jSlice != null)
205+
trackModified(mapper.setJSlice(jSlice));
169206
}, [jSlice, getMapper, trackModified]);
170207

171208
useEffect(() => {
172-
if (kSlice != null) trackModified(getMapper().setKSlice(kSlice));
209+
const mapper = getMapper();
210+
if (isVtkImageMapper(mapper) && kSlice != null)
211+
trackModified(mapper.setKSlice(kSlice));
173212
}, [kSlice, getMapper, trackModified]);
174213

175214
useEffect(() => {
176-
if (xSlice != null) trackModified(getMapper().setXSlice(xSlice));
215+
const mapper = getMapper();
216+
if (isVtkImageMapper(mapper) && xSlice != null)
217+
trackModified(mapper.setXSlice(xSlice));
177218
}, [xSlice, getMapper, trackModified]);
178219

179220
useEffect(() => {
180-
if (ySlice != null) trackModified(getMapper().setYSlice(ySlice));
221+
const mapper = getMapper();
222+
if (isVtkImageMapper(mapper) && ySlice != null)
223+
trackModified(mapper.setYSlice(ySlice));
181224
}, [ySlice, getMapper, trackModified]);
182225

183226
useEffect(() => {
184-
if (zSlice != null) trackModified(getMapper().setZSlice(zSlice));
227+
const mapper = getMapper();
228+
if (isVtkImageMapper(mapper) && zSlice != null)
229+
trackModified(mapper.setZSlice(zSlice));
185230
}, [zSlice, getMapper, trackModified]);
186231

232+
// --- vtkImageArrayMapper setSlice --- //
233+
234+
useEffect(() => {
235+
const mapper = getMapper();
236+
if (
237+
isVtkImageArrayMapper(mapper) &&
238+
kSlice != null &&
239+
kSlice !== mapper.getSlice()
240+
) {
241+
trackModified(true);
242+
mapper.setSlice(kSlice);
243+
}
244+
}, [kSlice, getMapper, trackModified]);
245+
187246
// --- //
188247

189248
const renderer = useRendererContext();

src/core/View.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default forwardRef(function View(props: ViewProps, fwdRef) {
1919

2020
const multiViewRoot = useContext(MultiViewRootContext);
2121

22-
const api = useMemo<IView | null>(() => {
22+
const api = useMemo<IView>(() => {
2323
const getView = () =>
2424
multiViewRoot ? parentedViewRef.current : singleViewRef.current;
2525
return {

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export { default as CellData } from './core/CellData';
99
export * as Contexts from './core/contexts';
1010
export { default as DataArray } from './core/DataArray';
1111
export type { DataArrayProps } from './core/DataArray';
12+
export { default as Dataset } from './core/Dataset';
13+
export type { DatasetProps } from './core/Dataset';
1214
export { default as FieldData } from './core/FieldData';
1315
export { default as Geometry2DRepresentation } from './core/Geometry2DRepresentation';
1416
export type { Geometry2DRepresentationProps } from './core/Geometry2DRepresentation';

usage/src/App.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ const demos = new Map([
2929
// ],
3030
// ['Volume/VolumeRendering', lazy(() => import('./Volume/VolumeRendering'))],
3131
// ['Volume/DynamicUpdate', lazy(() => import('./Volume/DynamicUpdate'))],
32+
[
33+
'Volume/ImageSeriesRendering',
34+
lazy(() => import('./Volume/ImageSeriesRendering')),
35+
],
3236
['Tests/PropertyUpdate', lazy(() => import('./Tests/PropertyUpdate'))],
3337
['Tests/CameraTest', lazy(() => import('./Tests/CameraTest'))],
3438
['Tests/ShareGeometry', lazy(() => import('./Tests/ShareGeometry'))],

usage/src/Volume/ImageSeriesRendering.jsx

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import React, { useState, useContext, useEffect } from 'react';
2-
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps.js';
31
import vtkCollection from '@kitware/vtk.js/Common/DataModel/Collection';
4-
import vtkImageArrayMapper from '@kitware/vtk.js/Rendering/Core/ImageArrayMapper.js';
5-
import vtkResourceLoader from '@kitware/vtk.js/IO/Core/ResourceLoader';
2+
import vtkITKHelper from '@kitware/vtk.js/Common/DataModel/ITKHelper';
63
import vtkLiteHttpDataAccessHelper from '@kitware/vtk.js/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper';
4+
import vtkResourceLoader from '@kitware/vtk.js/IO/Core/ResourceLoader';
5+
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps.js';
6+
import vtkImageArrayMapper from '@kitware/vtk.js/Rendering/Core/ImageArrayMapper.js';
77
import { unzipSync } from 'fflate';
8-
import vtkITKHelper from '@kitware/vtk.js/Common/DataModel/ITKHelper';
8+
import { useContext, useEffect, useMemo, useState } from 'react';
99

1010
import {
11-
View,
11+
Contexts,
1212
Dataset,
13-
ShareDataSet,
13+
RegisterDataSet,
14+
ShareDataSetRoot,
1415
SliceRepresentation,
15-
Contexts,
16+
UseDataSet,
17+
View,
1618
} from 'react-vtk-js';
1719

1820
function Slider(props) {
@@ -110,13 +112,11 @@ function CheckBox(props) {
110112
);
111113
}
112114

113-
114115
const loadData = async () => {
115116
console.log('Loading itk module...');
116117
loadData.setStatusText('Loading itk module...');
117-
if(!window.itk) {
118-
await vtkResourceLoader
119-
.loadScript(
118+
if (!window.itk) {
119+
await vtkResourceLoader.loadScript(
120120
'https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/itk-wasm.js'
121121
);
122122
}
@@ -144,7 +144,7 @@ const loadData = async () => {
144144

145145
// Read individual dcm files into an array of vtkImageData.
146146
const imageArray = [];
147-
if(window.itk) {
147+
if (window.itk) {
148148
await Promise.all(
149149
dcmFiles.map(async (filename, index) => {
150150
const { image: itkImage, webWorker } =
@@ -167,12 +167,13 @@ const loadData = async () => {
167167
collection.addItem(img);
168168
}
169169
const totalSlices = imageArray.reduce(
170-
(accumulator, currImage) => currImage.getDimensions()[2] + accumulator, 0
170+
(accumulator, currImage) => currImage.getDimensions()[2] + accumulator,
171+
0
171172
);
172173
loadData.setMaxSlicingValue(totalSlices - 1);
173174
loadData.setStatusText('');
174175
return collection;
175-
}
176+
};
176177

177178
function Example(props) {
178179
const [statusText, setStatusText] = useState('Loading data, please wait ...');
@@ -187,34 +188,50 @@ function Example(props) {
187188
loadData.setMaxSlicingValue = setMaxKSlice;
188189
loadData.setStatusText = setStatusText;
189190

190-
useEffect(
191-
() => {
192-
const img = mapper.getImage(kSlice);
193-
const range = img?.getPointData()?.getScalars()?.getRange();
194-
if(range && range.length == 2) {
195-
const maxWidth = range[1] - range[0];
196-
setColorWindow(maxWidth);
197-
const center = Math.round((range[0] + range[1]) / 2);
198-
setColorLevel(center);
199-
}
200-
},
201-
[kSlice]
191+
const [imageCollection, setImageCollection] = useState(null);
192+
193+
useEffect(() => {
194+
loadData().then((ds) => {
195+
window.ds = ds;
196+
setImageCollection(ds);
197+
});
198+
}, []);
199+
200+
useEffect(() => {
201+
const img = mapper.getImage(kSlice);
202+
const range = img?.getPointData()?.getScalars()?.getRange();
203+
if (range && range.length == 2) {
204+
const maxWidth = range[1] - range[0];
205+
setColorWindow(maxWidth);
206+
const center = Math.round((range[0] + range[1]) / 2);
207+
setColorLevel(center);
208+
}
209+
}, [kSlice, mapper]);
210+
211+
const cameraParams = useMemo(
212+
() => ({
213+
position: [400, 400, -1000],
214+
viewUp: [0, -1, 0],
215+
viewAngle: 75,
216+
directionOfProjection: [0, 0, 1],
217+
clippingRange: [-100, 100],
218+
parallelProjection: false,
219+
}),
220+
[]
202221
);
203222

204223
return (
205224
<div style={{ width: '100%', height: '100%' }}>
225+
<ShareDataSetRoot>
226+
<RegisterDataSet id='mixedImages'>
227+
<Dataset dataset={imageCollection} />
228+
</RegisterDataSet>
206229
<View
207230
id='0'
208-
cameraPosition={[0, 0, -1]}
209-
cameraViewUp={[0, -1, 0]}
210-
cameraParallelProjection={false}
231+
autoResetCamera={false}
232+
camera={cameraParams}
211233
background={[65 / 255, 86 / 255, 122 / 255]}
212234
>
213-
<ShareDataSet
214-
name='mixedImages'
215-
>
216-
<Dataset fetchData={loadData} />
217-
</ShareDataSet>
218235
<label
219236
style={{
220237
position: 'absolute',
@@ -269,11 +286,10 @@ function Example(props) {
269286
}}
270287
colorMapPreset={colorPreset}
271288
>
272-
<ShareDataSet
273-
name='mixedImages'
274-
/>
289+
<UseDataSet id='mixedImages' />
275290
</SliceRepresentation>
276291
</View>
292+
</ShareDataSetRoot>
277293
</div>
278294
);
279295
}

0 commit comments

Comments
 (0)