/* file mrgann.c G. Moody 28 May 1995 Last revised: 3 November 2017 ------------------------------------------------------------------------------- mrgann: Merge annotation files by segments Copyright (C) 1999 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/). _______________________________________________________________________________ This program reads two annotation files and creates a third. Command-line arguments divide the annotation files into segments. Within each segment, the annotations copied to the output annotation file may be any of the following: - none (selected by -m0) - all annotations from the first input file (selected by -m1) - all annotations from the second input file (selected by -m2) - all annotations from both input files (default; selected by -m3) Optionally, mrgann can remap the `chan' field in each annotation from one or both input files to a value that can be specified separately (using -c or -C) for each input annotation file. This feature may be useful, for example, to merge annotations for independent signals, where there may be occasional simultaneous input annotations. By remapping only the `chan' fields from one input file, it is possible in two or more passes to merge three or more annotation files in this way. When in `-m3' mode, if simultaneous annotations with the same `chan' field (after any remapping has been done) are present, only the annotation from the first annotator is copied, and a warning message is written to the standard output. */ #include #ifndef __STDC__ extern void exit(); #endif #include /* mode definitions */ #define UNINITIALIZED (-1) #define DISCARD_ALL 0 #define COPY_0 1 #define COPY_1 2 #define MERGE 3 char *pname, *record = NULL; static int ateof[2], map0 = -1, map1 = -1, vflag; static WFDB_Frequency sfreq, ffreq, afreq = 0; static WFDB_Anninfo ai[3]; static WFDB_Annotation annot[2]; void help(), mergeann(); main(argc, argv) int argc; char *argv[]; { char *prog_name(); WFDB_Time tf = (WFDB_Time)(-1); int i, mode = UNINITIALIZED, next_mode = MERGE; pname = prog_name(argv[0]); /* Interpret command-line options. */ for (i = 1; i < argc; i++) { if (*argv[i] == '-') switch (*(argv[i]+1)) { case 'c': /* map0 follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: `chan' mapping for first annotator must follow -c\n", pname); exit(1); } map0 = atoi(argv[i]); if (map0 < -1 || map0 > 255) map0 = -1; break; case 'C': /* map1 follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: `chan' mapping for second annotator must follow -C\n", pname); exit(1); } map1 = atoi(argv[i]); if (map1 < -1 || map1 > 255) map1 = -1; break; case 'h': /* print usage summary and quit */ help(); exit(0); break; case 'i': /* input annotators follow */ if (++i >= argc-1) { (void)fprintf(stderr, "%s: input annotators must follow -i\n", pname); exit(1); } ai[0].name = argv[i]; ai[0].stat = WFDB_READ; ai[1].name = argv[++i]; ai[1].stat = WFDB_READ; break; case 'm': /* time to switch modes follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: time to change modes must follow %s\n", pname, argv[i-1]); exit(1); } if (mode == UNINITIALIZED) { init(); mode = MERGE; } tf = strtim(argv[i]); if (tf < (WFDB_Time)0) tf = -tf; if (argv[i][0] == 'e') tf = (WFDB_Time)(-1); else tf = (tf * afreq / sfreq) + 0.5; mergeann(mode, tf); switch (*(argv[i-1]+2)) { case '0': mode = DISCARD_ALL; break; case '1': mode = COPY_0; break; case '2': mode = COPY_1; break; case '3': mode = MERGE; break; default: fprintf(stderr, "%s: unrecognized mode `%c' -> 3\n", pname, *(argv[i]+2)); mode = MERGE; break; } break; case 'o': /* output annotator follows */ if (++i >= argc) { (void)fprintf(stderr, "%s: output annotator must follow -o\n", pname); exit(1); } ai[2].name = argv[i]; ai[2].stat = WFDB_WRITE; 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 'v': /* verbose mode */ vflag = 1; break; default: (void)fprintf(stderr, "%s: unrecognized option %s (ignored)\n", pname, argv[i]); break; } else (void)fprintf(stderr, "%s: unrecognized argument %s (ignored)\n", pname, argv[i]); } if (mode == UNINITIALIZED) { init(); mode = MERGE; } mergeann(mode, (WFDB_Time)(-1)); wfdbquit(); exit(0); /*NOTREACHED*/ } init() { WFDB_Frequency af1, af2; if (record == NULL || ai[0].name == NULL || ai[1].name == NULL || ai[2].name == NULL) { help(); exit(1); } if ((sfreq = sampfreq(record)) < 0.) (void)setsampfreq(sfreq = WFDB_DEFFREQ); ffreq = sfreq / getspf(); if (annopen(record, ai, 3) < 0) exit(2); af1 = getiaorigfreq(0); af2 = getiaorigfreq(1); if (af1 > 0 && af2 > 0) setafreq(afreq = (af1 > af2 ? af1 : af2)); else if (af1 > 0) setafreq(afreq = (af1 > ffreq ? af1 : ffreq)); else if (af2 > 0) setafreq(afreq = (af2 > ffreq ? af2 : ffreq)); else afreq = ffreq; setiafreq(0, afreq); setiafreq(1, afreq); ateof[0] = getann(0, &annot[0]); ateof[1] = getann(1, &annot[1]); } void mergeann(mode, tf) int mode; WFDB_Time tf; { switch (mode) { case DISCARD_ALL: while (!ateof[0] && (tf < 0L || annot[0].time < tf)) ateof[0] = getann(0, &annot[0]); while (!ateof[1] && (tf < 0L || annot[1].time < tf)) ateof[1] = getann(1, &annot[1]); break; case COPY_0: while (!ateof[0] && (tf < 0L || annot[0].time < tf)) { if (map0 >= 0) annot[0].chan = map0; putann(0, &annot[0]); ateof[0] = getann(0, &annot[0]); } while (!ateof[1] && (tf < 0L || annot[1].time < tf)) ateof[1] = getann(1, &annot[1]); break; case COPY_1: while (!ateof[0] && (tf < 0L || annot[0].time < tf)) ateof[0] = getann(0, &annot[0]); while (!ateof[1] && (tf < 0L || annot[1].time < tf)) { if (map1 >= 0) annot[1].chan = map1; putann(0, &annot[1]); ateof[1] = getann(1, &annot[1]); } break; case MERGE: while (!ateof[0] && !ateof[1] && (tf < 0L || annot[0].time < tf || annot[1].time < tf)) { if (annot[0].time < annot[1].time) { if (map0 >= 0) annot[0].chan = map0; putann(0, &annot[0]); ateof[0] = getann(0, &annot[0]); } else if (annot[0].time > annot[1].time) { if (map1 >= 0) annot[1].chan = map1; putann(0, &annot[1]); ateof[1] = getann(1, &annot[1]); } else { if (map0 >= 0) annot[0].chan = map0; if (map1 >= 0) annot[1].chan = map1; if (annot[0].chan < annot[1].chan) { putann(0, &annot[0]); putann(0, &annot[1]); } else if (annot[0].chan > annot[1].chan) { putann(0, &annot[1]); putann(0, &annot[0]); } else { putann(0, &annot[0]); if (vflag && annot[0].anntyp != annot[1].anntyp) fprintf(stderr, "%s: %s written, %s discarded\n", mstimstr(annot[0].time), annstr(annot[0].anntyp), annstr(annot[1].anntyp)); } ateof[0] = getann(0, &annot[0]); ateof[1] = getann(1, &annot[1]); } } while (!ateof[0] && ateof[1] && (tf < 0L || annot[0].time < tf)) { if (map0 >= 0) annot[0].chan = map0; putann(0, &annot[0]); ateof[0] = getann(0, &annot[0]); } while (ateof[0] && !ateof[1] && (tf < 0L || annot[1].time < tf)) { if (map1 >= 0) annot[1].chan = map1; putann(0, &annot[1]); ateof[1] = getann(1, &annot[1]); } break; } } 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 -i ANNOTATOR1 ANNOTATOR2 -o ANNOTATOR3 [OPTIONS ...]\n", "where RECORD, ANNOTATOR1, and ANNOTATOR2 specify the input, ANNOTATOR3", "specifies an output annotation file for RECORD, and OPTIONS may include:", " -h print this usage summary", " -mX TIME change mode to X at specified TIME, where X is one of:", " 0 discard all annotations beginning at TIME", " 1 copy ANNOTATOR1 annotations and discard ANNOTATOR2 annotations", " 2 copy ANNOTATOR2 annotations and discard ANNOTATOR1 annotations", " 3 merge ANNOTATOR1 and ANNOTATOR2 annotations (default)", " -v verbose mode (warn about simultaneous annotations)", " -c N map `chan' fields of ANNOTATOR1 annotations to N (-1 <= N <= 255)", " -C N map `chan' fields of ANNOTATOR2 annotations to N (-1 <= N <= 255)", "Specifying N as -1 disables `chan' mapping (default).", 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]); }