This guide demonstrates how to calculate the R value from a given SpO₂ level and use it to set up and play PPG waveforms with the AECG100 SDK.
Reference Code: spo2_to_r_value_output_ppg.py
Create an AECG100 instance and connect to your device. You can specify a COM port or let the SDK auto-select.
device = AECG100('./AECG100x64-1.dll')
if not device.connect(-1, 5000): # -1: auto-select port, 5000ms timeout
print('Error: device is not connected')
sys.exit()
Or, if you know the COM port:
device = AECG100('./SDK/windows/AECG100x64-1.dll')
if not device.connect(9, 5000): # COM9
sys.exit("Error: Device connection failed.")
Check connection:
print(f"Device connected (Main SN: {device.get_serial_number()}, PPG SN: {device.get_ppg_serial_number()})")
The R value is derived from the SpO₂ using a linear or quadratic equation:
SpO2 = A + B * RSpO2 = A + B * R + C * R^2Use the following function to calculate R from a target SpO₂:
# Define valid range of R value
MIN_R_Value = 0
MAX_R_Value = 1.0
def calculate_R_Value(target_spo2, degree, A, B, C = None):
"""
Calculate R value from SpO2 using linear or quadratic equation.
Returns the valid R value in [0, 1], or None if not found.
"""
if degree == 1:
# SpO2 = A + B * R_Value
R_Value = (A - target_spo2) / B
R_Values = [R_Value]
elif degree == 2:
# SpO2 = A + B * R_Value + C * (R_Value ^ 2)
if C is None:
print("Not enough argument to calculate R_Value")
return None
# Transforming into general quadratic equation form: a*R^2 + b*R + c = 0
a = C
b = B
c = A - target_spo2
# Computes the discriminant(D) of the quadratic equation to determine possible R-values
# D = b^2 - 4*a*c
D = b ** 2 - 4 * a * c
if D < 0:
# No real solution exists if the discriminant(D) is negative
print("Error: Invalid argument and target SpO2 combination (no real root for R-value)")
return None
R_Value_1 = (-b + math.sqrt(D)) / (2 * a)
R_Value_2 = (-b - math.sqrt(D)) / (2 * a)
R_Values = [R_Value_1, R_Value_2]
else:
print("Invalid degree")
return None
valid_R_Value = select_valid_R(R_Values, degree)
return valid_R_Value
def select_valid_R(R_Values, degree):
valid_R_Values = [R for R in R_Values if MIN_R_Value <= R <= MAX_R_Value]
if len(valid_R_Values) == 1:
R_Value = valid_R_Values[0]
elif len(valid_R_Values) == 2:
# the smaller R value is selected, as it typically corresponds to a higher SpO₂
R_Value = min(valid_R_Values)
print(
f"Warning: Two valid R value found ({valid_R_Values[0]:.4f} & {valid_R_Values[1]:.4f})"
f"Warning: The smaller value {R_Value:.4f} has been selected."
)
else:
if degree == 1:
print(
f"Calculated solutions ({R_Values[0]:.4f}) falls outside the reference range ({MIN_R_Value} ~ {MAX_R_Value})."
)
elif degree == 2:
print(
f"Both calculated solutions ({R_Values[0]:.4f} and {R_Values[1]:.4f}) fall outside the reference range ({MIN_R_Value} ~ {MAX_R_Value})."
)
return None
return R_Value
Example usage:
target_spo2 = 85
# R_Value = calculate_R_Value(target_spo2, 1, A=110, B=25)
R_Value = calculate_R_Value(target_spo2, 2, A=111.8, B=-18, C=-11.7)
if R_Value is None:
device.free()
sys.exit("No valid R value found for the given SpO2.")
Once the R value is determined, the combinations of R_DC, R_AC, IR_DC, and IR_AC are constrained. Among these four parameters, any three can be given, and the remaining one can be derived using the following equations:
where the Perfusion Index (PI) is defined as:
By combining the above two equations, a simplified expression is obtained:
Example:
R_DC = 400
R_AC = None
IR_DC = 400
IR_AC = 20
if R_DC is None:
R_DC = (R_AC * IR_DC) / (R_Value * IR_AC)
elif IR_AC is None:
R_DC = (R_AC * IR_DC) / (R_Value * R_DC)
elif R_AC is None:
R_AC = (R_Value * R_DC * IR_AC) / IR_DC
elif IR_DC is None:
IR_DC = (R_Value * R_DC * IR_AC) / R_AC
# Get default waveform parameters
ppg_waveform_1 = device.get_default_ppg_ch1_waveform() # R
ppg_waveform_2 = device.get_default_ppg_ch2_waveform() # IR
ppg_waveform_1.VolDC = R_DC
ppg_waveform_1.VolSP = R_AC
ppg_waveform_2.VolDC = IR_DC
ppg_waveform_2.VolSP = IR_AC
Set the frequency and time period for both channels, then rescale using SDK helpers:
bpm = 60
frequency = bpm / 60
ppg_waveform_1.Frequency = ppg_waveform_2.Frequency = frequency
ppg_waveform_1.TimePeriod = ppg_waveform_2.TimePeriod = int(1000 / frequency)
ppg_waveform_1 = device.rescale_ppg_waveform(ppg_waveform_1)
ppg_waveform_2 = device.rescale_ppg_waveform(ppg_waveform_2)
Play two-channel PPG:
device.play_ppg_ex(pointer(ppg_waveform_1), pointer(ppg_waveform_2), OutputSignalCallback(0), OutputSignalCallback(0))
Or single-channel:
device.play_ppg(1, pointer(ppg_waveform_1), OutputSignalCallback(0)) # R channel
device.play_ppg(2, pointer(ppg_waveform_2), OutputSignalCallback(0)) # IR channel