Repository: mne-tools/mne-python

Issue:
FIX: projs type checking in compute_covariance
code:
`noiseCov = compute_covariance(epochsList, tmin=None, tmax=0, method='shrunk',return_estimators=False, verbose=True, n_jobs=1,projs=projList)`
`mne.write_cov(subjectDirectory + URSI + '/' + URSI + '_' + testName + '-cov.fif', noiseCov)`

error:
`mne.write_cov(subjectDirectory + URSI + '/' + URSI + '_' + testName + '-cov.fif', noiseCov)`
`File "/usr/local/lib/python2.7/dist-packages/mne/cov.py", line 1179, in write_cov`
`  cov.save(fname)`
`File "/usr/local/lib/python2.7/dist-packages/mne/cov.py", line 152, in save`
`  raise inst`
`TypeError: list indices must be integers, not str`



Discussion:
That's a weird one. Can you try writing the covariance to HDF5 with `mne.externals.h5io.write_hdf5('test.h5', noiseCov)` and uploading the result somewhere? If that works it's an easy way to debug and figure out what's wrong with the object.

Are you on latest `master` or 0.13?
When trying to write hdf5 i got the following:

`Traceback (most recent call last):`
  `File "pythonScripts/pipeline.py", line 410, in <module>`
    `main()`
 `File "pythonScripts/pipeline.py", line 349, in main`
   `mne.externals.h5io.write_hdf5('test.h5', noiseCov)`
  `File "/usr/local/lib/python2.7/dist-packages/mne/externals/h5io/_h5io.py", line 106, in write_hdf5
    cleanup_data=cleanup_data, slash=slash, title=title)`
  `File "/usr/local/lib/python2.7/dist-packages/mne/externals/h5io/_h5io.py", line 137, in _triage_write
    where + '["%s"]' % key, cleanup_data=cleanup_data, slash=slash)`
  `File "/usr/local/lib/python2.7/dist-packages/mne/externals/h5io/_h5io.py", line 206, in _triage_write
    raise TypeError(err_str)`
`TypeError: unsupported type <class 'mne.cov._ShrunkCovariance'> (in <class 'mne.cov.Covariance'>["estimator"])`

I am using 0.14.git

Okay let's try something else. I just pushed a commit that should give us a better traceback. Can you update to latest master, try running your code again, and paste the new, improved traceback?
pretty sure this is exactly the same - version 0.14.dev0

`File "pipeline.py", line 413, in <module>`
   `main()`
  `File "pipeline.py", line 352, in main`
    `mne.externals.h5io.write_hdf5('test.h5', noiseCov)`
  `File "/na/homes/wcollishaw/pythonScripts/mne-python/mne/externals/h5io/_h5io.py", line 106, in write_hdf5`
    `cleanup_data=cleanup_data, slash=slash, title=title)`
  `File "/na/homes/wcollishaw/pythonScripts/mne-python/mne/externals/h5io/_h5io.py", line 137, in _triage_write`
    `where + '["%s"]' % key, cleanup_data=cleanup_data, slash=slash)`
  `File "/na/homes/wcollishaw/pythonScripts/mne-python/mne/externals/h5io/_h5io.py", line 206, in _triage_write`
    `raise TypeError(err_str)`
`TypeError: unsupported type <class 'mne.cov._ShrunkCovariance'> (in <class 'mne.cov.Covariance'>["estimator"])`

Ahh sorry, I mean retry the `cov.save`
... or `write_cov`
`Traceback (most recent call last):`
  `File "pipeline.py", line 414, in <module>`
    `main()`
 `File "pipeline.py", line 353, in main`
    `mne.write_cov(subjectDirectory + URSI + '/' + URSI + '_' + testName + '-cov.fif', noiseCov)`
  `File "/na/homes/wcollishaw/pythonScripts/mne-python/mne/cov.py", line 1189, in write_cov`
    `cov.save(fname)`
 `File "/na/homes/wcollishaw/pythonScripts/mne-python/mne/cov.py", line 147, in save`
    `_write_cov(fid, self)`
  `File "/na/homes/wcollishaw/pythonScripts/mne-python/mne/cov.py", line 1805, in _write_cov`
    `_write_proj(fid, cov['projs'])`
  `File "/na/homes/wcollishaw/pythonScripts/mne-python/mne/io/proj.py", line 405, in _write_proj`
    `write_int(fid, FIFF.FIFF_NCHAN, proj['data']['ncol'])`
`TypeError: list indices must be integers, not str`

So there is something weird about your `cov['projs']`, which suggests there is something weird about `epochs.info['projs']`. I can get something similar with a nonsensical call:
```
>>> mne.write_proj('foo', 'bar')
<stdin>:1: RuntimeWarning: This filename (foo) does not conform to MNE naming conventions. All projection files should end with -proj.fif or -proj.fif.gz
This filename (foo) does not conform to MNE naming conventions. All projection files should end with -proj.fif or -proj.fif.gz
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "mne/proj.py", line 66, in write_proj
    io.proj._write_proj(fid, projs)
  File "mne/io/proj.py", line 405, in _write_proj
    write_int(fid, FIFF.FIFF_NCHAN, proj['data']['ncol'])
TypeError: string indices must be integers, not str
```
What is your `epochs.info['projs']`?

Do you get an error with `mne.write_proj('test-proj.fif', epochs.info['projs']`)? If so, how did you create your projectors? What raw data format are you using (FIF, CTF, etc.)?

... oh I guess there might be something weird about `projList`. What is `projList`? Can you successfully do `mne.write_projs('test-proj.fif', projList)`?
When trying to write projList i get the same error
projList is a list of projectors
`projList = []`
`projList.append(mne.read_proj(file))`

I tried to write_proj for a single projector and it worked.

in the documentation it states:
projs : list of Projection | None (default None)
List of projectors to use in covariance calculation, or None to indicate that the projectors from the epochs should be inherited. If None, then projectors from all epochs must match.

projs = list of projections.

Am I using it incorrectly?
You want a list of projectors, so `projList.extend(mne.read_proj(...))` because `read_proj` returns a list itself. What you currently create is a list containing a list containing projectors.
(we should probably add a check for this in the code to prevent this problem)
So, I had a list of list of projectors, when i needed a list of projectors. I love data structures!

Using extend I was able to write_proj, will try recalculate the noise_cov and let you know my progress.

Thanks for all the prompt responses!
No problem, fingers crossed it works this time...
It worked! Thank you for the help!
No problem. I'll re-title this to make it clear that we should raise an error if `projs_list` is not a list of projs

Resolving patch:
diff --git a/mne/cov.py b/mne/cov.py
--- a/mne/cov.py
+++ b/mne/cov.py
@@ -4,7 +4,7 @@
 #
 # License: BSD (3-clause)
 
-import copy as cp
+from copy import deepcopy
 from distutils.version import LooseVersion
 import itertools as itt
 from math import log
@@ -15,7 +15,7 @@
 
 from .io.write import start_file, end_file
 from .io.proj import (make_projector, _proj_equal, activate_proj,
-                      _needs_eeg_average_ref_proj)
+                      _needs_eeg_average_ref_proj, _check_projs)
 from .io import fiff_open
 from .io.pick import (pick_types, pick_channels_cov, pick_channels, pick_info,
                       _picks_by_type, _pick_data_channels)
@@ -108,6 +108,7 @@ def __init__(self, data, names, bads, projs, nfree, eig=None, eigvec=None,
                  method=None, loglik=None):
         """Init of covariance."""
         diag = True if data.ndim == 1 else False
+        projs = _check_projs(projs)
         self.update(data=data, dim=len(data), names=names, bads=bads,
                     nfree=nfree, eig=eig, eigvec=eigvec, diag=diag,
                     projs=projs, kind=FIFF.FIFFV_MNE_NOISE_COV)
@@ -160,7 +161,7 @@ def copy(self):
         cov : instance of Covariance
             The copied object.
         """
-        return cp.deepcopy(self)
+        return deepcopy(self)
 
     def as_diag(self):
         """Set covariance to be processed as being diagonal.
@@ -195,7 +196,7 @@ def __repr__(self):  # noqa: D105
     def __add__(self, cov):
         """Add Covariance taking into account number of degrees of freedom."""
         _check_covs_algebra(self, cov)
-        this_cov = cp.deepcopy(cov)
+        this_cov = cov.copy()
         this_cov['data'] = (((this_cov['data'] * this_cov['nfree']) +
                              (self['data'] * self['nfree'])) /
                             (self['nfree'] + this_cov['nfree']))
@@ -460,8 +461,8 @@ def compute_raw_covariance(raw, tmin=0, tmax=None, tstep=0.2, reject=None,
         logger.info('[done]')
         ch_names = [raw.info['ch_names'][k] for k in picks]
         bads = [b for b in raw.info['bads'] if b in ch_names]
-        projs = cp.deepcopy(raw.info['projs'])
-        return Covariance(data, ch_names, bads, projs, nfree=n_samples)
+        return Covariance(data, ch_names, bads, raw.info['projs'],
+                          nfree=n_samples)
     del picks, pick_mask
 
     # This makes it equivalent to what we used to do (and do above for
@@ -694,7 +695,7 @@ def _unpack_epochs(epochs):
 
     bads = epochs[0].info['bads']
     if projs is None:
-        projs = cp.deepcopy(epochs[0].info['projs'])
+        projs = epochs[0].info['projs']
         # make sure Epochs are compatible
         for epochs_t in epochs[1:]:
             if epochs_t.proj != epochs[0].proj:
@@ -702,8 +703,7 @@ def _unpack_epochs(epochs):
             for proj_a, proj_b in zip(epochs_t.info['projs'], projs):
                 if not _proj_equal(proj_a, proj_b):
                     raise ValueError('Epochs must have same projectors')
-    else:
-        projs = cp.deepcopy(projs)
+    projs = _check_projs(projs)
     ch_names = epochs[0].ch_names
 
     # make sure Epochs are compatible
@@ -998,7 +998,7 @@ def _cross_val(data, est, cv, n_jobs):
 def _auto_low_rank_model(data, mode, n_jobs, method_params, cv,
                          stop_early=True, verbose=None):
     """Compute latent variable models."""
-    method_params = cp.deepcopy(method_params)
+    method_params = deepcopy(method_params)
     iter_n_components = method_params.pop('iter_n_components')
     if iter_n_components is None:
         iter_n_components = np.arange(5, data.shape[1], 5)
@@ -1016,7 +1016,7 @@ def _auto_low_rank_model(data, mode, n_jobs, method_params, cv,
     scores.fill(np.nan)
 
     # make sure we don't empty the thing if it's a generator
-    max_n = max(list(cp.deepcopy(iter_n_components)))
+    max_n = max(list(deepcopy(iter_n_components)))
     if max_n > data.shape[1]:
         warn('You are trying to estimate %i components on matrix '
              'with %i features.' % (max_n, data.shape[1]))
@@ -1327,7 +1327,7 @@ def prepare_noise_cov(noise_cov, info, ch_names, rank=None,
         warn('No average EEG reference present in info["projs"], covariance '
              'may be adversely affected. Consider recomputing covariance using'
              ' a raw file with an average eeg reference projector added.')
-    noise_cov = cp.deepcopy(noise_cov)
+    noise_cov = noise_cov.copy()
     noise_cov.update(data=C, eig=eig, eigvec=eigvec, dim=len(ch_names),
                      diag=False, names=ch_names)
     return noise_cov
@@ -1377,7 +1377,7 @@ def regularize(cov, info, mag=0.1, grad=0.1, eeg=0.1, exclude='bads',
     --------
     mne.compute_covariance
     """  # noqa: E501
-    cov = cp.deepcopy(cov)
+    cov = cov.copy()
     info._check_consistency()
 
     if exclude is None:
@@ -1567,7 +1567,7 @@ def compute_whitener(noise_cov, info, picks=None, rank=None,
 
     ch_names = [info['chs'][k]['ch_name'] for k in picks]
 
-    noise_cov = cp.deepcopy(noise_cov)
+    noise_cov = noise_cov.copy()
     noise_cov = prepare_noise_cov(noise_cov, info, ch_names,
                                   rank=rank, scalings=scalings)
     n_chan = len(ch_names)
@@ -1625,7 +1625,7 @@ def whiten_evoked(evoked, noise_cov, picks=None, diag=False, rank=None,
     evoked_white : instance of Evoked
         The whitened evoked data.
     """
-    evoked = cp.deepcopy(evoked)
+    evoked = evoked.copy()
     if picks is None:
         picks = pick_types(evoked.info, meg=True, eeg=True)
     W = _get_whitener_data(evoked.info, noise_cov, picks,
@@ -1647,7 +1647,7 @@ def _get_whitener_data(info, noise_cov, picks, diag=False, rank=None,
         raise RuntimeError('Not all channels present in noise covariance:\n%s'
                            % missing)
     if diag:
-        noise_cov = cp.deepcopy(noise_cov)
+        noise_cov = noise_cov.copy()
         noise_cov['data'] = np.diag(np.diag(noise_cov['data']))
 
     scalings = _handle_default('scalings_cov_rank', scalings)
diff --git a/mne/io/proj.py b/mne/io/proj.py
--- a/mne/io/proj.py
+++ b/mne/io/proj.py
@@ -424,6 +424,20 @@ def _write_proj(fid, projs):
 
 ###############################################################################
 # Utils
+
+def _check_projs(projs, copy=True):
+    """Check that projs is a list of Projection."""
+    if not isinstance(projs, (list, tuple)):
+        raise TypeError('projs must be a list or tuple, got %s'
+                        % (type(projs),))
+    for pi, p in enumerate(projs):
+        if not isinstance(p, Projection):
+            raise TypeError('All entries in projs list must be Projection '
+                            'instances, but projs[%d] is type %s'
+                            % (pi, type(p)))
+    return deepcopy(projs) if copy else projs
+
+
 def make_projector(projs, ch_names, bads=(), include_active=True):
     """Create an SSP operator from SSP projection vectors.
 


Test patch:
diff --git a/mne/tests/test_cov.py b/mne/tests/test_cov.py
--- a/mne/tests/test_cov.py
+++ b/mne/tests/test_cov.py
@@ -322,6 +322,11 @@ def test_cov_estimation_with_triggers():
                     baseline=(-0.2, -0.1), proj=True, reject=reject)
     compute_covariance(epochs)
 
+    # projs checking
+    compute_covariance(epochs, projs=[])
+    assert_raises(TypeError, compute_covariance, epochs, projs='foo')
+    assert_raises(TypeError, compute_covariance, epochs, projs=['foo'])
+
 
 def test_arithmetic_cov():
     """Test arithmetic with noise covariance matrices."""
@@ -573,7 +578,6 @@ def test_compute_covariance_auto_reg():
 
     covs = compute_covariance(epochs, method='auto',
                               method_params=method_params,
-                              projs=True,
                               return_estimators=True)
 
     logliks = [c['loglik'] for c in covs]