1+ # 25 samples per second (in algorithm.h)
2+ SAMPLE_FREQ = 25
3+ #计算HR时取4个样本的移动平均值
4+ MA_SIZE = 4
5+ #采样频率
6+ BUFFER_SIZE = 100
7+
8+ #取平均值
9+
10+ def get_mean (ls ):
11+ return sum (ls )/ len (ls )
12+
13+ def calc_hr_and_spo2 (ir_data , red_data ):
14+
15+ ir_mean = int (get_mean (ir_data ))
16+ x = []
17+ for k in ir_data :
18+ x .append ((k - ir_mean )* - 1 )
19+ m = len (x ) - MA_SIZE
20+ for i in range (m ):
21+ x [i ] = sum (x [i :i + MA_SIZE ]) / MA_SIZE
22+
23+ #计算阈值
24+ n_th = int (get_mean (x ))
25+ n_th = 30 if n_th < 30 else n_th #允许的最小值
26+ n_th = 60 if n_th > 60 else n_th #允许的最大值
27+
28+ ir_valley_locs , n_peaks = find_peaks (x , BUFFER_SIZE , n_th , 4 , 15 )
29+ peak_interval_sum = 0
30+ if n_peaks >= 2 :
31+ for i in range (1 , n_peaks ):
32+ peak_interval_sum += (ir_valley_locs [i ] - ir_valley_locs [i - 1 ])
33+ peak_interval_sum = int (peak_interval_sum / (n_peaks - 1 ))
34+ hr = int (SAMPLE_FREQ * 60 / peak_interval_sum )
35+ hr_valid = True
36+ else :
37+ hr = - 999 #错误值
38+ hr_valid = False
39+
40+ # ---------spo2 血氧浓度---------
41+ exact_ir_valley_locs_count = n_peaks
42+ for i in range (exact_ir_valley_locs_count ):
43+ if ir_valley_locs [i ] > BUFFER_SIZE :
44+ spo2 = - 999
45+ spo2_valid = False
46+ return hr , hr_valid , spo2 , spo2_valid
47+
48+ i_ratio_count = 0
49+ ratio = []
50+
51+ # find max between two valley locations 在两个山谷位置之间找到最大值
52+ # and use ratio between AC component of Ir and Red DC component of Ir and Red for SpO2 以及Ir的AC分量与Ir和Red的Red DC分量对于SpO2的使用比率
53+ red_dc_max_index = - 1
54+ ir_dc_max_index = - 1
55+ for k in range (exact_ir_valley_locs_count - 1 ):
56+ red_dc_max = - 16777216
57+ ir_dc_max = - 16777216
58+ if ir_valley_locs [k + 1 ] - ir_valley_locs [k ] > 3 :
59+ for i in range (ir_valley_locs [k ], ir_valley_locs [k + 1 ]):
60+ if ir_data [i ] > ir_dc_max :
61+ ir_dc_max = ir_data [i ]
62+ ir_dc_max_index = i
63+ if red_data [i ] > red_dc_max :
64+ red_dc_max = red_data [i ]
65+ red_dc_max_index = i
66+
67+ red_ac = int ((red_data [ir_valley_locs [k + 1 ]] - red_data [ir_valley_locs [k ]]) * (red_dc_max_index - ir_valley_locs [k ]))
68+ red_ac = red_data [ir_valley_locs [k ]] + int (red_ac / (ir_valley_locs [k + 1 ] - ir_valley_locs [k ]))
69+ red_ac = red_data [red_dc_max_index ] - red_ac # subtract linear DC components from raw 从原始中减去线性直流分量
70+
71+ ir_ac = int ((ir_data [ir_valley_locs [k + 1 ]] - ir_data [ir_valley_locs [k ]]) * (ir_dc_max_index - ir_valley_locs [k ]))
72+ ir_ac = ir_data [ir_valley_locs [k ]] + int (ir_ac / (ir_valley_locs [k + 1 ] - ir_valley_locs [k ]))
73+ ir_ac = ir_data [ir_dc_max_index ] - ir_ac # subtract linear DC components from raw
74+
75+ nume = red_ac * ir_dc_max
76+ denom = ir_ac * red_dc_max
77+ if (denom > 0 and i_ratio_count < 5 ) and nume != 0 :
78+ # original cpp implementation uses overflow intentionally.
79+ # but at 64-bit OS, Pyhthon 3.X uses 64-bit int and nume*100/denom does not trigger overflow
80+ # so using bit operation ( &0xffffffff ) is needed
81+ ratio .append (int (((nume * 100 ) & 0xffffffff ) / denom ))
82+ i_ratio_count += 1
83+
84+ # choose median value since PPG signal may vary from beat to beat
85+ ratio = sorted (ratio ) # sort to ascending order
86+ mid_index = int (i_ratio_count / 2 )
87+
88+ ratio_ave = 0
89+ if mid_index > 1 :
90+ ratio_ave = int ((ratio [mid_index - 1 ] + ratio [mid_index ])/ 2 )
91+ else :
92+ if len (ratio ) != 0 :
93+ ratio_ave = ratio [mid_index ]
94+
95+ # why 184?
96+ # print("ratio average: ", ratio_ave)
97+ if ratio_ave > 2 and ratio_ave < 184 :
98+ # -45.060 * ratioAverage * ratioAverage / 10000 + 30.354 * ratioAverage / 100 + 94.845
99+ spo2 = - 45.060 * (ratio_ave ** 2 ) / 10000.0 + 30.054 * ratio_ave / 100.0 + 94.845
100+ spo2_valid = True
101+ else :
102+ spo2 = - 999
103+ spo2_valid = False
104+
105+ return hr , hr_valid , spo2 , spo2_valid
106+
107+
108+ def find_peaks (x , size , min_height , min_dist , max_num ):
109+ """
110+ Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE
111+ """
112+ ir_valley_locs , n_peaks = find_peaks_above_min_height (x , size , min_height , max_num )
113+ ir_valley_locs , n_peaks = remove_close_peaks (n_peaks , ir_valley_locs , x , min_dist )
114+
115+ n_peaks = min ([n_peaks , max_num ])
116+
117+ return ir_valley_locs , n_peaks
118+
119+
120+ def find_peaks_above_min_height (x , size , min_height , max_num ):
121+ """
122+ Find all peaks above MIN_HEIGHT
123+ """
124+
125+ i = 0
126+ n_peaks = 0
127+ ir_valley_locs = [] # [0 for i in range(max_num)]
128+ while i < size - 1 :
129+ if x [i ] > min_height and x [i ] > x [i - 1 ]: # find the left edge of potential peaks
130+ n_width = 1
131+ # original condition i+n_width < size may cause IndexError
132+ # so I changed the condition to i+n_width < size - 1
133+ while i + n_width < size - 1 and x [i ] == x [i + n_width ]: # find flat peaks
134+ n_width += 1
135+ if x [i ] > x [i + n_width ] and n_peaks < max_num : # find the right edge of peaks
136+ # ir_valley_locs[n_peaks] = i
137+ ir_valley_locs .append (i )
138+ n_peaks += 1 # original uses post increment
139+ i += n_width + 1
140+ else :
141+ i += n_width
142+ else :
143+ i += 1
144+
145+ return ir_valley_locs , n_peaks
146+
147+
148+ def remove_close_peaks (n_peaks , ir_valley_locs , x , min_dist ):
149+ """
150+ Remove peaks separated by less than MIN_DISTANCE
151+ """
152+
153+ # should be equal to maxim_sort_indices_descend
154+ # order peaks from large to small
155+ # should ignore index:0
156+ sorted_indices = sorted (ir_valley_locs , key = lambda i : x [i ])
157+ sorted_indices .reverse ()
158+
159+ # this "for" loop expression does not check finish condition
160+ # for i in range(-1, n_peaks):
161+ i = - 1
162+ while i < n_peaks :
163+ old_n_peaks = n_peaks
164+ n_peaks = i + 1
165+ # this "for" loop expression does not check finish condition
166+ # for j in (i + 1, old_n_peaks):
167+ j = i + 1
168+ while j < old_n_peaks :
169+ n_dist = (sorted_indices [j ] - sorted_indices [i ]) if i != - 1 else (sorted_indices [j ] + 1 ) # lag-zero peak of autocorr is at index -1
170+ if n_dist > min_dist or n_dist < - 1 * min_dist :
171+ sorted_indices [n_peaks ] = sorted_indices [j ]
172+ n_peaks += 1 # original uses post increment
173+ j += 1
174+ i += 1
175+
176+ sorted_indices [:n_peaks ] = sorted (sorted_indices [:n_peaks ])
177+
178+ return sorted_indices , n_peaks
0 commit comments