Conversion from python VTK to VTK.js

Hello, I am fairly new to VTK and especially to VTK.js, so please forgive my ignorance …

I have a working VTK application in Python which I am trying to convert to VTK.js so that it can run in a browser. I am using CDN rather than NPM and am finding it hard to location some of the functions that I was using in Python.

As a very high level overview, I have 2D or 3D data points (arrays) which I am rendering as spheres. As a first step I am needing to convert the 2D/3D arrays to polyData before using that as inputs for generating the spheres etc.

In Python, some of the steps included:

pointdata = vtk.vtkPoints()
for (...):
   pointdata.InsertNextPoint(pt[0], pt[1], pt[2])

polydata = vtk.vtkPolyData()
polydata.SetPoints(pointdata)

I am finding it hard to determine where those corresponding functions lie within VTK.js, i.e.

pointdata = vtk.Common.Core.vtkPoints()

Is there some easy way to determine these paths?

As an aside, I am still calling some of my other Python code (to generate the data) through the backend, so if there was a better way of doing this that meant I could utilise my already generated code that would be very useful.

VTK/C++ and VTK.js share the same source location for all the core classes but nothing specifically tell you from the outside where things are. The best approach would be to download the source and use grep to find the classes you are looking for.

Also, they are 2 things you need to worry about:

  1. vtk.js is not covering everything that VTK/C++/Python is offering, so make sure you have all you need before doing a plain JS conversion. (Otherwise other options exist involving VTK/Python and vtk.js for doing local rendering)
  2. vtk.js provide some shortcut that should not be ignored when building your data structure that are different than VTK/C++/Python.

For example, to fill the coordinates in a Polydata depending how your xyzs are stored you can do something like:

const vtkDataArray =  vtk.Common.Core.vtkDataArray;
const polydata = vtk.Common.DataModel.vtkPolyData.newInstance();
polydata.getPoints().setData(Float64Array.from(xyzs), 3);
polydata.getPointData().setScalars(vtkDataArray.newInstance({ values: Float32Array.from(field) });

Otherwise if you already have a VTK/Python application and you are just looking at getting a Web interface for driving it and you don’t mind having a server running as your application, you can look at that example on how to share a scene for local rendering with VTK

HTH

Seb

Thanks for that Seb, I appreciate it. Firstly, I believe vtk.js should have everything I need (at a high level):

  • Ability to generate multiple spheres & lines in space, while also allowing for clicking on specific points to highlight the selected spheres
  • Ability to be able to view a 3D volume and perform slicing over it.

Secondly, I am finding the classes. It is a cumbersome way of doing it, but it is working thus far,

However, I am still having a little issue with the code you sent.

My polydata does not seem to contain any data? When I run the code through I get Invalid or missing input within the render.

As a test I tried the following:

var array = new Float64Array([0, 0, 0, 1, 1, 1])
polydata.getPoints().setData(array, 3)
console.log(polydata.getPointData().getNumberOfArrays())

expecting to see the data within polydata but I get a value of 0 returned. How can I see that the data has indeed been added to the polydata variable?

Many thanks

Hi,

Not as concise as Sebastien’s few lines but I build up polydata like so:

    const coneSource = vtkConeSource.newInstance({ height: 2, radius: 1, resolution: 80 });
    const polydata = coneSource.getOutputData();

    const N = polydata.getNumberOfPoints();
    let valuesRandom = [];
    for (let i = 0; i < N; i++) {
      valuesRandom[i] = Math.random();
    }


    const scalars = vtkDataArray.newInstance({
      values: valuesRandom,
      numberOfComponents: 1, 
      dataType: Float32Array, 
      name: 'scalars'
    });
    polydata.getPointData().setScalars(scalars);

Thanks again, I think the polydata is right now (based on polydata.getNumberOfPoints() showing the right value (I have 17000 xyz points in my example and this results in 51000 in the console) I am expecting 17000 spheres to be rendered on screen.

However, I am still getting No Input when I run the whole code through. My thoughts are some issue with the glyphs / appendData section? Any thoughts? (And sorry for taking your time!)

  // xyzLocations is a 2D array, convert to 1D
  oneD_xyzLocations = [].concat(...xyzLocations)

  const vtkDataArray = vtk.Common.Core.vtkDataArray
  const polydata = vtk.Common.DataModel.vtkPolyData.newInstance()
  polydata.getPoints().setData(oneD_xyzLocations, 3)

  // Add the scalar values to the polydata (just showing one here)

  var scalarAr = vtkDataArray.newInstance({
    numberOfComponents: scalars.length,
    values: scalars.map(function(value,idx) { return value[0]; }),
    name: scalarLbls[0]
  });

  polydata.getPointData().setScalars(scalarAr);
  polydata.getPointData().setActiveScalars(scalarLbls[0]);
  
  var N = polydata.getNumberOfPoints();

  const container = document.querySelector('#VTKPanel')

  // VTK renderWindow/renderer
  const renderWindow = vtk.Rendering.Core.vtkRenderWindow.newInstance();
  const renderer = vtk.Rendering.Core.vtkRenderer.newInstance();
  renderWindow.addRenderer(renderer);

  // WebGL.OpenGL impl
  const openGLRenderWindow = vtk.Rendering.OpenGL.vtkRenderWindow.newInstance();
  openGLRenderWindow.setContainer(container);
  openGLRenderWindow.setSize(1000, 1000);
  renderWindow.addView(openGLRenderWindow);

  // Interactor
  const interactor = vtk.Rendering.Core.vtkRenderWindowInteractor.newInstance();
  interactor.setView(openGLRenderWindow)
  interactor.initialize();
  interactor.bindEvents(container);

  // Interactor style
  const trackball = vtk.Interaction.Style.vtkInteractorStyleTrackballCamera.newInstance();
  interactor.setInteractorStyle(trackball);

  // Pipeline
  const sphereSrc = vtk.Filters.Sources.vtkSphereSource.newInstance();
  const glyphs = vtk.Rendering.Core.vtkGlyph3DMapper.newInstance();
  const actor = vtk.Rendering.Core.vtkActor.newInstance();
  const mapper = vtk.Rendering.Core.vtkMapper.newInstance();
  const appendData = vtk.Filters.General.vtkAppendPolyData.newInstance()

/**************/
// Attempt 1 */
/**************/
  appendData.addInputConnection(sphereSrc.getOutputPort())
  appendData.update()

  sphereSrc.setRadius(2)

  glyphs.setInputData(polydata)
  glyphs.setInputConnection(appendData.getOutputPort())
  glyphs.setScalarModeToUseCellData()
/**************/
/* Attempt 2 */
/**************/
  glyphs.setInputData(polydata)
  glyphs.setInputConnection(sphereSrc.getOutputPort())

/**************/
  actor.setMapper(glyphs);
  actor.getProperty().setColor(0.0, 1.0, 1.0);
  actor.getProperty().setOpacity(1.0);

  renderer.addActor(actor);

  // Render
  renderer.resetCamera();
  renderWindow.render();

I think that vtkGlyph3DMapper wants to take inputs of the form:
glyphs.setInputData(polydata_for_point_locations, 0);
glyphs.setInputData(glyphdata, 1);

I am not sure about defaults, but you may want to specify colour, orientation, scaling etc too.

1 Like

@fraser29 is right, the glyph mapper to have the shape to duplicate on port=1 while the points location on port=0.

Another approach that does not require to have actual geometry for the sphere is to use vtkSphereMapper.

Thank you both. It is working now.
For anyone looking in the future …

...
  /* Set up the Polydata */

  // Polydata holds the location and scalar values for the spheres
  var polydata = vtk.Common.DataModel.vtkPolyData.newInstance()
  polydata.getPoints().setData(Float64Array.from(oneD_xyzLocations), 3)

  // There is neighborhood data, so we should add it to the scalar values here.
  var scalarAr = vtk.Common.Core.vtkDataArray.newInstance({
    numberOfComponents: 1,
    values: values,
    name: 'name'
  });
  polydata.getPointData().setScalars(scalarAr);

  /*Set up the viewer */

  // Where the VTK will be placed in the HTML
  const container = document.querySelector('#VTKPanel')

  // VTK renderWindow/renderer
  const renderWindow = vtk.Rendering.Core.vtkRenderWindow.newInstance();
  const renderer = vtk.Rendering.Core.vtkRenderer.newInstance();
  renderWindow.addRenderer(renderer);

  // WebGL.OpenGL impl
  const openGLRenderWindow = vtk.Rendering.OpenGL.vtkRenderWindow.newInstance();
  openGLRenderWindow.setContainer(container);
  const { width, height } = container.getBoundingClientRect();
  openGLRenderWindow.setSize(width, height);
  renderWindow.addView(openGLRenderWindow);

  // Interactor
  const interactor = vtk.Rendering.Core.vtkRenderWindowInteractor.newInstance();
  interactor.setView(openGLRenderWindow)
  interactor.initialize();
  interactor.bindEvents(container);

  // Interactor style
  const trackball = vtk.Interaction.Style.vtkInteractorStyleTrackballCamera.newInstance();
  interactor.setInteractorStyle(trackball);

  // Pipeline
  let sphereSrc = vtk.Filters.Sources.vtkSphereSource.newInstance();
  let glyph3DMapper = vtk.Rendering.Core.vtkGlyph3DMapper.newInstance();
  var actor = vtk.Rendering.Core.vtkActor.newInstance();

  sphereSrc.setRadius(0.01)

  glyph3DMapper.setInputData(polydata, 0)
  glyph3DMapper.setInputConnection(sphereSrc.getOutputPort(), 1)

  actor.setMapper(glyph3DMapper);
  actor.getProperty().setColor(0.0, 1.0, 0.0);
  actor.getProperty().setOpacity(1.0);

  renderer.addActor(actor);

  // Render
  renderer.resetCamera();
  renderWindow.render();

2 Likes