Q Peak Plotting Example#
Setup and imports#
[1]:
import matplotlib.pyplot as plt
import seaborn as sns
from fau_colors import cmaps
from pepbench.algorithms.ecg import QPeakExtractionVanLien2013
from pepbench.example_data import get_example_dataset
from pepbench.plotting.algorithms import plot_q_peak_extraction_vanlien2013
%matplotlib inline
%load_ext autoreload
%autoreload 2
Plotting style#
[2]:
plt.close("all")
palette = sns.color_palette(cmaps.faculties)
sns.set_theme(context="notebook", style="ticks", font="sans-serif", palette=palette)
plt.rcParams["figure.figsize"] = (10, 5)
Load Example Dataset#
[3]:
dataset = get_example_dataset(return_clean=True)
dataset
[3]:
ExampleDataset [2 groups/rows]
participant
0
VP_001
1
VP_002
| participant | |
|---|---|
| 0 | VP_001 |
| 1 | VP_002 |
Select the first datapoint
[5]:
datapoint = list(dataset)[0]
datapoint
[5]:
ExampleDataset [1 groups/rows]
participant
0
VP_001
| participant | |
|---|---|
| 0 | VP_001 |
Run Q-peak extraction (Van Lien 2013)#
[6]:
q_algo = QPeakExtractionVanLien2013()
# use the computed heartbeat segmentation
q_algo.extract(ecg=datapoint.ecg, heartbeats=datapoint.heartbeats, sampling_rate_hz=datapoint.sampling_rate_ecg)
[6]:
QPeakExtractionVanLien2013(handle_missing_events='warn', time_interval_ms=40)
Inspect detected points (first rows)#
[7]:
display(q_algo.points_.head())
| q_peak_sample | nan_reason | |
|---|---|---|
| heartbeat_id | ||
| 0 | 145 | <NA> |
| 1 | 572 | <NA> |
| 2 | 993 | <NA> |
| 3 | 1389 | <NA> |
| 4 | 1782 | <NA> |
Plot a few heartbeats to visualise detected Q-peaks#
determine the first three heartbeat_ids available in the reference_heartbeats index
[8]:
hb_ids = list(datapoint.reference_heartbeats.index.get_level_values("heartbeat_id").unique())[:3]
print("Plotting heartbeat ids:", hb_ids)
fig, ax = plot_q_peak_extraction_vanlien2013(datapoint, heartbeat_subset=hb_ids, normalize_time=True)
plt.show()
Plotting heartbeat ids: [0, 1, 2]
Lower-level plotting helpers#
Demonstrate the primitive plotting helpers that are used by the high-level plotting wrappers. This shows how to add R/Q peaks and ICG C/B points and heartbeat borders manually on axes.
[9]:
from pepbench.algorithms.icg import CPointExtractionScipyFindPeaks
from pepbench.plotting._utils import (
_add_ecg_q_peaks,
_add_ecg_r_peaks,
_add_heartbeat_borders,
_add_icg_b_points,
_add_icg_c_points,
_get_data,
_get_heartbeat_borders,
_get_heartbeats,
_get_reference_labels,
)
# prepare a small heartbeat subset (reuse hb_ids from above)
# hb_ids is expected to be defined in the notebook earlier; if not, compute it again
try:
hb_ids
except NameError:
hb_ids = list(datapoint.reference_heartbeats.index.get_level_values("heartbeat_id").unique())[:3]
# get sliced ECG/ICG data (no time normalization here)
ecg_data, icg_data = _get_data(datapoint, normalize_time=False, heartbeat_subset=hb_ids)
# relative heartbeats for the subset
heartbeats_rel = _get_heartbeats(datapoint, heartbeat_subset=hb_ids, normalize=True)
# heartbeat border x positions
heartbeat_borders = _get_heartbeat_borders(ecg_data, heartbeats_rel)
# reference labels for Q/B points adjusted to the subset
refs = _get_reference_labels(datapoint, heartbeat_subset=hb_ids)
q_ref = refs["q_peaks"]
b_ref = refs["b_points"]
# compute C-points for the ICG subset using the scipy find-peaks C-point extractor
c_algo = CPointExtractionScipyFindPeaks()
# extract on the sliced icg and the relative heartbeats
c_algo.extract(icg=icg_data, heartbeats=heartbeats_rel, sampling_rate_hz=datapoint.sampling_rate_icg)
c_points = c_algo.points_["c_point_sample"].dropna().astype(int)
# Now plot ECG (top) and ICG (bottom) and overlay markers using the low-level helpers
fig, axs = plt.subplots(nrows=2, sharex=True, figsize=(10, 6))
# ECG top
axs[0].plot(ecg_data.index, ecg_data.squeeze(), color=cmaps.tech[1], label="ECG")
_add_ecg_r_peaks(ecg_data, heartbeats_rel["r_peak_sample"], ax=axs[0], r_peak_marker="X", r_peak_color=cmaps.tech[3])
_add_ecg_q_peaks(ecg_data, q_ref, ax=axs[0], q_peak_marker="o", q_peak_color=cmaps.tech[4])
_add_heartbeat_borders(heartbeat_borders, ax=axs[0], heartbeat_border_color=cmaps.tech[2])
axs[0].set_ylabel("ECG amplitude")
axs[0].legend()
# ICG bottom
axs[1].plot(icg_data.index, icg_data.squeeze(), color=cmaps.tech[0], label="ICG")
_add_icg_c_points(icg_data, c_points, ax=axs[1], c_point_marker="X", c_point_color=cmaps.tech[3])
_add_icg_b_points(icg_data, b_ref, ax=axs[1], b_point_marker="o", b_point_color=cmaps.tech[4])
_add_heartbeat_borders(heartbeat_borders, ax=axs[1], heartbeat_border_color=cmaps.tech[2])
axs[1].set_ylabel("ICG amplitude")
axs[1].legend()
plt.tight_layout()
plt.show()
Normalize time#
Show how time-normalization affects plotted signals and helper indices: raw indices vs seconds-from-zero.
[10]:
# get non-normalized and normalized slices for the same heartbeat subset
ecg_raw, icg_raw = _get_data(datapoint, normalize_time=False, heartbeat_subset=hb_ids)
ecg_norm, icg_norm = _get_data(datapoint, normalize_time=True, heartbeat_subset=hb_ids)
print("Raw ECG index sample (first 5):", list(ecg_raw.index[:5]))
print("Normalized ECG index sample (first 5):", list(ecg_norm.index[:5]))
# quick visual comparison
fig, axs = plt.subplots(1, 2, figsize=(12, 3), sharey=True)
axs[0].plot(ecg_raw.index, ecg_raw.squeeze(), color=cmaps.fau[0])
axs[0].set_title("Raw time (samples)")
axs[0].set_xlabel("Sample index")
axs[1].plot(ecg_norm.index, ecg_norm.squeeze(), color=cmaps.fau[0])
axs[1].set_title("Normalized time (seconds from 0)")
axs[1].set_xlabel("Time (s)")
plt.tight_layout()
plt.show()
Raw ECG index sample (first 5): [Timestamp('2018-02-16 10:45:04.972000+0100', tz='UTC+01:00'), Timestamp('2018-02-16 10:45:04.974000+0100', tz='UTC+01:00'), Timestamp('2018-02-16 10:45:04.976000+0100', tz='UTC+01:00'), Timestamp('2018-02-16 10:45:04.978000+0100', tz='UTC+01:00'), Timestamp('2018-02-16 10:45:04.980000+0100', tz='UTC+01:00')]
Normalized ECG index sample (first 5): [0.0, 0.002, 0.004, 0.006, 0.008]
Notes:#
You can swap
QPeakExtractionVanLien2013for other algorithms available inpepbench.algorithms.ecg(e.g.,QPeakExtractionMartinez2004Neurokit,QPeakExtractionForouzanfar2018).To use non-reference heartbeats, use
datapoint.heartbeatsinstead ofdatapoint.reference_heartbeats.get_example_dataset()returns the packaged demo dataset, which is suitable for lightweight algorithm exploration and plotting.
[ ]: