Click here to Skip to main content
15,901,373 members
Articles / Programming Languages / C++

ECG Annotation C++ Library

Rate me:
Please Sign up or sign in to vote.
4.91/5 (35 votes)
23 Oct 2007GPL35 min read 176.3K   16.8K   59   49
The article demonstrating electrocardiogram (ECG) annotation C++ library is based on wavelet-analysis and console application for extraction of vital intervals and waves from ECG data (P, T, QRS, PQ, QT, RR, RRn), ectopic beats and noise detection.
Screenshot - ecg.png


For those concerned about their cardiac health and researchers investigating in that field I'd like to present the ECG annotation library I developed during several years since my PhD studies. The last year I spent improving its quality of annotation during my post-doc research in Cambridge. It turned out that the work was not futile as I took the first place in Physionet Computers In Cardiology 2006 QT Annotation challenge. You may also have a look at the previous versions of the library at the physionet sources. The paper describing the algorithm available online Individually Adaptable Automatic QT Detector. Computers in Cardiology 2006. V. 33. PP. 337-341. You may have a look at the conference poster (Power Point 2007) I provided as a download at the top of this article.

I provided a console application to the library, so you can start with your ECG data and annotate it. You should have your data in Physionet ECG format, have a look at the site for a tool that convert text data files to it.


You should have basics in cardiology, be familiar with physionet ECG data format and optionally with C++ programming and wavelet-analysis if you want to extend or modify the code or use wavelet-transforms classes I provided with my library. There is a nice tutorial by Robi Polikar available online on wavelet-transforms.

Using the Code

To annotate the ECG file, just run console application this way (I enclosed the physionet file n26c.dat with its header file n26c.hea from afpdb database for example):

>ecg.exe n26c.dat 1

This will annotate the first lead from the record.

>ecg.exe n26c.dat 2

To annotate the second lead, and so on.

Upon completion, the ECG annotation including P, T, QRS waves, ectopic beats and noise will be printed to stdout and saved to n26c.atr physionet compatible annotation file. Also HRV data will be saved in text format to n26c.hrv file and mean heart rate will be displayed at the stdout. To view the ECG record with the annotation you may browse physionet tools. I'll think of posting a simple GUI to view the record and the annotation.

As the records could be quite long in duration the output to stdout is quite slow so you may redirect it to the file for faster processing:

>ecg.exe n26c.dat 1 >output

The ECG annotation parameters used for specifying minimum, maximum ECG wave's intervals, durations, etc... are set to default values. I may post the modification with external set up for the console to fit them for particular ECG morphology, otherwise you may modify it yourself passing them to EcgAnnotation constructor.

The classes present in the library are:

  • Signal
  • CWT : public Signal
  • FWT : public Signal
  • EcgDenoise : public FWT
  • EcgAnnotation : public Signal

The Signal class provides reading ECG data in text, physionet and my custom file formats, saving it to disk, data normalization routines, denoising operations and some math functions. Have a look at the signal.h file.

The CWT class provides continuous wavelet transform. It is equipped with common wavelets (Mehihan Hat, Morlet, Gaussian derivatives). You can use it this way:

double SR;      //signal sampling rate

double w0;      //parameter for full Morlet wavelet

double* Data;   //your data signal

int size;       //size of your signal for analysis

double* pSpec;  //pointer to the CWT spectrum

CWT cwt;
cwt.InitCWT(size, CWT::MHAT, w0, SR);   //init it with Mexihan Hat wavelet

pSpec = cwt->CwtTrans(Data, 30.0);      //to transform Data on 30.0Hz

pSpec = cwt->CwtTrans(Data, 45.56);     //to transform Data on 45.56Hz

// and so on ... 

// size of the spectrum pSpec equals to signal size

cwt.CloseCWT();                         //close it 

The FWT class provides 1D fast wavelet transform. You can use it in a similar way as CWT one.

wchar_t filter[] = L"path\\filters\\daub2.flt";  //full path to the filter

FWT fwt;
fwt.InitFWT(filter, Data, size);                 //init it with Daub2 wavelet

fwt.FwtTrans(3);                                 //3 level FWT transform

//meddle with FWT spectrum

pSpec = GetFwtSpectrum();

fwt.FwtSynth(3);                                 //3 level FWT reconstruction

fwt.CloseFWT();                                  //close it

EcgDenoise is designed to denoise ECG signal with fast wavelet transform.

wchar_t path[] = L"fullpath\\filters";      //full path to the filters dir

EcgDenoise enoise;
enoise.InitDenoise(path, Data, size, SR);

enoise.LFDenoise();             //baseline wander removal

enoise.HFDenoise();             //high frequency noise removal

enoise.LFHFDenoise();           //both noises removal

enoise.CloseDenoise();          //close it

You may call denoising functions any number of times and in any order after initialization. Each time your Data will be filled with a denoised version of the signal.

EcgAnnotation is the class for complete ECG annotation. Here is the code from my console application on how to annotate the ECG data and save the results:

class Signal signal;
if (signal.ReadFile(argv[1])) {

        int size = signal.GetLength();
        double sr = signal.GetSR();
        int h, m, s, ms;
        int msec = int(((double)size / sr) * 1000.0);
        signal.mSecToTime(msec, h, m, s, ms);

        wprintf(L"  leads: %d\n", signal.GetLeadsNum());
        wprintf(L"     sr: %.2lf Hz\n", sr);
        wprintf(L"   bits: %d\n", signal.GetBits());
        wprintf(L"    UmV: %d\n", signal.GetUmV());
        wprintf(L" length: %02d:%02d:%02d.%03d\n\n", h, m, s, ms);
        double* data = signal.GetData(leadNumber);


        class EcgAnnotation ann;  //default annotation params

        //or add your custom ECG params to annotation class from lib.h

        // ANNHDR hdr;

        //  hdr.minbpm = 30;

        //  etc...

        // class EcgAnnotation ann( &hdr );

        wprintf(L" getting QRS complexes... ");
        //get QRS complexes

        int** qrsAnn = ann.GetQRS(data, size, sr, L"filters");         
        //qrsAnn = ann->GetQRS(psig, size, SR, L"filters", qNOISE);    

        //get QRS complexes if signal is quite noisy

        if (qrsAnn) {
                wprintf(L" %d beats.\n", ann.GetQrsNumber());
                //label Ectopic beats

                ann.GetEctopics(qrsAnn, ann.GetQrsNumber(), sr);        

                wprintf(L" getting P, T waves... ");
                int annNum = 0;
                int** ANN = ann.GetPTU(data, size, sr, L"filters", 
                    qrsAnn, ann.GetQrsNumber()); //find P,T waves

                if (ANN) {
                        annNum = ann.GetEcgAnnotationSize();
                        wprintf(L" done.\n");
                        //save ECG annotation

                        wcscpy(annName, argv[1]);
                        change_extension(annName, L".atr");
                        ann.SaveAnnotation(annName, ANN, annNum);
                } else {
                        ANN = qrsAnn;
                        annNum = 2 * ann.GetQrsNumber();
                        wprintf(L" failed.\n");
                //printing out annotation

                for (int i = 0; i < annNum; i++) {
                        int smpl = ANN[i][0];
                        int type = ANN[i][1];

                        msec = int(((double)smpl / sr) * 1000.0);
                        signal.mSecToTime(msec, h, m, s, ms);

                        wprintf(L"%10d %02d:%02d:%02d.%03d   %s\n", 
                            smpl, h, m, s, ms, anncodes[type]);

                //saving RR seq

                vector<double /> rrs;
                vector<int> rrsPos;

                wcscpy(hrvName, argv[1]);
                change_extension(hrvName, L".hrv");
                if (ann.GetRRseq(ANN, annNum, sr, &rrs, &rrsPos)) {
                        FILE *fp = _wfopen(hrvName, L"wt");
                        for (int i = 0; i < (int)rrs.size(); i++)
                                fwprintf(fp, L"%lf\n", rrs[i]);

                        wprintf(L"\n mean heart rate: %.2lf", 
                    signal.Mean(&rrs[0], (int)rrs.size()));

        } else {
                wprintf(L"could not get QRS complexes. 
                make sure you have got \"filters\" 
                directory in the ecg application dir.");

} else {
        wprintf(L"failed to read %s file", argv[1]);


Update 1.0 - 28 Oct 2007

I modified console code and EcgAnnotation class after being asked by researchers using my code to provide more precise annotation for biphasic T waves and read annotation parameters from an external file. Now you may change the last setting in your parameters file biTwave from 0 to 1 to handle biphasic T waves. Changes concern EcgAnnotation::GetPTU() function there I added gaussian CWT filter for annotation of biphasic T waves.

Biphasic T waves

You may run the console providing an additional optional third parameter as a file name with your particular annotation settings:

>ecg.exe patient1.dat 1 patient1params
>ecg.exe patient2.dat 1 patient2biTwave
>ecg.exe patient3.dat 2 patient3largeTwaves

Update 1.1 - 29 Oct 2007

This one provides additional parameters loaded from an external file. With ampQRS set to 1 you can pre amplify QRS complex during the detection process. The qrsFreq denotes filtering frequency of CWT transform, default is 13Hz. The above mentioned changes allow to handle such abnormal ECGs as the one depicted below. As you can see the T wave is biphasic and peak shaped quite similar to QRS. With default params you cannot filter out such T wave and it spawns spurious detections of noise or another beat. The actual heart rate is 160 bpm, quite high. To deal with such abnormalities you have to increase qrsFreq filtering frequency to about 25Hz also with pre amplification of QRS complex ampQRS = 1. With those settings such T waves will be successfully filtered out and the QRS detection stage will be perfect.

Biphasic peaked T waves

Now the configuration file contains those fields:

 minbpm  40
 maxbpm  180
 minQRS  0.04
 maxQRS  0.2
qrsFreq  13
 ampQRS  0
 minUmV  0.2
  minPQ  0.07
  maxPQ  0.20
  minQT  0.22
  maxQT  0.45
  pFreq  9.0
  tFreq  3.0
biTwave  0


This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

Written By
Russian Federation Russian Federation
Highly skilled Engineer with 14 years of experience in academia, R&D and commercial product development supporting full software life-cycle from idea to implementation and further support. During my academic career I was able to succeed in MIT Computers in Cardiology 2006 international challenge, as a R&D and SW engineer gain CodeProject MVP, find algorithmic solutions to quickly resolve tough customer problems to pass product requirements in tight deadlines. My key areas of expertise involve Object-Oriented
Analysis and Design OOAD, OOP, machine learning, natural language processing, face recognition, computer vision and image processing, wavelet analysis, digital signal processing in cardiology.

Comments and Discussions

Questionplot ECG Pin
Member 1415290616-Apr-19 20:49
Member 1415290616-Apr-19 20:49 
NewsISHNE and Linux support Pin
atpage14-Jan-16 8:07
atpage14-Jan-16 8:07 
GeneralRe: ISHNE and Linux support Pin
Member 1400851310-Oct-18 20:26
Member 1400851310-Oct-18 20:26 
Questionwhat anncodes array mean? Pin
maildongxin23-Sep-13 17:12
maildongxin23-Sep-13 17:12 
QuestionThank you Pin
Elessar812-Feb-13 20:44
Elessar812-Feb-13 20:44 
Questioninter1.flt How to get this wavelet filters Pin
Member 78679847-Jan-13 20:18
Member 78679847-Jan-13 20:18 
AnswerRe: inter1.flt How to get this wavelet filters Pin
Chesnokov Yuriy22-Jan-13 0:26
professionalChesnokov Yuriy22-Jan-13 0:26 
Questionaudio ecg signal Pin
nidhalhameed28-May-12 12:11
nidhalhameed28-May-12 12:11 
Questionneed a help Pin
nidhalhameed28-May-12 12:07
nidhalhameed28-May-12 12:07 
QuestionMIT-BIH Arrhythmia Database Pin
Soheera22-Mar-12 1:09
Soheera22-Mar-12 1:09 
QuestionRe: MIT-BIH Arrhythmia Database Pin
Chesnokov Yuriy22-Mar-12 3:40
professionalChesnokov Yuriy22-Mar-12 3:40 
GeneralRe: MIT-BIH Arrhythmia Database Pin
Soheera22-Mar-12 4:40
Soheera22-Mar-12 4:40 
GeneralRe: MIT-BIH Arrhythmia Database Pin
Ye Niu4-Nov-14 14:19
Ye Niu4-Nov-14 14:19 
Questiongood job,thanks, Pin
larry_evants29-Feb-12 21:37
larry_evants29-Feb-12 21:37 
GeneralMy vote of 5 Pin
woodballhead11-Dec-11 15:06
woodballhead11-Dec-11 15:06 
GeneralSignal sample rate Pin
Jmyalex28-May-11 1:12
Jmyalex28-May-11 1:12 
AnswerRe: Signal sample rate Pin
Chesnokov Yuriy28-May-11 6:40
professionalChesnokov Yuriy28-May-11 6:40 
Generalcollaboration Pin
Andraž25-May-11 9:29
Andraž25-May-11 9:29 
GeneralRe: collaboration Pin
Chesnokov Yuriy25-May-11 18:41
professionalChesnokov Yuriy25-May-11 18:41 
Generalurgent help , please ! Pin
heba saleh15-Feb-11 3:06
heba saleh15-Feb-11 3:06 
AnswerRe: urgent help , please ! Pin
Chesnokov Yuriy15-Feb-11 7:10
professionalChesnokov Yuriy15-Feb-11 7:10 
GeneralRe: urgent help , please ! Pin
heba saleh19-Feb-11 9:48
heba saleh19-Feb-11 9:48 
AnswerRe: urgent help , please ! Pin
Chesnokov Yuriy19-Feb-11 21:09
professionalChesnokov Yuriy19-Feb-11 21:09 
Generalurgent help , please ! Pin
heba saleh15-Feb-11 3:06
heba saleh15-Feb-11 3:06 
GeneralAnnotation Viewer Pin
mail4vinoth@yahoo.com11-Oct-10 22:45
mail4vinoth@yahoo.com11-Oct-10 22:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.