Skip to content

[ENH/WIP] Add SPM Fieldmap Tool wrapper #1905

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 8 commits into from
Feb 20, 2018
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: 1 addition & 1 deletion nipype/interfaces/spm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from .base import (Info, SPMCommand, logger, no_spm, scans_for_fname,
scans_for_fnames)
from .preprocess import (SliceTiming, Realign, Coregister, Normalize,
from .preprocess import (FieldMap, SliceTiming, Realign, Coregister, Normalize,
Normalize12, Segment, Smooth, NewSegment, DARTEL,
DARTELNorm2MNI, CreateWarped, VBMSegment)
from .model import (Level1Design, EstimateModel, EstimateContrast, Threshold,
Expand Down
2 changes: 2 additions & 0 deletions nipype/interfaces/spm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ def _format_arg(self, opt, spec, val):
"""Convert input to appropriate format for SPM."""
if spec.is_trait_type(traits.Bool):
return int(val)
elif spec.is_trait_type(traits.Tuple):
return list(val)
else:
return val

Expand Down
143 changes: 139 additions & 4 deletions nipype/interfaces/spm/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,149 @@
# Local imports
from ...utils.filemanip import (fname_presuffix, filename_to_list,
list_to_filename, split_filename)
from ..base import (OutputMultiPath, TraitedSpec, isdefined, traits,
InputMultiPath, File)
from .base import (SPMCommand, scans_for_fname, func_is_3d, scans_for_fnames,
SPMCommandInputSpec, ImageFileSPM)
from ..base import (OutputMultiPath, TraitedSpec, isdefined,
traits, InputMultiPath, File, Str)
from .base import (SPMCommand, scans_for_fname, func_is_3d,
scans_for_fnames, SPMCommandInputSpec, ImageFileSPM)

__docformat__ = 'restructuredtext'


class FieldMapInputSpec(SPMCommandInputSpec):
jobtype = traits.Enum('calculatevdm', 'applyvdm', usedefault=True,
desc='one of: calculatevdm, applyvdm')
phase_file = File(mandatory=True, exists=True, copyfile=False,
field='subj.data.presubphasemag.phase',
desc='presubstracted phase file')
magnitude_file = File(mandatory=True, exists=True, copyfile=False,
field='subj.data.presubphasemag.magnitude',
desc='presubstracted magnitude file')
echo_times = traits.Tuple(traits.Float, traits.Float, mandatory=True,
field='subj.defaults.defaultsval.et',
desc='short and long echo times')
maskbrain = traits.Bool(True, usedefault=True,
field='subj.defaults.defaultsval.maskbrain',
desc='masking or no masking of the brain')
blip_direction = traits.Enum(1, -1, mandatory=True,
field='subj.defaults.defaultsval.blipdir',
desc='polarity of the phase-encode blips')
total_readout_time = traits.Float(mandatory=True,
field='subj.defaults.defaultsval.tert',
desc='total EPI readout time')
epifm = traits.Bool(False, usedefault=True,
field='subj.defaults.defaultsval.epifm',
desc='epi-based field map');
jacobian_modulation = traits.Bool(False, usedefault=True,
field='subj.defaults.defaultsval.ajm',
desc='jacobian modulation');
# Unwarping defaults parameters
method = traits.Enum('Mark3D', 'Mark2D', 'Huttonish', usedefault=True,
desc='One of: Mark3D, Mark2D, Huttonish',
field='subj.defaults.defaultsval.uflags.method');
unwarp_fwhm = traits.Range(low=0, value=10, usedefault=True,
field='subj.defaults.defaultsval.uflags.fwhm',
desc='gaussian smoothing kernel width');
pad = traits.Range(low=0, value=0, usedefault=True,
field='subj.defaults.defaultsval.uflags.pad',
desc='padding kernel width');
ws = traits.Bool(True, usedefault=True,
field='subj.defaults.defaultsval.uflags.ws',
desc='weighted smoothing');
# Brain mask defaults parameters
template = File(copyfile=False, exists=True,
field='subj.defaults.defaultsval.mflags.template',
desc='template image for brain masking');
mask_fwhm = traits.Range(low=0, value=5, usedefault=True,
field='subj.defaults.defaultsval.mflags.fwhm',
desc='gaussian smoothing kernel width');
nerode = traits.Range(low=0, value=2, usedefault=True,
field='subj.defaults.defaultsval.mflags.nerode',
desc='number of erosions');
ndilate = traits.Range(low=0, value=4, usedefault=True,
field='subj.defaults.defaultsval.mflags.ndilate',
desc='number of erosions');
thresh = traits.Float(0.5, usedefault=True,
field='subj.defaults.defaultsval.mflags.thresh',
desc='threshold used to create brain mask from segmented data');
reg = traits.Float(0.02, usedefault=True,
field='subj.defaults.defaultsval.mflags.reg',
desc='regularization value used in the segmentation');
# EPI unwarping for quality check
epi_file = File(copyfile=False, exists=True, mandatory=True,
field='subj.session.epi',
desc='EPI to unwarp');
matchvdm = traits.Bool(True, usedefault=True,
field='subj.matchvdm',
desc='match VDM to EPI');
sessname = Str('_run-', usedefault=True,
field='subj.sessname',
desc='VDM filename extension');
writeunwarped = traits.Bool(False, usedefault=True,
field='subj.writeunwarped',
desc='write unwarped EPI');
anat_file = File(copyfile=False, exists=True,
field='subj.anat',
desc='anatomical image for comparison');
matchanat = traits.Bool(True, usedefault=True,
field='subj.matchanat',
desc='match anatomical image to EPI');


class FieldMapOutputSpec(TraitedSpec):
vdm = File(exists=True, desc='voxel difference map')


class FieldMap(SPMCommand):
"""Use the fieldmap toolbox from spm to calculate the voxel displacement map (VDM).

http://www.fil.ion.ucl.ac.uk/spm/doc/manual.pdf#page=173

To do
-----
Deal with real/imag magnitude images and with the two phase files case.

Examples
--------
>>> from nipype.interfaces.spm import FieldMap
>>> fm = FieldMap()
>>> fm.inputs.phase_file = 'phase.nii'
>>> fm.inputs.magnitude_file = 'magnitude.nii'
>>> fm.inputs.echo_times = (5.19, 7.65)
>>> fm.inputs.blip_direction = 1
>>> fm.inputs.total_readout_time = 15.6
>>> fm.inputs.epi_file = 'epi.nii'
>>> fm.run() # doctest: +SKIP

"""

input_spec = FieldMapInputSpec
output_spec = FieldMapOutputSpec
_jobtype = 'tools'
_jobname = 'fieldmap'

def _format_arg(self, opt, spec, val):
"""Convert input to appropriate format for spm
"""
if opt in ['phase_file', 'magnitude_file', 'anat_file', 'epi_file']:
return scans_for_fname(filename_to_list(val))

return super(FieldMap, self)._format_arg(opt, spec, val)

def _parse_inputs(self):
"""validate spm fieldmap options if set to None ignore
"""
einputs = super(FieldMap, self)._parse_inputs()
return [{self.inputs.jobtype: einputs[0]}]

def _list_outputs(self):
outputs = self._outputs().get()
jobtype = self.inputs.jobtype
if jobtype == "calculatevdm":
outputs['vdm'] = fname_presuffix(self.inputs.phase_file, prefix='vdm5_sc')

return outputs


class SliceTimingInputSpec(SPMCommandInputSpec):
in_files = InputMultiPath(
traits.Either(
Expand Down
133 changes: 133 additions & 0 deletions nipype/interfaces/spm/tests/test_auto_FieldMap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
from __future__ import unicode_literals
from ..preprocess import FieldMap


def test_FieldMap_inputs():
input_map = dict(
anat_file=dict(
copyfile=False,
field='subj.anat',
),
blip_direction=dict(
field='subj.defaults.defaultsval.blipdir',
mandatory=True,
),
echo_times=dict(
field='subj.defaults.defaultsval.et',
mandatory=True,
),
epi_file=dict(
copyfile=False,
field='subj.session.epi',
mandatory=True,
),
epifm=dict(
field='subj.defaults.defaultsval.epifm',
usedefault=True,
),
ignore_exception=dict(
deprecated='1.0.0',
nohash=True,
usedefault=True,
),
jacobian_modulation=dict(
field='subj.defaults.defaultsval.ajm',
usedefault=True,
),
jobtype=dict(usedefault=True, ),
magnitude_file=dict(
copyfile=False,
field='subj.data.presubphasemag.magnitude',
mandatory=True,
),
mask_fwhm=dict(
field='subj.defaults.defaultsval.mflags.fwhm',
usedefault=True,
),
maskbrain=dict(
field='subj.defaults.defaultsval.maskbrain',
usedefault=True,
),
matchanat=dict(
field='subj.matchanat',
usedefault=True,
),
matchvdm=dict(
field='subj.matchvdm',
usedefault=True,
),
matlab_cmd=dict(),
method=dict(
field='subj.defaults.defaultsval.uflags.method',
usedefault=True,
),
mfile=dict(usedefault=True, ),
ndilate=dict(
field='subj.defaults.defaultsval.mflags.ndilate',
usedefault=True,
),
nerode=dict(
field='subj.defaults.defaultsval.mflags.nerode',
usedefault=True,
),
pad=dict(
field='subj.defaults.defaultsval.uflags.pad',
usedefault=True,
),
paths=dict(),
phase_file=dict(
copyfile=False,
field='subj.data.presubphasemag.phase',
mandatory=True,
),
reg=dict(
field='subj.defaults.defaultsval.mflags.reg',
usedefault=True,
),
sessname=dict(
field='subj.sessname',
usedefault=True,
),
template=dict(
copyfile=False,
field='subj.defaults.defaultsval.mflags.template',
),
thresh=dict(
field='subj.defaults.defaultsval.mflags.thresh',
usedefault=True,
),
total_readout_time=dict(
field='subj.defaults.defaultsval.tert',
mandatory=True,
),
unwarp_fwhm=dict(
field='subj.defaults.defaultsval.uflags.fwhm',
usedefault=True,
),
use_mcr=dict(),
use_v8struct=dict(
min_ver='8',
usedefault=True,
),
writeunwarped=dict(
field='subj.writeunwarped',
usedefault=True,
),
ws=dict(
field='subj.defaults.defaultsval.uflags.ws',
usedefault=True,
),
)
inputs = FieldMap.input_spec()

for key, metadata in list(input_map.items()):
for metakey, value in list(metadata.items()):
assert getattr(inputs.traits()[key], metakey) == value
def test_FieldMap_outputs():
output_map = dict(vdm=dict(), )
outputs = FieldMap.output_spec()

for key, metadata in list(output_map.items()):
for metakey, value in list(metadata.items()):
assert getattr(outputs.traits()[key], metakey) == value
1 change: 1 addition & 0 deletions nipype/interfaces/tests/test_auto_DataGrabber.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
def test_DataGrabber_inputs():
input_map = dict(
base_directory=dict(),
drop_blank_outputs=dict(usedefault=True, ),
ignore_exception=dict(
deprecated='1.0.0',
nohash=True,
Expand Down
1 change: 1 addition & 0 deletions nipype/interfaces/tests/test_auto_SSHDataGrabber.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def test_SSHDataGrabber_inputs():
input_map = dict(
base_directory=dict(mandatory=True, ),
download_files=dict(usedefault=True, ),
drop_blank_outputs=dict(usedefault=True, ),
hostname=dict(mandatory=True, ),
ignore_exception=dict(
deprecated='1.0.0',
Expand Down