"""
Time Domain Features Module for Physiological Signal Processing
This module provides comprehensive time-domain feature extraction capabilities for
physiological signals such as ECG and PPG. It implements standard Heart Rate Variability
(HRV) metrics and additional time-domain analysis methods for characterizing signal
variability and patterns.
Author: vitalDSP Team
Date: 2025-01-27
Version: 1.0.0
Key Features:
- Standard HRV metrics (SDNN, RMSSD, NN50, pNN50)
- Advanced time-domain statistics (median, IQR, CVNN)
- HRV Triangular Index and TINN computation
- Successive difference analysis (SDSD)
- Comprehensive NN interval analysis
Examples:
--------
Basic HRV analysis:
>>> import numpy as np
>>> from vitalDSP.physiological_features.time_domain import TimeDomainFeatures
>>> nn_intervals = [800, 810, 790, 805, 795, 820, 780, 815, 800, 810]
>>> tdf = TimeDomainFeatures(nn_intervals)
>>> sdnn = tdf.compute_sdnn()
>>> rmssd = tdf.compute_rmssd()
>>> print(f"SDNN: {sdnn:.2f} ms, RMSSD: {rmssd:.2f} ms")
Advanced metrics:
>>> pnn50 = tdf.compute_pnn50()
>>> cvnn = tdf.compute_cvnn()
>>> hrv_triangular = tdf.compute_hrv_triangular_index()
>>> print(f"pNN50: {pnn50:.2f}%, CVNN: {cvnn:.2f}")
Statistical analysis:
>>> median_nn = tdf.compute_median_nn()
>>> iqr_nn = tdf.compute_iqr_nn()
>>> print(f"Median NN: {median_nn:.2f} ms, IQR: {iqr_nn:.2f} ms")
"""
import numpy as np
[docs]
class TimeDomainFeatures:
"""
A class for computing time-domain features from physiological signals (ECG, PPG).
Attributes:
nn_intervals (list or np.array): The NN intervals (in milliseconds) between heartbeats.
Methods:
compute_sdnn(): Computes the standard deviation of NN intervals (SDNN).
compute_rmssd(): Computes the root mean square of successive differences (RMSSD).
compute_nn50(): Computes the number of successive NN intervals differing by >50 ms.
compute_pnn50(): Computes the percentage of NN50 over the total NN intervals.
compute_median_nn(): Computes the median NN interval.
compute_iqr_nn(): Computes the interquartile range (IQR) of NN intervals.
compute_mean_nn(): Computes the mean of NN intervals.
compute_std_nn(): Computes the standard deviation of NN intervals (alias for SDNN).
compute_pnn20(): Computes the percentage of NN intervals differing by more than 20 ms.
compute_nn20(): Computes the count of NN intervals differing by more than 20 ms.
compute_cvnn(): Computes the coefficient of variation of NN intervals (CVNN).
compute_hrv_triangular_index(): Computes the HRV Triangular Index based on the histogram of NN intervals.
compute_tinn(): Computes TINN (Triangular Interpolation of NN Interval Histogram).
compute_sdsd(): Computes the standard deviation of successive differences (SDSD).
"""
def __init__(self, nn_intervals):
"""
Initializes the TimeDomainFeatures object with NN intervals.
Args:
nn_intervals (list or np.array): The NN intervals (in milliseconds) between heartbeats.
"""
self.nn_intervals = np.array(nn_intervals)
[docs]
def compute_sdnn(self):
"""
Computes the standard deviation of NN intervals (SDNN).
Returns:
float: The SDNN value.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_sdnn()
7.483314773547883
"""
return np.std(self.nn_intervals, ddof=1)
[docs]
def compute_rmssd(self):
"""
Computes the root mean square of successive differences (RMSSD).
Returns:
float: The RMSSD value.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_rmssd()
10.0
"""
diff_nn_intervals = np.diff(self.nn_intervals)
return np.sqrt(np.mean(diff_nn_intervals**2))
[docs]
def compute_nn50(self):
"""
Computes the number of successive NN intervals differing by more than 50 ms (NN50).
Returns:
int: The NN50 value.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_nn50()
1
"""
diff_nn_intervals = np.abs(np.diff(self.nn_intervals))
return np.sum(diff_nn_intervals > 50)
[docs]
def compute_pnn50(self):
"""
Computes the percentage of NN50 over the total number of NN intervals (pNN50).
Returns:
float: The pNN50 value as a percentage.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_pnn50()
20.0
"""
# Input validation to prevent division by zero
if len(self.nn_intervals) == 0:
return 0.0
if len(self.nn_intervals) == 1:
return 0.0 # No differences possible with single interval
nn50 = self.compute_nn50()
n_successive = len(self.nn_intervals) - 1
if n_successive <= 0:
return 0.0
return 100.0 * nn50 / n_successive
[docs]
def compute_median_nn(self):
"""
Computes the median of the NN intervals.
Returns:
float: The median NN interval.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_median_nn()
800.0
"""
return np.median(self.nn_intervals)
[docs]
def compute_iqr_nn(self):
"""
Computes the interquartile range (IQR) of the NN intervals.
Returns:
float: The IQR of NN intervals.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_iqr_nn()
10.0
"""
return np.percentile(self.nn_intervals, 75) - np.percentile(
self.nn_intervals, 25
)
[docs]
def compute_mean_nn(self):
"""
Computes the mean of NN intervals.
Returns:
float: The mean NN interval.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_mean_nn()
800.0
"""
return np.mean(self.nn_intervals)
[docs]
def compute_std_nn(self):
"""
Computes the standard deviation of NN intervals (alias for SDNN).
Returns:
float: The standard deviation of NN intervals (SDNN).
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_std_nn()
7.483314773547883
"""
return self.compute_sdnn()
[docs]
def compute_pnn20(self):
"""
Computes the percentage of successive NN intervals differing by more than 20 ms (pNN20).
Returns:
float: The pNN20 value as a percentage.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_pnn20()
40.0
"""
# Input validation to prevent division by zero
if len(self.nn_intervals) == 0:
return 0.0
if len(self.nn_intervals) == 1:
return 0.0 # No differences possible with single interval
diff_nn_intervals = np.abs(np.diff(self.nn_intervals))
nn20 = np.sum(diff_nn_intervals > 20)
n_successive = len(self.nn_intervals) - 1
if n_successive <= 0:
return 0.0
return 100.0 * nn20 / n_successive
[docs]
def compute_cvnn(self):
"""
Computes the coefficient of variation of NN intervals (CVNN), which is the ratio of the standard deviation
to the mean NN interval.
Returns:
float: The CVNN value.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_cvnn()
0.009354143466934854
"""
# Input validation to prevent division by zero
if len(self.nn_intervals) < 2:
return 0.0
mean_nn = self.compute_mean_nn()
if mean_nn == 0:
return 0.0 # Avoid division by zero
sdnn = self.compute_sdnn()
if not np.isfinite(sdnn):
return 0.0
return sdnn / mean_nn
[docs]
def compute_hrv_triangular_index(self):
"""
Computes the HRV Triangular Index, which is the total number of NN intervals divided by the
height of the histogram of all NN intervals.
Returns:
float: The HRV Triangular Index.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_hrv_triangular_index()
5.0
"""
hist, bin_edges = np.histogram(self.nn_intervals, bins="auto")
max_bin_count = np.max(hist)
return len(self.nn_intervals) / max_bin_count
[docs]
def compute_tinn(self):
"""
Computes the Triangular Interpolation of NN Interval Histogram (TINN).
TINN is the baseline width of the NN interval histogram triangle,
computed as the difference between the points N and M on the x-axis
where the triangular interpolation reaches the baseline.
Returns:
float: The TINN value in the same units as nn_intervals.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_tinn()
20.0
"""
hist, bin_edges = np.histogram(self.nn_intervals, bins="auto")
bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
max_bin_index = np.argmax(hist)
peak_x = bin_centers[max_bin_index]
peak_y = hist[max_bin_index]
if peak_y == 0:
return 0.0
best_n = bin_centers[0]
best_m = bin_centers[-1]
min_error = np.inf
for n_idx in range(max_bin_index + 1):
for m_idx in range(max_bin_index, len(bin_centers)):
n_val = bin_centers[n_idx]
m_val = bin_centers[m_idx]
if m_val <= n_val:
continue
triangle = np.zeros_like(hist, dtype=float)
for i, x in enumerate(bin_centers):
if x <= n_val or x >= m_val:
triangle[i] = 0.0
elif x <= peak_x:
triangle[i] = peak_y * (x - n_val) / (peak_x - n_val)
else:
triangle[i] = peak_y * (m_val - x) / (m_val - peak_x)
error = np.sum((hist - triangle) ** 2)
if error < min_error:
min_error = error
best_n = n_val
best_m = m_val
return best_m - best_n
[docs]
def compute_sdsd(self):
"""
Computes the standard deviation of successive differences (SDSD) between NN intervals.
Returns:
float: The SDSD value.
Example:
>>> tdf = TimeDomainFeatures([800, 810, 790, 805, 795])
>>> tdf.compute_sdsd()
10.0
"""
diff_nn_intervals = np.diff(self.nn_intervals)
return np.std(diff_nn_intervals, ddof=1)