65.9K
CodeProject 正在变化。 阅读更多。
Home

ECG 注释 C++ 库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (32投票s)

2007 年 10 月 24 日

GPL3

5分钟阅读

viewsIcon

182278

downloadIcon

17038

本文介绍的 electrocardiogram (ECG) 标注 C++ 库基于小波分析和控制台应用程序,用于从 ECG 数据中提取重要的间隔和波形 (P, T, QRS, PQ, QT, RR, RRn),以及异位搏动和噪声检测。

Screenshot - ecg.png

引言

对于关心心脏健康的人们以及在该领域进行研究的学者们,我想介绍我在攻读博士学位期间花了数年时间开发的 ECG 标注库。去年,我在剑桥大学的博士后研究期间改进了其标注质量。事实证明,这项工作并非徒劳,我在 Physionet Computers In Cardiology 2006 QT Annotation challenge 中获得了第一名。您也可以在 physionet 源代码 中查看该库的早期版本。描述该算法的论文在线可查阅 Individually Adaptable Automatic QT Detector. Computers in Cardiology 2006. V. 33. PP. 337-341。您可以在本文顶部下载的会议海报(Power Point 2007)中查看。

我为该库提供了一个控制台应用程序,因此您可以使用您的 ECG 数据并对其进行标注。您的数据应为 Physionet ECG 格式,请参阅 网站 获取可将文本数据文件转换为此格式的工具。

背景

如果您想扩展或修改代码,或者使用我库中提供的小波变换类,您应该具备心脏病学基础知识,熟悉 physionet ECG 数据格式,并可选地熟悉 C++ 编程和小波分析。Robi Polikar 提供了一篇关于小波变换的精彩在线教程:Robi Polikar 的教程

Using the Code

要标注 ECG 文件,只需按此方式运行控制台应用程序(例如,我包含了来自 afpdb 数据库的 physionet 文件 *n26c.dat* 及其头文件 *n26c.hea*)。

>ecg.exe n26c.dat 1

这将标注记录的第一个导联。

>ecg.exe n26c.dat 2

要标注第二个导联,以此类推。

完成后,ECG 标注(包括 P、T、QRS 波、异位搏动和噪声)将打印到 stdout 并保存到 *n26c.atr* Physionet 兼容标注文件中。HRV 数据也将以文本格式保存到 *n26c.hrv* 文件中,平均心率将显示在 stdout 上。要查看带有标注的 ECG 记录,您可以浏览 physionet 工具。我将考虑发布一个简单的 GUI 来查看记录和标注。

由于记录可能持续时间很长,stdout 的输出速度会很慢,因此您可以将其重定向到文件以加快处理速度。

>ecg.exe n26c.dat 1 >output

用于指定最小、最大 ECG 波的间隔、持续时间等的 ECG 标注参数已设置为默认值。我可能会发布一个带有外部设置的修改版,供控制台使用,以适应特定的 ECG 形态,否则您可以自己修改,将其传递给 EcgAnnotation 构造函数。

库中包含的类有:

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

Signal 类提供以文本、physionet 和我自定义文件格式读取 ECG 数据、将其保存到磁盘、数据归一化例程、去噪操作和一些数学函数。请查看 *signal.h* 文件。

CWT 类提供连续小波变换。它配备了常见的母小波(Mehihan Hat、Morlet、高斯导数)。您可以这样使用它:

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 

FWT 类提供一维快速小波变换。您可以以与 CWT 类似的方式使用它。

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 旨在通过快速小波变换对 ECG 信号进行去噪。

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

初始化后,您可以根据需要调用去噪函数任意次数并按任意顺序调用。每次您的 Data 都将填充去噪后的信号版本。

EcgAnnotation 是用于完整 ECG 标注的类。以下是我的控制台应用程序中用于标注 ECG 数据并保存结果的代码:

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

        //annotation

        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... ");
        tic();
        //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");
                        toc();
                        wprintf(L"\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");
                        toc();
                        wprintf(L"\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]);
                        fclose(fp);

                        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.");
                exit(1);
        }

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

历史

更新 1.0 - 2007 年 10 月 28 日

在我的代码的用户研究人员要求我提供更精确的双相 T 波标注并通过外部文件读取标注参数后,我修改了控制台代码和 EcgAnnotation 类。现在您可以在参数文件 *biTwave* 中将最后一个设置从 0 更改为 1 以处理双相 T 波。更改涉及 EcgAnnotation::GetPTU() 函数,我在其中添加了高斯 CWT 滤波器用于标注双相 T 波。

Biphasic T waves

您可以运行控制台,并提供一个额外的可选第三个参数,即包含您特定标注设置的文件名。

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

更新 1.1 - 2007 年 10 月 29 日

此更新提供了从外部文件加载的附加参数。将 ampQRS 设置为 1,您可以在检测过程中预先放大 QRS 波群。qrsFreq 表示 CWT 变换的滤波频率,默认为 13Hz。上述更改允许处理异常 ECG,如下所示。如您所见,T 波是双相的,峰形与 QRS 非常相似。使用默认参数,您无法过滤掉这种 T 波,它会产生虚假的噪声或其他搏动的检测。实际心率是 160 bpm,相当高。要处理此类异常,您需要将 qrsFreq 滤波频率提高到大约 25Hz,并预先放大 QRS 波群 ampQRS = 1。通过这些设置,此类 T 波将被成功过滤掉,QRS 检测阶段将完美无缺。

Biphasic peaked T waves

现在配置文件包含以下字段:

  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
© . All rights reserved.