Engee documentation
Notebook

Simulation of a digital blood pressure monitor and analysis of human blood pressure

Currently, there are two most common methods of measuring blood pressure - the Korotkov method (auscultation or acoustic) and oscillometric. Each of these methods provides the necessary accuracy and convenient reproducibility, however, each of them has its advantages and disadvantages.

Currently, the Korotkov method is a non–invasive blood pressure measurement method approved by the World Health Organization. The measurement is performed using a tonometer, and listening to tones from an artery clamped by a cuff is performed using a stethoscope or microphone.

The oscillometric method, like the Korotkov method, is based on recording arterial pulsations when the laminar blood flow changes to turbulent, but without listening to tones. The oscillometric method uses the recording of pressure fluctuations (oscillations) directly in the cuff. Thus, during the first Korotkov tone, the most dramatic increase in the amplitude of pulsations is observed – systolic pressure is recorded. A sharp decrease in the amplitude of pulsations indicates a change in the turbulent blood flow to laminar – diastolic pressure.

Using this example, you can study the basic principles of operation of a digital tonometer with the oscillometric method of measuring pressure and consider the influence of human movement during the study.

Installing the necessary libraries for further modeling.

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

General information about the structure of the cardiovascular system

To begin with, we will describe the physiological parameters of a person (patient) that are necessary for modeling

  • Pulse rate - the number of cycles of contraction (systole) and relaxation (diastole) of the heart, recorded by pulse waves in peripheral arteries per unit time

  • Systolic (upper) arterial pressure (SD) is the level of blood pressure at the moment of maximum contraction of the heart, characterizes the state of the myocardium of the left ventricle.

  • Diastolic (lower) arterial pressure (DB) is the blood pressure level at the moment of maximum relaxation of the heart, characterizes the degree of tone of the arterial walls.

In [ ]:
# Физиологические параметры человека (пацеинта)
age = 20            # Возраст, год
weight = 80         # Вес, кг
height = 180        # Рост, см

pulse_rate = 75;             # Частота пульса, уд./мин.
systolic_bp = 120.0;         # Систолическое давление, мм.рт.ст.
diastolic_bp = 80.0;         # Диастолическое давление, мм.рт.ст.

Blood pressure is measured in millimeters of mercury, abbreviated as mmHg. A blood pressure value of 120/80 means that systolic pressure is 120 mmHg, and diastolic blood pressure is 80 mmHg.

The difference between systolic and diastolic pressure values is called pulse pressure (PD). It shows how much systolic pressure exceeds diastolic pressure, which is necessary to open the semilunar aortic valve during systole. Normally, the pulse pressure is 35-55 mmHg.

Blood pressure (BP) is measured in millimeters of mercury (mmHg).
Writing a blood pressure value of 120/80 mmHg means:

  • Systolic blood pressure (DM) = 120 mmHg.

  • Diastolic blood pressure (DD) = 80 mmHg.

Pulse pressure (PD) is the difference between systolic and diastolic pressure values.:

In [ ]:
pulse_bp = systolic_bp - diastolic_bp;      # Пульсовое давление, мм.рт.ст.

To determine the average arterial pressure (MPP), which expresses the energy of continuous blood flow, the following Hickam formula can be used:

In [ ]:
mean_bp = (systolic_bp + 2*diastolic_bp)/3;   # Среднее артериальное давление, мм.рт.ст.

To assess the functional state of the cardiovascular system, the minute volume of the heart (MO) is calculated and compared with the proper value (DMO).

where 2.2 is the cardiac index, measured in liters, and PT is the body surface, calculated using a nomogram or various formulas. In this example, the Dubois formula will be used.:

And the stroke volume (UO) of the heart is calculated by the expression:

where B is the age, g.

Let's introduce the notation in English

  • PT (body surface) → BSA (Body Surface Area)

  • UO (impact volume) → SV (Stroke Volume)

  • MO (minute volume) → CO (Cardiac Output)

  • DMO (due minute volume) → RCO (Required Cardiac Output)

In [ ]:
BSA =  0.007184 * (weight^0.425) * (height^0.725);                  # Поверхность тела, м^2 
SV = 101 + 0.5 * systolic_bp - 1.08 * diastolic_bp - 0.6 * age;     # Ударный объем, мл
CO = SV * pulse_rate;                                               # Минутный объем, мл/мин 
RCO = 2.2 * BSA * 1000;                                             # Должный минутный объем, мл/мин

We will display the obtained results of calculating the parameters of the cardiovascular system (CVS).

In [ ]:
println("Результаты расчета параметров сердечно сосудистой системы (ССС):")
println("----------------------------------------------------------------")
println("1. Пульсовое давление (ПП): ", round(pulse_bp; digits=1), " мм.рт.ст")
println("2. Среднее артериально давление (СрАД): ", round(mean_bp; digits=1), " мм.рт.ст")
println("3. Площадь поверхности тела (ПТ): ", round(BSA; digits=4), " м²")
println("4. Ударный объем (УО): ", round(SV; digits=1), " мл")
println("5. Минутный объем сердца (МО): ", round(CO; digits=1), " мл/мин")
println("6. Должный минутный объем (ДМО): ", round(RCO; digits=1), " мл/мин")
Результаты расчета параметров сердечно сосудистой системы (ССС):
----------------------------------------------------------------
1. Пульсовое давление (ПП): 40.0 мм.рт.ст
2. Среднее артериально давление (СрАД): 93.3 мм.рт.ст
3. Площадь поверхности тела (ПТ): 1.9964 м²
4. Ударный объем (УО): 62.6 мл
5. Минутный объем сердца (МО): 4695.0 мл/мин
6. Должный минутный объем (ДМО): 4392.1 мл/мин

Simulation of the operation of a digital tonometer with an oscillometric measurement method

A digital tonometer with oscillometric measurement method determines blood pressure by analyzing fluctuations (oscillations) of air pressure that occur in the cuff when a pulse wave is transmitted from the artery. Let's analyze the stages of his work sequentially.:

  1. Increasing the pressure in the cuff

  2. Slow release of pressure in the cuff

  3. Registration of fluctuations

  4. Processing of measurement results

  5. Displaying the results

The preparatory stage of modeling consists in setting the signal parameters necessary for the subsequent generation of physiological data. This stage determines the key characteristics of the time axis on which all signals will be built.

In [ ]:
# Параметры сигнала
fs = 200            # Частота дискретизации (200 Гц)
total = 60.0        # Общая длительность измерения (60 сек)

# Временная шкала
t = 0:1/fs:total - 1/fs;

The first stage is to simulate the pressure in the cuff of the tonometer. The model uses three key parameters to generate a realistic pressure curve.:

  • Duration of pumping: 10.0 seconds
    (optimal time for comfortable cuff filling without excessive load on the arteries)

  • Plateau duration: 1.0 sec
    (the period of pressure stabilization for complete compression of the artery)

  • Peak pressure: 160.0 mmHg
    (standard value exceeding typical systolic pressure by 30-40 mmHg)

In [ ]:
# Фазы измерения давления в манжете тонометра
time_pump = 10.0                # Длительность накачивания манжеты, сек
time_plato = 1.0                # Длительность плато после накачивания, сек      
peak_pressure = 160.0           # Максимальное давление в манжете, мм.рт.ст      


# Моделирования давления в манжете тонометра
function generate_pressure(time)
    pressure = zeros(length(time))       # Создаем массив нулей той же длины, что вектор времени
    
    # Накачивание давления в манжет
    pump_phase = time .<= time_pump
    pressure[pump_phase] = peak_pressure * (time[pump_phase] / time_pump).^0.85

    # Плато
    plato_start = time_pump
    plato_end = time_pump + time_plato
    plato_phase = (time .> plato_start) .& (time .<= plato_end)
    pressure[plato_phase] .= peak_pressure

    # Сброс давления в манжете
    deflate_phase = (time.> plato_end)
    deflate_time = time[deflate_phase] .- plato_end
    tau = 7.0 
    pressure[deflate_phase] = peak_pressure .* exp.(-deflate_time ./ tau)
    
    return pressure, deflate_phase, plato_end
end;

The second stage consists in modeling the oscillations in the cuff of the tonometer. The generart_oscillation function implements fluctuations taking into account the following aspects:

  • The systolic phase corresponds to a direct pulse beat.

  • The decrotic recession simulates a reflected wave from a closed aortic valve.

  • Diastolic fluctuations reflect residual pulsation in blood vessels.

  • Gaussian modulation simulates a real phenomenon: the maximum amplitude of oscillations is observed when the pressure in the cuff is equal to the average arterial pressure.

In [ ]:
function generate_oscillations(pressure, time, pulse_rate, systolic_bp, diastolic_bp, mean_bp, inflation_duration; movement=false)
    pulse_freq = pulse_rate / 60.0
    T = 1 / pulse_freq
    oscillations = zeros(length(time))
    movement_detected = false
    movement_start_time = 0
    movement_end_time = 0
    movement_start = 140.0          # Начальное давление артефактов движения, мм.рт.ст
    movement_end = 110.0            # Конечное давление артефактов движения, мм.рт.ст
    
    for i in eachindex(time)
        t_val = time[i]
        p = pressure[i]
        t_mod = mod(t_val, T)
        
        # Фаза сердечного цикла
        systolic_phase = 0.15 * T       # Систола, сек
        dicrotic_phase = 0.18 * T       # Декротическая выемка, сек
        diastolic_phase = 0.45 * T      # Диастола, сек
        
        wave = 0.0
        if t_mod < systolic_phase
            phase_ratio = t_mod / systolic_phase
            wave = 1.6 * phase_ratio * exp(1.8 * (1 - phase_ratio))
        elseif t_mod < dicrotic_phase
            phase_ratio = (t_mod - systolic_phase) / (dicrotic_phase - systolic_phase)
            wave = 1.0 - 2.2 * phase_ratio
        elseif t_mod < systolic_phase + diastolic_phase
            phase_ratio = (t_mod - dicrotic_phase) / (diastolic_phase - (dicrotic_phase - systolic_phase))
            wave = 0.3 * exp(-3.5 * phase_ratio) * sin(2*π * 1.2 * phase_ratio)
        end
        
        # Модуляция амплитуды
        spread = (systolic_bp - diastolic_bp) / 2.5
        amp_mod = exp(-0.5 * ((p - mean_bp) / spread)^2)
        
        if t_val < inflation_duration
            amp_mod = 0.0
        elseif p < 60
            amp_mod = 0.0
        elseif p > systolic_bp + 20 || p < diastolic_bp - 15
            amp_mod *= 0.02
        elseif p < 80
            amp_mod *= (p - 60) / 20
        end
        
        # Добавление базового шума
        noise = 0.03 * randn()
        oscillations[i] = 5.2 * amp_mod * (wave + noise)
        
        # Генерация артефактов движения
        if movement
            if t_val > inflation_duration && p <= movement_start && p >= movement_end
                if !movement_detected
                    movement_detected = true
                    movement_start_time = t_val
                end
                oscillations[i] += 3.5 * sin(2π * 0.8 * t_val)
                if rand() < 0.15
                    oscillations[i] += 8.0 * randn()
                end
            else
                if movement_detected
                    movement_detected = false
                    movement_end_time = t_val
                end
            end
        end
    end
    
    # Если движение не закончилось
    if movement && movement_detected
        movement_end_time = time[end]
    end
    
    return oscillations, movement_start_time, movement_end_time
end;

The third stage consists in analyzing the results obtained and detecting DM, DD and SrAD using generally accepted methods.

In [ ]:
function analysis(raw_osc, deflate_phase, pressure, time, fs)
    # Фильтрация сигнала
    ttime = 0.15                        # Время реакции фильтра
    n = Int(round(ttime * fs))          # Размер окна фильтра
    b = ones(n)/n                       # Коэффициенты КИХ-фильтра
    filt_osc = filt(b, [1], raw_osc)    # Применение фильтра
    
    # Детектирование фазы сдувания давления в манжете
    deflate_osc = filt_osc[deflate_phase]
    deflate_pres = pressure[deflate_phase]
    deflate_times = time[deflate_phase]
    
    # Построение огибающей
    abs_osc = abs.(deflate_osc)
    window_size = Int(round(0.45 * fs))
    window = ones(window_size)/window_size
    envelope = filt(window, [1], abs_osc)
    
    # Находим максимальную амплитуду
    max_amp, max_idx = findmax(envelope)
    pres_max = deflate_pres[max_idx]
    
    # Систолическое давление
    systolic_lim = 0.15 * max_amp
    systolic_region = findfirst(x -> x >= systolic_lim, envelope[1:max_idx])
    systolic_idx = something(systolic_region, 1)
    systolic = deflate_pres[systolic_idx]
    
    # Диастолическое давление
    diastolic_lim = 0.55 * max_amp
    diastolic_region = findlast(x -> x >= diastolic_lim, envelope[max_idx:end])
    diastolic_idx = diastolic_region === nothing ? length(envelope) : max_idx - 1 + diastolic_region
    diastolic = deflate_pres[diastolic_idx]
    
    return deflate_osc, deflate_times, envelope, systolic, diastolic, pres_max
end;

The first scenario.

In this scenario, we will consider the case when the patient adheres to the rules for measuring blood pressure.

In [ ]:
#Первый сценарий: без артефактов движения
pressure, deflate_phase, plato_end = generate_pressure(t)
time_period = time_pump + time_plato
osc, _, _ = generate_oscillations(pressure, t, pulse_rate, systolic_bp, diastolic_bp, mean_bp, time_period, movement=false)
deflate_osc, deflate_times, envelope, systolic_est, diastolic_est, pres_max = analysis(osc, deflate_phase, pressure, t, fs)
pres_osc = pressure .+ osc

# Создаем график с двумя подграфиками
plt_1 = plot(layout=(2,1), size=(900, 700), legend=:topright, margin=40*Plots.px)

# Верхний график: Давление в манжете тонометра
plot!(plt_1[1], t, pres_osc, 
    title = "Давление в манжете тонометра (без артефактов)",
    xlabel = "Время, с",
    ylabel = "Давление, мм рт.ст",
    label = "Давление в манжете",
    linewidth = 2,
    color = :blue,
    ylims = (0, 200),
    xlims = (0, total),
    grid = true)

# Ключевые точки давления
hline!(plt_1[1], [systolic_est], linestyle=:dash, color=:red, 
       label="Систолическое (изм: $(round(systolic_est, digits=1))")
hline!(plt_1[1], [diastolic_est], linestyle=:dash, color=:purple, 
       label="Диастолическое (изм: $(round(diastolic_est, digits=1))")
hline!(plt_1[1], [pres_max], linestyle=:dash, color=:brown, 
       label="Среднее (изм: $(round(pres_max, digits=1))")

# Реальные значения
hline!(plt_1[1], [systolic_bp], linestyle=:dot, color=:red, label="Систолическое (реал: $systolic_bp)")
hline!(plt_1[1], [diastolic_bp], linestyle=:dot, color=:purple, label="Диастолическое (реал: $diastolic_bp)")

# Нижний график: Осцилляции давления
plot!(plt_1[2], t, osc, 
    title = "Осцилляции давления",
    xlabel = "Время, с",
    ylabel = "Амплитуда, мм рт.ст",
    label = "Осцилляции",
    linewidth = 1.5,
    color = :blue,
    xlims = (0, total),
    grid = true)

# Огибающая только на фазе сдувания
plot!(plt_1[2], deflate_times, envelope, 
    label = "Огибающая осцилляций",
    linewidth = 2.0,
    color = :red)

# Пороги определения
systolic_lim = 0.15 * maximum(envelope)
diastolic_lim = 0.55 * maximum(envelope)
hline!(plt_1[2], [systolic_lim], linestyle=:dash, color=:green, label="Систолический порог (15%)")
hline!(plt_1[2], [diastolic_lim], linestyle=:dash, color=:orange, label="Диастолический порог (55%)")
hline!(plt_1[2], [maximum(envelope)], linestyle=:dash, color=:purple, label="Максимальная амплитуда")

# Вывод результатов первого сценария
println("\nРезультаты анализа первого сценария (отсутствие движения):")
println("Систолическое давление: $(round(systolic_est, digits=1)) мм рт.ст. (реальное: $systolic_bp)")
println("Диастолическое давление: $(round(diastolic_est, digits=1)) мм рт.ст. (реальное: $diastolic_bp)")
println("Среднее артериальное давление: $(round(pres_max, digits=1)) мм рт.ст.")
println("Пульс: $pulse_rate уд/мин")

# Отображения графиков
display(plt_1)
savefig(plt_1, "tonometer_no_movement.png")
Результаты анализа первого сценария (отсутствие движения):
Систолическое давление: 120.4 мм рт.ст. (реальное: 120.0)
Диастолическое давление: 73.2 мм рт.ст. (реальное: 80.0)
Среднее артериальное давление: 92.2 мм рт.ст.
Пульс: 75 уд/мин
Out[0]:
"/user/Demo_public/biomedical/modeling_blood_pressure_monitor/tonometer_no_movement.png"

From the simulation results of the first scenario, it can be seen that the resulting model detects fluctuations in human blood pressure with an accuracy of at least 5%.

The second scenario.

In this scenario, we will consider the case when the patient ignores the rules for measuring blood pressure.

In [ ]:
# Второй сценарий: с артефактами движения
osc_movement, movement_start_time, movement_end_time = generate_oscillations(pressure, t, pulse_rate, systolic_bp, diastolic_bp, mean_bp, time_period, movement=true)
deflate_osc_movement, deflate_times_movement, envelope_movement, systolic_est_movement, diastolic_est_movement, max_pres_movement = analysis(osc_movement, deflate_phase, pressure, t, fs)
pressure_movement = pressure .+ osc_movement

# Создаем график (два подграфика)
plt_2 = plot(layout=(2,1), size=(900, 700), legend=:topright, margin=40*Plots.px)

# Верхний график: Давление манжета тонометра с артефактами
plot!(plt_2[1], t, pressure_movement, 
    title = "Давление в манжете тонометра (с артефактами движения)",
    xlabel = "Время, с",
    ylabel = "Давление, мм рт.ст",
    label = "Давление в манжете",
    linewidth = 2,
    color = :blue,
    ylims = (0, 200),
    xlims = (0, total),
    grid = true)

# Выделение области артефактов движения
if !isnan(movement_start_time) && !isnan(movement_end_time)
    vspan!(plt_2[1], [movement_start_time, movement_end_time], alpha=0.25, color=:magenta, label="Артефакты движения")
end

# Ключевые точки давления
hline!(plt_2[1], [systolic_est_movement], linestyle=:dash, color=:red, label="Систолическое (изм: $(round(systolic_est_movement, digits=1))")
hline!(plt_2[1], [diastolic_est_movement], linestyle=:dash, color=:purple,label="Диастолическое (изм: $(round(diastolic_est_movement, digits=1))")
hline!(plt_2[1], [max_pres_movement], linestyle=:dash, color=:brown, label="Среднее (изм: $(round(max_pres_movement, digits=1))")

# Реальные значения давления
hline!(plt_2[1], [systolic_bp], linestyle=:dot, color=:red, label="Систолическое (реал: $systolic_bp)")
hline!(plt_2[1], [diastolic_bp], linestyle=:dot, color=:purple, label="Диастолическое (реал: $diastolic_bp)")

# Нижний график: Осцилляции с артефактами движения
plot!(plt_2[2], t, osc_movement, 
    title = "Осцилляции давления с артефактами",
    xlabel = "Время, с",
    ylabel = "Амплитуда, мм рт.ст",
    label = "Осцилляции",
    linewidth = 1.5,
    color = :blue,
    xlims = (0, total),
    grid = true)

# Огибающая
plot!(plt_2[2], deflate_times_movement, envelope_movement, 
    label = "Огибающая",
    linewidth = 2.0,
    color = :red)

# Пороги определения
systolic_lim_mov = 0.15 * maximum(envelope_movement)
diastolic_lim_mov = 0.55 * maximum(envelope_movement)
hline!(plt_2[2], [systolic_lim_mov], linestyle=:dash, color=:green, label="Систолический порог (15%)")
hline!(plt_2[2], [diastolic_lim_mov], linestyle=:dash, color=:orange, label="Диастолический порог (55%)")
hline!(plt_2[2], [maximum(envelope_movement)], linestyle=:dash, color=:purple, label="Максимальная амплитуда")

# Область артефактов движения
if !isnan(movement_start_time) && !isnan(movement_end_time)
    vspan!(plt_2[2], [movement_start_time, movement_end_time], 
           alpha=0.15, color=:magenta, label="Артефакты движения")
end

# Вывод результатов для второго сценария
println("\nРезультаты анализа второго сценария (наличие движения):")
println("Систолическое давление: $(round(systolic_est_movement, digits=1)) мм рт.ст. (реальное: $systolic_bp)")
println("Диастолическое давление: $(round(diastolic_est_movement, digits=1)) мм рт.ст. (реальное: $diastolic_bp)")
println("Среднее артериальное давление: $(round(max_pres_movement, digits=1)) мм рт.ст.")
println("Пульс: $pulse_rate уд/мин")

# Отображение графика
display(plt_2)
savefig(plt_2, "tonometer_with_movement.png")
Результаты анализа второго сценария (наличие движения):
Систолическое давление: 137.0 мм рт.ст. (реальное: 120.0)
Диастолическое давление: 81.3 мм рт.ст. (реальное: 80.0)
Среднее артериальное давление: 118.4 мм рт.ст.
Пульс: 75 уд/мин
Out[0]:
"/user/Demo_public/biomedical/modeling_blood_pressure_monitor/tonometer_with_movement.png"

According to the results of the second scenario, it is clear that the determination of systolic pressure is not correct. Erroneous detection occurs due to artifacts caused by human movement during measurement.

Visualization of scenarios for measuring human pressure in the absence and presence of artifacts caused by human movement.

In [ ]:
# Визуализация результатов моделирвоания первого и второго сценария
# Вывод результатов первого сценария
println("\nРезультаты анализа первого сценария (отсутствие движения):")
println("Систолическое давление: $(round(systolic_est, digits=1)) мм рт.ст. (реальное: $systolic_bp)")
println("Диастолическое давление: $(round(diastolic_est, digits=1)) мм рт.ст. (реальное: $diastolic_bp)")
println("Среднее артериальное давление: $(round(pres_max, digits=1)) мм рт.ст.")
println("Пульс: $pulse_rate уд/мин")

# Отображения графиков
display(plt_1)

# Вывод результатов для второго сценария
println("\nРезультаты анализа второго сценария (наличие движения):")
println("Систолическое давление: $(round(systolic_est_movement, digits=1)) мм рт.ст. (реальное: $systolic_bp)")
println("Диастолическое давление: $(round(diastolic_est_movement, digits=1)) мм рт.ст. (реальное: $diastolic_bp)")
println("Среднее артериальное давление: $(round(max_pres_movement, digits=1)) мм рт.ст.")
println("Пульс: $pulse_rate уд/мин")

# Отображение графика
display(plt_2)
Результаты анализа первого сценария (отсутствие движения):
Систолическое давление: 120.4 мм рт.ст. (реальное: 120.0)
Диастолическое давление: 73.2 мм рт.ст. (реальное: 80.0)
Среднее артериальное давление: 92.2 мм рт.ст.
Пульс: 75 уд/мин
Результаты анализа второго сценария (наличие движения):
Систолическое давление: 136.3 мм рт.ст. (реальное: 120.0)
Диастолическое давление: 81.1 мм рт.ст. (реальное: 80.0)
Среднее артериальное давление: 129.0 мм рт.ст.
Пульс: 75 уд/мин

Conclusion

In this example, a digital tonometer model with an oscillometric pressure measurement method was considered. Two scenarios were also considered:

  • cuff pressure without artefacts of human (patient) movement;
  • pressure in the cuff with artifacts of human (patient) movement.

Analyzing the results obtained, it can be said that in order to correctly determine a person's systolic and diastolic blood pressure, a number of rules must be followed.:

  1. the measurement should be carried out in a comfortable, calm environment, the room should be at room temperature;

  2. Blood pressure can only be measured after at least a five-minute rest period.;

  3. it should be remembered that the shoulder should not be squeezed by clothes, especially since it is incorrect to measure blood pressure through clothes.;

  4. Do not move or talk during the measurement.

During the initial measurement, blood pressure should be determined on both hands and then blood pressure should be measured on the hand where the pressure was higher. (The difference in blood pressure on the hands up to 10-15 mmHg is normal.)