How to correctly apply affine transforms from NIfTI images to PolyData derived from that input image?

Consider this scenario: I am processing 3D medical imaging data (stored as NIfTIs) using some specific segmentation tool (not based on VTK/ITK). This tool extracts a segmentation masks for a certain structure of interest. I finally convert the resulting segmentation masks into a 3D surface mesh, using some VTK functionality.

The specifics of these steps are not so relevant here. My goal is is to place extracted 3D surface mesh correctly within the same reference frame of the original NIfTI image. Unfortunately, the segmentation tool disregards the header information of the original NIfTI image, most notably the information about the reference frame(s). I therefore need to apply an affine transform on the resulting PolyData object.

I encountered these explanations by David Gobbi, which explains that the way VTK handles NIfTI images may differ from other tools, e.g. NiBabel (my preferred tool to handle NIfTIs in Python).

How to correctly transform the extracted PolyData object such that it corresponds to the spacial coordinates of input NIfTI? I thought to address this forum first before digging deeper into spacial transforms and (inconsistent?) conventions.

You could report to the segmentation tool develoeprs that their tool creates invalid output (the segmentation is not aligned with the input image in physical space) and wait for them to fix the issue. You could also go ahead and fix the issue yourself (maybe all you need is to copy metadata from the source image to the output segmentation) and submit a pull request. This fix may be simpler than workarounds and would solve the issue not just for you but all other users of that segmentation tool.

If you still want to experiment with a workaround then you can set origin to 0, spacing to 1, directions to identity matrix in the image, extract the surface, and apply the voxel to phsyical space transform (that you obtained from the image header) using vtkTransformPolyDataFilter.

1 Like

Thank you! The segmentation tool is messy, not mine, and not worth a pull request at this stage :slight_smile: The tool operates entirely in the voxel space, and does not retain the information from the NIfTI header.

As you suggested, I’ve experimented with affine transformations:

def apply_transform(source, trafo):
    if trafo is None:
        return source
    trafo_filter = vtk.vtkTransformFilter()
    trafo_filter.SetInputConnection(source.GetOutputPort())
    trafo_filter.SetTransform(trafo)
    trafo_filter.Update()
    return trafo_filter

I’m using the affine transform stored in the NiBabel NIfTI image:

type(nifti_image)
# nibabel.nifti1.Nifti1Image

trafo_matrix = nifti_image.affine
trafo = vtk.vtkTransform()
trafo.SetMatrix(trafo_matrix.flatten())

# ...

mesh = ...
mesh = apply_transform(source=mesh, trafo=trafo)

This has worked quite well so far. At least the distances seem to be measured accurately. Maybe there’s a problem with flipped axes, but I haven’t looked into this yet.