Source code for OBM.HypoxicBurdenMeasures
import numpy as np
from OBM.DesaturationsMeasures import DesaturationsMeasures
from OBM._ErrorHandler import _check_shape_
from OBM._ResultsClasses import HypoxicBurdenMeasuresResults
[docs]class HypoxicBurdenMeasures:
"""
Class that calculates Hypoxic Burden Features from spo2 time series.
Suppose that the data has been preprocessed.
:param begin: List of indices of beginning of each desaturation event.
:param end: List of indices of end of each desaturation event.
:param CT_Threshold: Percentage of the time spent below the “CT_Threshold” % oxygen saturation level.
:param CA_Baseline: Baseline to compute the CA feature. Default value is mean of the signal.
"""
def __init__(self, begin, end, CT_Threshold=90, CA_Baseline=None):
self.begin = begin
self.end = end
self.CT_Threshold = CT_Threshold
self.CA_Baseline = CA_Baseline
[docs] def compute(self, signal):
"""
:param signal: 1-d array, of shape (N,) where N is the length of the signal
:return:
HypoxicBurdenMeasuresResults class containing the following features:
- CA: Integral SpO2 below the xx SpO2 level normalized by the total recording time
- CT: Percentage of the time spent below the xx% oxygen saturation level
- POD: Percentage of oxygen desaturation events
- AODmax: The area under the oxygen desaturation event curve, using the maximum SpO2 value as baseline
and normalized by the total recording time
- AOD100: Cumulative area of desaturations under the 100% SpO2 level as baseline and normalized
by the total recording time
"""
_check_shape_(signal)
desaturations = {'begin': self.begin, 'end': self.end}
if self.CA_Baseline is None:
self.CA_Baseline = np.nanmean(signal)
return self.__comp_hypoxic(signal, desaturations)
def __comp_hypoxic(self, signal, desaturations_signal):
"""
Helper function, to calculate the Hypoxic Burden biomarkers from the desaturations
:param signal: 1-d array, of shape (N,) where N is the length of the signal
:param desaturations_signal: dict with 2 keys:
- begin: indices of begininning of each desaturation
- end: indices of end of each desaturation
:return:
HypoxicBurdenMeasuresResults class containing the following features:
- CA: Integral SpO2 below the xx SpO2 level normalized by the total recording time
- CT: Percentage of the time spent below the xx% oxygen saturation level
- POD: Percentage of oxygen desaturation events
- AODmax: The area under the oxygen desaturation event curve, using the maximum SpO2 value as baseline
and normalized by the total recording time
- AOD100: Cumulative area of desaturations under the 100% SpO2 level as baseline and normalized
by the total recording time
"""
desat_class = DesaturationsMeasures(desaturations_signal['begin'], desaturations_signal['end'])
desaturations, desaturation_valid, desaturation_length_all, desaturation_int_100_all, \
desaturation_int_max_all, desaturation_depth_100_all, desaturation_depth_max_all, \
desaturation_slope_all = desat_class.desat_embedding()
time_spo2_array = np.array(range(len(signal)))
for (i, desaturation) in enumerate(desaturations):
desaturation_idx = (time_spo2_array >= desaturation['Start']) & (time_spo2_array <= desaturation['End'])
if np.sum(desaturation_idx) == 0:
continue
signal = np.array(signal)
desaturation_spo2 = signal[desaturation_idx]
desaturation_max = np.nanmax(desaturation_spo2)
desaturation_valid[i] = True
desaturation_length_all[i] = desaturation['Duration']
desaturation_int_100_all[i] = np.nansum(100 - desaturation_spo2)
desaturation_int_max_all[i] = np.nansum(desaturation_max - desaturation_spo2)
desaturation_features = HypoxicBurdenMeasuresResults(self.__comp_ca(signal),
self.__comp_ct(signal),
0.0, 0.0, 0.0)
if np.sum(desaturation_valid) != 0:
desaturation_features.POD = np.nansum(desaturation_length_all[desaturation_valid]) / len(signal)
desaturation_features.AODmax = np.nansum(desaturation_int_max_all[desaturation_valid]) / len(signal)
desaturation_features.AOD100 = np.nansum(desaturation_int_100_all[desaturation_valid]) / len(signal)
return desaturation_features
def __comp_ca(self, signal):
"""
Compute the cumulative area biomarker
:param signal: 1-d array, of shape (N,) where N is the length of the signal
:return: CA, the cumulative area (float)
"""
with np.errstate(invalid='ignore'):
signal_under_baseline = signal[signal < self.CA_Baseline]
if len(signal_under_baseline) == 0:
return 0.0
# return integrate.cumtrapz(signal_under_baseline) / len(signal)
return sum(signal_under_baseline) / len(signal)
def __comp_ct(self, signal):
"""
Compute the cumulative time biomarker
:param signal: 1-d array, of shape (N,) where N is the length of the signal
:return: CT, the cumulative time (float)
"""
with np.errstate(invalid='ignore'):
return 100 * len(signal[signal <= self.CT_Threshold]) / len(signal)