You are on page 1of 8

Machine Learning for

medicine: QRS detection in a


single channel ECG signal
(Part 1: data-set creation)
(Code and data included)

Introduction
As machine learning tools become increasingly easy to use,
the crucial challenge for data science researchers is the
process of data manipulation and creation of properly
designed data-sets that can be used to test ideas and validate
architectures.

In this post we would like to go through such a process using


Python (2.7). The problem we will be working on has already
been solved many years ago with classical signal processing
methods with an algorithm developed by Pan and Tompkins.

Although there still is plenty of room for improvement


regarding ECG signal analysis or QRS classification, we will
try only to recreate the Pan and Tompkins results with a
Tensorflow implementation of a neural network. The full
repository used in this tutorial is available at Github.

Only non trivial dependency we will be using is


the wfdbpackage used for reading data stored in the
physionet format. You can install the most recent version
straight from github:

git clone https://github.com/MIT-LCP/wfdb-python.git


cd wfdb-python
pip install . -U
If you got that, clone the main repository for this tutorial:

git clone https://github.com/Nospoko/qrs-


tutorial.git
cd qrs-tutorial

To begin with any kind of research we need to be able to


access the data. The MIT Arrhythmia database contains 48
recordings of 2 channel ECG signals, each stored withing 3
files containing different types of information and identified
by the file name without the extension. You can find more
information on the official Physionet about page.

To load one of these files and perform basic inspection:

The get_records() function will download all of the required


data files for you. It can be easily adjusted to download other
of the physionet databases as well.

Actual numerical values of the ECG signals are stored in the


attribute p_signals array, we can plot a short fragment from
one of the channels.

Detection of the QRS complex may seem trivial for that


signal, as the R-waves form a set of highly spiked peaks, but
often the signal is much less clear and populated with noisy
artifacts that can easily trick any simple detection algorithm.

Noisy fragment from MLII signal from file 117.


Most of the ECG signals available in the Physionet database
has been annotated properly and information about the
position of each QRS complex can be extracted:

Signal and annotations for the first 4 seconds of 112 file.

Take notice that there are annotations regarding information


other then the QRS position, e.g. the + sign seen at the
beginning of the signal above. There is a page explaining all
annotation types you can encounter working with signals
from the Physionet database.

Defining the objective


We are facing a problem of event detection in a high
frequency (more than 1Hz) signal. To keep things simple we
will start to build a solution based on a moving window
mechanism. What it means is that we would like to construct
a machine learning pipeline which takes constant-width ECG
fragments as an input and returns information about
presence of any QRS events. So the transformation we want
might look like this:
Possible signal transformations.

Here the window width was chosen to be 2.5 seconds, which


is equal to 900 sample points. This value will be one of the
model defining parameters and we will try to prepare for
testing different values.

Another important factor we would like to control is the


nature of output signal generated by our model. At the end
we need to get precise sample positions of the located QRS
complex (second plot), but due to the lack of uniformity in
Physionet annotations (identical beats might be marked at
different points by different annotators) it may be a good
idea to predict a bell curve shape (third plot) interpreted as
probability density and thresholded manually at later stage
of the signal processing.

Data-set generation
The MIT arrhythmia database contains 48 records, each with
2 signals of 650000 samples. That totals to over 60 million
samples or 120 thousand fragments with 500 samples width.
This number can be easily increased by signal augmentation,
generation of artificial signals, inclusion of other databases,
reducing the fragmenting window’s width and stride, etc.

First function we will need to generate our data set should


convert a raw signal and it’s annotations into the desired
output signals.
From 48 available data files we will use 30 for training, 9 for
validation and 9 for final testing. Each of those will be
generated in the form of a Numpy array and later accessed to
pull data batches out. Unless amount of data you will be
operating on does not exceed your machine’s memory
capabilities, an abstract function creating and saving a
proper set of arrays should be fairly simple.
All of the data pre-processing, manipulations and chopping
must be handled inside the convert_data() function,
specifically the window width must be controlled there.

Splitting the data boils down to choosing the ECG records for
each of the data-set.

Data-set access
To train any neural network using this data, we need to
provide a mechanism for extracting randomized batches of
any given shape. You might have come across a Tensorflow
implementation for the MNIST data set. We are going to use
similar approach, focusing on the next_batch() function.
Stay tuned for part 2.

You might also like