How scalar opacity slider/blending mode selector can be used to interact with two volume at once?

Hello everyone,
I upload two samples at the same time so that both can be rendered in the same canvas with different colours and the below code works very well for that.

But now I want to control their scalar opacity and blending modes with event listener which I have already created in the HTML section. If I load two samples and play with the Scalar opacity slider and blending modes, it nicely interacts with the second sample (second uploaded sample) but not with the first although both samples were rendered at the same time.

I tried putting renderWindow.render() inside/outside of for loop but it didn’t work at all and I also did not find any example which can help me to solve this problem.

var container = document.getElementById('viewContainer');
      const openglRenderWindow = vtk.Rendering.OpenGL.vtkRenderWindow.newInstance();
      const renderer = vtk.Rendering.Core.vtkRenderer.newInstance();
      const renderWindow = vtk.Rendering.Core.vtkRenderWindow.newInstance();
      const interactorStyle = vtk.Interaction.Style.vtkInteractorStyleTrackballCamera.newInstance();
      const interactor = vtk.Rendering.Core.vtkRenderWindowInteractor.newInstance();
      const vtkBoundingBox = vtk.Common.DataModel.vtkBoundingBox.newInstance();
      const vtkColorMaps = vtk.Rendering.Core.vtkColorTransferFunction.vtkColorMaps;
      const vtkColorTransferFunction = vtk.Rendering.Core.vtkColorTransferFunction;
      const vtkConvolution2DPass = vtk.Rendering.OpenGL.vtkConvolution2DPass;
      const vtkCamera = vtk.Rendering.Core.vtkCamera;

       openglRenderWindow.setContainer(container);
       renderWindow.addView(openglRenderWindow);

function updateBlendMode(event) {
  const blendMode = parseInt(event.target.value, 10);
  const ipScalarEls = document.querySelectorAll('.ipScalar');

  volumeMapper.setBlendMode(blendMode);
  volumeMapper.setIpScalarRange(0.0, 1.0);

  // if average or additive blend mode
  if (blendMode === 3 || blendMode === 4) {
    // Show scalar ui
    for (let i = 0; i < ipScalarEls.length; i += 1) {
      const el = ipScalarEls[i];
      el.style.display = 'table-row';
    }
  } else {
    // Hide scalar ui
    for (let i = 0; i < ipScalarEls.length; i += 1) {
      const el = ipScalarEls[i];
      el.style.display = 'none';
    }
  }
  renderWindow.render();
}

function updateScalarMin(event) {
  volumeMapper.setIpScalarRange(event.target.value, volumeMapper.getIpScalarRange()[1]);
  renderWindow.render(); 
}

function updateScalarMax(event) {
  volumeMapper.setIpScalarRange(volumeMapper.getIpScalarRange()[0], event.target.value);
  renderWindow.render(); 
}

      for (i = 0; i < imageData.length; i++){
          mycolor = color_maps[i]
          console.log(i)

          volume_vtk = vtk.Rendering.Core.vtkVolume.newInstance();
          volumeMapper = vtk.Rendering.Core.vtkVolumeMapper.newInstance();
          volumeProperty = vtk.Rendering.Core.vtkVolumeProperty.newInstance();
          ofun = vtk.Common.DataModel.vtkPiecewiseFunction.newInstance();
          ctfun = vtk.Rendering.Core.vtkColorTransferFunction.newInstance();

          dataRange = imageData[i].getPointData().getScalars().getRange();
          dimensions = imageData[i].getDimensions()
          console.log('volume dimensions', dimensions)
          // Calculating Pixel range
          ii_0 = parseInt(dataRange[0])
          ii_mid = parseInt(dataRange[1] / 2)
          ii_1 = parseInt(dataRange[1])

        // Define PieceWise transfer function
        ofun.removeAllPoints();
        ofun.addPoint(ii_0, 0.0);
        ofun.addPoint(ii_1, 1.0);

       // ######################################################################
        // Define Color transfer Function
        ctfun.addRGBPoint(i, mycolor[i+1].rgb.r, mycolor[i+1].rgb.g, mycolor[i+1].rgb.b);
       
        // ######################################################################
        // Define Volume Property
        volumeProperty.setRGBTransferFunction(0, ctfun);
        volumeProperty.setScalarOpacity(0, ofun);
        volumeProperty.setShade(true); 
        volumeProperty.setInterpolationTypeToLinear();
        volumeProperty.setAmbient(0.7);
        volumeProperty.setDiffuse(0.7);
        volumeProperty.setSpecular(0.3);
        volumeProperty.setSpecularPower(30.0); 
        volumeProperty.setOpacityMode(0, 20);
        volumeProperty.setIndependentComponents(true);
        volumeProperty.setUseGradientOpacity(0, true); 
        volumeProperty.setGradientOpacityMinimumValue(0, 3.5);
        volumeProperty.setGradientOpacityMinimumOpacity(0, 0.0);
        volumeProperty.setGradientOpacityMaximumValue(0, 20);
        volumeProperty.setGradientOpacityMaximumOpacity(0, 1.0);
        // volumeProperty.setScalarOpacityUnitDistance(0, asasa); 

        // Define VolumeMapper
        volumeMapper.setInputData(imageData[i]);
        volumeMapper.setSampleDistance(0.4);

        // Define vtkVolume
        volume_vtk.setMapper(volumeMapper);
        volume_vtk.setProperty(volumeProperty);
        
        renderer.addActor(volume_vtk);
        renderer.resetCamera();
        renderWindow.render();
      }

        // Add blending mode into the viewer
        const el = document.querySelector('.blendMode');
        el.addEventListener('change', updateBlendMode);
        const scalarMinEl = document.querySelector('.scalarMin');
        scalarMinEl.addEventListener('input', updateScalarMin);
        const scalarMaxEl = document.querySelector('.scalarMax');
        scalarMaxEl.addEventListener('input', updateScalarMax);

        // Control Scalar Opacity
        $( "#setScalarOpacityUnitDistance" ).change(function() {
          newVal= this.value
          volumeProperty.setScalarOpacityUnitDistance(0, newVal); 
           renderWindow.render(); 
           console.log(newVal)
        });

       const camera = vtkCamera.newInstance();
       renderer.getActiveCamera();
       camera.setFreezeFocalPoint(true);
      renderer.resetCamera();
      camera.elevation(70);
      renderWindow.addRenderer(renderer);
      interactor.setView(openglRenderWindow);
      interactor.initialize();
      interactor.bindEvents(container);
      interactor.setInteractorStyle(interactorStyle);
      renderWindow.render()
}

In the below image, we can see two samples loaded at once, the first sample is shown with red colour and the second sample is shown in green colour.
After loading when I use a scalar opacity slider from 0 to 50. It only interacts with the green sample as the result can be seen below image. I want to know what am I doing wrong and how can I modify the scalar opacity slider so that it can interact both images at the same time.

Likewise, I also tried by changing the blend mode from composite to maximum intensity and again it only change the second green sample (as it disappeared) as can be seen in the below figure but it did not make disappear to the red sample. As for now, it only interacts with the latest or last uploaded sample not with the first sample.

Can anyone give me some suggestions to solve this problem?
Thank you!

Based on your loop, you assign volumeProperty twice, with the second volume’s property being set to volumeProperty. Thus, when you change your slider, you only update the second volume’s VolumeProperty. You need to store those properties in an array, and when in your slider event handler, loop over the two volume properties and change their scalar opacity.

1 Like