Repository: mne-tools/mne-python

Issue:
can't raw.filter MISC channels
Hello!

With raw.filter I can not filter the instance of the raw created with pick_channels using names of MISC channels. It complains about not finding the channels to work with. Is it intended and one is supposed to filter only EEG or MEG channels? I would love to have a "parallel" code handling the two types of channels!

works:
```
raw_grad = raw.copy().pick_channels(names_grad);
raw_grad.filter(1, 45., h_trans_bandwidth='auto', filter_length ='auto', phase = 'zero');
```
does not work:
```
raw_msc = raw.copy().pick_channels(names_msc);
raw_msc.filter(0.5, 5., h_trans_bandwidth='auto', filter_length ='auto', phase = 'zero');
```
THANKS!!


Discussion:
`raw.filter` filters only data channels (EEG, MEG). You need to pass picks parameter manually to the  filter method. Please reopen if it does not work.
 Thanks, Alex, but if I understood you correctly 
neither 
```
raw_misc.filter(0.5, 5., h_trans_bandwidth='auto', filter_length='auto',
                            phase = 'zero', picks = names_misc);
```
nor
```
raw_misc.filter(0.5, 5., h_trans_bandwidth='auto', filter_length='auto',
                             phase = 'zero', picks = np.array([0,1,2])); 
```
appear to work and still give the same error. :(
can you share a minimal code snippet to replicate the issue using mne sample data?
Thanks! Yes, here is the snippet reproducing the problem. I added a Misc channel as there were none in the sample data. 

```
import os.path as op

data_path = op.join(mne.datasets.sample.data_path(), 'MEG', 'sample')
raw = mne.io.read_raw_fif(op.join(data_path, 'sample_audvis_raw.fif'),preload = True)

info = mne.create_info(['MISC1'], raw.info['sfreq'], ['misc'])
misc_raw = mne.io.RawArray(np.zeros((1,raw[0][0].shape[1])), info)
raw.add_channels([misc_raw], force_update_info=True)

# make a list of names for the grads and misc
picks_grad = mne.pick_types(raw.info, stim=False, meg = 'grad');
names_grad  = [raw.info['ch_names'][n] for n in picks_grad];

picks_misc = mne.pick_types(raw.info, stim=False, meg = False, misc = True);
names_misc  = [raw.info['ch_names'][n] for n in picks_misc];

raw_grad = raw.copy().pick_channels(names_grad);
raw_misc = raw.copy().pick_channels(names_misc);


raw_grad.filter(1, 45., h_trans_bandwidth='auto', filter_length='auto',
                            phase = 'zero');

raw_misc.filter(0.5, 5., h_trans_bandwidth='auto', filter_length='auto',
                           phase = 'zero', picks = names_misc);

raw_misc.filter(0.5, 5., h_trans_bandwidth='auto', filter_length='auto',
                           phase = 'zero', picks = np.arange(1));

``` 
here is another snippet I was hacking

```
import os.path as op

import mne
from mne.datasets import sample

data_path = sample.data_path()
raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif')

tmin, tmax = 0, 50

raw = mne.io.read_raw_fif(raw_fname)
raw.crop(tmin, tmax).load_data()

raw.set_channel_types({'EEG 001': 'misc'})

# make a list of names for the grads and misc
picks_grad = mne.pick_types(raw.info, stim=False, meg='grad')
names_grad = [raw.info['ch_names'][n] for n in picks_grad]
picks_misc = mne.pick_types(raw.info, stim=False, meg=False, misc=True)
names_misc = [raw.info['ch_names'][n] for n in picks_misc]
raw_grad = raw.copy().pick_channels(names_grad)
raw_misc = raw.copy().pick_channels(names_misc)

# works:
raw_grad.filter(1, 45.)
# fails:
raw_misc.filter(0.5, 5., picks=names_misc)
# fails:
raw_misc.filter(0.5, 5., picks=[0])
```

@larsoner this is a regression that appeared with the new picks API.
@larsoner I would appreciate your help here as it touches the new logic of pick. I can hack something but you certainly have a simpler approach in mind.
may be introducing  a special channel type that CAN be filtered but normally there are no channels of this  type. Then, "non-filterable", but requested by the user channels, can be reassigned to this special type, filtered and then set back to their original type.    
I can look tonight or tomorrow, should be some simple fix in principle

Resolving patch:
diff --git a/mne/filter.py b/mne/filter.py
--- a/mne/filter.py
+++ b/mne/filter.py
@@ -2236,8 +2236,13 @@ def _filt_check_picks(info, picks, h_freq, l_freq):
     # This will pick *all* data channels
     picks = _picks_to_idx(info, picks, 'data_or_ica', exclude=())
     if h_freq is not None or l_freq is not None:
-        data_picks = _picks_to_idx(info, None, 'data_or_ica', exclude=())
-        if np.in1d(data_picks, picks).all():
+        data_picks = _picks_to_idx(info, None, 'data_or_ica', exclude=(),
+                                   allow_empty=True)
+        if len(data_picks) == 0:
+            logger.info('No data channels found. The highpass and '
+                        'lowpass values in the measurement info will not '
+                        'be updated.')
+        elif np.in1d(data_picks, picks).all():
             update_info = True
         else:
             logger.info('Filtering a subset of channels. The highpass and '
diff --git a/mne/io/pick.py b/mne/io/pick.py
--- a/mne/io/pick.py
+++ b/mne/io/pick.py
@@ -929,7 +929,7 @@ def _picks_to_idx(info, picks, none='data', exclude='bads', allow_empty=False,
         raise ValueError('picks must be 1D, got %sD' % (picks.ndim,))
     if picks.dtype.char in ('S', 'U'):
         picks = _picks_str_to_idx(info, picks, exclude, with_ref_meg,
-                                  return_kind, orig_repr)
+                                  return_kind, orig_repr, allow_empty)
         if return_kind:
             picked_ch_type_or_generic = picks[1]
             picks = picks[0]
@@ -957,7 +957,7 @@ def _picks_to_idx(info, picks, none='data', exclude='bads', allow_empty=False,
 
 
 def _picks_str_to_idx(info, picks, exclude, with_ref_meg, return_kind,
-                      orig_repr):
+                      orig_repr, allow_empty):
     """Turn a list of str into ndarray of int."""
     # special case for _picks_to_idx w/no info: shouldn't really happen
     if isinstance(info, int):
@@ -980,7 +980,8 @@ def _picks_str_to_idx(info, picks, exclude, with_ref_meg, return_kind,
                                                     with_ref_meg=with_ref_meg)
             elif picks[0] == 'data_or_ica':
                 picks_generic = _pick_data_or_ica(info, exclude=exclude)
-            if len(picks_generic) == 0 and orig_repr.startswith('None, '):
+            if len(picks_generic) == 0 and orig_repr.startswith('None, ') and \
+                    not allow_empty:
                 raise ValueError('picks (%s) yielded no channels, consider '
                                  'passing picks explicitly' % (orig_repr,))
 
@@ -1035,11 +1036,13 @@ def _picks_str_to_idx(info, picks, exclude, with_ref_meg, return_kind,
     all_picks = (picks_generic, picks_name, picks_type)
     any_found = [len(p) > 0 for p in all_picks]
     if sum(any_found) == 0:
-        raise ValueError(
-            'picks (%s) could not be interpreted as '
-            'channel names (no channel "%s"), channel types (no '
-            'type "%s"), or a generic type (just "all" or "data")'
-            % (orig_repr, bad_name, bad_type))
+        if not allow_empty:
+            raise ValueError(
+                'picks (%s) could not be interpreted as '
+                'channel names (no channel "%s"), channel types (no '
+                'type "%s"), or a generic type (just "all" or "data")'
+                % (orig_repr, bad_name, bad_type))
+        picks = np.array([], int)
     elif sum(any_found) > 1:
         raise RuntimeError('Some channel names are ambiguously equivalent to '
                            'channel types, cannot use string-based '


Test patch:
diff --git a/mne/tests/test_filter.py b/mne/tests/test_filter.py
--- a/mne/tests/test_filter.py
+++ b/mne/tests/test_filter.py
@@ -10,6 +10,7 @@
 from mne import create_info
 from mne.fixes import _sosfreqz, fft, fftfreq
 from mne.io import RawArray, read_raw_fif
+from mne.io.pick import _DATA_CH_TYPES_SPLIT
 from mne.filter import (filter_data, resample, _resample_stim_channels,
                         construct_iir_filter, notch_filter, detrend,
                         _overlap_add_filter, _smart_pad, design_mne_c_filter,
@@ -722,4 +723,39 @@ def test_reporting_fir(phase, fir_window, btype):
         assert_allclose(h[mids], expected, atol=0.01)
 
 
+def test_filter_picks():
+    """Test filter picking."""
+    data = np.random.RandomState(0).randn(3, 1000)
+    fs = 1000.
+    kwargs = dict(l_freq=None, h_freq=40.)
+    filt = filter_data(data, fs, **kwargs)
+    # don't include seeg or stim in this list because they are in the one below
+    # to ensure default cases are treated properly
+    for kind in ('eeg', 'grad', 'emg', 'misc'):
+        for picks in (None, [-2], kind, 'k'):
+            # With always at least one data channel
+            info = create_info(['s', 'k', 't'], fs, ['seeg', kind, 'stim'])
+            raw = RawArray(data.copy(), info)
+            raw.filter(picks=picks, **kwargs)
+            if picks is None:
+                if kind in _DATA_CH_TYPES_SPLIT:  # should be included
+                    want = np.concatenate((filt[:2], data[2:]))
+                else:  # shouldn't
+                    want = np.concatenate((filt[:1], data[1:]))
+            else:  # just the kind of interest ([-2], kind, 'j' should be eq.)
+                want = np.concatenate((data[:1], filt[1:2], data[2:]))
+            assert_allclose(raw.get_data(), want)
+
+            # Now with sometimes no data channels
+            info = create_info(['k', 't'], fs, [kind, 'stim'])
+            raw = RawArray(data[1:].copy(), info.copy())
+            if picks is None and kind not in _DATA_CH_TYPES_SPLIT:
+                with pytest.raises(ValueError, match='yielded no channels'):
+                    raw.filter(picks=picks, **kwargs)
+            else:
+                raw.filter(picks=picks, **kwargs)
+                want = want[1:]
+                assert_allclose(raw.get_data(), want)
+
+
 run_tests_if_main()