Signal Preprocessing

This notebook demonstrates noise reduction, baseline correction, bandpass filtering, and respiratory extraction for physiological signals using vitalDSP.

Setup

import numpy as np
import plotly.io as pio
pio.renderers.default = "sphinx_gallery"
from plotly import graph_objects as go
from vitalDSP.notebooks import load_sample_ecg_small, load_sample_ppg, plot_trace

fs = 128
signal_col, date_col = load_sample_ecg_small()
signal_col = np.array(signal_col)

ppg_col, ppg_date = load_sample_ppg()
ppg_col = np.array(ppg_col)

print(f"ECG signal: {len(signal_col)} samples at {fs} Hz")
plot_trace(signal_col)
ECG signal: 82176 samples at 128 Hz

Wavelet Denoising

from vitalDSP.preprocess.noise_reduction import wavelet_denoising

# Soft-threshold wavelet denoising
wavelet_denoised = wavelet_denoising(signal_col, wavelet_name='db4', level=4)

plot_trace(signal_col, wavelet_denoised)

Median, Moving Average, and Gaussian Denoising

from vitalDSP.preprocess.noise_reduction import median_denoising, moving_average_denoising, gaussian_denoising

median_denoised = median_denoising(signal_col, kernel_size=5)
ma_denoised     = moving_average_denoising(signal_col, window_size=5)
gauss_denoised  = gaussian_denoising(signal_col, sigma=1.0)

fig = go.Figure()
fig.add_trace(go.Scatter(y=signal_col[:512], mode='lines', name='Raw ECG', opacity=0.5))
fig.add_trace(go.Scatter(y=median_denoised[:512], mode='lines', name='Median Filter'))
fig.add_trace(go.Scatter(y=ma_denoised[:512], mode='lines', name='Moving Average'))
fig.add_trace(go.Scatter(y=gauss_denoised[:512], mode='lines', name='Gaussian'))
fig.update_layout(title='Noise Reduction Comparison', xaxis_title='Sample', yaxis_title='Amplitude')
fig.show()

Savitzky-Golay Smoothing

from vitalDSP.preprocess.noise_reduction import savgol_denoising

savgol_denoised = savgol_denoising(signal_col, window_length=11, polyorder=3)
plot_trace(signal_col, savgol_denoised)

Full Preprocessing Pipeline

from vitalDSP.preprocess.preprocess_operations import preprocess_signal

# Apply bandpass filter + wavelet denoising in one call
preprocessed = preprocess_signal(
    signal_col,
    sampling_rate=fs,
    filter_type='bandpass',
    lowcut=0.5,
    highcut=40.0,
    order=4,
    noise_reduction_method='wavelet',
    wavelet_name='db4',
    level=3
)

plot_trace(signal_col, preprocessed)

Baseline Estimation

from vitalDSP.preprocess.preprocess_operations import estimate_baseline

# Estimate slow baseline drift via moving average
baseline = estimate_baseline(signal_col, fs=fs, method='moving_average', window_size=64)
corrected = signal_col - baseline

fig = go.Figure()
fig.add_trace(go.Scatter(y=signal_col[:512], mode='lines', name='Raw ECG', opacity=0.5))
fig.add_trace(go.Scatter(y=baseline[:512], mode='lines', name='Baseline', line=dict(dash='dash')))
fig.add_trace(go.Scatter(y=corrected[:512], mode='lines', name='Baseline Corrected'))
fig.update_layout(title='Baseline Estimation and Correction', xaxis_title='Sample', yaxis_title='Amplitude')
fig.show()

Respiratory Component Extraction

from vitalDSP.preprocess.preprocess_operations import respiratory_filtering

# Bandpass filter to isolate the respiratory frequency band (0.1–0.5 Hz)
resp_signal = respiratory_filtering(signal_col, sampling_rate=fs, lowcut=0.1, highcut=0.5)

t = np.arange(len(signal_col)) / fs
fig = go.Figure()
fig.add_trace(go.Scatter(x=t, y=signal_col, mode='lines', name='ECG', opacity=0.4))
fig.add_trace(go.Scatter(x=t, y=resp_signal, mode='lines', name='Respiratory Component (0.1–0.5 Hz)'))
fig.update_layout(
    title='Respiratory Component Extraction from ECG',
    xaxis_title='Time (s)', yaxis_title='Amplitude'
)
fig.show()

PPG Preprocessing

# Chain denoising and bandpass filtering for PPG
ppg_denoised = wavelet_denoising(ppg_col, wavelet_name='db4', level=3)
ppg_preprocessed = preprocess_signal(
    ppg_denoised,
    sampling_rate=fs,
    filter_type='bandpass',
    lowcut=0.5,
    highcut=8.0,
    order=4,
    noise_reduction_method='moving_average'
)

plot_trace(ppg_col, ppg_preprocessed)