Issues with vtkImageCanvasSource2D

Hello!

I’m writing a PyQt5 application to draw on medical images using vtkImageCanvasSource2D, however I’m having issues with oriented images.

It seems that when I connect a vtkImageCanvasSource2D to a mapper using GetOutputPort(), the spacing and origin information is lost. Since this class is used to draw a mask, the output is not correctly overlaid on the underlying image.

I’m attaching a small snippet hoping that it illustrates well my issue. In the code below, I would expect that the second file would have preserved it’s spacing information.

(python 3.8.10, vtk 9.0.3, nibabel 3.2.1)

import nibabel as nib
import vtk
from vtkmodules.util import vtkConstants


def run():

    inp = vtk.vtkImageData()
    inp.SetDimensions(10, 10, 10)
    inp.SetSpacing(1, 2, 3)
    inp.SetOrigin(-1, -3, -2)
    inp.SetDirectionMatrix(0, -1, 0, 1, 0, 0, 0, 0, 1)
    inp.AllocateScalars(vtkConstants.VTK_UNSIGNED_CHAR, 0)

    canvas = vtk.vtkImageCanvasSource2D()
    canvas.InitializeCanvasVolume(inp)

    writer = vtk.vtkNIFTIImageWriter()

    f1 = "f1.nii.gz"
    f2 = "f2.nii.gz"

    writer.SetFileName(f1)
    writer.SetInputData(inp)
    writer.Write()

    writer.SetFileName(f2)
    writer.SetInputConnection(canvas.GetOutputPort())
    writer.Write()

    print(nib.load(f1).header.get_zooms())
    print(nib.load(f2).header.get_zooms())

if __name__ == "__main__":
    run()

This outputs the following

(1.0, 2.0, 3.0)
(1.0, 1.0, 1.0)

Reading the source code, I see that RequestInformation sets the spacing and origin to default values. Would that be the source of the issue?

Thanks for helping!

After some more digging I realized that I could create a separate image algorithm which resets the direction matrix.

from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
from vtkmodules.vtkCommonDataModel import vtkDataObject

class SetImageDirection(VTKPythonAlgorithmBase):
    def __init__(self, ref):
        super().__init__(
            nInputPorts=1,
            inputType="vtkImageData",
            nOutputPorts=1,
            outputType="vtkImageData",
        )
        self.direction = (-1, 0, 0, 0, -1, 0, 0, 0, 1)

    def RequestData(self, request, inInfo, outInfo):
        inp = vtk.vtkImageData.GetData(inInfo[0])
        opt = vtk.vtkImageData.GetData(outInfo)
        opt.ShallowCopy(inp)
        opt.SetDirectionMatrix(self.direction)
        return 1

    def RequestInformation(self, request, inInfo, outInfo):
        info = outInfo.GetInformationObject(0)
        info.Set(vtkDataObject.DIRECTION(), self.direction, 9)
        return 1

Chaining this filter in combination with vtkImageChangeInformation after algorithms that do not transmit the direction matrix seems to solve my problem. I’m linking to that blog post that came in handy, for reference.