@@ -37,366 +37,3 @@ def find_peaks(x):
37
37
i += 1
38
38
soft_peaks = numpy .asarray (soft_peaks )+ 1
39
39
return hard_peaks , soft_peaks
40
-
41
-
42
- class GQRS (object ):
43
- def putann (self , annotation ):
44
- self .annotations .append (copy .deepcopy (annotation ))
45
-
46
- def detect (self , x , freq , adczero , threshold = 1.0 ):
47
- self .c = Conf (ADC_units = 1.0 , freq = freq )
48
- self .annotations = []
49
- self .sample_valid = False
50
-
51
- if freq < 50 :
52
- print ("Sampling frequency is too low!" )
53
- return
54
-
55
- if len (x ) < 1 :
56
- return []
57
-
58
- self .x = x
59
- self .freq = freq
60
- self .adczero = adczero
61
-
62
- self .qfv = numpy .zeros ((self .c ._BUFLN ))
63
- self .smv = numpy .zeros ((self .c ._BUFLN ))
64
- self .v1 = 0
65
-
66
- t0 = 0
67
- tf = len (x )- 1
68
- self .t = 0 - self .c .dt4
69
-
70
- self .annot = Annotation (0 , "NOTE" , 0 , 0 )
71
- self .putann (self .annot )
72
-
73
- # Cicular buffer of Peaks
74
- first_peak = Peak (0 ,0 ,0 )
75
- tmp = first_peak
76
- for _ in range (1 , self .c ._NPEAKS ):
77
- tmp .next_peak = Peak (0 ,0 ,0 )
78
- tmp .next_peak .prev_peak = tmp
79
- tmp = tmp .next_peak
80
- tmp .next_peak = first_peak
81
- first_peak .prev_peak = tmp
82
- self .current_peak = first_peak
83
-
84
- if self .c .spm > self .c ._BUFLN :
85
- if tf - t0 > self .c ._BUFLN :
86
- tf_learn = t0 + self .c ._BUFLN - self .c .dt4
87
- else :
88
- tf_learn = tf - self .c .dt4
89
- else :
90
- if tf - t0 > self .c .spm :
91
- tf_learn = t0 + self .c .spm - self .c .dt4
92
- else :
93
- tf_learn = tf - self .c .dt4
94
-
95
- self .state = "LEARNING"
96
- self .gqrs (t0 , tf_learn )
97
-
98
- self .rewind_gqrs ()
99
-
100
- self .state = "RUNNING"
101
- t = t0 - self .c .dt4
102
- self .gqrs (t0 , tf )
103
-
104
- def rewind_gqrs (self ):
105
- self .countdown = - 1
106
- self .annot .time = 0
107
- self .annot .type = "NORMAL"
108
- self .annot .subtype = 0
109
- self .annot .num = 0
110
- p = self .current_peak
111
- for _ in range (self .c ._NPEAKS ):
112
- p .time = 0
113
- p .type = 0
114
- p .amp = 0
115
- p = p .next_peak
116
-
117
- def at (self , t ):
118
- if t < 0 :
119
- self .sample_valid = False
120
- return None
121
- if t > len (self .x )- 1 :
122
- self .sample_valid = False
123
- return None
124
- return self .x [t ]
125
-
126
-
127
- def smv_at (self , t ):
128
- return self .smv [t & (self .c ._BUFLN - 1 )]
129
-
130
- def smv_put (self , t , v ):
131
- self .smv [t & (self .c ._BUFLN - 1 )] = v
132
-
133
- def qfv_at (self , t ):
134
- return self .qfv [t & (self .c ._BUFLN - 1 )]
135
-
136
- def qfv_put (self , t , v ):
137
- self .qfv [t & (self .c ._BUFLN - 1 )] = v
138
-
139
- def sm (self , t ): # CHECKED!
140
- # implements a trapezoidal low pass (smoothing) filter
141
- # (with a gain of 4*smdt) applied to input signal sig
142
- # before the QRS matched filter qf().
143
- # Before attempting to 'rewind' by more than BUFLN-smdt
144
- # samples, reset smt and smt0.
145
- smt = self .c .smt
146
- smdt = self .c .smdt
147
-
148
- while self .t > smt :
149
- smt += 1
150
- if smt > smt0 :
151
- self .smv_put (smt , self .smv_at (smt - 1 ) + \
152
- self .at (smt + smdt ) + self .at (smt + smdt - 1 ) - \
153
- self .at (smt - smdt ) + self .at (smt - smdt - 1 ))
154
- else :
155
- for j in range (1 , smdt ):
156
- v = self .at (smt ) + self .at (smt + j ) + self .at (smt - j )
157
- self .smv_put (smt , (v << 1 ) + self .at (smt + j ) + self .at (smt - j ) - \
158
- self .adczero * (smdt << 2 ))
159
- self .c .smt = smt
160
-
161
- return self .smv_at (t )
162
-
163
- def qf (self , t ): # CHECKED!
164
- # evaluate the QRS detector filter for the next sample
165
-
166
- bufln = self .c ._BUFLN - 1
167
-
168
- # do this first, to ensure that all of the other smoothed values needed below are in the buffer
169
- dv2 = self .sm (t + self .c .dt4 ) - self .smv_at (t - self .c .dt4 )
170
- dv1 = self .smv_at (t + self .c .dt ) - self .smv_at (t - self .c .dt )
171
- dv = dv1 << 1
172
- dv -= self .smv_at (t + self .c .dt2 ) - self .smv_at (t - self .c .dt2 )
173
- dv = dv << 1
174
- dv += dv1
175
- dv -= self .smv_at (t + self .c .dt3 ) - self .smv_at (t - self .c .dt3 )
176
-
177
- dv -= self .smv_at (t + self .c .dt3 ) - self .smv_at (t - self .c .dt3 )
178
- dv = dv << 1
179
- dv += dv2
180
- self .v1 += dv
181
- v0 = self .v1 / self .c .v1norm
182
- self .qfv_put (t , v0 * v0 )
183
-
184
- def gqrs (self , from_sample , to_sample ):
185
- self .countdown = - 1
186
-
187
- c = None
188
- i = None
189
- qamp = None
190
- q0 = None
191
- q1 = 0
192
- q2 = 0
193
- rr = None
194
- rrd = None
195
- rt = None
196
- rtd = None
197
- rtdmin = None
198
-
199
- p = None # (Peak)
200
- q = None # (Peak)
201
- r = None # (Peak)
202
- tw = None # (Peak)
203
-
204
- last_peak = from_sample
205
- last_qrs = from_sample
206
-
207
- def add_peak (peak_time , peak_amp , type ):
208
- p = self .current_peak .next_peak
209
- p .time = peak_time
210
- p .amp = peak_amp
211
- p .type = type
212
- self .current_peak = p
213
- p .next_peak .amp = 0
214
-
215
- def peaktype (p ):
216
- # peaktype() returns 1 if p is the most prominent peak in its neighborhood, 2
217
- # otherwise. The neighborhood consists of all other peaks within rrmin.
218
- # Normally, "most prominent" is equivalent to "largest in amplitude", but this
219
- # is not always true. For example, consider three consecutive peaks a, b, c
220
- # such that a and b share a neighborhood, b and c share a neighborhood, but a
221
- # and c do not; and suppose that amp(a) > amp(b) > amp(c). In this case, if
222
- # there are no other peaks, a is the most prominent peak in the (a, b)
223
- # neighborhood. Since b is thus identified as a non-prominent peak, c becomes
224
- # the most prominent peak in the (b, c) neighborhood. This is necessary to
225
- # permit detection of low-amplitude beats that closely precede or follow beats
226
- # with large secondary peaks (as, for example, in R-on-T PVCs).
227
- if p .type : # TODO: check that
228
- return p .type
229
- else :
230
- a = p .amp
231
- t0 = p .time - self .c .rrmin
232
- t1 = p .time + self .c .rrmin
233
-
234
- if t0 < 0 :
235
- t0 = 0
236
-
237
- pp = p .prev_peak
238
- while t0 < pp .time and pp .time < pp .next_peak .time :
239
- if pp .amp == 0 :
240
- break
241
- if a < pp .amp and peaktype (pp ) == 1 :
242
- p .type = 2
243
- return p .type
244
- # end:
245
- pp = pp .prev_peak
246
-
247
- pp = p .next_peak
248
- while pp .time < t1 and pp .time > pp .prev_peak .time :
249
- if pp .amp == 0 :
250
- break
251
- if a < pp .amp and peaktype (pp ) == 1 :
252
- p .type = 2
253
- return p .type
254
- # end:
255
- pp = pp .next_peak
256
-
257
- p .type = 1
258
- return p .type
259
-
260
- def find_missing (r , p ):
261
- if r is None or p is None :
262
- return None
263
-
264
- minrrerr = p .time - r .time
265
-
266
- s = None
267
- q = r .next_peak
268
- while q .time < p .time :
269
- if peaktype (q ) == 1 :
270
- rrtmp = q .time - r .time
271
- rrerr = rrtmp - self .c .rrmean
272
- if rrerr < 0 :
273
- rrerr = - rrerr
274
- if rrerr < minrrerr :
275
- minrrerr = rrerr
276
- s = q
277
- # end:
278
- q = q .next_peak
279
-
280
- return s
281
-
282
- r = None
283
- next_minute = 0
284
- minutes = 0
285
- while self .t <= to_sample + self .c .sps :
286
- if self .countdown < 0 :
287
- if self .sample_valid :
288
- self .qf ()
289
- else :
290
- self .countdown = time_to_sample_number (1 , self .freq )
291
- self .state = "CLEANUP"
292
- else :
293
- self .countdown -= 1
294
- if self .countdown < 0 :
295
- break
296
-
297
- q0 = self .qfv_at (self .t )
298
- q1 = self .qfv_at (self .t - 1 )
299
- q2 = self .qfv_at (self .t - 2 )
300
-
301
- if q1 > self .c .pthr and q2 < q1 and q1 >= q0 and t > self .c .dt4 :
302
- add_peak (self .t - 1 , q1 , 0 )
303
- last_peak = self .t - 1
304
-
305
- p = self .current_peak .next
306
- while p .time < t - self .c .rtmax :
307
- if p .time >= self .annot .time + self .c .rrmin and peaktype (p ) == 1 :
308
- if p .amp > self .c .qthr :
309
- rr = p .time - self .annot .time
310
- q = find_missing (r , p )
311
- if rr > self .c .rrmean + 2 * self .c .rrdev and \
312
- rr > 2 * (self .c .rrmean - self .c .rrdev ) and \
313
- q is not None :
314
- p = q
315
- rr = p .time - self .annot .time
316
- annot .subtype = 1
317
- rrd = rr - self .c .rrmean
318
- if rrd < 0 :
319
- rrd = - rrd
320
- self .c .rrdev += (rrd - self .c .rrdev ) >> 3
321
- if rrd > self .c .rrinc :
322
- rrd = self .c .rrinc
323
- if rr > self .c .rrmean :
324
- self .c .rrmean += rrd
325
- else :
326
- self .c .rrmean -= rrd
327
- if p .amp > self .c .qthr * 4 :
328
- self .c .qthr += 1
329
- elif p .amp < self .c .qthr :
330
- self .c .qthr -= 1
331
- if self .c .qthr > self .c .pthr * 20 :
332
- self .c .qthr = self .c .pthr * 20
333
- last_qrs = p .time
334
-
335
- if state == "RUNNING" :
336
- self .annot .time = p .time - self .c .dt2
337
- self .annot .type = "NORMAL"
338
- qsize = p .amp * 10.0 / self .c .qthr
339
- if qsize > 127 :
340
- qsize = 127
341
- self .annot .num = qsize
342
- putann (self .annot )
343
- self .annot .time += self .c .dt2
344
-
345
- # look for this beat's T-wave
346
- tw = None
347
- rtdmin = self .c .rtmean
348
- q = p .next
349
- while q .time > self .annot .time :
350
- rt = q .time - self .annot .time - self .c .dt2
351
- if rt < self .c .rtmin :
352
- continue
353
- if rt > self .c .rtmax :
354
- break
355
- rtd = rt - self .c .rtmean
356
- if rtd < 0 :
357
- rtd = - rtd
358
- if rtd < rtdmin :
359
- rtdmin = rtd
360
- tw = q
361
- # end:
362
- q = q .next
363
- if tw is not None :
364
- tmp_time = tw .time - self .c .dt2
365
- tann = Annotation (tmp_time , "TWAVE" , 1 if tmp_time > self .annot .time + self .c .rtmean else 0 , rtdmin )
366
- if state == "RUNNING" :
367
- putann (tann )
368
- rt = tann .time - self .annot .time
369
- self .c .rtmean += (rt - self .c .rtmean ) >> 4
370
- if self .c .rtmean > self .c .rtmax :
371
- self .c .rtmean = self .c .rtmax
372
- elif self .c .rtmean < self .c .rtmin :
373
- self .c .rtmean = self .c .rrmin
374
- tw .type = 2 # mark T-wave as secondary
375
- r = p
376
- q = None
377
- qamp = 0
378
- self .annot .subtype = 0
379
- elif t - last_qrs > self .c .rrmax and self .c .qthr > self .c .qthmin :
380
- self .c .qthr -= (self .c .qthr >> 4 )
381
- elif t - last_qrs > self .c .rrmax and self .c .pthr > self .c .pthmin :
382
- self .c .pthr -= (self .c .pthr >> 4 )
383
-
384
- t += 1
385
- if t >= next_minute :
386
- next_minute += self .c .spm
387
- minutes += 1
388
- if minutes >= 60 :
389
- minutes = 0
390
-
391
- if self .state == "LEANING" :
392
- return
393
-
394
- # Mark the last beat or two.
395
- p = self .current_peak .next_peak
396
- while p .time < p .next_peak .time :
397
- if p .time >= self .annot .time + self .c .rrmin and p .time < tf and peaktype (p ) == 1 :
398
- self .annot .type = "NORMAL"
399
- self .annot .time = p .time
400
- putann (self .annot )
401
- # end:
402
- p = p .next_peak
0 commit comments