Skip to content

ENH: Miscellaneous FreeSurfer registration updates #2172

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 6 commits into from
Sep 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nipype/interfaces/freesurfer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
RelabelHypointensities, Aparc2Aseg, Apas2Aseg, MRIsExpand, MRIsCombine)
from .longitudinal import (RobustTemplate, FuseSegmentations)
from .registration import (MPRtoMNI305, RegisterAVItoTalairach, EMRegister, Register,
Paint)
Paint, MRICoreg)
40 changes: 27 additions & 13 deletions nipype/interfaces/freesurfer/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,8 @@ class BBRegisterInputSpec(FSTraitedSpec):
desc="write the transformation matrix in LTA format")
registered_file = traits.Either(traits.Bool, File, argstr='--o %s',
desc='output warped sourcefile either True or filename')
init_cost_file = traits.Either(traits.Bool, File, argstr='--initcost %s',
desc='output initial registration cost file')


class BBRegisterInputSpec6(BBRegisterInputSpec):
Expand All @@ -1172,10 +1174,11 @@ class BBRegisterInputSpec6(BBRegisterInputSpec):

class BBRegisterOutputSpec(TraitedSpec):
out_reg_file = File(exists=True, desc='Output registration file')
out_fsl_file = File(desc='Output FLIRT-style registration file')
out_lta_file = File(desc='Output LTA-style registration file')
out_fsl_file = File(exists=True, desc='Output FLIRT-style registration file')
out_lta_file = File(exists=True, desc='Output LTA-style registration file')
min_cost_file = File(exists=True, desc='Output registration minimum cost file')
registered_file = File(desc='Registered and resampled source file')
init_cost_file = File(exists=True, desc='Output initial registration cost file')
registered_file = File(exists=True, desc='Registered and resampled source file')


class BBRegister(FSCommand):
Expand Down Expand Up @@ -1242,17 +1245,19 @@ def _list_outputs(self):
else:
outputs['out_fsl_file'] = op.abspath(_in.out_fsl_file)

if isdefined(_in.init_cost_file):
if isinstance(_in.out_fsl_file, bool):
outputs['init_cost_file'] = outputs['out_reg_file'] + '.initcost'
else:
outputs['init_cost_file'] = op.abspath(_in.init_cost_file)

outputs['min_cost_file'] = outputs['out_reg_file'] + '.mincost'
return outputs

def _format_arg(self, name, spec, value):

if name in ['registered_file', 'out_fsl_file', 'out_lta_file']:
if isinstance(value, bool):
fname = self._list_outputs()[name]
else:
fname = value
return spec.argstr % fname
if name in ('registered_file', 'out_fsl_file', 'out_lta_file',
'init_cost_file') and isinstance(value, bool):
value = self._list_outputs()[name]
return super(BBRegister, self)._format_arg(name, spec, value)

def _gen_filename(self, name):
Expand All @@ -1277,7 +1282,15 @@ class ApplyVolTransformInputSpec(FSTraitedSpec):
fs_target = traits.Bool(argstr='--fstarg', xor=_targ_xor, mandatory=True,
requires=['reg_file'],
desc='use orig.mgz from subject in regfile as target')
_reg_xor = ('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject')
_reg_xor = ('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file',
'reg_header', 'mni_152_reg', 'subject')
reg_file = File(exists=True, xor=_reg_xor, argstr='--reg %s',
mandatory=True,
desc='tkRAS-to-tkRAS matrix (tkregister2 format)')
lta_file = File(exists=True, xor=_reg_xor, argstr='--lta %s',
mandatory=True, desc='Linear Transform Array file')
lta_inv_file = File(exists=True, xor=_reg_xor, argstr='--lta-inv %s',
mandatory=True, desc='LTA, invert')
reg_file = File(exists=True, xor=_reg_xor, argstr='--reg %s',
mandatory=True,
desc='tkRAS-to-tkRAS matrix (tkregister2 format)')
Expand All @@ -1290,8 +1303,9 @@ class ApplyVolTransformInputSpec(FSTraitedSpec):
reg_header = traits.Bool(xor=_reg_xor, argstr='--regheader',
mandatory=True,
desc='ScannerRAS-to-ScannerRAS matrix = identity')
subject = traits.Str(xor=_reg_xor, argstr='--s %s',
mandatory=True,
mni_152_reg = traits.Bool(xor=_reg_xor, argstr='--regheader', mandatory=True,
desc='target MNI152 space')
subject = traits.Str(xor=_reg_xor, argstr='--s %s', mandatory=True,
desc='set matrix = identity and use subject for any templates')
inverse = traits.Bool(desc='sample from target to source',
argstr='--inv')
Expand Down
157 changes: 157 additions & 0 deletions nipype/interfaces/freesurfer/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,160 @@ def _list_outputs(self):
outputs = self.output_spec().get()
outputs['out_file'] = os.path.abspath(self.inputs.out_file)
return outputs


class MRICoregInputSpec(FSTraitedSpec):
source_file = File(argstr='--mov %s', desc='source file to be registered',
mandatory=True, copyfile=False)
reference_file = File(argstr='--ref %s', desc='reference (target) file',
mandatory=True, copyfile=False, xor=['subject_id'])
out_lta_file = traits.Either(True, File, argstr='--lta %s', default=True,
usedefault=True,
desc='output registration file (LTA format)')
out_reg_file = traits.Either(True, File, argstr='--regdat %s',
desc='output registration file (REG format)')
out_params_file = traits.Either(True, File, argstr='--params %s',
desc='output parameters file')

subjects_dir = Directory(exists=True, argstr='--sd %s',
desc='FreeSurfer SUBJECTS_DIR')
subject_id = traits.Str(
argstr='--s %s', position=1, mandatory=True, xor=['reference_file'],
requires=['subjects_dir'],
desc='freesurfer subject ID (implies ``reference_mask == '
'aparc+aseg.mgz`` unless otherwise specified)')
dof = traits.Enum(6, 9, 12, argstr='--dof %d',
desc='number of transform degrees of freedom')
reference_mask = traits.Either(
False, traits.Str, argstr='--ref-mask %s', position=2,
desc='mask reference volume with given mask, or None if ``False``')
source_mask = traits.Str(argstr='--mov-mask',
desc='mask source file with given mask')
num_threads = traits.Int(argstr='--threads %d',
desc='number of OpenMP threads')
no_coord_dithering = traits.Bool(argstr='--no-coord-dither',
desc='turn off coordinate dithering')
no_intensity_dithering = traits.Bool(argstr='--no-intensity-dither',
desc='turn off intensity dithering')
sep = traits.List(argstr='--sep %s...', minlen=1, maxlen=2,
desc='set spatial scales, in voxels (default [2, 4])')
initial_translation = traits.Tuple(
traits.Float, traits.Float, traits.Float, argstr='--trans %g %g %g',
desc='initial translation in mm (implies no_cras0)')
initial_rotation = traits.Tuple(
traits.Float, traits.Float, traits.Float, argstr='--rot %g %g %g',
desc='initial rotation in degrees')
initial_scale = traits.Tuple(
traits.Float, traits.Float, traits.Float, argstr='--scale %g %g %g',
desc='initial scale')
initial_shear = traits.Tuple(
traits.Float, traits.Float, traits.Float, argstr='--shear %g %g %g',
desc='initial shear (Hxy, Hxz, Hyz)')
no_cras0 = traits.Bool(argstr='--no-cras0',
desc='do not set translation parameters to align '
'centers of source and reference files')
max_iters = traits.Range(low=1, argstr='--nitersmax %d',
desc='maximum iterations (default: 4)')
ftol = traits.Float(argstr='--ftol %e',
desc='floating-point tolerance (default=1e-7)')
linmintol = traits.Float(argstr='--linmintol %e')
saturation_threshold = traits.Range(
low=0.0, high=100.0, argstr='--sat %g',
desc='saturation threshold (default=9.999)')
conform_reference = traits.Bool(argstr='--conf-ref',
desc='conform reference without rescaling')
no_brute_force = traits.Bool(argstr='--no-bf',
desc='do not brute force search')
brute_force_limit = traits.Float(
argstr='--bf-lim %g', xor=['no_brute_force'],
desc='constrain brute force search to +/- lim')
brute_force_samples = traits.Int(
argstr='--bf-nsamp %d', xor=['no_brute_force'],
desc='number of samples in brute force search')
no_smooth = traits.Bool(
argstr='--no-smooth',
desc='do not apply smoothing to either reference or source file')
ref_fwhm = traits.Float(argstr='--ref-fwhm',
desc='apply smoothing to reference file')
source_oob = traits.Bool(
argstr='--mov-oob',
desc='count source voxels that are out-of-bounds as 0')
# Skipping mat2par


class MRICoregOutputSpec(TraitedSpec):
out_reg_file = File(exists=True, desc='output registration file')
out_lta_file = File(exists=True, desc='output LTA-style registration file')
out_params_file = File(exists=True, desc='output parameters file')


class MRICoreg(FSCommand):
""" This program registers one volume to another

mri_coreg is a C reimplementation of spm_coreg in FreeSurfer

Examples
========
>>> from nipype.interfaces.freesurfer import MRICoreg
>>> coreg = MRICoreg()
>>> coreg.inputs.source_file = 'moving1.nii'
>>> coreg.inputs.reference_file = 'fixed1.nii'
>>> coreg.inputs.subjects_dir = '.'
>>> coreg.cmdline # doctest: +ALLOW_UNICODE +ELLIPSIS
'mri_coreg --lta .../registration.lta --ref fixed1.nii --mov moving1.nii --sd .'

If passing a subject ID, the reference mask may be disabled:

>>> coreg = MRICoreg()
>>> coreg.inputs.source_file = 'moving1.nii'
>>> coreg.inputs.subjects_dir = '.'
>>> coreg.inputs.subject_id = 'fsaverage'
>>> coreg.inputs.reference_mask = False
>>> coreg.cmdline # doctest: +ALLOW_UNICODE +ELLIPSIS
'mri_coreg --s fsaverage --no-ref-mask --lta .../registration.lta --mov moving1.nii --sd .'

Spatial scales may be specified as a list of one or two separations:

>>> coreg.inputs.sep = [4]
>>> coreg.cmdline # doctest: +ALLOW_UNICODE +ELLIPSIS
'mri_coreg --s fsaverage --no-ref-mask --lta .../registration.lta --sep 4 --mov moving1.nii --sd .'

>>> coreg.inputs.sep = [4, 5]
>>> coreg.cmdline # doctest: +ALLOW_UNICODE +ELLIPSIS
'mri_coreg --s fsaverage --no-ref-mask --lta .../registration.lta --sep 4 --sep 5 --mov moving1.nii --sd .'
"""

_cmd = 'mri_coreg'
input_spec = MRICoregInputSpec
output_spec = MRICoregOutputSpec

def _format_arg(self, opt, spec, val):
if opt in ('out_reg_file', 'out_lta_file',
'out_params_file') and val is True:
val = self._list_outputs()[opt]
elif opt == 'reference_mask' and val is False:
return '--no-ref-mask'
return super(MRICoreg, self)._format_arg(opt, spec, val)

def _list_outputs(self):
outputs = self.output_spec().get()

out_lta_file = self.inputs.out_lta_file
if isdefined(out_lta_file):
if out_lta_file is True:
out_lta_file = 'registration.lta'
outputs['out_lta_file'] = os.path.abspath(out_lta_file)

out_reg_file = self.inputs.out_reg_file
if isdefined(out_reg_file):
if out_reg_file is True:
out_reg_file = 'registration.dat'
outputs['out_reg_file'] = os.path.abspath(out_reg_file)

out_params_file = self.inputs.out_params_file
if isdefined(out_params_file):
if out_params_file is True:
out_params_file = 'registration.par'
outputs['out_params_file'] = os.path.abspath(out_params_file)

return outputs
5 changes: 4 additions & 1 deletion nipype/interfaces/freesurfer/tests/test_BBRegister.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def test_BBRegister_inputs():
fsldof=dict(argstr='--fsl-dof %d',),
ignore_exception=dict(nohash=True, usedefault=True,),
init=dict(argstr='--init-%s', mandatory=True, xor=['init_reg_file'],),
init_cost_file=dict(argstr='--initcost %s',),
init_reg_file=dict(argstr='--init-reg %s', mandatory=True, xor=['init'],),
intermediate_file=dict(argstr='--int %s',),
out_fsl_file=dict(argstr='--fslmat %s',),
Expand All @@ -36,6 +37,7 @@ def test_BBRegister_inputs():
ignore_exception=dict(nohash=True, usedefault=True,),
init=dict(argstr='--init-%s', xor=['init_reg_file'],),
init_reg_file=dict(argstr='--init-reg %s', xor=['init'],),
init_cost_file=dict(argstr='--initcost %s',),
intermediate_file=dict(argstr='--int %s',),
out_fsl_file=dict(argstr='--fslmat %s',),
out_lta_file=dict(argstr='--lta %s', min_ver='5.2.0',),
Expand All @@ -62,7 +64,8 @@ def test_BBRegister_inputs():


def test_BBRegister_outputs():
output_map = dict(min_cost_file=dict(),
output_map = dict(init_cost_file=dict(),
min_cost_file=dict(),
out_fsl_file=dict(),
out_lta_file=dict(),
out_reg_file=dict(),
Expand Down
22 changes: 17 additions & 5 deletions nipype/interfaces/freesurfer/tests/test_auto_ApplyVolTransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_ApplyVolTransform_inputs():
),
fsl_reg_file=dict(argstr='--fsl %s',
mandatory=True,
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
),
ignore_exception=dict(nohash=True,
usedefault=True,
Expand All @@ -28,28 +28,40 @@ def test_ApplyVolTransform_inputs():
invert_morph=dict(argstr='--inv-morph',
requires=['m3z_file'],
),
lta_file=dict(argstr='--lta %s',
mandatory=True,
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
),
lta_inv_file=dict(argstr='--lta-inv %s',
mandatory=True,
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
),
m3z_file=dict(argstr='--m3z %s',
),
mni_152_reg=dict(argstr='--regheader',
mandatory=True,
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
),
no_ded_m3z_path=dict(argstr='--noDefM3zPath',
requires=['m3z_file'],
),
no_resample=dict(argstr='--no-resample',
),
reg_file=dict(argstr='--reg %s',
mandatory=True,
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
),
reg_header=dict(argstr='--regheader',
mandatory=True,
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
),
source_file=dict(argstr='--mov %s',
copyfile=False,
mandatory=True,
),
subject=dict(argstr='--s %s',
mandatory=True,
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
),
subjects_dir=dict(),
tal=dict(argstr='--tal',
Expand All @@ -69,7 +81,7 @@ def test_ApplyVolTransform_inputs():
),
xfm_reg_file=dict(argstr='--xfm %s',
mandatory=True,
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
),
)
inputs = ApplyVolTransform.input_spec()
Expand Down
Loading