Repository: nipy/nipype

Issue:
Rename dropping outputs in wrong directory
### Summary

Upstreaming poldracklab/fmriprep#1261.

In an fMRIPrep workflow, we have a `Rename` interface that is run by a `MapNode`, tagged `run_without_submitting`. In the most recent release of fMRIPrep (which includes nipype 1.1.2), this produces the output error:

`traits.trait_errors.TraitError: The 'out_file' trait of a RenameOutputSpec instance must be a file name, but a value of '/home/ds366/linux/fsaverage5.nii' <class 'str'> was specified.`

The location should have been `/mnt/Analyses/fMRIPrep/scratch-03/fmriprep_wf/single_subject_03_wf/func_preproc_task_rest_wf/bold_surf_wf/rename_src/mapflow/_rename_src0/fsaverage5.nii`, which suggests that setting the working directory has regressed.

Unfortunately, I'm unable to reproduce the behavior locally, so testing is going to be very difficult.

cc @sundelinustc

### Actual behavior

Creates (or falsely reports creating) symlink in original working directory, not node WD.

### Expected behavior

Create and report symlink in node WD.

### How to replicate the behavior

Unknown.

### Script/Workflow details

```Python
import os
import numpy as np
import nibabel as nb
from nipype.pipeline import engine as pe
from nipype.interfaces import utility as niu

# needed values
DEFAULT_MEMORY_MIN_GB = 0.01
spaces = ['fsaverage5']
nb.Nifti1Image(np.zeros((5,5,5,5), dtype=np.uint8),
               np.eye(4)).to_filename('img.nii')

# Failing node
rename_src = pe.MapNode(niu.Rename(format_string='%(subject)s', keep_ext=True),
                        iterfield='subject', name='rename_src', run_without_submitting=True,
                        mem_gb=DEFAULT_MEMORY_MIN_GB)
rename_src.inputs.subject = spaces

rename_src.inputs.in_file = os.path.abspath('img.nii')

# Trivial workflow
workflow = pe.Workflow(name='bold_surf_wf')
workflow.add_nodes([rename_src])

workflow.run(plugin='MultiProc')
```

### Platform details:

<!-- Please run the following code from your shell and place the output between the triple ticks, below.
python -c "import nipype; from pprint import pprint; pprint(nipype.get_info())"
-->

```
[ds366@blade17 Predator]$ /usr/local/packages/singularity/bin/singularity run /usr/local/packages/singularity/images/fmriprep.simg --version
fmriprep v1.1.4
[ds366@blade17 Predator]$ /usr/local/packages/singularity/bin/singularity exec /usr/local/packages/singularity/images/fmriprep.simg python -c 'import nipype; print(nipype.version)'
1.1.2
```

### Execution environment

Choose one
- Singularity


Discussion:

Resolving patch:
diff --git a/nipype/interfaces/utility/base.py b/nipype/interfaces/utility/base.py
--- a/nipype/interfaces/utility/base.py
+++ b/nipype/interfaces/utility/base.py
@@ -20,7 +20,7 @@
 
 from ..base import (traits, TraitedSpec, DynamicTraitedSpec, File, Undefined,
                     isdefined, OutputMultiPath, InputMultiPath, BaseInterface,
-                    BaseInterfaceInputSpec, Str)
+                    BaseInterfaceInputSpec, Str, SimpleInterface)
 from ..io import IOBase, add_traits
 from ...utils.filemanip import ensure_list, copyfile, split_filename
 
@@ -204,7 +204,6 @@ def _list_outputs(self):
 
 
 class RenameInputSpec(DynamicTraitedSpec):
-
     in_file = File(exists=True, mandatory=True, desc="file to rename")
     keep_ext = traits.Bool(
         desc=("Keep in_file extension, replace "
@@ -218,12 +217,11 @@ class RenameInputSpec(DynamicTraitedSpec):
 
 
 class RenameOutputSpec(TraitedSpec):
-
     out_file = traits.File(
         exists=True, desc="softlink to original file with new name")
 
 
-class Rename(IOBase):
+class Rename(SimpleInterface, IOBase):
     """Change the name of a file based on a mapped format string.
 
     To use additional inputs that will be defined at run-time, the class
@@ -303,15 +301,11 @@ def _rename(self):
 
     def _run_interface(self, runtime):
         runtime.returncode = 0
-        _ = copyfile(self.inputs.in_file,
-                     os.path.join(os.getcwd(), self._rename()))
+        out_file = os.path.join(runtime.cwd, self._rename())
+        _ = copyfile(self.inputs.in_file, out_file)
+        self._results['out_file'] = out_file
         return runtime
 
-    def _list_outputs(self):
-        outputs = self._outputs().get()
-        outputs["out_file"] = os.path.join(os.getcwd(), self._rename())
-        return outputs
-
 
 class SplitInputSpec(BaseInterfaceInputSpec):
     inlist = traits.List(


Test patch: