How to draw a simulated ECG?

524 Views Asked by At

What I would like to create (it doesn't have to be very precise, it's just for entertainment purposes):

ECG DEMO

enter image description here

ECG Picture

ECG Picture

What I have:

I have a REST API to a service that let's me query the current BPM of the person wearing the pulse measuring device. So I don't have an Event that indicates when a hearth beat is happening. I would have to calculate this with the given BPM. But that's not the issue.

The Question:

How would you draw the lines and make them disappear behind and from left to right with a reset when it reaches the right corner. I have some experience in C# WPF, that's why I created the REST Querying stuff there. Are there some Libraries for the drawing part? Is there an easy way to do this by hand?

I'm really greatful for any advice, since I can't find that much on a rather specific issue like this on the internet. So thank you!

UPDATE: I got something working using WritableBitmap, but it looks pretty bad. Any idea on how to get a better resolution? I already increased the resolution of the bitmap, but it still looks quite horrible. Is there some sort of Anti-aliasing on this?

DEMO

2

There are 2 best solutions below

3
Eric Cartman On

This will get you started:

enter image description here

Install WriteableBitmapEx package:

Install-Package WriteableBitmapEx

Code:

<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>

Code:

#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;
        }
    }
}

Here I simply I erase a 1 pixel wide rectangle ahead the value I'm drawing, in your case, erase a larger rectangle to get the effect you want.

See that you'll need to keep history of previous value and draw a line from it instead to have a solid drawing.

Also, you may want to look at these libraries as well:

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

https://github.com/rdtek/ECGViewer

https://github.com/Refactoring/ECGToolkit

0
Mark Feldman On

You can use a LinearGradientBrush as an OpacityMask to black out parts of the image and then animate the offsets horizontally:

<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>

enter image description here

It's not 100% perfect, because your ECG image has blank space to the left and right of it, but if you clip it it'll look better. All you have to do now is bind all those Duration properties to a value in your view model that you set based on the person's BPM.