Repository: mne-tools/mne-python

Issue:
mne.Epochs with event_repeated parameter throws RuntimeError
MNE 0.19 has a convenient event_repeated parameter in mne.Epochs. But it does not work, a RuntimeError is thrown when trying to use it.

#### Steps and/or code to reproduce
```Python
import mne
import numpy as np

nr_samples = 10000
nr_channels = 20
ch_names = ['chan%i' %i for i in range(nr_channels)]
info = mne.create_info(ch_names=ch_names, sfreq=1000)
data = np.zeros((nr_channels,nr_samples))
raw = mne.io.RawArray(data,info)

events = np.array([[1000, 0, 1],[1000, 0, 2]])
epo = mne.Epochs(raw,events,event_repeated='drop')
```


#### Expected results
An epoch structure should be created with the repeated events dropped.


#### Actual results
`RuntimeError: Event time samples were not unique. Consider setting the event_repeated parameter."`




Discussion:
@sappelhoff can you look?
I am on vacation right now with only my smartphone ... So it'll take 14 more days until i can look, sorry-------- Original Message --------Subject: Re: [mne-tools/mne-python] mne.Epochs with event_repeated parameter throws RuntimeError (#6842)From: Eric Larson To: mne-tools/mne-python CC: Stefan Appelhoff ,Mention @sappelhoff can you look?

—You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub, or mute the thread.
someone can look here at the mne sprint

Ok, I will work on it :)

Resolving patch:
diff --git a/mne/epochs.py b/mne/epochs.py
--- a/mne/epochs.py
+++ b/mne/epochs.py
@@ -184,7 +184,7 @@ def _save_split(epochs, fname, part_idx, n_parts, fmt):
     end_file(fid)
 
 
-def _merge_events(events, event_id):
+def _merge_events(events, event_id, selection):
     """Merge repeated events."""
     event_id = event_id.copy()
     new_events = events.copy()
@@ -241,17 +241,25 @@ def _merge_events(events, event_id):
 
     # Delete duplicate event idxs
     new_events = np.delete(new_events, event_idxs_to_delete, 0)
+    new_selection = np.delete(selection, event_idxs_to_delete, 0)
 
-    return new_events, event_id
+    return new_events, event_id, new_selection
 
 
-def _handle_event_repeated(events, event_id, event_repeated):
-    """Handle repeated events."""
+def _handle_event_repeated(events, event_id, event_repeated, selection,
+                           drop_log):
+    """Handle repeated events.
+
+    Note that drop_log will be modified inplace
+    """
+    assert len(events) == len(selection)
+    selection = np.asarray(selection)
+
     unique_events, u_ev_idxs = np.unique(events[:, 0], return_index=True)
 
     # Return early if no duplicates
     if len(unique_events) == len(events):
-        return events, event_id
+        return events, event_id, selection, drop_log
 
     # Else, we have duplicates. Triage ...
     _check_option('event_repeated', event_repeated, ['error', 'drop', 'merge'])
@@ -263,17 +271,26 @@ def _handle_event_repeated(events, event_id, event_repeated):
         logger.info('Multiple event values for single event times found. '
                     'Keeping the first occurrence and dropping all others.')
         new_events = events[u_ev_idxs]
-
+        new_selection = selection[u_ev_idxs]
+        drop_ev_idxs = np.setdiff1d(selection, new_selection)
+        for idx in drop_ev_idxs:
+            drop_log[idx].append('DROP DUPLICATE')
+        selection = new_selection
     elif event_repeated == 'merge':
         logger.info('Multiple event values for single event times found. '
                     'Creating new event value to reflect simultaneous events.')
-        new_events, event_id = _merge_events(events, event_id)
+        new_events, event_id, new_selection = \
+            _merge_events(events, event_id, selection)
+        drop_ev_idxs = np.setdiff1d(selection, new_selection)
+        for idx in drop_ev_idxs:
+            drop_log[idx].append('MERGE DUPLICATE')
+        selection = new_selection
 
     # Remove obsolete kv-pairs from event_id after handling
     keys = new_events[:, 1:].flatten()
     event_id = {k: v for k, v in event_id.items() if v in keys}
 
-    return new_events, event_id
+    return new_events, event_id, selection, drop_log
 
 
 @fill_doc
@@ -404,11 +421,12 @@ def __init__(self, info, data, events, event_id=None, tmin=-0.2, tmax=0.5,
                                                     max(self.selection) + 1))]
             else:
                 self.drop_log = drop_log
+
             events = events[selected]
 
-            events, self.event_id = _handle_event_repeated(events,
-                                                           self.event_id,
-                                                           event_repeated)
+            events, self.event_id, self.selection, self.drop_log = \
+                _handle_event_repeated(events, self.event_id, event_repeated,
+                                       self.selection, self.drop_log)
 
             n_events = len(events)
             if n_events > 1:
@@ -1930,7 +1948,7 @@ def __init__(self, raw, events, event_id=None, tmin=-0.2, tmax=0.5,
             flat=flat, decim=decim, reject_tmin=reject_tmin,
             reject_tmax=reject_tmax, detrend=detrend,
             proj=proj, on_missing=on_missing, preload_at_end=preload,
-            verbose=verbose)
+            event_repeated=event_repeated, verbose=verbose)
 
     @verbose
     def _get_epoch_from_raw(self, idx, verbose=None):


Test patch:
diff --git a/mne/tests/test_epochs.py b/mne/tests/test_epochs.py
--- a/mne/tests/test_epochs.py
+++ b/mne/tests/test_epochs.py
@@ -59,6 +59,24 @@
 rng = np.random.RandomState(42)
 
 
+def test_event_repeated():
+    """Test epochs takes into account repeated events"""
+    n_samples = 100
+    n_channels = 2
+    ch_names = ['chan%i' % i for i in range(n_channels)]
+    info = mne.create_info(ch_names=ch_names, sfreq=1000.)
+    data = np.zeros((n_channels, n_samples))
+    raw = mne.io.RawArray(data, info)
+
+    events = np.array([[10, 0, 1], [10, 0, 2]])
+    epochs = mne.Epochs(raw, events, event_repeated='drop')
+    assert epochs.drop_log == [[], ['DROP DUPLICATE']]
+    assert_array_equal(epochs.selection, [0])
+    epochs = mne.Epochs(raw, events, event_repeated='merge')
+    assert epochs.drop_log == [[], ['MERGE DUPLICATE']]
+    assert_array_equal(epochs.selection, [0])
+
+
 def test_handle_event_repeated():
     """Test handling of repeated events."""
     # A general test case
@@ -67,24 +85,38 @@ def test_handle_event_repeated():
                        [3, 0, 2], [3, 0, 1],
                        [5, 0, 2], [5, 0, 1], [5, 0, 3],
                        [7, 0, 1]])
-
+    SELECTION = np.arange(len(EVENTS))
+    DROP_LOG = [list() for _ in range(len(EVENTS))]
     with pytest.raises(RuntimeError, match='Event time samples were not uniq'):
-        _handle_event_repeated(EVENTS, EVENT_ID, event_repeated='error')
+        _handle_event_repeated(EVENTS, EVENT_ID, event_repeated='error',
+                               selection=SELECTION,
+                               drop_log=deepcopy(DROP_LOG))
 
-    events, event_id = _handle_event_repeated(EVENTS, EVENT_ID, 'drop')
+    events, event_id, selection, drop_log = _handle_event_repeated(
+        EVENTS, EVENT_ID, 'drop', SELECTION, deepcopy(DROP_LOG))
     assert_array_equal(events, [[0, 0, 1], [3, 0, 2], [5, 0, 2], [7, 0, 1]])
+    assert_array_equal(events, EVENTS[selection])
+    unselection = np.setdiff1d(SELECTION, selection)
+    assert all(drop_log[k] == ['DROP DUPLICATE'] for k in unselection)
     assert event_id == {'aud': 1, 'vis': 2}
 
-    events, event_id = _handle_event_repeated(EVENTS, EVENT_ID, 'merge')
+    events, event_id, selection, drop_log = _handle_event_repeated(
+        EVENTS, EVENT_ID, 'merge', SELECTION, deepcopy(DROP_LOG))
     assert_array_equal(events[0][-1], events[1][-1])
     assert_array_equal(events, [[0, 0, 4], [3, 0, 4], [5, 0, 5], [7, 0, 1]])
+    assert_array_equal(events[:, :2], EVENTS[selection][:, :2])
+    unselection = np.setdiff1d(SELECTION, selection)
+    assert all(drop_log[k] == ['MERGE DUPLICATE'] for k in unselection)
     assert set(event_id.keys()) == set(['aud', 'aud/vis', 'aud/foo/vis'])
     assert event_id['aud/vis'] == 4
 
     # Test early return with no changes: no error for wrong event_repeated arg
     fine_events = np.array([[0, 0, 1], [1, 0, 2]])
-    events, event_id = _handle_event_repeated(fine_events, EVENT_ID, 'no')
+    events, event_id, selection, drop_log = _handle_event_repeated(
+        fine_events, EVENT_ID, 'no', [0, 2], deepcopy(DROP_LOG))
     assert event_id == EVENT_ID
+    assert_array_equal(selection, [0, 2])
+    assert drop_log == DROP_LOG
     assert_array_equal(events, fine_events)
     del fine_events
 
@@ -93,27 +125,34 @@ def test_handle_event_repeated():
     # take components, sort, and join on "/"
     # should make new event_id value: 5 (because 1,2,3,4 are taken)
     heterogeneous_events = np.array([[0, 3, 2], [0, 4, 1]])
-    events, event_id = _handle_event_repeated(heterogeneous_events,
-                                              EVENT_ID, 'merge')
+    events, event_id, selection, drop_log = _handle_event_repeated(
+        heterogeneous_events, EVENT_ID, 'merge', [0, 1], deepcopy(DROP_LOG))
     assert set(event_id.keys()) == set(['aud/vis'])
     assert event_id['aud/vis'] == 5
+    assert_array_equal(selection, [0])
+    assert drop_log[1] == ['MERGE DUPLICATE']
     assert_array_equal(events, np.array([[0, 0, 5], ]))
     del heterogeneous_events
 
     # Test keeping a homogeneous "prior-to-event" code (=events[:, 1])
     homogeneous_events = np.array([[0, 99, 1], [0, 99, 2],
                                    [1, 0, 1], [2, 0, 2]])
-    events, event_id = _handle_event_repeated(homogeneous_events,
-                                              EVENT_ID, 'merge')
+    events, event_id, selection, drop_log = _handle_event_repeated(
+        homogeneous_events, EVENT_ID, 'merge', [1, 3, 4, 7],
+        deepcopy(DROP_LOG))
     assert set(event_id.keys()) == set(['aud', 'vis', 'aud/vis'])
     assert_array_equal(events, np.array([[0, 99, 4], [1, 0, 1], [2, 0, 2]]))
+    assert_array_equal(selection, [1, 4, 7])
+    assert drop_log[3] == ['MERGE DUPLICATE']
     del homogeneous_events
 
     # Test dropping instead of merging, if event_codes to be merged are equal
     equal_events = np.array([[0, 0, 1], [0, 0, 1]])
-    events, event_id = _handle_event_repeated(equal_events,
-                                              EVENT_ID, 'merge')
+    events, event_id, selection, drop_log = _handle_event_repeated(
+        equal_events, EVENT_ID, 'merge', [3, 5], deepcopy(DROP_LOG))
     assert_array_equal(events, np.array([[0, 0, 1], ]))
+    assert_array_equal(selection, [3])
+    assert drop_log[5] == ['MERGE DUPLICATE']
     assert set(event_id.keys()) == set(['aud'])