Controlling setAttribute value of min and max of vtkPlane viewer doesn't shows the MRI brain images?

Hello everyone,
I am using the clipping features of VTK.js to clip the desired view of the volume rendering screen.
It works very well for microscope image data as the image has an origin (0, 0, 0), however the same below code does not work for MRI brain images even though I manually set setOrign to (0, 0, 0). I was wondering why it shows microscope and CT scan datasets but why the viewer does not show the image although it renders MRI brain images, only shows a black screen.
As can be seen here:

However, if I put universal min and max value as shown in example (vtk.js) el.setAttribute('min', ((-sizeX) + img_origin_X)); el.setAttribute('max', img_origin_X); then it shows all types of image but the slider is not in the range of image width and height. However, when I control it by passing the command like ((-sizeX) + img_origin_X) then I can’t visualize brain MRI data. So weird.

And the second problem is when I load data and then press render then the viewer shows the black screen even though I pass the values, it will start to show the image after I slide the clipping plane slider. How to solve this one?

            volume_imageData[i].setOrigin(0.0, 0.0, 0.0)
            image_origin = volume_imageData[i].getOrigin();
            image_center = volume_imageData[i].getCenter();
            console.log('image_origin', image_origin);
            console.log('image_center', image_center);

            img_origin_X = image_origin[0];
            img_origin_Y = image_origin[1];
            img_origin_Z = image_origin[2];

            const extent = volume_imageData[i].getExtent();
            const spacing = volume_imageData[i].getSpacing();

            const sizeX = extent[1] * spacing[0];
            const sizeY = extent[3] * spacing[1];
            const sizeZ = extent[5] * spacing[2];

            console.log(extent, "extent");
            console.log(spacing, "spacing");
            console.log(sizeX, "sizeX");
            console.log(sizeY, "sizeY");

            const clipPlaneX = vtkPlane.newInstance();
            const clipPlaneX_inv = vtkPlane.newInstance();
            const clipPlaneZ = vtkPlane.newInstance();
            const clipPlaneZ_inv = vtkPlane.newInstance();
            const clipPlaneY = vtkPlane.newInstance();
            const clipPlaneY_inv = vtkPlane.newInstance();

            let clipPlane1Position = 0;
            let clipPlane2Position = 0;

            const clipPlaneNormalX = [1, 0, 0]; // X min
            const clipPlaneNormalX_inv = [-1, 0, 0]; // X max
            const clipPlaneNormalZ = [0, 0, 1]; // Z min
            const clipPlaneNormalZ_inv = [0, 0, -1]; // Z max
            const clipPlaneNormalY = [0, 1, 0]; // Y min
            const clipPlaneNormalY_inv = [0, -1, 0]; // Y max

            // Need to be same for all clipPlane
            clipPlane1Position = sizeX/ 4;;
            clipPlane2Position = sizeY/ 2;

            const clipPlaneOriginX = [clipPlane1Position * clipPlaneNormalX[0], clipPlane1Position * clipPlaneNormalX[1], clipPlane1Position * clipPlaneNormalX[2], ];
            const clipPlaneOriginX_inv = [clipPlane1Position * clipPlaneNormalX_inv[0], clipPlane1Position * clipPlaneNormalX_inv[1], clipPlane1Position * clipPlaneNormalX_inv[2], ];
            const clipPlaneOriginZ = [clipPlane2Position * clipPlaneNormalZ[0], clipPlane2Position * clipPlaneNormalZ[1], clipPlane2Position * clipPlaneNormalZ[2], ];
            const clipPlaneOriginZ_inv = [clipPlane2Position * clipPlaneNormalZ_inv[0], clipPlane2Position * clipPlaneNormalZ_inv[1], clipPlane2Position * clipPlaneNormalZ_inv[2], ];
            const clipPlaneOriginY = [clipPlane1Position * clipPlaneNormalY[0], clipPlane1Position * clipPlaneNormalY[1], clipPlane1Position * clipPlaneNormalY[2], ];
            const clipPlaneOriginY_inv = [clipPlane1Position * clipPlaneNormalY_inv[0],clipPlane1Position * clipPlaneNormalY_inv[1],clipPlane1Position * clipPlaneNormalY_inv[2], ];

            console.log(clipPlaneOriginX, "clipPlaneOriginX");
            console.log(clipPlaneOriginX_inv, "clipPlaneOriginX_inv");
            console.log(clipPlaneOriginY, "clipPlaneOriginY");
            console.log(clipPlaneOriginY_inv, "clipPlaneOriginY_inv");
            console.log(clipPlaneOriginZ, "clipPlaneOriginZ");
            console.log(clipPlaneOriginZ_inv, "clipPlaneOriginZ_inv");

            clipPlaneX.setNormal(clipPlaneNormalX);
            clipPlaneX.setOrigin(clipPlaneOriginX);
            clipPlaneX_inv.setNormal(clipPlaneNormalX_inv);
            clipPlaneX_inv.setOrigin(clipPlaneOriginX_inv);

            clipPlaneY.setNormal(clipPlaneNormalY);
            clipPlaneY.setOrigin(clipPlaneOriginY);
            clipPlaneY_inv.setNormal(clipPlaneNormalY_inv);
            clipPlaneY_inv.setOrigin(clipPlaneOriginY_inv);

            clipPlaneZ.setNormal(clipPlaneNormalZ);
            clipPlaneZ.setOrigin(clipPlaneOriginZ);
            clipPlaneZ_inv.setNormal(clipPlaneNormalZ_inv);
            clipPlaneZ_inv.setOrigin(clipPlaneOriginZ_inv);

            volumeMapper.setInputData(volume_imageData[i]);

            volumeMapper.addClippingPlane(clipPlaneX);
            volumeMapper.addClippingPlane(clipPlaneX_inv);
            volumeMapper.addClippingPlane(clipPlaneY);
            volumeMapper.addClippingPlane(clipPlaneY_inv);
            volumeMapper.addClippingPlane(clipPlaneZ);
            volumeMapper.addClippingPlane(clipPlaneZ_inv);

            // volumeMapper.setSampleDistance(0.4);
            volumeMapper.setMaximumSamplesPerRay(true);
            volumeMapper.setAutoAdjustSampleDistances(true);

            let el = document.querySelector('.planePositionX');
            el.setAttribute('min', ((-sizeX) + img_origin_X));
            el.setAttribute('max', img_origin_X);
            el.setAttribute('value', ((-sizeX) + img_origin_X));

            el = document.querySelector('.planePositionX_inv');
            el.setAttribute('min', img_origin_X);
            el.setAttribute('max', (sizeX + img_origin_X));
            el.setAttribute('value', img_origin_X);

            el = document.querySelector('.planePositionY');
            el.setAttribute('min', ((-sizeY) + img_origin_Y));
            el.setAttribute('max', img_origin_Y);
            el.setAttribute('value', ((-sizeY) + img_origin_Y));

            el = document.querySelector('.planePositionY_inv');
            el.setAttribute('min', img_origin_Y);
            el.setAttribute('max', (sizeY + img_origin_Y));
            el.setAttribute('value', img_origin_Y);

            el = document.querySelector('.planePositionZ');
            el.setAttribute('min', ((-sizeZ) + img_origin_Z));
            el.setAttribute('max', img_origin_Z);
            el.setAttribute('value', ((-sizeZ) + img_origin_Z));

            el = document.querySelector('.planePositionZ_inv');
            el.setAttribute('min', img_origin_Z);
            el.setAttribute('max', (sizeZ + img_origin_Z));
            el.setAttribute('value', img_origin_Z);


            document.querySelector('.planePositionX').addEventListener('input', (e) => {
              clipPlane1PositionX = Number(e.target.value);
              console.log(clipPlane1PositionX)
              const clipPlaneOriginX = [clipPlane1PositionX * clipPlaneNormalX[0], clipPlane1PositionX * clipPlaneNormalX[1], clipPlane1PositionX * clipPlaneNormalX[2], ];
              clipPlaneX.setOrigin(clipPlaneOriginX);
              renderWindow.render();
            });

            document.querySelector('.planePositionX_inv').addEventListener('input', (e) => {
              clipPlane1PositionX_inv = Number(e.target.value);
              console.log(clipPlane1PositionX_inv)
              const clipPlaneOriginX_inv = [clipPlane1PositionX_inv * clipPlaneNormalX_inv[0], clipPlane1PositionX_inv * clipPlaneNormalX_inv[1], clipPlane1PositionX_inv * clipPlaneNormalX_inv[2], ];
              clipPlaneX_inv.setOrigin(clipPlaneOriginX_inv);
              renderWindow.render();
            });

            document.querySelector('.planePositionY').addEventListener('input', (e) => {
              clipPlane1PositionY = Number(e.target.value);
              console.log(clipPlane1PositionY)
              const clipPlaneOriginY = [clipPlane1PositionY * clipPlaneNormalY[0], clipPlane1PositionY * clipPlaneNormalY[1], clipPlane1PositionY * clipPlaneNormalY[2], ];
              clipPlaneY.setOrigin(clipPlaneOriginY);
              renderWindow.render();
            });

            document.querySelector('.planePositionY_inv').addEventListener('input', (e) => {
              clipPlane1PositionY_inv = Number(e.target.value);
              console.log(clipPlane1PositionY_inv)
              const clipPlaneOriginY_inv = [clipPlane1PositionY_inv * clipPlaneNormalY_inv[0],clipPlane1PositionY_inv * clipPlaneNormalY_inv[1],clipPlane1PositionY_inv * clipPlaneNormalY_inv[2], ];
              clipPlaneY_inv.setOrigin(clipPlaneOriginY_inv);
              renderWindow.render();
            });

            document.querySelector('.planePositionZ').addEventListener('input', (e) => {
              clipPlane1PositionZ = Number(e.target.value);
              console.log(clipPlane1PositionZ)
              const clipPlaneOriginZ = [clipPlane1PositionZ * clipPlaneNormalZ[0], clipPlane1PositionZ * clipPlaneNormalZ[1], clipPlane1PositionZ * clipPlaneNormalZ[2], ];
              clipPlaneZ.setOrigin(clipPlaneOriginZ);
              renderWindow.render();
            });

            document.querySelector('.planePositionZ_inv').addEventListener('input', (e) => {
              clipPlane1PositionZ_inv = Number(e.target.value);
              console.log(clipPlane1PositionZ_inv)
              const clipPlaneOriginZ_inv = [clipPlane1PositionZ_inv * clipPlaneNormalZ_inv[0], clipPlane1PositionZ_inv * clipPlaneNormalZ_inv[1], clipPlane1PositionZ_inv * clipPlaneNormalZ_inv[2], ];
              clipPlaneZ_inv.setOrigin(clipPlaneOriginZ_inv);
              renderWindow.render();
            });

Any suggestion?

As a debugging approach, try a single clipping plane at a time to verify that the sliders work properly for it. As for your slider min/max, you can always transform the slider value into the proper origin for the clipping plane.

Hi,
Thanks for the comment.
I checked the viewer by uploading other CT and Microscope images and for all, it work, however, in the case of some BRAIN samples it didn’t show anything. Even though I tried clipping just one side.

I have another question related to set/getSpacing().
As I understood, it shows the space between each slice.
I was trying to accomplish something like this:

And I thought I can achieve that I by setSpacing command and I did like below:

$(Slider_scale).change(function() {
  newVal= this.value
  console.log(newVal)
   input_image_obj[0].setSpacing(spacing[0], spacing[1], newVal);
   // volume_visibility_control[0].setScale(1, 1, newVal)
   renderWindow.render();
});

however, the result is not what I expected which can be seen below:

Even though I slide the slider a maximum of 50 space but render image does not seems to be stretched. Why is that?
Any suggestion?

I might not be understanding what you are looking for. In your last video, a low spacing results in a thin plane, while a high spacing results in a thick plane, so things look like. they are working. (~0:20 time marker)

Hi,
thanks for the reply.
Ỳeah you are right it’s working for the range of 0-1 as the getSpacing of the image is (1, 1, 1) but when I increase the slider beyond 1 then the image does not seem to stretch like in the first video were if I increase voxelDimension.Z to 10 then u can see the image is stretch a lot (I obtained this output from clearvolume plugin in ImageJ).

Oh, I see now. It looks like your clipping planes are clipping the volume. Increasing the spacing results in an increased size in world space. As your clipping planes are defined in world coordinates, your slab will not increase in size as a result.

One solution is to define your clipping planes in image space, and then transform that into world space parameters based on the image’s orientation, translation, and scale.

Hi,
I was looking for examples of what you stated previously but couldn’t locate any. So, could you perhaps provide me with some suggestions on how to accomplish that?

As I work on implementing this in the volume rendering viewer. As a result, I’m utilizing the VTK libraries listed below:

            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();
            vtkPlane = vtk.Common.DataModel.vtkPlane;
          

I tried the below code but I get lost:

coordinate = vtk.Rendering.Core.vtkCoordinate.newInstance();
coordinate.setCoordinateSystemToWorld();

After this step, I don’t know where to pass coordinate input. I tried to pass it into volumemapper.setTransformCoordinate(coordinate);
But as it seems, volumemapper has no setTransformCoordinate property.

Any advice on how can I proceed?

Let’s define your planes in IJK space. In the following definition, I’m saying that the visible portion is between [0, max], so this actually defines 2 planes.

const xPlanes = [0, imgDims[0]-1];
const yPlanes = [0, imgDims[1]-1];
const zPlanes = [0, imgDims[2]-1];

You can then transform these planes into their corresponding world location:

function ijkPlanesToWorld(planes: [number, number], axis: 0 | 1 | 2 /* for x,y,z (really: i,j,k) */) {
let normal = [0, 0, 0];
let origin = [0, 0, 0];
// for first plane
normal[axis] = 1;
origin[axis] = planes[0];
origin = image.indexToWorld(origin);
normal = vec3.transformMat3(image.getDirection());
const firstPlane = { normal, origin };

// second plane
normal = [0, 0, 0];
origin = [0, 0, 0];
normal[axis] = -1;
origin[axis] = planes[1];
origin = image.indexToWorld(origin);
normal = vec3.transformMat3(image.getDirection());
const secondPlane = { normal, origin };
}

(None of this code is tested. I’m just sketching out a possible solution.)

1 Like

Hi,
I am trying what you have suggested above but whenever I pass volume_imageData[i] input data on this line, normal = vec3.transformMat3(image.getDirection()); It shows me an error as below:

 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '0')
    at Object.transformMat3 (gl_matrix.js:1:30093)
    at volume_rendering (index_vol_replica.js:514:15)

I searched about the error but could not understand much.
Therefore, I tried the function getCroppingPlanes which exactly does the same thing I suppose. I replace vec3.transformQuat(out, vec, rotation) this code with vec3.transformMat3(out, vec, imageData.getDirection());
However, it didn’t help to stretch the image.

function volume_rendering(volume_imageData){

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

function getCroppingPlanes(imageData, ijkPlanes) {
  const rotation = quat.create();
  mat4.getRotation(rotation, imageData.getIndexToWorld());

  const rotateVec = (vec) => {
    const out = [0, 0, 0];
    vec3.transformMat3(out, vec, imageData.getDirection());
    return out;
  };

  const [iMin, iMax, jMin, jMax, kMin, kMax] = ijkPlanes;
  const origin = imageData.indexToWorld([iMin, jMin, kMin]);
  // opposite corner from origin
  const corner = imageData.indexToWorld([iMax, jMax, kMax]);
  const volumePlanes = [
    
    vtkPlane.newInstance({ normal: rotateVec([1, 0, 0]), origin }),
    vtkPlane.newInstance({ normal: rotateVec([-1, 0, 0]), origin: corner }),
    
    vtkPlane.newInstance({ normal: rotateVec([0, 1, 0]), origin }),
    vtkPlane.newInstance({ normal: rotateVec([0, -1, 0]), origin: corner }),
    
    vtkPlane.newInstance({ normal: rotateVec([0, 0, 1]), origin }),
    vtkPlane.newInstance({ normal: rotateVec([0, 0, -1]), origin: corner })
  ];

  return [volumePlanes];
}

volume_imageData_obj = volume_imageData

   for (i = 0; i < volume_imageData.length; i++){

            // Define the Volume rendering Functions
            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();
            ImagecropFilter = vtk.Filters.General.vtkImageCropFilter.newInstance();
            vtkPlane = vtk.Common.DataModel.vtkPlane;
            vtkMatrixBuilder = vtk.Common.Core.vtkMatrixBuilder;
            coordinate = vtk.Rendering.Core.vtkCoordinate.newInstance();

            // Load the Input
            dataRange = volume_imageData[i].getPointData().getScalars().getRange();
            dimensions = volume_imageData[i].getDimensions()

            // 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(0, 85 / 255.0, 0, 0);
            ctfun.addRGBPoint(95, 1.0, 1.0, 1.0);
            ctfun.addRGBPoint(225, 0.66, 0.66, 0.5);
            ctfun.addRGBPoint(255, 0.3, 1.0, 0.5);

            // Define Volume Property
            volumeProperty.setRGBTransferFunction(0, ctfun); // Color TransferFUnction
            volumeProperty.setScalarOpacity(0, ofun); //PieceWise TransferFunction
            volumeProperty.setShade(true); 
            volumeProperty.setInterpolationTypeToFastLinear();
            volumeProperty.setAmbient(0.2);
            volumeProperty.setDiffuse(0.7);
            volumeProperty.setSpecular(0.3);
            volumeProperty.setSpecularPower(8.0); 
            volumeProperty.setOpacityMode(0, 20);
            volumeProperty.setIndependentComponents(true);
            volumeProperty.setUseGradientOpacity(0, true); 
            volumeProperty.setGradientOpacityMinimumValue(0, 0);
            volumeProperty.setGradientOpacityMinimumOpacity(0, 0);
            volumeProperty.setGradientOpacityMaximumValue(0, 20);
            volumeProperty.setGradientOpacityMaximumOpacity(0, 1.0);

            //////////////////////////////////////////////////////////////////////////////////////////////////////
            // set origin of a data to zero
            volume_imageData[i].setOrigin(0.0, 0.0, 0.0) 
            image_origin = volume_imageData[i].getOrigin();

            Direction = volume_imageData[i].getDirection();
            console.log('Direction', Direction);

            img_origin_X = image_origin[0];
            img_origin_Y = image_origin[1];
            img_origin_Z = image_origin[2];

            const extent = volume_imageData[i].getExtent();
            const spacing = volume_imageData[i].getSpacing();
            console.log('spacing', spacing);

            const sizeX = extent[1] * spacing[0];
            const sizeY = extent[3] * spacing[1];
            const sizeZ = extent[5] * spacing[2];

            //////////////////////////////////////////////////////////////////////////////////////////////////////
             [volumePlanes] = getCroppingPlanes(volume_imageData[i], extent);
            console.log('volumePlanes',volumePlanes)
            //////////////////////////////////////////////////////////////////////////////////////////////////////

            // Call the function of ClipPlane
            const clipPlaneX = vtkPlane.newInstance();
            const clipPlaneX_inv = vtkPlane.newInstance();
            const clipPlaneZ = vtkPlane.newInstance();
            const clipPlaneZ_inv = vtkPlane.newInstance();
            const clipPlaneY = vtkPlane.newInstance();
            const clipPlaneY_inv = vtkPlane.newInstance();
            
            clipPlaneX.setNormal(volumePlanes[1].getNormal());
            clipPlaneX.setOrigin(volumePlanes[1].getOrigin());

            clipPlaneX_inv.setNormal(volumePlanes[0].getNormal());
            clipPlaneX_inv.setOrigin(volumePlanes[0].getOrigin());

            clipPlaneY.setNormal(volumePlanes[3].getNormal());
            clipPlaneY.setOrigin(volumePlanes[3].getOrigin());

            clipPlaneY_inv.setNormal(volumePlanes[2].getNormal());
            clipPlaneY_inv.setOrigin(volumePlanes[2].getOrigin());

            clipPlaneZ.setNormal(volumePlanes[5].getNormal());
            clipPlaneZ.setOrigin(volumePlanes[5].getOrigin());

            clipPlaneZ_inv.setNormal(volumePlanes[4].getNormal());
            clipPlaneZ_inv.setOrigin(volumePlanes[4].getOrigin());

            volumeMapper.setInputData(volume_imageData[i]);

            volumeMapper.addClippingPlane(clipPlaneX);
            volumeMapper.addClippingPlane(clipPlaneX_inv);
            volumeMapper.addClippingPlane(clipPlaneY);
            volumeMapper.addClippingPlane(clipPlaneY_inv);
            volumeMapper.addClippingPlane(clipPlaneZ);
            volumeMapper.addClippingPlane(clipPlaneZ_inv);

            volumeMapper.setMaximumSamplesPerRay(true);
            volumeMapper.setAutoAdjustSampleDistances(true);

            // Define vtkVolume
            volume_vtk.setMapper(volumeMapper);
            volume_vtk.setProperty(volumeProperty);

            renderer.addActor(volume_vtk);
      }

$(Spacing_slider).change(function() {
  newVal= this.value
  console.log(newVal)
   volume_imageData_obj[0].setSpacing(1, 1, newVal);
   renderWindow.render();
});

...................................
...................................
...................................

renderer.resetCamera();
renderWindow.render()
}

Here, I have provided the minimal code which currently I am using to achieve that features in volume rendering.

Hello Forrest,

Sorry to ping you.
I am still struggling to find a solution to my problem.
Can you please suggest to me what am I doing wrong in the above process?

Thank you

This is the correct solution to scale the image.

function ijkPlanesToWorld(image, planes= [number, number], axis= 0 | 1 | 2 /* for x,y,z (really: i,j,k) */) {
let normal = [0, 0, 0];
let origin = [0, 0, 0];
// for first plane
normal[axis] = 1;
origin[axis] = planes[0];
origin = image.indexToWorld(origin);
rotateVe = (vec) => {
out = [0, 0, 0];
vec3.transformMat3(out, vec, image.getDirection());
console.log(vec3.transformMat3(out, vec, image.getDirection()));
return out;
};

const firstPlane = vtkPlane.newInstance({ normal: rotateVe([normal[0], normal[1], normal[2]]), origin });

// second plane
normal = [0, 0, 0];
origin = [0, 0, 0];

normal[axis] = -1;
origin[axis] = planes[1];
origin = image.indexToWorld(origin);
rotateVe = (vec) => {
out = [0, 0, 0];
vec3.transformMat3(out, vec, image.getDirection());
console.log(vec3.transformMat3(out, vec, image.getDirection()));
return out;
};

const secondPlane = vtkPlane.newInstance({ normal: rotateVe([normal[0], normal[1], normal[2]]), origin });

return [firstPlane, secondPlane];

}