Repository: nipy/nipype

Issue:
FSL 6.0 mcflirt mean_reg.nii.gz naming error?
### Summary
Mcflirt node crashed workflow with the following error:

**TraitError: The trait 'mean_img' of a MCFLIRTOutputSpec instance is an existing file name, but the path  '/NIPYPE_TEST_DATA/Preproc/mcflirt/22_corrected_mcf.nii.gz_mean_reg.nii.gz' does not exist.**

The path is fine, the file name is wrong. The correct file name is **22_corrected_mcf_mean_reg.nii.gz** without the extra nii.gz at the end. 

If I run the command.txt command in the terminal, it works fine. The only help I found online was someone that said it was fixed by installing FSL 5, so they assumed it was looking for how older versions of FSL named the file. The issue is the extra nii.gz, but not sure where it comes from. 

### 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())"
-->

```
{'commit_hash': '%h',
 'commit_source': 'archive substitution',
 'networkx_version': '2.2',
 'nibabel_version': '2.4.0',
 'nipype_version': '1.1.9',
 'numpy_version': '1.16.2',
 'pkg_path': '/Users/dvd296/miniconda2/envs/fmri/lib/python2.7/site-packages/nipype',
 'scipy_version': '1.2.1',
 'sys_executable': '/Users/dvd296/miniconda2/envs/fmri/bin/python',
 'sys_platform': 'darwin',
 'sys_version': '2.7.15 | packaged by conda-forge | (default, Feb 27 2019, 20:44:16) \n[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]',
 'traits_version': '5.0.0'}
```

### Execution environment

Choose one
- My python environment outside container



Discussion:
I tried using Nipype with FSL 6.0 and can confirm this TraitError. Running the command without nipype results in the more sensible file name without 2nd **nii.gz**.

I could fix this for FSL 6.0, but I am not sure how Nipype would like to maintain compatibility with multiple versions of FSL.
It would be good to verify whether this is a bug in FSL 5 that we're accommodating or a bug in nipype.
Not sure if I can call it a 'bug', but mcflirt in FSL 5 always produces **{...}_mcf.nii.gz_mean_reg.nii.gz** as mean_img. Nipype is accommodating this quirk.

This error also occurs with stat_imgs: **nii.gz_{variance,sigma}.nii.gz**

Considering that more people will be using FSL 6 as time passes, the file checks should definitely be fixed. The question is how to keep compatibility with FSL 5. Is there a way to query FSL version during the Trait check?
There are always a number of ways to do these things. One example you could take is #1811, where we created a modified input specification that would be selected if the version was new enough.

(Note that it's now [a bit different](https://github.com/nipy/nipype/blob/0470641905b0a92df97bbded8e7f9089ed441b65/nipype/interfaces/freesurfer/preprocess.py#L1462-L1465).)

There's also a `nipype.interfaces.fsl.Info` class that should behave equivalently to `nipype.interfaces.freesurfer.Info`.

There are other options that involve determining the version when it's time to run the interface, but those will generally be more opaque to read.

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
@@ -16,6 +16,7 @@
 import numpy as np
 from nibabel import load
 
+from ... import LooseVersion
 from ...utils.filemanip import split_filename
 from ..base import (TraitedSpec, File, InputMultiPath, OutputMultiPath,
                     Undefined, traits, isdefined)
@@ -826,10 +827,17 @@ def _list_outputs(self):
         output_dir = os.path.dirname(outputs['out_file'])
 
         if isdefined(self.inputs.stats_imgs) and self.inputs.stats_imgs:
-            outputs['variance_img'] = self._gen_fname(
-                outputs['out_file'] + '_variance.ext', cwd=output_dir)
-            outputs['std_img'] = self._gen_fname(
-                outputs['out_file'] + '_sigma.ext', cwd=output_dir)
+            if LooseVersion(Info.version()) < LooseVersion('6.0.0'):
+                # FSL <6.0 outputs have .nii.gz_variance.nii.gz as extension
+                outputs['variance_img'] = self._gen_fname(
+                    outputs['out_file'] + '_variance.ext', cwd=output_dir)
+                outputs['std_img'] = self._gen_fname(
+                    outputs['out_file'] + '_sigma.ext', cwd=output_dir)
+            else:
+                outputs['variance_img'] = self._gen_fname(
+                    outputs['out_file'], suffix='_variance', cwd=output_dir)
+                outputs['std_img'] = self._gen_fname(
+                    outputs['out_file'], suffix='_sigma', cwd=output_dir)
 
         # The mean image created if -stats option is specified ('meanvol')
         # is missing the top and bottom slices. Therefore we only expose the
@@ -838,8 +846,13 @@ def _list_outputs(self):
         # Note that the same problem holds for the std and variance image.
 
         if isdefined(self.inputs.mean_vol) and self.inputs.mean_vol:
-            outputs['mean_img'] = self._gen_fname(
-                outputs['out_file'] + '_mean_reg.ext', cwd=output_dir)
+            if LooseVersion(Info.version()) < LooseVersion('6.0.0'):
+                # FSL <6.0 outputs have .nii.gz_mean_img.nii.gz as extension
+                outputs['mean_img'] = self._gen_fname(
+                    outputs['out_file'] + '_mean_reg.ext', cwd=output_dir)
+            else:
+                outputs['mean_img'] = self._gen_fname(
+                    outputs['out_file'], suffix='_mean_reg', cwd=output_dir)
 
         if isdefined(self.inputs.save_mats) and self.inputs.save_mats:
             _, filename = os.path.split(outputs['out_file'])


Test patch: