Repository: nipy/nipype

Issue:
Add `dwifslpreproc` mrtrix3 interface
### Summary
Add the new mrtrix3 `dwifslpreproc` command as an interface. See https://mrtrix.readthedocs.io/en/latest/reference/commands/dwifslpreproc.html

### Actual behavior
no `dwipreproc` or `dwifslpreproc` interface in current release

### Expected behavior
Interface exists under mrtrix3's preprocess module

I'm currently working on a pull request... There are a few other functionalities from mrtrix3 that needs to be updated as interfaces, thinking of submitting this as a hackathon project for the upcoming brainhack global... 



Discussion:

Resolving patch:
diff --git a/nipype/interfaces/mrtrix3/__init__.py b/nipype/interfaces/mrtrix3/__init__.py
--- a/nipype/interfaces/mrtrix3/__init__.py
+++ b/nipype/interfaces/mrtrix3/__init__.py
@@ -18,6 +18,7 @@
     ResponseSD,
     ACTPrepareFSL,
     ReplaceFSwithFIRST,
+    DWIPreproc,
     DWIDenoise,
     MRDeGibbs,
     DWIBiasCorrect,
diff --git a/nipype/interfaces/mrtrix3/preprocess.py b/nipype/interfaces/mrtrix3/preprocess.py
--- a/nipype/interfaces/mrtrix3/preprocess.py
+++ b/nipype/interfaces/mrtrix3/preprocess.py
@@ -233,6 +233,7 @@ class DWIBiasCorrect(MRTrix3Base):
     _cmd = "dwibiascorrect"
     input_spec = DWIBiasCorrectInputSpec
     output_spec = DWIBiasCorrectOutputSpec
+
     def _format_arg(self, name, trait_spec, value):
         if name in ("use_ants", "use_fsl"):
             ver = self.version
@@ -241,6 +242,139 @@ def _format_arg(self, name, trait_spec, value):
                 return f"-{trait_spec.argstr}"
         return super()._format_arg(name, trait_spec, value)
 
+
+class DWIPreprocInputSpec(MRTrix3BaseInputSpec):
+    in_file = File(
+        exists=True, argstr="%s", position=0, mandatory=True, desc="input DWI image"
+    )
+    out_file = File(
+        "preproc.mif",
+        argstr="%s",
+        mandatory=True,
+        position=1,
+        usedefault=True,
+        desc="output file after preprocessing",
+    )
+    rpe_options = traits.Enum(
+        "none",
+        "pair",
+        "all",
+        "header",
+        argstr="-rpe_%s",
+        position=2,
+        mandatory=True,
+        desc='Specify acquisition phase-encoding design. "none" for no reversed phase-encoding image, "all" for all DWIs have opposing phase-encoding acquisition, "pair" for using a pair of b0 volumes for inhomogeneity field estimation only, and "header" for phase-encoding information can be found in the image header(s)',
+    )
+    pe_dir = traits.Str(
+        argstr="-pe_dir %s",
+        mandatory=True,
+        desc="Specify the phase encoding direction of the input series, can be a signed axis number (e.g. -0, 1, +2), an axis designator (e.g. RL, PA, IS), or NIfTI axis codes (e.g. i-, j, k)",
+    )
+    ro_time = traits.Float(
+        argstr="-readout_time %f",
+        desc="Total readout time of input series (in seconds)",
+    )
+    in_epi = File(
+        exists=True,
+        argstr="-se_epi %s",
+        desc="Provide an additional image series consisting of spin-echo EPI images, which is to be used exclusively by topup for estimating the inhomogeneity field (i.e. it will not form part of the output image series)",
+    )
+    align_seepi = traits.Bool(
+        argstr="-align_seepi",
+        desc="Achieve alignment between the SE-EPI images used for inhomogeneity field estimation, and the DWIs",
+    )
+    eddy_options = traits.Str(
+        argstr='-eddy_options "%s"',
+        desc="Manually provide additional command-line options to the eddy command",
+    )
+    topup_options = traits.Str(
+        argstr='-topup_options "%s"',
+        desc="Manually provide additional command-line options to the topup command",
+    )
+    export_grad_mrtrix = traits.Bool(
+        argstr="-export_grad_mrtrix", desc="export new gradient files in mrtrix format"
+    )
+    export_grad_fsl = traits.Bool(
+        argstr="-export_grad_fsl", desc="export gradient files in FSL format"
+    )
+    out_grad_mrtrix = File(
+        "grad.b",
+        argstr="%s",
+        usedefault=True,
+        requires=["export_grad_mrtrix"],
+        desc="name of new gradient file",
+    )
+    out_grad_fsl = traits.Tuple(
+        File("grad.bvecs", usedefault=True, desc="bvecs"),
+        File("grad.bvals", usedefault=True, desc="bvals"),
+        argstr="%s, %s",
+        requires=["export_grad_fsl"],
+        desc="Output (bvecs, bvals) gradients FSL format",
+    )
+
+
+class DWIPreprocOutputSpec(TraitedSpec):
+    out_file = File(argstr="%s", desc="output preprocessed image series")
+    out_grad_mrtrix = File(
+        "grad.b",
+        argstr="%s",
+        usedefault=True,
+        desc="preprocessed gradient file in mrtrix3 format",
+    )
+    out_fsl_bvec = File(
+        "grad.bvecs",
+        argstr="%s",
+        usedefault=True,
+        desc="exported fsl gradient bvec file",
+    )
+    out_fsl_bval = File(
+        "grad.bvals",
+        argstr="%s",
+        usedefault=True,
+        desc="exported fsl gradient bval file",
+    )
+
+
+class DWIPreproc(MRTrix3Base):
+    """
+    Perform diffusion image pre-processing using FSL's eddy tool; including inhomogeneity distortion correction using FSL's topup tool if possible
+
+    For more information, see
+    <https://mrtrix.readthedocs.io/en/latest/reference/commands/dwifslpreproc.html>
+
+    Example
+    -------
+
+    >>> import nipype.interfaces.mrtrix3 as mrt
+    >>> preproc = mrt.DWIPreproc()
+    >>> preproc.inputs.in_file = 'dwi.mif'
+    >>> preproc.inputs.rpe_options = 'none'
+    >>> preproc.inputs.out_file = "preproc.mif"
+    >>> preproc.inputs.eddy_options = '--slm=linear --repol'     # linear second level model and replace outliers
+    >>> preproc.inputs.export_grad_mrtrix = True    # export final gradient table in MRtrix format
+    >>> preproc.inputs.ro_time = 0.165240   # 'TotalReadoutTime' in BIDS JSON metadata files
+    >>> preproc.inputs.pe_dir = 'j'     # 'PhaseEncodingDirection' in BIDS JSON metadata files
+    >>> preproc.cmdline
+    'dwifslpreproc dwi.mif preproc.mif -rpe_none -eddy_options "--slm=linear --repol" -export_grad_mrtrix grad.b -pe_dir j -readout_time 0.165240'
+    >>> preproc.run()                             # doctest: +SKIP
+    """
+
+    _cmd = "dwifslpreproc"
+    input_spec = DWIPreprocInputSpec
+    output_spec = DWIPreprocOutputSpec
+
+    def _list_outputs(self):
+        outputs = self.output_spec().get()
+        outputs["out_file"] = op.abspath(self.inputs.out_file)
+        if self.inputs.export_grad_mrtrix:
+            outputs["out_grad_mrtrix"] = op.abspath(self.inputs.out_grad_mrtrix)
+        if self.inputs.export_grad_fsl:
+            outputs["out_fsl_bvec"] = op.abspath(self.inputs.out_grad_fsl[0])
+            outputs["out_fsl_bval"] = op.abspath(self.inputs.out_grad_fsl[1])
+
+        return outputs
+
+
 class ResponseSDInputSpec(MRTrix3BaseInputSpec):
     algorithm = traits.Enum(
         "msmt_5tt",


Test patch:
diff --git a/nipype/interfaces/mrtrix3/tests/test_auto_DWIPreproc.py b/nipype/interfaces/mrtrix3/tests/test_auto_DWIPreproc.py
new file mode 100644
--- /dev/null
+++ b/nipype/interfaces/mrtrix3/tests/test_auto_DWIPreproc.py
@@ -0,0 +1,54 @@
+# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
+from ..preprocess import DWIPreproc
+
+
+def test_DWIPreproc_inputs():
+    input_map = dict(
+        align_seepi=dict(argstr="-align_seepi"),
+        args=dict(argstr="%s"),
+        bval_scale=dict(argstr="-bvalue_scaling %s"),
+        eddy_options=dict(argstr='-eddy_options "%s"'),
+        environ=dict(nohash=True, usedefault=True),
+        export_grad_fsl=dict(argstr="-export_grad_fsl"),
+        export_grad_mrtrix=dict(argstr="-export_grad_mrtrix"),
+        grad_file=dict(argstr="-grad %s", extensions=None, xor=["grad_fsl"]),
+        grad_fsl=dict(argstr="-fslgrad %s %s", xor=["grad_file"]),
+        in_bval=dict(extensions=None),
+        in_bvec=dict(argstr="-fslgrad %s %s", extensions=None),
+        in_epi=dict(argstr="-se_epi %s", extensions=None),
+        in_file=dict(argstr="%s", extensions=None, mandatory=True, position=0),
+        nthreads=dict(argstr="-nthreads %d", nohash=True),
+        out_file=dict(
+            argstr="%s", extensions=None, mandatory=True, position=1, usedefault=True
+        ),
+        out_grad_fsl=dict(argstr="%s, %s", requires=["export_grad_fsl"]),
+        out_grad_mrtrix=dict(
+            argstr="%s",
+            extensions=None,
+            requires=["export_grad_mrtrix"],
+            usedefault=True,
+        ),
+        pe_dir=dict(argstr="-pe_dir %s", mandatory=True),
+        ro_time=dict(argstr="-readout_time %f"),
+        rpe_options=dict(argstr="-rpe_%s", mandatory=True, position=2),
+        topup_options=dict(argstr='-topup_options "%s"'),
+    )
+    inputs = DWIPreproc.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_DWIPreproc_outputs():
+    output_map = dict(
+        out_file=dict(argstr="%s", extensions=None),
+        out_fsl_bval=dict(argstr="%s", extensions=None, usedefault=True),
+        out_fsl_bvec=dict(argstr="%s", extensions=None, usedefault=True),
+        out_grad_mrtrix=dict(argstr="%s", extensions=None, usedefault=True),
+    )
+    outputs = DWIPreproc.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/testing/data/grad.b b/nipype/testing/data/grad.b
new file mode 100644