Skip to content
Snippets Groups Projects
Commit 83238724 authored by Zach Fredin's avatar Zach Fredin
Browse files

added apparatus details

parent 4ac9443a
Branches
No related tags found
No related merge requests found
img/apparatus.jpg

252 KiB

img/pulseox_snooper_models.png

214 KiB

......@@ -19,13 +19,13 @@ Pulse oximetry devices use several LEDs to measure pulse rate and blood oxygen c
- a design study weighing the relative merits of different pulse-ox probe types for a low-cost device: Parlato, Matthew Brian, et al. "Low cost pulse oximeter probe." Conjunction with Engineering, World Health and the MEdCal Project (2009).
### Commercial Example
A quick teardown of a ~$20 500BL from Walgreens revealed no [integrated photonics package](https://www.maximintegrated.com/en/products/interface/sensor-interface/MAX30101.html) or [signal processing ASIC](https://www.maximintegrated.com/en/products/interface/sensor-interface/MAX32664.html); instead, the device uses a bi-color IR/red LED on one side of a spring-loaded plastic clam-shell and a PCB with a decent sized photodiode on the other, paired with an [SGM8634](www.sg-micro.com/uploads/soft/20190626/1561538475.pdf) op-amp and an [STM32F100](https://www.st.com/en/microcontrollers-microprocessors/stm32f100-value-line.html)-series 32-bit Arm Cortex M3 microcontroller. The display is a custom multi-segment LED device, but the PCB labels suggest an OLED is used for an alternate model. TX/RX test points were spotted that could be investigated further; with any luck, these could be used to pull live data out of the instrument.
A quick teardown of a ~$20 Zacurate 500BL from Walgreens revealed no [integrated photonics package](https://www.maximintegrated.com/en/products/interface/sensor-interface/MAX30101.html) or [signal processing ASIC](https://www.maximintegrated.com/en/products/interface/sensor-interface/MAX32664.html); instead, the device uses a bi-color IR/red LED on one side of a spring-loaded plastic clam-shell and a PCB with a decent sized photodiode on the other, paired with an [SGM8634](www.sg-micro.com/uploads/soft/20190626/1561538475.pdf) op-amp and an [STM32F100](https://www.st.com/en/microcontrollers-microprocessors/stm32f100-value-line.html)-series 32-bit Arm Cortex M3 microcontroller. The display is a custom multi-segment LED device, but the PCB labels suggest an OLED is used for an alternate model. TX/RX test points were spotted that could be investigated further; with any luck, these could be used to pull live data out of the instrument.
![pulseox1](img/pulseox_1.jpg)
![pulseox1](../img/pulseox_1.jpg)
![pulseox2](img/pulseox_2.jpg)
![pulseox2](../img/pulseox_2.jpg)
![pulseox3](img/pulseox_3.jpg)
![pulseox3](../img/pulseox_3.jpg)
### Operational Theory
Pulse oximetry is based on the [Beer-Lambert law](https://en.wikipedia.org/wiki/Beer%E2%80%93Lambert_law), a principle that relates the concentration of a species to the attenuation of light through a sample:
......@@ -42,7 +42,7 @@ I=I_{in}e^{-(D_1C_1\epsilon_1+D_2C_2\epsilon_2+\dots+D_nC_n\epsilon_n)}
Typical commercial pulse oximeters use a red LED (660 nm) and an IR LED (940 nm) to quantify the relative concentration of reduced and oxygen-rich hemoglobin in a person's bloodstream based on the following absorbance curves:
![hemoglobin_curve](img/hemoglobin_curve.png)
![hemoglobin_curve](../img/hemoglobin_curve.png)
_Figure source: Bülbül, Ali & Küçük, Serdar. (2016). Pulse Oximeter Manufacturing & Wireless Telemetry for Ventilation Oxygen Support. International Journal of Applied Mathematics, Electronics and Computers. 211-211. 10.18100/ijamec.270309._
......@@ -54,12 +54,216 @@ R=\frac{A_{AC_{660}}/A_{DC_{660}}}{A_{AC_{940}}/A_{DC_{940}}}
As the photodiode sensor does not differentiate by wavelength, the device rapidly cycles between red, IR, and no LED, allowing the system to compensate for ambient light variation as well. The cycling speed must be substantially faster than the heart rate, since the ratio $`R`$ assumes absorption at all wavelengths is carried out simultaneously in order to cancel out path length. $`R`$ is then related to SpO2 using an empirically determined curve:
![pulseox_curve](img/pulseox_curve.jpg)
![pulseox_curve](../img/pulseox_curve.jpg)
_Figure source, via Ohmeda Corp: Pologe, Jonas A. "Pulse oximetry: technical aspects of machine design." International anesthesiology clinics 25.3 (1987): 137-153._
Note that methemoglobin (MetHb) and carboxyhemoglobin (COHb) are not factored in with this method and will thus cause systematic errors; the above calculation assumes these two compounds are minimally present. Additional wavelengths are needed to quantify all four hemoglobin species.
### Apparatus
An apparatus was constructed to simultaneously gather raw sensor data and calculated SpO2 from the Zacurate 500BL sensor described above, along with a simple fabricated sensor. The apparatus consists of a few parts:
- an OpenMV machine vision camera mounted on a 3D printed bracket watching the SpO2 display
- a 3D printed cuff for the fabricated sensor with an IR and red LED, along with a photodiode and high-gain op-amp circuit
- a Teensy 4.0 development board to perform data logging (analog and UART) and LED control
![apparatus](../img/apparatus.jpg)
The 3D printed parts were designed in Fusion360; both native and STEP files are available in the `cad` directory:
![models](../img/pulseox_snooper_models.png)
The OpenMV code is relatively simple; since the 3D printed bracket holds the camera in a fixed location, the segment LED states are identified by checking average illumination values for defined pixel rectangles. One could also imagine directly tapping into the LED display driver lines, but the scanning speed of the display matrix made this complicated (and this is a good excuse to try out an OpenMV board):
```
import sensor, image, time, ustruct
from pyb import UART
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE) # or RGB565.
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # must be turned off for color tracking
sensor.set_auto_whitebal(False) # must be turned off for color tracking
clock = time.clock()
uart = UART(3, 19200)
seg_thresh = 60
SpO2 = 0;
# ---A1--- ---A2---
# | | | |
# F1 B1 F2 B2
# | | | |
# ---G1--- ---G2---
# | | | |
# E1 C1 E2 C2
# | | | |
# ---D1--- ---D2---
while(True):
clock.tick()
SpO2 = 0;
img = sensor.snapshot()
# segment 1 (left)
if(img.get_statistics(roi=(43,50,8,20)).mean() > seg_thresh): #F1
if(img.get_statistics(roi=(45,131,6,17)).mean() > seg_thresh): #E1
if(img.get_statistics(roi=(77,96,18,7)).mean() > seg_thresh): #G1
if(img.get_statistics(roi=(117,50,9,17)).mean() > seg_thresh): #B1
SpO2 += 80
else:
SpO2 += 60
else:
SpO2 += 0
elif(img.get_statistics(roi=(79,174,15,8)).mean() > seg_thresh): #D1
if(img.get_statistics(roi=(117,50,9,17)).mean() > seg_thresh): #B1
SpO2 += 90
else:
SpO2 += 50
else:
SpO2 += 40
elif(img.get_statistics(roi=(77,96,18,7)).mean() > seg_thresh): #G1
if(img.get_statistics(roi=(70,16,22,10)).mean() > seg_thresh): #A1
if(img.get_statistics(roi=(45,131,6,17)).mean() > seg_thresh): #E1
SpO2 += 20
else:
SpO2 += 30
else:
SpO2 = 0
elif(img.get_statistics(roi=(70,16,22,10)).mean() > seg_thresh): #A1
SpO2 += 70
elif(img.get_statistics(roi=(117,50,9,17)).mean() > seg_thresh): #B1
SpO2 += 10
else:
SpO2 += 0
# segment 2 (right)
if(img.get_statistics(roi=(154,47,10,22)).mean() > seg_thresh): #F1
if(img.get_statistics(roi=(156,133,10,19)).mean() > seg_thresh): #E1
if(img.get_statistics(roi=(188,94,18,8)).mean() > seg_thresh): #G1
if(img.get_statistics(roi=(234,43,8,22)).mean() > seg_thresh): #B1
SpO2 += 8
else:
SpO2 += 6
else:
SpO2 += 0
elif(img.get_statistics(roi=(188,176,21,8)).mean() > seg_thresh): #D1
if(img.get_statistics(roi=(234,43,8,22)).mean() > seg_thresh): #B1
SpO2 += 9
else:
SpO2 += 5
else:
SpO2 += 4
elif(img.get_statistics(roi=(188,94,18,8)).mean() > seg_thresh): #G1
if(img.get_statistics(roi=(183,13,21,8)).mean() > seg_thresh): #A1
if(img.get_statistics(roi=(156,133,10,19)).mean() > seg_thresh): #E1
SpO2 += 2
else:
SpO2 += 3
else:
SpO2 = 0
elif(img.get_statistics(roi=(183,13,21,8)).mean() > seg_thresh): #A1
SpO2 += 7
elif(img.get_statistics(roi=(234,43,8,22)).mean() > seg_thresh): #B1
SpO2 += 1
else:
SpO2 += 0
uart.write(ustruct.pack("<b", SpO2))
print(SpO2)
```
The Teensy 4.0 firmware is similarly straightforward. Some considerations are made to store a reasonably large number of samples (25k) in RAM before periodically dumping the array into the SD card, to avoid constant write operations. Precise timing is ensured by capturing samples on a microsecond-accurate timer interrupt:
```
#include <IntervalTimer.h>
#include <SD.h>
#include <SPI.h>
#define LED_red 0
#define LED_IR 1
int SpO2_OpenMV = 0;
int SpO2_Raw_Zacurate = 0;
int SpO2_Raw_Fab = 0;
int Arr_SpO2_OpenMV[25000];
int Arr_SpO2_Raw_Zacurate[25000];
int Arr_SpO2_Raw_Fab[25000];
int Arr_micros[25000];
int counter = 0;
int led_counter = 0;
IntervalTimer sampleTimer;
IntervalTimer ledTimer;
void writelog() {
Arr_SpO2_OpenMV[counter] = SpO2_OpenMV;
Arr_SpO2_Raw_Zacurate[counter] = SpO2_Raw_Zacurate;
Arr_SpO2_Raw_Fab[counter] = SpO2_Raw_Fab;
Arr_micros[counter] = micros();
counter++;
}
void updateLEDs() {
if (led_counter < 6) {
digitalWrite(0, HIGH);
}
else if (led_counter < 12) {
digitalWrite(0, LOW);
}
else if (led_counter < 18) {
digitalWrite(1, HIGH);
}
else {
digitalWrite(1, LOW);
}
led_counter++;
if (led_counter > 72) {
led_counter = 0;
}
}
void setup() {
Serial.begin(115200); //USB serial port
Serial5.begin(19200); //OpenMV UART
analogReadResolution(16);
pinMode(0, OUTPUT);
pinMode(1, OUTPUT);
pinMode(13, OUTPUT);
SD.begin(BUILTIN_SDCARD);
delay(5000); //mostly to let the OpenMV board boot up
sampleTimer.begin(writelog, 200); // 200 us period
ledTimer.begin(updateLEDs, 100); // 100 us period
}
void loop() {
int i;
digitalWrite(13, HIGH);
if (Serial5.available() > 0) {
SpO2_OpenMV = Serial5.read();
}
SpO2_Raw_Zacurate = analogRead(4);
SpO2_Raw_Fab = analogRead(9);
if (counter == 25000) {
noInterrupts();
File dataFile = SD.open("datalog.txt", FILE_WRITE);
if (dataFile) {
for (i = 0; i < 25000; i++) {
dataFile.print(Arr_micros[i]);
dataFile.print(",");
dataFile.print(Arr_SpO2_OpenMV[i]);
dataFile.print(",");
dataFile.print(Arr_SpO2_Raw_Zacurate[i]);
dataFile.print(",");
dataFile.println(Arr_SpO2_Raw_Fab[i]);
}
dataFile.close();
}
counter = 0;
interrupts();
}
}
```
See my [NMM final project page](http://fab.cba.mit.edu/classes/864.20/people/zach/final.html) for a first pass at data analysis.
### Practical Considerations
Commercial pulse oximeters trace their calibrations back to empirical studies on human volunteers whose blood oxygenation is simultaneously observed using an invasive measurement device. To avoid needing to repeat this process for every device that is manufactured, designers rely on pre-assembly binning or per-unit spectroscopy testing to compensate for LED wavelength variation, and likely perform extensive electrical testing to ensure photodiode and amplifier differences are accounted for. The spirit of this exercise, open, low-cost devices that can be made anywhere and remain useful, means these techniques aren't particularly useful.
......
File added
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment