Skip to content

Commit 9a59e1c

Browse files
authored
Merge pull request #95 from jadh4v/ENH-update-for-vtkjs-ImageArrayMapper
feat(ImageArrayMapper): New vtk.js mapper for array of mixed images
2 parents 833d192 + f4f8fed commit 9a59e1c

10 files changed

+489
-47
lines changed

package-lock.json

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@
4242
"dev": "rollup ./src/index.js -c --watch"
4343
},
4444
"peerDependencies": {
45-
"@kitware/vtk.js": "^25.14.2",
45+
"@kitware/vtk.js": "^26.5.3",
4646
"react": "^16.0.0"
4747
},
4848
"devDependencies": {
4949
"@babel/core": "^7.12.10",
5050
"@babel/plugin-transform-runtime": "^7.12.10",
5151
"@babel/preset-env": "^7.12.11",
5252
"@babel/preset-react": "^7.12.10",
53-
"@kitware/vtk.js": "^25.14.2",
53+
"@kitware/vtk.js": "^26.5.3",
5454
"@rollup/plugin-babel": "^5.2.2",
5555
"@rollup/plugin-commonjs": "17.0.0",
5656
"@rollup/plugin-eslint": "^8.0.1",

src/core/Dataset.js

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React, { Component } from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import {
5+
RepresentationContext,
6+
DownstreamContext,
7+
DataSetContext,
8+
} from './View';
9+
10+
/**
11+
* The Dataset component exposes any input data object to a downstream filter.
12+
* It allows you to set the input data instance directly through props.data,
13+
* or through the async/lazy loading mechanism using a callback function
14+
* props.fetchData().
15+
* It takes the following set of properties:
16+
* - data: directly sets the input data instance.
17+
* - fetchData: callback function to fetch input data asynchronously.
18+
*/
19+
export default class Dataset extends Component {
20+
componentDidMount() {
21+
if (this.dataset && !this.dataset.isDeleted()) {
22+
// data already available
23+
this.dataAvailable();
24+
} else {
25+
// update data from current props
26+
const prevProps = { data: null, fetchData: null };
27+
this.update(this.props, prevProps);
28+
}
29+
}
30+
31+
componentWillUnmount() {}
32+
33+
render() {
34+
return (
35+
<RepresentationContext.Consumer>
36+
{(representation) => (
37+
<DownstreamContext.Consumer>
38+
{(downstream) => {
39+
this.representation = representation;
40+
if (!this.downstream) {
41+
this.downstream = downstream;
42+
}
43+
return (
44+
<DataSetContext.Provider value={this}>
45+
<div key={this.props.id} id={this.props.id}>
46+
{this.props.children}
47+
</div>
48+
</DataSetContext.Provider>
49+
);
50+
}}
51+
</DownstreamContext.Consumer>
52+
)}
53+
</RepresentationContext.Consumer>
54+
);
55+
}
56+
57+
componentDidUpdate(prevProps, prevState, snapshot) {
58+
this.update(this.props, prevProps);
59+
}
60+
61+
update(props, previous) {
62+
const { data, fetchData } = props;
63+
if (data && data !== previous.data) {
64+
// direct assignment of data object
65+
this.dataset = data;
66+
this.dataAvailable();
67+
} else if (fetchData && fetchData !== previous.fetchData) {
68+
// async fetch data
69+
fetchData().then((response) => {
70+
if (response) {
71+
this.dataset = response;
72+
this.dataAvailable();
73+
}
74+
});
75+
}
76+
}
77+
78+
dataAvailable() {
79+
if (this.downstream && this.dataset) {
80+
this.downstream.setInputData(this.dataset);
81+
}
82+
83+
if (this.representation) {
84+
this.representation.dataAvailable();
85+
this.representation.dataChanged();
86+
}
87+
}
88+
}
89+
90+
Dataset.defaultProps = {
91+
data: null,
92+
fetchData: null,
93+
};
94+
95+
Dataset.propTypes = {
96+
/**
97+
* The ID used to identify this component.
98+
*/
99+
id: PropTypes.string,
100+
101+
/**
102+
* Directly set the dataset object as a property value.
103+
*/
104+
data: PropTypes.object,
105+
106+
/**
107+
* Optional callback function for async loading of input data.
108+
*/
109+
fetchData: PropTypes.func,
110+
111+
children: PropTypes.oneOfType([
112+
PropTypes.arrayOf(PropTypes.node),
113+
PropTypes.node,
114+
]),
115+
};

src/core/SliceRepresentation.js

+59-32
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ export default class SliceRepresentation extends Component {
3333
this.lookupTable.applyColorMap(preset);
3434
this.piecewiseFunction = vtkPiecewiseFunction.newInstance();
3535
this.actor = vtkImageSlice.newInstance({ visibility: false });
36-
this.mapper = vtkImageMapper.newInstance();
36+
// use the mapper instance if provided, otherwise create default instance.
37+
this.mapper = props.mapperInstance ?? vtkImageMapper.newInstance();
3738
this.actor.setMapper(this.mapper);
38-
3939
this.actor.getProperty().setRGBTransferFunction(0, this.lookupTable);
4040
// this.actor.getProperty().setScalarOpacity(0, this.piecewiseFunction);
4141
this.actor.getProperty().setInterpolationTypeToLinear();
@@ -105,7 +105,11 @@ export default class SliceRepresentation extends Component {
105105
if (property && (!previous || property !== previous.property)) {
106106
changed = this.actor.getProperty().set(property) || changed;
107107
}
108-
if (mapper && (!previous || mapper !== previous.mapper)) {
108+
if (
109+
mapper &&
110+
(!previous || mapper !== previous.mapper) &&
111+
mapper !== this.mapper
112+
) {
109113
changed = this.mapper.set(mapper) || changed;
110114
}
111115
if (
@@ -146,25 +150,35 @@ export default class SliceRepresentation extends Component {
146150
}
147151
}
148152

149-
// ijk
150-
if (iSlice != null && (!previous || iSlice !== previous.iSlice)) {
151-
changed = this.mapper.setISlice(iSlice) || changed;
152-
}
153-
if (jSlice != null && (!previous || jSlice !== previous.jSlice)) {
154-
changed = this.mapper.setJSlice(jSlice) || changed;
155-
}
156-
if (kSlice != null && (!previous || kSlice !== previous.kSlice)) {
157-
changed = this.mapper.setKSlice(kSlice) || changed;
158-
}
159-
// xyz
160-
if (xSlice != null && (!previous || xSlice !== previous.xSlice)) {
161-
changed = this.mapper.setXSlice(xSlice) || changed;
162-
}
163-
if (ySlice != null && (!previous || ySlice !== previous.ySlice)) {
164-
changed = this.mapper.setYSlice(ySlice) || changed;
165-
}
166-
if (zSlice != null && (!previous || zSlice !== previous.zSlice)) {
167-
changed = this.mapper.setZSlice(zSlice) || changed;
153+
// check if we have valid input
154+
if (this.validData) {
155+
if (this.mapper.isA('vtkImageMapper')) {
156+
// ijk
157+
if (iSlice != null && (!previous || iSlice !== previous.iSlice)) {
158+
changed = this.mapper.setISlice(iSlice) || changed;
159+
}
160+
if (jSlice != null && (!previous || jSlice !== previous.jSlice)) {
161+
changed = this.mapper.setJSlice(jSlice) || changed;
162+
}
163+
if (kSlice != null && (!previous || kSlice !== previous.kSlice)) {
164+
changed = this.mapper.setKSlice(kSlice) || changed;
165+
}
166+
// xyz
167+
if (xSlice != null && (!previous || xSlice !== previous.xSlice)) {
168+
changed = this.mapper.setXSlice(xSlice) || changed;
169+
}
170+
if (ySlice != null && (!previous || ySlice !== previous.ySlice)) {
171+
changed = this.mapper.setYSlice(ySlice) || changed;
172+
}
173+
if (zSlice != null && (!previous || zSlice !== previous.zSlice)) {
174+
changed = this.mapper.setZSlice(zSlice) || changed;
175+
}
176+
} else if (this.mapper.isA('vtkImageArrayMapper')) {
177+
// vtkImageArrayMapper only supports k-slicing
178+
if (kSlice != null && (!previous || kSlice !== previous.kSlice)) {
179+
changed = this.mapper.setSlice(kSlice) || changed;
180+
}
181+
}
168182
}
169183

170184
// actor visibility
@@ -186,6 +200,11 @@ export default class SliceRepresentation extends Component {
186200
this.validData = true;
187201
this.actor.setVisibility(this.currentVisibility);
188202

203+
// reset camera after input data is lazy-loaded
204+
if (this.view && this.view.props.autoResetCamera) {
205+
this.view.resetCamera();
206+
}
207+
189208
// trigger render
190209
this.dataChanged();
191210
}
@@ -194,16 +213,18 @@ export default class SliceRepresentation extends Component {
194213
dataChanged() {
195214
if (this.props.colorDataRange === 'auto') {
196215
this.mapper.update();
197-
const input = this.mapper.getInputData();
198-
const array = input && input.getPointData()?.getScalars();
199-
const dataRange = array && array.getRange();
200-
if (dataRange) {
201-
this.lookupTable.setMappingRange(...dataRange);
202-
this.lookupTable.updateRange();
203-
this.piecewiseFunction.setNodes([
204-
{ x: dataRange[0], y: 0, midpoint: 0.5, sharpness: 0 },
205-
{ x: dataRange[1], y: 1, midpoint: 0.5, sharpness: 0 },
206-
]);
216+
if (this.mapper.getInputData()) {
217+
const input = this.mapper.getCurrentImage();
218+
const array = input && input.getPointData()?.getScalars();
219+
const dataRange = array && array.getRange();
220+
if (dataRange) {
221+
this.lookupTable.setMappingRange(...dataRange);
222+
this.lookupTable.updateRange();
223+
this.piecewiseFunction.setNodes([
224+
{ x: dataRange[0], y: 0, midpoint: 0.5, sharpness: 0 },
225+
{ x: dataRange[1], y: 1, midpoint: 0.5, sharpness: 0 },
226+
]);
227+
}
207228
}
208229

209230
if (this.view) {
@@ -229,6 +250,12 @@ SliceRepresentation.propTypes = {
229250
*/
230251
mapper: PropTypes.object,
231252

253+
/**
254+
* Optional parameter to set vtk mapper instance from outside.
255+
* Allows to control which mapper class {vtkImageMapper, vtkImageArrayMapper} to use.
256+
*/
257+
mapperInstance: PropTypes.object,
258+
232259
/**
233260
* Properties to set to the slice/actor
234261
*/

src/core/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import vtkPointData from './PointData';
55
import vtkPolyData from './PolyData';
66
import vtkReader from './Reader';
77
import vtkShareDataSet from './ShareDataSet';
8+
import vtkDataset from './Dataset';
89
import vtkView from './ViewContainer';
910
import vtkGeometryRepresentation from './GeometryRepresentation';
1011
import vtkGeometry2DRepresentation from './Geometry2DRepresentation';
@@ -24,6 +25,7 @@ export const PointData = vtkPointData;
2425
export const PolyData = vtkPolyData;
2526
export const Reader = vtkReader;
2627
export const ShareDataSet = vtkShareDataSet;
28+
export const DataSet = vtkDataset;
2729
export const View = vtkView;
2830
export const GeometryRepresentation = vtkGeometryRepresentation;
2931
export const Geometry2DRepresentation = vtkGeometry2DRepresentation;
@@ -44,6 +46,7 @@ export default {
4446
PolyData: vtkPolyData,
4547
Reader: vtkReader,
4648
ShareDataSet: vtkShareDataSet,
49+
Dataset: vtkDataset,
4750
View: vtkView,
4851
GeometryRepresentation: vtkGeometryRepresentation,
4952
Geometry2DRepresentation: vtkGeometry2DRepresentation,

src/light.js

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const PointData = Core.PointData;
1818
export const PolyData = Core.PolyData;
1919
export const Reader = Core.Reader;
2020
export const ShareDataSet = Core.ShareDataSet;
21+
export const Dataset = Core.Dataset;
2122
export const View = Core.View;
2223
export const GeometryRepresentation = Core.GeometryRepresentation;
2324
export const Geometry2DRepresentation = Core.Geometry2DRepresentation;

0 commit comments

Comments
 (0)