InsightSoftwareConsortium / ITKElastix

An ITK Python interface to elastix, a toolbox for rigid and nonrigid registration of images
Apache License 2.0
209 stars 22 forks source link

How to convert ITK transform into Elastix transform parameters file? #246

Open dzenanz opened 1 year ago

dzenanz commented 1 year ago

I have corresponding fiducial points between a pair of images. I use it to compute similarity transform. What is the best way to pass this as initial transform? See #245 for an attempt.

Setting it via SetInitialTransform seems to work (the compound registration does not work correctly in Slicer unless it is "split"), but a later call to:

parameter_object.WriteParameterFile(elastix_transform, str(output_dir / "rigid_transform.txt"))

produces:

RuntimeError: D:\a\im\_skbuild\win-amd64-3.9\cmake-build\_deps\elx-src\Core\Main\elxParameterObject.cxx:314:
ITK ERROR: ParameterObject(000002044268FF30): Error writing to disk: The number of parameter maps (2) does not match the number of provided filenames (1). Please call WriteParameterFiles instead, and provide a vector of filenames.

So I tried:

parameter_object.WriteParameterFiles(
    elastix_transform,
    ["fiducial_transform.txt", str(output_dir / "rigid_transform.txt")],
)

and it does not write the fiducial_transform.txt anywhere on the disk, with rigid_transform.txt having the following content:

(CenterOfRotationPoint -4.944945387437201 -37.24496703995354 -59.44102459574103)
(CompressResultImage "false")
(DefaultPixelValue 0)
(Direction 1 0 0 0 1 0 0 0 1)
(FinalBSplineInterpolationOrder 3)
(FixedImageDimension 3)
(FixedInternalImagePixelType "float")
(HowToCombineTransforms "Compose")
(Index 0 0 0)
(InitialTransformParameterFileName "fiducial_transform.txt")
(MovingImageDimension 3)
(MovingInternalImagePixelType "float")
(NumberOfParameters 7)
(Origin -140 -140 -383.625)
(ResampleInterpolator "FinalBSplineInterpolator")
(Resampler "DefaultResampler")
(ResultImageFormat "nii")
(ResultImagePixelType "float")
(Size 281 281 355)
(Spacing 1 1 1)
(Transform "SimilarityTransform")
(TransformParameters 0.0015680670087272384 -0.0006157409483032898 0.0031103486499469784 0.16096493087864483 -0.12426840552647811 2.9018697571742758 0.988135955256508)
(UseDirectionCosines "true")

Passing rigid_transform.txt via SetInitialTransformParameterFileName to the next (BSpline) stage of registration produces an error:

File: D:\a\im\_skbuild\win-amd64-3.9\cmake-build\_deps\elx-src\Common\ParameterFileParser\itkParameterFileParser.cxx
Line: 255
Description: ITK ERROR: ERROR: the file fiducial_transform.txt does not exist.

If I do not provide initial transform to the first registration phase, the whole pipeline works - with imperfect results.

dzenanz commented 1 year ago

Applying an old trick from https://github.com/InsightSoftwareConsortium/ITKElastix/issues/79#issuecomment-878602486 seems to work, at least from the no-crashing point of view. Is this still the best way to do it? Why doesn't Elastix automatically combine two similarity transforms into one?

thewtex commented 1 year ago

@dzenanz have you tried SetExternalInitialTransform?

dzenanz commented 1 year ago

I tried it, but at the time ran into a different issue: #245.

N-Dekker commented 1 year ago

So I tried:

parameter_object.WriteParameterFiles(
    elastix_transform,
    ["fiducial_transform.txt", str(output_dir / "rigid_transform.txt")],
)

and it does not write the fiducial_transform.txt anywhere on the disk

Looks like that's the following issue:

I wasn't sure if it was worth fixing, because apparently it behaved like that for more than five years already. So that's why it's still open. ParameterObject::WriteParameterFile still works well for a single parameter map, fortunately.

dzenanz commented 1 year ago

With itk-elastix 0.19.0 and code from https://github.com/InsightSoftwareConsortium/ITKElastix/issues/245#issuecomment-1721253341,

parameter_object.WriteParameterFiles(
    elastix_transform_parameters,
    ["fiducial_transform.txt", "rigid_transform.txt"])

produces both files, but content of fiducial_transform.txt is this:

(NumberOfParameters 0)
(Transform "ExternalTransform")
(TransformAddress "000001FF3C274330")

and subsequent run fails with:

itk::ExceptionObject (000000712F3E9F80)
Location: "ElastixTemplate - Run()" 
File: D:\a\im\_skbuild\win-amd64-3.9\cmake-build\_deps\elx-src\Components\Transforms\ExternalTransform/elxAdvancedTransformAdapter.h
Line: 146
Description: ITK ERROR: AdvancedTransformAdapter(0000021F3508AA80): Not implemented for AdvancedTransformAdapter

Error occurred during actual registration.
N-Dekker commented 1 year ago

With itk-elastix 0.19.0 and ... produces both files, but content of fiducial_transform.txt is this:

(NumberOfParameters 0)
(Transform "ExternalTransform")
(TransformAddress "000001FF3C274330")

If you do elastix_registration_method_object.SetOutputDirectory(an-existing-directory) and then run the registration, does it produce the transform parameter files properly? (In the specified output directory, I mean.)

dzenanz commented 1 year ago

As far as I can tell, it does: temp.zip.

dzenanz commented 1 year ago

The initial registration does that (produce those files).

N-Dekker commented 1 year ago

ElastixRegistrationMethod::GenerateData() has some code to specifically write an "ExternalTransform" to a separate ITK compatible transform file, either ".tfm" or ".h5". It's not yet clear to me how to make that functionality available to ParameterObject.WriteParameterFiles. Do you think it's necessary anyway? Or is it sufficient to just let elastix write the transform files to the output directory specified by SetOutputDirectory?

dzenanz commented 1 year ago

I am using ParameterObject.WriteParameterFiles to create initial transform for the next stage of registration.

ElastixRegistrationMethod::GenerateData() has some code to specifically write an "ExternalTransform" to a separate ITK compatible transform file

As I wrote earlier, I am doing this manually now. It would be good if WriteParameterFiles took care of that when appropriate.

N-Dekker commented 1 year ago

@dzenanz Sorry, can you please clarify your use case? Do I understand correctly that you have a similarity transform, created outside of elastix? Do you have it stored in an ITK ".tfm" or ".h5" file already?

Elastix supports specifying an external ".tfm" or ".h5" file as "TransformFileName" parameter, in an elastix transform parameter map or a transform parameter txt file:

https://github.com/SuperElastix/elastix/blob/57511dd16c68fbe967ff82fc734a05a9cc916bc6/Testing/Data/Translation(1%2C-2)/TransformParameters-link-to-ITK-tfm-file.txt

(This support is only implemented for ITK transform files that represent AffineTransform, BSplineTransform, Euler2D, Euler3D, Similarity2D, Similarity3D, TranslationTransform, or a Composite of them.)

Does that help you any further?

dzenanz commented 1 year ago

The use case is: use landmarks to create an initial transform, then use that to initialize first stage of registration, then result of that to initialize the next stage etc. I explained it a bit here: https://github.com/InsightSoftwareConsortium/ITKElastix/issues/245#issuecomment-1717995997.

I already have a working code, via a workaround of providing the initial transform via TransformFileName, as described here: https://github.com/InsightSoftwareConsortium/ITKElastix/issues/79#issuecomment-878602486.

It would be good if this workaround was not needed. If fixing that has lousy cost/benefit ratio, than this issue can be closed.

N-Dekker commented 1 year ago

We need to further investigate how ParameterObject.WriteParameterFiles should deal with the "ExternalTransform" feature. You see, it's a bit tricky, because the pointer to the external transform may be dangling, once the ParameterObject is written to file. It's on our TODO list, thanks.

idhamari commented 10 months ago

Why there are no functions to read/write and convert between both transforms?

e.g. something like:

        elxTransform = readElastixTransform(elastixTransformFilePath)   
        itkTranform    =  convertElastix2ItkTransform(elxTranform)
        writeItkTransform(itkTransformFilePath)   

        itkTransform = readItkTransform(itkTransformFilePath)   
        elxTranform    =  convertItk2ElastixTransform(itkTransform)
        writeElastixTransform(elastixTransformFilePath)