Skip to content

Commit eab4b2a

Browse files
authored
Merge pull request #3394 from fabioboh/master
ENH: added Apply VDM functionality to FieldMap SPM interface
2 parents 20331fd + 4575810 commit eab4b2a

File tree

6 files changed

+211
-5
lines changed

6 files changed

+211
-5
lines changed

.zenodo.json

+5
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,11 @@
318318
"name": "Geisler, Daniel",
319319
"orcid": "0000-0003-2076-5329"
320320
},
321+
{
322+
"affiliation": "Division of Psychological and Social Medicine and Developmental Neuroscience, Faculty of Medicine, Technische Universit\u00e4t Dresden, Dresden, Germany",
323+
"name": "Bernardoni, Fabio",
324+
"orcid": "0000-0002-5112-405X"
325+
},
321326
{
322327
"name": "Salvatore, John"
323328
},

nipype/interfaces/spm/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""SPM is a software package for the analysis of brain imaging data sequences."""
55
from .base import Info, SPMCommand, logger, no_spm, scans_for_fname, scans_for_fnames
66
from .preprocess import (
7+
ApplyVDM,
78
FieldMap,
89
SliceTiming,
910
Realign,

nipype/interfaces/spm/preprocess.py

+133-5
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@
3939

4040

4141
class FieldMapInputSpec(SPMCommandInputSpec):
42+
4243
jobtype = traits.Enum(
4344
"calculatevdm",
44-
"applyvdm",
4545
usedefault=True,
46-
desc="one of: calculatevdm, applyvdm",
46+
deprecated="1.9.0", # Two minor releases in the future
47+
desc="Must be 'calculatevdm'; to apply VDM, use the ApplyVDM interface.",
4748
)
49+
4850
phase_file = File(
4951
mandatory=True,
5052
exists=True,
@@ -231,22 +233,148 @@ class FieldMap(SPMCommand):
231233

232234
def _format_arg(self, opt, spec, val):
233235
"""Convert input to appropriate format for spm"""
236+
234237
if opt in ["phase_file", "magnitude_file", "anat_file", "epi_file"]:
238+
235239
return scans_for_fname(ensure_list(val))
236240

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

239243
def _parse_inputs(self):
240244
"""validate spm fieldmap options if set to None ignore"""
245+
241246
einputs = super(FieldMap, self)._parse_inputs()
242-
return [{self.inputs.jobtype: einputs[0]}]
247+
return [{"calculatevdm": einputs[0]}]
243248

244249
def _list_outputs(self):
245250
outputs = self._outputs().get()
246251
jobtype = self.inputs.jobtype
247-
if jobtype == "calculatevdm":
248-
outputs["vdm"] = fname_presuffix(self.inputs.phase_file, prefix="vdm5_sc")
249252

253+
outputs["vdm"] = fname_presuffix(self.inputs.phase_file, prefix="vdm5_sc")
254+
255+
return outputs
256+
257+
258+
class ApplyVDMInputSpec(SPMCommandInputSpec):
259+
260+
in_files = InputMultiObject(
261+
ImageFileSPM(exists=True),
262+
field="data.scans",
263+
mandatory=True,
264+
copyfile=True,
265+
desc="list of filenames to apply the vdm to",
266+
)
267+
vdmfile = File(
268+
field="data.vdmfile",
269+
desc="Voxel displacement map to use",
270+
mandatory=True,
271+
copyfile=True,
272+
)
273+
distortion_direction = traits.Int(
274+
2,
275+
field="roptions.pedir",
276+
desc="phase encode direction input data have been acquired with",
277+
usedefault=True,
278+
)
279+
write_which = traits.ListInt(
280+
[2, 1],
281+
field="roptions.which",
282+
minlen=2,
283+
maxlen=2,
284+
usedefault=True,
285+
desc="If the first value is non-zero, reslice all images. If the second value is non-zero, reslice a mean image.",
286+
)
287+
interpolation = traits.Range(
288+
value=4,
289+
low=0,
290+
high=7,
291+
field="roptions.rinterp",
292+
desc="degree of b-spline used for interpolation",
293+
)
294+
write_wrap = traits.List(
295+
traits.Int(),
296+
minlen=3,
297+
maxlen=3,
298+
field="roptions.wrap",
299+
desc=("Check if interpolation should wrap in [x,y,z]"),
300+
)
301+
write_mask = traits.Bool(
302+
field="roptions.mask", desc="True/False mask time series images"
303+
)
304+
out_prefix = traits.String(
305+
"u",
306+
field="roptions.prefix",
307+
usedefault=True,
308+
desc="fieldmap corrected output prefix",
309+
)
310+
311+
312+
class ApplyVDMOutputSpec(TraitedSpec):
313+
out_files = OutputMultiPath(
314+
traits.Either(traits.List(File(exists=True)), File(exists=True)),
315+
desc=("These will be the fieldmap corrected files."),
316+
)
317+
mean_image = File(exists=True, desc="Mean image")
318+
319+
320+
class ApplyVDM(SPMCommand):
321+
"""Use the fieldmap toolbox from spm to apply the voxel displacement map (VDM) to some epi files.
322+
323+
http://www.fil.ion.ucl.ac.uk/spm/doc/manual.pdf#page=173
324+
325+
.. important::
326+
327+
This interface does not deal with real/imag magnitude images nor
328+
with the two phase files case.
329+
330+
"""
331+
332+
input_spec = ApplyVDMInputSpec
333+
output_spec = ApplyVDMOutputSpec
334+
_jobtype = "tools"
335+
_jobname = "fieldmap"
336+
337+
def _format_arg(self, opt, spec, val):
338+
"""Convert input to appropriate format for spm"""
339+
340+
if opt in ["in_files", "vdmfile"]:
341+
return scans_for_fname(ensure_list(val))
342+
return super(FieldMap, self)._format_arg(opt, spec, val)
343+
344+
def _parse_inputs(self):
345+
"""validate spm fieldmap options if set to None ignore"""
346+
347+
einputs = super(ApplyVDM, self)._parse_inputs()
348+
349+
return [{"applymap": einputs[0]}]
350+
351+
def _list_outputs(self):
352+
outputs = self._outputs().get()
353+
jobtype = self.inputs.jobtype
354+
resliced_all = self.inputs.write_which[0] > 0
355+
resliced_mean = self.inputs.write_which[1] > 0
356+
if resliced_mean:
357+
if isinstance(self.inputs.in_files[0], list):
358+
first_image = self.inputs.in_files[0][0]
359+
else:
360+
first_image = self.inputs.in_files[0]
361+
outputs["mean_image"] = fname_presuffix(first_image, prefix="meanu")
362+
363+
if resliced_all:
364+
outputs["out_files"] = []
365+
for idx, imgf in enumerate(ensure_list(self.inputs.in_files)):
366+
appliedvdm_run = []
367+
if isinstance(imgf, list):
368+
for i, inner_imgf in enumerate(ensure_list(imgf)):
369+
newfile = fname_presuffix(
370+
inner_imgf, prefix=self.inputs.out_prefix
371+
)
372+
appliedvdm_run.append(newfile)
373+
else:
374+
appliedvdm_run = fname_presuffix(
375+
imgf, prefix=self.inputs.out_prefix
376+
)
377+
outputs["out_files"].append(appliedvdm_run)
250378
return outputs
251379

252380

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
2+
from ..preprocess import ApplyVDM
3+
4+
5+
def test_ApplyVDM_inputs():
6+
input_map = dict(
7+
distortion_direction=dict(
8+
field="roptions.pedir",
9+
usedefault=True,
10+
),
11+
in_files=dict(
12+
copyfile=True,
13+
field="data.scans",
14+
mandatory=True,
15+
),
16+
interpolation=dict(
17+
field="roptions.rinterp",
18+
),
19+
matlab_cmd=dict(),
20+
mfile=dict(
21+
usedefault=True,
22+
),
23+
out_prefix=dict(
24+
field="roptions.prefix",
25+
usedefault=True,
26+
),
27+
paths=dict(),
28+
use_mcr=dict(),
29+
use_v8struct=dict(
30+
min_ver="8",
31+
usedefault=True,
32+
),
33+
vdmfile=dict(
34+
copyfile=True,
35+
extensions=None,
36+
field="data.vdmfile",
37+
mandatory=True,
38+
),
39+
write_mask=dict(
40+
field="roptions.mask",
41+
),
42+
write_which=dict(
43+
field="roptions.which",
44+
maxlen=2,
45+
minlen=2,
46+
usedefault=True,
47+
),
48+
write_wrap=dict(
49+
field="roptions.wrap",
50+
),
51+
)
52+
inputs = ApplyVDM.input_spec()
53+
54+
for key, metadata in list(input_map.items()):
55+
for metakey, value in list(metadata.items()):
56+
assert getattr(inputs.traits()[key], metakey) == value
57+
58+
59+
def test_ApplyVDM_outputs():
60+
output_map = dict(
61+
mean_image=dict(
62+
extensions=None,
63+
),
64+
out_files=dict(),
65+
)
66+
outputs = ApplyVDM.output_spec()
67+
68+
for key, metadata in list(output_map.items()):
69+
for metakey, value in list(metadata.items()):
70+
assert getattr(outputs.traits()[key], metakey) == value

nipype/interfaces/spm/tests/test_auto_FieldMap.py

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def test_FieldMap_inputs():
3232
usedefault=True,
3333
),
3434
jobtype=dict(
35+
deprecated="1.9.0",
3536
usedefault=True,
3637
),
3738
magnitude_file=dict(

nipype/sphinxext/plot_workflow.py

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
Provide a customized template for preparing restructured text.
107107
108108
"""
109+
109110
import sys
110111
import os
111112
import shutil

0 commit comments

Comments
 (0)