Skip to content

Warn when Epochs events fall outside the raw data range (#12989)#14004

Open
CedricConday wants to merge 12 commits into
mne-tools:mainfrom
CedricConday:fix/warn-out-of-bounds-events
Open

Warn when Epochs events fall outside the raw data range (#12989)#14004
CedricConday wants to merge 12 commits into
mne-tools:mainfrom
CedricConday:fix/warn-out-of-bounds-events

Conversation

@CedricConday

@CedricConday CedricConday commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #12989

What was wrong

When mne.Epochs is created from an events array containing sample numbers outside the data, the corresponding epochs are silently dropped with no indication that the events were out of bounds. This is easy to hit when events was generated at a different sampling frequency, or contains samples before first_samp.

Fix

At construction, compute each epoch's sample window (the same start/stop math used by _get_epoch_from_raw) and warn if any fall before the start or past the end of the raw data, reporting how many.

Test

Adds test_epochs_warn_out_of_bounds_events: an event past the end of the data now raises a RuntimeWarning. Verified it does not warn on main (the silent-drop behavior) and warns with this change.


AI-assisted, human-reviewed — I'm an AI engineer; I find, fix, and test with AI (Claude Code), then review and verify before opening.

@tsbinns

tsbinns commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Thanks for opening this PR @CedricConday!

I agree with the discussion in the issue that there should be an option to control whether a warning is emitted. That would also be in line with how we handle the similar situation for setting annotations in Raw objects that are outside the data boundaries:

mne-python/mne/io/base.py

Lines 714 to 719 in 90b8cee

def set_annotations(
self, annotations, emit_warning=True, on_missing="raise", *, verbose=None
):
"""Setter for annotations.
This setter checks if they are inside the data range.

CedricConday added a commit to CedricConday/mne-python that referenced this pull request Jun 30, 2026
Mirrors the on_missing pattern used for out-of-range Raw annotations
(_on_missing helper): 'raise' | 'warn' (default) | 'ignore'. Addresses
maintainer review on mne-toolsgh-14004.
@CedricConday

Copy link
Copy Markdown
Contributor Author

Thanks @tsbinns! Added an on_outside parameter ('raise' | 'warn' | 'ignore', default 'warn') routed through the _on_missing helper, mirroring the out-of-range Raw annotations handling you linked. Updated the docstring, changelog entry, and tests (warn / raise / ignore + the in-range no-warn case). Happy to change the default or the name if you'd prefer.

@tsbinns tsbinns left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the extra control @CedricConday!
Just a few comments.

Comment thread mne/epochs.py Outdated
Comment on lines +3662 to +3683
# Warn when an event's *sample number* falls outside the recorded data
# (before ``first_samp`` or at/after the last sample). Such an event yields
# no epoch and is silently dropped, which is surprising and usually means
# the events were created at a different sampling frequency or precede
# ``first_samp``. This checks the event positions, not the epoch window —
# an in-range event whose window merely clips the edge is normal and quiet.
# See gh-12989.
if self._raw is not None and len(self.events) > 0:
lo = self._raw.first_samp
hi = lo + self._raw.n_times
n_oob = int(((self.events[:, 0] < lo) | (self.events[:, 0] >= hi)).sum())
if n_oob:
_on_missing(
on_outside,
f"{n_oob} event{_pl(n_oob)} {'has' if n_oob == 1 else 'have'} a "
"sample number outside the recorded data; the corresponding "
f"epoch{_pl(n_oob)} will be dropped. This can happen if the "
"events were created at a different sampling frequency, or "
"contain sample numbers before first_samp.",
name="on_outside",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest including this within BaseEpochs, as we'd want this warning for both the Epochs and EpochsArray classes.

Also, might be worth only checking for this after selection has been applied in BaseEpochs.

Comment thread mne/epochs.py
Comment on lines 3596 to 3598
event_repeated="error",
on_outside="warn",
verbose=None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
event_repeated="error",
on_outside="warn",
verbose=None,
event_repeated="error",
*,
on_outside="warn",
verbose=None,

Should make sure this new param is kwarg only.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, when the param is added to EpochsArray, it should be as kwarg-only (like some others there).

Comment thread mne/tests/test_epochs.py
Comment on lines +5297 to +5303
with warnings.catch_warnings():
warnings.simplefilter("error")
mne.Epochs(raw, oob, tmin=-0.2, tmax=0.5, baseline=None, on_outside="ignore")
# an in-range event whose epoch window merely clips the edge must NOT warn
with warnings.catch_warnings():
warnings.simplefilter("error")
mne.Epochs(raw, np.array([[990, 0, 1]]), tmin=0, tmax=0.5, baseline=None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warnings count as errors in our tests, so we don't need to catch them.

@tsbinns

tsbinns commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

@CedricConday Please also link your GitHub account to CircleCI so that the documentation build will run, and we can verify that looks good.

CedricConday and others added 11 commits July 3, 2026 10:50
Events whose epoch window lies (partly) outside the raw data are silently
dropped, which is surprising e.g. when events come from a different sampling
frequency or contain samples before first_samp. Warn at construction listing
how many events are out of bounds. Adds a regression test.
Warn only when an event's sample number falls outside the recorded data
([first_samp, first_samp + n_times)) — the issue's actual scenario (events
at a different sampling frequency, or before first_samp). The earlier check
on the epoch *window* also fired for in-range events whose window merely
clips the edge (wide tmax / negative tmin on short data), which is normal
and broke several existing tests. Full test_epochs.py is green under -W error.
Mirrors the on_missing pattern used for out-of-range Raw annotations
(_on_missing helper): 'raise' | 'warn' (default) | 'ignore'. Addresses
maintainer review on mne-toolsgh-14004.
…x test

- on_outside made keyword-only in both Epochs and EpochsArray
- Warning logic moved from Epochs to BaseEpochs._oob_check for reuse
- EpochsArray now accepts and forwards on_outside to BaseEpochs
- Test uses pytest.raises instead of pytest.warns per mne warnings-as-errors policy
…-as-errors, add missing BaseEpochs on_outside doc
…event file (mne-toolsgh-12989)

test_warnings and test_n_components_none pass the complete event file against a
shorter/cropped raw, so some events fall past the data and are dropped — expected
here. Pass on_outside='ignore' at those call sites so the new default warning does
not trip warnings-as-errors. No behavior change to the tests' actual assertions.
@CedricConday CedricConday force-pushed the fix/warn-out-of-bounds-events branch from ed3a816 to 11f6a3b Compare July 3, 2026 11:43
@CedricConday

Copy link
Copy Markdown
Contributor Author

Thanks for the review @tsbinns — I've addressed all of it: on_outside is now keyword-only on both Epochs and EpochsArray, the check moved into BaseEpochs (so EpochsArray is covered) and runs after selection is applied, and the test no longer catches the warning. I also traced the CI matrix failures to three ICA tests that feed the full event file to a shorter raw — they now pass on_outside="ignore", since dropping the out-of-range events is expected there. Ready for another look when you have a moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Warn if event sample number is out of bound for raw when creating Epochs

2 participants