如何绘制模拟心电图?

Posted

技术标签:

【中文标题】如何绘制模拟心电图?【英文标题】:How to draw a simulated ECG? 【发布时间】:2022-01-13 22:05:43 【问题描述】:

我想创作的东西(不需要很精确,只是为了娱乐目的):

ECG DEMO

ECG Picture

我有什么:

我有一个服务的 REST API,让我可以查询佩戴脉搏测量设备的人的当前 BPM。因此,我没有指示何时发生炉膛跳动的事件。我必须用给定的 BPM 来计算这个。但这不是问题。

问题:

您将如何绘制线条并使其在到达右上角时从左到右消失并重置。我在 C# WPF 方面有一些经验,这就是我在那里创建 REST Querying 的原因。绘图部分是否有一些库?有没有简单的手动操作方法?

我真的很感激任何建议,因为我在互联网上找不到像这样的相当具体的问题。所以谢谢你!

更新: 我使用 WritableBitmap 得到了一些工作,但它看起来很糟糕。关于如何获得更好的分辨率的任何想法?我已经提高了位图的分辨率,但它看起来仍然很糟糕。是否有某种抗锯齿功能?

【问题讨论】:

下次自己贴图,我已经给你做了。 【参考方案1】:

这会让你开始:

安装 WriteableBitmapEx 包:

Install-Package WriteableBitmapEx

代码:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" Width="256" Height="256">
    <Grid>
        <Image x:Name="Image1" Stretch="Fill" />
    </Grid>
</Window>

代码:

#nullable enable
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace WpfApp1

    public partial class MainWindow
    
        private readonly WriteableBitmap _bitmap;
        private readonly DispatcherTimer _timer;
        private int _bitmapCursor;

        public MainWindow()
        
            InitializeComponent();

            Loaded += MainWindow_Loaded;

            _bitmap = BitmapFactory.New(256, 256);

            Image1.Source = _bitmap;

            _timer = new DispatcherTimer(
                TimeSpan.FromMilliseconds(20),
                DispatcherPriority.Render,
                Tick,
                Dispatcher.CurrentDispatcher
            );
        

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        
            _timer.Start();
        

        private void Tick(object? sender, EventArgs e)
        
            var cy = _bitmap.Height * 0.5;
            var y = (int)(cy + Math.Sin(DateTime.Now.TimeOfDay.TotalSeconds) * cy);
            _bitmap.DrawLine(_bitmapCursor, 0, _bitmapCursor, _bitmap.PixelHeight - 1, Colors.Transparent);
            _bitmap.SetPixel(_bitmapCursor, y, Colors.Red);
            _bitmapCursor++;
            _bitmapCursor %= _bitmap.PixelWidth;
        
    

在这里,我只是在我正在绘制的值前面擦除一个 1 像素宽的矩形,在你的情况下,擦除一个更大的矩形以获得你想要的效果。

请注意,您需要保留先前值的历史记录,并从中画一条线,而不是绘制实体图。

此外,您可能还想查看这些库:

https://sourceforge.net/projects/ecgtoolkit-cs/

https://github.com/rdtek/ECGViewer

https://github.com/Refactoring/ECGToolkit

【讨论】:

你先生,太棒了。我会检查它并在我实施后立即报告!谢谢 不是位图,而是更新 Canvas 的两个 Polylines 子元素的 Points 集合。 @Clemens 这也有道理!【参考方案2】:

您可以使用 LinearGradientBrush 作为 OpacityMask 将图像的某些部分涂黑,然后水平动画偏移:

<Grid Background="Black" HorizontalAlignment="Center" VerticalAlignment="Center">
    <Image Source="4ku8e.png">
        <Image.Triggers>
            <EventTrigger RoutedEvent="Image.Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="Gradient1" Storyboard.TargetProperty="Offset" From="-1.0" To="0" Duration="0:0:6" RepeatBehavior="Forever" />
                        <DoubleAnimation Storyboard.TargetName="Gradient2" Storyboard.TargetProperty="Offset" From="-0.9" To="0.1" Duration="0:0:6" RepeatBehavior="Forever" />
                        <DoubleAnimation Storyboard.TargetName="Gradient3" Storyboard.TargetProperty="Offset" From="-0.5" To="0.5" Duration="0:0:6" RepeatBehavior="Forever" />
                        <DoubleAnimation Storyboard.TargetName="Gradient4" Storyboard.TargetProperty="Offset" From="0" To="1" Duration="0:0:6" RepeatBehavior="Forever" />
                        <DoubleAnimation Storyboard.TargetName="Gradient5" Storyboard.TargetProperty="Offset" From="0" To="1" Duration="0:0:6" RepeatBehavior="Forever" />
                        <DoubleAnimation Storyboard.TargetName="Gradient6" Storyboard.TargetProperty="Offset" From="0.1" To="1.1" Duration="0:0:6" RepeatBehavior="Forever" /> 
                        <DoubleAnimation Storyboard.TargetName="Gradient7" Storyboard.TargetProperty="Offset" From="0.5" To="1.5" Duration="0:0:6" RepeatBehavior="Forever" /> 
                        <DoubleAnimation Storyboard.TargetName="Gradient8" Storyboard.TargetProperty="Offset" From="1" To="2.0" Duration="0:0:6" RepeatBehavior="Forever" /> 
                        <DoubleAnimation Storyboard.TargetName="Gradient9" Storyboard.TargetProperty="Offset" From="1" To="2.0" Duration="0:0:6" RepeatBehavior="Forever" /> 
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Image.Triggers>
        <Image.OpacityMask>
            <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                <LinearGradientBrush.GradientStops>
                    <GradientStop x:Name="Gradient1" Color="#00000000" />
                    <GradientStop x:Name="Gradient2" Color="#00000000" />
                    <GradientStop x:Name="Gradient3" Color="#FF000000" />
                    <GradientStop x:Name="Gradient4" Color="#FF000000" />
                    <GradientStop x:Name="Gradient5" Color="#00000000" />
                    <GradientStop x:Name="Gradient6" Color="#00000000" />
                    <GradientStop x:Name="Gradient7" Color="#FF000000" />
                    <GradientStop x:Name="Gradient8" Color="#FF000000" />
                    <GradientStop x:Name="Gradient9" Color="#00000000" />
                </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
        </Image.OpacityMask>
    </Image>
</Grid>

这不是 100% 完美的,因为您的心电图图像的左右两侧都有空白区域,但如果您将其剪辑,它会更好看。您现在要做的就是将所有这些 Duration 属性绑定到您的视图模型中的一个值,该值是您根据该人的 BPM 设置的。

【讨论】:

以上是关于如何绘制模拟心电图?的主要内容,如果未能解决你的问题,请参考以下文章

Canvas绘制心电图并且添加部分功能

Android——ECG心电图的绘制实现

Canvas绘制心电图——网格开关

WPF 实现心电图曲线绘制

Android——ECG心电图的绘制实现

微信小程序中使用画布canvas实现动态心电图绘制