/* file: ann2rr.c G. Moody 16 May 1995 Last revised: 4 November 2020 ------------------------------------------------------------------------------- ann2rr: Calculate RR intervals from an annotation file Copyright (C) 1995-2009 George B. Moody This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . You may contact the author by e-mail (wfdb@physionet.org) or postal mail (MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software, please visit PhysioNet (http://www.physionet.org/). _______________________________________________________________________________ */ #include #ifndef __STDC__ extern void exit(); #endif #include #define map1 #define map2 #define ammap #define mamap #define annpos #include char *pname; main(argc, argv) int argc; char *argv[]; { char *record = NULL, rrfstr[16], t0fstr[16], t1fstr[16], *prog_name(); double sps, spm, sph; int Aflag = 0, a0, a0flag = 0, a1, a1flag = 0, cflag = 0, i, j, pflag = 0, previous_annot_valid = 0, rrdigits = 3, rrformat = 0, t0digits = 3, t0flag = 0, t0format = 0, t1digits = 3, t1flag = 0, t1format = 0; long atol(); static char a0f[ACMAX+1], a1f[ACMAX+1]; static WFDB_Anninfo ai; static WFDB_Annotation annot; static WFDB_Time from, to, rr, t0, t1; void help(); pname = prog_name(argv[0]); /* Interpret command-line options. */ for (i = a0f[0] = a1f[0] = 1; i < argc; i++) { if (*argv[i] == '-') switch (*(argv[i]+1)) { case 'a': /* annotator follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: annotator must follow -a\n", pname); exit(1); } ai.name = argv[i]; break; case 'A': /* print intervals between annotations of all types */ Aflag = 1; break; case 'c': /* print intervals between consecutive valid annotations only */ cflag = 1; break; case 'f': /* starting time follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: starting time must follow -f\n", pname); exit(1); } from = i; /* to be converted to sample intervals below */ break; case 'h': /* print usage summary and quit */ help(); exit(0); break; case 'i': /* specify interval output format */ if (++i >= argc || *(argv[i]) == '-') { (void)fprintf(stderr, "%s: interval output format must follow -i\n", pname); exit(1); } rrformat = *(argv[i]); sscanf(argv[i]+1, "%d", &rrdigits); break; case 'p': /* annotation mnemonic(s) follow */ if (++i >= argc || !isann(j = strann(argv[i]))) { (void)fprintf(stderr, "%s: annotation mnemonic(s) must follow -P\n", pname); exit(1); } a1f[j] = 1; /* The code above not only checks that there is a mnemonic where there should be one, but also allows for the possibility that there might be a (user-defined) mnemonic beginning with `-'. The following lines pick up any other mnemonics, but assume that arguments beginning with `-' are options, not mnemonics. */ while (++i < argc && argv[i][0] != '-') if (isann(j = strann(argv[i]))) a1f[j] = 1; if (i == argc || argv[i][0] == '-') i--; a1f[0] = 0; break; case 'P': /* annotation mnemonic(s) follow */ if (++i >= argc || !isann(j = strann(argv[i]))) { (void)fprintf(stderr, "%s: annotation mnemonic(s) must follow -p\n", pname); exit(1); } a0f[j] = 1; while (++i < argc && argv[i][0] != '-') if (isann(j = strann(argv[i]))) a0f[j] = 1; if (i == argc || argv[i][0] == '-') i--; a0f[0] = 0; break; case 'r': /* input record name follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: input record name must follow -r\n", pname); exit(1); } record = argv[i]; break; case 't': /* ending time follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: end time must follow -t\n", pname); exit(1); } to = i; break; case 'v': /* output times of ends of intervals */ t1flag = 1; if (++i >= argc || argv[i][0] == '-') i--; else { t1format = *(argv[i]); sscanf(argv[i]+1, "%d", &t1digits); } break; case 'V': /* output times of beginnings of intervals */ t0flag = 1; if (++i >= argc || argv[i][0] == '-') i--; else { t0format = *(argv[i]); sscanf(argv[i]+1, "%d", &t0digits); } break; case 'w': /* output annotation types following intervals */ a1flag = 1; break; case 'W': /* output annotation types preceeding intervals */ a0flag = 1; break; default: (void)fprintf(stderr, "%s: unrecognized option %s\n", pname, argv[i]); exit(1); } else { (void)fprintf(stderr, "%s: unrecognized argument %s\n", pname, argv[i]); exit(1); } } if (record == NULL || ai.name == NULL) { help(); exit(1); } if ((sps = sampfreq(record)) < 0.) (void)setsampfreq(sps = WFDB_DEFFREQ); spm = 60.0*sps; sph = 60.0*spm; ai.stat = WFDB_READ; if (annopen(record, &ai, 1) < 0) /* open annotation file */ exit(2); if (rrdigits < 0 || rrdigits > 15) rrdigits = 3; sprintf(rrfstr, "%%.%dlf", rrdigits); if (t0digits < 0 || t0digits > 15) t0digits = 3; sprintf(t0fstr, "%%.%dlf", t0digits); if (t1digits < 0 || t1digits > 15) t1digits = 3; sprintf(t1fstr, "%%.%dlf", t1digits); if (from) { from = strtim(argv[(int)from]); if (from < (WFDB_Time)0) from = -from; } if (to) { to = strtim(argv[(int)to]); if (to < (WFDB_Time)0) to = -to; } if (to > (WFDB_Time)0 && to < from) { WFDB_Time tt = from; from = to; to = tt; } if (from > (WFDB_Time)0 && iannsettime(from) < 0) exit(2); a0 = NOTQRS; t0 = from; while (getann(0, &annot) == 0 && (to == (WFDB_Time)0 || annot.time <= to)){ a1 = annot.anntyp; t1 = annot.time; /* Does t1 mark a valid interval end point? */ if (Aflag || (a1f[0] && isqrs(a1)) || a1f[a1]) { /* Does t0 mark a valid interval starting point? */ if (cflag == 0 || previous_annot_valid == 1) { /* If requested, print time at beginning of interval. */ if (t0flag) { switch (t0format) { case 'h': (void)printf(t0fstr, t0/sph); break; case 'm': (void)printf(t0fstr, t0/spm); break; case 's': (void)printf(t0fstr, t0/sps); break; case 't': if (t0 == (WFDB_Time)0) (void)printf(" 0:00.000"); else (void)printf("%s", mstimstr(t0)); break; case 'T': (void)printf("%s", mstimstr(-t0)); break; default: (void)printf("%"WFDB_Pd_TIME, t0); break; } (void)printf("\t"); } /* If requested, print annotation at beginning of interval. */ if (a0flag) { if (a0 == NOTQRS) (void)printf("[0]\t"); else (void)printf("%s\t", annstr(a0)); } /* Print the interval, and update t0 and a0. */ rr = t1 - t0; switch (rrformat) { char rrstr[20]; double frr; case 'h': snprintf(rrstr, sizeof(rrstr), rrfstr, rr/sph); (void)printf("%s", rrstr); (void)sscanf(rrstr, "%lf", &frr); t1 = t0 + frr * sph + 0.5; break; case 'm': snprintf(rrstr, sizeof(rrstr), rrfstr, rr/spm); (void)printf("%s", rrstr); (void)sscanf(rrstr, "%lf", &frr); t1 = t0 + frr * spm + 0.5; break; case 's': snprintf(rrstr, sizeof(rrstr), rrfstr, rr/sps); (void)printf("%s", rrstr); (void)sscanf(rrstr, "%lf", &frr); t1 = t0 + frr * sps + 0.5; break; case 't': (void)printf("%s", mstimstr(t1)); break; default: (void)printf("%"WFDB_Pd_TIME, rr); break; } /* If requested, print annotation at end of interval. */ if (a1flag) (void)printf("\t%s", annstr(a1)); /* If requested, print time at end of interval. */ if (t1flag) { (void)printf("\t"); switch (t1format) { case 'h': (void)printf(t1fstr, t1/sph); break; case 'm': (void)printf(t1fstr, t1/spm); break; case 's': (void)printf(t1fstr, t1/sps); break; case 't': (void)printf("%s", mstimstr(t1)); break; case 'T': (void)printf("%s", mstimstr(-t1)); break; default: (void)printf("%"WFDB_Pd_TIME, t1); break; } } (void)printf("\n"); } } /* Does t1 mark a valid interval starting point? */ if (Aflag || (a0f[0] && isqrs(a1)) || a0f[a1]) { a0 = a1; t0 = t1; previous_annot_valid = 1; } else if (cflag) previous_annot_valid = 0; } exit(0); /*NOTREACHED*/ } char *prog_name(s) 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); } static char *help_strings[] = { "usage: %s -r RECORD -a ANNOTATOR [OPTIONS ...]\n", "where RECORD and ANNOTATOR specify the input, and OPTIONS may include:", " -A print all intervals between annotations (default: print only RR", " intervals; overrides -c, -p)", " -c print intervals between consecutive valid annotations only", " -f TIME start at specified TIME", " -h print this usage summary", " -i FMT print intervals using format FMT (see below for values of FMT)", " -p TYPE [TYPE ...] print intervals ending with annotations of specified", " TYPEs only (use mnemonics such as N or V for TYPE)", " -P TYPE [TYPE ...] print intervals beginning with specified types only", " -t TIME stop at specified TIME", " -v FMT print times of ends of intervals using format FMT (see below)", " -V FMT print times of beginnings of intervals using format FMT (see below)", " -w print annotations that end intervals", " -W print annotations that begin intervals", "By default, the output contains the RR intervals only, unless one or more", "of -v, -V, -w, or -W are used. Intervals and times are printed in units of", "sample intervals, unless a format is specified using -i, -v, or -V.", "Formats can be 'h' (hours), 'm' (minutes), 's' (seconds), 't' (hh:mm:ss);", "when used with -v or -V, format 'T' yields dates and times if available,", "or the format can be omitted to obtain times in sample intervals. Formats", "'h', 'm', and 's' may be followed by a number between 0 and 15, specifying", "the number of decimal places (default: 3). For example, to obtain intervals", "in seconds with 8 decimal places, use '-i s8'.", 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]); if (i % 23 == 0) { char b[5]; (void)fprintf(stderr, "--More--"); (void)fgets(b, 5, stdin); (void)fprintf(stderr, "\033[A\033[2K"); /* erase "--More--"; assumes ANSI terminal */ } } }