Repository: nipy/nipype

Issue:
FSL (+6.0) BET chokes with long filenames
Would adding the copyfile metadata just fix this, @satra?

I think I've seen this issue reported before, but I can't seem to find it. Feel free to link and close this if it is a duplicate


Discussion:
is it a path length issue or a filename issue or a command path length issue? 

we could use `copyfile=False` and change the command to use a relative path. but that interface (one of our oldest), also returns absolute path for the output file name, which bet needs to run.
> is it a path length issue or a filename issue or a command path length issue?

Not sure, it could definitely be that it doesn't like nipype's parameterized folders (meaning, a filename issue). What is the difference between path length issue and command path length issue?

> we could use `copyfile=False` and change the command to use a relative path. but that interface (one of our oldest), also returns absolute path for the output file name, which bet needs to run.

The output file is the first thing I tried shortening, so you're probably right on this one too. Which means there's not an immediate fix for this, right?
Okay, this also happens with the output:

```
terminate called after throwing an instance of 'NiftiIO::NiftiException'
  what():  Error: cant open file /home/oesteban/tmp/fmriprep-ds210/work/fmriprep_wf/single_subject_02_wf/func_preproc_task_cuedSGT_run_03_echo_1_wf/skullstrip_bold_wf/_bold_file_..oak..stanford..groups..russpold..data..openfmri..ds000210..sub-02..func..sub-02_task-cuedSGT_run-03_echo-3_bold/skullstrip_first_pass/vol0000_xform-00000_merged_brain.nii.gz
```
is it possible that just this part `..oak..stanford..groups..russpold..data..openfmri..ds000210..sub-02..func..sub-02_task` is throwing things off?
Doesn't seem so:

```
bet derivatives/smriprep-0.6/sub-02/anat/sub-02_desc-preproc_T1w.nii.gz _bold_file_..oak..stanford..groups..russpold..data..openfmri..ds000210..sub-02..func..sub-02_task-cuedSGT_run-03_echo-3_bold/skullstrip_first_pass//some.file.nii.gz
```
works okay
so the easiest fix would be:
1. use `copyfile=False` for the input file to link locally
2. make the output filename be local (no absolute path) for generation, but not for list_outputs.

bet allows the user to send an output location, so that would have to remain as is.
Yup, I imagined this was the solution.

Resolving patch:
diff --git a/nipype/interfaces/fsl/preprocess.py b/nipype/interfaces/fsl/preprocess.py
--- a/nipype/interfaces/fsl/preprocess.py
+++ b/nipype/interfaces/fsl/preprocess.py
@@ -35,6 +35,7 @@ class BETInputSpec(FSLCommandInputSpec):
         argstr="%s",
         position=0,
         mandatory=True,
+        copyfile=False,
     )
     out_file = File(
         desc="name of output skull stripped image",
@@ -164,15 +165,27 @@ def _run_interface(self, runtime):
             self.raise_exception(runtime)
         return runtime
 
+    def _format_arg(self, name, spec, value):
+        formatted = super(BET, self)._format_arg(name, spec, value)
+        if name == "in_file":
+            # Convert to relative path to prevent BET failure
+            # with long paths.
+            return op.relpath(formatted, start=os.getcwd())
+        return formatted
+
     def _gen_outfilename(self):
         out_file = self.inputs.out_file
+        # Generate default output filename if non specified.
         if not isdefined(out_file) and isdefined(self.inputs.in_file):
             out_file = self._gen_fname(self.inputs.in_file, suffix="_brain")
-        return os.path.abspath(out_file)
+            # Convert to relative path to prevent BET failure
+            # with long paths.
+            return op.relpath(out_file, start=os.getcwd())
+        return out_file
 
     def _list_outputs(self):
         outputs = self.output_spec().get()
-        outputs["out_file"] = self._gen_outfilename()
+        outputs["out_file"] = os.path.abspath(self._gen_outfilename())
 
         basename = os.path.basename(outputs["out_file"])
         cwd = os.path.dirname(outputs["out_file"])
@@ -1309,10 +1322,7 @@ def _list_outputs(self):
 
             if key == "out_intensitymap_file" and isdefined(outputs[key]):
                 basename = FNIRT.intensitymap_file_basename(outputs[key])
-                outputs[key] = [
-                    outputs[key],
-                    "%s.txt" % basename,
-                ]
+                outputs[key] = [outputs[key], "%s.txt" % basename]
         return outputs
 
     def _format_arg(self, name, spec, value):


Test patch:
diff --git a/nipype/interfaces/fsl/tests/test_preprocess.py b/nipype/interfaces/fsl/tests/test_preprocess.py
--- a/nipype/interfaces/fsl/tests/test_preprocess.py
+++ b/nipype/interfaces/fsl/tests/test_preprocess.py
@@ -31,6 +31,9 @@ def setup_infile(tmpdir):
 @pytest.mark.skipif(no_fsl(), reason="fsl is not installed")
 def test_bet(setup_infile):
     tmp_infile, tp_dir = setup_infile
+    # BET converts the in_file path to be relative to prevent
+    # failure with long paths.
+    tmp_infile = os.path.relpath(tmp_infile, start=os.getcwd())
     better = fsl.BET()
     assert better.cmd == "bet"
 
@@ -41,8 +44,7 @@ def test_bet(setup_infile):
     # Test generated outfile name
     better.inputs.in_file = tmp_infile
     outfile = fsl_name(better, "foo_brain")
-    outpath = os.path.join(os.getcwd(), outfile)
-    realcmd = "bet %s %s" % (tmp_infile, outpath)
+    realcmd = "bet %s %s" % (tmp_infile, outfile)
     assert better.cmdline == realcmd
     # Test specified outfile name
     outfile = fsl_name(better, "/newdata/bar")
@@ -79,12 +81,11 @@ def func():
     # test each of our arguments
     better = fsl.BET()
     outfile = fsl_name(better, "foo_brain")
-    outpath = os.path.join(os.getcwd(), outfile)
     for name, settings in list(opt_map.items()):
         better = fsl.BET(**{name: settings[1]})
         # Add mandatory input
         better.inputs.in_file = tmp_infile
-        realcmd = " ".join([better.cmd, tmp_infile, outpath, settings[0]])
+        realcmd = " ".join([better.cmd, tmp_infile, outfile, settings[0]])
         assert better.cmdline == realcmd