-
-
Notifications
You must be signed in to change notification settings - Fork 437
/
Copy pathecg_clean.py
275 lines (214 loc) · 10.4 KB
/
ecg_clean.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# -*- coding: utf-8 -*-
from warnings import warn
import numpy as np
import pandas as pd
import scipy.signal
from ..misc import NeuroKitWarning, as_vector
from ..signal import signal_filter
def ecg_clean(ecg_signal, sampling_rate=1000, method="neurokit", **kwargs):
"""**ECG Signal Cleaning**
Clean an ECG signal to remove noise and improve peak-detection accuracy. Different cleaning
method are implemented.
* ``'neurokit'`` (default): 0.5 Hz high-pass butterworth filter (order = 5), followed by
powerline filtering (see ``signal_filter()``). By default, ``powerline = 50``.
* ``'biosppy'``: Same as in the biosppy package. **Please help providing a better description!**
* ``'pantompkins1985'``: Method used in Pan & Tompkins (1985). **Please help providing a better
description!**
* ``'hamilton2002'``: Method used in Hamilton (2002). **Please help providing a better
description!**
* ``'elgendi2010'``: Method used in Elgendi et al. (2010). **Please help providing a better
description!**
* ``'engzeemod2012'``: Method used in Engelse & Zeelenberg (1979). **Please help providing a
better description!**
Parameters
----------
ecg_signal : Union[list, np.array, pd.Series]
The raw ECG channel.
sampling_rate : int
The sampling frequency of ``ecg_signal`` (in Hz, i.e., samples/second). Defaults to 1000.
method : str
The processing pipeline to apply. Can be one of ``"neurokit"`` (default),
``"biosppy"``, ``"pantompkins1985"``, ``"hamilton2002"``, ``"elgendi2010"``,
``"engzeemod2012"``.
**kwargs
Other arguments to be passed to specific methods.
Returns
-------
array
Vector containing the cleaned ECG signal.
See Also
--------
ecg_peaks, ecg_process, ecg_plot, .signal_rate, .signal_filter
Examples
--------
.. ipython:: python
import pandas as pd
import neurokit2 as nk
import matplotlib.pyplot as plt
ecg = nk.ecg_simulate(duration=10, sampling_rate=1000)
signals = pd.DataFrame({"ECG_Raw" : ecg,
"ECG_NeuroKit" : nk.ecg_clean(ecg, sampling_rate=1000, method="neurokit"),
"ECG_BioSPPy" : nk.ecg_clean(ecg, sampling_rate=1000, method="biosppy"),
"ECG_PanTompkins" : nk.ecg_clean(ecg, sampling_rate=1000, method="pantompkins1985"),
"ECG_Hamilton" : nk.ecg_clean(ecg, sampling_rate=1000, method="hamilton2002"),
"ECG_Elgendi" : nk.ecg_clean(ecg, sampling_rate=1000, method="elgendi2010"),
"ECG_EngZeeMod" : nk.ecg_clean(ecg, sampling_rate=1000, method="engzeemod2012")})
@savefig p_ecg_clean.png scale=100%
signals.plot()
References
--------------
* Engelse, W. A., & Zeelenberg, C. (1979). A single scan algorithm for QRS-detection and
feature extraction. Computers in cardiology, 6(1979), 37-42.
* Pan, J., & Tompkins, W. J. (1985). A real-time QRS detection algorithm. IEEE transactions
on biomedical engineering, (3), 230-236.
* Hamilton, P. (2002). Open source ECG analysis. In Computers in cardiology (pp. 101-104).
IEEE.
* Elgendi, M., Jonkman, M., & De Boer, F. (2010). Frequency Bands Effects on QRS Detection.
Biosignals, Proceedings of the Third International Conference on Bio-inspired Systems and
Signal Processing, 428-431.
"""
ecg_signal = as_vector(ecg_signal)
# Missing data
n_missing = np.sum(np.isnan(ecg_signal))
if n_missing > 0:
warn(
"There are " + str(n_missing) + " missing data points in your signal."
" Filling missing values by using the forward filling method.",
category=NeuroKitWarning,
)
ecg_signal = _ecg_clean_missing(ecg_signal)
method = method.lower() # remove capitalised letters
if method in ["nk", "nk2", "neurokit", "neurokit2"]:
clean = _ecg_clean_nk(ecg_signal, sampling_rate, **kwargs)
elif method in ["biosppy", "gamboa2008"]:
clean = _ecg_clean_biosppy(ecg_signal, sampling_rate)
elif method in ["pantompkins", "pantompkins1985"]:
clean = _ecg_clean_pantompkins(ecg_signal, sampling_rate)
elif method in ["hamilton", "hamilton2002"]:
clean = _ecg_clean_hamilton(ecg_signal, sampling_rate)
elif method in ["elgendi", "elgendi2010"]:
clean = _ecg_clean_elgendi(ecg_signal, sampling_rate)
elif method in ["engzee", "engzee2012", "engzeemod", "engzeemod2012"]:
clean = _ecg_clean_engzee(ecg_signal, sampling_rate)
elif method in ["vg", "vgraph", "koka2022"]:
clean = _ecg_clean_vgraph(ecg_signal, sampling_rate)
elif method in [
"christov",
"christov2004",
"ssf",
"slopesumfunction",
"zong",
"zong2003",
"kalidas2017",
"swt",
"kalidas",
"kalidastamil",
"kalidastamil2017"
]:
clean = ecg_signal
else:
raise ValueError(
"NeuroKit error: ecg_clean(): 'method' should be "
"one of 'neurokit', 'biosppy', 'pantompkins1985',"
" 'hamilton2002', 'elgendi2010', 'engzeemod2012'."
)
return clean
# =============================================================================
# Handle missing data
# =============================================================================
def _ecg_clean_missing(ecg_signal):
ecg_signal = pd.DataFrame.pad(pd.Series(ecg_signal))
return ecg_signal
# =============================================================================
# NeuroKit
# =============================================================================
def _ecg_clean_nk(ecg_signal, sampling_rate=1000, **kwargs):
# Remove slow drift and dc offset with highpass Butterworth.
clean = signal_filter(signal=ecg_signal, sampling_rate=sampling_rate, lowcut=0.5, method="butterworth", order=5)
clean = signal_filter(signal=clean, sampling_rate=sampling_rate, method="powerline", **kwargs)
return clean
# =============================================================================
# Biosppy
# =============================================================================
def _ecg_clean_biosppy(ecg_signal, sampling_rate=1000):
"""Adapted from https://github.com/PIA-
Group/BioSPPy/blob/e65da30f6379852ecb98f8e2e0c9b4b5175416c3/biosppy/signals/ecg.py#L69."""
order = int(0.3 * sampling_rate)
if order % 2 == 0:
order += 1 # Enforce odd number
# -> filter_signal()
frequency = [3, 45]
# -> get_filter()
# -> _norm_freq()
frequency = 2 * np.array(frequency) / sampling_rate # Normalize frequency to Nyquist Frequency (Fs/2).
# -> get coeffs
a = np.array([1])
b = scipy.signal.firwin(numtaps=order, cutoff=frequency, pass_zero=False)
# _filter_signal()
filtered = scipy.signal.filtfilt(b, a, ecg_signal)
return filtered
# =============================================================================
# Pan & Tompkins (1985)
# =============================================================================
def _ecg_clean_pantompkins(ecg_signal, sampling_rate=1000):
"""Adapted from https://github.com/PIA-
Group/BioSPPy/blob/e65da30f6379852ecb98f8e2e0c9b4b5175416c3/biosppy/signals/ecg.py#L69."""
order = 1
clean = signal_filter(
signal=ecg_signal, sampling_rate=sampling_rate, lowcut=5, highcut=15, method="butterworth_zi", order=order
)
return clean # Return filtered
# =============================================================================
# Elgendi et al. (2010)
# =============================================================================
def _ecg_clean_elgendi(ecg_signal, sampling_rate=1000):
"""From https://github.com/berndporr/py-ecg-detectors/
- Elgendi, Mohamed & Jonkman, Mirjam & De Boer, Friso. (2010). Frequency Bands Effects on QRS
Detection. The 3rd International Conference on Bio-inspired Systems and Signal Processing
(BIOSIGNALS2010). 428-431.
"""
order = 2
clean = signal_filter(
signal=ecg_signal, sampling_rate=sampling_rate, lowcut=8, highcut=20, method="butterworth_zi", order=order
)
return clean # Return filtered
# =============================================================================
# Hamilton (2002)
# =============================================================================
def _ecg_clean_hamilton(ecg_signal, sampling_rate=1000):
"""Adapted from https://github.com/PIA-
Group/BioSPPy/blob/e65da30f6379852ecb98f8e2e0c9b4b5175416c3/biosppy/signals/ecg.py#L69."""
order = 1
clean = signal_filter(
signal=ecg_signal, sampling_rate=sampling_rate, lowcut=8, highcut=16, method="butterworth_zi", order=order
)
return clean # Return filtered
# =============================================================================
# Engzee Modified (2012)
# =============================================================================
def _ecg_clean_engzee(ecg_signal, sampling_rate=1000):
"""From https://github.com/berndporr/py-ecg-detectors/
- C. Zeelenberg, A single scan algorithm for QRS detection and feature extraction, IEEE Comp.
in Cardiology, vol. 6, pp. 37-42, 1979.
- A. Lourenco, H. Silva, P. Leite, R. Lourenco and A. Fred, "Real Time Electrocardiogram Segmentation
for Finger Based ECG Biometrics", BIOSIGNALS 2012, pp. 49-54, 2012.
"""
order = 4
clean = signal_filter(
signal=ecg_signal, sampling_rate=sampling_rate, lowcut=52, highcut=48, method="butterworth_zi", order=order
)
return clean # Return filtered
# =============================================================================
# Engzee Modified (2012)
# =============================================================================
def _ecg_clean_vgraph(ecg_signal, sampling_rate=1000):
"""Filtering used by Taulant Koka and Michael Muma (2022).
References
----------
- T. Koka and M. Muma (2022), Fast and Sample Accurate R-Peak Detection for Noisy ECG Using
Visibility Graphs. In: 2022 44th Annual International Conference of the IEEE Engineering
in Medicine & Biology Society (EMBC). Uses the Pan and Tompkins thresholding.
"""
order = 2
clean = signal_filter(signal=ecg_signal, sampling_rate=sampling_rate, lowcut=4, method="butterworth", order=order)
return clean # Return filtered