find_ecg_events on noisy ECG channel

  • MNE version: 1.5.1
  • operating system: e.g. Ubuntu 24.04

Hey all, I need to detect R-peaks from a pretty noisy ECG channel that came from one of our simultaneous EEG-fMRI experiments. I’m mostly hoping to get more details about the algorithm that find_ecg_events and qrs_detector are using to detect R-peak events. But any advice on how to reliably obtain R-peaks from ECG recorded inside fMRi in general would be greatly appreciated (it’s my first time working with EEG data recorded inside fMRI).

Some details about my application:

  • The only preprocessing done on this data thus far has been downsampling, applying a carbon wire loop regression (we have CW channels) to remove balliso-cardiographic artifacts, and removing gradient artifacts.
  • I’m using minimal code to detect R-peaks with MNE:
raw = mne.io.read_raw('sub-011_ses-001_task-ExperienceSampling_run-001_eeg_Bergen_CWreg.set')
raw.set_channel_types({'ECG':'ecg'})
ecg_events = find_ecg_events(raw)
  • Running this for one representative subject and plotting the results reveals maybe this algorithm did … okay? (very hard to independently confirm accuracy of such a detection algorithm in noisy data =/).

  • See attached visualization of two different time windows from the same subject for representative points in the detection that look problematic. Detected R-peak events from find_ecg_events are represented with vertical dashed lines. Panel A highlights with the green arrow what stands out to me as likely to be an R peak, but a few seconds later the algorithm detects the large artifact (red arrow; maybe the T wave?). Panel B demonstrates the algorithm detecting a bizarre negative deflection (red arrow).

  • This leads me to wonder which algorithm find_ecg_events is using to detect R-peaks. I’m still early in my reading the literature on this, but at least one paper (Wang et al., 2018) suggests using the k-Teager energy operator (see excerpt from section “QRS Peak Detection” from p. 3 below)

The earliest ECG R-peaks detection algorithm was put forward by Allen et al. (1998) in simple thresholding detections. However, they were defective due to time-consumption and being poorly robust in the MR environment. Thus, Christov (2004) proposed a combined adaptive threshold algorithm for the detection of the ECG R-peak. Niazy et al. (2005) modified this algorithm by computing a complex lead from some ECG channels. The ECG channel should be band-pass filtered from 7 to 40 Hz firstly, and then the complex lead was calculated by using a k-Teager energy operator (k-TEO) (Mukhopadhyay and Ray, 1998; Kim et al., 2004) to the filtered ECG channel and by setting all negative values to zero, (see Equation 1):

ECG(n) = max(E2(n) − E(n − k)E(n + k), 0) (1)

Where ECG is the complex lead, n is the time index, E is the filtered ECG, and k is a frequency selection parameter (Kim et al., 2004). Next, the combined adaptive thresholding algorithm was applied (Christov, 2004) to detect the peaks. The sum of the steep-slope threshold (M), the integrating threshold for high-frequency signal components (F), and the beat expectation threshold (R) are used as the MFR threshold, (see Equation 2):

MFR = M + F + R (2)

When ECG(n)≥MFR(n), it will be detected as a QRS peak. For more information about the QRS peak detection, please check the original paper (Niazy et al., 2005).

So k-TEO algorithm first cleans the ECG by squaring each time point and subtracting from it the product of two proximal timepoints at the edges of some window k. Then it applies this adaptive thresholding algorithm, which I don’t quite understand. But I wonder how this compares to what’s happening in find_ecg_events?

Thanks!