Skip to content

Commit 5a86d1c

Browse files
authored
Merge pull request #2172 from effigies/enh/mri_coreg
ENH: Miscellaneous FreeSurfer registration updates
2 parents 7ead69d + 757cf51 commit 5a86d1c

File tree

6 files changed

+313
-20
lines changed

6 files changed

+313
-20
lines changed

nipype/interfaces/freesurfer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@
2424
RelabelHypointensities, Aparc2Aseg, Apas2Aseg, MRIsExpand, MRIsCombine)
2525
from .longitudinal import (RobustTemplate, FuseSegmentations)
2626
from .registration import (MPRtoMNI305, RegisterAVItoTalairach, EMRegister, Register,
27-
Paint)
27+
Paint, MRICoreg)

nipype/interfaces/freesurfer/preprocess.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,8 @@ class BBRegisterInputSpec(FSTraitedSpec):
11591159
desc="write the transformation matrix in LTA format")
11601160
registered_file = traits.Either(traits.Bool, File, argstr='--o %s',
11611161
desc='output warped sourcefile either True or filename')
1162+
init_cost_file = traits.Either(traits.Bool, File, argstr='--initcost %s',
1163+
desc='output initial registration cost file')
11621164

11631165

11641166
class BBRegisterInputSpec6(BBRegisterInputSpec):
@@ -1172,10 +1174,11 @@ class BBRegisterInputSpec6(BBRegisterInputSpec):
11721174

11731175
class BBRegisterOutputSpec(TraitedSpec):
11741176
out_reg_file = File(exists=True, desc='Output registration file')
1175-
out_fsl_file = File(desc='Output FLIRT-style registration file')
1176-
out_lta_file = File(desc='Output LTA-style registration file')
1177+
out_fsl_file = File(exists=True, desc='Output FLIRT-style registration file')
1178+
out_lta_file = File(exists=True, desc='Output LTA-style registration file')
11771179
min_cost_file = File(exists=True, desc='Output registration minimum cost file')
1178-
registered_file = File(desc='Registered and resampled source file')
1180+
init_cost_file = File(exists=True, desc='Output initial registration cost file')
1181+
registered_file = File(exists=True, desc='Registered and resampled source file')
11791182

11801183

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

1248+
if isdefined(_in.init_cost_file):
1249+
if isinstance(_in.out_fsl_file, bool):
1250+
outputs['init_cost_file'] = outputs['out_reg_file'] + '.initcost'
1251+
else:
1252+
outputs['init_cost_file'] = op.abspath(_in.init_cost_file)
1253+
12451254
outputs['min_cost_file'] = outputs['out_reg_file'] + '.mincost'
12461255
return outputs
12471256

12481257
def _format_arg(self, name, spec, value):
1249-
1250-
if name in ['registered_file', 'out_fsl_file', 'out_lta_file']:
1251-
if isinstance(value, bool):
1252-
fname = self._list_outputs()[name]
1253-
else:
1254-
fname = value
1255-
return spec.argstr % fname
1258+
if name in ('registered_file', 'out_fsl_file', 'out_lta_file',
1259+
'init_cost_file') and isinstance(value, bool):
1260+
value = self._list_outputs()[name]
12561261
return super(BBRegister, self)._format_arg(name, spec, value)
12571262

12581263
def _gen_filename(self, name):
@@ -1277,7 +1282,15 @@ class ApplyVolTransformInputSpec(FSTraitedSpec):
12771282
fs_target = traits.Bool(argstr='--fstarg', xor=_targ_xor, mandatory=True,
12781283
requires=['reg_file'],
12791284
desc='use orig.mgz from subject in regfile as target')
1280-
_reg_xor = ('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject')
1285+
_reg_xor = ('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file',
1286+
'reg_header', 'mni_152_reg', 'subject')
1287+
reg_file = File(exists=True, xor=_reg_xor, argstr='--reg %s',
1288+
mandatory=True,
1289+
desc='tkRAS-to-tkRAS matrix (tkregister2 format)')
1290+
lta_file = File(exists=True, xor=_reg_xor, argstr='--lta %s',
1291+
mandatory=True, desc='Linear Transform Array file')
1292+
lta_inv_file = File(exists=True, xor=_reg_xor, argstr='--lta-inv %s',
1293+
mandatory=True, desc='LTA, invert')
12811294
reg_file = File(exists=True, xor=_reg_xor, argstr='--reg %s',
12821295
mandatory=True,
12831296
desc='tkRAS-to-tkRAS matrix (tkregister2 format)')
@@ -1290,8 +1303,9 @@ class ApplyVolTransformInputSpec(FSTraitedSpec):
12901303
reg_header = traits.Bool(xor=_reg_xor, argstr='--regheader',
12911304
mandatory=True,
12921305
desc='ScannerRAS-to-ScannerRAS matrix = identity')
1293-
subject = traits.Str(xor=_reg_xor, argstr='--s %s',
1294-
mandatory=True,
1306+
mni_152_reg = traits.Bool(xor=_reg_xor, argstr='--regheader', mandatory=True,
1307+
desc='target MNI152 space')
1308+
subject = traits.Str(xor=_reg_xor, argstr='--s %s', mandatory=True,
12951309
desc='set matrix = identity and use subject for any templates')
12961310
inverse = traits.Bool(desc='sample from target to source',
12971311
argstr='--inv')

nipype/interfaces/freesurfer/registration.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,160 @@ def _list_outputs(self):
338338
outputs = self.output_spec().get()
339339
outputs['out_file'] = os.path.abspath(self.inputs.out_file)
340340
return outputs
341+
342+
343+
class MRICoregInputSpec(FSTraitedSpec):
344+
source_file = File(argstr='--mov %s', desc='source file to be registered',
345+
mandatory=True, copyfile=False)
346+
reference_file = File(argstr='--ref %s', desc='reference (target) file',
347+
mandatory=True, copyfile=False, xor=['subject_id'])
348+
out_lta_file = traits.Either(True, File, argstr='--lta %s', default=True,
349+
usedefault=True,
350+
desc='output registration file (LTA format)')
351+
out_reg_file = traits.Either(True, File, argstr='--regdat %s',
352+
desc='output registration file (REG format)')
353+
out_params_file = traits.Either(True, File, argstr='--params %s',
354+
desc='output parameters file')
355+
356+
subjects_dir = Directory(exists=True, argstr='--sd %s',
357+
desc='FreeSurfer SUBJECTS_DIR')
358+
subject_id = traits.Str(
359+
argstr='--s %s', position=1, mandatory=True, xor=['reference_file'],
360+
requires=['subjects_dir'],
361+
desc='freesurfer subject ID (implies ``reference_mask == '
362+
'aparc+aseg.mgz`` unless otherwise specified)')
363+
dof = traits.Enum(6, 9, 12, argstr='--dof %d',
364+
desc='number of transform degrees of freedom')
365+
reference_mask = traits.Either(
366+
False, traits.Str, argstr='--ref-mask %s', position=2,
367+
desc='mask reference volume with given mask, or None if ``False``')
368+
source_mask = traits.Str(argstr='--mov-mask',
369+
desc='mask source file with given mask')
370+
num_threads = traits.Int(argstr='--threads %d',
371+
desc='number of OpenMP threads')
372+
no_coord_dithering = traits.Bool(argstr='--no-coord-dither',
373+
desc='turn off coordinate dithering')
374+
no_intensity_dithering = traits.Bool(argstr='--no-intensity-dither',
375+
desc='turn off intensity dithering')
376+
sep = traits.List(argstr='--sep %s...', minlen=1, maxlen=2,
377+
desc='set spatial scales, in voxels (default [2, 4])')
378+
initial_translation = traits.Tuple(
379+
traits.Float, traits.Float, traits.Float, argstr='--trans %g %g %g',
380+
desc='initial translation in mm (implies no_cras0)')
381+
initial_rotation = traits.Tuple(
382+
traits.Float, traits.Float, traits.Float, argstr='--rot %g %g %g',
383+
desc='initial rotation in degrees')
384+
initial_scale = traits.Tuple(
385+
traits.Float, traits.Float, traits.Float, argstr='--scale %g %g %g',
386+
desc='initial scale')
387+
initial_shear = traits.Tuple(
388+
traits.Float, traits.Float, traits.Float, argstr='--shear %g %g %g',
389+
desc='initial shear (Hxy, Hxz, Hyz)')
390+
no_cras0 = traits.Bool(argstr='--no-cras0',
391+
desc='do not set translation parameters to align '
392+
'centers of source and reference files')
393+
max_iters = traits.Range(low=1, argstr='--nitersmax %d',
394+
desc='maximum iterations (default: 4)')
395+
ftol = traits.Float(argstr='--ftol %e',
396+
desc='floating-point tolerance (default=1e-7)')
397+
linmintol = traits.Float(argstr='--linmintol %e')
398+
saturation_threshold = traits.Range(
399+
low=0.0, high=100.0, argstr='--sat %g',
400+
desc='saturation threshold (default=9.999)')
401+
conform_reference = traits.Bool(argstr='--conf-ref',
402+
desc='conform reference without rescaling')
403+
no_brute_force = traits.Bool(argstr='--no-bf',
404+
desc='do not brute force search')
405+
brute_force_limit = traits.Float(
406+
argstr='--bf-lim %g', xor=['no_brute_force'],
407+
desc='constrain brute force search to +/- lim')
408+
brute_force_samples = traits.Int(
409+
argstr='--bf-nsamp %d', xor=['no_brute_force'],
410+
desc='number of samples in brute force search')
411+
no_smooth = traits.Bool(
412+
argstr='--no-smooth',
413+
desc='do not apply smoothing to either reference or source file')
414+
ref_fwhm = traits.Float(argstr='--ref-fwhm',
415+
desc='apply smoothing to reference file')
416+
source_oob = traits.Bool(
417+
argstr='--mov-oob',
418+
desc='count source voxels that are out-of-bounds as 0')
419+
# Skipping mat2par
420+
421+
422+
class MRICoregOutputSpec(TraitedSpec):
423+
out_reg_file = File(exists=True, desc='output registration file')
424+
out_lta_file = File(exists=True, desc='output LTA-style registration file')
425+
out_params_file = File(exists=True, desc='output parameters file')
426+
427+
428+
class MRICoreg(FSCommand):
429+
""" This program registers one volume to another
430+
431+
mri_coreg is a C reimplementation of spm_coreg in FreeSurfer
432+
433+
Examples
434+
========
435+
>>> from nipype.interfaces.freesurfer import MRICoreg
436+
>>> coreg = MRICoreg()
437+
>>> coreg.inputs.source_file = 'moving1.nii'
438+
>>> coreg.inputs.reference_file = 'fixed1.nii'
439+
>>> coreg.inputs.subjects_dir = '.'
440+
>>> coreg.cmdline # doctest: +ALLOW_UNICODE +ELLIPSIS
441+
'mri_coreg --lta .../registration.lta --ref fixed1.nii --mov moving1.nii --sd .'
442+
443+
If passing a subject ID, the reference mask may be disabled:
444+
445+
>>> coreg = MRICoreg()
446+
>>> coreg.inputs.source_file = 'moving1.nii'
447+
>>> coreg.inputs.subjects_dir = '.'
448+
>>> coreg.inputs.subject_id = 'fsaverage'
449+
>>> coreg.inputs.reference_mask = False
450+
>>> coreg.cmdline # doctest: +ALLOW_UNICODE +ELLIPSIS
451+
'mri_coreg --s fsaverage --no-ref-mask --lta .../registration.lta --mov moving1.nii --sd .'
452+
453+
Spatial scales may be specified as a list of one or two separations:
454+
455+
>>> coreg.inputs.sep = [4]
456+
>>> coreg.cmdline # doctest: +ALLOW_UNICODE +ELLIPSIS
457+
'mri_coreg --s fsaverage --no-ref-mask --lta .../registration.lta --sep 4 --mov moving1.nii --sd .'
458+
459+
>>> coreg.inputs.sep = [4, 5]
460+
>>> coreg.cmdline # doctest: +ALLOW_UNICODE +ELLIPSIS
461+
'mri_coreg --s fsaverage --no-ref-mask --lta .../registration.lta --sep 4 --sep 5 --mov moving1.nii --sd .'
462+
"""
463+
464+
_cmd = 'mri_coreg'
465+
input_spec = MRICoregInputSpec
466+
output_spec = MRICoregOutputSpec
467+
468+
def _format_arg(self, opt, spec, val):
469+
if opt in ('out_reg_file', 'out_lta_file',
470+
'out_params_file') and val is True:
471+
val = self._list_outputs()[opt]
472+
elif opt == 'reference_mask' and val is False:
473+
return '--no-ref-mask'
474+
return super(MRICoreg, self)._format_arg(opt, spec, val)
475+
476+
def _list_outputs(self):
477+
outputs = self.output_spec().get()
478+
479+
out_lta_file = self.inputs.out_lta_file
480+
if isdefined(out_lta_file):
481+
if out_lta_file is True:
482+
out_lta_file = 'registration.lta'
483+
outputs['out_lta_file'] = os.path.abspath(out_lta_file)
484+
485+
out_reg_file = self.inputs.out_reg_file
486+
if isdefined(out_reg_file):
487+
if out_reg_file is True:
488+
out_reg_file = 'registration.dat'
489+
outputs['out_reg_file'] = os.path.abspath(out_reg_file)
490+
491+
out_params_file = self.inputs.out_params_file
492+
if isdefined(out_params_file):
493+
if out_params_file is True:
494+
out_params_file = 'registration.par'
495+
outputs['out_params_file'] = os.path.abspath(out_params_file)
496+
497+
return outputs

nipype/interfaces/freesurfer/tests/test_BBRegister.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def test_BBRegister_inputs():
1212
fsldof=dict(argstr='--fsl-dof %d',),
1313
ignore_exception=dict(nohash=True, usedefault=True,),
1414
init=dict(argstr='--init-%s', mandatory=True, xor=['init_reg_file'],),
15+
init_cost_file=dict(argstr='--initcost %s',),
1516
init_reg_file=dict(argstr='--init-reg %s', mandatory=True, xor=['init'],),
1617
intermediate_file=dict(argstr='--int %s',),
1718
out_fsl_file=dict(argstr='--fslmat %s',),
@@ -36,6 +37,7 @@ def test_BBRegister_inputs():
3637
ignore_exception=dict(nohash=True, usedefault=True,),
3738
init=dict(argstr='--init-%s', xor=['init_reg_file'],),
3839
init_reg_file=dict(argstr='--init-reg %s', xor=['init'],),
40+
init_cost_file=dict(argstr='--initcost %s',),
3941
intermediate_file=dict(argstr='--int %s',),
4042
out_fsl_file=dict(argstr='--fslmat %s',),
4143
out_lta_file=dict(argstr='--lta %s', min_ver='5.2.0',),
@@ -62,7 +64,8 @@ def test_BBRegister_inputs():
6264

6365

6466
def test_BBRegister_outputs():
65-
output_map = dict(min_cost_file=dict(),
67+
output_map = dict(init_cost_file=dict(),
68+
min_cost_file=dict(),
6669
out_fsl_file=dict(),
6770
out_lta_file=dict(),
6871
out_reg_file=dict(),

nipype/interfaces/freesurfer/tests/test_auto_ApplyVolTransform.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def test_ApplyVolTransform_inputs():
1616
),
1717
fsl_reg_file=dict(argstr='--fsl %s',
1818
mandatory=True,
19-
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
19+
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
2020
),
2121
ignore_exception=dict(nohash=True,
2222
usedefault=True,
@@ -28,28 +28,40 @@ def test_ApplyVolTransform_inputs():
2828
invert_morph=dict(argstr='--inv-morph',
2929
requires=['m3z_file'],
3030
),
31+
lta_file=dict(argstr='--lta %s',
32+
mandatory=True,
33+
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
34+
),
35+
lta_inv_file=dict(argstr='--lta-inv %s',
36+
mandatory=True,
37+
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
38+
),
3139
m3z_file=dict(argstr='--m3z %s',
3240
),
41+
mni_152_reg=dict(argstr='--regheader',
42+
mandatory=True,
43+
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
44+
),
3345
no_ded_m3z_path=dict(argstr='--noDefM3zPath',
3446
requires=['m3z_file'],
3547
),
3648
no_resample=dict(argstr='--no-resample',
3749
),
3850
reg_file=dict(argstr='--reg %s',
3951
mandatory=True,
40-
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
52+
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
4153
),
4254
reg_header=dict(argstr='--regheader',
4355
mandatory=True,
44-
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
56+
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
4557
),
4658
source_file=dict(argstr='--mov %s',
4759
copyfile=False,
4860
mandatory=True,
4961
),
5062
subject=dict(argstr='--s %s',
5163
mandatory=True,
52-
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
64+
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
5365
),
5466
subjects_dir=dict(),
5567
tal=dict(argstr='--tal',
@@ -69,7 +81,7 @@ def test_ApplyVolTransform_inputs():
6981
),
7082
xfm_reg_file=dict(argstr='--xfm %s',
7183
mandatory=True,
72-
xor=('reg_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'subject'),
84+
xor=('reg_file', 'lta_file', 'lta_inv_file', 'fsl_reg_file', 'xfm_reg_file', 'reg_header', 'mni_152_reg', 'subject'),
7385
),
7486
)
7587
inputs = ApplyVolTransform.input_spec()

0 commit comments

Comments
 (0)