Skip to content

Commit 04cfa7a

Browse files
authored
Merge pull request #2608 from effigies/enh/tshift_pattern
ENH: Allow BIDS-style slice timings to be passed directly to TShift
2 parents 7fa55ae + afecc94 commit 04cfa7a

File tree

3 files changed

+124
-9
lines changed

3 files changed

+124
-9
lines changed

nipype/interfaces/afni/preprocess.py

+111-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
AFNICommandOutputSpec, AFNIPythonCommandInputSpec,
2121
AFNIPythonCommand, Info, no_afni)
2222

23+
from ...import logging
24+
iflogger = logging.getLogger('nipype.interface')
25+
2326

2427
class CentralityInputSpec(AFNICommandInputSpec):
2528
"""Common input spec class for all centrality-related commands
@@ -2565,9 +2568,10 @@ class TProject(AFNICommand):
25652568
output_spec = AFNICommandOutputSpec
25662569

25672570

2571+
25682572
class TShiftInputSpec(AFNICommandInputSpec):
25692573
in_file = File(
2570-
desc='input file to 3dTShift',
2574+
desc='input file to 3dTshift',
25712575
argstr='%s',
25722576
position=-1,
25732577
mandatory=True,
@@ -2594,12 +2598,26 @@ class TShiftInputSpec(AFNICommandInputSpec):
25942598
desc='ignore the first set of points specified', argstr='-ignore %s')
25952599
interp = traits.Enum(
25962600
('Fourier', 'linear', 'cubic', 'quintic', 'heptic'),
2597-
desc='different interpolation methods (see 3dTShift for details) '
2601+
desc='different interpolation methods (see 3dTshift for details) '
25982602
'default = Fourier',
25992603
argstr='-%s')
2600-
tpattern = Str(
2604+
tpattern = traits.Either(
2605+
traits.Enum('alt+z', 'altplus', # Synonyms
2606+
'alt+z2',
2607+
'alt-z', 'altminus', # Synonyms
2608+
'alt-z2',
2609+
'seq+z', 'seqplus', # Synonyms
2610+
'seq-z', 'seqminus'), # Synonyms
2611+
Str, # For backwards compatibility
26012612
desc='use specified slice time pattern rather than one in header',
2602-
argstr='-tpattern %s')
2613+
argstr='-tpattern %s',
2614+
xor=['slice_timing'])
2615+
slice_timing = traits.Either(
2616+
File(exists=True),
2617+
traits.List(traits.Float),
2618+
desc='time offsets from the volume acquisition onset for each slice',
2619+
argstr='-tpattern @%s',
2620+
xor=['tpattern'])
26032621
rlt = traits.Bool(
26042622
desc='Before shifting, remove the mean and linear trend',
26052623
argstr='-rlt')
@@ -2609,6 +2627,10 @@ class TShiftInputSpec(AFNICommandInputSpec):
26092627
argstr='-rlt+')
26102628

26112629

2630+
class TShiftOutputSpec(AFNICommandOutputSpec):
2631+
timing_file = File(desc="AFNI formatted timing file, if ``slice_timing`` is a list")
2632+
2633+
26122634
class TShift(AFNICommand):
26132635
"""Shifts voxel time series from input so that seperate slices are aligned
26142636
to the same temporal origin.
@@ -2619,19 +2641,101 @@ class TShift(AFNICommand):
26192641
Examples
26202642
========
26212643
2644+
Slice timing details may be specified explicitly via the ``slice_timing``
2645+
input:
2646+
26222647
>>> from nipype.interfaces import afni
2648+
>>> TR = 2.5
2649+
>>> tshift = afni.TShift()
2650+
>>> tshift.inputs.in_file = 'functional.nii'
2651+
>>> tshift.inputs.tzero = 0.0
2652+
>>> tshift.inputs.tr = '%.1fs' % TR
2653+
>>> tshift.inputs.slice_timing = list(np.arange(40) / TR)
2654+
>>> tshift.cmdline
2655+
'3dTshift -prefix functional_tshift -tpattern @slice_timing.1D -TR 2.5s -tzero 0.0 functional.nii'
2656+
2657+
When the ``slice_timing`` input is used, the ``timing_file`` output is populated,
2658+
in this case with the generated file.
2659+
2660+
>>> tshift._list_outputs()['timing_file'] # doctest: +ELLIPSIS
2661+
'.../slice_timing.1D'
2662+
2663+
This method creates a ``slice_timing.1D`` file to be passed to ``3dTshift``.
2664+
A pre-existing slice-timing file may be used in the same way:
2665+
2666+
>>> tshift = afni.TShift()
2667+
>>> tshift.inputs.in_file = 'functional.nii'
2668+
>>> tshift.inputs.tzero = 0.0
2669+
>>> tshift.inputs.tr = '%.1fs' % TR
2670+
>>> tshift.inputs.slice_timing = 'slice_timing.1D'
2671+
>>> tshift.cmdline
2672+
'3dTshift -prefix functional_tshift -tpattern @slice_timing.1D -TR 2.5s -tzero 0.0 functional.nii'
2673+
2674+
When a pre-existing file is provided, ``timing_file`` is simply passed through.
2675+
2676+
>>> tshift._list_outputs()['timing_file'] # doctest: +ELLIPSIS
2677+
'.../slice_timing.1D'
2678+
2679+
Alternatively, pre-specified slice timing patterns may be specified with the
2680+
``tpattern`` input.
2681+
For example, to specify an alternating, ascending slice timing pattern:
2682+
26232683
>>> tshift = afni.TShift()
26242684
>>> tshift.inputs.in_file = 'functional.nii'
2685+
>>> tshift.inputs.tzero = 0.0
2686+
>>> tshift.inputs.tr = '%.1fs' % TR
26252687
>>> tshift.inputs.tpattern = 'alt+z'
2688+
>>> tshift.cmdline
2689+
'3dTshift -prefix functional_tshift -tpattern alt+z -TR 2.5s -tzero 0.0 functional.nii'
2690+
2691+
For backwards compatibility, ``tpattern`` may also take filenames prefixed
2692+
with ``@``.
2693+
However, in this case, filenames are not validated, so this usage will be
2694+
deprecated in future versions of Nipype.
2695+
2696+
>>> tshift = afni.TShift()
2697+
>>> tshift.inputs.in_file = 'functional.nii'
26262698
>>> tshift.inputs.tzero = 0.0
2699+
>>> tshift.inputs.tr = '%.1fs' % TR
2700+
>>> tshift.inputs.tpattern = '@slice_timing.1D'
26272701
>>> tshift.cmdline
2628-
'3dTshift -prefix functional_tshift -tpattern alt+z -tzero 0.0 functional.nii'
2629-
>>> res = tshift.run() # doctest: +SKIP
2702+
'3dTshift -prefix functional_tshift -tpattern @slice_timing.1D -TR 2.5s -tzero 0.0 functional.nii'
2703+
2704+
In these cases, ``timing_file`` is undefined.
26302705
2706+
>>> tshift._list_outputs()['timing_file'] # doctest: +ELLIPSIS
2707+
<undefined>
2708+
2709+
In any configuration, the interface may be run as usual:
2710+
2711+
>>> res = tshift.run() # doctest: +SKIP
26312712
"""
26322713
_cmd = '3dTshift'
26332714
input_spec = TShiftInputSpec
2634-
output_spec = AFNICommandOutputSpec
2715+
output_spec = TShiftOutputSpec
2716+
2717+
def _format_arg(self, name, trait_spec, value):
2718+
if name == 'tpattern' and value.startswith('@'):
2719+
iflogger.warning('Passing a file prefixed by "@" will be deprecated'
2720+
'; please use the `slice_timing` input')
2721+
elif name == 'slice_timing' and isinstance(value, list):
2722+
value = self._write_slice_timing()
2723+
return super(TShift, self)._format_arg(name, trait_spec, value)
2724+
2725+
def _write_slice_timing(self):
2726+
fname = 'slice_timing.1D'
2727+
with open(fname, 'w') as fobj:
2728+
fobj.write('\t'.join(map(str, self.inputs.slice_timing)))
2729+
return fname
2730+
2731+
def _list_outputs(self):
2732+
outputs = super(TShift, self)._list_outputs()
2733+
if isdefined(self.inputs.slice_timing):
2734+
if isinstance(self.inputs.slice_timing, list):
2735+
outputs['timing_file'] = os.path.abspath('slice_timing.1D')
2736+
else:
2737+
outputs['timing_file'] = os.path.abspath(self.inputs.slice_timing)
2738+
return outputs
26352739

26362740

26372741
class VolregInputSpec(AFNICommandInputSpec):

nipype/interfaces/afni/tests/test_auto_TShift.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ def test_TShift_inputs():
3030
outputtype=dict(),
3131
rlt=dict(argstr='-rlt', ),
3232
rltplus=dict(argstr='-rlt+', ),
33-
tpattern=dict(argstr='-tpattern %s', ),
33+
slice_timing=dict(
34+
argstr='-tpattern @%s',
35+
xor=['tpattern'],
36+
),
37+
tpattern=dict(
38+
argstr='-tpattern %s',
39+
xor=['slice_timing'],
40+
),
3441
tr=dict(argstr='-TR %s', ),
3542
tslice=dict(
3643
argstr='-slice %s',
@@ -47,7 +54,10 @@ def test_TShift_inputs():
4754
for metakey, value in list(metadata.items()):
4855
assert getattr(inputs.traits()[key], metakey) == value
4956
def test_TShift_outputs():
50-
output_map = dict(out_file=dict(), )
57+
output_map = dict(
58+
out_file=dict(),
59+
timing_file=dict(),
60+
)
5161
outputs = TShift.output_spec()
5262

5363
for key, metadata in list(output_map.items()):

nipype/testing/data/slice_timing.1D

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.0 0.4 0.8 1.2 1.6 2.0 2.4 2.8 3.2 3.6 4.0 4.4 4.8 5.2 5.6 6.0 6.4 6.8 7.2 7.6 8.0 8.4 8.8 9.2 9.6 10.0 10.4 10.8 11.2 11.6 12.0 12.4 12.8 13.2 13.6 14.0 14.4 14.8 15.2 15.6

0 commit comments

Comments
 (0)