Skip to content

FUGUE interface refactoring #857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 29, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Next Release
============

* ENH: FUGUE interface has been refactored to use the name_template system, 3 examples
added to doctests, some bugs solved.
* ENH: Added new interfaces (fsl.utils.WarpUtils, ConvertWarp) to fnirtfileutils and convertwarp
* API: Interfaces to external packages are no longer available in the top-level
``nipype`` namespace, and must be imported directly (e.g.
Expand Down
239 changes: 156 additions & 83 deletions nipype/interfaces/fsl/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -1159,35 +1159,27 @@ def _gen_filename(self, name):
class FUGUEInputSpec(FSLCommandInputSpec):
in_file = File(exists=True, argstr='--in=%s',
desc='filename of input volume')
unwarped_file = File(
argstr='--unwarp=%s', genfile=True,
desc='apply unwarping and save as filename', hash_files=False)
forward_warping = traits.Bool(
False, usedefault=True,
desc='apply forward warping instead of unwarping')
warped_file = File(argstr='--warp=%s',
desc='apply forward warping and save as filename',
hash_files=False)
phasemap_file = File(exists=True, argstr='--phasemap=%s',
desc='filename for input phase image')
shift_in_file = File(exists=True, argstr='--loadshift=%s',
desc='filename for reading pixel shift volume')
phasemap_in_file = File(exists=True, argstr='--phasemap=%s',
desc='filename for input phase image')
fmap_in_file = File(exists=True, argstr='--loadfmap=%s',
desc='filename for loading fieldmap (rad/s)')
unwarped_file = File(argstr='--unwarp=%s', desc='apply unwarping and save as filename',
xor=['warped_file'], requires=['in_file'])
warped_file = File(argstr='--warp=%s', desc='apply forward warping and save as filename',
xor=['unwarped_file'], requires=['in_file'])

forward_warping = traits.Bool(False, usedefault=True,
desc='apply forward warping instead of unwarping')

dwell_to_asym_ratio = traits.Float(argstr='--dwelltoasym=%.10f',
desc='set the dwell to asym time ratio')
dwell_time = traits.Float(argstr='--dwell=%.10f',
desc='set the EPI dwell time per phase-encode line - same as echo spacing - (sec)')
desc=('set the EPI dwell time per phase-encode line - same as echo '
'spacing - (sec)'))
asym_se_time = traits.Float(argstr='--asym=%.10f',
desc='set the fieldmap asymmetric spin echo time (sec)')
fmap_out_file = File(argstr='--savefmap=%s',
desc='filename for saving fieldmap (rad/s)', hash_files=False)
fmap_in_file = File(exists=True, argstr='--loadfmap=%s',
desc='filename for loading fieldmap (rad/s)')

save_shift = traits.Bool(desc='output pixel shift volume')

shift_out_file = traits.File(argstr='--saveshift=%s',
desc='filename for saving pixel shift volume', hash_files=False)

shift_in_file = File(exists=True, argstr='--loadshift=%s',
desc='filename for reading pixel shift volume')
median_2dfilter = traits.Bool(argstr='--median',
desc='apply 2D median filtering')
despike_2dfilter = traits.Bool(argstr='--despike',
Expand Down Expand Up @@ -1217,16 +1209,24 @@ class FUGUEInputSpec(FSLCommandInputSpec):
desc='apply intensity correction to unwarping (pixel shift method only)')
icorr_only = traits.Bool(argstr='--icorronly', requires=['unwarped_file'],
desc='apply intensity correction only')
mask_file = File(exists=True, argstr='--mask=%s',
desc='filename for loading valid mask')
save_unmasked_fmap = traits.Bool(argstr='--unmaskfmap',
requires=['fmap_out_file'],
desc='saves the unmasked fieldmap when using --savefmap')
save_unmasked_shift = traits.Bool(argstr='--unmaskshift',
requires=['shift_out_file'],
mask_file = File(exists=True, argstr='--mask=%s', desc='filename for loading valid mask')
nokspace = traits.Bool(False, argstr='--nokspace', desc='do not use k-space forward warping')

# Special outputs: shift (voxel shift map, vsm)
save_shift = traits.Bool(False, xor=['save_unmasked_shift'],
desc='write pixel shift volume')
shift_out_file = File(argstr='--saveshift=%s', desc='filename for saving pixel shift volume')
save_unmasked_shift = traits.Bool(argstr='--unmaskshift', xor=['save_shift'],
desc='saves the unmasked shiftmap when using --saveshift')
nokspace = traits.Bool(
argstr='--nokspace', desc='do not use k-space forward warping')

# Special outputs: fieldmap (fmap)
save_fmap = traits.Bool(False, xor=['save_unmasked_fmap'],
desc='write field map volume')
fmap_out_file = File(argstr='--savefmap=%s', desc='filename for saving fieldmap (rad/s)')
save_unmasked_fmap = traits.Bool(False, argstr='--unmaskfmap', xor=['save_fmap'],
desc='saves the unmasked fieldmap when using --savefmap')




class FUGUEOutputSpec(TraitedSpec):
Expand All @@ -1237,75 +1237,148 @@ class FUGUEOutputSpec(TraitedSpec):


class FUGUE(FSLCommand):
"""Use FSL FUGUE to unwarp epi's with fieldmaps
"""
`FUGUE <http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FUGUE>`_ is, most generally, a set of tools for
EPI distortion correction.

Distortions may be corrected for
1. improving registration with non-distorted images (e.g. structurals), or
2. dealing with motion-dependent changes.

FUGUE is designed to deal only with the first case - improving registration.


Examples
--------

Please insert examples for use of this command

"""
Unwarping an input image (shift map is known) ::

_cmd = 'fugue'
input_spec = FUGUEInputSpec
output_spec = FUGUEOutputSpec
>>> from nipype.interfaces.fsl.preprocess import FUGUE
>>> fugue = FUGUE()
>>> fugue.inputs.in_file = 'epi.nii'
>>> fugue.inputs.mask_file = 'epi_mask.nii'
>>> fugue.inputs.shift_in_file = 'vsm.nii' # Previously computed with fugue as well
>>> fugue.inputs.unwarp_direction = 'y'
>>> fugue.cmdline #doctest: +ELLIPSIS
'fugue --in=epi.nii --mask=epi_mask.nii --loadshift=vsm.nii --unwarpdir=y --unwarp=epi_unwarped.nii.gz'
>>> fugue.run() #doctest: +SKIP

def __init__(self, **kwargs):
super(FUGUE, self).__init__(**kwargs)
warn(
'This interface has not been fully tested. Please report any failures.')

def _list_outputs(self):
outputs = self._outputs().get()
if self.inputs.forward_warping:
out_field = 'warped_file'
else:
out_field = 'unwarped_file'
Warping an input image (shift map is known) ::

out_file = getattr(self.inputs, out_field)
if not isdefined(out_file):
if isdefined(self.inputs.in_file):
out_file = self._gen_fname(self.inputs.in_file,
suffix='_'+out_field[:-5])
if isdefined(out_file):
outputs[out_field] = os.path.abspath(out_file)
if isdefined(self.inputs.fmap_out_file):
outputs['fmap_out_file'] = os.path.abspath(
self.inputs.fmap_out_file)
if isdefined(self.inputs.shift_out_file):
outputs['shift_out_file'] = os.path.abspath(
self.inputs.shift_out_file)
>>> from nipype.interfaces.fsl.preprocess import FUGUE
>>> fugue = FUGUE()
>>> fugue.inputs.in_file = 'epi.nii'
>>> fugue.inputs.forward_warping = True
>>> fugue.inputs.mask_file = 'epi_mask.nii'
>>> fugue.inputs.shift_in_file = 'vsm.nii' # Previously computed with fugue as well
>>> fugue.inputs.unwarp_direction = 'y'
>>> fugue.cmdline #doctest: +ELLIPSIS
'fugue --in=epi.nii --mask=epi_mask.nii --loadshift=vsm.nii --unwarpdir=y --warp=epi_warped.nii.gz'
>>> fugue.run() #doctest: +SKIP

return outputs

def _gen_filename(self, name):
if name == 'unwarped_file' and not self.inputs.forward_warping:
return self._list_outputs()['unwarped_file']
if name == 'warped_file' and self.inputs.forward_warping:
return self._list_outputs()['warped_file']
return None
Computing the vsm (unwrapped phase map is known) ::

>>> from nipype.interfaces.fsl.preprocess import FUGUE
>>> fugue = FUGUE()
>>> fugue.inputs.phasemap_in_file = 'epi_phasediff.nii'
>>> fugue.inputs.mask_file = 'epi_mask.nii'
>>> fugue.inputs.dwell_to_asym_ratio = (0.77e-3 * 3) / 2.46e-3
>>> fugue.inputs.unwarp_direction = 'y'
>>> fugue.inputs.save_shift = True
>>> fugue.cmdline #doctest: +ELLIPSIS
'fugue --dwelltoasym=0.9390243902 --mask=epi_mask.nii --phasemap=epi_phasediff.nii --saveshift=epi_phasediff_vsm.nii.gz --unwarpdir=y'
>>> fugue.run() #doctest: +SKIP


"""

_cmd = 'fugue'
input_spec = FUGUEInputSpec
output_spec = FUGUEOutputSpec

def _parse_inputs(self, skip=None):
if skip is None:
skip = []

if not isdefined(self.inputs.save_shift) or not self.inputs.save_shift:
skip += ['shift_out_file']
else:
if not isdefined(self.inputs.shift_out_file):
self.inputs.shift_out_file = self._gen_fname(
self.inputs.in_file, suffix='_vsm')
input_phase = isdefined(self.inputs.phasemap_in_file)
input_vsm = isdefined(self.inputs.shift_in_file)
input_fmap = isdefined(self.inputs.fmap_in_file)

if not input_phase and not input_vsm and not input_fmap:
raise RuntimeError('Either phasemap_in_file, shift_in_file or fmap_in_file must be set.')

if not isdefined(self.inputs.in_file):
skip += ['unwarped_file', 'warped_file']
elif self.inputs.forward_warping:
if not isdefined(self.inputs.warped_file):
self.inputs.warped_file = self._gen_fname(
self.inputs.in_file, suffix='_warped')
elif not self.inputs.forward_warping:
if not isdefined(self.inputs.unwarped_file):
self.inputs.unwarped_file = self._gen_fname(
self.inputs.in_file, suffix='_unwarped')
else:
if self.inputs.forward_warping:
skip += ['unwarped_file']
trait_spec = self.inputs.trait('warped_file')
trait_spec.name_template = "%s_warped"
trait_spec.name_source = 'in_file'
trait_spec.output_name = 'warped_file'
else:
skip += ['warped_file']
trait_spec = self.inputs.trait('unwarped_file')
trait_spec.name_template = "%s_unwarped"
trait_spec.name_source = 'in_file'
trait_spec.output_name = 'unwarped_file'

# Handle shift output
if not isdefined(self.inputs.shift_out_file):
vsm_save_masked = (isdefined(self.inputs.save_shift) and self.inputs.save_shift)
vsm_save_unmasked = (isdefined(self.inputs.save_unmasked_shift) and
self.inputs.save_unmasked_shift)

if (vsm_save_masked or vsm_save_unmasked):
trait_spec = self.inputs.trait('shift_out_file')
trait_spec.output_name = 'shift_out_file'

if input_fmap:
trait_spec.name_source = 'fmap_in_file'
elif input_phase:
trait_spec.name_source = 'phasemap_in_file'
elif input_vsm:
trait_spec.name_source = 'shift_in_file'
else:
raise RuntimeError(('Either phasemap_in_file, shift_in_file or '
'fmap_in_file must be set.'))

if vsm_save_unmasked:
trait_spec.name_template = '%s_vsm_unmasked'
else:
trait_spec.name_template = '%s_vsm'
else:
skip += ['save_shift', 'save_unmasked_shift', 'shift_out_file']

# Handle fieldmap output
if not isdefined(self.inputs.fmap_out_file):
fmap_save_masked = (isdefined(self.inputs.save_fmap) and self.inputs.save_fmap)
fmap_save_unmasked = (isdefined(self.inputs.save_unmasked_fmap) and
self.inputs.save_unmasked_fmap)

if (fmap_save_masked or fmap_save_unmasked):
trait_spec = self.inputs.trait('fmap_out_file')
trait_spec.output_name = 'fmap_out_file'

if input_vsm:
trait_spec.name_source = 'shift_in_file'
elif input_phase:
trait_spec.name_source = 'phasemap_in_file'
elif input_fmap:
trait_spec.name_source = 'fmap_in_file'
else:
raise RuntimeError(('Either phasemap_in_file, shift_in_file or '
'fmap_in_file must be set.'))

if fmap_save_unmasked:
trait_spec.name_template = '%s_fieldmap_unmasked'
else:
trait_spec.name_template = '%s_fieldmap'
else:
skip += ['save_fmap', 'save_unmasked_fmap', 'fmap_out_file']

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

Expand Down
20 changes: 11 additions & 9 deletions nipype/interfaces/fsl/tests/test_auto_FUGUE.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ def test_FUGUE_inputs():
fmap_in_file=dict(argstr='--loadfmap=%s',
),
fmap_out_file=dict(argstr='--savefmap=%s',
hash_files=False,
),
forward_warping=dict(usedefault=True,
),
Expand Down Expand Up @@ -53,21 +52,23 @@ def test_FUGUE_inputs():
),
phase_conjugate=dict(argstr='--phaseconj',
),
phasemap_file=dict(argstr='--phasemap=%s',
phasemap_in_file=dict(argstr='--phasemap=%s',
),
poly_order=dict(argstr='--poly=%d',
),
save_shift=dict(),
save_fmap=dict(xor=['save_unmasked_fmap'],
),
save_shift=dict(xor=['save_unmasked_shift'],
),
save_unmasked_fmap=dict(argstr='--unmaskfmap',
requires=['fmap_out_file'],
xor=['save_fmap'],
),
save_unmasked_shift=dict(argstr='--unmaskshift',
requires=['shift_out_file'],
xor=['save_shift'],
),
shift_in_file=dict(argstr='--loadshift=%s',
),
shift_out_file=dict(argstr='--saveshift=%s',
hash_files=False,
),
smooth2d=dict(argstr='--smooth2=%.2f',
),
Expand All @@ -79,11 +80,12 @@ def test_FUGUE_inputs():
unwarp_direction=dict(argstr='--unwarpdir=%s',
),
unwarped_file=dict(argstr='--unwarp=%s',
genfile=True,
hash_files=False,
requires=['in_file'],
xor=['warped_file'],
),
warped_file=dict(argstr='--warp=%s',
hash_files=False,
requires=['in_file'],
xor=['unwarped_file'],
),
)
inputs = FUGUE.input_spec()
Expand Down
Empty file.
Empty file added nipype/testing/data/vsm.nii
Empty file.
4 changes: 2 additions & 2 deletions nipype/workflows/dmri/fsl/epi.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ def fieldmap_correction(name='fieldmap_correction', nocheck=False):
,(inputnode, mask_mag, [('in_mask', 'mask_file' )])
,(select_mag, mask_mag, [('roi_file', 'in_file')])
,(mask_mag, fslprep, [('out_file', 'in_magnitude')])
,(fslprep, vsm, [('out_fieldmap', 'phasemap_file')])
,(fslprep, vsm, [('out_fieldmap', 'phasemap_in_file')])
,(inputnode, vsm, [('fieldmap_mag', 'in_file'),
('encoding_direction','unwarp_direction'),
(('te_diff', _ms2sec), 'asym_se_time'),
Expand Down Expand Up @@ -550,7 +550,7 @@ def create_epidewarp_pipeline(name='epidewarp', fieldmap_registration=False):
,(mask_mag_dil, prelude, [('out_file', 'mask_file')])
,(prelude, fill_phase, [('unwrapped_phase_file', 'in_file')])
,(inputnode, vsm, [('fieldmap_mag', 'in_file')])
,(fill_phase, vsm, [('out_file', 'phasemap_file')])
,(fill_phase, vsm, [('out_file', 'phasemap_in_file')])
,(inputnode, vsm, [(('te_diff', _ms2sec), 'asym_se_time'), ('vsm_sigma', 'smooth2d')])
,(dwell_time, vsm, [(('dwell_time', _ms2sec), 'dwell_time')])
,(mask_mag_dil, vsm, [('out_file', 'mask_file')])
Expand Down