Skip to content

liegepr/Two_moving_average_qrs_detector

Repository files navigation

A two moving average qrs detector with optional temporal correction

  • References

    • Elgendi, Mohamed; Eskofier, Björn; Dokos, Socrates; Abbott, Derek; Amaral, Luís A. Nunes. (2014): Revisiting QRS Detection Methodologies for Portable, Wearable, Battery-Operated, and Wireless ECG Systems. In PLoS ONE 9 (1), e84018. DOI: 10.1371/journal.pone.0084018.
    • Elgendi, Mohamed; Jonkman, Mirjam; Boer, Friso de (2009): Improved QRS Detection Algorithm using Dynamic Thresholds. In International Journal of Hybrid Information Technology 2, pp. 65–80.
    • Elgendi, Mohamed; Jonkman, Mirjam; DeBoer, Friso (Eds.) (2010): Frequency Bands Effects on QRS Detection. International Conference on Bio-inspired Systems and Signal Processing.
    • Elgendi, Mohamed; Talkachova, Alena (2013): Fast QRS Detection with an Optimized Knowledge-Based Method. Evaluation on 11 Standard ECG Databases. In PLoS ONE 8 (9), e73557. DOI: 10.1371/journal.pone.0073557.
    • Gradl, Stefan; Leutheuser, Heike; Elgendi, Mohamed; Lang, Nadine; Eskofier, Bjoern M. (2015): Temporal correction of detected R-peaks in ECG signals. A crucial step to improve QRS detection algorithms. In Annual International Conference of the IEEE Engineering in Medicine and Biology Society. IEEE Engineering in Medicine and Biology Society. Annual International Conference 2015, pp. 522–525. DOI: 10.1109/embc.2015.7318414.
    • Porr, Bernd; Howell, Luis (2019): R-peak detector stress test with a new noisy ECG database reveals significant performance differences amongst popular detectors: Cold Spring Harbor Laboratory.

Implementation details

  • qrs_detector_2ma.r and qrs_detector_2ma_mod.r both return a data table containing the input signal, all intermediate calculations including filtered and squared signal, windows and blocks along with detected qrs (loc).
  • One additional column (loc_sr) is reported if the user needs slackness reduction.
  • The modified version (qrs_detector_2ma_mod.r) attempts to detect the very first and very last beats which are missed by design in the original version. The implementation of slackness reduction for these marginal peaks was found effective in most cases but though I am not saying it is 100% safe.
  • Two dependencies only: packages gsignal (butter() & filtfilt()) and data.table (fread(), data.table(), fifelse(), rleid(), frollingmeans() & foverlaps() + faster computation times) are required.
  • No for-loops are being used.

Validation against the Glasgow University Database (GUDB)

High precision ECG Database with annotated R peaks, recorded and filmed under realistic conditions (https://researchdata.gla.ac.uk/716/ and https://github.com/berndporr/ECG-GUDB) Howell, L. and Porr, B. (2018) High precision ECG Database with annotated R peaks, recorded and filmed under realistic conditions. University of Glascow Datacite DOI: 10.5525/gla.researchdata.716

  • All recordings with annotations were tested: 123 for the chest strap and 106 for the loose cable setup.

  • Without slackness correction

    • With zero tolerance
task channel TPR F1
hand_bike cable 0.3458 0.3454
hand_bike cheststrap 0.3253 0.3236
jogging cable 0.2661 0.2692
jogging cheststrap 0.2797 0.2822
maths cable 0.3487 0.3484
maths cheststrap 0.4023 0.4022
sitting cable 0.3394 0.3393
sitting cheststrap 0.3922 0.3924
walking cable 0.2908 0.2905
walking cheststrap 0.3505 0.3504
  • Using a 40 ms tolerance window

According to Porr & Howell, the default tolerance is a tenth of the sampling rate as may be read in the Physionet comparison algorithms. For a 250 Hz sampling rate, a tolerance of 40 ms corresponds to (40e-3) * 250 = 10 samples. The detection is said to be right shifted (Porr & Howell 2019) but our sample plots show some cases of left-shifted detection. So, we used a tolerance interval which is symmetrical around the reference annotation. The WFBD application guide (WAG.pdf) says that the match window specifies the maximum absolute difference in annotation times that is permitted for matching annotations. Its default value used by the bxb function is 0.15 seconds which is way too large.

task channel TPR F1
hand_bike cable 0.9949 0.9933
hand_bike cheststrap 0.9846 0.9788
jogging cable 0.9433 0.9519
jogging cheststrap 0.9569 0.9628
maths cable 0.9996 0.9988
maths cheststrap 0.9665 0.9664
sitting cable 1 0.9995
sitting cheststrap 0.9678 0.9679
walking cable 0.9869 0.9852
walking cheststrap 0.9686 0.9681
  • Using slackness correction

    • With zero tolerance
task channel TPR F1
hand_bike cable 0.9926 0.9911
hand_bike cheststrap 0.9832 0.9774
jogging cable 0.9381 0.9466
jogging cheststrap 0.9529 0.9587
maths cable 0.9994 0.9986
maths cheststrap 0.9665 0.9664
sitting cable 1 0.9995
sitting cheststrap 0.9668 0.9669
walking cable 0.9851 0.9835
walking cheststrap 0.9678 0.9673
  • Using a 40 ms tolerance window
task channel TPR F1
hand_bike cable 0.9949 0.9933
hand_bike cheststrap 0.9846 0.9788
jogging cable 0.9433 0.9519
jogging cheststrap 0.9569 0.9628
maths cable 0.9996 0.9988
maths cheststrap 0.9665 0.9664
sitting cable 1 0.9995
sitting cheststrap 0.9678 0.9679
walking cable 0.9869 0.9852
walking cheststrap 0.9686 0.9681
  • Compare with the detect_rpeaks() function from the rsleep package

    • With zero tolerance
task channel TPR F1
hand_bike cable 0.1519 0.1516
hand_bike cheststrap 0.2479 0.2444
jogging cable 0.06779 0.07407
jogging cheststrap 0.1936 0.1999
maths cable 0.1692 0.1691
maths cheststrap 0.3334 0.3272
sitting cable 0.1922 0.1923
sitting cheststrap 0.371 0.3538
walking cable 0.07615 0.07591
walking cheststrap 0.3008 0.2974
  • Using a 40 ms tolerance window
task channel TPR F1
hand_bike cable 0.9903 0.9905
hand_bike cheststrap 0.9787 0.9552
jogging cable 0.8355 0.8799
jogging cheststrap 0.8662 0.9047
maths cable 0.9989 0.9985
maths cheststrap 0.9852 0.9662
sitting cable 0.9981 0.9985
sitting cheststrap 0.9952 0.9539
walking cable 0.9897 0.99
walking cheststrap 0.9933 0.9715
> library(rbenchmark)
> benchmark(
>     dtbr <- apply(sample_list, 1, valid_fn, freq_sampling = 250L,  slred = TRUE)
> )
> test replications  elapsed  relative  user.self  sys.self  user.child  sys.child
>             100   776.02         1     299.06     27.72          NA         NA

plot1 rightend

plot2 detail