diff --git a/preload/apps/ecg/__init__.py b/preload/apps/ecg/__init__.py index b6adca37ff1a959840b39578aee84e3084d55bcf..6a0dd08886f37173306c49bbf40eee792d105f89 100644 --- a/preload/apps/ecg/__init__.py +++ b/preload/apps/ecg/__init__.py @@ -41,16 +41,83 @@ leds.dim_top(1) COLORS = [((23 + (15 * i)) % 360, 1.0, 1.0) for i in range(11)] +# variables for high-pass filter +moving_average = 0 +alpha = 2 +beta = 3 + + +def update_history(datasets): + global history, moving_average, alpha, beta + for val in datasets: + history.append(val - moving_average) + moving_average = (alpha * moving_average + beta * val) / (alpha + beta) + + # trim old elements + history = history[-HISTORY_MAX:] + + +# variables for pulse detection +pulse = -1 +samples_since_last_pulse = 0 +q_threshold = -1 +r_threshold = 1 +q_spike = -ECG_RATE + + +def neighbours(n, lst): + """ + neighbours(2, "ABCDE") = ("AB", "BC", "CD", "DE") + neighbours(3, "ABCDE") = ("ABC", "BCD", "CDE") + """ + + for i in range(len(lst) - (n - 1)): + yield lst[i : i + n] + + +def detect_pulse(num_new_samples): + global history, pulse, samples_since_last_pulse, q_threshold, r_threshold, q_spike + + # look at 3 consecutive samples, starting 2 samples before the samples that were just added, e.g.: + # existing samples: "ABCD" + # new samples: "EF" => "ABCDEF" + # consider ["CDE", "DEF"] + # new samples: "GHI" => "ABCDEFGHI" + # consider ["EFG", "FGH", "GHI"] + for [prev, cur, next_] in neighbours(3, history[-(num_new_samples + 2) :]): + samples_since_last_pulse += 1 + + if prev > cur < next_ and cur < q_threshold: + q_spike = samples_since_last_pulse + # we expect the next q-spike to be at least 60% as high as this one + q_threshold = (cur * 3) // 5 + elif ( + prev < cur > next_ + and cur > r_threshold + and samples_since_last_pulse - q_spike < ECG_RATE // 10 + ): + # the full QRS complex is < 0.1s long, so the q and r spike in particular cannot be more than ECG_RATE//10 samples apart + pulse = 60 * ECG_RATE // samples_since_last_pulse + samples_since_last_pulse = 0 + q_spike = -ECG_RATE + if pulse < 30 or pulse > 210: + pulse = -1 + # we expect the next r-spike to be at least 60% as high as this one + r_threshold = (cur * 3) // 5 + elif samples_since_last_pulse > 2 * ECG_RATE: + q_threshold = -1 + r_threshold = 1 + pulse = -1 + + def callback_ecg(datasets): global update_screen, history, filebuffer, write update_screen += len(datasets) # update histogram datalist if not pause_histogram: - history += datasets - - # trim old elements - history = history[-HISTORY_MAX:] + update_history(datasets) + detect_pulse(len(datasets)) # buffer for writes if write > 0: @@ -207,12 +274,24 @@ def draw_histogram(): ) else: draw_leds((max(history[-5:]) * scale + SCALE_FACTOR) * 11 / (SCALE_FACTOR * 2)) - disp.print( - current_mode + ("+Bias" if bias else ""), - posx=0, - posy=0, - fg=(COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB), - ) + if pulse < 0: + disp.print( + current_mode + ("+Bias" if bias else ""), + posx=0, + posy=0, + fg=( + COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB + ), + ) + else: + disp.print( + "BPM: {}".format(pulse), + posx=0, + posy=0, + fg=( + COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB + ), + ) # announce writing ecg log if write > 0: