{ "cells": [ { "cell_type": "markdown", "id": "cell-0", "metadata": {}, "source": [ "# Autonomic and ECG/PPG Feature Engineering\n", "\n", "This notebook demonstrates ECG morphology analysis, PPG autonomic nervous system features, SpO2 and perfusion index from dual-wavelength PPG, and ECG-PPG synchronization metrics." ] }, { "cell_type": "markdown", "id": "cell-1", "metadata": {}, "source": [ "## Setup" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-2", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import plotly.io as pio\n", "pio.renderers.default = \"sphinx_gallery\"\n", "from plotly import graph_objects as go\n", "from vitalDSP.notebooks import load_sample_ecg_small, load_sample_ppg, plot_trace\n", "\n", "fs = 128\n", "signal_col, date_col = load_sample_ecg_small()\n", "signal_col = np.array(signal_col)\n", "\n", "ppg_col, ppg_date = load_sample_ppg()\n", "ppg_col = np.array(ppg_col)\n", "\n", "print(f\"ECG: {len(signal_col)} samples at {fs} Hz\")\n", "print(f\"PPG: {len(ppg_col)} samples\")" ] }, { "cell_type": "markdown", "id": "cell-3", "metadata": {}, "source": [ "## ECG Morphology Features" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-4", "metadata": {}, "outputs": [], "source": [ "from vitalDSP.feature_engineering.ecg_autonomic_features import ECGExtractor\n", "\n", "ecg_extractor = ECGExtractor(signal_col, sampling_frequency=fs)\n", "\n", "# Detect R peaks\n", "r_peaks = ecg_extractor.detect_r_peaks()\n", "print(f\"R peaks detected: {len(r_peaks)}\")\n", "\n", "# Compute ECG morphology intervals\n", "qrs_duration = ecg_extractor.compute_qrs_duration()\n", "pr_interval = ecg_extractor.compute_pr_interval()\n", "qt_interval = ecg_extractor.compute_qt_interval()\n", "st_interval = ecg_extractor.compute_st_interval()\n", "\n", "print(\"ECG Morphology Features:\")\n", "print(f\" QRS Duration : {qrs_duration:.4f} s\")\n", "print(f\" PR Interval : {pr_interval:.4f} s\")\n", "print(f\" QT Interval : {qt_interval:.4f} s\")\n", "print(f\" ST Interval : {st_interval:.4f} s\")\n", "\n", "# Visualize R peaks on ECG\n", "fig = go.Figure()\n", "t = np.arange(len(signal_col)) / fs\n", "fig.add_trace(go.Scatter(x=t[:512], y=signal_col[:512], mode='lines', name='ECG'))\n", "r_in_range = [r for r in r_peaks if r < 512]\n", "if r_in_range:\n", " fig.add_trace(go.Scatter(\n", " x=np.array(r_in_range) / fs,\n", " y=signal_col[r_in_range],\n", " mode='markers', marker=dict(color='red', size=8, symbol='x'),\n", " name='R Peaks'\n", " ))\n", "fig.update_layout(title='ECG with R Peak Detection', xaxis_title='Time (s)', yaxis_title='Amplitude')\n", "fig.show()" ] }, { "cell_type": "markdown", "id": "cell-5", "metadata": {}, "source": [ "## P-Wave and S-Wave Analysis" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-6", "metadata": {}, "outputs": [], "source": [ "p_wave_dur = ecg_extractor.compute_p_wave_duration()\n", "s_wave = ecg_extractor.compute_s_wave()\n", "\n", "print(f\"P-Wave Duration : {p_wave_dur:.4f} s\")\n", "print(f\"S-Wave Feature : {s_wave}\")" ] }, { "cell_type": "markdown", "id": "cell-7", "metadata": {}, "source": [ "## Arrhythmia Detection" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-8", "metadata": {}, "outputs": [], "source": [ "arrhythmias = ecg_extractor.detect_arrhythmias()\n", "print(\"Arrhythmia Detection:\")\n", "print(arrhythmias)" ] }, { "cell_type": "markdown", "id": "cell-9", "metadata": {}, "source": [ "## PPG Autonomic Nervous System Features" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-10", "metadata": {}, "outputs": [], "source": [ "from vitalDSP.feature_engineering.ppg_autonomic_features import PPGAutonomicFeatures\n", "\n", "ppg_auto = PPGAutonomicFeatures(ppg_col, sampling_frequency=fs)\n", "\n", "# Respiratory Sinus Arrhythmia — autonomic balance indicator\n", "rsa = ppg_auto.compute_rsa()\n", "print(f\"RSA (Respiratory Sinus Arrhythmia): {rsa:.4f}\")\n", "\n", "# Respiratory Rate Variability\n", "rrv = ppg_auto.compute_rrv()\n", "print(f\"RRV (Resp. Rate Variability) : {rrv:.4f}\")\n", "\n", "# Fractal dimension of PPG\n", "fd = ppg_auto.compute_fractal_dimension()\n", "print(f\"Fractal Dimension : {fd:.4f}\")\n", "\n", "# DFA scaling exponent\n", "dfa = ppg_auto.compute_dfa()\n", "print(f\"DFA Scaling Exponent : {dfa:.4f}\")" ] }, { "cell_type": "markdown", "id": "cell-11", "metadata": {}, "source": [ "## PPG Light Features (SpO2, Perfusion Index)" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-12", "metadata": {}, "outputs": [], "source": "from vitalDSP.feature_engineering.ppg_light_features import PPGLightFeatureExtractor\n\n# Simulate IR and Red PPG signals (in real use, load dual-wavelength sensor data)\nir_signal = ppg_col\nred_signal = ppg_col * 0.95 + np.random.normal(0, 0.001, len(ppg_col))\n\nppg_light = PPGLightFeatureExtractor(ir_signal=ir_signal, red_signal=red_signal, sampling_freq=fs)\n\nspo2_values, spo2_indices = ppg_light.calculate_spo2()\npi = ppg_light.calculate_perfusion_index()\nrr = ppg_light.calculate_respiratory_rate()\nppr = ppg_light.calculate_ppr()\n\nprint(\"PPG Light-Based Features:\")\nprint(f\" SpO2 (mean) : {float(np.mean(spo2_values)):.1f} %\")\nprint(f\" Perfusion Index : {pi:.4f} %\")\nprint(f\" Respiratory Rate : {rr:.2f} breaths/min\")\nprint(f\" PPR (Pulse/Pleth) : {ppr:.4f}\")" }, { "cell_type": "markdown", "id": "cell-13", "metadata": {}, "source": [ "## ECG-PPG Synchronization" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-14", "metadata": {}, "outputs": [], "source": [ "from vitalDSP.feature_engineering.ecg_ppg_synchronization_features import ECGPPGSynchronization\n", "\n", "sync = ECGPPGSynchronization(\n", " ecg_signal=signal_col,\n", " ppg_signal=ppg_col,\n", " ecg_fs=fs,\n", " ppg_fs=fs\n", ")\n", "\n", "# Pulse Transit Time — correlates with blood pressure\n", "ptt = sync.compute_ptt()\n", "print(f\"Pulse Transit Time (PTT) : {ptt:.4f} s\")\n", "\n", "# Pulse Arrival Time\n", "pat = sync.compute_pat()\n", "print(f\"Pulse Arrival Time (PAT) : {pat:.4f} s\")\n", "\n", "# Heart rate / pulse rate synchrony\n", "hr_pr = sync.compute_hr_pr_sync()\n", "print(f\"HR-PR Synchrony : {hr_pr:.4f}\")\n", "\n", "# PPG rise time\n", "rise_time = sync.compute_ppg_rise_time()\n", "print(f\"PPG Rise Time : {rise_time:.4f} s\")" ] }, { "cell_type": "markdown", "id": "cell-15", "metadata": {}, "source": [ "## ECG-PPG Overlay Visualization" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-16", "metadata": {}, "outputs": [], "source": [ "# Show ECG and PPG overlaid, aligned in time\n", "n = min(len(signal_col), len(ppg_col), 512)\n", "t = np.arange(n) / fs\n", "\n", "ecg_norm = (signal_col[:n] - signal_col[:n].mean()) / (signal_col[:n].std() + 1e-8)\n", "ppg_norm = (ppg_col[:n] - ppg_col[:n].mean()) / (ppg_col[:n].std() + 1e-8)\n", "\n", "fig = go.Figure()\n", "fig.add_trace(go.Scatter(x=t, y=ecg_norm, mode='lines', name='ECG (normalized)'))\n", "fig.add_trace(go.Scatter(x=t, y=ppg_norm, mode='lines', name='PPG (normalized)'))\n", "fig.update_layout(\n", " title='ECG-PPG Synchronization',\n", " xaxis_title='Time (s)', yaxis_title='Normalized Amplitude'\n", ")\n", "fig.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.9.0" } }, "nbformat": 4, "nbformat_minor": 5 }