Repository: nipy/nipype

Issue:
FSL dtifit add sse to outputspec
### Summary
nipype's interface for FSL dtifit currently has a parameter "sse" which produces the sum of squared errors. This output does not appear in the OutputSpec.

### Actual behavior
You can use the parameter and the image is produced correctly, but there is no way to further connect the output to other nodes, since it is not part of dtifit's official outputs.

### Expected behavior
Similar to the tensor file the sse-image should be made available for further processing.

### How to replicate the behavior
    >>> from nipype.interfaces import fsl
    >>> dti = fsl.DTIFit()
    >>> dti.inputs.dwi = 'diffusion.nii'
    >>> dti.inputs.bvecs = 'bvecs'
    >>> dti.inputs.bvals = 'bvals'
    >>> dti.inputs.base_name = 'TP'
    >>> dti.inputs.mask = 'mask.nii'
    >>> dti.inputs.sse = True
    >>> dti.save_tensor = True
    >>> dti.cmdline
    'dtifit -k diffusion.nii -o TP -m mask.nii -r bvecs -b bvals --save_tensor --sse'
    >>> out = dti.run()
    >>> out.outputs
    'FA = private/dtifit__FA.nii.gz
     L1 = private/dtifit__L1.nii.gz
     L2 = private/dtifit__L2.nii.gz
     L3 = private/dtifit__L3.nii.gz
     MD = private/dtifit__MD.nii.gz
     MO = private/dtifit__MO.nii.gz
     S0 = private/dtifit__S0.nii.gz
     V1 = private/dtifit__V1.nii.gz
     V2 = private/dtifit__V2.nii.gz
     V3 = private/dtifit__V3.nii.gz
     tensor = private/dtifit__tensor.nii.gz'

-> sse is missing

### Platform details:
This is not platform dependant!
{'commit_hash': '3ec8065',
 'commit_source': 'repository',
 'networkx_version': '2.1',
 'nibabel_version': '2.1.0',
 'nipype_version': '1.1.4-dev+g3ec8065',
 'numpy_version': '1.15.0',
 'pkg_path': 'private/nipype/nipype',
 'scipy_version': '1.1.0',
 'sys_executable': '/usr/bin/python',
 'sys_platform': 'linux2',
 'sys_version': '2.7.9 (default, Jun 29 2016, 13:08:31) \n[GCC 4.9.2]',
 'traits_version': '4.6.0'}

I have written a fix based on the example of 'save_tensor', but the guidelines say i should open an issue before making a pull request, so here you go ;)
Thanks for the great work so far!



Discussion:

Patch:
diff --git a/nipype/interfaces/ants/segmentation.py b/nipype/interfaces/ants/segmentation.py
index 04d212ec0..b0ce64764 100644
--- a/nipype/interfaces/ants/segmentation.py
+++ b/nipype/interfaces/ants/segmentation.py
@@ -203,9 +203,7 @@ class LaplacianThicknessInputSpec(ANTSCommandInputSpec):
         desc='name of output file',
         argstr='%s',
         position=3,
-        name_source=['input_wm'],
-        name_template='%s_thickness',
-        keep_extension=True,
+        genfile=True,
         hash_files=False)
     smooth_param = traits.Float(argstr='smoothparam=%d', desc='', position=4)
     prior_thickness = traits.Float(
@@ -230,9 +228,6 @@ class LaplacianThickness(ANTSCommand):
     >>> cort_thick = LaplacianThickness()
     >>> cort_thick.inputs.input_wm = 'white_matter.nii.gz'
     >>> cort_thick.inputs.input_gm = 'gray_matter.nii.gz'
-    >>> cort_thick.cmdline
-    'LaplacianThickness white_matter.nii.gz gray_matter.nii.gz white_matter_thickness.nii.gz'
-
     >>> cort_thick.inputs.output_image = 'output_thickness.nii.gz'
     >>> cort_thick.cmdline
     'LaplacianThickness white_matter.nii.gz gray_matter.nii.gz output_thickness.nii.gz'
@@ -243,6 +238,22 @@ class LaplacianThickness(ANTSCommand):
     input_spec = LaplacianThicknessInputSpec
     output_spec = LaplacianThicknessOutputSpec
 
+    def _gen_filename(self, name):
+        if name == 'output_image':
+            output = self.inputs.output_image
+            if not isdefined(output):
+                _, name, ext = split_filename(self.inputs.input_wm)
+                output = name + '_thickness' + ext
+            return output
+        return None
+
+    def _list_outputs(self):
+        outputs = self._outputs().get()
+        if not outputs.get('output_image', None) \
+                or not isdefined(outputs['output_image']):
+            outputs['output_image'] = self._gen_filename('output_image')
+        return outputs
+
 
 class N4BiasFieldCorrectionInputSpec(ANTSCommandInputSpec):
     dimension = traits.Enum(
diff --git a/nipype/interfaces/fsl/dti.py b/nipype/interfaces/fsl/dti.py
index d8812cec6..c842ff05c 100644
--- a/nipype/interfaces/fsl/dti.py
+++ b/nipype/interfaces/fsl/dti.py
@@ -85,6 +85,8 @@ class DTIFitOutputSpec(TraitedSpec):
               'diffusion weighting'))
     tensor = File(
         exists=True, desc='path/name of file with the 4D tensor volume')
+    sse = File(
+        exists=True, desc='path/name of file with the summed squared error')
 
 
 class DTIFit(FSLCommand):
@@ -111,13 +113,21 @@ class DTIFit(FSLCommand):
     output_spec = DTIFitOutputSpec
 
     def _list_outputs(self):
+        keys_to_ignore = {'outputtype', 'environ', 'args'}
+        # Optional output: Map output name to input flag
+        opt_output = {'tensor': self.inputs.save_tensor,
+                      'sse': self.inputs.sse}
+        # Ignore optional output, whose corresponding input-flag is not defined
+        # or set to False
+        for output, input_flag in opt_output.items():
+            if isdefined(input_flag) and input_flag:
+                # this is wanted output, do not ignore
+                continue
+            keys_to_ignore.add(output)
+
         outputs = self.output_spec().get()
-        for k in list(outputs.keys()):
-            if k not in ('outputtype', 'environ', 'args'):
-                if k != 'tensor' or (isdefined(self.inputs.save_tensor)
-                                     and self.inputs.save_tensor):
-                    outputs[k] = self._gen_fname(
-                        self.inputs.base_name, suffix='_' + k)
+        for k in set(outputs.keys()) - keys_to_ignore:
+            outputs[k] = self._gen_fname(self.inputs.base_name, suffix='_' + k)
         return outputs
 
 
diff --git a/nipype/interfaces/fsl/utils.py b/nipype/interfaces/fsl/utils.py
index f4ef73c0e..e237124bb 100644
--- a/nipype/interfaces/fsl/utils.py
+++ b/nipype/interfaces/fsl/utils.py
@@ -551,9 +551,9 @@ class Split(FSLCommand):
         """
         outputs = self._outputs().get()
         ext = Info.output_type_to_ext(self.inputs.output_type)
-        outbase = 'vol[0-9]*'
+        outbase = 'vol*'
         if isdefined(self.inputs.out_base_name):
-            outbase = '%s[0-9]*' % self.inputs.out_base_name
+            outbase = '%s*' % self.inputs.out_base_name
         outputs['out_files'] = sorted(
             glob(os.path.join(os.getcwd(), outbase + ext)))
         return outputs


Test patch:
diff --git a/nipype/interfaces/ants/tests/test_auto_LaplacianThickness.py b/nipype/interfaces/ants/tests/test_auto_LaplacianThickness.py
index f45efecfb..8f9a13d83 100644
--- a/nipype/interfaces/ants/tests/test_auto_LaplacianThickness.py
+++ b/nipype/interfaces/ants/tests/test_auto_LaplacianThickness.py
@@ -36,10 +36,8 @@ def test_LaplacianThickness_inputs():
         ),
         output_image=dict(
             argstr='%s',
+            genfile=True,
             hash_files=False,
-            keep_extension=True,
-            name_source=['input_wm'],
-            name_template='%s_thickness',
             position=3,
         ),
         prior_thickness=dict(
diff --git a/nipype/interfaces/fsl/tests/test_auto_DTIFit.py b/nipype/interfaces/fsl/tests/test_auto_DTIFit.py
index 8bfdf5f36..af6a0ea11 100644
--- a/nipype/interfaces/fsl/tests/test_auto_DTIFit.py
+++ b/nipype/interfaces/fsl/tests/test_auto_DTIFit.py
@@ -66,6 +66,7 @@ def test_DTIFit_outputs():
         V2=dict(),
         V3=dict(),
         tensor=dict(),
+        sse=dict(),
     )
     outputs = DTIFit.output_spec()