Skip to content

[ENH] Support for multiple initial-moving-transforms for ants.Registration and ComposeMultiTransforms #2187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Oct 13, 2017

Conversation

Gilles86
Copy link
Contributor

In my workflows I use multiple registration steps that I want to inspect and build upon each other. For that I think it would be useful if you can use multiple transforms to initialize ANTSRegistration and/or compose new transforms out of a list of transforms.

I tried to implement that in this pull request. It would be good if someone would review this code. The antsRegistration script and its interface are quite messy.

In a related note: the antsRegistration-interface currently outputs transforms in the order of which you would like to apply them. However, if you want to input them into ans.ApplyTransforms or, now, as initial_moving_transform for ants.Registration you should give them in the reverse order. I think that is quite unclear and not user-friendly. However, I can see that it's too late to reverse this now. Should ants.Registration maybe get an input flag which can inverse the transforms already for you?

@satra
Copy link
Member

satra commented Sep 15, 2017

@Gilles86 - regarding the reversing of transforms, there is already an output (reverse_transforms) that does this. also one can use the composite_transform/inverse_composite_transform if there is a no need to combine with other external transforms.

@satra
Copy link
Member

satra commented Oct 6, 2017

@Gilles86 - just checking in on the status of this.

also a merge with current master would be advised.

@codecov-io
Copy link

codecov-io commented Oct 9, 2017

Codecov Report

Merging #2187 into master will increase coverage by <.01%.
The diff coverage is 74.6%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2187      +/-   ##
==========================================
+ Coverage   72.31%   72.32%   +<.01%     
==========================================
  Files        1180     1181       +1     
  Lines       58879    58911      +32     
  Branches     8474     8482       +8     
==========================================
+ Hits        42579    42607      +28     
- Misses      14921    14923       +2     
- Partials     1379     1381       +2
Flag Coverage Δ
#smoketests 72.32% <74.6%> (ø) ⬆️
#unittests 69.89% <74.6%> (ø) ⬆️
Impacted Files Coverage Δ
nipype/interfaces/ants/__init__.py 100% <ø> (ø) ⬆️
...pe/interfaces/ants/tests/test_auto_Registration.py 85.71% <ø> (ø) ⬆️
nipype/interfaces/ants/utils.py 87.12% <100%> (+1.41%) ⬆️
nipype/interfaces/ants/registration.py 75% <56.25%> (ø) ⬆️
...aces/ants/tests/test_auto_ComposeMultiTransform.py 85.71% <85.71%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8e1a86a...1045d2a. Read the comment docs.

@Gilles86
Copy link
Contributor Author

Gilles86 commented Oct 9, 2017

Hey @satra,

Thanks for the tip about the reverse_transforms. I misunderstood that output field for a list of inverse transforms! Maybe we should extend the documentation a bit? What do you think?

I merged this branch with origin/master and made some new commits that make this branch pass all tests. Do we need anything else?

@oesteban
Copy link
Contributor

oesteban commented Oct 9, 2017

@Gilles86 - if you could extend the documentation as you proposed that'd be awesome. Since you have been looking into this the latest, you are probably the best indicated candidate to do so :)

@Gilles86
Copy link
Contributor Author

I updated the documentation with some information I think is useful.

Copy link
Contributor

@oesteban oesteban left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great work. I'm +1 after a few changes to improve readability and exploit nipype's name_source are done.

Thanks a very much for this @Gilles86, happy to help if needed.

Also, there are some linting errors worth addressing in this PR:

flake8 nipype/interfaces/ants/registration.py --ignore=E501
nipype/interfaces/ants/registration.py:56:42: E127 continuation line over-indented for visual indent
nipype/interfaces/ants/registration.py:57:42: E127 continuation line over-indented for visual indent
nipype/interfaces/ants/registration.py:879:81: E261 at least two spaces before inline comment
nipype/interfaces/ants/registration.py:1041:10: E126 continuation line over-indented for hanging indent
nipype/interfaces/ants/registration.py:1044:10: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1046:10: E126 continuation line over-indented for hanging indent
nipype/interfaces/ants/registration.py:1048:10: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1050:10: E126 continuation line over-indented for hanging indent
nipype/interfaces/ants/registration.py:1052:10: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1054:10: E126 continuation line over-indented for hanging indent
nipype/interfaces/ants/registration.py:1056:10: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1058:10: E126 continuation line over-indented for hanging indent
nipype/interfaces/ants/registration.py:1060:10: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1062:10: E126 continuation line over-indented for hanging indent
nipype/interfaces/ants/registration.py:1065:10: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1067:10: E126 continuation line over-indented for hanging indent
nipype/interfaces/ants/registration.py:1071:10: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1073:10: E126 continuation line over-indented for hanging indent
nipype/interfaces/ants/registration.py:1077:10: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1081:9: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1085:9: E123 closing bracket does not match indentation of opening bracket's line
nipype/interfaces/ants/registration.py:1121:9: E122 continuation line missing indentation or outdented
nipype/interfaces/ants/registration.py:1122:9: E122 continuation line missing indentation or outdented
nipype/interfaces/ants/registration.py:1136:13: E122 continuation line missing indentation or outdented
nipype/interfaces/ants/registration.py:1142:13: E122 continuation line missing indentation or outdented

@@ -421,8 +436,57 @@ class RegistrationOutputSpec(TraitedSpec):
class Registration(ANTSCommand):

"""
`antsRegister <http://stnava.github.io/ANTs/>`_ registers a 'moving_image' to a 'fixed_image',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

antsRegistration ?, also please replace single quotes "'" with tilted quotes "``" so that sphinx renders this as code (eg. ``fixed_image``).

@@ -421,8 +436,57 @@ class RegistrationOutputSpec(TraitedSpec):
class Registration(ANTSCommand):

"""
`antsRegister <http://stnava.github.io/ANTs/>`_ registers a 'moving_image' to a 'fixed_image',
using a predefined (sequence of) cost function(s) and transformation operattions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operations

Note, however, that ANTS tools always apply lists of transformations in reverse order (the last
transformation in the list is applied first). Therefore, if the output forward_transforms
is a list, one can not directly feed it into, for example, ants.ApplyTransforms. To
make ants.ApplyTransforms apply the transformations in the same order as ants.Registration,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

``ants.ApplyTransforms`` and ``ants.Registration``


Note, however, that ANTS tools always apply lists of transformations in reverse order (the last
transformation in the list is applied first). Therefore, if the output forward_transforms
is a list, one can not directly feed it into, for example, ants.ApplyTransforms. To
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

``ants.ApplyTransforms``

@@ -865,6 +985,22 @@ def _format_winsorize_image_intensities(self):
return '--winsorize-image-intensities [ %s, %s ]' % (self.inputs.winsorize_lower_quantile,
self.inputs.winsorize_upper_quantile)

def _get_initial_transform_filenames(self):
retval = ['--initial-moving-transform']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps more readable:

    def _get_initial_transform_filenames(self):
        n_transforms = len(self.inputs.initial_moving_transform)

        # Assume transforms should not be inverted by default
        invert_flags = [0] * n_transforms
        if isdefined(self.inputs.invert_initial_moving_transform):
            if len(self.inputs.invert_initial_moving_transform) != n_transforms:
                raise Exception(
                    'Inputs "initial_moving_transform" and "invert_initial_moving_transform"'
                    'should have the same length.')
            invert_flags = self.inputs.invert_initial_moving_transform

        retval = ["[ %s, %d ]" % (xfm, int(flag)) for xfm, flag in zip(
            self.inputs.initial_moving_transform, invert_flags)]
        return " ".join(['--initial-moving-transform'] + retval)

Please also note two details: the direct casting with int(flag) to avoid extra if .. else and the more specific error message when the invert flags don't match the number of input xforms.



class ComposeMultiTransform(ANTSCommand):
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a one-line description of what ComposeMultiTransform does.

input_spec = ComposeMultiTransformInputSpec
output_spec = ComposeMultiTransformOutputSpec

def _format_arg(self, opt, spec, val):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be necessary, please remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I don't include this I get an error:

traits.trait_errors.TraitError: Cannot set the undefined 'dimension' attribute of a 'ANTSCommandInputSpec' object.
/src/nipype/nipype/interfaces/ants/utils.py:256: UnexpectedException

Maybe this functionality is not yet implemented for ANTSCommand?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that'd be fixed by removing mandatory=True from the dimension input trait.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No :(.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! (it works on my settings at least 👍 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! This works for me too. Maybe it was too late yesterday :).

class ComposeMultiTransformInputSpec(ANTSCommandInputSpec):
dimension = traits.Enum(3, 2, argstr='%d', usedefault=True, mandatory=True,
position=0, desc='image dimension (2 or 3)')
output_transform = File(argstr='%s', mandatory=True, position=1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use name_source:

        output_transform = File(argstr='%s', mandatory=True, position=1,
                                   name_source=['transforms'], name_template='%s_composed',
                                   desc='the name of the resulting transform.')

And remove member _list_outputs that will not be necessary anymore.

@Gilles86
Copy link
Contributor Author

Thanks for the feedback, @oesteban!

I think I implemented all of it. leaving out the input/outputspec-definition in the Interface-class doesn't work for me. Maybe you can have a look why this is.

@oesteban
Copy link
Contributor

Sorry I forgot to update the specs. Hopefully this is the one passing.

@oesteban oesteban merged commit 0af4d56 into nipy:master Oct 13, 2017
@satra satra added this to the 0.14.0 milestone Oct 20, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants