Kitware / vtk-js

Visualization Toolkit for the Web
https://kitware.github.io/vtk-js/
BSD 3-Clause "New" or "Revised" License
1.21k stars 370 forks source link

TypeError: Failed to execute 'bindTexture' on 'WebGL2RenderingContext': parameter 2 is not of type 'WebGLTexture' #2651

Closed sanderteirlynck closed 1 year ago

sanderteirlynck commented 1 year ago

High-level description

Unable to render ImageData coming from a Niftii file after using ImageMarchingCubes and WindowedSincPolyDataFilter.

Steps to reproduce

The (cleaned up) code I'm using that throws the error:

import { useEffect } from 'react';

// MUI imports
import {
    Container,
    Grid,
} from '@mui/material';

// VTK imports.
import '@kitware/vtk.js/Rendering/Profiles/Geometry';
import '@kitware/vtk.js/Rendering/Profiles/Volume';
import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
import vtkGenericRenderWindow from '@kitware/vtk.js/Rendering/Misc/GenericRenderWindow';
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps';
import vtkImageMarchingCubes from '@kitware/vtk.js/Filters/General/ImageMarchingCubes';
import vtkWindowedSincPolyDataFilter from '@kitware/vtk.js/Filters/General/WindowedSincPolyDataFilter';
import vtkITKHelper from '@kitware/vtk.js/Common/DataModel/ITKHelper';
import vtkLiteHttpDataAccessHelper from '@kitware/vtk.js/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper';

// ITK imports.
import { readImageArrayBuffer } from 'itk-wasm';

const App = () => {

    useEffect(() => {
        createWindow();
    }, []);

    const readFile = async (file) => {
        const arrayBuffer = await vtkLiteHttpDataAccessHelper.fetchBinary(file.path);

        const { image: itkImage, webWorker } = await readImageArrayBuffer(
            null,
            arrayBuffer,
            file.name,
        );
        webWorker.terminate();

        return vtkITKHelper.convertItkToVtkImage(itkImage);
    };

    const update = async (actor, renderer, renderWindow, imageData) => {
        // Set up color lookup table and opacity piecewise function.
        const lookupTable = vtkColorTransferFunction.newInstance();
        const piecewiseFun = vtkPiecewiseFunction.newInstance();

        // Set up color transfer function.
        lookupTable.applyColorMap(vtkColorMaps.getPresetByName('bone_Matlab'));

        // Initial mapping range.
        const range = imageData.getPointData().getScalars().getRange();
        lookupTable.setMappingRange(...range);
        lookupTable.updateRange();

        // Set up simple linear opacity function.
        // This assumes a data range of 0 -> 256.
        for (let i = 0; i <= 8; i++) {
            piecewiseFun.addPoint(i * 32, i / 8);
        }

        // Set the actor properties.
        actor.getProperty().setRGBTransferFunction(0, lookupTable);
        actor.getProperty().setScalarOpacity(0, piecewiseFun);
        actor.getProperty().setInterpolationTypeToLinear();

        // Add volume actor to scene.
        renderer.addVolume(actor);

        // Update lookup table mapping range based on input dataset.
        lookupTable.setMappingRange(...range);
        lookupTable.updateRange();

        // Reset camera and render scene.
        renderer.resetCamera();
        renderer.updateLightsGeometryToFollowCamera();
        renderWindow.render();
    };

    const createWindow = async () => {
        const container = document.querySelector('.container');
        const genericRenderWindow = vtkGenericRenderWindow.newInstance({
            background: [0, 0, 0],
        });
        genericRenderWindow.setContainer(container);
        genericRenderWindow.resize();

        const renderer = genericRenderWindow.getRenderer();
        const renderWindow = genericRenderWindow.getRenderWindow();

        const actor = vtkVolume.newInstance();
        const mapper = vtkVolumeMapper.newInstance();
        actor.setMapper(mapper);

        const file = {
            name: 'test.nii',
            path: 'data/test.nii',
        };

        const imageData = await readFile(file);

        // This is where the issues happen.
        const mCubes = vtkImageMarchingCubes.newInstance({
            contourValue: 0.5,
            computeNormals: true,
            mergePoints: true,
        });
        mCubes.setInputData(imageData);

        const smoothFilter = vtkWindowedSincPolyDataFilter.newInstance();
        smoothFilter.setInputData(mCubes.getOutputData());

        // mapper.setInputData(imageData); --> This works.
        mapper.setInputData(smoothFilter.getOutputData()); // --> This throws an error.

        update(actor, renderer, renderWindow, imageData);
    };

    return (
        <Container>
            <Grid container>
                <Grid item>
                    <div
                        className="container"
                        style={{ width: '400px', height: '400px' }}
                    ></div>
                </Grid>
            </Grid>
        </Container>
    );
};

export default App;

Detailed behavior

I'm unable to render ImageData from a Niftii file after trying to apply smoothing.

When I render the imageData variable, my volume appears and is interactive, as expected. However, it looks like there's some stair-stepping happening, so I'm trying to apply smoothing. That's where things go wrong.

When I apply smoothing by using ImageMarchingCubes and WindowedSincPolyDataFilter, the error appears in my browser. I've tried multiple browsers, I've tried a whole lot of suggestions online, I've tried other ways to render my file, I've tried applying PolyDataNormals on my WindowedSincPolyDataFilter output, I've tried upgrading the version through npm, ... But nothing seems to work.

I've opened a thread on the VTK Forums, but the suggestion I received didn't fix the issue. I've since tried other possible fixes, but nothing seems to work. Could this be a bug?

On Firefox the errors reads as follows:

TypeError: WebGL2RenderingContext.bindTexture: Argument 2 is not an object.

Expected behavior

After applying ImageMarchingCubes and WindowedSyncPolyDataFilter and rendering the output, a smooth volume appears.

Environment

floryst commented 1 year ago

You're using a vtkVolumeMapper when the input data is a polydata. You should use vtkMapper + vtkActor instead.

sanderteirlynck commented 1 year ago

Thank you for the suggestion, @floryst. That did solve the issue. Stupid mistake, as I tried this before but that threw an error as well, since I was changing the colors of the volumes. Removing the code to apply color to the volume rendered everything as expected. Thanks again!