{ "cells": [ { "cell_type": "code", "metadata": { "tags": [ "plotly-renderer-setup" ] }, "source": [ "# Configure Plotly to render inline figures via CDN so they display\n", "# in the rendered Sphinx output (RTD). Safe to ignore when running\n", "# locally in Jupyter.\n", "import plotly.io as pio\n", "pio.renderers.default = 'notebook_connected'\n" ], "outputs": [], "execution_count": null }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Import and load sample ECG & PPG" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import vital_sqi\n", "from vital_sqi.data.signal_io import ECG_reader,PPG_reader\n", "import os\n", "import plotly.graph_objects as go\n", "from plotly.subplots import make_subplots\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "file_name = \"example.edf\"\n", "ecg_data = ECG_reader(os.path.join(\"test_data\",file_name),'edf')\n", "file_name = \"ppg_smartcare.csv\"\n", "ppg_data = PPG_reader(os.path.join(\"test_data\",file_name),\n", " signal_idx=6,\n", " timestamp_idx= 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Explain the signal object structure\n", "#### The signal object contains the following attributes:\n", "1. signals: a dataframe stores the raw signals mark by a timestamp column \n", "2. sampling_rate: the signal sampling_rate\n", "3. wave_type: either 'ECG' or 'PPG'\n", "4. infor: additional information retrieve from the csv (other columns) or edf file\n", "5. sqis: a dataframe stores the scores of all sqis computation.\n", "6. rules: the list of decision rule with the threshold to reject invalid signal\n", "7. ruleset: the set of ruleset will be applied to determine valid/invalid signal" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "print(\"List of ECG object attributes: \",list(ecg_data.__dict__.keys()))\n", "print(\"List of PPG object attributes: \",list(ppg_data.__dict__.keys()))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ecg_signals = ecg_data.signals\n", "ecg_sampling_rate = int(ecg_data.sampling_rate)\n", "print(ecg_signals.head())\n", "\n", "ppg_signals = ppg_data.signals\n", "ppg_sampling_rate = int(ppg_data.sampling_rate)\n", "print(ppg_signals.head())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Explore the ECG signal with different channels" ] }, { "cell_type": "code", "execution_count": null, "id": "6d7b6da5", "metadata": {}, "outputs": [], "source": [ "ppg_signals" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = go.Figure()\n", "fig.add_trace(go.Scatter(x= ecg_signals.iloc[:,0],\n", " y= ecg_signals.iloc[:,1],\n", " name='channel 1'))\n", "fig.add_trace(go.Scatter(x= ecg_signals.iloc[:,0],\n", " y= ecg_signals.iloc[:,2],\n", " name='channel 2'))\n", "fig.show()\n", "\n", "fig = go.Figure()\n", "fig.add_trace(go.Scatter(x= ppg_signals.iloc[:,0],\n", " y= ppg_signals.iloc[:,1],\n", " name='ppg signal'))\n", "fig.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example of splitting the whole data into subsegment using time domain for ECG." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**The whole channel length will be splitted into each n-second segment**\n", "### Notes:\n", "1. Segment is split by time (every n-second) (type=0) or by beat (every n-beat) (type=1)\n", "2. The process is executed in only 1 channel at a time\n", "3. The split segment also have the option of overlapping" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from vital_sqi.preprocess.segment_split import split_segment" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#======================================================\n", "#ECG signal\n", "#======================================================\n", "channel_1 = ecg_signals.iloc[:,[0,1]]\n", "channel_2 = ecg_signals.iloc[:,[0,2]]\n", "n = 10\n", "segments_channel_1, segment_channel_1_milestones = split_segment(channel_1,\n", " sampling_rate= ecg_sampling_rate,\n", " split_type=0,\n", " duration=n)\n", "segments_channel_2, segment_channel_2_milestones = split_segment(channel_2,\n", " sampling_rate= ecg_sampling_rate,\n", " split_type=0,\n", " duration=n)\n", "\n", "# ecg_sample_idx = 0 \n", "ecg_sample_idx = np.random.randint(len(segments_channel_1))\n", "fig = go.Figure()\n", "fig.add_trace(go.Scatter(x= segments_channel_1[ecg_sample_idx].iloc[:,0],\n", " y= segments_channel_1[ecg_sample_idx].iloc[:,1],\n", " name='channel 1'))\n", "fig.add_trace(go.Scatter(x= segments_channel_2[ecg_sample_idx].iloc[:,0],\n", " y= segments_channel_2[ecg_sample_idx].iloc[:,1],\n", " name='channel 2'))\n", "fig.show()\n", "\n", "#======================================================\n", "#PPG signal\n", "#======================================================\n", "ppg_sig = ppg_signals.iloc[:,:2]\n", "n = 10\n", "segments_ppg, segment_ppg_milestones = split_segment(ppg_sig,\n", " sampling_rate= ppg_sampling_rate,\n", " split_type=0,\n", " duration=n)\n", "\n", "# ppg_sample_idx = 55 \n", "ppg_sample_idx = np.random.randint(len(segments_ppg))\n", "fig = go.Figure()\n", "fig.add_trace(go.Scatter(x= segments_ppg[ppg_sample_idx].iloc[:,0],\n", " y= segments_ppg[ppg_sample_idx].iloc[:,2],\n", " name='ppg signal'))\n", "fig.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data Preprocessing\n", "#### We will manipulate the following features: \n", "1. bandpass filtering\n", "2. smoothing\n", "3. tapering" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# -----------------------------------------------------\n", "# Apply band pass filter on ECG\n", "# -----------------------------------------------------\n", "from vital_sqi.common.band_filter import BandpassFilter\n", "# Create instances\n", "butter_bandpass = BandpassFilter(\"butter\", fs=ecg_sampling_rate)\n", "cheby_bandpass = BandpassFilter(\"cheby1\", fs=ecg_sampling_rate)\n", "ellip_bandpass = BandpassFilter(\"ellip\", fs=ecg_sampling_rate)\n", "\n", "s1_ecg = segments_channel_1[ecg_sample_idx].iloc[:,1]\n", "times_ecg = segments_channel_1[ecg_sample_idx].iloc[:,0]\n", "# Apply\n", "b1_ecg = butter_bandpass.signal_highpass_filter(s1_ecg, cutoff=1, order=5)\n", "b2_ecg = butter_bandpass.signal_highpass_filter(s1_ecg, cutoff=0.8, order=5)\n", "b3_ecg = butter_bandpass.signal_highpass_filter(s1_ecg, cutoff=0.6, order=5)\n", "c1_ecg = cheby_bandpass.signal_highpass_filter(s1_ecg, cutoff=1, order=5)\n", "e1_ecg = ellip_bandpass.signal_highpass_filter(s1_ecg, cutoff=1, order=5)\n", "\n", "fig = go.Figure()\n", "# Add traces\n", "fig.add_trace(go.Scatter(x=times_ecg, y=s1_ecg, name='original'))\n", "fig.add_trace(go.Scatter(x=times_ecg, y=b1_ecg, name='f=Butter, cutoff 1Hz'))\n", "fig.add_trace(go.Scatter(x=times_ecg, y=b2_ecg, name='f=Butter, cutoff 0.8Hz'))\n", "fig.add_trace(go.Scatter(x=times_ecg, y=b3_ecg, name='f=Butter, cutoff 0.6Hz'))\n", "fig.add_trace(go.Scatter(x=times_ecg, y=c1_ecg, name='f=Butter, cutoff 0.6Hz'))\n", "fig.add_trace(go.Scatter(x=times_ecg, y=e1_ecg, name='f=Butter, cutoff 0.6Hz'))\n", "\n", "fig.show()\n", "\n", "# -----------------------------------------------------\n", "# Apply band pass filter on PPG\n", "# -----------------------------------------------------\n", "# Create instances\n", "butter_bandpass = BandpassFilter(\"butter\", fs=ppg_sampling_rate)\n", "cheby_bandpass = BandpassFilter(\"cheby1\", fs=ppg_sampling_rate)\n", "ellip_bandpass = BandpassFilter(\"ellip\", fs=ppg_sampling_rate)\n", "\n", "s_ppg = segments_ppg[ppg_sample_idx].iloc[:,2]\n", "times_ppg = segments_ppg[ppg_sample_idx].iloc[:,0]\n", "# Apply\n", "b1_ppg = butter_bandpass.signal_highpass_filter(s_ppg, cutoff=1, order=5)\n", "b2_ppg = butter_bandpass.signal_highpass_filter(s_ppg, cutoff=0.8, order=5)\n", "b3_ppg = butter_bandpass.signal_highpass_filter(s_ppg, cutoff=0.6, order=5)\n", "c1_ppg = cheby_bandpass.signal_highpass_filter(s_ppg, cutoff=1, order=5)\n", "e1_ppg = ellip_bandpass.signal_highpass_filter(s_ppg, cutoff=1, order=5)\n", "\n", "fig = go.Figure()\n", "# Add traces\n", "fig.add_trace(go.Scatter(x=times_ppg, y=s_ppg, name='original'))\n", "fig.add_trace(go.Scatter(x=times_ppg, y=b1_ppg, name='f=Butter, cutoff 1Hz'))\n", "fig.add_trace(go.Scatter(x=times_ppg, y=b2_ppg, name='f=Butter, cutoff 0.8Hz'))\n", "fig.add_trace(go.Scatter(x=times_ppg, y=b3_ppg, name='f=Butter, cutoff 0.6Hz'))\n", "fig.add_trace(go.Scatter(x=times_ppg, y=c1_ppg, name='f=Butter, cutoff 0.6Hz'))\n", "fig.add_trace(go.Scatter(x=times_ppg, y=e1_ppg, name='f=Butter, cutoff 0.6Hz'))\n", "\n", "fig.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# --------------------------------------------------\n", "# Apply Smooth Signal and Tapering on ECG\n", "# --------------------------------------------------\n", "from vital_sqi.preprocess.preprocess_signal import smooth_signal,taper_signal\n", "# Apply\n", "smoothed_s1_ecg = smooth_signal(s1_ecg,window_len=5, window='flat')\n", "tapered_smoothed_s1_ecg = taper_signal(smoothed_s1_ecg,shift_min_to_zero=False)\n", "\n", "fig = go.Figure()\n", "# Add traces\n", "fig.add_trace(go.Scatter(x=times_ecg, y=s1_ecg, name='original'))\n", "fig.add_trace(go.Scatter(x=times_ecg, y=smoothed_s1_ecg, name='smoothed signal'))\n", "fig.add_trace(go.Scatter(x=times_ecg, y=tapered_smoothed_s1_ecg, name='tapered smoothed signal'))\n", "\n", "fig.show()\n", "\n", "\n", "# --------------------------------------------------\n", "# Apply Smooth Signal and Tapering on PPG\n", "# --------------------------------------------------\n", "smoothed_s_ppg = smooth_signal(s_ppg,window_len=5, window='flat')\n", "tapered_smoothed_s_ppg = taper_signal(b2_ppg, shift_min_to_zero=False)\n", "\n", "fig = go.Figure()\n", "# Add traces\n", "fig.add_trace(go.Scatter(x=times_ppg, y=s_ppg, name='original'))\n", "fig.add_trace(go.Scatter(x=times_ppg, y=smoothed_s_ppg, name='smoothed signal'))\n", "fig.add_trace(go.Scatter(x=times_ppg, y=tapered_smoothed_s_ppg, name='tapered smoothed signal'))\n", "\n", "fig.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example of trimming the first and the last n-minute data.\n", "**The before and after trimming 5 minutes segment (300 seconds)**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "from vital_sqi.preprocess.removal_utilities import trim_signal\n", "\n", "trimmed_ecg = trim_signal(ecg_signals.iloc[:,:2], \n", " ecg_sampling_rate, \n", " duration_left=300, \n", " duration_right=300)\n", "fig = go.Figure()\n", "fig.add_trace(go.Scatter(x= ecg_signals.iloc[:,0],\n", " y= ecg_signals.iloc[:,1],\n", " name='channel 1'))\n", "fig.add_trace(go.Scatter(x= trimmed_ecg.iloc[:,0],\n", " y= trimmed_ecg.iloc[:,1],\n", " name='trimmed channel 1'))\n", "fig.show()" ] } ], "metadata": { "kernelspec": { "display_name": "wearables", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.17" } }, "nbformat": 4, "nbformat_minor": 5 }