/* 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]);
}