Hi there,
I’m new to vtk.js and attempting to recreate the Volume Viewer demo inside a simple React app with one of the .vti files included in the demo. I’ve included the same .vti file inside the codebase for the React app, attempted to load it, and all I have been able to manage to render is a completely blank screen.
My App.js file is below and I am using the head-binary.vti file from here. You will see that it borrows heavily from the Volume Viewer demo and the vtk.js React demo components.
The result is this beautiful blank purple scene:
Could anyone please help point me in the right direction? Thank you!
function App() {
const vtkContainerRef = useRef(null);
const context = useRef(null);
function makeRequest(method, url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.responseType = "blob";
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject({
status: this.status,
statusText: xhr.statusText,
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText,
});
};
xhr.send();
});
}
useEffect(() => {
if (!context.current) {
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
rootContainer: vtkContainerRef.current,
});
const createViewer = (res) => {
const vtiReader = vtkXMLImageDataReader.newInstance();
const renderer = fullScreenRenderer.getRenderer();
const renderWindow = fullScreenRenderer.getRenderWindow();
renderWindow.getInteractor().setDesiredUpdateRate(15);
vtiReader.parseAsArrayBuffer(res);
const mapper = vtkVolumeMapper.newInstance();
const actor = vtkVolume.newInstance();
actor.setMapper(mapper);
mapper.setInputData(source);
renderer.addActor(actor);
const dataArray =
source.getPointData().getScalars() ||
source.getPointData().getArrays()[0];
const dataRange = dataArray.getRange();
const lookupTable = vtkColorTransferFunction.newInstance();
const piecewiseFunction = vtkPiecewiseFunction.newInstance();
const sampleDistance =
0.7 *
Math.sqrt(
source
.getSpacing()
.map((v) => v * v)
.reduce((a, b) => a + b, 0)
);
mapper.setSampleDistance(sampleDistance);
actor.getProperty().setRGBTransferFunction(0, lookupTable);
actor.getProperty().setScalarOpacity(0, piecewiseFunction);
actor.getProperty().setInterpolationTypeToFastLinear();
actor.getProperty().setInterpolationTypeToLinear();
// For better looking volume rendering
// - distance in world coordinates a scalar opacity of 1.0
actor
.getProperty()
.setScalarOpacityUnitDistance(
0,
vtkBoundingBox.getDiagonalLength(source.getBounds()) /
Math.max(...source.getDimensions())
);
// - control how we emphasize surface boundaries
// => max should be around the average gradient magnitude for the
// volume or maybe average plus one std dev of the gradient magnitude
// (adjusted for spacing, this is a world coordinate gradient, not a
// pixel gradient)
// => max hack: (dataRange[1] - dataRange[0]) * 0.05
actor.getProperty().setGradientOpacityMinimumValue(0, 0);
actor
.getProperty()
.setGradientOpacityMaximumValue(
0,
(dataRange[1] - dataRange[0]) * 0.05
);
// - Use shading based on gradient
actor.getProperty().setShade(true);
actor.getProperty().setUseGradientOpacity(0, true);
// - generic good default
actor.getProperty().setGradientOpacityMinimumOpacity(0, 0.0);
actor.getProperty().setGradientOpacityMaximumOpacity(0, 1.0);
actor.getProperty().setAmbient(0.2);
actor.getProperty().setDiffuse(0.7);
actor.getProperty().setSpecular(0.3);
actor.getProperty().setSpecularPower(8.0);
// Control UI
const controllerWidget = vtkVolumeController.newInstance({
size: [400, 150],
rescaleColorMap: true,
});
// setUpContent above sets the size to the container.
// We need to set the size after that.
// controllerWidget.setExpanded(false);
fullScreenRenderer.setResizeCallback(({ width, height }) => {
// 2px padding + 2x1px boder + 5px edge = 14
if (width > 414) {
controllerWidget.setSize(400, 150);
} else {
controllerWidget.setSize(width - 14, 150);
}
controllerWidget.render();
});
renderer.resetCamera();
renderWindow.render();
global.pipeline = {
actor,
renderer,
renderWindow,
lookupTable,
mapper,
source,
piecewiseFunction,
fullScreenRenderer,
};
context.current = {
fullScreenRenderer,
renderWindow,
renderer,
actor,
mapper,
};
};
makeRequest("GET", "../head-binary.vti").then((res) => {
const reader = new FileReader();
reader.onload = function onLoad(e) {
createViewer(reader.result);
};
const file = new File([res], "head-binary.vti");
const fileObj = { file, ext: "vti" };
reader.readAsArrayBuffer(fileObj.file);
});
}
return () => {
if (context.current) {
const { fullScreenRenderer, actor, mapper } = context.current;
actor.delete();
mapper.delete();
fullScreenRenderer.delete();
context.current = null;
}
};
}, [vtkContainerRef]);
return (
<div>
<div ref={vtkContainerRef} />
</div>
);
}
export default App;