Signal Conditioning, Filtering & Quality Assessment (SQI)¶
In clinical electrocardiography and edge telemetry, raw biopotential measurements are susceptible to diverse physiological and electromagnetic disturbances. The AI ECG SDK (ecg_sdk) provides a robust, zero-phase signal conditioning suite and multi-metric Signal Quality Index (SQI) pipeline designed to prepare recordings for automated AI interpretation and clinical paper visualization.
1. Noise Archetypes in Electrocardiography¶
FREQUENCY SPECTRUM OF NOISE IN CLINICAL ECG
┌───────────────────────┬──────────────────────┬───────────────────────┐
│ Respiratory Wander │ Diagnostic QRS Band │ High-Frequency Noise │
│ (0.05 Hz — 0.5 Hz) │ (0.5 Hz — 45.0 Hz) │ (> 45.0 Hz & 50/60Hz) │
└───────────────────────┴──────────────────────┴───────────────────────┘
0 Hz 0.5 Hz 45 Hz 125 Hz (Nyquist)
- Powerline Interference (50 Hz / 60 Hz): Electromagnetic induction from AC power grids, fluorescent lighting, and ungrounded biomedical equipment.
- Respiratory Baseline Wander (0.05–0.5 Hz): Mechanical chest movements during breathing, perspiration altering skin-electrode impedance, and patient body movements.
- Electromyographic (EMG) Muscle Noise (20–500 Hz): High-frequency biopotentials originating from skeletal muscle contractions (shivering, patient anxiety, speech).
- Electrode Motion Artifacts & Disconnections: High-amplitude step discontinuities, baseline saturation, or flatlines.
2. Digital Filtering Architecture¶
2.1 Zero-Phase Butterworth Bandpass Filter (0.5–45 Hz)¶
To maintain strict temporal alignment of \(P, Q, R, S, T\) fiducial wave onsets, peaks, and offsets, the SDK applies a 4th-order zero-phase Butterworth bandpass filter implemented via Second-Order Sections (SOS):
- Forward-Backward Filtering (
sosfiltfilt): Doubles the effective filter order (\(2 \times 4 = 8\text{th}\) order) while achieving exactly zero phase distortion (\(\theta(\omega) = 0\)): $\(y(t) = f^{-1}\Big(f\big(x(t)\big)\Big)\)$ - Lower Cutoff (\(0.5\text{ Hz}\)): Attenuates slow respiration drift while preserving the low-frequency morphology of the \(T\)-wave.
- Upper Cutoff (\(45.0\text{ Hz}\) / \(100.0\text{ Hz}\)): Rejects high-frequency EMG noise while preserving rapid QRS depolarization slopes (\(\Delta V / \Delta t\)).
from ecg_sdk.core.loaders import load_mitbih_record
# Load raw record and apply bandpass filtering
sig = load_mitbih_record("106", start_time=10.0, duration=10.0)
filtered_sig = sig.filter(lowcut=0.5, highcut=45.0, notch_freq=60.0)
2.2 IIR Notch Filter (50 Hz / 60 Hz Powerline Cancellation)¶
A digital Infinite Impulse Response (IIR) notch filter cancels line hum with a narrow rejection notch centered at \(f_0 \in \{50.0, 60.0\}\text{ Hz}\) and quality factor \(Q = f_0 / \Delta f \approx 30\):
# Apply standalone 50 Hz powerline notch filter
from ecg_sdk.preprocessing.filters import iir_notch_filter
clean_lead = iir_notch_filter(sig.get_lead("II"), freq=50.0, fs=sig.sampling_rate, quality_factor=30.0)
3. Two-Stage Cascaded Median Baseline Wander Removal¶
Standard linear highpass filters can introduce undesirable phase distortions or artificial ST-segment depressions. Following de Chazal et al. (2004) and Clifford et al. (2006), the SDK provides a non-linear two-stage cascaded median filter:
flowchart LR
A[Raw ECG x_t] --> B[Stage 1: 200ms Median Filter]
B -->|Suppresses P and QRS| C[Stage 2: 600ms Median Filter]
C -->|Suppresses T-Wave| D[Estimated Baseline b_t]
A --> E[Subtraction]
D --> E
E --> F[Baseline-Corrected ECG x_tilde_t]
- Stage 1 (\(w_1 = 200\text{ ms}\)): Strips narrow \(P\)-waves and \(QRS\) complexes (\(\approx 0.20 \cdot f_s\) samples).
- Stage 2 (\(w_2 = 600\text{ ms}\)): Strips broader \(T\)-waves (\(\approx 0.60 \cdot f_s\) samples), extracting the continuous isoelectric drift \(\mathbf{b}(t)\).
- Subtraction: \(\tilde{\mathbf{x}}(t) = \mathbf{x}(t) - \mathbf{b}(t)\).
# Remove baseline wander and inspect the extracted baseline trend
cleaned_sig, baseline = sig.remove_baseline(
method="cascaded_median",
window1_ms=200.0,
window2_ms=600.0,
return_baseline=True,
)
4. Multi-Metric Signal Quality Index (SQI) Engine¶
Prior to feeding biopotentials into diagnostic AI models or downstream feature extraction, the SDK assesses data quality across 5 electrophysiological dimensions (Clifford et al., 2012; Orphanidou et al., 2015):
| Index | Formula & Definition | Physiological Interpretation |
|---|---|---|
| \(p\text{SQI}\) | \(\frac{\int_{5\text{Hz}}^{15\text{Hz}} S(f)\,df}{\int_{5\text{Hz}}^{40\text{Hz}} S(f)\,df}\) | Power Spectrum QRS Concentration: High values (\(0.4\text{--}0.9\)) indicate dominant, well-formed QRS energy. |
| \(k\text{SQI}\) | \(\kappa = \frac{\mathbb{E}[(x - \mu)^4]}{\sigma^4}\) | Kurtosis (4th Moment): Clean ECG with sharp R-peaks has \(\kappa > 5.0\). Gaussian noise has \(\kappa \approx 3.0\). |
| \(s\text{SQI}\) | \(\gamma = \frac{\mathbb{E}[(x - \mu)^3]}{\sigma^3}\) | Skewness (3rd Moment): Quantifies biopotential distribution asymmetry around the isoelectric line. |
| \(\text{basSQI}\) | \(\frac{\int_{0\text{Hz}}^{0.5\text{Hz}} S(f)\,df}{\int_{0.5\text{Hz}}^{40\text{Hz}} S(f)\,df}\) | Baseline Drift Ratio: Low values indicate negligible respiratory baseline drift. |
| \(\text{hfSQI}\) | \(\frac{\int_{45\text{Hz}}^{f_{\text{nyq}}} S(f)\,df}{\int_{0.5\text{Hz}}^{40\text{Hz}} S(f)\,df}\) | High-Frequency Noise Ratio: Low values confirm absence of severe EMG tremor or powerline noise. |
4.1 Composite Quality Score & Decision Gating¶
The composite index \(\text{cSQI} \in [0.0, 1.0]\) combines individual metric scores into a standardized clinical acceptability classification:
"excellent"(\(\text{cSQI} \ge 0.75\)): Ideal for deep learning delineation and automated biomarker extraction."acceptable"(\(0.60 \le \text{cSQI} < 0.75\)): Clinically diagnostic; minor baseline drift or noise present."unusable"(\(\text{cSQI} < 0.60\)): Saturated, flatlined, or corrupted by severe motion artifacts. Downstream AI inference should be gated and an alert raised.
# Assess multi-lead signal quality
quality_report = sig.assess_quality(acceptance_threshold=0.60)
print(quality_report.summary())
5. End-to-End Code Example¶
import ecg_sdk
from ecg_sdk.core.loaders import load_mitbih_record
# 1. Load record with 250 Hz harmonization
sig = load_mitbih_record("106", start_time=10.0, duration=8.0, target_fs=250.0)
# 2. Assess initial quality
raw_sqi = sig.assess_quality()
print("Raw Quality:", raw_sqi.overall_grade, f"({raw_sqi.overall_sqi:.3f})")
# 3. Apply conditioning pipeline
clean_sig = sig.filter(lowcut=0.5, highcut=45.0, notch_freq=60.0).remove_baseline()
# 4. Assess conditioned quality
clean_sqi = clean_sig.assess_quality()
print("Cleaned Quality:", clean_sqi.overall_grade, f"({clean_sqi.overall_sqi:.3f})")
# 5. Render 1:1 clinical paper plot
clean_sig.plot_clinical(lead="MLII", display_mode="digital", grid_style="clinical_red")