diff --git a/.zenodo.json b/.zenodo.json index 0b2d0f65fb..e152cb308f 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -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" }, diff --git a/nipype/interfaces/spm/__init__.py b/nipype/interfaces/spm/__init__.py index 817b62a9e1..1823bef4da 100644 --- a/nipype/interfaces/spm/__init__.py +++ b/nipype/interfaces/spm/__init__.py @@ -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, diff --git a/nipype/interfaces/spm/preprocess.py b/nipype/interfaces/spm/preprocess.py index 5dc2a8fa3e..4b245f70bb 100644 --- a/nipype/interfaces/spm/preprocess.py +++ b/nipype/interfaces/spm/preprocess.py @@ -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, @@ -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 diff --git a/nipype/interfaces/spm/tests/test_auto_ApplyVDM.py b/nipype/interfaces/spm/tests/test_auto_ApplyVDM.py new file mode 100644 index 0000000000..2f56b49ef2 --- /dev/null +++ b/nipype/interfaces/spm/tests/test_auto_ApplyVDM.py @@ -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 diff --git a/nipype/interfaces/spm/tests/test_auto_FieldMap.py b/nipype/interfaces/spm/tests/test_auto_FieldMap.py index a91eec64d8..ccd9e70c6e 100644 --- a/nipype/interfaces/spm/tests/test_auto_FieldMap.py +++ b/nipype/interfaces/spm/tests/test_auto_FieldMap.py @@ -32,6 +32,7 @@ def test_FieldMap_inputs(): usedefault=True, ), jobtype=dict( + deprecated="1.9.0", usedefault=True, ), magnitude_file=dict( diff --git a/nipype/sphinxext/plot_workflow.py b/nipype/sphinxext/plot_workflow.py index 95b87da157..a83b69f55c 100644 --- a/nipype/sphinxext/plot_workflow.py +++ b/nipype/sphinxext/plot_workflow.py @@ -106,6 +106,7 @@ Provide a customized template for preparing restructured text. """ + import sys import os import shutil