Skip to content

ENH: added Apply VDM functionality to FieldMap SPM interface #3394

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
Oct 21, 2021
Merged
5 changes: 5 additions & 0 deletions .zenodo.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@
"name": "Geisler, Daniel",
"orcid": "0000-0003-2076-5329"
},
{
"affiliation": "Division of Psychological and Social Medicine and Developmental Neuroscience, Faculty of Medicine, Technische Universit\u00e4t Dresden, Dresden, Germany",
"name": "Bernardoni, Fabio",
"orcid": "0000-0002-5112-405X"
},
{
"name": "Salvatore, John"
},
Expand Down
1 change: 1 addition & 0 deletions nipype/interfaces/spm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""SPM is a software package for the analysis of brain imaging data sequences."""
from .base import Info, SPMCommand, logger, no_spm, scans_for_fname, scans_for_fnames
from .preprocess import (
ApplyVDM,
FieldMap,
SliceTiming,
Realign,
Expand Down
138 changes: 133 additions & 5 deletions nipype/interfaces/spm/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@


class FieldMapInputSpec(SPMCommandInputSpec):

jobtype = traits.Enum(
"calculatevdm",
"applyvdm",
usedefault=True,
desc="one of: calculatevdm, applyvdm",
deprecated="1.9.0", # Two minor releases in the future
desc="Must be 'calculatevdm'; to apply VDM, use the ApplyVDM interface.",
)

phase_file = File(
mandatory=True,
exists=True,
Expand Down Expand Up @@ -231,22 +233,148 @@ class FieldMap(SPMCommand):

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(ensure_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]}]
return [{"calculatevdm": 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")

outputs["vdm"] = fname_presuffix(self.inputs.phase_file, prefix="vdm5_sc")

return outputs


class ApplyVDMInputSpec(SPMCommandInputSpec):

in_files = InputMultiObject(
ImageFileSPM(exists=True),
field="data.scans",
mandatory=True,
copyfile=True,
desc="list of filenames to apply the vdm to",
)
vdmfile = File(
field="data.vdmfile",
desc="Voxel displacement map to use",
mandatory=True,
copyfile=True,
)
distortion_direction = traits.Int(
2,
field="roptions.pedir",
desc="phase encode direction input data have been acquired with",
usedefault=True,
)
write_which = traits.ListInt(
[2, 1],
field="roptions.which",
minlen=2,
maxlen=2,
usedefault=True,
desc="If the first value is non-zero, reslice all images. If the second value is non-zero, reslice a mean image.",
)
interpolation = traits.Range(
value=4,
low=0,
high=7,
field="roptions.rinterp",
desc="degree of b-spline used for interpolation",
)
write_wrap = traits.List(
traits.Int(),
minlen=3,
maxlen=3,
field="roptions.wrap",
desc=("Check if interpolation should wrap in [x,y,z]"),
)
write_mask = traits.Bool(
field="roptions.mask", desc="True/False mask time series images"
)
out_prefix = traits.String(
"u",
field="roptions.prefix",
usedefault=True,
desc="fieldmap corrected output prefix",
)


class ApplyVDMOutputSpec(TraitedSpec):
out_files = OutputMultiPath(
traits.Either(traits.List(File(exists=True)), File(exists=True)),
desc=("These will be the fieldmap corrected files."),
)
mean_image = File(exists=True, desc="Mean image")


class ApplyVDM(SPMCommand):
"""Use the fieldmap toolbox from spm to apply the voxel displacement map (VDM) to some epi files.

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

.. important::

This interface does not deal with real/imag magnitude images nor
with the two phase files case.

"""

input_spec = ApplyVDMInputSpec
output_spec = ApplyVDMOutputSpec
_jobtype = "tools"
_jobname = "fieldmap"

def _format_arg(self, opt, spec, val):
"""Convert input to appropriate format for spm"""

if opt in ["in_files", "vdmfile"]:
return scans_for_fname(ensure_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(ApplyVDM, self)._parse_inputs()

return [{"applymap": einputs[0]}]

def _list_outputs(self):
outputs = self._outputs().get()
jobtype = self.inputs.jobtype
resliced_all = self.inputs.write_which[0] > 0
resliced_mean = self.inputs.write_which[1] > 0
if resliced_mean:
if isinstance(self.inputs.in_files[0], list):
first_image = self.inputs.in_files[0][0]
else:
first_image = self.inputs.in_files[0]
outputs["mean_image"] = fname_presuffix(first_image, prefix="meanu")

if resliced_all:
outputs["out_files"] = []
for idx, imgf in enumerate(ensure_list(self.inputs.in_files)):
appliedvdm_run = []
if isinstance(imgf, list):
for i, inner_imgf in enumerate(ensure_list(imgf)):
newfile = fname_presuffix(
inner_imgf, prefix=self.inputs.out_prefix
)
appliedvdm_run.append(newfile)
else:
appliedvdm_run = fname_presuffix(
imgf, prefix=self.inputs.out_prefix
)
outputs["out_files"].append(appliedvdm_run)
return outputs


Expand Down
70 changes: 70 additions & 0 deletions nipype/interfaces/spm/tests/test_auto_ApplyVDM.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
from ..preprocess import ApplyVDM


def test_ApplyVDM_inputs():
input_map = dict(
distortion_direction=dict(
field="roptions.pedir",
usedefault=True,
),
in_files=dict(
copyfile=True,
field="data.scans",
mandatory=True,
),
interpolation=dict(
field="roptions.rinterp",
),
matlab_cmd=dict(),
mfile=dict(
usedefault=True,
),
out_prefix=dict(
field="roptions.prefix",
usedefault=True,
),
paths=dict(),
use_mcr=dict(),
use_v8struct=dict(
min_ver="8",
usedefault=True,
),
vdmfile=dict(
copyfile=True,
extensions=None,
field="data.vdmfile",
mandatory=True,
),
write_mask=dict(
field="roptions.mask",
),
write_which=dict(
field="roptions.which",
maxlen=2,
minlen=2,
usedefault=True,
),
write_wrap=dict(
field="roptions.wrap",
),
)
inputs = ApplyVDM.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_ApplyVDM_outputs():
output_map = dict(
mean_image=dict(
extensions=None,
),
out_files=dict(),
)
outputs = ApplyVDM.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/spm/tests/test_auto_FieldMap.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def test_FieldMap_inputs():
usedefault=True,
),
jobtype=dict(
deprecated="1.9.0",
usedefault=True,
),
magnitude_file=dict(
Expand Down
1 change: 1 addition & 0 deletions nipype/sphinxext/plot_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
Provide a customized template for preparing restructured text.

"""

import sys
import os
import shutil
Expand Down