Here is my code:
import '@kitware/vtk.js/favicon';
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
import '@kitware/vtk.js/Rendering/Profiles/Volume';
import vtkBoundingBox from '@kitware/vtk.js/Common/DataModel/BoundingBox';
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
import vtkVolumeController from '@kitware/vtk.js/Interaction/UI/VolumeController';
import vtkURLExtract from '@kitware/vtk.js/Common/Core/URLExtract';
import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
import vtkXMLImageDataReader from '@kitware/vtk.js/IO/XML/XMLImageDataReader';
import vtkPolyLineWidget from '@kitware/vtk.js/Widgets/Widgets3D/PolyLineWidget';
import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
// Force DataAccessHelper to have access to various data source
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HtmlDataAccessHelper';
import '@kitware/vtk.js/IO/Core/DataAccessHelper/JSZipDataAccessHelper';
import style from './VolumeViewer.module.css';
let autoInit = true;
const userParams = vtkURLExtract.extractURLParameters();
// ----------------------------------------------------------------------------
function emptyContainer(container) {
while (container.firstChild) {
container.removeChild(container.firstChild);
}
}
// ----------------------------------------------------------------------------
function createViewer(rootContainer, fileContents, options) {
const controlPanel =`
<button>GrabFocus</button>
<input type="checkbox">Show SVG layer
`;
const background = options.background
? options.background.split(',').map((s) => Number(s))
: [0.5, 0.5, 0.5];
const containerStyle = options.containerStyle;
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
background,
rootContainer,
containerStyle,
});
const renderer = fullScreenRenderer.getRenderer();
const renderWindow = fullScreenRenderer.getRenderWindow();
renderWindow.getInteractor().setDesiredUpdateRate(30);
const vtiReader = vtkXMLImageDataReader.newInstance();
vtiReader.parseAsArrayBuffer(fileContents);
const source = vtiReader.getOutputData();
const mapper = vtkVolumeMapper.newInstance();
const actor = vtkVolume.newInstance();
const dataArray =
source.getPointData().getScalars() || source.getPointData().getArrays()[0];
const dataRange = dataArray.getRange();
const lookupTable = vtkColorTransferFunction.newInstance();
const piecewiseFunction = vtkPiecewiseFunction.newInstance();
// Pipeline handling
//console.log(source.getOutputPort());
mapper.setInputData(source);
mapper.setSampleDistance(1.1);
actor.setMapper(mapper);
lookupTable.addRGBPoint(0, 85 / 255.0, 0, 0);
lookupTable.addRGBPoint(95, 1.0, 1.0, 1.0);
lookupTable.addRGBPoint(225, 0.66, 0.66, 0.5);
lookupTable.addRGBPoint(255, 0.0, 1.0, 0.5);
piecewiseFunction.addPoint(0.0, 0.0);
piecewiseFunction.addPoint(100.0, 1.0);
piecewiseFunction.addPoint(255.0, 1.0);
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,
// });
// controllerWidget.setContainer(rootContainer);
// controllerWidget.setupContent(renderWindow, actor, false);
// ----------------------------------------------------------------------------
// Widget manager
// ----------------------------------------------------------------------------
const widgetManager = vtkWidgetManager.newInstance();
widgetManager.setRenderer(renderer);
//
const widget = vtkPolyLineWidget.newInstance();
widget.placeWidget(source.getBounds());
//
widgetManager.addWidget(widget);
//
renderer.resetCamera();
widgetManager.enablePicking();
widgetManager.grabFocus(widget);
// ---------------------------------------------------------------------------
// First render
renderer.addVolume(actor);
renderer.resetCamera();
renderWindow.render();
// -----------------------------------------------------------
// UI control handling
// -----------------------------------------------------------
fullScreenRenderer.addController(controlPanel);
document.querySelector('button').addEventListener('click', () => {
widgetManager.grabFocus(widget);
});
document
.querySelector('input[type=checkbox]')
.addEventListener('change', (ev) => {
widgetManager.setUseSvgLayer(ev.target.checked);
});
}
// ----------------------------------------------------------------------------
export function load(container, options) {
autoInit = false;
emptyContainer(container);
const reader = new FileReader();
reader.onload = function onLoad() {
createViewer(container, reader.result, options);
};
reader.readAsArrayBuffer(options.file);
}
export function initLocalFileLoader(container) {
const exampleContainer = document.querySelector('.content');
const rootBody = document.querySelector('body');
const myContainer = container || exampleContainer || rootBody;
const fileContainer = document.createElement('div');
fileContainer.innerHTML = `<div class="${style.bigFileDrop}"/><input type="file" accept=".vti" style="display: none;"/>`;
myContainer.appendChild(fileContainer); //这个fileContainer是加载界面图,需要先加载再卸载
const fileInput = fileContainer.querySelector('input');
function handleFile(e) {
const dataTransfer = e.dataTransfer;
const files = e.target.files || dataTransfer.files;
myContainer.removeChild(fileContainer);
const ext = files[0].name.split('.').slice(-1)[0];
const options = { file: files[0], ext, ...userParams };
load(myContainer, options);
}
fileInput.addEventListener('change', handleFile);
fileContainer.addEventListener('click', (e) => fileInput.click());
}
// Auto setup if no method get called within 100ms
setTimeout(() => {
if (autoInit) {
initLocalFileLoader();
}
}, 100);