Repository: mne-tools/mne-python

Issue:
Iterating over `Raw` fails
Given a `Raw` object, it is tempting to assume that iterating will work channel-wise, but it doesn't:

```python
import mne

path = mne.datasets.sample.data_path()
raw = mne.io.read_raw(path / "MEG" / "sample" / "sample_audvis_raw.fif").pick("eeg")

for ch in raw:  # note that raw has 60 channels
    pass
```

This results in the a `ValueError: All picks must be < n_channels (60), got 60`, and the issue crops up from time to time (e.g. in [this forum post](https://mne.discourse.group/t/valueerror-all-picks-must-be-n-channels-73-got-73/7375/7?u=cbrnr), where I incorrectly dismissed the issue).

The problem is that `Raw` implements `__getitem__()` to support indexing. However, this method is also the old (deprecated) way of implementing an iterator in Python (this was superseded by `__iter__()`, but the old behavior of `__getitem__()` is kept around for backward compatibility).

So I think we should support proper iteration, and my suggestion is to mimic the current indexing behavior of returning a tuple of samples and times.


Discussion:

Resolving patch:
diff --git a/mne/_fiff/pick.py b/mne/_fiff/pick.py
--- a/mne/_fiff/pick.py
+++ b/mne/_fiff/pick.py
@@ -1299,9 +1299,9 @@ def _picks_to_idx(
             "(%r)" % (picks_on, orig_picks)
         )
     if (picks < -n_chan).any():
-        raise ValueError("All picks must be >= %d, got %r" % (-n_chan, orig_picks))
+        raise IndexError("All picks must be >= %d, got %r" % (-n_chan, orig_picks))
     if (picks >= n_chan).any():
-        raise ValueError(
+        raise IndexError(
             "All picks must be < n_%s (%d), got %r" % (picks_on, n_chan, orig_picks)
         )
     picks %= n_chan  # ensure positive


Test patch:
diff --git a/mne/_fiff/tests/test_pick.py b/mne/_fiff/tests/test_pick.py
--- a/mne/_fiff/tests/test_pick.py
+++ b/mne/_fiff/tests/test_pick.py
@@ -596,9 +596,9 @@ def test_picks_to_idx():
     # Name indexing
     assert_array_equal([2], _picks_to_idx(info, info["ch_names"][2]))
     assert_array_equal(np.arange(5, 9), _picks_to_idx(info, info["ch_names"][5:9]))
-    with pytest.raises(ValueError, match="must be >= "):
+    with pytest.raises(IndexError, match="must be >= "):
         _picks_to_idx(info, -len(picks) - 1)
-    with pytest.raises(ValueError, match="must be < "):
+    with pytest.raises(IndexError, match="must be < "):
         _picks_to_idx(info, len(picks))
     with pytest.raises(ValueError, match="could not be interpreted"):
         _picks_to_idx(info, ["a", "b"])
diff --git a/mne/channels/tests/test_channels.py b/mne/channels/tests/test_channels.py
--- a/mne/channels/tests/test_channels.py
+++ b/mne/channels/tests/test_channels.py
@@ -288,7 +288,7 @@ def test_read_ch_adjacency(tmp_path):
     assert_equal(x[0, 1], False)
     assert_equal(x[0, 2], True)
     assert np.all(x.diagonal())
-    pytest.raises(ValueError, read_ch_adjacency, mat_fname, [0, 3])
+    pytest.raises(IndexError, read_ch_adjacency, mat_fname, [0, 3])
     ch_adjacency, ch_names = read_ch_adjacency(mat_fname, picks=[0, 2])
     assert_equal(ch_adjacency.shape[0], 2)
     assert_equal(len(ch_names), 2)
diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py
--- a/mne/io/fiff/tests/test_raw_fiff.py
+++ b/mne/io/fiff/tests/test_raw_fiff.py
@@ -915,10 +915,19 @@ def test_getitem():
         )
         with pytest.raises(ValueError, match="No appropriate channels"):
             raw[slice(-len(raw.ch_names) - 1), slice(None)]
-        with pytest.raises(ValueError, match="must be"):
+        with pytest.raises(IndexError, match="must be"):
             raw[-1000]
 
 
+@testing.requires_testing_data
+def test_iter():
+    """Test iterating over Raw via __getitem__()."""
+    raw = read_raw_fif(fif_fname).pick("eeg")  # 60 EEG channels
+    for i, _ in enumerate(raw):  # iterate over channels
+        pass
+    assert i == 59  # 60 channels means iterating from 0 to 59
+
+
 @testing.requires_testing_data
 def test_proj(tmp_path):
     """Test SSP proj operations."""