Click here to Skip to main content
15,896,496 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 176K   16.8K   59  
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.

#include "stdafx.h"
#include "signal.h"


Signal::Signal(): pData(0), fp(0), fpmap(0), lpMap(0),
                  SR(0.0), Bits(0), UmV(0), Lead(0), Length(0),
                  hh(0), mm(0), ss(0)

{
        wcscpy(EcgFileName, L"");
}
Signal::~Signal()
{
        if (EcgSignals.size()) {
                for (int i = 0; i < (int)EcgSignals.size(); i++) {
                        pData = EcgSignals[i];
                        delete[] pData;
                }
        }
}

double* Signal::ReadFile(const char* name)
{
        wchar_t ustr[_MAX_PATH] = L"";
        for (int i = 0; i < (int)strlen(name); i++)
                mbtowc(ustr + i, name + i, MB_CUR_MAX);
        return ReadFile(ustr);
}

double* Signal::ReadFile(const wchar_t* name)
{
        wcscpy(EcgFileName, name);

        if (!IsFileValid(name)) 
                return 0;

        if (IsBinFile) {
                if (!ReadDatFile()) 
                        return 0;
        } else {
                if (!ReadTxtFile()) { //read text file
                        if (!ReadMitbihFile()) //read mit-bih file
                                return 0;
                }
        }

        return GetData();
}

bool Signal::IsFileValid(const wchar_t* name)
{
        fp = CreateFileW(name, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
        if (fp == INVALID_HANDLE_VALUE)
                return false;
        fpmap = CreateFileMapping(fp, 0, PAGE_READONLY, 0, sizeof(DATAHDR), 0);
        lpMap = MapViewOfFile(fpmap, FILE_MAP_READ, 0, 0, sizeof(DATAHDR));

        pEcgHeader = (PDATAHDR)lpMap;

        if (lpMap != 0 && !memcmp(pEcgHeader->hdr, "DATA", 4)) {
                Length = pEcgHeader->size;
                SR = pEcgHeader->sr;
                Bits = pEcgHeader->bits;
                Lead = pEcgHeader->lead;
                UmV = pEcgHeader->umv;
                hh = pEcgHeader->hh;
                mm = pEcgHeader->mm;
                ss = pEcgHeader->ss;
                IsBinFile = true;
        } else
                IsBinFile = false;

        CloseFile();
        return true;
}

bool Signal::ReadDatFile()
{
        short tmp;

        fp = CreateFileW(EcgFileName, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
        if (fp == INVALID_HANDLE_VALUE)
                return false;
        fpmap = CreateFileMapping(fp, 0, PAGE_READONLY, 0, 0, 0);
        lpMap = MapViewOfFile(fpmap, FILE_MAP_READ, 0, 0, 0);
        if (lpMap == 0) {
                CloseFile();
                return false;
        }

        pEcgHeader = (PDATAHDR)lpMap;
        EcgHeaders.push_back(*pEcgHeader);

        lpf = (float *)lpMap;
        lps = (short *)lpMap;
        lpu = (unsigned short *)lpMap;
        lpc = (char *)lpMap;

        Length = pEcgHeader->size;
        SR = pEcgHeader->sr;
        Bits = pEcgHeader->bits;
        Lead = pEcgHeader->lead;
        UmV = pEcgHeader->umv;

        lpf += sizeof(DATAHDR) / sizeof(float);
        lps += sizeof(DATAHDR) / sizeof(short);
        lpc += sizeof(DATAHDR);

        pData = new double[Length];
        EcgSignals.push_back(pData);

        for (int i = 0; i < Length; i++)
                switch (Bits) {
                case 12:                                             //212 format   12bit
                        if (i % 2 == 0) {
                                tmp = MAKEWORD(lpc[0], lpc[1] & 0x0f);
                                if (tmp > 0x7ff)
                                        tmp |= 0xf000;
                                pData[i] = (double)tmp / (double)UmV;
                        } else {
                                tmp = MAKEWORD(lpc[2], (lpc[1] & 0xf0) >> 4);
                                if (tmp > 0x7ff)
                                        tmp |= 0xf000;
                                pData[i] = (double)tmp / (double)UmV;
                                lpc += 3;
                        }
                        break;

                case 16:                                             //16format
                        pData[i] = (double)lps[i] / (double)UmV;
                        break;

                case 0:
                case 32:                                             //32bit float
                        pData[i] = (double)lpf[i];
                        break;
                }


        CloseFile();
        return true;
}

bool Signal::ReadTxtFile()
{
        vector<double> Buffer;
        double tmp;
        int res;

        if ((in = _wfopen(EcgFileName, L"rt")) == 0)
                return false;

        for (;;) {
                res = fscanf(in, "%lf", &tmp);
                if (res == EOF || res == 0)
                        break;
                else
                        Buffer.push_back(tmp);
        }

        fclose(in);
        Length = (int)Buffer.size();
        if (Length < 2)
                return false;

        pData = new double[Length];
        for (int i = 0; i < Length; i++)
                pData[i] = Buffer[i];
        EcgSignals.push_back(pData);

        DATAHDR hdr;
        memset(&hdr, 0, sizeof(DATAHDR));
        hdr.size = Length;
        hdr.umv = 1;
        hdr.bits = 32;
        EcgHeaders.push_back(hdr);

        return true;
}

bool Signal::ReadMitbihFile()
{
        wchar_t HeaFile[_MAX_PATH];
        wcscpy(HeaFile, EcgFileName);

        ChangeExtension(HeaFile, L".hea");
        FILE* fh = _wfopen(HeaFile, L"rt");
        if (!fh) 
                return false;

        if (ParseMitbihHeader(fh)) {
                fclose(fh);

                pEcgHeader = &EcgHeaders[0];
                int lNum = (int)EcgHeaders.size();
                int size = pEcgHeader->size;

                fp = CreateFileW(EcgFileName, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
                if (fp == INVALID_HANDLE_VALUE || (GetFileSize(fp, 0) != (lNum * pEcgHeader->size * pEcgHeader->bits) / 8))
                        return false;
                fpmap = CreateFileMapping(fp, 0, PAGE_READONLY, 0, 0, 0);
                lpMap = MapViewOfFile(fpmap, FILE_MAP_READ, 0, 0, 0);
                if (lpMap == 0) {
                        CloseFile();
                        return false;
                }

                lps = (short *)lpMap;
                lpc = (char *)lpMap;

                for (int i = 0; i < lNum; i++) {
                        pData = new double[pEcgHeader->size];
                        EcgSignals.push_back(pData);
                }

                short tmp;
                for (int s = 0; s < size; s++) {
                        for (int n = 0; n < lNum; n++) {
                                pData = EcgSignals[n];
                                pEcgHeader = &EcgHeaders[n];

                                switch (pEcgHeader->bits) {
                                case 12:                                             //212 format   12bit
                                        if ((s*lNum + n) % 2 == 0) {
                                                tmp = MAKEWORD(lpc[0], lpc[1] & 0x0f);
                                                if (tmp > 0x7ff)
                                                        tmp |= 0xf000;
                                                tmp -= pEcgHeader->bline;
                                                pData[s] = (double)tmp / (double)pEcgHeader->umv;
                                        } else {
                                                tmp = MAKEWORD(lpc[2], (lpc[1] & 0xf0) >> 4);
                                                if (tmp > 0x7ff)
                                                        tmp |= 0xf000;
                                                tmp -= pEcgHeader->bline;
                                                pData[s] = (double)tmp / (double)pEcgHeader->umv;
                                                lpc += 3;
                                        }
                                        break;

                                case 16:                                      //16format
                                        pData[s] = (double)(*lps++ - pEcgHeader->bline) / (double)pEcgHeader->umv;
                                        break;
                                }
                        }
                }
                
                CloseFile();
                return true;
        } else {
                fclose(fh);
                return false;
        }
}

double* Signal::GetData(int index)
{
        if (!EcgSignals.size()) 
                return 0;
        if (index > (int)EcgSignals.size() - 1) 
                index = (int)EcgSignals.size() - 1;
        else if (index < 0) 
                index = 0;

        pEcgHeader = &EcgHeaders[index];
        pData = EcgSignals[index];
        SR = pEcgHeader->sr;
        Lead = pEcgHeader->lead;
        UmV = pEcgHeader->umv;
        Bits = pEcgHeader->bits;
        Length = pEcgHeader->size;
        hh = pEcgHeader->hh;
        mm = pEcgHeader->mm;
        ss = pEcgHeader->ss;

        return pData;
}

int Signal::ParseMitbihHeader(FILE* fh)
{
        char leads[18][6] =  {"I", "II", "III", "aVR", "aVL", "aVF", "v1", "v2",
                              "v3", "v4", "v5", "v6", "MLI", "MLII", "MLIII", "vX", "vY", "vZ"
                             };
        char str[10][256];

        if (ReadLine(fh, str[0]) <= 0)
                return false;

        int sNum, size;
        float sr;
        int res = sscanf(str[0], "%s %s %s %s %s", str[1], str[2], str[3], str[4], str[5]);
        if (res < 4)
                return 0;
        if (res == 5 && strlen(str[5]) == 8) {
                char* ptime = str[5];
                hh = atoi(ptime);
                mm = atoi(ptime + 3);
                ss = atoi(ptime + 6);
        }

        sNum = atoi(str[2]);
        sr = (float)atof(str[3]);
        size = atoi(str[4]);

        int eNum = 0;
        for (int i = 0; i < sNum; i++) {
                if (ReadLine(fh, str[0]) <= 0)
                        return 0;

                int umv, bits, bline;
                memset(str[9], 0, 256);

                res = sscanf(str[0], "%s %s %s %s %s %s %s %s %s", str[1], str[2], str[3], str[4], str[5], str[6], str[7], str[8], str[9]);
                if (res < 5) return 0;

                bits = atoi(str[2]);
                umv = atoi(str[3]);
                bline = atoi(str[5]);

                int offs = (int)strlen(str[1]), j = 0;
                for (j = 0; j < offs; j++) {
                        if (EcgFileName[(wcslen(EcgFileName)-offs)+j] != str[1][j])  //wctomb
                                break;
                }
                if (j == offs) {
                        eNum++;
                        DATAHDR hdr;
                        memset(&hdr, 0, sizeof(DATAHDR));
                        hdr.sr = sr;
                        hdr.bits = (bits == 212) ? 12 : bits;
                        hdr.umv = (umv == 0) ? 200 : umv;
                        hdr.bline = bline;
                        hdr.size = size;
                        hdr.hh = hh;
                        hdr.mm = mm;
                        hdr.ss = ss;
                        for (int l = 0; l < 18; l++) {
                                if (!stricmp(leads[l], str[9])) {
                                        hdr.lead = l + 1;
                                        break;
                                }
                        }
                        EcgHeaders.push_back(hdr);
                }
        }
        return eNum;
}

bool Signal::SaveFile(const char* name, const double* buffer, PDATAHDR hdr)
{
        wchar_t ustr[_MAX_PATH] = L"";
        for (int i = 0; i < (int)strlen(name); i++)
                mbtowc(ustr + i, name + i, MB_CUR_MAX);
        return SaveFile(ustr, buffer, hdr);
}

bool Signal::SaveFile(const wchar_t* name, const double* buffer, PDATAHDR hdr)
{
        int filesize;
        int tmp;

        switch (hdr->bits) {
        case 12:
                if (hdr->size % 2 != 0)
                        filesize = int((double)(hdr->size + 1) * 1.5);
                else
                        filesize = int((double)(hdr->size) * 1.5);
                break;
        case 16:
                filesize = hdr->size * 2;
                break;
        case 0:
        case 32:
                filesize = hdr->size * 4;
                break;
        }


        fp = CreateFileW(name, GENERIC_WRITE | GENERIC_READ, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        if (fp == INVALID_HANDLE_VALUE)
                return false;
        fpmap = CreateFileMapping(fp, 0, PAGE_READWRITE, 0, filesize + sizeof(DATAHDR), 0);
        lpMap = MapViewOfFile(fpmap, FILE_MAP_WRITE, 0, 0, filesize + sizeof(DATAHDR));
        if (lpMap == 0) {
                CloseFile();
                return false;
        }

        lpf = (float *)lpMap;
        lps = (short *)lpMap;
        lpu = (unsigned short *)lpMap;
        lpc = (char *)lpMap;

        memset(lpMap, 0, filesize + sizeof(DATAHDR));
        memcpy(lpMap, hdr, sizeof(DATAHDR));

        lpf += sizeof(DATAHDR) / sizeof(float);
        lps += sizeof(DATAHDR) / sizeof(short);
        lpc += sizeof(DATAHDR);


        for (unsigned int i = 0; i < hdr->size; i++) {
                switch (hdr->bits) {
                case 12:                                               //212 format   12bit
                        tmp = int(buffer[i] * (double)hdr->umv);
                        if (tmp > 2047) tmp = 2047;
                        if (tmp < -2048) tmp = -2048;

                        if (i % 2 == 0) {
                                lpc[0] = LOBYTE((short)tmp);
                                lpc[1] = 0;
                                lpc[1] = HIBYTE((short)tmp) & 0x0f;
                        } else {
                                lpc[2] = LOBYTE((short)tmp);
                                lpc[1] |= HIBYTE((short)tmp) << 4;
                                lpc += 3;
                        }
                        break;

                case 16:                                               //16format
                        tmp = int(buffer[i] * (double)hdr->umv);
                        if (tmp > 32767) tmp = 32767;
                        if (tmp < -32768) tmp = -32768;
                        *lps++ = (short)tmp;
                        break;

                case 0:
                case 32:                                               //32bit float
                        *lpf++ = (float)buffer[i];
                        break;
                }
        }

        CloseFile();
        return true;
}

bool Signal::ToTxt(const wchar_t* name, const double* buffer, int size)
{
        in = _wfopen(name, L"wt");

        if (in) {
                for (int i = 0; i < size; i++)
                        fwprintf(in, L"%lf\n", buffer[i]);

                fclose(in);
                return true;
        } else
                return false;
}

void Signal::ChangeExtension(wchar_t* path, const wchar_t* ext) const
{
        for (int i = (int)wcslen(path) - 1; i > 0; i--) {
                if (path[i] == '.') {
                        path[i] = 0;
                        wcscat(path, ext);
                        return;
                }
        }

        wcscat(path, ext);
}

int Signal::ReadLine(FILE* fh, char* buffer) const
{
        int res = 0;
        char* pbuffer = buffer;

        while ((short)res != EOF) {
                res = fgetc(fh);
                if (res == 0xD || res == 0xA) {
                        if (pbuffer == buffer) continue;

                        *pbuffer = 0;
                        return 1;
                }
                if ((short)res != EOF) {
                        *pbuffer++ = (char)res;
                }
        }

        return (short)res;
}

void Signal::mSecToTime(int msec, int& h, int& m, int& s, int& ms) const
{
        ms = msec % 1000;
        msec /= 1000;

        if (msec < 60) {
                h = 0;
                m = 0;
                s = msec;                 //sec to final
        } else {
                double tmp;
                tmp = (double)(msec % 60) / 60;
                tmp *= 60;
                s = int(tmp);
                msec /= 60;

                if (msec < 60) {
                        h = 0;
                        m = msec;
                } else {
                        h = msec / 60;
                        tmp = (double)(msec % 60) / 60;
                        tmp *= 60;
                        m = int(tmp);
                }
        }
}

void Signal::MinMax(const double* buffer, int size, double& min, double& max) const
{
        max = buffer[0];
        min = buffer[0];
        for (int i = 1; i < size; i++) {
                if (buffer[i] > max)max = buffer[i];
                if (buffer[i] < min)min = buffer[i];
        }
}

void Signal::CloseFile()
{
        if (lpMap != 0)
                UnmapViewOfFile(lpMap);
        if (fpmap != 0)
                CloseHandle(fpmap);
        if (fp != 0 && fp != INVALID_HANDLE_VALUE)
                CloseHandle(fp);
}

double Signal::Mean(const double* buffer, int size) const
{
        double mean = 0;

        for (int i = 0; i < size; i++)
                mean += buffer[i];

        return mean / (double)size;
}

double Signal::Std(const double* buffer, int size) const
{
        double mean, disp = 0;

        mean = Mean(buffer, size);

        for (int i = 0; i < size; i++)
                disp += (buffer[i] - mean) * (buffer[i] - mean);

        return (sqrt(disp / (double)(size - 1)));
}

void  Signal::nMinMax(double* buffer, int size, double a, double b) const
{
        double min, max;
        MinMax(buffer, size, min, max);

        for (int i = 0; i < size; i++) {
                if (max - min)
                        buffer[i] = (buffer[i] - min) * ((b - a) / (max - min)) + a;
                else
                        buffer[i] = a;
        }
}

void Signal::nMean(double* buffer, int size) const
{
        double mean = Mean(buffer, size);

        for (int i = 0; i < size; i++)
                buffer[i] = buffer[i] - mean;
}

void Signal::nZscore(double* buffer, int size) const
{
        double mean = Mean(buffer, size);
        double disp = Std(buffer, size);

        if (disp == 0.0) disp = 1.0;
        for (int i = 0; i < size; i++)
                buffer[i] = (buffer[i] - mean) / disp;
}

void Signal::nSoftmax(double* buffer, int size) const
{
        double mean = Mean(buffer, size);
        double disp = Std(buffer, size);

        if (disp == 0.0) disp = 1.0;
        for (int i = 0; i < size; i++)
                buffer[i] = 1.0 / (1 + exp(-((buffer[i] - mean) / disp)));
}

void Signal::nEnergy(double* buffer, int size, int L) const
{
        double enrg = 0.0;
        for (int i = 0; i < size; i++)
                enrg += pow(fabs(buffer[i]), (double)L);

        enrg = pow(enrg, 1.0 / (double)L);
        if (enrg == 0.0) enrg = 1.0;

        for (int i = 0; i < size; i++)
                buffer[i] /= enrg;
}

double Signal::MINIMAX(const double* buffer, int size) const
{
        return Std(buffer, size)*(0.3936 + 0.1829*log((double)size));
}

double Signal::FIXTHRES(const double* buffer, int size) const
{
        return Std(buffer, size)*sqrt(2.0*log((double)size));
}

double Signal::SURE(const double* buffer, int size) const
{
        return Std(buffer, size)*sqrt(2.0*log((double)size*log((double)size)));
}

void Signal::Denoise(double* buffer, int size, int window, int type, bool soft) const
{
        double TH;

        for (int i = 0; i < size / window; i++) {
                switch (type) {
                case 0:
                        TH = MINIMAX(buffer, window);
                        break;
                case 1:
                        TH = FIXTHRES(buffer, window);
                        break;
                case 2:
                        TH = SURE(buffer, window);
                        break;
                }
                if (soft)
                        SoftTH(buffer, window, TH);
                else
                        HardTH(buffer, window, TH);

                buffer += window;
        }

        if (size % window > 5) { //skip len=1
                switch (type) {
                case 0:
                        TH = MINIMAX(buffer, size % window);
                        break;
                case 1:
                        TH = FIXTHRES(buffer, size % window);
                        break;
                case 2:
                        TH = SURE(buffer, size % window);
                        break;
                }
                if (soft)
                        SoftTH(buffer, size % window, TH);
                else
                        HardTH(buffer, size % window, TH);
        }
}

void  Signal::HardTH(double* buffer, int size, double TH, double l) const
{
        for (int i = 0; i < size; i++)
                if (fabs(buffer[i]) <= TH)
                        buffer[i] *= l;
}

void  Signal::SoftTH(double* buffer, int size, double TH, double l) const
{
        for (int i = 0; i < size; i++) {
                if (fabs(buffer[i]) <= TH) {
                        buffer[i] *= l;
                } else {
                        if (buffer[i] > 0) {
                                buffer[i] -= TH * (1 - l);
                        } else {
                                buffer[i] += TH * (1 - l);
                        }
                }
        }
}


void Signal::AutoCov(double* buffer, int size) const
{
        double* rk, mu;
        int t;
        rk = new double[size];

        mu = Mean(buffer, size);

        for (int k = 0; k < size; k++) {
                rk[k] = 0;

                t = 0;
                while (t + k != size) {
                        rk[k] += (buffer[t] - mu) * (buffer[t+k] - mu);
                        t++;
                }

                rk[k] /= (double)t;                       // rk[k] /= t ?  autocovariance
        }

        for (int i = 0; i < size; i++)
                buffer[i] = rk[i];

        delete[] rk;        
}

void Signal::AutoCov1(double* buffer, int size) const
{
        double* rk, mu;        
        rk = new double[size];

        mu = Mean(buffer, size);

        for (int k = 0; k < size; k++) {
                rk[k] = 0;

                for (int t = 0; t < size; t++) {
                        if (t + k >= size)
                                rk[k] += (buffer[t] - mu) * (buffer[2*size-(t+k+2)] - mu);
                        else
                                rk[k] += (buffer[t] - mu) * (buffer[t+k] - mu);
                }

                rk[k] /= (double)size;
        }

        for (int i = 0; i < size; i++)
                buffer[i] = rk[i];

        delete[] rk;        
}

void Signal::AutoCor(double* buffer, int size) const
{
        double* rk, mu, std;
        int t;
        rk = new double[size];

        mu = Mean(buffer, size);
        std = Std(buffer, size);

        for (int k = 0; k < size; k++) {
                rk[k] = 0;

                t = 0;
                while (t + k != size) {
                        rk[k] += (buffer[t] - mu) * (buffer[t+k] - mu);
                        t++;
                }

                rk[k] /= (double)t * std * std;
        }

        for (int i = 0; i < size; i++)
                buffer[i] = rk[i];

        delete[] rk;        
}

void Signal::AutoCor1(double* buffer, int size) const
{
        double* rk, mu, std;
        rk = new double[size];

        mu = Mean(buffer, size);
        std = Std(buffer, size);

        for (int k = 0; k < size; k++) {
                rk[k] = 0;

                for (int t = 0; t < size; t++) {
                        if (t + k >= size)
                                rk[k] += (buffer[t] - mu) * (buffer[2*size-(t+k+2)] - mu);
                        else
                                rk[k] += (buffer[t] - mu) * (buffer[t+k] - mu);
                }

                rk[k] /= (double)size * std * std;
        }

        for (int i = 0; i < size; i++)
                buffer[i] = rk[i];

        delete[] rk;        
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Engineer
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