Repository: nipy/nipype

Issue:
New version 1.2.0 generates str error with mrTrix denoise, biascorrect
Hi! I have a DWI workflow that has run smoothly in Nipype version 1.1.8, utilizing mrTrix nodes including DWIDenoise. I upgraded to version 1.2.0 so I could incorporate MRDeGibbs (Gibbs ringing removal), but the denoising node generated the following error after running: "TypeError: expected str, bytes or os.PathLike object, not _Undefined". DWIBiasCorrect also generated same error. I downgraded to version 1.1.8 again, removed the Gibbs node, and everything ran smoothly. Thanks for any help!

### Script/Workflow details
**Node**
```
#Denoise DWI data using local PCA correction - mrTrix3
denoise = Node(mtx.DWIDenoise(),
              name='denoise')
```

**Workflow**
```
(drop2, denoise, [('roi_file', 'in_file')]),
                      (denoise, datasink, [('out_file', '1_Check_Unwarped.@par.@par.@par')])
```

### Platform details:
mac OS Mojave 10.14.5


Discussion:
Could you provide the full traceback and workflow? And can you verify that upgrading to 1.2.0 while not changing the script causes the problem?
Definitely, thanks for your help! I just tested it again, and while it ran fine in version 1.1.8, it crashed again when I upgraded to 1.2.0. It also runs in version 1.1.9. [Script is here](https://github.com/lucindasisk/Diffusion/blob/master/1_DTI_Preprocessing-SHAPES.ipynb). Traceback below:

```
Traceback (most recent call last):
  File "1_DTI_Preprocessing-SHAPES.py", line 247, in <module>
    preproc = preproc_flow.run('MultiProc', plugin_args={'n_procs': 4})
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/pipeline/engine/workflows.py", line 599, in run
    runner.run(execgraph, updatehash=updatehash, config=self.config)
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/pipeline/plugins/base.py", line 161, in run
    self._clean_queue(jobid, graph, result=result))
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/pipeline/plugins/base.py", line 224, in _clean_queue
    raise RuntimeError("".join(result['traceback']))
RuntimeError: Traceback (most recent call last):
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/pipeline/plugins/multiproc.py", line 69, in run_node
    result['result'] = node.run(updatehash=updatehash)
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/pipeline/engine/nodes.py", line 472, in run
    result = self._run_interface(execute=True)
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/pipeline/engine/nodes.py", line 563, in _run_interface
    return self._run_command(execute)
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/pipeline/engine/nodes.py", line 643, in _run_command
    result = self._interface.run(cwd=outdir)
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/interfaces/base/core.py", line 377, in run
    outputs = self.aggregate_outputs(runtime)
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/interfaces/base/core.py", line 455, in aggregate_outputs
    predicted_outputs = self._list_outputs()
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/site-packages/nipype/interfaces/mrtrix3/preprocess.py", line 81, in _list_outputs
    outputs['out_file'] = op.abspath(self.inputs.out_file)
  File "/Users/lucindasisk/.local/share/virtualenvs/lucindasisk-jFZfvjp6/lib/python3.6/posixpath.py", line 369, in abspath
    path = os.fspath(path)
TypeError: expected str, bytes or os.PathLike object, not _Undefined
```


Okay. It looks like this was introduce in #2904. @josephmje Could you clarify the reasons for the changes to DWIDenoise outputs? Looking at it, I don't immediately understand why `out_file` was changed.
Thank you @effigies!
Sorry @effigies @lucindasisk . I wanted to work with the output noise file in the rest of my workflow and modified the `_list_outputs()` method as suggested in the PR. But it looks like the change to the DWIDenoise class causes things to break unless `out_file` is specifically defined. 

I've been working through this by modifying the noise trait in `DWIDenoiseInputSpec` to:
```
    noise = File(
        argstr="-noise %s",
        name_template="%s_noise",
        name_source=["in_file"],
        keep_extension=True,
        desc="the output noise map",
    )
```
and removing the `_list_outputs()` method. But I'm not sure if this is the correct solution.
Is the `-noise` argument optional?
Yes it is.

Resolving patch:
diff --git a/nipype/interfaces/mrtrix3/base.py b/nipype/interfaces/mrtrix3/base.py
--- a/nipype/interfaces/mrtrix3/base.py
+++ b/nipype/interfaces/mrtrix3/base.py
@@ -47,12 +47,14 @@ class MRTrix3BaseInputSpec(CommandLineInputSpec):
     grad_file = File(
         exists=True,
         argstr='-grad %s',
-        desc='dw gradient scheme (MRTrix format')
+        desc='dw gradient scheme (MRTrix format)',
+        xor=['grad_fsl'])
     grad_fsl = traits.Tuple(
         File(exists=True),
         File(exists=True),
         argstr='-fslgrad %s %s',
-        desc='(bvecs, bvals) dw gradient scheme (FSL format')
+        desc='(bvecs, bvals) dw gradient scheme (FSL format)',
+        xor=['grad_file'])
     bval_scale = traits.Enum(
         'yes',
         'no',
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
@@ -23,19 +23,24 @@ class DWIDenoiseInputSpec(MRTrix3BaseInputSpec):
         argstr='-mask %s',
         position=1,
         desc='mask image')
-    extent = traits.Tuple((traits.Int, traits.Int, traits.Int),
+    extent = traits.Tuple(
+        (traits.Int, traits.Int, traits.Int),
         argstr='-extent %d,%d,%d',
         desc='set the window size of the denoising filter. (default = 5,5,5)')
     noise = File(
         argstr='-noise %s',
-        desc='the output noise map')
-    out_file = File(name_template='%s_denoised',
+        name_template='%s_noise',
         name_source='in_file',
         keep_extension=True,
+        desc='the output noise map')
+    out_file = File(
         argstr='%s',
         position=-1,
-        desc='the output denoised DWI image',
-        genfile=True)
+        name_template='%s_denoised',
+        name_source='in_file',
+        keep_extension=True,
+        desc='the output denoised DWI image')
+
 
 class DWIDenoiseOutputSpec(TraitedSpec):
     noise = File(desc='the output noise map', exists=True)
@@ -67,8 +72,9 @@ class DWIDenoise(MRTrix3Base):
     >>> denoise = mrt.DWIDenoise()
     >>> denoise.inputs.in_file = 'dwi.mif'
     >>> denoise.inputs.mask = 'mask.mif'
+    >>> denoise.inputs.noise = 'noise.mif'
     >>> denoise.cmdline                               # doctest: +ELLIPSIS
-    'dwidenoise -mask mask.mif dwi.mif dwi_denoised.mif'
+    'dwidenoise -mask mask.mif -noise noise.mif dwi.mif dwi_denoised.mif'
     >>> denoise.run()                                 # doctest: +SKIP
     """
 
@@ -76,13 +82,6 @@ class DWIDenoise(MRTrix3Base):
     input_spec = DWIDenoiseInputSpec
     output_spec = DWIDenoiseOutputSpec
 
-    def _list_outputs(self):
-        outputs = self.output_spec().get()
-        outputs['out_file'] = op.abspath(self.inputs.out_file)
-        if self.inputs.noise != Undefined:
-            outputs['noise'] = op.abspath(self.inputs.noise)
-        return outputs
-
 
 class MRDeGibbsInputSpec(MRTrix3BaseInputSpec):
     in_file = File(
@@ -92,7 +91,7 @@ class MRDeGibbsInputSpec(MRTrix3BaseInputSpec):
         mandatory=True,
         desc='input DWI image')
     axes = traits.ListInt(
-        default_value=[0,1],
+        default_value=[0, 1],
         usedefault=True,
         sep=',',
         minlen=2,
@@ -117,13 +116,13 @@ class MRDeGibbsInputSpec(MRTrix3BaseInputSpec):
         argstr='-maxW %d',
         desc='right border of window used for total variation (TV) computation '
              '(default = 3)')
-    out_file = File(name_template='%s_unr',
+    out_file = File(
+        name_template='%s_unr',
         name_source='in_file',
         keep_extension=True,
         argstr='%s',
         position=-1,
-        desc='the output unringed DWI image',
-        genfile=True)
+        desc='the output unringed DWI image')
 
 class MRDeGibbsOutputSpec(TraitedSpec):
     out_file = File(desc='the output unringed DWI image', exists=True)
@@ -179,31 +178,21 @@ class DWIBiasCorrectInputSpec(MRTrix3BaseInputSpec):
     in_mask = File(
         argstr='-mask %s',
         desc='input mask image for bias field estimation')
-    _xor_methods = ('use_ants', 'use_fsl')
     use_ants = traits.Bool(
-        default_value=True,
-        usedefault=True,
         argstr='-ants',
+        mandatory=True,
         desc='use ANTS N4 to estimate the inhomogeneity field',
-        xor=_xor_methods)
+        xor=['use_fsl'])
     use_fsl = traits.Bool(
         argstr='-fsl',
+        mandatory=True,
         desc='use FSL FAST to estimate the inhomogeneity field',
-        xor=_xor_methods,
-        min_ver='5.0.10')
-    _xor_grads = ('mrtrix_grad', 'fsl_grad')
-    mrtrix_grad = File(
-        argstr='-grad %s',
-        desc='diffusion gradient table in MRtrix format',
-        xor=_xor_grads)
-    fsl_grad = File(
-        argstr='-fslgrad %s %s',
-        desc='diffusion gradient table in FSL bvecs/bvals format',
-        xor=_xor_grads)
+        xor=['use_ants'])
     bias = File(
         argstr='-bias %s',
         desc='bias field')
-    out_file = File(name_template='%s_biascorr',
+    out_file = File(
+        name_template='%s_biascorr',
         name_source='in_file',
         keep_extension=True,
         argstr='%s',
@@ -228,6 +217,7 @@ class DWIBiasCorrect(MRTrix3Base):
     >>> import nipype.interfaces.mrtrix3 as mrt
     >>> bias_correct = mrt.DWIBiasCorrect()
     >>> bias_correct.inputs.in_file = 'dwi.mif'
+    >>> bias_correct.inputs.use_ants = True
     >>> bias_correct.cmdline
     'dwibiascorrect -ants dwi.mif dwi_biascorr.mif'
     >>> bias_correct.run()                             # doctest: +SKIP
@@ -237,13 +227,6 @@ class DWIBiasCorrect(MRTrix3Base):
     input_spec = DWIBiasCorrectInputSpec
     output_spec = DWIBiasCorrectOutputSpec
 
-    def _list_outputs(self):
-        outputs = self.output_spec().get()
-        outputs['out_file'] = op.abspath(self.inputs.out_file)
-        if self.inputs.bias != Undefined:
-            outputs['bias'] = op.abspath(self.inputs.bias)
-        return outputs
-
 
 class ResponseSDInputSpec(MRTrix3BaseInputSpec):
     algorithm = traits.Enum(


Test patch:
diff --git a/nipype/interfaces/mrtrix3/tests/test_auto_DWIBiasCorrect.py b/nipype/interfaces/mrtrix3/tests/test_auto_DWIBiasCorrect.py
--- a/nipype/interfaces/mrtrix3/tests/test_auto_DWIBiasCorrect.py
+++ b/nipype/interfaces/mrtrix3/tests/test_auto_DWIBiasCorrect.py
@@ -15,16 +15,15 @@ def test_DWIBiasCorrect_inputs():
             nohash=True,
             usedefault=True,
         ),
-        fsl_grad=dict(
-            argstr='-fslgrad %s %s',
-            extensions=None,
-            xor=('mrtrix_grad', 'fsl_grad'),
-        ),
         grad_file=dict(
             argstr='-grad %s',
             extensions=None,
+            xor=['grad_fsl'],
+        ),
+        grad_fsl=dict(
+            argstr='-fslgrad %s %s',
+            xor=['grad_file'],
         ),
-        grad_fsl=dict(argstr='-fslgrad %s %s', ),
         in_bval=dict(extensions=None, ),
         in_bvec=dict(
             argstr='-fslgrad %s %s',
@@ -40,11 +39,6 @@ def test_DWIBiasCorrect_inputs():
             argstr='-mask %s',
             extensions=None,
         ),
-        mrtrix_grad=dict(
-            argstr='-grad %s',
-            extensions=None,
-            xor=('mrtrix_grad', 'fsl_grad'),
-        ),
         nthreads=dict(
             argstr='-nthreads %d',
             nohash=True,
@@ -60,13 +54,13 @@ def test_DWIBiasCorrect_inputs():
         ),
         use_ants=dict(
             argstr='-ants',
-            usedefault=True,
-            xor=('use_ants', 'use_fsl'),
+            mandatory=True,
+            xor=['use_fsl'],
         ),
         use_fsl=dict(
             argstr='-fsl',
-            min_ver='5.0.10',
-            xor=('use_ants', 'use_fsl'),
+            mandatory=True,
+            xor=['use_ants'],
         ),
     )
     inputs = DWIBiasCorrect.input_spec()
diff --git a/nipype/interfaces/mrtrix3/tests/test_auto_DWIDenoise.py b/nipype/interfaces/mrtrix3/tests/test_auto_DWIDenoise.py
--- a/nipype/interfaces/mrtrix3/tests/test_auto_DWIDenoise.py
+++ b/nipype/interfaces/mrtrix3/tests/test_auto_DWIDenoise.py
@@ -15,8 +15,12 @@ def test_DWIDenoise_inputs():
         grad_file=dict(
             argstr='-grad %s',
             extensions=None,
+            xor=['grad_fsl'],
+        ),
+        grad_fsl=dict(
+            argstr='-fslgrad %s %s',
+            xor=['grad_file'],
         ),
-        grad_fsl=dict(argstr='-fslgrad %s %s', ),
         in_bval=dict(extensions=None, ),
         in_bvec=dict(
             argstr='-fslgrad %s %s',
@@ -36,6 +40,9 @@ def test_DWIDenoise_inputs():
         noise=dict(
             argstr='-noise %s',
             extensions=None,
+            keep_extension=True,
+            name_source='in_file',
+            name_template='%s_noise',
         ),
         nthreads=dict(
             argstr='-nthreads %d',
@@ -44,7 +51,6 @@ def test_DWIDenoise_inputs():
         out_file=dict(
             argstr='%s',
             extensions=None,
-            genfile=True,
             keep_extension=True,
             name_source='in_file',
             name_template='%s_denoised',
diff --git a/nipype/interfaces/mrtrix3/tests/test_auto_MRDeGibbs.py b/nipype/interfaces/mrtrix3/tests/test_auto_MRDeGibbs.py
--- a/nipype/interfaces/mrtrix3/tests/test_auto_MRDeGibbs.py
+++ b/nipype/interfaces/mrtrix3/tests/test_auto_MRDeGibbs.py
@@ -21,8 +21,12 @@ def test_MRDeGibbs_inputs():
         grad_file=dict(
             argstr='-grad %s',
             extensions=None,
+            xor=['grad_fsl'],
+        ),
+        grad_fsl=dict(
+            argstr='-fslgrad %s %s',
+            xor=['grad_file'],
         ),
-        grad_fsl=dict(argstr='-fslgrad %s %s', ),
         in_bval=dict(extensions=None, ),
         in_bvec=dict(
             argstr='-fslgrad %s %s',
@@ -53,7 +57,6 @@ def test_MRDeGibbs_inputs():
         out_file=dict(
             argstr='%s',
             extensions=None,
-            genfile=True,
             keep_extension=True,
             name_source='in_file',
             name_template='%s_unr',