diff --git a/nipype/interfaces/freesurfer/__init__.py b/nipype/interfaces/freesurfer/__init__.py index 7f72ff3f6c..46e2dc9c0d 100644 --- a/nipype/interfaces/freesurfer/__init__.py +++ b/nipype/interfaces/freesurfer/__init__.py @@ -24,4 +24,4 @@ RelabelHypointensities, Aparc2Aseg, Apas2Aseg, MRIsExpand, MRIsCombine) from .longitudinal import (RobustTemplate, FuseSegmentations) from .registration import (MPRtoMNI305, RegisterAVItoTalairach, EMRegister, Register, - Paint) + Paint, MRICoreg) diff --git a/nipype/interfaces/freesurfer/preprocess.py b/nipype/interfaces/freesurfer/preprocess.py index 4e164c342d..13bd2d86bf 100644 --- a/nipype/interfaces/freesurfer/preprocess.py +++ b/nipype/interfaces/freesurfer/preprocess.py @@ -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): @@ -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): @@ -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): @@ -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)') @@ -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') diff --git a/nipype/interfaces/freesurfer/registration.py b/nipype/interfaces/freesurfer/registration.py index d3cba1749c..72a3fdb0ee 100644 --- a/nipype/interfaces/freesurfer/registration.py +++ b/nipype/interfaces/freesurfer/registration.py @@ -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 diff --git a/nipype/interfaces/freesurfer/tests/test_BBRegister.py b/nipype/interfaces/freesurfer/tests/test_BBRegister.py index e29ea17b63..9725065fef 100644 --- a/nipype/interfaces/freesurfer/tests/test_BBRegister.py +++ b/nipype/interfaces/freesurfer/tests/test_BBRegister.py @@ -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',), @@ -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',), @@ -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(), diff --git a/nipype/interfaces/freesurfer/tests/test_auto_ApplyVolTransform.py b/nipype/interfaces/freesurfer/tests/test_auto_ApplyVolTransform.py index 6142ae84f1..e4f93a1ce2 100644 --- a/nipype/interfaces/freesurfer/tests/test_auto_ApplyVolTransform.py +++ b/nipype/interfaces/freesurfer/tests/test_auto_ApplyVolTransform.py @@ -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, @@ -28,8 +28,20 @@ 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'], ), @@ -37,11 +49,11 @@ def test_ApplyVolTransform_inputs(): ), 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, @@ -49,7 +61,7 @@ def test_ApplyVolTransform_inputs(): ), 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', @@ -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() diff --git a/nipype/interfaces/freesurfer/tests/test_auto_MRICoreg.py b/nipype/interfaces/freesurfer/tests/test_auto_MRICoreg.py new file mode 100644 index 0000000000..5ba95570c8 --- /dev/null +++ b/nipype/interfaces/freesurfer/tests/test_auto_MRICoreg.py @@ -0,0 +1,107 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from __future__ import unicode_literals +from ..registration import MRICoreg + + +def test_MRICoreg_inputs(): + input_map = dict(args=dict(argstr='%s', + ), + brute_force_limit=dict(argstr='--bf-lim %g', + xor=['no_brute_force'], + ), + brute_force_samples=dict(argstr='--bf-nsamp %d', + xor=['no_brute_force'], + ), + conform_reference=dict(argstr='--conf-ref', + ), + dof=dict(argstr='--dof %d', + ), + environ=dict(nohash=True, + usedefault=True, + ), + ftol=dict(argstr='--ftol %e', + ), + ignore_exception=dict(nohash=True, + usedefault=True, + ), + initial_rotation=dict(argstr='--rot %g %g %g', + ), + initial_scale=dict(argstr='--scale %g %g %g', + ), + initial_shear=dict(argstr='--shear %g %g %g', + ), + initial_translation=dict(argstr='--trans %g %g %g', + ), + linmintol=dict(argstr='--linmintol %e', + ), + max_iters=dict(argstr='--nitersmax %d', + ), + no_brute_force=dict(argstr='--no-bf', + ), + no_coord_dithering=dict(argstr='--no-coord-dither', + ), + no_cras0=dict(argstr='--no-cras0', + ), + no_intensity_dithering=dict(argstr='--no-intensity-dither', + ), + no_smooth=dict(argstr='--no-smooth', + ), + num_threads=dict(argstr='--threads %d', + ), + out_lta_file=dict(argstr='--lta %s', + usedefault=True, + ), + out_params_file=dict(argstr='--params %s', + ), + out_reg_file=dict(argstr='--regdat %s', + ), + ref_fwhm=dict(argstr='--ref-fwhm', + ), + reference_file=dict(argstr='--ref %s', + copyfile=False, + mandatory=True, + xor=['subject_id'], + ), + reference_mask=dict(argstr='--ref-mask %s', + position=2, + ), + saturation_threshold=dict(argstr='--sat %g', + ), + sep=dict(argstr='--sep %s...', + ), + source_file=dict(argstr='--mov %s', + copyfile=False, + mandatory=True, + ), + source_mask=dict(argstr='--mov-mask', + ), + source_oob=dict(argstr='--mov-oob', + ), + subject_id=dict(argstr='--s %s', + mandatory=True, + position=1, + requires=['subjects_dir'], + xor=['reference_file'], + ), + subjects_dir=dict(argstr='--sd %s', + ), + terminal_output=dict(nohash=True, + ), + ) + inputs = MRICoreg.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_MRICoreg_outputs(): + output_map = dict(out_lta_file=dict(), + out_params_file=dict(), + out_reg_file=dict(), + ) + outputs = MRICoreg.output_spec() + + for key, metadata in list(output_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(outputs.traits()[key], metakey) == value