ITKElastix for Mesh Transformation: TransformixFilter.GetOutputMesh() returns None

All–

Say I have used elastix to register a moving image moving.nii.gz to a fixed image fixed.nii.gz, resulting in a transformation file params.txt. I would now like to apply the transformation in params.txt to a mesh input-mesh.obj describing the geometry of an object in the moving image. I have tried to adapt the example here [1], which seems to closely match my use case, but unfortunately TransformixFilter.GetOutputMesh() is returning None. I think the main difference between the example linked above and my implementation is that the example calls both SetTransformParameterObject and SetTransform, whereas mine only calls SetTransformParameterObject; however, this is intentional, and per my understanding of the documentation should work. Quoting the documentation for SetTransform() [2]:

Sets the transformation. If null, the transformation is entirely specified by the transform parameter object that is set by SetTransformParameterObject. Otherwise, the transformation is specified by this transform object, with additional information from the specified transform parameter object.

I’m attaching an example parameter file and mesh, as well as a minimal example to reproduce the issue. itk version 5.4.3. itk-elastix version 0.23.0. Would be extremely appreciative if anyone can spot my error!

Best, and thanks in advance,

–Davis

input-mesh.obj (59.6 KB)
params.txt (1.5 MB)

#!/usr/bin/env python3

import itk
import pathlib as pl


def transform_mesh_with_itk(
    input_mesh_path: pl.Path,
    transform_parameter_paths: list[pl.Path],
    output_mesh_path: pl.Path,
) -> None:
    if not input_mesh_path.exists():
        raise FileNotFoundError(f"Input mesh file not found: {input_mesh_path}")
    for param_file in transform_parameter_paths:
        if not param_file.exists():
            raise FileNotFoundError(f"Transform parameter file not found: {param_file}")

    PixelType = itk.D
    Dimension = 3

    try:
        original_mesh = itk.meshread(input_mesh_path, pixel_type=PixelType)
        num_points = original_mesh.GetNumberOfPoints()
    except Exception as e:
        raise RuntimeError(f"Failed to load mesh file: {e}")

    parameter_object = itk.ParameterObject.New()
    for param_file in transform_parameter_paths:
        parameter_object.AddParameterFile(str(param_file))

    try:
        # Create a dummy image for TransformixFilter (required by ITKElastix)
        ImageType = itk.Image[PixelType, Dimension]
        dummy_image = ImageType.New()
        dummy_image.SetRegions([1,1,1])
        dummy_image.Allocate(True)

        transformix_filter = itk.TransformixFilter[ImageType].New()
        transformix_filter.SetMovingImage(dummy_image)
        transformix_filter.SetInputMesh(original_mesh)
        transformix_filter.SetTransformParameterObject(parameter_object)
        transformix_filter.Update()

        transformed_mesh = transformix_filter.GetOutputMesh()
        if transformed_mesh is None:
            raise RuntimeError("TransformixFilter.GetOutputMesh() returned None.")
        print(f"Transformed mesh with {transformed_mesh.GetNumberOfPoints()} points")

    except Exception as e:
        raise RuntimeError(f"An error occurred during transformation: {e}")

    try:
        output_mesh_path.parent.mkdir(parents=True, exist_ok=True)
        itk.meshwrite(transformed_mesh, str(output_mesh_path))
    except Exception as e:
        raise RuntimeError(f"Failed to write output mesh file: {e}")


def main():

    try:
        transform_mesh_with_itk(
            input_mesh_path=pl.Path("./test/input-mesh.obj"),
            transform_parameter_paths=[pl.Path("./test/params.txt")],
            output_mesh_path=pl.Path("./test/output-mesh.obj"),
        )
    except (FileNotFoundError, RuntimeError) as e:
        print(f"\nERROR: {e}", file=sys.stderr)
        exit(1)


if __name__ == "__main__":
    import argparse
    import sys

    main()

[1] ITKElastix/examples/ITK_Example16_Transformix_mesh_TranslationTransform.ipynb at main · InsightSoftwareConsortium/ITKElastix · GitHub
[2] elastix: itk::TransformixFilter< TImage > Class Template Reference

Hello @DVigneault !

I believe the responsible code is here:

@Niels_Dekker what does ->front() mean in this context?

@DVigneault if an itk::Transform is passed, do you get the same result?

Thank you for the reply, @matt.mccormick!! Yes–if I initialize an empty BSplineTransform matching the transform in my params.txt file, I get the same result:

SplineOrder = 3
transform = itk.BSplineTransform[PixelType,Dimension,SplineOrder].New() # itk.D, 3, 3
...
transformix_filter.SetTransform(transform)
1 Like