Repository: mne-tools/mne-python

Issue:
[ENH] Add epoch rejection function to EpochsArray
Tried to monkey patch `EpochsArray` into dropping epochs as follows

```
reject = dict(mag=5e-12, grad=4000e-13)
epochs = mne.epochs.EpochsArray(
        X, info, events, event_id=dict(cond1=1, cond2=-1), tmin=-.5)
epochs.reject = reject
epochs._bad_dropped = False
```

Didn't work and it raised

`ValueError: An error has occurred, no valid raw file found. Please report this to the mne-python developers.`

@dengemann 



Discussion:
we should add reject param to EpochsArray constructor and allow to drop bads (populating drop_log etc...)

nice catch @eickenberg !

@agramfort @mluessi @Eric89GXL this is not going to be fun. Our dropping routine uses `_get_epoch_from_disk` which requires a `raw` object ... we need to restructure a few things to make this happen.

probably just use `_is_good_epoch` while looping over data.


Resolving patch:
diff --git a/mne/epochs.py b/mne/epochs.py
--- a/mne/epochs.py
+++ b/mne/epochs.py
@@ -1580,14 +1580,37 @@ class EpochsArray(Epochs):
         in the list are used. If None, all events will be used with
         and a dict is created with string integer names corresponding
         to the event id integers.
+    reject : dict
+        Epoch rejection parameters based on peak to peak amplitude.
+        Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg'.
+        If reject is None then no rejection is done.
+        Values are float. Example::
+
+            reject = dict(grad=4000e-13, # T / m (gradiometers)
+                          mag=4e-12, # T (magnetometers)
+                          eeg=40e-6, # uV (EEG channels)
+                          eog=250e-6 # uV (EOG channels)
+                          )
+
+    flat : dict
+        Epoch rejection parameters based on flatness of signal
+        Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg'
+        If flat is None then no rejection is done.
+    reject_tmin : scalar | None
+        Start of the time window used to reject epochs (with the default None,
+        the window will start with tmin).
+    reject_tmax : scalar | None
+        End of the time window used to reject epochs (with the default None,
+        the window will end with tmax).
     verbose : bool, str, int, or None
         If not None, override default verbose level (see mne.verbose).
         Defaults to raw.verbose.
     """
 
     @verbose
-    def __init__(self, data, info, events, event_id=None, tmin=0,
-                 verbose=None):
+    def __init__(self, data, info, events, tmin=0, event_id=None,
+                 reject=None, flat=None, reject_tmin=None,
+                 reject_tmax=None, verbose=None):
 
         dtype = np.complex128 if np.any(np.iscomplex(data)) else np.float64
         data = np.asanyarray(data, dtype=dtype)
@@ -1626,12 +1649,31 @@ def __init__(self, data, info, events, event_id=None, tmin=0,
         self.selection = np.arange(len(events))
         self.picks = None
         self.times = (np.arange(data.shape[-1], dtype=np.float) /
-                                info['sfreq'] + tmin)
+                      info['sfreq'] + tmin)
         self.tmin = self.times[0]
         self.tmax = self.times[-1]
         self.verbose = verbose
         self.name = 'Unknown'
         self._projector = None
+        self.reject = reject
+        self.flat = flat
+        self.reject_tmin = reject_tmin
+        self.reject_tmax = reject_tmax
+        self._reject_setup()
+        drop_inds = list()
+        if self.reject is not None or self.flat is not None:
+            for i_epoch, epoch in enumerate(self):
+                is_good, chan = self._is_good_epoch(epoch,
+                                                    verbose=self.verbose)
+                if not is_good:
+                    drop_inds.append(i_epoch)
+                    self.drop_log[i_epoch].extend(chan)
+        if drop_inds:
+            select = np.ones(len(events), dtype=np.bool)
+            select[drop_inds] = False
+            self.events = self.events[select]
+            self._data = self._data[select]
+            self.selection[select]
 
 
 def combine_event_ids(epochs, old_event_ids, new_event_id, copy=True):


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
@@ -1131,7 +1131,7 @@ def test_array_epochs():
 
     # creating
     rng = np.random.RandomState(42)
-    data = rng.random_sample((10, 20, 50))
+    data = rng.random_sample((10, 20, 300))
     sfreq = 1e3
     ch_names = ['EEG %03d' % (i + 1) for i in range(20)]
     types = ['eeg'] * 20
@@ -1161,3 +1161,13 @@ def test_array_epochs():
     # indexing
     assert_array_equal(np.unique(epochs['a'].events[:, 2]), np.array([1]))
     assert_equal(len(epochs[:2]), 2)
+    data[0, 5, 150] = 3000
+    data[1, :, :] = 0
+    data[2, 5, 210] = 3000
+    data[3, 5, 260] = 0
+    epochs = EpochsArray(data, info, events=events, event_id=event_id,
+                         tmin=0, reject=dict(eeg=1000), flat=dict(eeg=1e-1),
+                         reject_tmin=0.1, reject_tmax=0.2)
+    assert_equal(len(epochs), len(events) - 2)
+    assert_equal(epochs.drop_log[0], ['EEG 006'])
+    assert_equal(len(events), len(epochs.selection))