Repository: mne-tools/mne-python

Issue:
BUG: ecog re-reference applied to non-data channels
I'm trying my hand applying mne to some ecog data and noticed a weird-ish bug using `.set_eeg_reference` which perhaps I had no business using to begin with. I set a custom reference using 
```
good_inds = mne.pick_types(raw.info, ecog=True)
goodchs = [raw.ch_names[g] for g in good_inds]
raw.set_eeg_reference(ref_channels=goodchs)
```
I noticed after doing this that while the reference was successful, my trigger channel had some weird changes to it which suggests that the reference is being applied to non-data channels as well.

This seems to happen in the [_apply_reference](https://github.com/mne-tools/mne-python/blob/569924b3ddcca4245725930ebca243ef213b31ee/mne/io/reference.py#L98) function which searches for eeg channels to apply the reference to and comes up with an empty list. Then in L136 the pick_channels takes the empty list and returns ALL channels instead of none of them. 

Moral of the story: I expected the reference to apply only to the ecog channels but instead it was applied to all channels. I can see a couple of solutions to this. 
1. An error at the beginning of set_eeg_reference if there are no EEG channels. Is there an ECoG reference function I'm unaware of? Should I just be doing this by hand?
2. A more general function `set_reference()` which handles EEG as well as ECoG data more flexibly. Perhaps defaulting to common average reference for ecog grids and bipolar references for seeg electrodes as suggested in #5081 



Discussion:
I really think we should get rid of the `pick_channels(ch_names, []) == ch_names` behavior. We keep hitting bugs like this, and there easily could be other bugs hiding from us.

I can work on doing this in the code base if @agramfort agrees. I guess the thing to do for now would be to raise an error if the second argument is empty. Eventually we could consider returning an empty list but maybe we won't need to. But either way, raising an error should be safer than the counterintuitive behavior we have now.
> A more general function set_reference() which handles EEG as well as ECoG data more flexibly. 

We should probably just make `set_eeg_reference` mean "set eeg/seeg/ecog reference" even though it's a bit of a misnomer. It's probably not a totally deliberate design choice to currently have only EEG and exclude sEEG/ECoG, but more an issue that we haven't had so many sEEG / ECoG users with some of these functiosn.
that's definitely a bug in set_eeg_reference

it should not look for eeg channels when ref_channels are present

that's the minimal fix to do. I would go this route.

>

> it should not look for eeg channels when ref_channels are present

I'm actually not sure what you mean by this. I'll open a PR with a minimal fix to avoid the error for sEEG/ECoG people, but it means that we only have code for EEG users to (re)reference their data, not ECoG or sEEG people.

Resolving patch:
diff --git a/mne/io/reference.py b/mne/io/reference.py
--- a/mne/io/reference.py
+++ b/mne/io/reference.py
@@ -97,6 +97,11 @@ def _apply_reference(inst, ref_from, ref_to=None):
 
     if ref_to is None:
         ref_to = [inst.ch_names[i] for i in eeg_idx]
+        extra = 'EEG channels found'
+    else:
+        extra = 'channels supplied'
+    if len(ref_to) == 0:
+        raise ValueError('No %s to apply the reference to' % (extra,))
 
     # After referencing, existing SSPs might not be valid anymore.
     projs_to_remove = []
@@ -132,8 +137,12 @@ def _apply_reference(inst, ref_from, ref_to=None):
 
     # Compute reference
     if len(ref_from) > 0:
-        ref_from = pick_channels(inst.ch_names, ref_from)
-        ref_to = pick_channels(inst.ch_names, ref_to)
+        # this is guaranteed below, but we should avoid the crazy pick_channels
+        # behavior that [] gives all. Also use ordered=True just to make sure
+        # that all supplied channels actually exist.
+        assert len(ref_to) > 0
+        ref_from = pick_channels(inst.ch_names, ref_from, ordered=True)
+        ref_to = pick_channels(inst.ch_names, ref_to, ordered=True)
 
         data = inst._data
         ref_data = data[..., ref_from, :].mean(-2, keepdims=True)
@@ -348,11 +357,11 @@ def set_eeg_reference(inst, ref_channels='average', copy=True,
     """
     _validate_type(inst, (BaseRaw, BaseEpochs, Evoked), "Instance")
 
-    if ref_channels != 'average' and projection:
-        raise ValueError('Setting projection=True is only supported for '
-                         'ref_channels="average".')
-
-    if ref_channels == 'average' and projection:  # average reference projector
+    if projection:  # average reference projector
+        if ref_channels != 'average':
+            raise ValueError('Setting projection=True is only supported for '
+                             'ref_channels="average", got %r.'
+                             % (ref_channels,))
         if _has_eeg_average_ref_proj(inst.info['projs']):
             warn('An average reference projection was already added. The data '
                  'has been left untouched.')
@@ -381,8 +390,7 @@ def set_eeg_reference(inst, ref_channels='average', copy=True,
     if ref_channels == 'average' and not projection:  # apply average reference
         logger.info('Applying average reference.')
         eeg_idx = pick_types(inst.info, eeg=True, meg=False, ref_meg=False)
-        ref_from = [inst.ch_names[i] for i in eeg_idx]
-        ref_channels = ref_from
+        ref_channels = [inst.ch_names[i] for i in eeg_idx]
 
     if ref_channels == []:
         logger.info('EEG data marked as already having the desired reference. '


Test patch:
diff --git a/mne/io/fieldtrip/tests/helpers.py b/mne/io/fieldtrip/tests/helpers.py
--- a/mne/io/fieldtrip/tests/helpers.py
+++ b/mne/io/fieldtrip/tests/helpers.py
@@ -3,12 +3,15 @@
 #          Dirk Gütlin <dirk.guetlin@stud.sbg.ac.at>
 #
 # License: BSD (3-clause)
+from functools import partial
+import os
 import types
+
 import numpy as np
-import os
+import pytest
+
 import mne
 
-from functools import partial
 
 info_ignored_fields = ('file_id', 'hpi_results', 'hpi_meas', 'meas_id',
                        'meas_date', 'highpass', 'lowpass', 'subject_info',
@@ -121,7 +124,11 @@ def get_raw_data(system, drop_extra_chs=False):
     if system == 'eximia':
         crop -= 0.5 * (1.0 / raw_data.info['sfreq'])
     raw_data.crop(0, crop)
-    raw_data.set_eeg_reference([])
+    if 'eeg' in raw_data:
+        raw_data.set_eeg_reference([])
+    else:
+        with pytest.raises(ValueError, match='No EEG channels'):
+            raw_data.set_eeg_reference([])
     raw_data.del_proj('all')
     raw_data.info['comps'] = []
     raw_data.drop_channels(cfg_local['removed_chan_names'])
diff --git a/mne/io/tests/test_reference.py b/mne/io/tests/test_reference.py
--- a/mne/io/tests/test_reference.py
+++ b/mne/io/tests/test_reference.py
@@ -219,9 +219,17 @@ def test_set_eeg_reference():
                                         projection=False)
     _test_reference(raw, reref, ref_data, eeg_chans)
 
-    # projection=True only works for ref_channels='average'
-    pytest.raises(ValueError, set_eeg_reference, raw, [], True, True)
-    pytest.raises(ValueError, set_eeg_reference, raw, ['EEG 001'], True, True)
+    with pytest.raises(ValueError, match='supported for ref_channels="averag'):
+        set_eeg_reference(raw, [], True, True)
+    with pytest.raises(ValueError, match='supported for ref_channels="averag'):
+        set_eeg_reference(raw, ['EEG 001'], True, True)
+
+    # gh-6454
+    rng = np.random.RandomState(0)
+    data = rng.randn(2, 1000)
+    raw = RawArray(data, create_info(2, 1000., 'ecog'))
+    with pytest.raises(ValueError, match='No EEG channels found to apply'):
+        raw.set_eeg_reference()
 
 
 @testing.requires_testing_data