Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ Changelog

- :func:`mne.write_evokeds` will now accept :class:`mne.Evoked` objects with differing channel orders in ``info['bads']``, which would previously raise an exception by `Richard Höchenberger`_

- The ``reject_tmin`` and ``reject_tmax`` parameters of :class:`mne.Epochs` are now taken into account when using the ``reject_by_annotation`` parameter by `Stefan Appelhoff`_

Bug
~~~

Expand Down
27 changes: 22 additions & 5 deletions mne/epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2115,23 +2115,40 @@ def _get_epoch_from_raw(self, idx, verbose=None):
Returns
-------
data : array | str | None
If string it's details on rejection reason.
If None it means no data.
If string, it's details on rejection reason.
If array, it's the data in the desired range (good segment)
If None, it means no data is available.
"""
if self._raw is None:
# This should never happen, as raw=None only if preload=True
raise ValueError('An error has occurred, no valid raw file found.'
' Please report this to the mne-python '
raise ValueError('An error has occurred, no valid raw file found. '
'Please report this to the mne-python '
'developers.')
sfreq = self._raw.info['sfreq']
event_samp = self.events[idx, 0]
# Read a data segment
# Read a data segment from "start" to "stop" in samples
first_samp = self._raw.first_samp
start = int(round(event_samp + self._raw_times[0] * sfreq))
start -= first_samp
stop = start + len(self._raw_times)

# reject_tmin, and reject_tmax need to be converted to samples to
# check the reject_by_annotation boundaries: reject_start, reject_stop
reject_tmin = self.reject_tmin
if reject_tmin is None:
reject_tmin = self._raw_times[0]
reject_start = int(round(event_samp + reject_tmin * sfreq))
reject_start -= first_samp

reject_tmax = self.reject_tmax
if reject_tmax is None:
reject_tmax = self._raw_times[-1]
diff = int(round((self._raw_times[-1] - reject_tmax) * sfreq))
reject_stop = stop - diff

logger.debug(' Getting epoch for %d-%d' % (start, stop))
data = self._raw._check_bad_segment(start, stop, self.picks,
reject_start, reject_stop,
self.reject_by_annotation)
return data

Expand Down
9 changes: 7 additions & 2 deletions mne/io/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
raise NotImplementedError

def _check_bad_segment(self, start, stop, picks,
reject_start, reject_stop,
reject_by_annotation=False):
"""Check if data segment is bad.

Expand All @@ -446,6 +447,10 @@ def _check_bad_segment(self, start, stop, picks,
End of the slice.
picks : array of int
Channel picks.
reject_start : int
First sample to check for overlaps with bad annotations.
reject_stop : int
Last sample to check for overlaps with bad annotations.
reject_by_annotation : bool
Whether to perform rejection based on annotations.
False by default.
Expand All @@ -462,9 +467,9 @@ def _check_bad_segment(self, start, stop, picks,
annot = self.annotations
sfreq = self.info['sfreq']
onset = _sync_onset(self, annot.onset)
overlaps = np.where(onset < stop / sfreq)
overlaps = np.where(onset < reject_stop / sfreq)
overlaps = np.where(onset[overlaps] + annot.duration[overlaps] >
start / sfreq)
reject_start / sfreq)
for descr in annot.description[overlaps]:
if descr.lower().startswith('bad'):
return descr
Expand Down
26 changes: 26 additions & 0 deletions mne/tests/test_epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,32 @@ def test_reject():
raw.set_annotations(None)


def test_reject_by_annotations_reject_tmin_reject_tmax():
"""Test reject_by_annotations with reject_tmin and reject_tmax defined."""
# 10 seconds of data, event at 2s, bad segment from 1s to 1.5s
info = mne.create_info(ch_names=['test_a'], sfreq=1000, ch_types='eeg')
raw = mne.io.RawArray(np.atleast_2d(np.arange(0, 10, 1 / 1000)), info=info)
events = np.array([[2000, 0, 1]])
raw.set_annotations(mne.Annotations(1, 0.5, 'BAD'))

# Make the epoch based on the event at 2s, so from 1s to 3s ... assert it
# is rejected due to bad segment overlap from 1s to 1.5s
epochs = mne.Epochs(raw, events, tmin=-1, tmax=1,
preload=True, reject_by_annotation=True)
assert len(epochs) == 0

# Setting `reject_tmin` to prevent rejection of epoch.
epochs = mne.Epochs(raw, events, tmin=-1, tmax=1, reject_tmin=-0.2,
preload=True, reject_by_annotation=True)
assert len(epochs) == 1

# Same check but bad segment overlapping from 2.5s to 3s: use `reject_tmax`
raw.set_annotations(mne.Annotations(2.5, 0.5, 'BAD'))
epochs = mne.Epochs(raw, events, tmin=-1, tmax=1, reject_tmax=0.4,
preload=True, reject_by_annotation=True)
assert len(epochs) == 1


def test_own_data():
"""Test for epochs data ownership (gh-5346)."""
raw, events = _get_data()[:2]
Expand Down