Skip to content

antsRegistrationSyNQuick (continuation of #1392 and #2347) #2453

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 13 commits into from
Feb 23, 2018
3 changes: 2 additions & 1 deletion nipype/interfaces/ants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"""Top-level namespace for ants."""

# Registraiton programs
from .registration import ANTS, Registration, MeasureImageSimilarity
from .registration import (ANTS, Registration, RegistrationSynQuick,
MeasureImageSimilarity)

# Resampling Programs
from .resampling import (ApplyTransforms, ApplyTransformsToPoints,
Expand Down
95 changes: 95 additions & 0 deletions nipype/interfaces/ants/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
from ..base import TraitedSpec, File, Str, traits, InputMultiPath, isdefined
from .base import ANTSCommand, ANTSCommandInputSpec

try:
LOCAL_DEFAULT_NUMBER_OF_THREADS = int(os.getenv("LOCAL_DEFAULT_NUMBER_OF_THREADS"))
except TypeError:
LOCAL_DEFAULT_NUMBER_OF_THREADS = 1
Copy link
Member

Choose a reason for hiding this comment

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

Let's just import from .base import LOCAL_DEFAULT_NUMBER_OF_THREADS.

Also, I think in general this variable is kind of pointless, unless somebody's using a fork of nipype where it's defined as something other than 1. This isn't something that needs to be fixed in this PR, but just to flag it for others' attention.



class ANTSInputSpec(ANTSCommandInputSpec):
dimension = traits.Enum(
Expand Down Expand Up @@ -1505,3 +1510,93 @@ def aggregate_outputs(self, runtime=None, needed_outputs=None):
stdout = runtime.stdout.split('\n')
outputs.similarity = float(stdout[0])
return outputs


class RegistrationSynQuickInputSpec(ANTSCommandInputSpec):
dimension = traits.Enum(3, 2, argstr='-d %d',
usedefault=True, desc='image dimension (2 or 3)')
fixed_image = InputMultiPath(File(exists=True), mandatory=True, argstr='-f %s',
desc='Fixed image or source image or reference image')
moving_image = InputMultiPath(File(exists=True), mandatory=True, argstr='-m %s',
desc='Moving image or target image')
output_prefix = Str("transform", usedefault=True, argstr='-o %s',
desc="A prefix that is prepended to all output files")
num_threads = traits.Int(default_value=LOCAL_DEFAULT_NUMBER_OF_THREADS, usedefault=True,
desc='Number of threads (default = 1)', argstr='-n %d')

transform_type = traits.Enum('s', 't', 'r', 'a', 'sr', 'b', 'br', argstr='-t %s',
desc="""
transform type
t: translation
r: rigid
a: rigid + affine
s: rigid + affine + deformable syn (default)
sr: rigid + deformable syn
b: rigid + affine + deformable b-spline syn
br: rigid + deformable b-spline syn""",
usedefault=True)

use_histogram_matching = traits.Bool(False, argstr='-j %d',
desc='use histogram matching')
histogram_bins = traits.Int(default_value=32, argstr='-r %d',
desc='histogram bins for mutual information in SyN stage \
(default = 32)')
spline_distance = traits.Int(default_value=26, argstr='-s %d',
desc='spline distance for deformable B-spline SyN transform \
(default = 26)')
precision_type = traits.Enum('double', 'float', argstr='-p %s',
desc='precision type (default = double)', usedefault=True)


class RegistrationSynQuickOutputSpec(TraitedSpec):
warped_image = File(exists=True, desc="Warped image")
inverse_warped_image = File(exists=True, desc="Inverse warped image")
out_matrix = File(exists=True, desc='Affine matrix')
forward_warp_field = File(exists=True, desc='Forward warp field')
inverse_warp_field = File(exists=True, desc='Inverse warp field')


class RegistrationSynQuick(ANTSCommand):
"""
Reistration using a symmetric image normalization method (SyN).
Copy link
Member

Choose a reason for hiding this comment

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

"Registration" (you're missing a "g").

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

thanks :)

You can read more in Avants et al.; Med Image Anal., 2008
(https://www.ncbi.nlm.nih.gov/pubmed/17659998).

Examples
--------

>>> import copy, pprint
Copy link
Member

Choose a reason for hiding this comment

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

You're not using these imports

>>> from nipype.interfaces.ants import Registration
Copy link
Member

Choose a reason for hiding this comment

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

RegistrationSynQuick

>>> reg = RegistrationSynQuick()
>>> reg.inputs.fixed_image = 'fixed1.nii'
>>> reg.inputs.moving_image = 'moving1.nii'
>>> reg.inputs.num_threads = 2
>>> reg.cmdline
'antsRegistrationSynQuick.sh -d 3 -f fixed1.nii -m moving1.nii -n 2 -o transform -p d -t s'
>>> reg.run() # doctest: +SKIP
Copy link
Member

Choose a reason for hiding this comment

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

From the help:

     NB:  Multiple image pairs can be specified for registration during the SyN stage.
          Specify additional images using the '-m' and '-f' options.  Note that image
          pair correspondence is given by the order specified on the command line.
          Only the first fixed and moving image pair is used for the linear resgitration
          stages.

We should add a doctest to make sure that if fixed_image and moving_image are lists, that we see -f fixed1.nii -f fixed2.nii -m moving1.nii -m moving2.nii (or similar).

It may be more trouble than it's worth to verify that the lists are of the same length.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it gives -f fixed1.nii fixed2.nii -m moving1.nii moving2.nii, but i haven't checked if it is properly propagated to the main Registration (couldn't find also examples that confirms that this is a proper command)

Copy link
Member

Choose a reason for hiding this comment

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

Okay, then I think we want to change the argstrs for fixed_image and moving_image to -f %s... and -m %s..., respectively. (The ... will change -f A B to -f A -f B.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I pushed before reading this comment.

But do you know this is correct syntax of command line? If yes, I can change it, but just wasn't sure what is the correct syntax

Copy link
Member

Choose a reason for hiding this comment

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

Yes. -f A B will fail, while -f A -f B will succeed.

"""


_cmd = 'antsRegistrationSynQuick.sh'
input_spec = RegistrationSynQuickInputSpec
output_spec = RegistrationSynQuickOutputSpec

Copy link
Member

Choose a reason for hiding this comment

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

Just to avoid being affected by upstream changes, we should override _num_threads_update() to do nothing.

def _num_threads_update(self):
    """antsRegistrationSynQuick.sh ignores environment variables, so override environment update"""

def _format_arg(self, name, spec, value):
if name == 'precision_type':
return spec.argstr % value[0]
return super(RegistrationSynQuick, self)._format_arg(name, spec, value)

def _list_outputs(self):
outputs = self.output_spec().get()
outputs['warped_image'] = os.path.abspath(self.inputs.output_prefix + 'Warped.nii.gz')
outputs['inverse_warped_image'] = os.path.abspath(
self.inputs.output_prefix + 'InverseWarped.nii.gz')
Copy link
Member

Choose a reason for hiding this comment

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

Just to be a little more concise, maybe we can do the following:

out_base = os.path.abspath(self.inputs.output_prefix)
outputs['warped_image'] = out_base + 'Warped.nii.gz'
outputs['inverse_warped_image'] = out_base + 'InverseWarped.nii.gz'

and so on...

outputs['out_matrix'] = os.path.abspath(self.inputs.output_prefix + '0GenericAffine.mat')

# todo in the case of linear transformation-only there won't be fields. is there a more elegant way to specify that?
Copy link
Member

Choose a reason for hiding this comment

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

What you have seems like a reasonable approach to me.

if self.inputs.transform_type not in ('t', 'r', 'a'):
outputs['forward_warp_field'] = os.path.abspath(
self.inputs.output_prefix + '1Warp.nii.gz')
outputs['inverse_warp_field'] = os.path.abspath(
self.inputs.output_prefix + '1InverseWarp.nii.gz')
return outputs
68 changes: 68 additions & 0 deletions nipype/interfaces/ants/tests/test_auto_RegistrationSynQuick.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
from __future__ import unicode_literals
from ..registration import RegistrationSynQuick


def test_RegistrationSynQuick_inputs():
input_map = dict(
args=dict(argstr='%s', ),
dimension=dict(
argstr='-d %d',
usedefault=True,
),
environ=dict(
nohash=True,
usedefault=True,
),
fixed_image=dict(
argstr='-f %s',
mandatory=True,
),
histogram_bins=dict(argstr='-r %d', ),
ignore_exception=dict(
deprecated='1.0.0',
nohash=True,
usedefault=True,
),
moving_image=dict(
argstr='-m %s',
mandatory=True,
),
num_threads=dict(argstr='-n %d', ),
output_prefix=dict(
argstr='-o %s',
usedefault=True,
),
precision_type=dict(
argstr='-p %s',
usedefault=True,
),
spline_distance=dict(argstr='-s %d', ),
terminal_output=dict(
deprecated='1.0.0',
nohash=True,
),
transform_type=dict(
argstr='-t %s',
usedefault=True,
),
use_histogram_matching=dict(argstr='-j %d', ),
)
inputs = RegistrationSynQuick.input_spec()

for key, metadata in list(input_map.items()):
for metakey, value in list(metadata.items()):
assert getattr(inputs.traits()[key], metakey) == value
def test_RegistrationSynQuick_outputs():
output_map = dict(
forward_warp_field=dict(),
inverse_warp_field=dict(),
inverse_warped_image=dict(),
out_matrix=dict(),
warped_image=dict(),
)
outputs = RegistrationSynQuick.output_spec()

for key, metadata in list(output_map.items()):
for metakey, value in list(metadata.items()):
assert getattr(outputs.traits()[key], metakey) == value