Skip to content

Commit 9aeb965

Browse files
committed
hard-coded calibration coeffs for MAX30101
1 parent 9ac0335 commit 9aeb965

6 files changed

Lines changed: 28 additions & 110 deletions

File tree

EmotiBit SpO2 to EB Oscilloscope.ahk

Lines changed: 0 additions & 82 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Description
2-
This algorithm is derived from the Brainflow SpO2 algorithm, and uses PPG-R and PPG-IR data from the EmotiBit to calculate SPO2 level.
2+
This algorithm is derived from the Brainflow SpO2 algorithm, and uses PPG-R and PPG-IR data from the EmotiBit to calculate SPO2 level. As a starting point, `run.py` splits PPG-R and PPG-IR signals into buffers of length 64, which are then passed to the algorithm. It is recommended for the buffers to contain **at least** 64 samples for accurate results.
33

44
# Required Hardware
55
- Emotibit

src/bindings.cpp

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,18 @@ PYBIND11_MODULE(SPO2Algorithm, m) {
99
m.def(
1010
"get_oxygen_level",
1111
[](py::array_t<float, py::array::c_style | py::array::forcecast> ppg_ir,
12-
py::array_t<float, py::array::c_style | py::array::forcecast> ppg_red,
13-
int sampling_rate,
14-
float callib_coef1,
15-
float callib_coef2,
16-
float callib_coef3)
12+
py::array_t<float, py::array::c_style | py::array::forcecast> ppg_red)
1713
{
1814
float oxygen_level = 0.0;
1915
get_oxygen_level(
2016
const_cast<float*>(ppg_ir.data()),
2117
const_cast<float*>(ppg_red.data()),
2218
static_cast<int>(ppg_ir.size()),
23-
sampling_rate,
24-
callib_coef1,
25-
callib_coef2,
26-
callib_coef3,
2719
&oxygen_level
2820
);
2921
return oxygen_level;
3022
},
3123
py::arg("ppg_ir"),
32-
py::arg("ppg_red"),
33-
py::arg("sampling_rate"),
34-
py::arg("callib_coef1"),
35-
py::arg("callib_coef2"),
36-
py::arg("callib_coef3")
24+
py::arg("ppg_red")
3725
);
3826
}

src/run.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,7 @@ def split_into_chunks(data, chunk_size):
2626

2727
def main():
2828
# define constants
29-
chunk_size = 200
30-
sampling_rate = 10
31-
32-
# default calibration coefficients from Table 1 (https://www.analog.com/en/resources/technical-articles/guidelines-for-spo2-measurement--maxim-integrated.html)
33-
coeff_1 = 1.5958422
34-
coeff_2 = -34.6596622
35-
coeff_3 = 112.6898759
29+
chunk_size = 64
3630

3731
parser = argparse.ArgumentParser(description='Run SPO2 algorithm on PPG data.')
3832
parser.add_argument('data_folder', nargs='?', default=os.path.join(cur_dir, 'data'),
@@ -74,7 +68,7 @@ def main():
7468
o2_ts = []
7569
index = 0
7670
for red_chunk, ir_chunk in zip(ppg_red_chunks, ppg_ir_chunks):
77-
o2 = SPO2Algorithm.get_oxygen_level(ir_chunk, red_chunk, sampling_rate, coeff_1, coeff_2, coeff_3)
71+
o2 = SPO2Algorithm.get_oxygen_level(ir_chunk, red_chunk)
7872
o2_levels.append(o2)
7973
o2_ts.append(ppg_red_ts[index])
8074
index += chunk_size

src/spo2_algorithm.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@
22

33
#define MAX_FILTER_ORDER 8
44

5-
void get_oxygen_level(float *ppg_ir, float *ppg_red, int data_size, int sampling_rate, float callib_coef1, float callib_coef2, float callib_coef3, float *oxygen_level)
5+
/**
6+
* @brief Calculates the oxygen saturation level (SpO2) from PPG IR and RED signals.
7+
*
8+
* This function processes infrared (IR) and red photoplethysmogram (PPG) signals to estimate
9+
* the blood oxygen saturation level using filtering, detrending, and a calibration formula.
10+
*
11+
* @param ppg_ir Pointer to the array of IR PPG signal samples.
12+
* @param ppg_red Pointer to the array of RED PPG signal samples.
13+
* @param data_size Number of samples in the PPG signal arrays.
14+
* @param oxygen_level Pointer to a float where the calculated SpO2 value will be stored (0-100).
15+
*/
16+
void get_oxygen_level(float *ppg_ir, float *ppg_red, int data_size, float *oxygen_level)
617
{
718
float *red_raw = new float[data_size];
819
float *ir_raw = new float[data_size];
@@ -20,8 +31,8 @@ void get_oxygen_level(float *ppg_ir, float *ppg_red, int data_size, int sampling
2031
// filtering(full size)
2132
detrend (red_raw, data_size, (int)DetrendOperations::CONSTANT);
2233
detrend (ir_raw, data_size, (int)DetrendOperations::CONSTANT);
23-
perform_bandpass (red_raw, data_size, sampling_rate, 0.7, 1.5, 4, (int)FilterTypes::BUTTERWORTH, 0.0);
24-
perform_bandpass (ir_raw, data_size, sampling_rate, 0.7, 1.5, 4, (int)FilterTypes::BUTTERWORTH, 0.0);
34+
perform_bandpass (red_raw, data_size, FILTER_SAMPLING_RATE, 0.7, 1.5, 4, (int)FilterTypes::BUTTERWORTH, 0.0);
35+
perform_bandpass (ir_raw, data_size, FILTER_SAMPLING_RATE, 0.7, 1.5, 4, (int)FilterTypes::BUTTERWORTH, 0.0);
2536

2637
// calculate AC & DC components using mean & rms:
2738
float redac = rms (new_red_raw, new_size);
@@ -31,7 +42,7 @@ void get_oxygen_level(float *ppg_ir, float *ppg_red, int data_size, int sampling
3142

3243
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/6/6845.html
3344
float r = (redac / reddc) / (irac / irdc);
34-
float spo2 = callib_coef1 * r * r + callib_coef2 * r + callib_coef3;
45+
float spo2 = (CALIB_COEFF_1 * r * r) + (CALIB_COEFF_2 * r) + (CALIB_COEFF_3);
3546
if (spo2 > 100.0)
3647
{
3748
spo2 = 100.0;

src/spo2_algorithm.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
#include <stdlib.h>
66
#include "DspFilters/Dsp.h"
77

8+
// These constants are from Table 1 (https://www.analog.com/en/resources/technical-articles/guidelines-for-spo2-measurement--maxim-integrated.html)
9+
#define CALIB_COEFF_1 1.5958422
10+
#define CALIB_COEFF_2 -34.6596622
11+
#define CALIB_COEFF_3 112.6898759
12+
13+
#define FILTER_SAMPLING_RATE 10 // Hz
14+
815
enum class FilterTypes : int
916
{
1017
BUTTERWORTH = 0,
@@ -52,7 +59,7 @@ inline void reverse_array (float data[], int len)
5259
}
5360
}
5461

55-
void get_oxygen_level (float *ppg_ir, float *ppg_red, int data_size, int sampling_rate, float callib_coef1, float callib_coef2, float callib_coef3, float *oxygen_level);
62+
void get_oxygen_level (float *ppg_ir, float *ppg_red, int data_size, float *oxygen_level);
5663
void detrend (float *data, int data_len, int detrend_operation);
5764
void perform_bandpass (float *data, int data_len, int sampling_rate, float start_freq, float stop_freq, int order, int filter_type, float ripple);
5865

0 commit comments

Comments
 (0)