/***************************************************************************** FILE: easytest.cpp AUTHOR: Patrick S. Hamilton REVISED: 5/13/2002 (PSH); 4/10/2003 (GBM) ___________________________________________________________________________ easytest.cpp: Use bdac to generate an annotation file. Copyright (C) 2001 Patrick S. Hamilton Copyright (C) 1999 George B. Moody This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. You may contact the author by e-mail (pat@eplimited.edu) or postal mail (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, MA 02143 USA). For updates to this software, please visit our website (http://www.eplimited.com). __________________________________________________________________________ Easytest.exe is a simple program to help test the performance of our beat detection and classification software. Data is read from the indicated ECG file, the channel 1 signal is fed to bdac.c, and the resulting detections are saved in the annotation file .ate. .ate may then be compared to .atr to using bxb to analyze the performance of the the beat detector and classifier detector. Note that data in the MIT/BIH Arrythmia database file has been sampled at 360 samples-per-second, but the beat detection and classification software has been written for data sampled at 200 samples-per-second. Date is converterted from 360 sps to 200 sps with the function NextSample. Code for resampling was copied from George Moody's xform utility. The beat locations are then adjusted back to coincide with the original sample rate of 360 samples/second so that the annotation files generated by easytest can be compared to the "atruth" annotation files. This file must be linked with object files produced from: wfdb software library (source available at www.physionet.org) analbeat.cpp match.cpp rythmchk.cpp classify.cpp bdac.cpp qrsfilt.cpp qrsdet.cpp postclass.cpp noisechk.cpp __________________________________________________________________________ Revisions 4/13/02: Added conditional define statements that allow MIT/BIH or AHA records to be processed. Normalize input to 5 mV/LSB (200 A-to-D units/mV). 4/10/03: Moved definitions of Record[] array, ECG_DB_PATH, and REC_COUNT into "input.h" *******************************************************************************/ /* * For unknown reasons * * */ #include #include #include #include "stdio.h" #include "qrsdet.h" // For sample rate. #include "inputs.h" /* list of records to analyze and definitions of ECG_DB_PATH and REC_COUNT */ // External function prototypes. void ResetBDAC(void) ; int BeatDetectAndClassify(int ecgSample, int *beatType, int *beatMatch) ; // Local Prototypes. void help(void); char *prog_name(char *p); int NextSample(int *vout,int nosig,int ifreq, int ofreq,int init) ; int gcd(int x, int y); // Global variables. char *pname; /* name of this program, used in messages */ double user_thresh = 1.0; /* normalized detection threshold */ int main(int argc , char **argv) { int leads=0; char *record, *output, *name; int i=0, delay, recNum=0, beats=0; int ecg[WFDB_MAXSIG]; int ADCZero; int InputFileSampleFrequency ; double gain; WFDB_Siginfo sig_info[WFDB_MAXSIG] ; WFDB_Anninfo ann_info[WFDB_MAXSIG]; WFDB_Annotation annot[WFDB_MAXSIG]; unsigned char byte; long long SampleCount = 0, lTemp, DetectionTime ; int beatType, beatMatch ; int lead; //FILE *log= fopen ("Log.txt", "w"); pname = prog_name(argv[0]); // arg parsing from gqrs.c for (i = 1; i < argc; i++) { if (*argv[i] == '-') { switch (*(argv[i]+1)) { case 'h': /* help requested */ help(); return 0; case 'm': /* threshold */ if (++i >= argc) { (void)fprintf(stderr, "%s: threshold must follow -m\n", pname); return 1; } user_thresh = atof(argv[i]); break; case 'r': /* record name */ if (++i >= argc) { (void)fprintf(stderr, "%s: input record name must follow -r\n", pname); return 1; } record = argv[i]; break; case 's': /* signal name or number follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: signal name or # must follow -s\n", pname); return 1; } lead = i; break; default: (void)fprintf(stderr, "%s: unrecognized option %s\n", pname, argv[i]); return 1; } } else { (void)fprintf(stderr, "%s: unrecognized argument %s\n", pname, argv[i]); return 1; } } output = "./"; printf("Record %s\n",record) ; //setwfdb(".") ; leads=isigopen(record,sig_info,WFDB_MAXSIG); if( leads < 1) { printf("Couldn't open %s\n",record) ; return -1; } InputFileSampleFrequency = sampfreq(record) ; name = (char*) malloc (sizeof(char)*(strlen(output)+strlen(record)+2)); sprintf(name,"%s%s",output,record); ann_info[0].name = "epl"; ann_info[0].stat = WFDB_WRITE ; if(annopen(name, ann_info, 1) < 0) return -1; free(name); if (lead > 0) { i = findsig(argv[lead]); if (i < 0) { (void)fprintf(stderr, "%s: (warning) no signal %s in record %s\n", pname, argv[lead], record); return 1; } printf("%s: Processing %s.\n", pname, argv[lead]); lead = i; } ADCZero = sig_info[lead].adczero ; gain = ( (double)200.0) / ( (double)sig_info[lead].gain) ; NextSample(ecg,leads,InputFileSampleFrequency,SAMPLE_RATE,1) ; ResetBDAC() ; SampleCount = 0 ; beats = 0; while(NextSample(ecg,leads,InputFileSampleFrequency,SAMPLE_RATE,0) >= 0) { ++SampleCount ; lTemp = (int)( ((double)(ecg[lead]-ADCZero)) * gain) ; ecg[i] = lTemp ; delay = BeatDetectAndClassify(ecg[lead], &beatType, &beatMatch) ; if(delay != 0) { DetectionTime = SampleCount - delay ; DetectionTime *= InputFileSampleFrequency ; DetectionTime /= SAMPLE_RATE ; //annot.time = 1 ; //annot.anntyp = 2 ; annot[0].time = DetectionTime ; annot[0].anntyp = beatType ; beats++; //fprintf(log,"%d %d\n",annot.time,annot.anntyp) ; //printf("%d %d\n",annot.time,annot.anntyp) ; putann(0,annot) ; } } //fclose(log); wfdbquit() ; printf("%s: Detected %d heartbeats.\n", pname, beats); return 0; } /* prog_name() extracts this program's name from argv[0], for use in error and warning messages. */ char *prog_name(char *s) { char *p = s + strlen(s); #ifdef MSDOS while (p >= s && *p != '\\' && *p != ':') { if (*p == '.') *p = '\0'; /* strip off extension */ if ('A' <= *p && *p <= 'Z') *p += 'a' - 'A'; /* convert to lower case */ p--; } #else while (p >= s && *p != '/') p--; #endif return (p+1); } /* help() prints a (very) concise summary of how to use this program. A more detailed summary is in the man page (gqrs.1). */ static char *help_strings[] = { "usage: %s -r RECORD [OPTIONS ...]\n", "where RECORD is the name of the record to be analyzed, and OPTIONS may", "include any of:", " -h print this usage summary", " -m THRESH set detector threshold to THRESH (default: 1.00)", " -s SIGNAL analyze specified SIGNAL (default: 0)", " (Note: SIGNAL may be specified by number or name.)", "If too many beats are missed, decrease THRESH; if there are too many extra", "detections, increase THRESH.", NULL }; void help() { int i; (void)fprintf(stderr, help_strings[0], pname); for (i = 1; help_strings[i] != NULL; i++) (void)fprintf(stderr, "%s\n", help_strings[i]); } /********************************************************************** NextSample reads MIT/BIH Arrhythmia data from a file of data sampled at ifreq and returns data sampled at ofreq. Data is returned in vout via *vout. NextSample must be initialized by passing in a nonzero value in init. NextSample returns -1 when there is no more data left. ***********************************************************************/ int NextSample(int *vout,int nosig,int ifreq,int ofreq,int init) { int i , rval; static int m, n, mn, ot, it, vv[WFDB_MAXSIG], v[WFDB_MAXSIG] ; if(init) { i = gcd(ifreq, ofreq); m = ifreq/i; n = ofreq/i; mn = m*n; ot = it = 0 ; getvec(vv) ; rval = getvec(v) ; } else { while(ot > it) { for(i = 0; i < nosig; ++i) vv[i] = v[i] ; rval = getvec(v) ; if (it > mn) { it -= mn; ot -= mn; } it += n; } for(i = 0; i < nosig; ++i) vout[i] = vv[i] + (ot%n)*(v[i]-vv[i])/n; ot += m; } return(rval) ; } // Greatest common divisor of x and y (Euclid's algorithm) int gcd(int x, int y) { while (x != y) { if (x > y) x-=y; else y -= x; } return (x); }