vtkImageResliceMapper DICOM image slice disappears when zoomed in enough

Hi!

Our company has been making a piece of software with VTK for some time now and we’ve been using the vtkImageResliceMapper class to display DICOM CT images ( vtkImageData ). I came across an issue when zooming into the vtkImageSlice too far. If you zoom in too far, the image just disappears and the working maximum zoom also depends on how close the camera focal point is to the relevant bounds of the image. I made a small working example with the FullHead.mhd image as input:

#include <vtkImageData.h>
#include <vtkImageResliceMapper.h>
#include <vtkImageSlice.h>
#include <vtkInteractorStyleImage.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkImageProperty.h>
#include <vtkOpenGLRenderer.h>
#include <vtkCamera.h>
#include <vtkTransform.h>
#include <vtkMetaImageReader.h>

#include <iostream>

namespace {

    class vtkImageInteractionCallback1 : public vtkCommand
    {
    public:
        static vtkImageInteractionCallback1* New()
        {
            return new vtkImageInteractionCallback1;
        }

        vtkImageInteractionCallback1()
        {}

        ~vtkImageInteractionCallback1()
        {}

        void addCamera(vtkCamera* cam) {
            camera = cam;
        }

        void addRenderWindow(vtkRenderWindow* window) {
            renderWindow = window;
        }

        void addRenderer(vtkOpenGLRenderer* ren) {
            renderer = ren;
        }

        void addImageSlice(vtkImageSlice* slice) {
            imageSlice = slice;
        }

        virtual void Execute(vtkObject*, unsigned long event, void*)
        {
            if (event == vtkCommand::MouseWheelForwardEvent) {

                if (auto transform = dynamic_cast<vtkTransform*>(imageSlice->GetUserTransform())) {
                    transform->Translate(0, 0, 1);
                }
                renderer->ResetCameraClippingRange();
                camera->Modified();
                renderWindow->Render();
            }
            else {
                if (auto transform = dynamic_cast<vtkTransform*>(imageSlice->GetUserTransform())) {
                    transform->Translate(0, 0, -1);
                }
                renderer->ResetCameraClippingRange();
                camera->Modified();
                renderWindow->Render();
            }

            std::cout << "camera foc z " << camera->GetFocalPoint()[2] << "\n";
            std::cout << "camera pos z " << camera->GetPosition()[2] << "\n";
            std::cout << "foc pos diff " << sqrt(vtkMath::Distance2BetweenPoints(camera->GetFocalPoint(), camera->GetPosition())) << "\n";
            std::cout << "image z bnds "
                << imageSlice->GetBounds()[4] << " " 
                << imageSlice->GetBounds()[5] << "\n";
        }

    private:
        vtkCamera* camera;
        vtkRenderWindow* renderWindow;
        vtkOpenGLRenderer* renderer;
        vtkImageSlice* imageSlice;
    };

} // namespace

int main(int, char*[])
{
  vtkNew<vtkNamedColors> colors;

  vtkNew<vtkMetaImageReader> reader;
  reader->SetFileName("../FullHead.mhd");

  vtkNew<vtkImageResliceMapper> imageResliceMapper;
  imageResliceMapper->SetInputConnection(reader->GetOutputPort());
  imageResliceMapper->ResampleToScreenPixelsOff();
  imageResliceMapper->AutoAdjustImageQualityOn();
  imageResliceMapper->SliceFacesCameraOn();
  imageResliceMapper->SeparateWindowLevelOperationOff();
  imageResliceMapper->SetSliceAtFocalPoint(true);
  imageResliceMapper->JumpToNearestSliceOn();

  vtkNew<vtkTransform> transform;
  transform->Identity();

  vtkNew<vtkImageSlice> imageSlice;
  imageSlice->SetMapper(imageResliceMapper);
  imageSlice->GetProperty()->SetInterpolationTypeToNearest();
  imageSlice->SetUserTransform(transform);

  // Setup renderers.
  vtkNew<vtkOpenGLRenderer> renderer;
  renderer->AddViewProp(imageSlice);
  renderer->ResetCamera();
  renderer->SetBackground(colors->GetColor3d("NavajoWhite").GetData());

  // Setup render window.
  vtkNew<vtkRenderWindow> renderWindow;
  renderWindow->SetSize(1000, 1000);
  renderWindow->AddRenderer(renderer);

  // Setup render window interactor.
  vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;

  vtkNew<vtkImageInteractionCallback1> callback;
  callback->addCamera(renderer->GetActiveCamera());
  callback->addRenderWindow(renderWindow);
  callback->addRenderer(renderer);
  callback->addImageSlice(imageSlice);
  vtkNew<vtkInteractorStyleTrackballCamera> style;
  style->AddObserver(vtkCommand::MouseWheelBackwardEvent, callback);
  style->AddObserver(vtkCommand::MouseWheelForwardEvent, callback);

  renderWindowInteractor->SetInteractorStyle(style);

  // Render and start interaction.
  renderWindowInteractor->SetRenderWindow(renderWindow);
  renderWindow->Render();
  renderWindowInteractor->Initialize();

  renderWindowInteractor->Start();

  return EXIT_SUCCESS;
}

You can scroll through the slices using the mouse wheel, zoom with right drag and move with middle drag.

If you zoom into the image such that the distance between the focal point and the position of the camera is around 2.0 or lower (in the command line output; also you should see roughly one image voxel on the screen), then certain slices don’t render, particularly when the focal point of the camera is close to the upper bound of the image.

I tried with both JumpToNearestSliceOn() and JumpToNearestSliceOff(). I also tested it with VTK 8.2.0 and VTK 9.2.6 to see if it was a version issue, but the problem is identical in all cases.

Is there a known reason why this happens, and possibly a way to fix it?

I understand it’s a bit of niche problem, but our software has certain requirements it has to meet and with our DICOM images it happens even when zooming to about 3-5 voxels on the screen, so if possible I’d like to fix this.

Thank you for the help!

Jakob

If you change the camera to use parallel projection, then the zoom action will change the field of view, rather than moving the position of the camera. This allows you to zoom in as much as you like.

renderer->GetActiveCamera()->ParallelProjectionOn();

I’m not entirely sure why the image disappears for perspective projections, but my guess is that it’s the result of the interactor style automatically adjusting the near and far clipping planes. The automatic adjustment won’t allow the near clipping plane to get too close to the camera, and as a result, if the camera is too close to the object, the near clipping plane might end up behind the object.

Also note that there is a vtkInteractorStyleImage class for interacting with images. This interaction style has three modes that can be set: SetInteractionModeToImage2D(), SetInteractionModeToImage3D(), and SetInteractionModeToImageSlicing(), The difference between the last two is how out-of-plane rotation is handled.

Thank you David,

the clipping range was indeed the issue.