ICA find_bads_ecg Function

Hi everyone, I have been using the find_bads_ecg function to automatically identify ICA components with ECG artifacts. I noticed that the default frequency parameters were: 1) a lowpass frequency of 8 Hz and 2) a highpass frequency of 16 Hz.

I am curious as to how these default frequency parameters were chosen since the normal range for ECG waveforms is 0.5-3 Hz. I am also curious as to how these filters are being applied in order to identify ECG components.

Information:

  • MNE version: 1.0.3
  • operating system: Windows 11

I appreciate your help!

This function starts by computing the source (IC) time series on epochs around ECG artifacts. Epochs centered around ECG events are created with mne.preprocessing.create_ecg_epochs. This is where the low and high pass frequencies are used.

I don’t exactly know how this is beneficial to QRS detection. Still, if you look at an ECG channel filtered between (8, 16) Hz, the ECG event location is apparent and most likely easy to detect with a peak detection algorithm.

Thank you for this explanation.

I attempted to filter an ECG channel between 8 and 16 Hz, and in my example, it looks like the peaks have been filtered out.

Before filtering:

After filtering:

Since these peaks have been filtered out, I am not sure if the detection of ECG events would be accurate. Would it be possible to try a different frequency range to ensure that these peaks do not get filtered out (and ECG events can be accurately detected)?

Your plot looks exactly like mine, with this kind of 2 peak/1 peak ondulation. Honestly, I have no idea how the QRS detector implemented works, if you want to dig in, it’s define here.

Also, there is definitely weird lines and filter definition.

  • The ECG events are found with find_ecg_events which defaults to l_freq=5, h_freq=35.
  • The ECG epochs are created with create_ecg_epochs which defaults to l_freq=8, h_freq=16 and calls find_ecg_events with those different filter settings.
  • find_ecg_events calls the qrs_detector function with l_freq=None, h_freq=None despite this function having also l_freq=5, h_freq=35 as defaults.

This last point makes sense because at this point the channel is already filtered, but then why do we have those 2 filter parameters at the qrs_detector level since they are not even used :wink:


Regardless of this weirdness, let’s just test on an raw (unfiltered) ECG channel. I took one of my files recorded on an ANT Neuro ampifier with an ECG channel on the AUX8 channel:

from matplotlib import pyplot as plt
from mne.io import read_raw_fif
from mne.preprocessing import find_ecg_events


raw = read_raw_fif(
    "my-raw-file-with-an-ECG-channel-raw.fif", preload=False
)
raw.pick_channels(["AUX8"])
raw.set_channel_types({"AUX8": "ecg"})
raw.rename_channels({"AUX8": "ECG"})
raw.crop(0, 30)
raw.load_data()

# default for find_ecg_events (5, 35) Hz bandpass
ecg_events1, _, _ = find_ecg_events(
    raw, ch_name="ECG", l_freq=5, h_freq=35
)
# default for create_ecg_epochs (8, 16) Hz bandpass
ecg_events2, _, _ = find_ecg_events(
    raw, ch_name="ECG", l_freq=8, h_freq=16
)

# plot
f, ax = plt.subplots(2, 1, sharex=True, sharey=True)
ax[0].plot(raw.get_data()[0, :])
ax[1].plot(raw.get_data()[0, :])

for event in ecg_events1:
    ax[0].axvline(event[0], color="teal", linestyle="--")
for event in ecg_events2:
    ax[1].axvline(event[0], color="teal", linestyle="--")
    
# format
ax[0].set_title("BP (5, 35) Hz")
ax[1].set_title("BP (8, 16) Hz")
f.tight_layout()

And here is the figure:

Looks like both filter yield a very accurate QRS detection. Again, I did not dig in on how the qrs_detector function actually works, but it works.

1 Like