Документация Engee
Notebook

Анализ вариабельности сердечного ритма по ЭКГ сигналу

В проекте рассматривается анализ вариабельности сердечного ритма по ЭКГ сигналу. На основе найденных R-пиков рассчитываются RR-интервалы и частота сердечных сокращений, после чего выполняется визуализация результатов с использованием RR-тахограммы, гистограммы RR-интервалов и скаттерограммы. Дополнительно проводится сравнение нормального ритма, брадикардии и тахикардии.

Настройка отображения

Функция engee.clear() выполняет очистку рабочего пространств:

In [ ]:
engee.clear()

Для визуализации результатов доступны два режима отрисовки графиков:

  • Для статической, быстрой отрисовки - используйте gr().

  • Для интерактивной, динамической визуализации с возможностью масштабирования и просмотра значений - используйте plotlyjs().

Выберите одну из команд, закомментировав вторую. Оба бэкенда являются взаимоисключающими, и активирован будет последний вызванный.

In [ ]:
#gr()
plotlyjs()
Out[0]:
Plots.PlotlyJSBackend()

Подключим с помощью функции include файл "read.jl" для чтения файлов:

In [ ]:
include("$(@__DIR__)/read.jl")
Out[0]:
safe_read_ann (generic function with 1 method)

Установка необходимых библиотек

На данном этапе выполняется проверка наличия необходимых библиотек Julia и их автоматическая установка при отсутствии в рабочем окружении. В рассматриваемом примере используется библиотека Statistics, необходимая для выполнения базовых статистических расчетов при анализе вариабельности сердечного ритма, включая обработку RR-интервалов, расчет средних значений и оценку распределения данных.

In [ ]:
let
    installed_packages = collect(x.name for (_, x) in Pkg.dependencies() if x.is_direct_dep)
    list_packages = ["Statistics"]
    for pack in list_packages
        pack in installed_packages || Pkg.add(pack)
    end
end
using Statistics

Подключается библиотека EngeeDSP и импортируются функции, необходимые для цифровой обработки ЭКГ сигнала, включая фильтрацию, спектральный анализ, поиск пиков и выполнение свертки.

In [ ]:
import EngeeDSP.Functions: fft, butter, filter, findpeaks, conv

Обработка и анализ ЭКГ сигнала

В данном разделе рассматривается детекция R-пиков и анализ RR-интервалов ЭКГ сигнала. На основе найденных R-пиков рассчитываются RR-интервалы, после чего применяются основные методы анализа вариабельности сердечного ритма:

  • RR-тахограмма — показывает изменение длительности сердечных циклов во времени;

  • гистограмма RR-интервалов — отражает распределение RR-интервалов на выбранном фрагменте записи;

  • скаттерограмма — позволяет оценить взаимосвязь между соседними сердечными циклами.

Совместное использование этих методов позволяет наглядно проанализировать характер сердечного ритма и оценить его вариабельность.

Функция обработки ЭКГ сигнала

Представим функцию detect_r, предназначенную для обработки ЭКГ сигнала и обнаружения R-пиков. Внутри функции выполняются основные этапы, подробно рассмотренные в примере "Детекция R-пиков на ЭКГ сигнале": фильтрация сигнала, подготовка данных к детекции, поиск R-пиков, уточнение их положения, а также расчет RR-интервалов и частоты сердечных сокращений.

In [ ]:
function detect_r(ecg, fs;
    f_center = 60.0,
    bw = 6.0,
    filter_order = 6,
    smooth_window = 0.020,
    min_peak_distance = 0.30,
    search_window = 0.05
)

    N = length(ecg)

    # Подавление сетевой помехи
    f1 = f_center - bw / 2
    f2 = f_center + bw / 2

    wn1 = f1 / (fs / 2)
    wn2 = f2 / (fs / 2)

    b, a = butter(filter_order, [wn1, wn2], "stop")
    ecg_filt = filter(b, a, ecg)

    # Подготовка сигнала для детекции R-пиков
    d_ecg = vcat(0.0, diff(ecg_filt))
    sq_ecg = d_ecg .^ 2

    win = max(3, Int(round(smooth_window * fs)))
    kernel = ones(win) ./ win

    det_sig = conv(sq_ecg, kernel)
    det_sig = det_sig[1:N]

    # Поиск R-пиков
    thr = mean(det_sig) + 1.5*std(det_sig)
    min_dist = Int(round(min_peak_distance * fs))

    _, locs = findpeaks(
        det_sig,
        out = :data,
        MinPeakDistance = min_dist,
        MinPeakHeight = thr
    )

    # Уточнение положения R-пиков
    search_radius = Int(round(search_window * fs))
    r_locs = Int[]

    for idx in locs
        l = max(1, idx - search_radius)
        r = min(N, idx + search_radius)

        seg = ecg_filt[l:r]
        _, imax = findmax(seg)

        push!(r_locs, l + imax - 1)
    end

    r_locs = unique(sort(r_locs))

    # Время и амплитуды R-пиков
    r_times = (r_locs .- 1) ./ fs
    r_vals = ecg_filt[r_locs]

    # RR-интервалы и ЧСС
    rr = diff(r_times)
    rr_ms = rr .* 1000
    rr_time = r_times[2:end]
    heart_rate = 60.0 ./ rr

    return ecg_filt, r_times, r_vals, rr_ms, rr_time, heart_rate
end
Out[0]:
detect_r (generic function with 1 method)

Загрузка и обработка сигнала

Выполняется загрузка фрагмента ЭКГ сигнала из записи 122 базы MIT-BIH. В качестве анализируемого сигнала выбирается первый канал записи, после чего он передается в функцию detect_r.

In [ ]:
data = "$(@__DIR__)/mitdb/122"

hdr = read_header(data)
fs = Float64(hdr.fs)

# Выбор фрагмента сигнала для анализа
t_start = 10.0 * 60.0       # начало фрагмента, с
t_dur   = 10.0 * 60.0      # длительность фрагмента, с

# Перевод времени в номера отсчетов
sampfrom = Int(round(t_start * fs))
sampto   = sampfrom + Int(round(t_dur * fs))

# Чтение выбранного фрагмента ЭКГ сигнала
_, raw, phys = read_dat_212(data; sampfrom=sampfrom, sampto=sampto)

# Выбор первого канала ЭКГ сигнала
ecg = phys === nothing ? Float64.(raw[:, 1]) : Float64.(phys[:, 1])

# Формирование временной оси
N = length(ecg)
t = collect(0:N-1) ./ fs

# Обработка сигнала и детекция R-пиков
ecg_filt, r_times, r_vals, rr_ms, rr_time, heart_rate = detect_r(ecg, fs)
Out[0]:
([-0.8085840295479593, -0.6407114219986917, -0.8295744292817472, -1.1115044953253097, -1.218735198190335, -1.0831915944284476, -0.8753605696075456, -0.7877670680815773, -0.8717543420373358, -0.9955104486088345  …  -0.9720761258502096, -0.981535922982182, -0.9813596429430959, -0.974918498829814, -0.9792503169729933, -0.9797968784102892, -0.9649996169615994, -0.9639460503431428, -0.9465707864313421, -0.9284212640303497], [0.225, 0.9722222222222222, 1.7305555555555556, 2.4944444444444445, 3.238888888888889, 3.9583333333333335, 4.736111111111111, 5.4944444444444445, 6.25, 6.963888888888889  …  592.8388888888888, 593.6277777777777, 594.4138888888889, 595.2083333333334, 596.0027777777777, 596.7583333333333, 597.5472222222222, 598.325, 599.1194444444444, 599.9], [0.8073956333793104, 0.8065250858452058, 0.7379285779211336, 0.7061365786747847, 0.7272537751280435, 0.7440087195968599, 0.6796706508104955, 0.7280769766471427, 0.7334193595798135, 0.7459423536834737  …  0.8541559353482163, 0.9772985211844168, 0.8888330583314511, 0.8316965334704701, 0.9346614768511732, 0.9433789791995124, 0.8477715785448744, 0.8200259572797408, 0.9161066785107799, 0.9254050725231524], [747.2222222222223, 758.3333333333334, 763.8888888888888, 744.4444444444445, 719.4444444444446, 777.7777777777773, 758.3333333333337, 755.5555555555555, 713.8888888888886, 750.0  …  780.5555555555657, 788.8888888888914, 786.1111111111541, 794.4444444444798, 794.4444444443661, 755.5555555555884, 788.8888888888914, 777.7777777778283, 794.4444444443661, 780.5555555555657], [0.9722222222222222, 1.7305555555555556, 2.4944444444444445, 3.238888888888889, 3.9583333333333335, 4.736111111111111, 5.4944444444444445, 6.25, 6.963888888888889, 7.713888888888889  …  592.8388888888888, 593.6277777777777, 594.4138888888889, 595.2083333333334, 596.0027777777777, 596.7583333333333, 597.5472222222222, 598.325, 599.1194444444444, 599.9], [80.29739776951672, 79.12087912087911, 78.54545454545455, 80.59701492537313, 83.39768339768338, 77.1428571428572, 79.12087912087908, 79.41176470588235, 84.04669260700393, 80.0  …  76.86832740213424, 76.05633802816877, 76.32508833921844, 75.52447552447217, 75.52447552448297, 79.4117647058789, 76.05633802816877, 77.14285714285214, 75.52447552448297, 76.86832740213424])

Построим отфильтрованный ЭКГ сигнал с отображением найденных R-пиков.

In [ ]:
plot(t, ecg_filt,
    xlabel="Время, с",
    ylabel="Амплитуда, мВ",
    label="ЭКГ сигнал"
)
scatter!(r_times, r_vals, markersize=3, label="R-пики")
Out[0]:

Анализ RR-интервалов

Рассчитаем основные показатели сердечного ритма: средний RR-интервал, стандартное отклонение RR-интервалов и среднюю частоту сердечных сокращений.

In [ ]:
mean_rr = mean(rr_ms)       # средний RR-интервал, мс
std_rr  = std(rr_ms)        # стандартное отклонение RR-интервалов, мс
mean_hr = mean(heart_rate)  # средняя ЧСС, уд/мин

println("Средний RR-интервал: ", round(mean_rr, digits=2), " мс")
println("Стандартное отклонение RR-интервалов: ", round(std_rr, digits=2), " мс")
println("Средняя ЧСС: ", round(mean_hr, digits=2), " уд/мин")
Средний RR-интервал: 742.17 мс
Стандартное отклонение RR-интервалов: 37.34 мс
Средняя ЧСС: 81.05 уд/мин
RR-тахограмма

RR-тахограмма отражает изменение длительности RR-интервалов во времени, то есть показывает, как меняется продолжительность последовательных сердечных циклов на выбранном фрагменте ЭКГ сигнала. По этой зависимости можно оценить стабильность сердечного ритма, выявить плавные колебания RR-интервалов, а также заметить резкие изменения, которые могут быть связаны с артефактами, ошибками детекции или особенностями ритма.

In [ ]:
plot(rr_time, rr_ms,
    xlabel="Время, с",
    ylabel="RR-интервал, мс",
    title="RR-тахограмма",
    legend=false
)
Out[0]:
Гистограмма RR-интервалов

Гистограмма RR-интервалов показывает распределение длительности сердечных циклов. По оси X откладываются значения RR-интервалов в миллисекундах, а по оси Y – количество интервалов, попавших в соответствующий диапазон. Такая визуализация позволяет определить, около какого значения сосредоточена основная часть RR-интервалов, насколько выражен их разброс и присутствуют ли отдельные интервалы, заметно отличающиеся от основной группы.

In [ ]:
histogram(rr_ms,
    bins=30,
    xlabel="RR-интервал, мс",
    ylabel="Количество интервалов",
    title="Гистограмма RR-интервалов",
    legend=false
)
Out[0]:
Скаттерограмма RR-интервалов

Скаттерограмма RR-интервалов показывает взаимосвязь между соседними сердечными циклами. По оси X откладывается текущий RR-интервал , а по оси Y – следующий RR-интервал . Такой график позволяет оценить, насколько сильно длительность очередного сердечного цикла отличается от предыдущего. Если точки расположены компактно и близко к диагонали , соседние RR-интервалы имеют близкие значения, а ритм является относительно стабильным. Увеличение разброса точек отражает более выраженную вариабельность сердечного ритма или возможное наличие нерегулярных сокращений.

In [ ]:
rr_n = rr_ms[1:end-1]
rr_next = rr_ms[2:end]

# Границы осей для корректного отображения
rr_min = minimum(vcat(rr_n, rr_next))
rr_max = maximum(vcat(rr_n, rr_next))

scatter(rr_n, rr_next,
    xlabel="RR(n), мс",
    ylabel="RR(n+1), мс",
    title="Скаттерограмма RR-интервалов",
    legend=false,
    markersize=3,
    xlim=(0, 1200),
    ylim=(0, 1200)
)

plot!([0, 1200], [0, 1200],
    linestyle=:dash,
    linewidth=2
)
Out[0]:

Анализ нормального ритма, брадикардии и тахикардии

В данном разделе рассматривается сравнение нормального ритма, брадикардии и тахикардии по RR-интервалам и частоте сердечных сокращений. В общем случае нормальный сердечный ритм взрослого человека в состоянии покоя находится в диапазоне примерно 60-100 уд/мин. Брадикардия соответствует замедленному ритму, при котором ЧСС обычно ниже 60 уд/мин, а тахикардия - учащенному ритму, при котором ЧСС превышает 100 уд/мин.

В рамках примера указанные типы ритма рассматриваются как демонстрационные случаи для анализа ЭКГ сигналов. Для каждого сигнала будет выполнена детекция R-пиков, расчет RR-интервалов и средней ЧСС, а также построены RR-тахограмма, гистограмма RR-интервалов и скаттерограмма. Это позволит сравнить, как меняются длительность сердечных циклов и характер распределения RR-интервалов при нормальном ритме, брадикардии и тахикардии.

Загрузка, обработка и отображение ЭКГ сигналов

Для удобства работы сформируем функцию load_mitdb, предназначенную для загрузки выбранного фрагмента ЭКГ записи из базы MIT-BIH.

In [ ]:
function load_mitdb(record_name;
    t_start_min = 5.0,
    t_dur_min = 10.0,
    channel = 1
)
    data = "$(@__DIR__)/mitdb/$record_name"
    # Считывание информации о записи
    hdr = read_header(data)
    fs = Float64(hdr.fs)
    # Перевод времени в номера отсчетов
    t_start = t_start_min * 60.0
    t_dur = t_dur_min * 60.0
    sampfrom = Int(round(t_start * fs))
    sampto = sampfrom + Int(round(t_dur * fs))
    # Чтение выбранного фрагмента ЭКГ сигнала
    _, raw, phys = read_dat_212(data; sampfrom=sampfrom, sampto=sampto)
    # Выбор канала ЭКГ сигнала
    ecg = phys === nothing ? Float64.(raw[:, channel]) : Float64.(phys[:, channel])
    # Формирование временной оси
    N = length(ecg)
    t = collect(0:N-1) ./ fs

    return ecg, fs, t
end
Out[0]:
load_mitdb (generic function with 1 method)

Для демонстрации нормального сердечного ритма загрузим данные записи 100 и отобразим результат обработки: отфильтрованный ЭКГ сигнал с отмеченными R-пиками.

In [ ]:
ecg_norm, fs_norm, t_norm = load_mitdb(
    "100";
    t_start_min = 5.0,
    t_dur_min = 5.0,
    channel = 1
)
ecg_filt_norm, r_times_norm, r_vals_norm, rr_ms_norm, rr_time_norm, heart_rate_norm = detect_r(ecg_norm, fs_norm)

p_norm = plot(t_norm, ecg_filt_norm,
    xlabel = "Время, с",
    ylabel = "Амплитуда, мВ",
    label = "Нормальный ритм"
)
scatter!(p_norm, r_times_norm, r_vals_norm,markersize = 2,label = "R-пики")
Out[0]:

Аналогично выполним обработку записи 123, выбранной для демонстрации брадикардии. На графике будут показаны отфильтрованный ЭКГ сигнал и найденные R-пики, по которым далее рассчитываются RR-интервалы.

In [ ]:
ecg_brady, fs_brady, t_brady = load_mitdb(
    "123";
    t_start_min = 5.0,
    t_dur_min = 5.0,
    channel = 1
)
ecg_filt_brady, r_times_brady, r_vals_brady, rr_ms_brady, rr_time_brady, heart_rate_brady = detect_r(ecg_brady, fs_brady)

p_brady = plot(t_brady, ecg_filt_brady,
    xlabel = "Время, с",
    ylabel = "Амплитуда, мВ",
    label = "Брадикардия"
)
scatter!(p_brady, r_times_brady, r_vals_brady,markersize = 2,label = "R-пики")
Out[0]:

Для анализа учащенного сердечного ритма используем запись 209. После фильтрации и детекции R-пиков отобразим обработанный ЭКГ сигнал, что позволит визуально оценить уменьшение расстояния между соседними сердечными циклами.

In [ ]:
ecg_tachy, fs_tachy, t_tachy = load_mitdb(
    "209";
    t_start_min = 6.0,
    t_dur_min = 5.0,
    channel = 1
)
ecg_filt_tachy, r_times_tachy, r_vals_tachy, rr_ms_tachy, rr_time_tachy, heart_rate_tachy = detect_r(ecg_tachy, fs_tachy)

p_tachy = plot(t_tachy, ecg_filt_tachy,
    xlabel = "Время, с",
    ylabel = "Амплитуда, мВ",
    label = "Тахикардия"
)
scatter!(p_tachy, r_times_tachy, r_vals_tachy, markersize = 2,label = "R-пики")
Out[0]:

Отобразим обработанные ЭКГ сигналы для разных типов сердечного ритма.

In [ ]:
plot(p_norm, p_brady, p_tachy,
    layout = (3, 1),
    size = (1000, 850),
    margin = 40*Plots.px
)
Out[0]:

По полученным осциллограммам ЭКГ сигналов с различными типами сердечного ритма можно заметить различия в расстоянии между соседними R-пиками. При брадикардии интервалы между сердечными сокращениями увеличиваются по сравнению с нормальным ритмом, что соответствует снижению ЧСС. При тахикардии, наоборот, R-пики расположены ближе друг к другу, что указывает на учащение сердечного ритма и уменьшение длительности RR-интервалов.

Анализ RR-интервалов

Для удобства вывода численных показателей сформируем функцию print_hr, которая рассчитывает и отображает основные характеристики RR-интервалов и частоты сердечных сокращений.

In [ ]:
function print_hr(name, rr_ms, heart_rate)
    mean_rr = mean(rr_ms)          # средний RR-интервал, мс
    std_rr  = std(rr_ms)           # стандартное отклонение RR-интервалов, мс
    mean_hr = mean(heart_rate)     # средняя ЧСС, уд/мин

    println(name)
    println("Средний RR-интервал: ", round(mean_rr, digits=2), " мс")
    println("Стандартное отклонение RR-интервалов: ", round(std_rr, digits=2), " мс")
    println("Средняя ЧСС: ", round(mean_hr, digits=2), " уд/мин")
    println()
end

print_hr("Нормальный ритм", rr_ms_norm, heart_rate_norm)
print_hr("Брадикардия", rr_ms_brady, heart_rate_brady)
print_hr("Тахикардия", rr_ms_tachy, heart_rate_tachy)
Нормальный ритм
Средний RR-интервал: 771.8 мс
Стандартное отклонение RR-интервалов: 43.22 мс
Средняя ЧСС: 77.99 уд/мин

Брадикардия
Средний RR-интервал: 1172.63 мс
Стандартное отклонение RR-интервалов: 128.66 мс
Средняя ЧСС: 51.79 уд/мин

Тахикардия
Средний RR-интервал: 549.12 мс
Стандартное отклонение RR-интервалов: 116.93 мс
Средняя ЧСС: 115.26 уд/мин

По результатам статистического анализа видно, что полученные значения соответствуют ожидаемым различиям между типами сердечного ритма. Для нормального ритма средний RR-интервал составил 771,8 мс, а средняя ЧСС — 77,99 уд/мин, что соответствует диапазону нормального сердечного ритма.

При брадикардии средний RR-интервал увеличился до 1172,63 мс, а средняя ЧСС снизилась до 51,79 уд/мин. Это подтверждает замедление сердечного ритма по сравнению с нормальным состоянием. При тахикардии, наоборот, средний RR-интервал уменьшился до 549,12 мс, а средняя ЧСС увеличилась до 115,26 уд/мин, что соответствует учащенному сердечному ритму.

Таким образом, статистические показатели подтверждают результаты визуального анализа ЭКГ сигналов.

RR-тахограмма

Построим RR-тахограммы для рассматриваемых типов сердечного ритма: нормального ритма, брадикардии и тахикардии.

In [ ]:
p_rr_norm = plot(rr_time_norm, rr_ms_norm,
    xlabel = "Время, с",
    ylabel = "RR-интервал, мс",
    title = "Нормальный ритм",
    legend = false
)

p_rr_brady = plot(rr_time_brady, rr_ms_brady,
    xlabel = "Время, с",
    ylabel = "RR-интервал, мс",
    title = "Брадикардия",
    legend = false
)

p_rr_tachy = plot(rr_time_tachy, rr_ms_tachy,
    xlabel = "Время, с",
    ylabel = "RR-интервал, мс",
    title = "Тахикардия",
    legend = false
)

plot(p_rr_norm, p_rr_brady, p_rr_tachy,
    layout = (3, 1),
    size = (1000, 850),
    margin = 40*Plots.px
)
Out[0]:
Гистограмма RR-интервалов

Рассмотрим распределение RR-интервалов для анализируемых типов сердечного ритма. Гистограммы, построенные на одном графике, позволяют сравнить диапазоны значений и определить, где сосредоточена основная часть сердечных циклов для каждого случая.

In [ ]:
histogram(rr_ms_norm,
    bins = 30,
    alpha = 0.5,
    xlabel = "RR-интервал, мс",
    ylabel = "Количество интервалов",
    title = "Гистограммы RR-интервалов",
    label = "Нормальный ритм"
)

histogram!(rr_ms_brady,
    bins = 30,
    alpha = 0.5,
    label = "Брадикардия"
)

histogram!(rr_ms_tachy,
    bins = 30,
    alpha = 0.5,
    label = "Тахикардия"
)
Out[0]:

По гистограммам видно, что распределения RR-интервалов для анализируемых типов ритма занимают разные диапазоны. При нормальном ритме основная часть значений сосредоточена в области около 750–800 мс, что соответствует средней ЧСС около 78 уд/мин.

Для брадикардии распределение смещено в область больших RR-интервалов, примерно 1000–1300 мс. Это подтверждает увеличение длительности сердечных циклов и снижение частоты сердечных сокращений. При тахикардии распределение, наоборот, смещается в область меньших RR-интервалов, что соответствует учащенному сердечному ритму.

Так как анализ выполняется на реальных ЭКГ записях, распределения могут иметь неидеальную форму. В частности, для тахикардии заметны два характерных максимума, что указывает на наличие двух преобладающих диапазонов RR-интервалов в выбранном фрагменте записи. Это может быть связано с изменением ритма внутри анализируемого участка или особенностями реального клинического сигнала.

Таким образом, гистограммы позволяют наглядно сравнить длительность сердечных циклов и показать различия между нормальным ритмом, брадикардией и тахикардией по распределению RR-интервалов.

Скаттерограмма RR-интервалов

Рассмотрим скаттерограммы RR-интервалов для анализируемых типов сердечного ритма.

In [ ]:
rr_n_norm = rr_ms_norm[1:end-1]
rr_next_norm = rr_ms_norm[2:end]

rr_n_brady = rr_ms_brady[1:end-1]
rr_next_brady = rr_ms_brady[2:end]

rr_n_tachy = rr_ms_tachy[1:end-1]
rr_next_tachy = rr_ms_tachy[2:end]

rr_min = 0
rr_max = 1800

# Нормальный ритм
p_scatter_norm = scatter(rr_n_norm, rr_next_norm,
    xlabel = "RR(n), мс",
    ylabel = "RR(n+1), мс",
    title = "Нормальный ритм",
    legend = false,
    markersize = 3,
    xlim = (rr_min, rr_max),
    ylim = (rr_min, rr_max)
)

plot!(p_scatter_norm, [rr_min, rr_max], [rr_min, rr_max],
    linestyle = :dash,
    linewidth = 2
)

# Брадикардия
p_scatter_brady = scatter(rr_n_brady, rr_next_brady,
    xlabel = "RR(n), мс",
    ylabel = "RR(n+1), мс",
    title = "Брадикардия",
    legend = false,
    markersize = 3,
    xlim = (rr_min, rr_max),
    ylim = (rr_min, rr_max)
)

plot!(p_scatter_brady, [rr_min, rr_max], [rr_min, rr_max],
    linestyle = :dash,
    linewidth = 2
)

# Тахикардия
p_scatter_tachy = scatter(rr_n_tachy, rr_next_tachy,
    xlabel = "RR(n), мс",
    ylabel = "RR(n+1), мс",
    title = "Тахикардия",
    legend = false,
    markersize = 3,
    xlim = (rr_min, rr_max),
    ylim = (rr_min, rr_max)
)

plot!(p_scatter_tachy, [rr_min, rr_max], [rr_min, rr_max],
    linestyle = :dash,
    linewidth = 2
)

# Отображение скаттерограмм
plot(p_scatter_norm, p_scatter_brady, p_scatter_tachy,
    layout = (3, 1),
    size = (800, 1400),
    margin = 40*Plots.px
)
Out[0]:

Для нормального ритма точки расположены достаточно компактно и преимущественно сосредоточены вблизи диагональной линии. Это показывает, что соседние RR-интервалы имеют близкие значения, а изменение длительности сердечных циклов выражено умеренно.

Для брадикардии точки смещены в область больших RR-интервалов и распределены более широко. Четко выраженного компактного участка не наблюдается, что указывает на большую изменчивость длительности сердечных циклов. Такой характер также согласуется с гистограммой RR-интервалов, где распределение имеет более широкий диапазон значений.

Для тахикардии можно заметить два выраженных участка расположения точек: один в области коротких RR-интервалов, другой – в области более длинных интервалов. Это показывает, что в выбранном фрагменте присутствуют два характерных диапазона длительности сердечных циклов. Такой результат согласуется с гистограммой, где также наблюдаются два заметных максимума распределения.

Для более наглядного сравнения все три скатерограммы дополнительно отображаются на одном графике.

In [ ]:
p_scatter_all = scatter(rr_n_norm, rr_next_norm,
    xlabel = "RR(n), мс",
    ylabel = "RR(n+1), мс",
    label = "Нормальный ритм",
    markersize = 3,
    xlim = (rr_min, rr_max),
    ylim = (rr_min, rr_max),
    legend_position = :outertopright
)

scatter!(p_scatter_all, rr_n_brady, rr_next_brady,
    label = "Брадикардия",
    markersize = 3
)

scatter!(p_scatter_all, rr_n_tachy, rr_next_tachy,
    label = "Тахикардия",
    markersize = 3
)

plot!(p_scatter_all, [rr_min, rr_max], [rr_min, rr_max],
    linestyle = :dash,
    linewidth = 2,
    label = false
)
Out[0]:

На общей скатерограмме RR интервалов видно, что три типа ритма занимают разные области значений.

Используемые материалы

В работе использовались реальные экспериментальные записи электрокардиограммы, полученные из открытого ресурса PhysioNet. В качестве исходных данных применялась база MIT-BIH Arrhythmia Database. Для первичного анализа вариабельности сердечного ритма использовалась запись 122. Для сравнения различных типов сердечного ритма дополнительно применялись записи 100, 123 и 209, соответствующие демонстрационным примерам нормального ритма, брадикардии и тахикардии.

Заключение

В данном примере был рассмотрен анализ вариабельности сердечного ритма по ЭКГ сигналу. На основе найденных R-пиков были рассчитаны RR-интервалы и частота сердечных сокращений, после чего построены основные зависимости для оценки характера сердечного ритма.

Работа примера включает следующие основные этапы:

Загрузка данных. Исходные данные ЭКГ были получены из открытой базы данных PhysioNet MIT-BIH Arrhythmia Database. Для анализа использовались реальные экспериментальные записи электрокардиограммы.

Детекция R-пиков. Для обработки ЭКГ сигнала применялась функция detect_r, в которой выполняются фильтрация сигнала, подготовка к детекции, поиск R-пиков и уточнение их положения. Внутри данной функции используются функции библиотеки EngeeDSP, включая butter, filter, conv и findpeaks.

Расчет RR-интервалов и ЧСС. На основе временных координат R-пиков были рассчитаны RR-интервалы и частота сердечных сокращений. Дополнительно определялись средний RR-интервал, стандартное отклонение RR-интервалов и средняя ЧСС.

Анализ вариабельности сердечного ритма. Для оценки характера изменения сердечного ритма были построены RR-тахограмма, гистограмма RR-интервалов и скаттерограмма. Эти зависимости позволяют оценить длительность сердечных циклов, распределение RR-интервалов и взаимосвязь между соседними интервалами.

Сравнение типов сердечного ритма. Для демонстрации различий были рассмотрены нормальный ритм, брадикардия и тахикардия. По результатам анализа видно, что при брадикардии RR-интервалы увеличиваются, а средняя ЧСС снижается. При тахикардии, наоборот, RR-интервалы становятся короче, а средняя ЧСС возрастает.

В результате выполнения примера была показана последовательность анализа вариабельности сердечного ритма по ЭКГ сигналу: от детекции R-пиков до построения тахограммы, гистограммы и скаттерограммы RR-интервалов.