/* file: dbplot.c G. Moody 23 June 1983 Last revised: 1 July 1991 Display ECG data with annotations Copyright (C) Massachusetts Institute of Technology 1991. All rights reserved. */ #include #include /* database utilities */ #include /* annotation codes */ #define MAXLINE 80 /* maximum # of characters per command line */ static FILE *comfile = stdin; /* command input file */ static int refresh; /* 0: do not redraw display, 1: redraw */ main(argc, argv) int argc; char *argv[]; { char com[MAXLINE]; /* command buffer */ int i; if (argc < 2 || *(argv[1]) != '-' || *(argv[1]+1) != 't') { fprintf(stderr, "usage: %s -trecord [ options ]\n", argv[0]); exit(1); } for (i = 1; i < argc; i++) /* process argument list */ command(argv[i]); /* execute each argument */ display(); /* draw the first frame */ while (fgets(com,MAXLINE,comfile)!=NULL) { /* read a command */ parse(com); /* execute the command line */ if (refresh) display(); /* draw another frame */ else reset(); /* restore cursor */ } exit(0); } parse(cline) /* divide a null-terminated command line into words */ char *cline; { char *com, *pt; int inword = 0, inquote = 0; refresh = 0; for (pt = com = cline; *pt != '\0'; pt++) { if (*pt == '\n') { /* found end of command line */ *pt = '\0'; break; } else if (*pt != ' ' && !inword) {/* found first character of new word */ inword = 1; com = pt; if (*pt == '\'' || *pt == '"') { inquote = *pt; /* found beginning of quoted string */ com++; } } else if (inquote) { /* handle quoted string */ if (*pt == inquote) { /* found end of quoted string */ inword = inquote = 0; *pt = '\0'; command(com); } } else if (*pt == ' ' && inword) { /* found first space after word */ inword = 0; *pt = '\0'; command(com); /* execute command */ } } if (inword || pt == cline) command(com); /* execute last command */ } static int achange = 0, tchange = 0; command(cst) /* select appropriate command processor */ char *cst; { switch (*cst) { case '-': /* option or relative search */ if ((*(cst+1) >= '0' && *(cst+1) <= '9') || *(cst+1) == 's') search(cst); else option(cst+1); break; case '\033': /* zoom command (VT-52/VT-100 arrow key) */ zoom(cst+1); break; default: /* search command */ search(cst); break; } if (achange || tchange) init(); /* (re-)open database files as needed */ } static int nsamp = 0; /* input samples per frame */ static long start = 0L; /* sample number of first sample in next frame */ zoom(cst) /* execute a zoom command (VT-52/VT-100 arrow key) */ char *cst; { switch (*cst) { case 'A': /* zoom in centered (up arrow) */ nsamp >>= 1; start += nsamp >> 1; break; case 'B': /* zoom out centered (down arrow) */ if (nsamp < 0x3fff) { start -= nsamp >> 1; nsamp <<= 1; } break; case 'C': /* zoom in right (right arrow) */ nsamp >>= 1; start += nsamp; break; case 'D': /* zoom in left (left arrow) */ nsamp >>= 1; /* (start is unchanged) */ break; default: /* ignore unrecognized escape sequences */ break; } refresh = 1; } static char auxmatch[MAXLINE]; /* latest searched-for annotation aux field */ static int lines = 1; /* number of lines of output per frame */ static long tref = 0L; /* reference time (time of sample 0) */ static struct ann annot; /* latest annotation */ search(cst) /* execute a search command (move display window) */ char *cst; { static int asrch = NOTQRS; /* last searched-for annotation anntyp */ long atol(); if ((*cst == '\0' && asrch != NOTQRS) || /* go to next matching annot if last search command was an annotation mnemonic and current command is null */ ((asrch = strann(cst)) != NOTQRS)) { /* find a matching annotation if command is an annotation mnemonic */ while ((annot.anntyp != asrch) || /* loop until anntyp matches */ (*auxmatch && strcmp(annot.aux+1,auxmatch))) /* & aux matches */ if (getann(0, &annot) < 0) exit(0); /* quit if end of annot file */ start = annot.time - nsamp/2; /* center new frame on annot */ achange = 1; /* force re-open of annotation file */ } else { asrch = NOTQRS; if (*cst == '\0') /* go to next frame */ start += lines * (long)nsamp; else if (*cst == '\b') /* go to previous frame */ start -= lines * (long)nsamp; else { if (*cst == '+') start += strtim(cst+1); /* go ahead specified time */ else if (*cst == '-') start -= strtim(cst+1); /* go back specified time */ else start = strtim(cst); /* go to specified time */ } } refresh = 1; } static char annotator[MAXLINE]; /* annotator name (if null, no annotator) */ static char record[MAXLINE]; /* record name */ static char tstring[MAXLINE]; /* alternate title string */ static int comp = 0; /* compression (input samples per displayed sample) */ static int pause = 0; /* length of pause (seconds) between displays */ static int res = DEFRES; /* resolution (significant bits per input sample) */ static double sps = DEFFREQ; /* sampling frequency (Hz) */ static int tres = DEFRES; /* ADC resolution (bits) for current record */ static double tsps = DEFFREQ; /* sampling frequency (Hz) for current record */ static int aflag = 0; /* display annot aux field if <> 0 */ static int cflag = 0; /* display annot chan field if <> 0 */ static int dflag = 0; /* alignment marks dotted if <> 0, solid if = 0 */ static int nflag = 0; /* display annot num field if <> 0 */ static int sflag = 0; /* display annot subtyp field if <> 0 */ static int tflag = 0; /* use tstring for title if <> 0 */ static double speed = 1.; /* chart speed (relative to standard speed) */ option(cst) /* execute an option command */ char *cst; { FILE *temp; double atof(); switch (*cst) { case 'a': /* select annotator */ strcpy(annotator, cst+1); achange = 1; break; case 'A': /* toggle annotation aux data display */ aflag = 1 - aflag; refresh = 1; break; case 'c': /* reset compression ratio */ if (*(++cst) == 'h' && atof(cst+1) > 0.) comp = (int)(sps/atof(cst+1) + 0.5); else comp = (int)(atof(cst) + 0.5); break; case 'C': /* toggle annotation chan display */ cflag = 1 - cflag; refresh = 1; break; case 'd': /* toggle annotation alignment mark style */ if (++dflag > 2) dflag = 0; refresh = 1; break; case 'f': /* redirect command input */ temp = *(cst+1) ? fopen(cst+1, "r") : stdin; if (temp == NULL) fprintf(stderr, "can't open %s\n", cst+1); else { if (comfile != stdin) fclose(comfile); comfile = temp; } break; case 'F': /* redefine sampling frequency */ sps = (*(cst+1)) ? atof(cst+1) : tsps; setsampfreq(sps); refresh = 1; break; case 'l': /* set number of output lines */ lines = atoi(cst+1); refresh = 1; break; case 'm': /* specify a matching string for 'aux' search */ strcpy(auxmatch, cst+1); break; case 'N': /* toggle annotation num display */ nflag = 1 - nflag; refresh = 1; break; case 'p': /* set pause interval */ pause = (*(cst+1)) ? atoi(cst+1) : 0; break; case 'r': /* set resolution (gain) */ res = (*(cst+1)) ? atoi(cst+1) : tres; refresh = 1; break; case 'R': /* set reference time */ if ((tref = strtim(cst+1)) < 0L) tref = 0L; setbasetime(cst+1); break; case 'S': /* toggle annotation subtype display */ sflag = 1 - sflag; refresh = 1; break; case 't': /* select record */ strcpy(record, cst+1); tchange = 1; break; case 'T': /* (re)set title string */ if (*(cst+1)) { strcpy(tstring, cst+1); tflag = 1; } else { tflag = 1 - tflag; *tstring = '\0'; } refresh = 1; break; case 'w': /* set window width */ if ((nsamp = (int)strtim(cst+1)) < 1) nsamp = (int)(5.*sps); refresh = 1; break; case 'x': /* set chart speed */ if ((speed = atof(cst+1)) <= 0.) speed = 1.; break; default: /* ignore unrecognized options */ break; } } static int nchan = 0; /* number of signals to be displayed */ static long t = 0L; /* sample number of next sample to be read */ init() /* open a new record or annotation file */ { struct siginfo dfarray[MAXSIG]; struct anninfo afarray[1]; if (tchange) { /* open a new record */ dbquit(); /* close any active files */ if ((nchan = isigopen(record, dfarray, MAXSIG)) <= 0) exit(2); sps = tsps = sampfreq(record); res = tres = dfarray[0].adcres; if (nsamp < 1) nsamp = (int)(5.*sps); t = 0L; tchange = 0; } afarray[0].name = annotator; afarray[0].stat = READ; if (*annotator == '\0') annopen(record, afarray, 0); /* close active annot file */ else if (annopen(record, afarray, 1) < 0) /* open new annotation file */ *annotator = '\0'; achange = 0; annot.time = -1L; annot.anntyp = NOTQRS; refresh = 1; } display() { register int i, j, k; char title[MAXLINE]; int can, crit, is, l, off, stat, x[MAXSIG], xa, xold[MAXSIG], x0m[MAXSIG]; if (start < t) /* move backwards - reopen dataset */ init(); if (sps <= 0.) sps = DEFFREQ; /* replace illegal values with defaults */ if (comp < 1) comp = (sps >= 60.) ? (int)(sps/60. + 0.5) : 1; if (lines < 1) lines = 1; if (nsamp < 1) nsamp = (int)(5.*sps); if (res < 1) res = DEFRES; if (start < 0L) start = 0L; if (start != t && isigsettime(start) < 0) exit(4); /* can't skip to requested sample */ t = start; if (*annotator) { iannsettime(t); stat = getann(0, &annot); sprintf(title, (tflag ? tstring : "Record: %s Annotator: %s %s (%ld)"), record, annotator, timstr(-t), t); } else sprintf(title, (tflag ? tstring : "Record: %s %s (%ld)"), record, timstr(-t), t); openpl(); erase(); space(0,0,3000,3200); /* initialize plot */ if (pause < 0) sleep(-pause); move(0, 3100); label(title); /* print the title string */ for (l = 1; l <= lines; l++) { /* plot a line (a set of traces) */ space(0, (l-lines)*(nchan<= 0 && annot.time <= t) { /* print annotations */ i = (int)(annot.time - t + nsamp); if (isqrs(annot.anntyp)) xa = off; else if (annot.anntyp == RHYTHM) xa = off + 60; else xa = off - 60; switch (dflag) { case 0: /* solid annotation alignment marks */ line(i, 40, i, xa - 20); line(i, xa + 40, i, 980); break; case 1: /* dotted annotation alignment marks */ for (j = 40; j <= xa - 20; j += 10) point(i,j); for (j = xa + 40; j <= 980; j += 10) point(i,j); break; default: /* no annotation alignment marks */ break; } move(i, xa); if (annot.aux != NULL && !aflag && (annot.anntyp == NOTE || annot.anntyp == RHYTHM)) label(annot.aux + 1); else label(annstr(annot.anntyp)); i += nsamp/80; j = xa - 40; if (sflag) { move(i, j); j -= 40; sprintf(title, "%d", annot.subtyp); label(title); } if (cflag) { move(i, j); j -= 40; sprintf(title, "%d", annot.chan); label(title); } if (nflag) { move(i, j); j -= 40; sprintf(title, "%d", annot.num); label(title); } if (aflag && annot.aux != NULL) { move(i, j); sprintf(title, "%s", annot.aux + 1); label(title); } stat = getann(0, &annot); } } } move(0, 0); closepl(); fflush(stdout); if (pause > 0) sleep(pause); } reset() /* restore cursor to prompt position */ { openpl(); space(0, 0, 1024, 1024); move(0, 0); closepl(); }