|
1 | 1 | import copy
|
2 | 2 | import numpy
|
3 | 3 | from .gqrs import time_to_sample_number, Conf, Peak, Annotation
|
| 4 | +from .basic import smooth |
4 | 5 |
|
5 | 6 | def find_peaks(x):
|
6 | 7 | # Definitions:
|
@@ -37,3 +38,110 @@ def find_peaks(x):
|
37 | 38 | i += 1
|
38 | 39 | soft_peaks = numpy.asarray(soft_peaks)+1
|
39 | 40 | return hard_peaks, soft_peaks
|
| 41 | + |
| 42 | +def find_peaks(x): |
| 43 | + # Definitions: |
| 44 | + # * Hard peak: a peak that is either /\ or \/ |
| 45 | + # * Soft peak: a peak that is either /-*\ or \-*/ (In that cas we define the middle of it as the peak) |
| 46 | + |
| 47 | + # Returns two numpy arrays: |
| 48 | + # * hard_peaks contains the indexes of the Hard peaks |
| 49 | + # * soft_peaks contains the indexes of the Soft peaks |
| 50 | + |
| 51 | + if len(x) == 0: |
| 52 | + return numpy.empty([0]), numpy.empty([0]) |
| 53 | + |
| 54 | + tmp = x[1:] |
| 55 | + tmp = numpy.append(tmp, [x[-1]]) |
| 56 | + tmp = x-tmp |
| 57 | + tmp[numpy.where(tmp>0)] = +1 |
| 58 | + tmp[numpy.where(tmp==0)] = 0 |
| 59 | + tmp[numpy.where(tmp<0)] = -1 |
| 60 | + tmp2 = tmp[1:] |
| 61 | + tmp2 = numpy.append(tmp2, [0]) |
| 62 | + tmp = tmp-tmp2 |
| 63 | + hard_peaks = numpy.where(numpy.logical_or(tmp==-2,tmp==+2))[0]+1 |
| 64 | + soft_peaks = [] |
| 65 | + for iv in numpy.where(numpy.logical_or(tmp==-1,tmp==+1))[0]: |
| 66 | + t = tmp[iv] |
| 67 | + i = iv+1 |
| 68 | + while True: |
| 69 | + if i==len(tmp) or tmp[i] == -t or tmp[i] == -2 or tmp[i] == 2: |
| 70 | + break |
| 71 | + if tmp[i] == t: |
| 72 | + soft_peaks.append(int(iv+(i-iv)/2)) |
| 73 | + break |
| 74 | + i += 1 |
| 75 | + soft_peaks = numpy.asarray(soft_peaks)+1 |
| 76 | + return hard_peaks, soft_peaks |
| 77 | + |
| 78 | + |
| 79 | +def correct_peaks(signal, peaks_indexes, min_gap, max_gap, smooth_window): |
| 80 | + N = signal.shape[0] |
| 81 | + |
| 82 | + rpeaks = numpy.zeros(N) |
| 83 | + rpeaks[peaks_indexes] = 1.0 |
| 84 | + |
| 85 | + # Multiple ones '1' side by side: |
| 86 | + # in order to prevent that, the following code computes the best peak |
| 87 | + rpeaks = rpeaks.astype('int32') |
| 88 | + |
| 89 | + # 1- Extract ranges where we have one or many ones side by side (rpeaks locations predicted by NN) |
| 90 | + rpeaks_ranges = [] |
| 91 | + tmp_idx = 0 |
| 92 | + for i in range(1, len(rpeaks)): |
| 93 | + if rpeaks[i-1] == 1: |
| 94 | + if rpeaks[i] == 0: |
| 95 | + rpeaks_ranges.append((tmp_idx, i-1)) |
| 96 | + else: |
| 97 | + if rpeaks[i] == 1: |
| 98 | + tmp_idx = i |
| 99 | + |
| 100 | + smoothed = smooth(signal, smooth_window) |
| 101 | + |
| 102 | + # Compute signal's peaks |
| 103 | + hard_peaks, soft_peaks = find_peaks(x=signal) |
| 104 | + all_peak_idxs = numpy.concatenate((hard_peaks, soft_peaks)).astype('int64') |
| 105 | + |
| 106 | + # Replace each range of ones by the index of the best value in it |
| 107 | + tmp = set() |
| 108 | + for rp_range in rpeaks_ranges: |
| 109 | + r = numpy.arange(rp_range[0], rp_range[1]+1, dtype='int64') |
| 110 | + vals = signal[r] |
| 111 | + smoothed_vals = smoothed[r] |
| 112 | + p = r[numpy.argmax(numpy.absolute(numpy.asarray(vals)-smoothed_vals))] |
| 113 | + tmp.add(p) |
| 114 | + |
| 115 | + # Replace all peaks by the peak within x-max_gap < x < x+max_gap which have the bigget distance from smooth curve |
| 116 | + dist = numpy.absolute(signal-smoothed) # Peak distance from the smoothed mean |
| 117 | + rpeaks_indexes = set() |
| 118 | + for p in tmp: |
| 119 | + a = max(0, p-max_gap) |
| 120 | + b = min(N, p+max_gap) |
| 121 | + r = numpy.arange(a, b, dtype='int64') |
| 122 | + idx_best = r[numpy.argmax(dist[r])] |
| 123 | + rpeaks_indexes.add(idx_best) |
| 124 | + |
| 125 | + rpeaks_indexes = list(rpeaks_indexes) |
| 126 | + |
| 127 | + # Prevent multiple peaks to appear in the max bpm range (max_gap) |
| 128 | + # If we found more than one peak in this interval, then we choose the peak with the maximum amplitude compared to the mean of the signal |
| 129 | + tmp = numpy.asarray(rpeaks_indexes) |
| 130 | + to_remove = {} |
| 131 | + for idx in rpeaks_indexes: |
| 132 | + if idx in to_remove: |
| 133 | + continue |
| 134 | + r = tmp[numpy.where(numpy.absolute(tmp-idx)<=max_gap)[0]] |
| 135 | + if len(r) == 1: |
| 136 | + continue |
| 137 | + rr = r.astype('int64') |
| 138 | + vals = signal[rr] |
| 139 | + smoo = smoothed[rr] |
| 140 | + the_one = r[numpy.argmax(numpy.absolute(vals-smoo))] |
| 141 | + for i in r: |
| 142 | + if i != the_one: |
| 143 | + to_remove[i] = True |
| 144 | + for v, _ in to_remove.items(): |
| 145 | + rpeaks_indexes.remove(v) |
| 146 | + |
| 147 | + return rpeaks_indexes |
0 commit comments