Skip to content

Commit 6e07273

Browse files
authored
Merge pull request #1905 from jguillon/enh/spm-fieldmap-wrapper
[ENH/WIP] Add SPM Fieldmap Tool wrapper
2 parents 1a1f9fd + a4994ef commit 6e07273

File tree

6 files changed

+277
-5
lines changed

6 files changed

+277
-5
lines changed

nipype/interfaces/spm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from .base import (Info, SPMCommand, logger, no_spm, scans_for_fname,
77
scans_for_fnames)
8-
from .preprocess import (SliceTiming, Realign, Coregister, Normalize,
8+
from .preprocess import (FieldMap, SliceTiming, Realign, Coregister, Normalize,
99
Normalize12, Segment, Smooth, NewSegment, DARTEL,
1010
DARTELNorm2MNI, CreateWarped, VBMSegment)
1111
from .model import (Level1Design, EstimateModel, EstimateContrast, Threshold,

nipype/interfaces/spm/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,8 @@ def _format_arg(self, opt, spec, val):
375375
"""Convert input to appropriate format for SPM."""
376376
if spec.is_trait_type(traits.Bool):
377377
return int(val)
378+
elif spec.is_trait_type(traits.Tuple):
379+
return list(val)
378380
else:
379381
return val
380382

nipype/interfaces/spm/preprocess.py

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,149 @@
2222
# Local imports
2323
from ...utils.filemanip import (fname_presuffix, filename_to_list,
2424
list_to_filename, split_filename)
25-
from ..base import (OutputMultiPath, TraitedSpec, isdefined, traits,
26-
InputMultiPath, File)
27-
from .base import (SPMCommand, scans_for_fname, func_is_3d, scans_for_fnames,
28-
SPMCommandInputSpec, ImageFileSPM)
25+
from ..base import (OutputMultiPath, TraitedSpec, isdefined,
26+
traits, InputMultiPath, File, Str)
27+
from .base import (SPMCommand, scans_for_fname, func_is_3d,
28+
scans_for_fnames, SPMCommandInputSpec, ImageFileSPM)
2929

3030
__docformat__ = 'restructuredtext'
3131

3232

33+
class FieldMapInputSpec(SPMCommandInputSpec):
34+
jobtype = traits.Enum('calculatevdm', 'applyvdm', usedefault=True,
35+
desc='one of: calculatevdm, applyvdm')
36+
phase_file = File(mandatory=True, exists=True, copyfile=False,
37+
field='subj.data.presubphasemag.phase',
38+
desc='presubstracted phase file')
39+
magnitude_file = File(mandatory=True, exists=True, copyfile=False,
40+
field='subj.data.presubphasemag.magnitude',
41+
desc='presubstracted magnitude file')
42+
echo_times = traits.Tuple(traits.Float, traits.Float, mandatory=True,
43+
field='subj.defaults.defaultsval.et',
44+
desc='short and long echo times')
45+
maskbrain = traits.Bool(True, usedefault=True,
46+
field='subj.defaults.defaultsval.maskbrain',
47+
desc='masking or no masking of the brain')
48+
blip_direction = traits.Enum(1, -1, mandatory=True,
49+
field='subj.defaults.defaultsval.blipdir',
50+
desc='polarity of the phase-encode blips')
51+
total_readout_time = traits.Float(mandatory=True,
52+
field='subj.defaults.defaultsval.tert',
53+
desc='total EPI readout time')
54+
epifm = traits.Bool(False, usedefault=True,
55+
field='subj.defaults.defaultsval.epifm',
56+
desc='epi-based field map');
57+
jacobian_modulation = traits.Bool(False, usedefault=True,
58+
field='subj.defaults.defaultsval.ajm',
59+
desc='jacobian modulation');
60+
# Unwarping defaults parameters
61+
method = traits.Enum('Mark3D', 'Mark2D', 'Huttonish', usedefault=True,
62+
desc='One of: Mark3D, Mark2D, Huttonish',
63+
field='subj.defaults.defaultsval.uflags.method');
64+
unwarp_fwhm = traits.Range(low=0, value=10, usedefault=True,
65+
field='subj.defaults.defaultsval.uflags.fwhm',
66+
desc='gaussian smoothing kernel width');
67+
pad = traits.Range(low=0, value=0, usedefault=True,
68+
field='subj.defaults.defaultsval.uflags.pad',
69+
desc='padding kernel width');
70+
ws = traits.Bool(True, usedefault=True,
71+
field='subj.defaults.defaultsval.uflags.ws',
72+
desc='weighted smoothing');
73+
# Brain mask defaults parameters
74+
template = File(copyfile=False, exists=True,
75+
field='subj.defaults.defaultsval.mflags.template',
76+
desc='template image for brain masking');
77+
mask_fwhm = traits.Range(low=0, value=5, usedefault=True,
78+
field='subj.defaults.defaultsval.mflags.fwhm',
79+
desc='gaussian smoothing kernel width');
80+
nerode = traits.Range(low=0, value=2, usedefault=True,
81+
field='subj.defaults.defaultsval.mflags.nerode',
82+
desc='number of erosions');
83+
ndilate = traits.Range(low=0, value=4, usedefault=True,
84+
field='subj.defaults.defaultsval.mflags.ndilate',
85+
desc='number of erosions');
86+
thresh = traits.Float(0.5, usedefault=True,
87+
field='subj.defaults.defaultsval.mflags.thresh',
88+
desc='threshold used to create brain mask from segmented data');
89+
reg = traits.Float(0.02, usedefault=True,
90+
field='subj.defaults.defaultsval.mflags.reg',
91+
desc='regularization value used in the segmentation');
92+
# EPI unwarping for quality check
93+
epi_file = File(copyfile=False, exists=True, mandatory=True,
94+
field='subj.session.epi',
95+
desc='EPI to unwarp');
96+
matchvdm = traits.Bool(True, usedefault=True,
97+
field='subj.matchvdm',
98+
desc='match VDM to EPI');
99+
sessname = Str('_run-', usedefault=True,
100+
field='subj.sessname',
101+
desc='VDM filename extension');
102+
writeunwarped = traits.Bool(False, usedefault=True,
103+
field='subj.writeunwarped',
104+
desc='write unwarped EPI');
105+
anat_file = File(copyfile=False, exists=True,
106+
field='subj.anat',
107+
desc='anatomical image for comparison');
108+
matchanat = traits.Bool(True, usedefault=True,
109+
field='subj.matchanat',
110+
desc='match anatomical image to EPI');
111+
112+
113+
class FieldMapOutputSpec(TraitedSpec):
114+
vdm = File(exists=True, desc='voxel difference map')
115+
116+
117+
class FieldMap(SPMCommand):
118+
"""Use the fieldmap toolbox from spm to calculate the voxel displacement map (VDM).
119+
120+
http://www.fil.ion.ucl.ac.uk/spm/doc/manual.pdf#page=173
121+
122+
To do
123+
-----
124+
Deal with real/imag magnitude images and with the two phase files case.
125+
126+
Examples
127+
--------
128+
>>> from nipype.interfaces.spm import FieldMap
129+
>>> fm = FieldMap()
130+
>>> fm.inputs.phase_file = 'phase.nii'
131+
>>> fm.inputs.magnitude_file = 'magnitude.nii'
132+
>>> fm.inputs.echo_times = (5.19, 7.65)
133+
>>> fm.inputs.blip_direction = 1
134+
>>> fm.inputs.total_readout_time = 15.6
135+
>>> fm.inputs.epi_file = 'epi.nii'
136+
>>> fm.run() # doctest: +SKIP
137+
138+
"""
139+
140+
input_spec = FieldMapInputSpec
141+
output_spec = FieldMapOutputSpec
142+
_jobtype = 'tools'
143+
_jobname = 'fieldmap'
144+
145+
def _format_arg(self, opt, spec, val):
146+
"""Convert input to appropriate format for spm
147+
"""
148+
if opt in ['phase_file', 'magnitude_file', 'anat_file', 'epi_file']:
149+
return scans_for_fname(filename_to_list(val))
150+
151+
return super(FieldMap, self)._format_arg(opt, spec, val)
152+
153+
def _parse_inputs(self):
154+
"""validate spm fieldmap options if set to None ignore
155+
"""
156+
einputs = super(FieldMap, self)._parse_inputs()
157+
return [{self.inputs.jobtype: einputs[0]}]
158+
159+
def _list_outputs(self):
160+
outputs = self._outputs().get()
161+
jobtype = self.inputs.jobtype
162+
if jobtype == "calculatevdm":
163+
outputs['vdm'] = fname_presuffix(self.inputs.phase_file, prefix='vdm5_sc')
164+
165+
return outputs
166+
167+
33168
class SliceTimingInputSpec(SPMCommandInputSpec):
34169
in_files = InputMultiPath(
35170
traits.Either(
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
2+
from __future__ import unicode_literals
3+
from ..preprocess import FieldMap
4+
5+
6+
def test_FieldMap_inputs():
7+
input_map = dict(
8+
anat_file=dict(
9+
copyfile=False,
10+
field='subj.anat',
11+
),
12+
blip_direction=dict(
13+
field='subj.defaults.defaultsval.blipdir',
14+
mandatory=True,
15+
),
16+
echo_times=dict(
17+
field='subj.defaults.defaultsval.et',
18+
mandatory=True,
19+
),
20+
epi_file=dict(
21+
copyfile=False,
22+
field='subj.session.epi',
23+
mandatory=True,
24+
),
25+
epifm=dict(
26+
field='subj.defaults.defaultsval.epifm',
27+
usedefault=True,
28+
),
29+
ignore_exception=dict(
30+
deprecated='1.0.0',
31+
nohash=True,
32+
usedefault=True,
33+
),
34+
jacobian_modulation=dict(
35+
field='subj.defaults.defaultsval.ajm',
36+
usedefault=True,
37+
),
38+
jobtype=dict(usedefault=True, ),
39+
magnitude_file=dict(
40+
copyfile=False,
41+
field='subj.data.presubphasemag.magnitude',
42+
mandatory=True,
43+
),
44+
mask_fwhm=dict(
45+
field='subj.defaults.defaultsval.mflags.fwhm',
46+
usedefault=True,
47+
),
48+
maskbrain=dict(
49+
field='subj.defaults.defaultsval.maskbrain',
50+
usedefault=True,
51+
),
52+
matchanat=dict(
53+
field='subj.matchanat',
54+
usedefault=True,
55+
),
56+
matchvdm=dict(
57+
field='subj.matchvdm',
58+
usedefault=True,
59+
),
60+
matlab_cmd=dict(),
61+
method=dict(
62+
field='subj.defaults.defaultsval.uflags.method',
63+
usedefault=True,
64+
),
65+
mfile=dict(usedefault=True, ),
66+
ndilate=dict(
67+
field='subj.defaults.defaultsval.mflags.ndilate',
68+
usedefault=True,
69+
),
70+
nerode=dict(
71+
field='subj.defaults.defaultsval.mflags.nerode',
72+
usedefault=True,
73+
),
74+
pad=dict(
75+
field='subj.defaults.defaultsval.uflags.pad',
76+
usedefault=True,
77+
),
78+
paths=dict(),
79+
phase_file=dict(
80+
copyfile=False,
81+
field='subj.data.presubphasemag.phase',
82+
mandatory=True,
83+
),
84+
reg=dict(
85+
field='subj.defaults.defaultsval.mflags.reg',
86+
usedefault=True,
87+
),
88+
sessname=dict(
89+
field='subj.sessname',
90+
usedefault=True,
91+
),
92+
template=dict(
93+
copyfile=False,
94+
field='subj.defaults.defaultsval.mflags.template',
95+
),
96+
thresh=dict(
97+
field='subj.defaults.defaultsval.mflags.thresh',
98+
usedefault=True,
99+
),
100+
total_readout_time=dict(
101+
field='subj.defaults.defaultsval.tert',
102+
mandatory=True,
103+
),
104+
unwarp_fwhm=dict(
105+
field='subj.defaults.defaultsval.uflags.fwhm',
106+
usedefault=True,
107+
),
108+
use_mcr=dict(),
109+
use_v8struct=dict(
110+
min_ver='8',
111+
usedefault=True,
112+
),
113+
writeunwarped=dict(
114+
field='subj.writeunwarped',
115+
usedefault=True,
116+
),
117+
ws=dict(
118+
field='subj.defaults.defaultsval.uflags.ws',
119+
usedefault=True,
120+
),
121+
)
122+
inputs = FieldMap.input_spec()
123+
124+
for key, metadata in list(input_map.items()):
125+
for metakey, value in list(metadata.items()):
126+
assert getattr(inputs.traits()[key], metakey) == value
127+
def test_FieldMap_outputs():
128+
output_map = dict(vdm=dict(), )
129+
outputs = FieldMap.output_spec()
130+
131+
for key, metadata in list(output_map.items()):
132+
for metakey, value in list(metadata.items()):
133+
assert getattr(outputs.traits()[key], metakey) == value

nipype/interfaces/tests/test_auto_DataGrabber.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
def test_DataGrabber_inputs():
77
input_map = dict(
88
base_directory=dict(),
9+
drop_blank_outputs=dict(usedefault=True, ),
910
ignore_exception=dict(
1011
deprecated='1.0.0',
1112
nohash=True,

nipype/interfaces/tests/test_auto_SSHDataGrabber.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ def test_SSHDataGrabber_inputs():
77
input_map = dict(
88
base_directory=dict(mandatory=True, ),
99
download_files=dict(usedefault=True, ),
10+
drop_blank_outputs=dict(usedefault=True, ),
1011
hostname=dict(mandatory=True, ),
1112
ignore_exception=dict(
1213
deprecated='1.0.0',

0 commit comments

Comments
 (0)