Repository: nipy/nipype

Issue:
spm.EstimateModel additional args broken
### Summary
Issue brought up on [Neurostars#436](https://neurostars.org/t/nipype-interfaces-without-options-present-in-the-underlying-executable-function/436)
### Actual behavior
`EstimateModel` expects a string for input `flags`, but later expects a dictionary.
### Expected behavior
Additional inputs can be added.
### Platform details:

Please paste the output of: `python -c "import nipype; print(nipype.get_info()); print(nipype.__version__)"`
```
{'pkg_path': '/code/nipype/nipype', 'commit_source': 'repository', 'commit_hash': '71e2140', 'nipype_version': '1.0.0-g71e2140.dev', 'sys_version': '3.6.1 | packaged by conda-forge | (default, Mar 23 2017, 21:05:12) \n[GCC 4.8.2 20140120 (Red Hat 4.8.2-15)]', 'sys_executable': '/home/mathias/miniconda2/envs/dev3/bin/python', 'sys_platform': 'linux', 'numpy_version': '1.12.1', 'scipy_version': '0.19.0', 'networkx_version': '1.11', 'nibabel_version': '2.1.0', 'traits_version': '4.6.0'}
1.0.0-g71e2140.dev
```
### Execution environment
Local

Addressing this now

Discussion:

Patch:
diff --git a/nipype/interfaces/spm/model.py b/nipype/interfaces/spm/model.py
index 4a55a4ea7..ddf35ef44 100644
--- a/nipype/interfaces/spm/model.py
+++ b/nipype/interfaces/spm/model.py
@@ -29,7 +29,7 @@ from ...utils.filemanip import (filename_to_list, list_to_filename,
 from ..base import (Bunch, traits, TraitedSpec, File, Directory,
                     OutputMultiPath, InputMultiPath, isdefined)
 from .base import (SPMCommand, SPMCommandInputSpec,
-                   scans_for_fnames)
+                   scans_for_fnames, ImageFileSPM)
 
 __docformat__ = 'restructuredtext'
 logger = logging.getLogger('interface')
@@ -179,27 +179,38 @@ class Level1Design(SPMCommand):
 
 class EstimateModelInputSpec(SPMCommandInputSpec):
     spm_mat_file = File(exists=True, field='spmmat',
-                        desc='absolute path to SPM.mat',
-                        copyfile=True,
-                        mandatory=True)
-    estimation_method = traits.Dict(traits.Enum('Classical', 'Bayesian2',
-                                                'Bayesian'),
-                                    field='method',
-                                    desc=('Classical, Bayesian2, '
-                                          'Bayesian (dict)'),
-                                    mandatory=True)
-    flags = traits.Str(desc='optional arguments (opt)')
+        copyfile=True, mandatory=True,
+        desc='Absolute path to SPM.mat')
+    estimation_method = traits.Dict(
+        traits.Enum('Classical', 'Bayesian2', 'Bayesian'),
+        field='method', mandatory=True,
+        desc=('Dictionary of either Classical: 1, Bayesian: 1, '
+              'or Bayesian2: 1 (dict)'))
+    write_residuals = traits.Bool(field='write_residuals',
+        desc="Write individual residual images")
+    flags = traits.Dict(desc='Additional arguments')
 
 
 class EstimateModelOutputSpec(TraitedSpec):
-    mask_image = File(exists=True,
-                      desc='binary mask to constrain estimation')
-    beta_images = OutputMultiPath(File(exists=True),
-                                  desc='design parameter estimates')
-    residual_image = File(exists=True,
-                          desc='Mean-squared image of the residuals')
-    RPVimage = File(exists=True, desc='Resels per voxel image')
+    mask_image = ImageFileSPM(exists=True,
+        desc='binary mask to constrain estimation')
+    beta_images = OutputMultiPath(ImageFileSPM(exists=True),
+        desc='design parameter estimates')
+    residual_image = ImageFileSPM(exists=True,
+        desc='Mean-squared image of the residuals')
+    residual_images = OutputMultiPath(ImageFileSPM(exists=True),
+        desc="individual residual images (requires `write_residuals`")
+    RPVimage = ImageFileSPM(exists=True, desc='Resels per voxel image')
     spm_mat_file = File(exists=True, desc='Updated SPM mat file')
+    labels = ImageFileSPM(exists=True, desc="label file")
+    SDerror = OutputMultiPath(ImageFileSPM(exists=True),
+        desc="Images of the standard deviation of the error")
+    ARcoef = OutputMultiPath(ImageFileSPM(exists=True),
+        desc="Images of the AR coefficient")
+    Cbetas = OutputMultiPath(ImageFileSPM(exists=True),
+        desc="Images of the parameter posteriors")
+    SDbetas = OutputMultiPath(ImageFileSPM(exists=True),
+        desc="Images of the standard deviation of parameter posteriors")
 
 
 class EstimateModel(SPMCommand):
@@ -211,6 +222,7 @@ class EstimateModel(SPMCommand):
     --------
     >>> est = EstimateModel()
     >>> est.inputs.spm_mat_file = 'SPM.mat'
+    >>> est.inputs.estimation_method = {'Classical': 1}
     >>> est.run() # doctest: +SKIP
     """
     input_spec = EstimateModelInputSpec
@@ -225,7 +237,7 @@ class EstimateModel(SPMCommand):
             return np.array([str(val)], dtype=object)
         if opt == 'estimation_method':
             if isinstance(val, (str, bytes)):
-                return {'%s' % val: 1}
+                return {'{}'.format(val): 1}
             else:
                 return val
         return super(EstimateModel, self)._format_arg(opt, spec, val)
@@ -235,36 +247,43 @@ class EstimateModel(SPMCommand):
         """
         einputs = super(EstimateModel, self)._parse_inputs(skip=('flags'))
         if isdefined(self.inputs.flags):
-            einputs[0].update(self.inputs.flags)
+            einputs[0].update({flag: val for (flag, val) in
+                               self.inputs.flags.items()})
         return einputs
 
     def _list_outputs(self):
         outputs = self._outputs().get()
-        pth, _ = os.path.split(self.inputs.spm_mat_file)
-        spm12 = '12' in self.version.split('.')[0]
-        if spm12:
-            mask = os.path.join(pth, 'mask.nii')
-        else:
-            mask = os.path.join(pth, 'mask.img')
-        outputs['mask_image'] = mask
+        pth = os.path.dirname(self.inputs.spm_mat_file)
+        outtype = 'nii' if '12' in self.version.split('.')[0] else 'img'
         spm = sio.loadmat(self.inputs.spm_mat_file, struct_as_record=False)
-        betas = []
-        for vbeta in spm['SPM'][0, 0].Vbeta[0]:
-            betas.append(str(os.path.join(pth, vbeta.fname[0])))
-        if betas:
-            outputs['beta_images'] = betas
-        if spm12:
-            resms = os.path.join(pth, 'ResMS.nii')
-        else:
-            resms = os.path.join(pth, 'ResMS.img')
-        outputs['residual_image'] = resms
-        if spm12:
-            rpv = os.path.join(pth, 'RPV.nii')
-        else:
-            rpv = os.path.join(pth, 'RPV.img')
-        outputs['RPVimage'] = rpv
-        spm = os.path.join(pth, 'SPM.mat')
-        outputs['spm_mat_file'] = spm
+
+        betas = [vbeta.fname[0] for vbeta in spm['SPM'][0, 0].Vbeta[0]]
+        if ('Bayesian' in self.inputs.estimation_method.keys() or
+            'Bayesian2' in self.inputs.estimation_method.keys()):
+            outputs['labels'] = os.path.join(pth,
+                                            'labels.{}'.format(outtype))
+            outputs['SDerror'] = glob(os.path.join(pth, 'Sess*_SDerror*'))
+            outputs['ARcoef'] = glob(os.path.join(pth, 'Sess*_AR_*'))
+            if betas:
+                outputs['Cbetas'] = [os.path.join(pth, 'C{}'.format(beta))
+                                            for beta in betas]
+                outputs['SDbetas'] = [os.path.join(pth, 'SD{}'.format(beta))
+                                            for beta in betas]
+
+        if 'Classical' in self.inputs.estimation_method.keys():
+            outputs['residual_image'] = os.path.join(pth,
+                                            'ResMS.{}'.format(outtype))
+            outputs['RPVimage'] =  os.path.join(pth,
+                                            'RPV.{}'.format(outtype))
+            if self.inputs.write_residuals:
+                outputs['residual_images'] = glob(os.path.join(pth, 'Res_*'))
+            if betas:
+                outputs['beta_images'] = [os.path.join(pth, beta)
+                                            for beta in betas]
+
+        outputs['mask_image'] = os.path.join(pth,
+                                            'mask.{}'.format(outtype))
+        outputs['spm_mat_file'] = os.path.join(pth, 'SPM.mat')
         return outputs
 
 


Test patch:
diff --git a/nipype/interfaces/spm/tests/test_auto_EstimateModel.py b/nipype/interfaces/spm/tests/test_auto_EstimateModel.py
index c99ff7dd0..703c97c6f 100644
--- a/nipype/interfaces/spm/tests/test_auto_EstimateModel.py
+++ b/nipype/interfaces/spm/tests/test_auto_EstimateModel.py
@@ -23,6 +23,8 @@ def test_EstimateModel_inputs():
     use_v8struct=dict(min_ver='8',
     usedefault=True,
     ),
+    write_residuals=dict(field='write_residuals',
+    ),
     )
     inputs = EstimateModel.input_spec()
 
@@ -32,10 +34,16 @@ def test_EstimateModel_inputs():
 
 
 def test_EstimateModel_outputs():
-    output_map = dict(RPVimage=dict(),
+    output_map = dict(ARcoef=dict(),
+    Cbetas=dict(),
+    RPVimage=dict(),
+    SDbetas=dict(),
+    SDerror=dict(),
     beta_images=dict(),
+    labels=dict(),
     mask_image=dict(),
     residual_image=dict(),
+    residual_images=dict(),
     spm_mat_file=dict(),
     )
     outputs = EstimateModel.output_spec()