Repository: mne-tools/mne-python

Issue:
BUG: resampling of events with raw instance
Given an `events`-array from the original file, `raw.resample` can return the new (resampled) events times. I found an insidious bug: the `raw.first_samp` is not taken into consideration when returning the new event samples (times). This results in the resampled events repeating the same time sample when `events[sid, 0] > raw_rsl._data.shape[1]`. PR will follow.


Discussion:

Resolving patch:
diff --git a/mne/io/base.py b/mne/io/base.py
--- a/mne/io/base.py
+++ b/mne/io/base.py
@@ -1422,7 +1422,8 @@ def resample(self, sfreq, npad='auto', window='boxcar', stim_picks=None,
                      If resampling the continuous data is desired, it is
                      recommended to construct events using the original data.
                      The event onsets can be jointly resampled with the raw
-                     data using the 'events' parameter.
+                     data using the 'events' parameter (a resampled copy is
+                     returned).
 
         Parameters
         ----------
@@ -1446,7 +1447,8 @@ def resample(self, sfreq, npad='auto', window='boxcar', stim_picks=None,
             is installed properly and CUDA is initialized.
         events : 2D array, shape (n_events, 3) | None
             An optional event matrix. When specified, the onsets of the events
-            are resampled jointly with the data.
+            are resampled jointly with the data. NB: The input events are not
+            modified, but a new array is returned with the raw instead.
         pad : str
             The type of padding to use. Supports all :func:`numpy.pad` ``mode``
             options. Can also be "reflect_limited" (default), which pads with a
@@ -1463,6 +1465,8 @@ def resample(self, sfreq, npad='auto', window='boxcar', stim_picks=None,
         -------
         raw : instance of Raw
             The resampled version of the raw object.
+        events : 2D array, shape (n_events, 3) | None
+            If events are jointly resampled, these are returned with the raw.
 
         See Also
         --------
@@ -1542,12 +1546,12 @@ def resample(self, sfreq, npad='auto', window='boxcar', stim_picks=None,
 
             return self
         else:
-            if copy:
-                events = events.copy()
+            # always make a copy of events
+            events = events.copy()
 
             events[:, 0] = np.minimum(
                 np.round(events[:, 0] * ratio).astype(int),
-                self._data.shape[1]
+                self._data.shape[1] + self.first_samp
             )
             return self, events
 


Test patch:
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
@@ -1028,11 +1028,22 @@ def test_resample():
         assert_true(len(w) == 1)
 
     # test resampling events: this should no longer give a warning
-    stim = [0, 1, 1, 0, 0, 1, 1, 0]
-    raw = RawArray([stim], create_info(1, len(stim), ['stim']))
+    # we often have first_samp != 0, include it here too
+    stim = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
+    # test is on half the sfreq, but should work with trickier ones too
+    o_sfreq, sfreq_ratio = len(stim), 0.5
+    n_sfreq = o_sfreq * sfreq_ratio
+    first_samp = len(stim) // 2
+    raw = RawArray([stim], create_info(1, o_sfreq, ['stim']),
+                   first_samp=first_samp)
     events = find_events(raw)
-    raw, events = raw.resample(4., events=events, npad='auto')
-    assert_equal(events, np.array([[0, 0, 1], [2, 0, 1]]))
+    raw, events = raw.resample(n_sfreq, events=events, npad='auto')
+    n_fsamp = int(first_samp * sfreq_ratio)  # how it's calc'd in base.py
+    # NB np.round used for rounding event times, which has 0.5 as corner case:
+    # https://docs.scipy.org/doc/numpy/reference/generated/numpy.around.html
+    assert_equal(events,
+                 np.array([[np.round(1 * sfreq_ratio) + n_fsamp, 0, 1],
+                           [np.round(10 * sfreq_ratio) + n_fsamp, 0, 1]]))
 
     # test copy flag
     stim = [1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]