Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1963240

Browse files
committed
work on pantompkins
1 parent 1c86979 commit 1963240

File tree

1 file changed

+120
-96
lines changed

1 file changed

+120
-96
lines changed

wfdb/processing/ecg/peakdetect.py

Lines changed: 120 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -38,76 +38,84 @@ def detect_qrs_static(self):
3838
self.alignsignals()
3939

4040

41-
# Initialize parameters via the two learning phases
41+
# Initialize learning parameters via the two learning phases
4242
self.learnparams()
4343

44-
# Loop through every index and detect qrs locations
45-
for i in range(self.siglen):
46-
pass
44+
# Loop through every index and detect qrs locations.
45+
# Start from 200ms after the first qrs detected in the
46+
# learning phase
47+
for i in range(self.qrs_inds[0]+41, self.siglen):
4748

48-
# Go through each index and detect qrs complexes.
49-
for i in range(siglen):
50-
# Determine whether the current index is a peak
51-
# for each signal
52-
is_peak_F = ispeak(sig_F, siglen, i, 20)
53-
is_peak_I = ispeak(sig_I, siglen, i, 20)
54-
55-
# Whether the current index is a signal peak or noise peak
56-
is_sigpeak_F = False
57-
is_noisepeak_F = False
58-
is_sigpeak_I = False
59-
is_noisepeak_I = False
60-
61-
# If peaks are detected, classify them as signal or noise
62-
# for their respective channels
63-
64-
if is_peak_F:
65-
# Satisfied signal peak criteria for the channel
66-
# but not necessarily overall
67-
if sig_F[i] > thresh_F:
68-
is_sigpeak_F = True
69-
# Did not satisfy signal peak criteria.
70-
# Label as noise peak
71-
else:
72-
is_noisepeak_F = True
73-
74-
if is_peak_I:
75-
# The
76-
if sig_I[i] > thresh_I:
77-
is_peak_sig_I = True
78-
else:
79-
80-
# Check for double signal peak coincidence and at least >200ms (40 samples samples)
81-
# since the previous r peak
82-
is_sigpeak = is_sigpeak_F and is_sigpeak_I and ()
83-
84-
# Found a signal peak!
85-
if is_sigpeak:
86-
87-
# BUT WAIT, THERE'S MORE! It could be a T-Wave ...
49+
# Number of indices from the previous r peak to this index
50+
last_r_distance = i - qrs_inds[-1]
51+
52+
# Determine whether the current index is a peak
53+
# for each signal
54+
is_peak_F = ispeak(sig_F, self.siglen, i, 20)
55+
is_peak_I = ispeak(sig_I, self.siglen, i, 20)
8856

89-
# When an rr interval < 360ms (72 samples), it is checked
90-
# to determine whether it is a T-Wave
91-
if i - prev_r_ind < 360:
92-
57+
# Whether the current index is a signal peak or noise peak
58+
is_sigpeak_F = False
59+
is_sigpeak_I = False
60+
is_noisepeak_F = False
61+
is_noisepeak_I = False
9362

94-
# Update running parameters
63+
# If peaks are detected, classify them as signal or noise
64+
# for their respective channels
9565

96-
sigpeak_I = 0.875*sigpeak_I + 0.125*sig_I[i]
97-
sigpeak_F = 0.875*sigpeak_I + 0.125*sig_I[i]
66+
if is_peak_F:
67+
# Satisfied signal peak criteria for the channel
68+
if sig_F[i] > self.thresh_F:
69+
is_sigpeak_F = True
70+
# Did not satisfy signal peak criteria.
71+
# Label as noise peak
72+
else:
73+
is_noisepeak_F = True
74+
75+
if is_peak_I:
76+
# Satisfied signal peak criteria for the channel
77+
if sig_I[i] > self.thresh_I:
78+
is_peak_sig_I = True
79+
# Did not satisfy signal peak criteria.
80+
# Label as noise peak
81+
else:
82+
is_noisepeak_I = True
9883

99-
last_r_ind = i
100-
rr_limitavg
101-
rr_limitavg
84+
# Check for double signal peak coincidence and at least >200ms (40 samples samples)
85+
# since the previous r peak
86+
is_sigpeak = is_sigpeak_F and is_sigpeak_I and (last_r_distance>40)
87+
88+
# The peak crosses thresholds for each channel and >200ms from previous peak
89+
if is_sigpeak:
90+
91+
# If the rr interval < 360ms (72 samples), the peak is checked
92+
# to determine whether it is a T-Wave. This is the final test
93+
# to run before the peak can be marked as a qrs complex.
94+
if last_r_distance < 72:
95+
96+
97+
# "If the maximal slope that occurs during this waveform
98+
# is less than half that of the QRS waveform that preceded it,
99+
# it is identified to be a T-wave"
100+
101+
# Update running parameters
102+
103+
sigpeak_I = 0.875*sigpeak_I + 0.125*sig_I[i]
104+
sigpeak_F = 0.875*sigpeak_I + 0.125*sig_I[i]
105+
106+
last_r_ind = i
107+
rr_limitavg
108+
rr_limitavg
109+
110+
111+
# Not a signal peak. Update running parameters
112+
# if any other peaks are detected
113+
elif is_peak_F:
114+
102115

103-
# Not a signal peak. Update running parameters
104-
# if any other peaks are detected
105-
elif is_peak_F:
116+
last_r_distance = i - last_r_ind
106117

107-
108-
last_r_distance = i - last_r_ind
109-
110-
if last_r_distance >
118+
if last_r_distance >
111119

112120

113121
qrs = np.zeros(10)
@@ -118,14 +126,15 @@ def detect_qrs_static(self):
118126
qrs = qrs.astype('int64')
119127

120128

121-
return qrs
129+
return
122130

123131

124132

125133

126134
def resample(self):
127135
if self.fs != 200:
128136
self.sig = scisig.resample(self.sig, int(self.siglen*200/fs))
137+
return
129138

130139
# Bandpass filter the signal from 5-15Hz
131140
def bandpass(self, plotsteps):
@@ -144,6 +153,7 @@ def bandpass(self, plotsteps):
144153
plt.plot(self.sig_F)
145154
plt.legend(['After LP', 'After LP+HP'])
146155
plt.show()
156+
return
147157

148158
# Compute the moving wave integration waveform from the filtered signal
149159
def mwi(sig, plotsteps):
@@ -166,17 +176,20 @@ def mwi(sig, plotsteps):
166176
plt.plot(self.sig_I)
167177
plt.legend(['deriv', 'mwi'])
168178
plt.show()
179+
return
169180

170181
# Align the filtered and integrated signal with the original
171182
def alignsignals(self):
172183
self.sig_F = self.sig_F
173184

174185
self.sig_I = self.sig_I
175186

187+
return
188+
176189

177190
def learnparams(self):
178191
"""
179-
Initialize parameters using the start of the waveforms
192+
Initialize detection parameters using the start of the waveforms
180193
during the two learning phases described.
181194
182195
"Learning phase 1 requires about 2s to initialize
@@ -192,18 +205,19 @@ def learnparams(self):
192205
This code is not detailed in the Pan-Tompkins
193206
paper. The PT algorithm requires a threshold to
194207
categorize peaks as signal or noise, but the
195-
threshold is calculated from noise and signal
196-
peaks. There is a circular dependency when
197-
none of the fields are initialized. Therefore this learning phase will detect signal peaks using a
198-
different method, and estimate the threshold using those peaks.
208+
threshold is calculated from noise and signal
209+
peaks. There is a circular dependency when
210+
none of the fields are initialized. Therefore this
211+
learning phase will detect initial signal peaks using a
212+
different method, and estimate the threshold using
213+
those peaks.
199214
200215
This function works as follows:
201216
- Try to find at least 2 signal peaks (qrs complexes) in the
202-
first N seconds of both signals using simple low order
217+
first 2 seconds of both signals using simple low order
203218
moments. Signal peaks are only defined when the same index is
204-
determined to be a peak in both signals. Start with N==2.
205-
If fewer than 2 signal peaks are detected, increment N and
206-
try again.
219+
determined to be a peak in both signals. If fewer than 2 signal
220+
peaks are detected, shift to the next 2 window and try again.
207221
- Using the classified estimated peaks, threshold1 is estimated as
208222
based on the steady state estimate equation: thres = 0.75*noisepeak + 0.25*sigpeak
209223
using the mean of the noisepeaks and signalpeaks instead of the
@@ -216,10 +230,12 @@ def learnparams(self):
216230
radius = 20
217231
# The signal start duration to use for learning
218232
learntime = 2
219-
220-
while :
221-
wavelearn_F = filtsig[:200*learntime]
222-
wavelearn_I = mwi[:200*learntime]
233+
# The window number to inspect in the signal
234+
windownum = 0
235+
236+
while (windownum+1)*learntime*200<self.siglen:
237+
wavelearn_F = self.sig_F[windownum*learntime*200:(windownum+1)*learntime*200]
238+
wavelearn_I = self.sig_I[windownum*learntime*200:(windownum+1)*learntime*200]
223239

224240
# Find peaks in the signals
225241
peakinds_F = findpeaks_radius(wavelearn_F, radius)
@@ -242,43 +258,51 @@ def learnparams(self):
242258
noisepeakinds_F = np.setdiff1d(peakinds_F, sigpeakinds)
243259
noisepeakinds_I = np.setdiff1d(peakinds_I, sigpeakinds)
244260

245-
if len(sigpeakinds)>1:
261+
# Found at least 2 peaks. Also peak 1 and 2 must be >200ms apart
262+
if len(sigpeakinds)>1 and sigpeakinds[1]-sigpeakinds[0]>40:
246263
break
247264

248-
# Need to detect at least 2 peaks
249-
learntime = learntime + 1
265+
# Need to detect at least 2 peaks. Check the next window.
266+
windownum = windownum + 1
250267

251268
# Found at least 2 peaks. Use them to set parameters.
252-
269+
253270
# Set running peak estimates to first values
254-
sigpeak_F = wavelearn_F[sigpeakinds[0]]
255-
sigpeak_I = wavelearn_I[sigpeakinds[0]]
256-
noisepeak_F = wavelearn_F[noisepeakinds_F[0]]
257-
noisepeak_I = wavelearn_I[noisepeakinds_I[0]]
271+
self.sigpeak_F = wavelearn_F[sigpeakinds[0]]
272+
self.sigpeak_I = wavelearn_I[sigpeakinds[0]]
273+
self.noisepeak_F = wavelearn_F[noisepeakinds_F[0]]
274+
self.noisepeak_I = wavelearn_I[noisepeakinds_I[0]]
258275

259276
# Use all signal and noise peaks in learning window to estimate threshold
260277
# Based on steady state equation: thres = 0.75*noisepeak + 0.25*sigpeak
261-
thres_F = 0.75*np.mean(wavelearn_F[noisepeakinds_F]) + 0.25*np.mean(wavelearn_F[sigpeakinds_F])
262-
thres_I = 0.75*np.mean(wavelearn_I[noisepeakinds_I]) + 0.25*np.mean(wavelearn_I[sigpeakinds_I])
278+
self.thres_F = 0.75*np.mean(wavelearn_F[noisepeakinds_F]) + 0.25*np.mean(wavelearn_F[sigpeakinds_F])
279+
self.thres_I = 0.75*np.mean(wavelearn_I[noisepeakinds_I]) + 0.25*np.mean(wavelearn_I[sigpeakinds_I])
263280
# Alternatively, could skip all of that and do something very simple like thresh_F = max(filtsig[:400])/3
264281

265282
# Set the r-r history using the first r-r interval
266283
# The most recent 8 rr intervals
267-
rr_history_unbound = [wavelearn_F[sigpeakinds[1]]-wavelearn_F[sigpeakinds[0]]]*8
284+
self.rr_history_unbound = [wavelearn_F[sigpeakinds[1]]-wavelearn_F[sigpeakinds[0]]]*8
268285
# The most recent 8 rr intervals that fall within the acceptable low and high rr interval limits
269-
rr_history_bound = [wavelearn_I[sigpeakinds[1]]-wavelearn_I[sigpeakinds[0]]]*8
286+
self.rr_history_bound = [wavelearn_I[sigpeakinds[1]]-wavelearn_I[sigpeakinds[0]]]*8
270287

271-
rr_average_unbound = np.mean(rr_history_unbound)
272-
rr_average_bound = np.mean(rr_history_bound)
288+
self.rr_average_unbound = np.mean(self.rr_history_unbound)
289+
self.rr_average_bound = np.mean(self.rr_history_bound)
273290

274-
# Wait... what is rr_average_unbound for then?
275-
rr_low_limit = 0.92*rr_average_bound
276-
rr_high_limit = 1.16*rr_average_bound
277-
rr_missed_limit = 1.66*rr_average_bound
291+
# what is rr_average_unbound ever used for?
292+
self.rr_low_limit = 0.92*rr_average_bound
293+
self.rr_high_limit = 1.16*rr_average_bound
294+
self.rr_missed_limit = 1.66*rr_average_bound
278295

279-
return thresh_F, thresh_I, sigpeak_F, sigpeak_I,
280-
noisepeak_F, noisepeak_I, rr_freeavg_F,
281-
r_freeavg_I, rr_limitavg_F, rr_limitavg_I
296+
# The previous r index. Used for:
297+
# - refractory period (200ms, 40 samples)
298+
# - t-wave inspection (360ms, 72 samples)
299+
# - triggering searchback for missing r peaks (1.66*rr_average_bound)
300+
self.prev_r_ind = self.firstpeakind
301+
302+
# The qrs indices detected
303+
self.qrs_inds = [sigpeakinds[0]]
304+
305+
return
282306

283307

284308

0 commit comments

Comments
 (0)