Commit 83238724 authored by Zach Fredin's avatar Zach Fredin

added apparatus details

parent 4ac9443a
......@@ -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.
......@@ -67,6 +271,6 @@ A few paths exist that may be worth pursuing, given the aforementioned concerns:
- Build an uncalibrated device that allows users to track _changes_ in their blood oxygenation over time. Even without an absolute reference in terms of SpO<sub>2</sub>, this data could be used as an early warning for respiratory ailments. This fits with the use case, too; clinical devices need to be usable as spot-check instruments, where as a personal device could be used for weeks or months by one person.
- Develop a calibration system that can be easily manufactured and deployed based on fundamental principles, i.e. one that does not need to be _itself_ calibrated. One could build a spinning hollow clear plastic wheel with two chambers and controlled thickness, with the chambers filled with various concentrations of a solution whose absorption spectrum closely matches that of blood at a given oxygenation level. The wheel would be spun to simulate the heartbeat, and different wheels would represent different SpO<sub>2</sub> values. The solution could be accurately mixed using basic laboratory equipment, such as a scale or a pipette.
- Design an automated calibration system that uses a camera and optical character recognition to gather SpO<sub>2</sub> values from a commercial or clinical instrument and build a calibration table for the low-cost device while it is simultaneously clipped to the patient. Caregivers could "train" the low-cost device prior to patient discharge so they can self-monitor for flare-ups or subsequent respiratory ailments.
- Develop a methodology for cheaply and accurately characterizing LEDs and other components in the low-cost sensor, so that a master calibration file from a clinical study can be propagated to other devices as is done by traditional manufacturers.
- Develop a methodology for cheaply and accurately characterizing LEDs and other components in the low-cost sensor, so that a master calibration file from a clinical study can be propagated to other devices as is done by traditional manufacturers.
In all cases, a reasonable first step is to design and prototype a sensor with sufficient performance to measure $`R`$, the O<sub>2</sub>Hb / Hb ratio discussed above.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment