Skip to content

Commit 0205297

Browse files
committed
Changed to EmotiBitArduinoFilters
1 parent 797cd9a commit 0205297

4 files changed

Lines changed: 95 additions & 134 deletions

File tree

pybind/CMakeLists.txt

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,42 @@
1-
cmake_minimum_required(VERSION 3.14...3.18)
2-
project(SPO2Algorithm) # Project name becomes name of the Python module
1+
cmake_minimum_required(VERSION 4.1)
2+
set(CMAKE_POLICY_VERSION_MINIMUM "4.1")
3+
set(ARDUINO_INSTALL_PATH "C:/Users/jxie7/AppData/Local/Arduino15")
4+
set(ARDUINO_BOARD "Adafruit ESP32 Feather [esp32.featheresp32]")
5+
6+
# Include modules
7+
include("${CMAKE_CURRENT_SOURCE_DIR}/Arduino-toolchain.cmake")
8+
9+
project(BrainflowSpO2Algorithm VERSION 4.1 LANGUAGES C CXX) # Project name becomes name of the Python module
310

411
# See pybind11Config.cmake, instructions for pybind11_DIR
5-
# According to CMake convention, find_package searches in path set in the <package_name>_DIR variable
6-
set(pybind11_DIR "${CMAKE_CURRENT_SOURCE_DIR}\\..\\venv\\Lib\\site-packages\\pybind11\\share\\cmake\\pybind11")
12+
# According to CMake convention, find_package searches in path set in the <package_name>_DIR
13+
set(pybind11_DIR "${CMAKE_CURRENT_SOURCE_DIR}/venv/Lib/site-packages/pybind11/share/cmake/pybind11")
714
find_package(Python COMPONENTS Interpreter Development REQUIRED)
815
find_package(pybind11 CONFIG REQUIRED)
916

10-
# gather all source files
11-
file(GLOB SRC
12-
"C:/Users/jxie7/Documents/Arduino/libraries/DSPFilters/source/*.cpp"
13-
)
14-
15-
# Add local source files
16-
add_library(lib STATIC
17-
"${CMAKE_CURRENT_SOURCE_DIR}/spo2_algorithm.cpp"
18-
${SRC}
19-
)
20-
21-
# Add header files to be included
22-
target_include_directories(lib
23-
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}"
24-
PUBLIC "C:/Users/jxie7/Documents/Arduino/libraries/DSPFilters/include/"
25-
)
26-
27-
# Add library source files (see if there is a way to do it this way)
28-
# add_subdirectory(C:/Users/jxie7/Documents/Arduino/libraries/DSPFilters build)
29-
# target_link_libraries(lib
30-
# PUBLIC DSPFilters
17+
# CMake function is a convenience helper provided by the Pybind11 project
18+
pybind11_add_module(BrainflowSpO2Algorithm "${CMAKE_CURRENT_SOURCE_DIR}/bindings.cpp")
19+
20+
target_link_arduino_libraries(BrainflowSpO2Algorithm AUTO_PUBLIC)
21+
22+
# # gather all source files
23+
# file(GLOB SRC
24+
# "${CMAKE_CURRENT_SOURCE_DIR}/../src/*.cpp"
25+
# "C:/Users/jxie7/Documents/Arduino/libraries/EmotiBit_DSPFilters/src/*.cpp"
3126
# )
3227

33-
# CMake function is a convenience helper provided by the Pybind11 project
34-
pybind11_add_module(SPO2Algorithm bindings.cpp)
28+
# # Add local source files
29+
# add_library(lib STATIC
30+
# ${SRC}
31+
# )
32+
# # Add header files to be included
33+
# target_include_directories(lib
34+
# PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../src"
35+
# PUBLIC "C:/Users/jxie7/Documents/Arduino/libraries/EmotiBit_DSPFilters/src/"
36+
# )
37+
38+
# # CMake function is a convenience helper provided by the Pybind11 project
39+
# pybind11_add_module(BrainflowSpO2Algorithm "${CMAKE_CURRENT_SOURCE_DIR}/bindings.cpp")
3540

36-
target_link_libraries(SPO2Algorithm PRIVATE lib)
41+
# target_link_arduino_libraries(BrainflowSpO2Algorithm PRIVATE core)
42+
# target_link_libraries(BrainflowSpO2Algorithm PRIVATE lib)

pybind/bindings.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#include <pybind11/pybind11.h>
22
#include <pybind11/stl.h>
33
#include <pybind11/numpy.h>
4-
#include "spo2_algorithm.h"
4+
#include "BrainflowSpO2Algorithm.h"
55

66
namespace py = pybind11;
77

8-
PYBIND11_MODULE(SPO2Algorithm, m) {
8+
PYBIND11_MODULE(BrainflowSpO2Algorithm, m) {
99
m.def(
1010
"get_oxygen_level",
1111
[](py::array_t<float, py::array::c_style | py::array::forcecast> ppg_ir,

src/BrainflowSpO2Algorithm.cpp

Lines changed: 53 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#include "BrainflowSpO2Algorithm.h"
22

3-
#define MAX_FILTER_ORDER 8
4-
53
/**
64
* @brief Calculates the oxygen saturation level (SpO2) from PPG IR and RED signals.
75
*
@@ -15,46 +13,64 @@
1513
*/
1614
void get_oxygen_level(float *ppg_ir, float *ppg_red, int data_size, float *oxygen_level)
1715
{
18-
float *red_raw = new float[data_size];
19-
float *ir_raw = new float[data_size];
20-
int filter_shift = 25; // to get rif of filtereing artifact, dont use first elements
21-
int new_size = data_size - filter_shift;
22-
float *new_red_raw = red_raw + filter_shift;
23-
float *new_ir_raw = ir_raw + filter_shift;
24-
memcpy (red_raw, ppg_red, data_size * sizeof (float));
25-
memcpy (ir_raw, ppg_ir, data_size * sizeof (float));
16+
float *red_raw = new float[data_size];
17+
float *ir_raw = new float[data_size];
18+
int filter_shift = 25; // to get rif of filtereing artifact, dont use first elements
19+
int new_size = data_size - filter_shift;
20+
float *new_red_raw = red_raw + filter_shift;
21+
float *new_ir_raw = ir_raw + filter_shift;
22+
memcpy (red_raw, ppg_red, data_size * sizeof (float));
23+
memcpy (ir_raw, ppg_ir, data_size * sizeof (float));
2624

27-
// need prefiltered mean of red and ir for dc
28-
float mean_red = mean (new_red_raw, new_size);
29-
float mean_ir = mean (new_ir_raw, new_size);
25+
// need prefiltered mean of red and ir for dc
26+
float mean_red = mean (new_red_raw, new_size);
27+
float mean_ir = mean (new_ir_raw, new_size);
3028

31-
// filtering(full size)
32-
detrend (red_raw, data_size, (int)DetrendOperations::CONSTANT);
33-
detrend (ir_raw, data_size, (int)DetrendOperations::CONSTANT);
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);
29+
// filtering(full size)
30+
detrend (red_raw, data_size, (int)DetrendOperations::CONSTANT);
31+
detrend (ir_raw, data_size, (int)DetrendOperations::CONSTANT);
3632

37-
// calculate AC & DC components using mean & rms:
38-
float redac = rms (new_red_raw, new_size);
39-
float irac = rms (new_ir_raw, new_size);
40-
float reddc = mean_red;
41-
float irdc = mean_ir;
33+
const double start_f = 0.7;
34+
const double stop_f = 1.5;
4235

43-
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/6/6845.html
44-
float r = (redac / reddc) / (irac / irdc);
45-
float spo2 = (CALIB_COEFF_1 * r * r) + (CALIB_COEFF_2 * r) + (CALIB_COEFF_3);
46-
if (spo2 > 100.0)
47-
{
48-
spo2 = 100.0;
49-
}
50-
if (spo2 < 0)
51-
{
52-
spo2 = 0.0;
53-
}
54-
*oxygen_level = spo2;
36+
auto start_filter = butter<FILTER_ORDER>(2 * start_f / FILTER_SAMPLING_RATE);
37+
auto stop_filter = butter<FILTER_ORDER>(2 * stop_f / FILTER_SAMPLING_RATE);
38+
39+
for (int i = 0; i < data_size; i++) {
40+
red_raw[i] = red_raw[i] - start_filter(red_raw[i]);
41+
red_raw[i] = stop_filter(red_raw[i]);
42+
}
5543

56-
delete[] red_raw;
57-
delete[] ir_raw;
44+
// TODO better way to reset internal state of filters?
45+
start_filter = butter<FILTER_ORDER>(2 * start_f / FILTER_SAMPLING_RATE);
46+
stop_filter = butter<FILTER_ORDER>(2 * stop_f / FILTER_SAMPLING_RATE);
47+
48+
for (int i = 0; i < data_size; i++) {
49+
ir_raw[i] = ir_raw[i] - start_filter(ir_raw[i]);
50+
ir_raw[i] = stop_filter(ir_raw[i]);
51+
}
52+
53+
// calculate AC & DC components using mean & rms:
54+
float redac = rms (new_red_raw, new_size);
55+
float irac = rms (new_ir_raw, new_size);
56+
float reddc = mean_red;
57+
float irdc = mean_ir;
58+
59+
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/6/6845.html
60+
float r = (redac / reddc) / (irac / irdc);
61+
float spo2 = (CALIB_COEFF_1 * r * r) + (CALIB_COEFF_2 * r) + (CALIB_COEFF_3);
62+
if (spo2 > 100.0)
63+
{
64+
spo2 = 100.0;
65+
}
66+
if (spo2 < 0)
67+
{
68+
spo2 = 0.0;
69+
}
70+
*oxygen_level = spo2;
71+
72+
delete[] red_raw;
73+
delete[] ir_raw;
5874
}
5975

6076
void detrend (float *data, int data_len, int detrend_operation)
@@ -99,57 +115,4 @@ void detrend (float *data, int data_len, int detrend_operation)
99115
data[i] = data[i] - (grad * i + y_int);
100116
}
101117
}
102-
}
103-
104-
void perform_bandpass (float *data, int data_len, int sampling_rate, float start_freq, float stop_freq, int order, int filter_type, float ripple)
105-
{
106-
float center_freq = (start_freq + stop_freq) / 2.0;
107-
float band_width = stop_freq - start_freq;
108-
Dsp::Filter *f = NULL;
109-
float *filter_data[1];
110-
filter_data[0] = data;
111-
112-
switch (static_cast<FilterTypes> (filter_type))
113-
{
114-
case FilterTypes::BUTTERWORTH:
115-
f = new Dsp::FilterDesign<Dsp::Butterworth::Design::BandPass<MAX_FILTER_ORDER>, 1> ();
116-
break;
117-
case FilterTypes::BUTTERWORTH_ZERO_PHASE:
118-
f = new Dsp::FilterDesign<Dsp::Butterworth::Design::BandPass<MAX_FILTER_ORDER>, 1> ();
119-
break;
120-
case FilterTypes::CHEBYSHEV_TYPE_1:
121-
f = new Dsp::FilterDesign<Dsp::ChebyshevI::Design::BandPass<MAX_FILTER_ORDER>, 1> ();
122-
break;
123-
case FilterTypes::CHEBYSHEV_TYPE_1_ZERO_PHASE:
124-
f = new Dsp::FilterDesign<Dsp::ChebyshevI::Design::BandPass<MAX_FILTER_ORDER>, 1> ();
125-
break;
126-
case FilterTypes::BESSEL:
127-
f = new Dsp::FilterDesign<Dsp::Bessel::Design::BandPass<MAX_FILTER_ORDER>, 1> ();
128-
break;
129-
case FilterTypes::BESSEL_ZERO_PHASE:
130-
f = new Dsp::FilterDesign<Dsp::Bessel::Design::BandPass<MAX_FILTER_ORDER>, 1> ();
131-
break;
132-
}
133-
134-
Dsp::Params params;
135-
params[0] = sampling_rate; // sample rate
136-
params[1] = order; // order
137-
params[2] = center_freq; // center freq
138-
params[3] = band_width;
139-
if ((filter_type == (int)FilterTypes::CHEBYSHEV_TYPE_1) ||
140-
(filter_type == (int)FilterTypes::CHEBYSHEV_TYPE_1_ZERO_PHASE))
141-
{
142-
params[3] = ripple; // ripple
143-
}
144-
f->setParams (params);
145-
f->process (data_len, filter_data);
146-
if ((filter_type == (int)FilterTypes::BUTTERWORTH_ZERO_PHASE) ||
147-
(filter_type == (int)FilterTypes::CHEBYSHEV_TYPE_1_ZERO_PHASE) ||
148-
(filter_type == (int)FilterTypes::BESSEL_ZERO_PHASE))
149-
{
150-
reverse_array (data, data_len);
151-
f->process (data_len, filter_data);
152-
reverse_array (data, data_len);
153-
}
154-
delete f;
155118
}

src/BrainflowSpO2Algorithm.h

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,20 @@
22
#define SPO2_ALGORITHM_H
33

44
#include <math.h>
5+
#include <string.h>
56
#include <stdlib.h>
67

7-
#include "EmotiBit_DSPFilters.h"
8-
#include "DspFilters/Dsp.h"
8+
#include <Filters.h>
9+
#include <AH/Timing/MillisMicrosTimer.hpp>
10+
#include <Filters/Butterworth.hpp>
911

1012
// These constants are from Table 1 (https://www.analog.com/en/resources/technical-articles/guidelines-for-spo2-measurement--maxim-integrated.html)
1113
#define CALIB_COEFF_1 1.5958422
1214
#define CALIB_COEFF_2 -34.6596622
1315
#define CALIB_COEFF_3 112.6898759
1416

15-
#define FILTER_SAMPLING_RATE 10 // Hz
16-
17-
enum class FilterTypes : int
18-
{
19-
BUTTERWORTH = 0,
20-
CHEBYSHEV_TYPE_1 = 1,
21-
BESSEL = 2,
22-
BUTTERWORTH_ZERO_PHASE = 3,
23-
CHEBYSHEV_TYPE_1_ZERO_PHASE = 4,
24-
BESSEL_ZERO_PHASE = 5
25-
};
17+
#define FILTER_SAMPLING_RATE 50 // Hz
18+
#define FILTER_ORDER 4
2619

2720
enum class DetrendOperations : int
2821
{
@@ -63,6 +56,5 @@ inline void reverse_array (float data[], int len)
6356

6457
void get_oxygen_level (float *ppg_ir, float *ppg_red, int data_size, float *oxygen_level);
6558
void detrend (float *data, int data_len, int detrend_operation);
66-
void perform_bandpass (float *data, int data_len, int sampling_rate, float start_freq, float stop_freq, int order, int filter_type, float ripple);
6759

6860
#endif /* SPO2_ALGORITHM_H */

0 commit comments

Comments
 (0)