/* file: ecgeval.c G. Moody 22 March 1992 Last revised: 11 July 1995 Generate and run a script of commands to compare sets of annotation files Copyright (C) Massachusetts Institute of Technology 1995. All rights reserved. */ #include #ifndef __STDC__ extern void exit(); # ifndef NOMALLOC_H # include # else extern char *malloc(); # endif #endif #ifndef BSD #include #else #include #define strchr index #endif #include #include #define NDBMAX 50 /* maximum number of databases in `dblist' */ #ifdef MSDOS #define RNLMAX 8 /* maximum length of a record name */ #define ECHONOTHING "echo . >>%s\n" /* closest simple approximation */ #else #define RNLMAX 20 #define ECHONOTHING "echo >>%s\n" #endif char buf[256]; void getans(p, n) char *p; int n; { int l; if (n > 256) n = 256; (void)fgets(buf, n, stdin); l = strlen(buf); if (buf[l-1] == '\n') buf[--l] = '\0'; if (buf[0] && p) (void)strcpy(p, buf); } char *dbname[NDBMAX], *dbfname[NDBMAX], *dbdesc[NDBMAX], *dblname; int ndb; int getdblists() { char *p, *sep = "\t\n\r", *getenv(); FILE *dblfile; if ((dblname = getenv("DBLIST")) == NULL) dblname = "dblist"; if ((p = dbfile(dblname, (char *)NULL)) == NULL) { (void)fprintf(stderr, "can't find `%s' (list of databases available)\n", dblname); return (-1); } if ((dblfile = fopen(p, "r")) == NULL) { (void)fprintf(stderr, "can't read `%s' (list of databases available)\n", p); return (-1); } while (fgets(buf, 256, dblfile)) { /* read an entry */ for (p = buf; *p == ' ' || *p == '\t'; p++) ; /* skip any leading whitespace */ if (*p == '#' || (p = strtok(p, sep)) == NULL) continue; /* comment or empty line -- ignore */ if (ndb >= NDBMAX) { (void)fprintf(stderr, "(warning) list of databases is too long\n"); break; } if ((dbname[ndb] = (char *)malloc((unsigned)(strlen(p)+1))) == NULL) { (void)fprintf(stderr, "insufficient memory\n"); exit(2); } (void)strcpy(dbname[ndb], p); if ((p = strtok((char *)NULL, sep)) == NULL) continue; /* ignore lines with missing fields */ if ((dbfname[ndb] = (char *)malloc((unsigned)(strlen(p)+1))) == NULL) { (void)fprintf(stderr, "insufficient memory\n"); exit(2); } (void)strcpy(dbfname[ndb], p); if ((p = strtok((char *)NULL, sep)) == NULL) continue; if ((dbdesc[ndb] = (char *)malloc((unsigned)(strlen(p)+1))) == NULL) { (void)fprintf(stderr, "insufficient memory\n"); exit(2); } (void)strcpy(dbdesc[ndb++], p); } (void)fclose(dblfile); return (0); } char *month_name[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; main() { FILE *dbf, *rfile = NULL, *sfile = NULL; int dbi = -1, i, nhr = 1; int evalsv = 1, evalvf = 1, evalaf = 1, evalst = 1, evalst2 = 1; struct tm *now; static char rhrname[20], tname[20], tans[20], *dbfn, dbtn[5], reportname[30], scriptname[20], *epicSoption, evalcommand[30]; static char bxbcommand[256], rxrcommand[256], mxmcommand[256], epiccommand[256]; static char bxbfile1[40] = "bxb.out", bxbfile2[40] = "sd.out", rxrfile1[40] = "vruns.out", rxrfile2[40] = "sruns.out", mxmfile[40] = "hr%d.out", epicaffile[40] = "af.out", epicvffile[40] = "vf.out", epicstfile1[40] = "st.out", epicstfile2[40] = "stm.out", plotstmfile[40] = "stm.ps"; #ifdef MSDOS static char *rname = "atr"; #else static char *rname = "atruth"; #endif #ifdef __STDC__ time_t t, time(); t = time((time_t *)NULL); /* get current time from system clock */ #else long t, time(); t = time((long *)NULL); #endif now = localtime(&t); (void)fprintf(stderr, "\t\t_________________________________________\n"); (void)fprintf(stderr, "\t\tAutomated ECG Analyzer Evaluation Program\n"); (void)fprintf(stderr, "\t\t_________________________________________\n"); (void)fprintf(stderr, "Copyright (C) Massachusetts Institute of Technology 1992."); (void)fprintf(stderr, " All rights reserved.\n\n"); (void)fprintf(stderr, "If you have questions about this software, please contact the author:\n"); (void)fprintf(stderr, "\tGeorge B. Moody\n\tMIT Room 20A-113\n\tCambridge, MA 02139 USA\n"); (void)fprintf(stderr, "\te-mail: george@hstbme.mit.edu (Internet)\n"); (void)fprintf(stderr, "\tphone: +1 617 253-7424\t"); (void)fprintf(stderr, "\tfax: +1 617 253-2514\n\n"); if (getdblists() < 0) exit(1); (void)fprintf(stderr, "This program constructs a script (batch) file which evaluates a set of\n"); (void)fprintf(stderr, "test annotation files by comparing them with reference annotation files\n"); (void)fprintf(stderr, "in accordance with AAMI Recommended Practice ECAR-1987 and with ANSI/AAMI\n"); (void)fprintf(stderr, "Standard EC38-1994 for ambulatory ECGs. For some questions, a default\n"); (void)fprintf(stderr, "answer is provided in brackets; press RETURN (ENTER) to accept the\n"); (void)fprintf(stderr, "default, or type the desired answer followed by RETURN. After you have\n"); (void)fprintf(stderr, "answered all of the questions, you are given a chance to change any of\n"); (void)fprintf(stderr, "your answers before beginning the actual evaluation. At that time,\n"); (void)fprintf(stderr, "you may exit from this program and examine the evaluation script which\n"); (void)fprintf(stderr, "it has generated before running that script.\n\n"); (void)fprintf(stderr, "Press RETURN to begin: "); getans((char *)NULL, 5); (void)fprintf(stderr, "\n"); getanswers: do { (void)fprintf(stderr, "Which database do you wish to use? (press RETURN for list) "); if (dbi >= 0) (void)fprintf(stderr, "[%s]: ", dbname[dbi]); getans(tans, 20); if (tans[0] == '\0') { for (i = 0; i < ndb; i++) (void)fprintf(stderr, " %-10s %s\n", dbname[i], dbdesc[i]); (void)fprintf(stderr, "Enter a choice from the first column: "); getans(tans, 20); } for (i = 0; i < ndb; i++) if (strcmp(tans, dbname[i]) == 0) break; if (i < ndb) dbi = i; else tans[0] = '\0'; } while (dbi < 0); if ((dbfn = dbfile(dbfname[dbi], (char *)NULL)) == NULL || (dbf = fopen(dbfn, "r")) == NULL) { (void)fprintf(stderr, "Sorry, I can't find the list of records for the %s.\n", dbname[i]); (void)fprintf(stderr, "Check the file `%s' for errors.\n", dblname); exit(3); } (void)fprintf(stderr, "\n"); (void)fprintf(stderr, "You must have a set of annotation files generated by the device under\n"); (void)fprintf(stderr, "test from its analysis of the signal files of the %s. These files\n", dbname[dbi]); (void)fprintf(stderr, "are identified by the test annotator name (the part of the file name\n"); #ifdef MSDOS (void)fprintf(stderr, "that follows the record name and `.'; three characters or less).\n"); #else (void)fprintf(stderr, "that precedes the `.' and the record name).\n"); #endif (void)fprintf(stderr, "If you don't yet have a set of test annotation files, you can add the\n"); (void)fprintf(stderr, "commands needed to create them to the evaluation script that will be\n"); (void)fprintf(stderr, "generated by this program.\n"); do { (void)fprintf(stderr, "What is the test annotator name? "); if (tname[0]) (void)fprintf(stderr, "[%s]: ", tname); getans(tname, 20); #ifdef MSDOS if ((int)strlen(tname) > 3) tname[0] = '\0'; #endif } while (tname[0] == '\0'); (void)fprintf(stderr, "\n"); (void)fprintf(stderr, "To evaluate heart rate or HRV measurement error, you must have a set of\n"); (void)fprintf(stderr, "reference heart rate annotation files. These must be generated from the\n"); (void)fprintf(stderr, "reference (`atr') annotation files supplied with the %s.\n", dbname[dbi]); (void)fprintf(stderr, "If you don't yet have a set of reference heart rate annotation files,\n"); (void)fprintf(stderr, "add the commands needed to create them to the evaluation script that\n"); (void)fprintf(stderr, "will be generated by this program.\n"); do { (void)fprintf(stderr, "What is the reference heart rate annotator name? "); if (rhrname[0]) (void)fprintf(stderr, "[%s]: ", rhrname); getans(rhrname, 20); #ifdef MSDOS if ((int)strlen(rhrname) > 3) rhrname[0] = '\0'; #endif } while (rhrname[0] == '\0'); (void)fprintf(stderr, "\n"); (void)fprintf(stderr, "The next several questions refer to evaluation of optional ECG analyzer\n"); (void)fprintf(stderr, "outputs.\n\n"); (void)fprintf(stderr, "Test and reference heart rate annotation files may contain more than one\n"); (void)fprintf(stderr, "type of heart rate, HRV, or RRV measurement.\n"); do { (void)fprintf(stderr, "How many types are there? [%d]: ", nhr); tans[0] = '\0'; getans(tans, 20); if (tans[0] == '\0') i = 0; else { i = -1; if (sscanf(tans, "%d", &i) == 1 && 0 <= i && i <= 127) nhr = i; } } while (i < 0); tans[0] = evalsv ? 'y' : 'n'; (void)fprintf(stderr, "Do you wish to evaluate SVEB detection? [%c]: ", tans[0]); getans(tans, 20); evalsv = (tans[0] == 'y' || tans[0] == 'Y'); tans[0] = evalvf ? 'y' : 'n'; (void)fprintf(stderr, "Do you wish to evaluate VF detection? [%c]: ", tans[0]); getans(tans, 20); evalvf = (tans[0] == 'y' || tans[0] == 'Y'); tans[0] = evalaf ? 'y' : 'n'; (void)fprintf(stderr, "Do you wish to evaluate AF detection? [%c]: ", tans[0]); getans(tans, 20); evalaf = (tans[0] == 'y' || tans[0] == 'Y'); tans[0] = evalst ? 'y' : 'n'; (void)fprintf(stderr, "Do you wish to evaluate ST analysis? [%c]: ", tans[0]); getans(tans, 20); evalst = (tans[0] == 'y' || tans[0] == 'Y'); if (evalst) { tans[0] = evalst2 ? 'y' : 'n'; (void)fprintf(stderr, "Use both signals to define ST episodes? [%c]: ", tans[0]); getans(tans, 20); evalst2 = (tans[0] == 'y' || tans[0] == 'Y'); if (!evalst2) { (void)fprintf(stderr, "ST episodes will be defined for signal 0 only.\n"); epicSoption = " -S0 "; } else epicSoption = " -S "; } (void)fprintf(stderr, "\nDo you wish to change any of your answers? [y]: "); tans[0] = 'y'; getans(tans, 20); if (tans[0] == 'y' || tans[0] == 'Y') goto getanswers; for (i = 0; i < 4; i++) { if (dbname[dbi][i] == ' ') { dbtn[i] = '\0'; break; } if ('A' <= dbname[dbi][i] && dbname[dbi][i] <= 'Z') dbtn[i] = dbname[dbi][i] - 'A' + 'a'; else dbtn[i] = dbname[dbi][i]; } #ifdef MSDOS (void)sprintf(scriptname, "%s-%s.bat", tname, dbtn); (void)sprintf(reportname, "%s-%s.out", tname, dbtn); #else (void)sprintf(scriptname, "eval-%s-%s", tname, dbtn); (void)sprintf(reportname, "%s-%s-evaluation", tname, dbtn); #endif while (sfile == NULL) { do { (void)fprintf(stderr, "\nChoose a name for the evaluation script"); if (scriptname[0]) (void)fprintf(stderr, " [%s]", scriptname); (void)fprintf(stderr, ": "); #ifdef MSDOS getans(scriptname, 9); #else getans(scriptname, 20); #endif } while (scriptname[0] == '\0'); #ifdef MSDOS i = strlen(scriptname) - 4; if (i <= 0 || strcmp(scriptname+i, ".bat")) (void)strcat(scriptname, ".bat"); #endif if (sfile = fopen(scriptname, "r")) { (void)fclose(sfile); (void)fprintf(stderr, "There is already a file named `%s' in the current directory;\n", scriptname); (void)fprintf(stderr, "type `a' to append the evaluation script to the existing file, or\n"); (void)fprintf(stderr, "type `r' to replace the existing file, or\n"); (void)fprintf(stderr, "press RETURN to choose another name for the script: "); tans[0] = '\0'; getans(tans, 20); if (tans[0] == 'a') { if ((sfile = fopen(scriptname, "a")) == NULL) (void)fprintf(stderr, "Sorry, I can't append to `%s'.\n"); } else if (tans[0] == 'r') { if ((sfile = fopen(scriptname, "w")) == NULL) (void)fprintf(stderr, "Sorry, I can't replace `%s'.\n"); } else sfile = NULL; } else if ((sfile = fopen(scriptname, "w")) == NULL) (void)fprintf(stderr, "Sorry, I can't create `%s'.\n"); if (sfile == NULL) scriptname[0] = '\0'; } while (rfile == NULL) { do { (void)fprintf(stderr, "\nChoose a name for the evaluation report"); if (reportname[0]) (void)fprintf(stderr, " [%s]", reportname); (void)fprintf(stderr, ": "); getans(reportname, 20); } while (reportname[0] == '\0'); if (rfile = fopen(reportname, "r")) { (void)fclose(rfile); (void)fprintf(stderr, "There is already a file named `%s' in the current directory;\n", reportname); (void)fprintf(stderr, "type `a' to append the evaluation report to the existing file, or\n"); (void)fprintf(stderr, "type `r' to replace the existing file, or\n"); (void)fprintf(stderr, "press RETURN to choose another name for the report: "); tans[0] = '\0'; getans(tans, 20); if (tans[0] == 'a') { if ((rfile = fopen(reportname, "a")) == NULL) (void)fprintf(stderr, "Sorry, I can't append to `%s'.\n"); } else if (tans[0] == 'r') { if ((rfile = fopen(reportname, "w")) == NULL) (void)fprintf(stderr, "Sorry, I can't replace `%s'.\n"); } else rfile = NULL; } else if ((rfile = fopen(reportname, "w")) == NULL) (void)fprintf(stderr, "Sorry, I can't create `%s'.\n"); if (rfile == NULL) reportname[0] = '\0'; } (void)fprintf(stderr, "The next group of questions refers to the names of files in which\n"); (void)fprintf(stderr, "intermediate summary statistics are to be written. If any of these exist\n"); (void)fprintf(stderr, "already, new statistics will be appended, and the aggregate statistics\n"); (void)fprintf(stderr, "in `%s' will be based on the entire contents of these files.\n", reportname); getfilenames: (void)fprintf(stderr, "Beat detection and classification file [%s]: ", bxbfile1); getans(bxbfile1, 40); (void)fprintf(stderr, "Analysis shutdown file [%s]: ", bxbfile2); getans(bxbfile2, 40); (void)fprintf(stderr, "Ventricular ectopic run file [%s]: ", rxrfile1); getans(rxrfile1, 40); if (evalsv) { (void)fprintf(stderr, "Supraventricular ectopic run file [%s]: ", rxrfile2); getans(rxrfile2, 40); } if (evalvf) { (void)fprintf(stderr, "Ventricular fibrillation file [%s]: ", epicvffile); getans(epicvffile, 40); } if (evalaf) { (void)fprintf(stderr, "Atrial fibrillation file [%s]: ", epicaffile); getans(epicaffile, 40); } if (evalst) { (void)fprintf(stderr, "ST analysis file [%s]: ", epicstfile1); getans(epicstfile1, 40); (void)fprintf(stderr, "ST measurement file [%s]: ", epicstfile2); getans(epicstfile2, 40); (void)fprintf(stderr, "PostScript scatter plot of ST measurements [%s]: ", plotstmfile); getans(plotstmfile, 40); } (void)fprintf(stderr, "The name given for the heart rate measurement file must contain `%%d',\n"); (void)fprintf(stderr, "which is replaced by the measurement number.\n"); (void)fprintf(stderr, "Heart rate measurement file [%s]: ", mxmfile); getans(mxmfile, 40); (void)fprintf(stderr, "\nDo you wish to change any of these answers? [y]: "); tans[0] = 'y'; getans(tans, 20); if (tans[0] == 'y' || tans[0] == 'Y') goto getfilenames; /* Generate report file header. */ (void)fprintf(rfile, "file: %s\tecgeval\t\t%d %s %d\n", reportname, now->tm_mday, month_name[now->tm_mon], now->tm_year+1900); (void)fprintf(rfile, "Evaluation of `%s' on the %s\n\n", tname, dbdesc[dbi]); (void)fclose(rfile); (void)fprintf(stderr, "\nGenerating `%s' ...", scriptname); (void)sprintf(bxbcommand, "bxb -r %%s -a %s %s %s %s %s\n", rname, tname, evalsv ? "-L" : "-l", bxbfile1, bxbfile2); if (evalsv == 0) rxrfile2[0] = '\0'; (void)sprintf(rxrcommand, "rxr -r %%s -a %s %s %s %s %s\n", rname, tname, evalsv ? "-L" : "-l", rxrfile1, rxrfile2); (void)sprintf(mxmcommand, "mxm -r %%s -a %s %s -L %s -m %%d\n", rhrname, tname, mxmfile); if (evalaf || evalvf || evalst) { (void)sprintf(epiccommand, "epic -r %%s -a %s %s -L", rname, tname); if (evalaf) { (void)strcat(epiccommand, " -A "); (void)strcat(epiccommand, epicaffile); } if (evalvf) { (void)strcat(epiccommand, " -V "); (void)strcat(epiccommand, epicvffile); } if (evalst) { (void)strcat(epiccommand, epicSoption); (void)strcat(epiccommand, epicstfile1); (void)strcat(epiccommand, " "); (void)strcat(epiccommand, epicstfile2); } (void)strcat(epiccommand, "\n"); } #ifdef MSDOS (void)fprintf(sfile, "@echo off\n"); #endif (void)fprintf(sfile, ": file: %s\tecgeval\t\t%d %s %d\n", scriptname, now->tm_mday, month_name[now->tm_mon], now->tm_year+1900); (void)fprintf(sfile, ":\n: Evaluate test annotator %s on the %s\n", tname, dbname[dbi]); (void)fprintf(sfile, "\n: This file was automatically generated by ecgeval. Do not edit it\n"); (void)fprintf(sfile, ": unless you know what you are doing!\n"); while (fgets(buf, 256, dbf)) { char *record; record = strtok(buf, " \t\n\r"); if (*record == '#' || *record == '\0') continue; /* comment or empty line -- ignore */ if ((int)strlen(record) > RNLMAX) { (void)fprintf(stderr, "Illegal record name, `%s', found in `%s' (ignored).\n", record, dbfn); continue; } (void)fprintf(sfile, "\n: Record %s\n", record); (void)fprintf(sfile, bxbcommand, record); (void)fprintf(sfile, rxrcommand, record); for (i = 0; i < nhr; i++) (void)fprintf(sfile, mxmcommand, record, i, i); if (evalaf || evalvf || evalst) (void)fprintf(sfile, epiccommand, record); } (void)fclose(dbf); (void)fprintf(sfile, "\n: Generate summary report\n"); (void)fprintf(sfile, "echo Beat detection and classification performance >>%s\n", reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "sumstats %s >>%s\n", bxbfile1, reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "echo Analysis shutdowns >>%s\n", reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "sumstats %s >>%s\n", bxbfile2, reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "echo Ventricular ectopic run detection performance >>%s\n", reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "sumstats %s >>%s\n", rxrfile1, reportname); (void)fprintf(sfile, ECHONOTHING, reportname); if (evalsv) { (void)fprintf(sfile, "echo Supraventricular ectopic run detection performance >>%s\n", reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "sumstats %s >>%s\n", rxrfile2, reportname); (void)fprintf(sfile, ECHONOTHING, reportname); } for (i = 0; i < nhr; i++) { (void)fprintf(sfile, "echo Heart rate measurement number %d performance >>%s\n", i, reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "sumstats "); (void)fprintf(sfile, mxmfile, i); (void)fprintf(sfile, " >>%s\n", reportname); (void)fprintf(sfile, ECHONOTHING, reportname); } if (evalvf) { (void)fprintf(sfile, "echo Ventricular fibrillation detection performance >>%s\n", reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "sumstats %s >>%s\n", epicvffile, reportname); (void)fprintf(sfile, ECHONOTHING, reportname); } if (evalaf) { (void)fprintf(sfile, "echo Atrial fibrillation detection performance >>%s\n", reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "sumstats %s >>%s\n", epicaffile, reportname); (void)fprintf(sfile, ECHONOTHING, reportname); } if (evalst) { (void)fprintf(sfile, "echo Ischemic ST detection performance >>%s\n", reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, "sumstats %s >>%s\n", epicstfile1, reportname); (void)fprintf(sfile, ECHONOTHING, reportname); (void)fprintf(sfile, ": Generate PostScript scatter plot of ST measurements\n"); (void)fprintf(sfile, "plotstm %s >%s\n", epicstfile2, plotstmfile); } (void)fprintf(sfile, "echo The evaluation is complete. Print text file %s\n", reportname); if (evalst) (void)fprintf(sfile, "echo and PostScript file %s to get the results.\n", plotstmfile); else (void)fprintf(sfile, "echo to get the results.\n"); #ifdef MSDOS (void)fprintf(sfile, "@echo on\n"); #endif (void)fclose(sfile); (void)fprintf(stderr, " done\n\n"); #ifdef MSDOS strncpy(evalcommand, scriptname, strlen(scriptname) - 4); /* The command need not include the final `.bat'. */ #else (void)sprintf(evalcommand, "sh ./%s", scriptname); #endif (void)fprintf(stderr, "Do you wish to run the evaluation script now? [y]: "); tans[0] = 'y'; getans(tans, 20); if (tans[0] == 'y') (void)system(evalcommand); else { (void)fprintf(stderr, "Inspect and edit `%s' as necessary, then type\n", scriptname); (void)fprintf(stderr, " %s\nto run the evaluation.\n", evalcommand); } exit(0); /*NOTREACHED*/ }