Skip to content

Commit 1b03e6a

Browse files
hoechenbergerlarsoner
authored andcommitted
Make find_ecg_events work even if detection fails (#9236)
Fixes #9225
1 parent 0defe0a commit 1b03e6a

File tree

3 files changed

+39
-15
lines changed

3 files changed

+39
-15
lines changed

doc/changes/0.22.inc

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Bugs
3030

3131
- Fix bug with :func:`mne.SourceEstimate.plot` and related functions where the scalars were not interactively updated properly (:gh:`8985` by `Eric Larson`_)
3232

33+
- :func:`mne.preprocessing.find_ecg_events` now correctly handles situation where no ECG activity could be detected, and correctly returns an empty array of ECG events (:gh:`9236` by `Richard Höchenberger`_)
34+
3335
.. _changes_0_22:
3436

3537
Version 0.22.0

mne/preprocessing/ecg.py

+26-15
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def qrs_detector(sfreq, ecg, thresh_value=0.6, levels=2.5, n_thresh=3,
4949
Returns
5050
-------
5151
events : array
52-
Indices of ECG peaks
52+
Indices of ECG peaks.
5353
"""
5454
win_size = int(round((60.0 * sfreq) / 120.0))
5555

@@ -112,20 +112,27 @@ def qrs_detector(sfreq, ecg, thresh_value=0.6, levels=2.5, n_thresh=3,
112112
ce = time[b[a < n_thresh]]
113113

114114
ce += n_samples_start
115-
clean_events.append(ce)
116-
117-
# pick the best threshold; first get effective heart rates
118-
rates = np.array([60. * len(cev) / (len(ecg) / float(sfreq))
119-
for cev in clean_events])
120-
121-
# now find heart rates that seem reasonable (infant through adult athlete)
122-
idx = np.where(np.logical_and(rates <= 160., rates >= 40.))[0]
123-
if len(idx) > 0:
124-
ideal_rate = np.median(rates[idx]) # get close to the median
115+
if ce.size > 0: # We actually found an event
116+
clean_events.append(ce)
117+
118+
if clean_events:
119+
# pick the best threshold; first get effective heart rates
120+
rates = np.array([60. * len(cev) / (len(ecg) / float(sfreq))
121+
for cev in clean_events])
122+
123+
# now find heart rates that seem reasonable (infant through adult
124+
# athlete)
125+
idx = np.where(np.logical_and(rates <= 160., rates >= 40.))[0]
126+
if idx.size > 0:
127+
ideal_rate = np.median(rates[idx]) # get close to the median
128+
else:
129+
ideal_rate = 80. # get close to a reasonable default
130+
131+
idx = np.argmin(np.abs(rates - ideal_rate))
132+
clean_events = clean_events[idx]
125133
else:
126-
ideal_rate = 80. # get close to a reasonable default
127-
idx = np.argmin(np.abs(rates - ideal_rate))
128-
clean_events = clean_events[idx]
134+
clean_events = np.array([])
135+
129136
return clean_events
130137

131138

@@ -219,7 +226,11 @@ def find_ecg_events(raw, event_id=999, ch_name=None, tstart=0.0,
219226
remap[offset:offset + this_len] = np.arange(start, stop)
220227
offset += this_len
221228
assert offset == len(ecg)
222-
ecg_events = remap[ecg_events]
229+
230+
if ecg_events.size > 0:
231+
ecg_events = remap[ecg_events]
232+
else:
233+
ecg_events = np.array([])
223234

224235
n_events = len(ecg_events)
225236
duration_sec = len(ecg) / raw.info['sfreq'] - tstart

mne/preprocessing/tests/test_ecg.py

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os.path as op
22
import pytest
3+
import numpy as np
34

45
from mne.io import read_raw_fif
56
from mne import pick_types
@@ -84,3 +85,13 @@ def test_find_ecg():
8485
assert len(ecg_epochs.events) == n_events
8586
assert 'ECG-SYN' not in raw.ch_names
8687
assert 'ECG-SYN' not in ecg_epochs.ch_names
88+
89+
# Test behavior if no peaks can be found -> achieve this by providing
90+
# all-zero'd data
91+
raw._data[ecg_idx] = 0.
92+
ecg_events, _, average_pulse, ecg = find_ecg_events(
93+
raw, ch_name=raw.ch_names[ecg_idx], return_ecg=True
94+
)
95+
assert ecg_events.size == 0
96+
assert average_pulse == 0
97+
assert np.allclose(ecg, np.zeros_like(ecg))

0 commit comments

Comments
 (0)