vtk.js How to merge VolumeViewer with PolylineWidget

Hello!

I am trying to merge the example VolumeViewer and PolyLineWidget, but there is something wrong.

I add the necessary PolylineWidget code in function “createViewer”. When I added

widgetManager.setRenderer(renderer);

it couldn’t change the angle of view by the left mouse button. Moreover, when I clicked on the button ‘GrabFocus’, the PolylineWidget doesn’t work.

Could you please help me? Thank you!

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);

In the Widget manager section of your code, you have a call to grabFocus(widget). That will automatically focus the polyline widget when the viewer is created, which will prevent you from rotating the scene.