Skip to content

Commit d9f3442

Browse files
committed
FUGUE interface refactored.
Deeply reviewed the interface to use the new name_template system, along with the correction of several issues. Also includes changes of another PR, so close #856.
1 parent 39bfb99 commit d9f3442

File tree

4 files changed

+128
-95
lines changed

4 files changed

+128
-95
lines changed

nipype/interfaces/fsl/preprocess.py

Lines changed: 120 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,34 +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+
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)')
11621168
unwarped_file = File(argstr='--unwarp=%s', desc='apply unwarping and save as filename',
1163-
hash_files=False)
1164-
forward_warping = traits.Bool(
1165-
False, usedefault=True,
1166-
desc='apply forward warping instead of unwarping')
1167-
warped_file = File(argstr='--warp=%s',
1168-
desc='apply forward warping and save as filename',
1169-
hash_files=False)
1170-
phasemap_file = File(exists=True, argstr='--phasemap=%s',
1171-
desc='filename for input phase image')
1169+
xor=['warped_file'])
1170+
warped_file = File(argstr='--warp=%s', desc='apply forward warping and save as filename',
1171+
xor=['unwarped_file'])
1172+
1173+
forward_warping = traits.Bool(False, usedefault=True, mandatory=True,
1174+
desc='apply forward warping instead of unwarping')
1175+
11721176
dwell_to_asym_ratio = traits.Float(argstr='--dwelltoasym=%.10f',
11731177
desc='set the dwell to asym time ratio')
11741178
dwell_time = traits.Float(argstr='--dwell=%.10f',
1175-
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)'))
11761181
asym_se_time = traits.Float(argstr='--asym=%.10f',
11771182
desc='set the fieldmap asymmetric spin echo time (sec)')
1178-
fmap_out_file = File(argstr='--savefmap=%s',
1179-
desc='filename for saving fieldmap (rad/s)', hash_files=False)
1180-
fmap_in_file = File(exists=True, argstr='--loadfmap=%s',
1181-
desc='filename for loading fieldmap (rad/s)')
1182-
1183-
save_shift = traits.Bool(desc='output pixel shift volume')
1184-
1185-
shift_out_file = traits.File(argstr='--saveshift=%s',
1186-
desc='filename for saving pixel shift volume', hash_files=False)
1187-
1188-
shift_in_file = File(exists=True, argstr='--loadshift=%s',
1189-
desc='filename for reading pixel shift volume')
11901183
median_2dfilter = traits.Bool(argstr='--median',
11911184
desc='apply 2D median filtering')
11921185
despike_2dfilter = traits.Bool(argstr='--despike',
@@ -1216,16 +1209,22 @@ class FUGUEInputSpec(FSLCommandInputSpec):
12161209
desc='apply intensity correction to unwarping (pixel shift method only)')
12171210
icorr_only = traits.Bool(argstr='--icorronly', requires=['unwarped_file'],
12181211
desc='apply intensity correction only')
1219-
mask_file = File(exists=True, argstr='--mask=%s',
1220-
desc='filename for loading valid mask')
1221-
save_unmasked_fmap = traits.Bool(argstr='--unmaskfmap',
1222-
requires=['fmap_out_file'],
1223-
desc='saves the unmasked fieldmap when using --savefmap')
1224-
save_unmasked_shift = traits.Bool(argstr='--unmaskshift',
1225-
requires=['save_shift'],
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, desc='write pixel shift volume')
1217+
shift_out_file = File(argstr='--saveshift=%s', desc='filename for saving pixel shift volume')
1218+
save_unmasked_shift = traits.Bool(argstr='--unmaskshift', xor=['save_shift'],
12261219
desc='saves the unmasked shiftmap when using --saveshift')
1227-
nokspace = traits.Bool(
1228-
argstr='--nokspace', desc='do not use k-space forward warping')
1220+
1221+
# Special outputs: fieldmap (fmap)
1222+
save_fmap = traits.Bool(False, desc='write field map volume')
1223+
fmap_out_file = File(argstr='--savefmap=%s', desc='filename for saving fieldmap (rad/s)')
1224+
save_unmasked_fmap = traits.Bool(False, argstr='--unmaskfmap', xor=['save_fmap'],
1225+
desc='saves the unmasked fieldmap when using --savefmap')
1226+
1227+
12291228

12301229

12311230
class FUGUEOutputSpec(TraitedSpec):
@@ -1238,87 +1237,121 @@ class FUGUEOutputSpec(TraitedSpec):
12381237
class FUGUE(FSLCommand):
12391238
"""Use FSL FUGUE to unwarp epi's with fieldmaps
12401239
1241-
Examples
1242-
--------
1240+
Example (unwarping an input image, the shift map is known) ::
12431241
12441242
>>> from nipype.interfaces.fsl.preprocess import FUGUE
12451243
>>> fugue = FUGUE()
1246-
>>> fugue.inputs.forward_warping = True
12471244
>>> fugue.inputs.in_file = 'epi.nii'
12481245
>>> fugue.inputs.mask_file = 'epi_mask.nii'
1249-
>>> fugue.inputs.shift_in_file = 'image.nii' # Previously computed with fugue as well
1246+
>>> fugue.inputs.shift_in_file = 'vsm.nii' # Previously computed with fugue as well
12501247
>>> fugue.inputs.unwarp_direction = 'y'
12511248
>>> fugue.cmdline #doctest: +ELLIPSIS
1252-
'fugue --in=epi.nii --mask=epi_mask.nii --loadshift=image.nii --unwarpdir=y --warp=.../epi_warped.nii.gz'
1249+
'fugue --in=epi.nii --mask=epi_mask.nii --loadshift=vsm.nii --unwarpdir=y --unwarp=.../epi_unwarped.nii.gz'
12531250
>>> fugue.run() #doctest: +SKIP
12541251
1255-
"""
12561252
1257-
_cmd = 'fugue'
1258-
input_spec = FUGUEInputSpec
1259-
output_spec = FUGUEOutputSpec
1253+
Example (warping an input image, shift map is known) ::
12601254
1261-
def __init__(self, **kwargs):
1262-
super(FUGUE, self).__init__(**kwargs)
1263-
warn(
1264-
'This interface has not been fully tested. Please report any failures.')
1265-
1266-
def _list_outputs(self):
1267-
outputs = self._outputs().get()
1255+
>>> from nipype.interfaces.fsl.preprocess import FUGUE
1256+
>>> fugue = FUGUE()
1257+
>>> fugue.inputs.forward_warping = True
1258+
>>> fugue.inputs.in_file = 'epi.nii'
1259+
>>> fugue.inputs.mask_file = 'epi_mask.nii'
1260+
>>> fugue.inputs.shift_in_file = 'vsm.nii' # Previously computed with fugue as well
1261+
>>> fugue.inputs.unwarp_direction = 'y'
1262+
>>> fugue.cmdline #doctest: +ELLIPSIS
1263+
'fugue --in=epi.nii --mask=epi_mask.nii --loadshift=vsm.nii --unwarpdir=y --warp=.../epi_warped.nii.gz'
1264+
>>> fugue.run() #doctest: +SKIP
12681265
1269-
if isdefined(self.inputs.forward_warping) and self.inputs.forward_warping:
1270-
out_field = 'warped_file'
1271-
self.inputs.unwarped_file = Undefined
1272-
outputs.pop('unwarped_file')
1273-
else:
1274-
out_field = 'unwarped_file'
1275-
outputs.pop('warped_file')
12761266
1277-
out_file = getattr(self.inputs, out_field)
1278-
if not isdefined(out_file):
1279-
if isdefined(self.inputs.in_file):
1280-
out_file = self._gen_fname(self.inputs.in_file,
1281-
suffix='_'+out_field[:-5])
1282-
if isdefined(out_file):
1283-
outputs[out_field] = os.path.abspath(out_file)
1284-
if isdefined(self.inputs.fmap_out_file):
1285-
outputs['fmap_out_file'] = os.path.abspath(
1286-
self.inputs.fmap_out_file)
1287-
if isdefined(self.inputs.shift_out_file):
1288-
outputs['shift_out_file'] = os.path.abspath(
1289-
self.inputs.shift_out_file)
1267+
Example (computing the vsm, unwrapped phase map is known) ::
12901268
1291-
return outputs
1269+
>>> from nipype.interfaces.fsl.preprocess import FUGUE
1270+
>>> fugue = FUGUE()
1271+
>>> fugue.inputs.phasemap_in_file = 'epi_phasediff.nii'
1272+
>>> fugue.inputs.mask_file = 'epi_mask.nii'
1273+
>>> fugue.inputs.dwell_to_asym_ratio = 0.77e-3 / 2.46e-3
1274+
>>> fugue.inputs.unwarp_direction = 'y'
1275+
>>> fugue.inputs.save_shift = True
1276+
>>> fugue.cmdline #doctest: +ELLIPSIS
1277+
'fugue --dwelltoasym=0.7000000000 --mask=epi_mask.nii --phasemap=epi_phasediff.nii --saveshift=.../epi_phasediff_vsm.nii.gz --unwarpdir=y'
1278+
>>> fugue.run() #doctest: +SKIP
12921279
1293-
def _gen_filename(self, name):
1294-
is_fwd = isdefined(self.inputs.forward_warping) and self.inputs.forward_warping
12951280
1296-
if (is_fwd and name=='warped_file') or (not is_fwd and name=='unwarped_file'):
1297-
return self._list_outputs()[name]
1281+
"""
12981282

1299-
return None
1283+
_cmd = 'fugue'
1284+
input_spec = FUGUEInputSpec
1285+
output_spec = FUGUEOutputSpec
13001286

13011287
def _parse_inputs(self, skip=None):
13021288
if skip is None:
13031289
skip = []
13041290

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

13121298
if not isdefined(self.inputs.in_file):
13131299
skip += ['unwarped_file', 'warped_file']
1314-
elif self.inputs.forward_warping:
1315-
if not isdefined(self.inputs.warped_file):
1316-
self.inputs.warped_file = self._gen_fname(
1317-
self.inputs.in_file, suffix='_warped')
1318-
elif not self.inputs.forward_warping:
1319-
if not isdefined(self.inputs.unwarped_file):
1320-
self.inputs.unwarped_file = self._gen_fname(
1321-
self.inputs.in_file, suffix='_unwarped')
1300+
else:
1301+
if self.inputs.forward_warping:
1302+
skip += ['unwarped_file']
1303+
trait_spec = self.inputs.trait('warped_file')
1304+
trait_spec.name_template = "%s_warped"
1305+
trait_spec.name_source = ['in_file']
1306+
trait_spec.output_name = 'warped_file'
1307+
else:
1308+
skip += ['warped_file']
1309+
trait_spec = self.inputs.trait('unwarped_file')
1310+
trait_spec.name_template = "%s_unwarped"
1311+
trait_spec.name_source = ['in_file']
1312+
trait_spec.output_name = 'unwarped_file'
1313+
1314+
# Handle shift output
1315+
vsm_save_masked = isdefined(self.inputs.save_shift) and self.inputs.save_shift
1316+
vsm_save_unmasked = isdefined(self.inputs.save_unmasked_shift) \
1317+
and self.inputs.save_unmasked_shift
1318+
1319+
if (vsm_save_masked or vsm_save_unmasked) and not isdefined(self.inputs.shift_out_file):
1320+
trait_spec = self.inputs.trait('shift_out_file')
1321+
trait_spec.output_name = 'shift_out_file'
1322+
1323+
if vsm_save_unmasked:
1324+
trait_spec.name_template = '%s_vsm_unmasked'
1325+
else:
1326+
trait_spec.name_template = '%s_vsm'
1327+
1328+
if input_fmap:
1329+
trait_spec.name_source = ['fmap_in_file']
1330+
elif input_phase:
1331+
trait_spec.name_source = ['phasemap_in_file']
1332+
else:
1333+
trait_spec.name_source = ['shift_in_file']
1334+
1335+
# Handle fieldmap output
1336+
fmap_save_masked = isdefined(self.inputs.save_fmap) and self.inputs.save_shift
1337+
fmap_save_unmasked = isdefined(self.inputs.save_unmasked_fmap) and \
1338+
self.inputs.save_unmasked_fmap
1339+
1340+
if (fmap_save_masked or fmap_save_unmasked) and not isdefined(self.inputs.fmap_out_file):
1341+
trait_spec = self.inputs.trait('fmap_out_file')
1342+
trait_spec.output_name = 'fmap_out_file'
1343+
1344+
if fmap_save_unmasked:
1345+
trait_spec.name_template = '%s_fieldmap_unmasked'
1346+
else:
1347+
trait_spec.name_template = '%s_fieldmap'
1348+
1349+
if input_vsm:
1350+
trait_spec.name_source = ['shift_in_file']
1351+
elif input_phase:
1352+
trait_spec.name_source = ['phasemap_in_file']
1353+
else:
1354+
trait_spec.name_source = ['fmap_in_file']
13221355

13231356
return super(FUGUE, self)._parse_inputs(skip=skip)
13241357

nipype/interfaces/fsl/tests/test_auto_FUGUE.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ 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
),
26-
forward_warping=dict(usedefault=True,
25+
forward_warping=dict(mandatory=True,
26+
usedefault=True,
2727
),
2828
fourier_order=dict(argstr='--fourier=%d',
2929
),
@@ -53,21 +53,21 @@ def test_FUGUE_inputs():
5353
),
5454
phase_conjugate=dict(argstr='--phaseconj',
5555
),
56-
phasemap_file=dict(argstr='--phasemap=%s',
56+
phasemap_in_file=dict(argstr='--phasemap=%s',
5757
),
5858
poly_order=dict(argstr='--poly=%d',
5959
),
60+
save_fmap=dict(),
6061
save_shift=dict(),
6162
save_unmasked_fmap=dict(argstr='--unmaskfmap',
62-
requires=['fmap_out_file'],
63+
xor=['save_fmap'],
6364
),
6465
save_unmasked_shift=dict(argstr='--unmaskshift',
65-
requires=['save_shift'],
66+
xor=['save_shift'],
6667
),
6768
shift_in_file=dict(argstr='--loadshift=%s',
6869
),
6970
shift_out_file=dict(argstr='--saveshift=%s',
70-
hash_files=False,
7171
),
7272
smooth2d=dict(argstr='--smooth2=%.2f',
7373
),
@@ -79,10 +79,10 @@ def test_FUGUE_inputs():
7979
unwarp_direction=dict(argstr='--unwarpdir=%s',
8080
),
8181
unwarped_file=dict(argstr='--unwarp=%s',
82-
hash_files=False,
82+
xor=['warped_file'],
8383
),
8484
warped_file=dict(argstr='--warp=%s',
85-
hash_files=False,
85+
xor=['unwarped_file'],
8686
),
8787
)
8888
inputs = FUGUE.input_spec()

nipype/testing/data/epi_phasediff.nii

Whitespace-only changes.

nipype/testing/data/vsm.nii

Whitespace-only changes.

0 commit comments

Comments
 (0)