Repository: nipy/nipype

Issue:
 adding SliceEncodingDirection to afni.TShift
### Summary
BIDS allows to specify `SliceEncodingDirection`. How about adding a  parameter to `afni.TShift` to use this information and `SliceTiming` from BIDS sidecar files directly.


This comes via [fmriprep](https://github.com/poldracklab/fmriprep/issues/1345). @effigies suggested to add this here.

BIDS allows to declare `SliceEncodingDirection` to specify the direction of `SliceTiming`. What do you think about adding a `SliceEncodingDirection` input (`{k, k-}`) to `afni.TShift`. If this is `k-`, reverse the slice timing list before running the correction.

Does it make sense to also add `i, j, i-, j-`? Or does it make sense to always assume that slices have been acquired along the third axis?

What do you think?




Discussion:
I'll review, so I'd say full speed ahead. :-)

`SliceTiming` is passed to the `slice_timing` input, defined here:

https://github.com/nipy/nipype/blob/39675df33fadf4459e46b0dcdb78425b853dd22d/nipype/interfaces/afni/preprocess.py#L2615-L2620

I would start with `{k, k-}` as you suggest, as that will cover the majority of the cases, and we can think about `i` and `j` in a separate PR.

Resolving patch:
diff --git a/nipype/interfaces/afni/preprocess.py b/nipype/interfaces/afni/preprocess.py
--- a/nipype/interfaces/afni/preprocess.py
+++ b/nipype/interfaces/afni/preprocess.py
@@ -2618,6 +2618,14 @@ class TShiftInputSpec(AFNICommandInputSpec):
         desc='time offsets from the volume acquisition onset for each slice',
         argstr='-tpattern @%s',
         xor=['tpattern'])
+    slice_encoding_direction = traits.Enum(
+        'k', 'k-',
+        usedefault=True,
+        desc='Direction in which slice_timing is specified (default: k). If negative,'
+             'slice_timing is defined in reverse order, that is, the first entry '
+             'corresponds to the slice with the largest index, and the final entry '
+             'corresponds to slice index zero. Only in effect when slice_timing is '
+             'passed as list, not when it is passed as file.',)
     rlt = traits.Bool(
         desc='Before shifting, remove the mean and linear trend',
         argstr='-rlt')
@@ -2660,6 +2668,17 @@ class TShift(AFNICommand):
     >>> tshift._list_outputs()['timing_file']  # doctest: +ELLIPSIS
     '.../slice_timing.1D'
 
+    >>> np.loadtxt(tshift._list_outputs()['timing_file']).tolist()[:5]
+    [0.0, 0.4, 0.8, 1.2, 1.6]
+
+    If ``slice_encoding_direction`` is set to ``'k-'``, the slice timing is reversed:
+
+    >>> tshift.inputs.slice_encoding_direction = 'k-'
+    >>> tshift.cmdline
+    '3dTshift -prefix functional_tshift -tpattern @slice_timing.1D -TR 2.5s -tzero 0.0 functional.nii'
+    >>> np.loadtxt(tshift._list_outputs()['timing_file']).tolist()[:5]
+    [15.6, 15.2, 14.8, 14.4, 14.0]
+
     This method creates a ``slice_timing.1D`` file to be passed to ``3dTshift``.
     A pre-existing slice-timing file may be used in the same way:
 
@@ -2723,9 +2742,13 @@ def _format_arg(self, name, trait_spec, value):
         return super(TShift, self)._format_arg(name, trait_spec, value)
 
     def _write_slice_timing(self):
+        slice_timing = list(self.inputs.slice_timing)
+        if self.inputs.slice_encoding_direction.endswith("-"):
+            slice_timing.reverse()
+
         fname = 'slice_timing.1D'
         with open(fname, 'w') as fobj:
-            fobj.write('\t'.join(map(str, self.inputs.slice_timing)))
+            fobj.write('\t'.join(map(str, slice_timing)))
         return fname
 
     def _list_outputs(self):


Test patch: