ECG J-point detection [ex905.0]ΒΆ

Detects j-point in a ECG signal. J-point is defined as 45 Degree at 25mm/s and 10mm/mV.

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import find_peaks

import lmlib as lm
from lmlib.utils.beta import find_max_mask
from lmlib.utils.beta import load_source_csv

y = load_source_csv('./csv-data/cerebral-vasoreg-diabetes-heaad-up-tilt_day1-s0030DA-noheader.csv',
                    time_format = "H:M:S", K=2100, k=2200, ch_select=2) # for script start
K = y.shape[0] # number of loaded samples

# --- CONFIG: SLOPE of J-POINT ---
FS = 500  # sampling frequency
SEC_PER_MM = 25e-3 # x-axis: seconds per mm
MV_PER_MM = 0.1  # y-axis: mV per mm
SLOPE_MV_SEC = ( MV_PER_MM / SEC_PER_MM )  # slope of j-point [mV per s]
SLOPE_MV_SAMP = SLOPE_MV_SEC * 1 / FS # [mV per Sample]

# -- APPLY ALSSM Model ---

# Generate ALSSM Model for a line (offset+slope)
alssm = lm.AlssmPoly(poly_degree=1)
segment = lm.Segment(a=-10, b=10, direction=lm.FORWARD, g=1000)
cost = lm.CostSegment(alssm, segment)

# Apply ALSSM Filter/approximation
separam = lm.SEParam(cost)
x_hat = separam.minimize_x()
slope = x_hat[:,1]  # Extract slope from estimated line
y_hat = cost.eval_alssm(x_hat) # Get peak estimate (on the slope line)

# Find QRS locations using simple peak detection
(peaks_ecg_ind, _) = find_peaks(y, distance=FS*0.6, height=1.0)

# find positions of slope=SLOPE_MV_SAMP (45 Degree)
peaks_ecg_slope_ind = find_max_mask(-np.abs(slope-SLOPE_MV_SAMP), () , locs=peaks_ecg_ind, range_start=+int(FS*0.05), range_end=+int(FS*0.15))

# Get trajectories & windows (to be displayed in the plots)
trajs_edge = lm.map_trajectories(cost.trajectories(x_hat[peaks_ecg_slope_ind]), peaks_ecg_slope_ind, K, merge_ks=True)
wins = lm.map_window(cost.window(), peaks_ecg_slope_ind, K, merge_ks=True)

# ---------- PLOTTING  --------------
k = range(K)
_, axs = plt.subplots(2, 1, sharex='all', figsize=(8, 4))

axs[0].plot(k, y, lw=0.5, c='k', label='')
axs[0].plot(k, trajs_edge[:, 0], lw=1, c='r', label='')
axs[0].scatter(peaks_ecg_ind, y[peaks_ecg_ind], s=30, marker='+',  c='k', label='QRS peak', zorder=20)
axs[0].scatter(peaks_ecg_slope_ind, y_hat[peaks_ecg_slope_ind], s=30, marker=7,  c='k', label='J point', zorder=20)

# axs[0].scatter(peaks_abp_slope_ind, offset[peaks_abp_slope_ind], lw='2', marker='+', s=120, c='r', facecolors='none', label='$x_1=0$', zorder=20)
for xc in peaks_ecg_ind:
    axs[0].axvline(x=xc, c='k', ls='--')

axs[0].set(xlabel='k', ylabel=r'$y_k$ [mV]')
axs[0].set_title('Electrocardiogram (ECG) - J-Point Detection')

axs[1].plot(k, slope, c='tab:red', label='$a_1$ (Slope)')
for xc in peaks_ecg_ind:
    axs[1].axvline(x=xc, c='tab:gray', ls='--')
axs[1].axhline(y=SLOPE_MV_SAMP, c='k', ls='--', label='45 Degree')
axs[1].scatter(peaks_ecg_slope_ind, slope[peaks_ecg_slope_ind], s=30, marker=7,  c='k', label='J point', zorder=20)
axs[1].set(xlabel='k', ylabel=r'slope [mV/Sample]')

Total running time of the script: ( 0 minutes 0.486 seconds)

Gallery generated by Sphinx-Gallery