Skip to content

Commit af406fe

Browse files
committed
Merge pull request #857 from oesteban/bug/fixFUGUEsettings
FUGUE interface refactoring
2 parents 674e8ab + aee0789 commit af406fe

File tree

6 files changed

+171
-94
lines changed

6 files changed

+171
-94
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Next Release
22
============
33

4+
* ENH: FUGUE interface has been refactored to use the name_template system, 3 examples
5+
added to doctests, some bugs solved.
46
* ENH: Added new interfaces (fsl.utils.WarpUtils, ConvertWarp) to fnirtfileutils and convertwarp
57
* API: Interfaces to external packages are no longer available in the top-level
68
``nipype`` namespace, and must be imported directly (e.g.

nipype/interfaces/fsl/preprocess.py

Lines changed: 156 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,35 +1159,27 @@ def _gen_filename(self, name):
11591159
class FUGUEInputSpec(FSLCommandInputSpec):
11601160
in_file = File(exists=True, argstr='--in=%s',
11611161
desc='filename of input volume')
1162-
unwarped_file = File(
1163-
argstr='--unwarp=%s', genfile=True,
1164-
desc='apply unwarping and save as filename', hash_files=False)
1165-
forward_warping = traits.Bool(
1166-
False, usedefault=True,
1167-
desc='apply forward warping instead of unwarping')
1168-
warped_file = File(argstr='--warp=%s',
1169-
desc='apply forward warping and save as filename',
1170-
hash_files=False)
1171-
phasemap_file = File(exists=True, argstr='--phasemap=%s',
1172-
desc='filename for input phase image')
1162+
shift_in_file = File(exists=True, argstr='--loadshift=%s',
1163+
desc='filename for reading pixel shift volume')
1164+
phasemap_in_file = File(exists=True, argstr='--phasemap=%s',
1165+
desc='filename for input phase image')
1166+
fmap_in_file = File(exists=True, argstr='--loadfmap=%s',
1167+
desc='filename for loading fieldmap (rad/s)')
1168+
unwarped_file = File(argstr='--unwarp=%s', desc='apply unwarping and save as filename',
1169+
xor=['warped_file'], requires=['in_file'])
1170+
warped_file = File(argstr='--warp=%s', desc='apply forward warping and save as filename',
1171+
xor=['unwarped_file'], requires=['in_file'])
1172+
1173+
forward_warping = traits.Bool(False, usedefault=True,
1174+
desc='apply forward warping instead of unwarping')
1175+
11731176
dwell_to_asym_ratio = traits.Float(argstr='--dwelltoasym=%.10f',
11741177
desc='set the dwell to asym time ratio')
11751178
dwell_time = traits.Float(argstr='--dwell=%.10f',
1176-
desc='set the EPI dwell time per phase-encode line - same as echo spacing - (sec)')
1179+
desc=('set the EPI dwell time per phase-encode line - same as echo '
1180+
'spacing - (sec)'))
11771181
asym_se_time = traits.Float(argstr='--asym=%.10f',
11781182
desc='set the fieldmap asymmetric spin echo time (sec)')
1179-
fmap_out_file = File(argstr='--savefmap=%s',
1180-
desc='filename for saving fieldmap (rad/s)', hash_files=False)
1181-
fmap_in_file = File(exists=True, argstr='--loadfmap=%s',
1182-
desc='filename for loading fieldmap (rad/s)')
1183-
1184-
save_shift = traits.Bool(desc='output pixel shift volume')
1185-
1186-
shift_out_file = traits.File(argstr='--saveshift=%s',
1187-
desc='filename for saving pixel shift volume', hash_files=False)
1188-
1189-
shift_in_file = File(exists=True, argstr='--loadshift=%s',
1190-
desc='filename for reading pixel shift volume')
11911183
median_2dfilter = traits.Bool(argstr='--median',
11921184
desc='apply 2D median filtering')
11931185
despike_2dfilter = traits.Bool(argstr='--despike',
@@ -1217,16 +1209,24 @@ class FUGUEInputSpec(FSLCommandInputSpec):
12171209
desc='apply intensity correction to unwarping (pixel shift method only)')
12181210
icorr_only = traits.Bool(argstr='--icorronly', requires=['unwarped_file'],
12191211
desc='apply intensity correction only')
1220-
mask_file = File(exists=True, argstr='--mask=%s',
1221-
desc='filename for loading valid mask')
1222-
save_unmasked_fmap = traits.Bool(argstr='--unmaskfmap',
1223-
requires=['fmap_out_file'],
1224-
desc='saves the unmasked fieldmap when using --savefmap')
1225-
save_unmasked_shift = traits.Bool(argstr='--unmaskshift',
1226-
requires=['shift_out_file'],
1212+
mask_file = File(exists=True, argstr='--mask=%s', desc='filename for loading valid mask')
1213+
nokspace = traits.Bool(False, argstr='--nokspace', desc='do not use k-space forward warping')
1214+
1215+
# Special outputs: shift (voxel shift map, vsm)
1216+
save_shift = traits.Bool(False, xor=['save_unmasked_shift'],
1217+
desc='write pixel shift volume')
1218+
shift_out_file = File(argstr='--saveshift=%s', desc='filename for saving pixel shift volume')
1219+
save_unmasked_shift = traits.Bool(argstr='--unmaskshift', xor=['save_shift'],
12271220
desc='saves the unmasked shiftmap when using --saveshift')
1228-
nokspace = traits.Bool(
1229-
argstr='--nokspace', desc='do not use k-space forward warping')
1221+
1222+
# Special outputs: fieldmap (fmap)
1223+
save_fmap = traits.Bool(False, xor=['save_unmasked_fmap'],
1224+
desc='write field map volume')
1225+
fmap_out_file = File(argstr='--savefmap=%s', desc='filename for saving fieldmap (rad/s)')
1226+
save_unmasked_fmap = traits.Bool(False, argstr='--unmaskfmap', xor=['save_fmap'],
1227+
desc='saves the unmasked fieldmap when using --savefmap')
1228+
1229+
12301230

12311231

12321232
class FUGUEOutputSpec(TraitedSpec):
@@ -1237,75 +1237,148 @@ class FUGUEOutputSpec(TraitedSpec):
12371237

12381238

12391239
class FUGUE(FSLCommand):
1240-
"""Use FSL FUGUE to unwarp epi's with fieldmaps
1240+
"""
1241+
`FUGUE <http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FUGUE>`_ is, most generally, a set of tools for
1242+
EPI distortion correction.
1243+
1244+
Distortions may be corrected for
1245+
1. improving registration with non-distorted images (e.g. structurals), or
1246+
2. dealing with motion-dependent changes.
1247+
1248+
FUGUE is designed to deal only with the first case - improving registration.
1249+
12411250
12421251
Examples
12431252
--------
12441253
1245-
Please insert examples for use of this command
12461254
1247-
"""
1255+
Unwarping an input image (shift map is known) ::
12481256
1249-
_cmd = 'fugue'
1250-
input_spec = FUGUEInputSpec
1251-
output_spec = FUGUEOutputSpec
1257+
>>> from nipype.interfaces.fsl.preprocess import FUGUE
1258+
>>> fugue = FUGUE()
1259+
>>> fugue.inputs.in_file = 'epi.nii'
1260+
>>> fugue.inputs.mask_file = 'epi_mask.nii'
1261+
>>> fugue.inputs.shift_in_file = 'vsm.nii' # Previously computed with fugue as well
1262+
>>> fugue.inputs.unwarp_direction = 'y'
1263+
>>> fugue.cmdline #doctest: +ELLIPSIS
1264+
'fugue --in=epi.nii --mask=epi_mask.nii --loadshift=vsm.nii --unwarpdir=y --unwarp=epi_unwarped.nii.gz'
1265+
>>> fugue.run() #doctest: +SKIP
12521266
1253-
def __init__(self, **kwargs):
1254-
super(FUGUE, self).__init__(**kwargs)
1255-
warn(
1256-
'This interface has not been fully tested. Please report any failures.')
12571267
1258-
def _list_outputs(self):
1259-
outputs = self._outputs().get()
1260-
if self.inputs.forward_warping:
1261-
out_field = 'warped_file'
1262-
else:
1263-
out_field = 'unwarped_file'
1268+
Warping an input image (shift map is known) ::
12641269
1265-
out_file = getattr(self.inputs, out_field)
1266-
if not isdefined(out_file):
1267-
if isdefined(self.inputs.in_file):
1268-
out_file = self._gen_fname(self.inputs.in_file,
1269-
suffix='_'+out_field[:-5])
1270-
if isdefined(out_file):
1271-
outputs[out_field] = os.path.abspath(out_file)
1272-
if isdefined(self.inputs.fmap_out_file):
1273-
outputs['fmap_out_file'] = os.path.abspath(
1274-
self.inputs.fmap_out_file)
1275-
if isdefined(self.inputs.shift_out_file):
1276-
outputs['shift_out_file'] = os.path.abspath(
1277-
self.inputs.shift_out_file)
1270+
>>> from nipype.interfaces.fsl.preprocess import FUGUE
1271+
>>> fugue = FUGUE()
1272+
>>> fugue.inputs.in_file = 'epi.nii'
1273+
>>> fugue.inputs.forward_warping = True
1274+
>>> fugue.inputs.mask_file = 'epi_mask.nii'
1275+
>>> fugue.inputs.shift_in_file = 'vsm.nii' # Previously computed with fugue as well
1276+
>>> fugue.inputs.unwarp_direction = 'y'
1277+
>>> fugue.cmdline #doctest: +ELLIPSIS
1278+
'fugue --in=epi.nii --mask=epi_mask.nii --loadshift=vsm.nii --unwarpdir=y --warp=epi_warped.nii.gz'
1279+
>>> fugue.run() #doctest: +SKIP
12781280
1279-
return outputs
12801281
1281-
def _gen_filename(self, name):
1282-
if name == 'unwarped_file' and not self.inputs.forward_warping:
1283-
return self._list_outputs()['unwarped_file']
1284-
if name == 'warped_file' and self.inputs.forward_warping:
1285-
return self._list_outputs()['warped_file']
1286-
return None
1282+
Computing the vsm (unwrapped phase map is known) ::
1283+
1284+
>>> from nipype.interfaces.fsl.preprocess import FUGUE
1285+
>>> fugue = FUGUE()
1286+
>>> fugue.inputs.phasemap_in_file = 'epi_phasediff.nii'
1287+
>>> fugue.inputs.mask_file = 'epi_mask.nii'
1288+
>>> fugue.inputs.dwell_to_asym_ratio = (0.77e-3 * 3) / 2.46e-3
1289+
>>> fugue.inputs.unwarp_direction = 'y'
1290+
>>> fugue.inputs.save_shift = True
1291+
>>> fugue.cmdline #doctest: +ELLIPSIS
1292+
'fugue --dwelltoasym=0.9390243902 --mask=epi_mask.nii --phasemap=epi_phasediff.nii --saveshift=epi_phasediff_vsm.nii.gz --unwarpdir=y'
1293+
>>> fugue.run() #doctest: +SKIP
1294+
1295+
1296+
"""
1297+
1298+
_cmd = 'fugue'
1299+
input_spec = FUGUEInputSpec
1300+
output_spec = FUGUEOutputSpec
12871301

12881302
def _parse_inputs(self, skip=None):
12891303
if skip is None:
12901304
skip = []
12911305

1292-
if not isdefined(self.inputs.save_shift) or not self.inputs.save_shift:
1293-
skip += ['shift_out_file']
1294-
else:
1295-
if not isdefined(self.inputs.shift_out_file):
1296-
self.inputs.shift_out_file = self._gen_fname(
1297-
self.inputs.in_file, suffix='_vsm')
1306+
input_phase = isdefined(self.inputs.phasemap_in_file)
1307+
input_vsm = isdefined(self.inputs.shift_in_file)
1308+
input_fmap = isdefined(self.inputs.fmap_in_file)
1309+
1310+
if not input_phase and not input_vsm and not input_fmap:
1311+
raise RuntimeError('Either phasemap_in_file, shift_in_file or fmap_in_file must be set.')
12981312

12991313
if not isdefined(self.inputs.in_file):
13001314
skip += ['unwarped_file', 'warped_file']
1301-
elif self.inputs.forward_warping:
1302-
if not isdefined(self.inputs.warped_file):
1303-
self.inputs.warped_file = self._gen_fname(
1304-
self.inputs.in_file, suffix='_warped')
1305-
elif not self.inputs.forward_warping:
1306-
if not isdefined(self.inputs.unwarped_file):
1307-
self.inputs.unwarped_file = self._gen_fname(
1308-
self.inputs.in_file, suffix='_unwarped')
1315+
else:
1316+
if self.inputs.forward_warping:
1317+
skip += ['unwarped_file']
1318+
trait_spec = self.inputs.trait('warped_file')
1319+
trait_spec.name_template = "%s_warped"
1320+
trait_spec.name_source = 'in_file'
1321+
trait_spec.output_name = 'warped_file'
1322+
else:
1323+
skip += ['warped_file']
1324+
trait_spec = self.inputs.trait('unwarped_file')
1325+
trait_spec.name_template = "%s_unwarped"
1326+
trait_spec.name_source = 'in_file'
1327+
trait_spec.output_name = 'unwarped_file'
1328+
1329+
# Handle shift output
1330+
if not isdefined(self.inputs.shift_out_file):
1331+
vsm_save_masked = (isdefined(self.inputs.save_shift) and self.inputs.save_shift)
1332+
vsm_save_unmasked = (isdefined(self.inputs.save_unmasked_shift) and
1333+
self.inputs.save_unmasked_shift)
1334+
1335+
if (vsm_save_masked or vsm_save_unmasked):
1336+
trait_spec = self.inputs.trait('shift_out_file')
1337+
trait_spec.output_name = 'shift_out_file'
1338+
1339+
if input_fmap:
1340+
trait_spec.name_source = 'fmap_in_file'
1341+
elif input_phase:
1342+
trait_spec.name_source = 'phasemap_in_file'
1343+
elif input_vsm:
1344+
trait_spec.name_source = 'shift_in_file'
1345+
else:
1346+
raise RuntimeError(('Either phasemap_in_file, shift_in_file or '
1347+
'fmap_in_file must be set.'))
1348+
1349+
if vsm_save_unmasked:
1350+
trait_spec.name_template = '%s_vsm_unmasked'
1351+
else:
1352+
trait_spec.name_template = '%s_vsm'
1353+
else:
1354+
skip += ['save_shift', 'save_unmasked_shift', 'shift_out_file']
1355+
1356+
# Handle fieldmap output
1357+
if not isdefined(self.inputs.fmap_out_file):
1358+
fmap_save_masked = (isdefined(self.inputs.save_fmap) and self.inputs.save_fmap)
1359+
fmap_save_unmasked = (isdefined(self.inputs.save_unmasked_fmap) and
1360+
self.inputs.save_unmasked_fmap)
1361+
1362+
if (fmap_save_masked or fmap_save_unmasked):
1363+
trait_spec = self.inputs.trait('fmap_out_file')
1364+
trait_spec.output_name = 'fmap_out_file'
1365+
1366+
if input_vsm:
1367+
trait_spec.name_source = 'shift_in_file'
1368+
elif input_phase:
1369+
trait_spec.name_source = 'phasemap_in_file'
1370+
elif input_fmap:
1371+
trait_spec.name_source = 'fmap_in_file'
1372+
else:
1373+
raise RuntimeError(('Either phasemap_in_file, shift_in_file or '
1374+
'fmap_in_file must be set.'))
1375+
1376+
if fmap_save_unmasked:
1377+
trait_spec.name_template = '%s_fieldmap_unmasked'
1378+
else:
1379+
trait_spec.name_template = '%s_fieldmap'
1380+
else:
1381+
skip += ['save_fmap', 'save_unmasked_fmap', 'fmap_out_file']
13091382

13101383
return super(FUGUE, self)._parse_inputs(skip=skip)
13111384

nipype/interfaces/fsl/tests/test_auto_FUGUE.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ def test_FUGUE_inputs():
2121
fmap_in_file=dict(argstr='--loadfmap=%s',
2222
),
2323
fmap_out_file=dict(argstr='--savefmap=%s',
24-
hash_files=False,
2524
),
2625
forward_warping=dict(usedefault=True,
2726
),
@@ -53,21 +52,23 @@ def test_FUGUE_inputs():
5352
),
5453
phase_conjugate=dict(argstr='--phaseconj',
5554
),
56-
phasemap_file=dict(argstr='--phasemap=%s',
55+
phasemap_in_file=dict(argstr='--phasemap=%s',
5756
),
5857
poly_order=dict(argstr='--poly=%d',
5958
),
60-
save_shift=dict(),
59+
save_fmap=dict(xor=['save_unmasked_fmap'],
60+
),
61+
save_shift=dict(xor=['save_unmasked_shift'],
62+
),
6163
save_unmasked_fmap=dict(argstr='--unmaskfmap',
62-
requires=['fmap_out_file'],
64+
xor=['save_fmap'],
6365
),
6466
save_unmasked_shift=dict(argstr='--unmaskshift',
65-
requires=['shift_out_file'],
67+
xor=['save_shift'],
6668
),
6769
shift_in_file=dict(argstr='--loadshift=%s',
6870
),
6971
shift_out_file=dict(argstr='--saveshift=%s',
70-
hash_files=False,
7172
),
7273
smooth2d=dict(argstr='--smooth2=%.2f',
7374
),
@@ -79,11 +80,12 @@ def test_FUGUE_inputs():
7980
unwarp_direction=dict(argstr='--unwarpdir=%s',
8081
),
8182
unwarped_file=dict(argstr='--unwarp=%s',
82-
genfile=True,
83-
hash_files=False,
83+
requires=['in_file'],
84+
xor=['warped_file'],
8485
),
8586
warped_file=dict(argstr='--warp=%s',
86-
hash_files=False,
87+
requires=['in_file'],
88+
xor=['unwarped_file'],
8789
),
8890
)
8991
inputs = FUGUE.input_spec()

nipype/testing/data/epi_phasediff.nii

Whitespace-only changes.

nipype/testing/data/vsm.nii

Whitespace-only changes.

nipype/workflows/dmri/fsl/epi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def fieldmap_correction(name='fieldmap_correction', nocheck=False):
337337
,(inputnode, mask_mag, [('in_mask', 'mask_file' )])
338338
,(select_mag, mask_mag, [('roi_file', 'in_file')])
339339
,(mask_mag, fslprep, [('out_file', 'in_magnitude')])
340-
,(fslprep, vsm, [('out_fieldmap', 'phasemap_file')])
340+
,(fslprep, vsm, [('out_fieldmap', 'phasemap_in_file')])
341341
,(inputnode, vsm, [('fieldmap_mag', 'in_file'),
342342
('encoding_direction','unwarp_direction'),
343343
(('te_diff', _ms2sec), 'asym_se_time'),
@@ -550,7 +550,7 @@ def create_epidewarp_pipeline(name='epidewarp', fieldmap_registration=False):
550550
,(mask_mag_dil, prelude, [('out_file', 'mask_file')])
551551
,(prelude, fill_phase, [('unwrapped_phase_file', 'in_file')])
552552
,(inputnode, vsm, [('fieldmap_mag', 'in_file')])
553-
,(fill_phase, vsm, [('out_file', 'phasemap_file')])
553+
,(fill_phase, vsm, [('out_file', 'phasemap_in_file')])
554554
,(inputnode, vsm, [(('te_diff', _ms2sec), 'asym_se_time'), ('vsm_sigma', 'smooth2d')])
555555
,(dwell_time, vsm, [(('dwell_time', _ms2sec), 'dwell_time')])
556556
,(mask_mag_dil, vsm, [('out_file', 'mask_file')])

0 commit comments

Comments
 (0)