Volume Controller genericRenderWindow

Hi,

I managed to get the volume viewer functionality of adding the controller widget to work in full screen render mode. However, I experience difficulty making it work with a generic render window (so not full screen). In the html a div is created with id ‘container’, this container is used in the javascript to render the window in that div.

 <div id="container" class="rounded shadow-lg" ></div>
   <script type="text/javascript">
          const {renderer, renderWindow } = window.createVTKWindow("#container", file);
                window.renderer = renderer;
                window.renderWindow = renderWindow;
     </script>
  </div>

In the javascript I tried the following:

function createVTKWindow(name, url) {
  const container = document.querySelector(`container id from div`);
  const background = userParams.background
  ? options.background.split(',').map((s) => Number(s))
   : [1, 1, 1];
  const containerStyle = userParams.containerStyle;
  const genericRenderWindow = vtkGenericRenderWindow.newInstance({
    background,
    containerStyle,
  });
  genericRenderWindow.setContainer(container);

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

  const camera = renderer.getActiveCamera()
  renderer.setActiveCamera(window.camera);

  fetch(url)
      .then(resp => resp.blob())
      .then(blob => {
          onDownloaded(container, renderer, renderWindow, blob, function() {
             $("#loader").remove();
         });
     })
      .catch(() => {
         console.log('Error retrieving data..')
         $("#loader").remove();
     });

  return {
    renderer: renderer,
    renderWindow: renderWindow
 };
window.createVTKWindow = createVTKWindow;

function onDownloaded(container, renderer, renderWindow, bin, completion = null) {
const reader = new FileReader();
reader.onload = function onLoad(e) {
    const vtiReader = vtkXMLImageDataReader.newInstance();
    vtiReader.parseAsArrayBuffer(reader.result);

    // --- Set  up the cone actor ---
    const source = vtiReader.getOutputData(0);
    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 */
    actor.setMapper(mapper);
    mapper.setInputData(source);
    renderer.addActor(actor);

    const sampleDistance =
        0.7 *
        Math.sqrt(
            source
            .getSpacing()
            .map((v) => v * v)
            .reduce((a, b) => a + b, 0)
        );

    mapper.setSampleDistance(sampleDistance);

    // set the actor properties
    actor.getProperty().setRGBTransferFunction(0, lookupTable);
    actor.getProperty().setScalarOpacity(0, piecewiseFunction);
    actor.getProperty().setInterpolationTypeToLinear();

    actor
        .getProperty()
        .setScalarOpacityUnitDistance(
            0,
            vtkBoundingBox.getDiagonalLength(source.getBounds()) /
            Math.max(...source.getDimensions())
        );

    actor.getProperty().setGradientOpacityMinimumValue(0, 0);
    actor
        .getProperty()
        .setGradientOpacityMaximumValue(0, (dataRange[1] - dataRange[0]) * 0.05);
    actor.getProperty().setShade(true);
    actor.getProperty().setUseGradientOpacity(0, true);
    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);

     const controllerWidget = vtkVolumeController.newInstance({
        size: [200, 100],
        rescaleColorMap: true,
        expanded: true,
      });

    const isBackgroundDark = background[0] + background[1] + background[2] < 1.5;
    controllerWidget.setContainer(container);
    controllerWidget.setupContent(renderWindow, actor, isBackgroundDark);
    controllerWidget.setSize(200, 100);
    controllerWidget.render();
    fpsMonitor.update();

    // --- Add actor to scene ---
    renderer.resetCamera();
    renderWindow.render();

    if (completion) completion();

};
  reader.readAsArrayBuffer(new File([bin], "data"));
}
window.onDownloaded = onDownloaded;

What results from this is a rendered window within the specified div. However, the window remains black and keeps on loading. How do I make the Volume Controller work with a generic render window? Can someone help? Thanks in advance!

You should not share the same container between the RenderWindow and the VolumeController. Each of them should have their own div…

So I could put in the div above, another div for the controller? Then select that container and set it as the container for the controller widget?

Yes that was the idea… While also making sure the controller is above (z-index) the render window container…

@Sebastien_Jourdain I tried creating a new div as you mentioned. I added the following in the javascript:

const controllerContainer = document.createElement('div');
controllerContainer.innerHTML = `<div id="controller" style="
            display: flex;
            align-items: stretch;
            flex-direction: column;
            justify-content: space-between;
            position: absolute;
            top: 5px;
            left: 5px;
            background: rgba(128, 128, 128, 0.5);
            background-color: rgba(128, 128, 128, 0.5);
            background-position-x: 0%;
            background-position-y: 0%;
            background-repeat: repeat;
            background-attachment: scroll;
            background-image: none;
            background-size: auto;
            background-origin: padding-box;
            background-clip: border-box;
            border-radius: 5px;
            border: 0.5px solid black;
            box-sizing: border-box;
            padding: 2px;
            z-index:5;"><div/>`;
container.appendChild(controllerContainer);

The container is the window where the volume rendering happens. So I append the constructed controllerContainer to the other container. However, I can find the div in the browser but the widget does not render in it and the page keeps on loading the entire time. What am I doing wrong?

       const controllerWidget = vtkVolumeController.newInstance({
            size: [450, 150],
            rescaleColorMap: true,
            expanded: true,
          });

        const isBackgroundDark = background[0] + background[1] + background[2] < 1.5;
        controllerWidget.setContainer(controllerContainer);
        controllerWidget.setupContent(renderWindow, actor, isBackgroundDark);
        controllerWidget.setSize(450, 150);
        controllerWidget.render();
        fpsMonitor.update();

The following screen is what I receive:

I finally found what’s wrong and it’s working now! I have only one question left. The sliders of the widget (for shadow and the colormap, see picture below) are not working.
image
The rest is working very well. Is there perhaps a bug in the sliders? As the only thing that I am supposed to do is load in the controller and give it a container.

I don’t think there is any issue with the sliders. Can you explain what you mean by not working?

  1. You can not grab them to make them move?
  2. You can move them, but nothing change in the rendering?
  3. Something else?

It’s the first scenario you mention.

Adjust the z-index of the container that host the controller so it can go above the one that is grabbing the mouse events.

I did what you suggested, but unfortunately without success. This is what I did:

const controllerContainer = document.createElement('div');
controllerContainer.style.zIndex = "10";
container.appendChild(controllerContainer);

const controllerWidget = vtkVolumeController.newInstance({
     size: [300, 150],
     rescaleColorMap: true,
      expanded: true,
   });

const isBackgroundDark = background[0] + background[1] + background[2] < 1.5;
controllerWidget.setContainer(controllerContainer);
controllerWidget.setupContent(renderWindow, actor, isBackgroundDark);
controllerWidget.setSize(300, 150);
controllerWidget.render();
fpsMonitor.update();

I intentionally gave a large z-index in order to make sure it is higher than the window container. However this sadly does not solve the problem. Am I still doing something wrong or could there be another reason for not being able to drag those sliders? (I also have this problem with the fullscreenRenderWindow version)

If you had this issue with the fullscreenRenderWindow controller panel, that probably means you have something else still above your sliders. Is your page only the RW and controller? Or something else is there too?

You might need to fix the container for your RW with a smaller zindex.

Yes the page is only the renderwindow and the controller

It should work (assuming you also changed the z-index of the RenderWindow container too).
Is the example working as expected on the vtk.js web site?

Yes in the example the sliders work, so I guess it works as expected. It does work when I use the JavaScript obtained from the html page in the example, but not when I use the ES6 code.

The example that works for you is generated from that es6 code. You must be doing something special but it is hard to tell. It seems to be related to just JavaScript/HTML issue and not really on vtk.js.