diff --git a/doc/changes/dev/13556.new.rst b/doc/changes/dev/13556.new.rst new file mode 100644 index 00000000000..e3a6f53cb1c --- /dev/null +++ b/doc/changes/dev/13556.new.rst @@ -0,0 +1 @@ +Add ``on_drop_all`` parameter to :meth:`mne.Epochs.drop` to control behavior when all epochs are dropped. \ No newline at end of file diff --git a/mne/epochs.py b/mne/epochs.py index 4bd94ffa2c5..e925d3365b2 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1515,7 +1515,7 @@ def plot_image( ) @verbose - def drop(self, indices, reason="USER", verbose=None): + def drop(self, indices, reason="USER", verbose=None, on_drop_all="ignore"): """Drop epochs based on indices or boolean mask. .. note:: The indices refer to the current set of undropped epochs @@ -1537,6 +1537,12 @@ def drop(self, indices, reason="USER", verbose=None): Reason(s) for dropping the epochs ('ECG', 'timeout', 'blink' etc). Reason(s) are applied to all indices specified. Default: 'USER'. + on_drop_all : 'ignore' | 'warn' | 'raise' + Behavior when all epochs are dropped. + If 'ignore', no error is raised and the epochs object is empty. + If 'warn', a RuntimeWarning is emitted. + If 'raise', a ValueError is raised. + Default: 'ignore'. %(verbose)s Returns @@ -1569,6 +1575,18 @@ def drop(self, indices, reason="USER", verbose=None): ", ".join(map(str, np.sort(try_idx))), ) + if len(self.events) == 0: + msg = "All epochs dropped" + if on_drop_all == "raise": + raise ValueError(msg) + elif on_drop_all == "warn": + warn(msg) + elif on_drop_all != "ignore": + raise ValueError( + 'on_drop_all must be "warn", "raise" or "ignore", ' + f"got {on_drop_all}" + ) + return self def _get_epoch_from_raw(self, idx, verbose=None): diff --git a/mne/tests/test_epochs.py b/mne/tests/test_epochs.py index 91c5f902ac8..d8f69241b1b 100644 --- a/mne/tests/test_epochs.py +++ b/mne/tests/test_epochs.py @@ -5273,3 +5273,33 @@ def test_empty_error(method, epochs_empty): pytest.importorskip("pandas") with pytest.raises(RuntimeError, match="is empty."): getattr(epochs_empty.copy(), method[0])(**method[1]) + + +def test_drop_all_epochs(): + """Test on_drop_all parameter in Epochs.drop.""" + # Create tiny dummy data (3 epochs) + data = np.random.RandomState(0).randn(1, 1, 10) + info = create_info(["ch1"], 1000.0, "eeg") + epochs = EpochsArray(data, info) + + # 1. Test 'ignore' (default) + epochs.copy().drop([0]) + + # 2. Test 'warn' explicitly + # We expect a warning when dropping all epochs + with pytest.warns(RuntimeWarning, match="All epochs dropped"): + epochs.copy().drop([0], on_drop_all="warn") + + # 3. Test 'raise' + # We expect a ValueError when dropping all epochs + with pytest.raises(ValueError, match="All epochs dropped"): + epochs.copy().drop([0], on_drop_all="raise") + + # 4. Test 'ignore' explicitly + # Should run silently (no warning, no error) + epochs.copy().drop([0], on_drop_all="ignore") + + # 4. Test Typo + # We expect a ValueError because 'wrn' is not valid + with pytest.raises(ValueError, match="on_drop_all must be"): + epochs.copy().drop([0], on_drop_all="wrn")