/* file: wfdbmap.c G. Moody 22 March 2009 Last revised: 4 November 2020 ------------------------------------------------------------------------------- wfdbmap: generates a 'plt' script to make a PostScript map of a WFDB record Copyright (C) 2009-2011 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/). _______________________________________________________________________________ The file signal-colors.h in this directory defines the colors used by wfdbmap. */ #include #include #include #include #include char *pname; WFDB_Anninfo *ai = NULL; WFDB_Siginfo *si = NULL; int mflag = 0; int spm; main(argc, argv) int argc; char *argv[]; { char *record = NULL, *prog_name(); int i, j, length, **map = NULL, nann = 0, nsig; void help(); void map_sig(char *record, WFDB_Siginfo *si, int nsig,int **map,int length); void map_ann(char *record, WFDB_Anninfo *ai, int nann,int **map,int length); void write_map(int **map, int nsig, int nann, int length); void write_script(int **map, int nsig, int nann, int length); pname = prog_name(argv[0]); /* Interpret command-line options. */ for (i = 1; i < argc; i++) { if (*argv[i] == '-') switch (*(argv[i]+1)) { case 'a': /* annotators follow */ if (++i >= argc) { (void)fprintf(stderr, "%s: annotators must follow -a\n", pname); exit(1); } /* how may annotators are there? */ for (j = i; j < argc && *argv[j] != '-'; j++) ; nann = j - i; /* allocate *ai and initialize it */ if (nann > 0) { if ((ai = malloc(nann * sizeof(WFDB_Anninfo))) == NULL) { fprintf(stderr, "%s: insufficient memory\n", pname); exit(2); } for (j = 0; j < nann; j++) { ai[j].name = argv[i++]; ai[j].stat = WFDB_READ; } } i--; break; case 'h': /* print usage summary and quit */ help(); exit(0); break; case 'm': /* print map only */ mflag = 1; 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; 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) { help(); exit(1); } nsig = isigopen(record, NULL, 0); if (nsig > 0) { if ((si = malloc(nsig * sizeof(WFDB_Siginfo))) == NULL) { fprintf(stderr, "%s: insufficient memory\n", pname); if (ai) { wfdbquit(); free(ai); } exit(2); } if ((nsig = isigopen(record, si, nsig)) < 0) nsig = 0; } spm = strtim("60"); length = (strtim("e") + (spm-1))/spm; /* number of minutes in record */ if (length < 1 && nsig > 0) { /* calculate the length of the record from the signal file size */ char *p; double fs = 0; FILE *ifile = NULL; long bpm; for (i = 0; i < nsig && si[i].group == 0; i++) switch (si[i].fmt) { case 8: case 80: fs += si[i].spf; break; case 310: case 311: fs += 1.33333333*si[i].spf; break; case 212: fs += 1.5*si[i].spf; break; case 16: case 61: case 160: fs += 2*si[i].spf; break; case 24: fs += 3*si[i].spf; break; case 32: fs += 3*si[i].spf; break; default: fs = 0; break; } if (fs > 0) { bpm = fs * spm + 0.5; /* bytes per minute */ if ((p = wfdbfile(si[i].fname, NULL)) != NULL && (ifile = fopen(p, "r")) != NULL && fseek(ifile, 0L, SEEK_END) == 0) length = ftell(ifile)/bpm; if (ifile) fclose(ifile); } } if (length < 1) { /* try to get length from annotation file(s) */ int alen; WFDB_Annotation annot; WFDB_Time t; for (i = 0; i < nann; i++) { if (annopen(record, &ai[i], 1) < 0) continue; while (getann(0, &annot) >= 0) t = annot.time; alen = (t + (spm-1))/spm; if (alen > length) length = alen; } } if (length > 60000) /* 1000 hours > 41 days */ length = 60000; /* truncate extremely long records */ if (length < 1) { fprintf(stderr, "%s: can't map record %s (length unspecified)\n", pname, record); exit(1); } if ((map = malloc((nsig + 4*nann) * sizeof(int *))) == NULL) { fprintf(stderr, "%s: insufficient memory\n", pname); wfdbquit(); if (ai) free(ai); if (si) free(si); exit(2); } for (i = 0; i < nsig + 4*nann; i++) if ((map[i] = calloc(length, sizeof(int))) == NULL) { fprintf(stderr, "%s: insufficient memory\n", pname); while (--i > 0) if (map[i]) free(map[i]); free(map); wfdbquit(); if (ai) free(ai); if (si) free(si); exit(2); } if (nsig > 0) map_sig(record, si, nsig, map, length); if (nann > 0) map_ann(record, ai, nann, &map[nsig], length); if (mflag) { printf("T"); for (i = 0; i < nsig; i++) printf("\t%s", si[i].desc); for (i = 0; i < nann; i++) printf("\t%s\tQRS\tEctopic\tVE", ai[i].name); printf("\n"); write_map(map, nsig, nann, length); } else write_script(map, nsig, nann, length); for (i = 0; i < nsig + 4*nann; i++) free(map[i]); free(map); wfdbquit(); if (ai) free(ai); if (si) free(si); exit(0); } void map_sig(char *record, WFDB_Siginfo *si, int nsig, int **map, int length) { if (si[0].nsamp == strtim("e") || si[0].nsamp != 0) { /* fixed-layout record */ int i, t; for (i = 0; i < nsig; i++) for (t = 0; t < length; t++) map[i][t] = 1; } else { /* variable-layout record */ char buf[256], *d, *p, *q, *r, *hfname, *shfname; long m, m0, mf, spm = strtim("60"), t = 0, tf; WFDB_FILE *ifile, *sfile; p = wfdbfile("hea", record); hfname = calloc(strlen(p) + 1, 1); strcpy(hfname, p); if ((ifile = wfdb_fopen(hfname, "r")) == NULL) { fprintf(stderr, "%s: can't open %s\n", pname, hfname); free(hfname); return; } for (d = p + strlen(p); d > p; d--) if (*(d-1) == '/') { *d = '\0'; break; } shfname = calloc(strlen(hfname) + 16, 1); strcpy(shfname, p); d = shfname + strlen(shfname); /* d points to first char after '/' */ wfdb_fgets(buf, sizeof(buf), ifile); /* read and ignore two lines */ wfdb_fgets(buf, sizeof(buf), ifile); m0 = 0; while (wfdb_fgets(buf, sizeof(buf), ifile)) {/* read a segment desc */ char *tp; if (buf[0] == '~') { /* segment is null (all signals off) */ t += atol(buf+2); m0 = t/spm; continue; } for (tp = buf+1; *tp != ' '; tp++) ; *tp = '\0'; tf = t + atol(tp+1); if ((mf = tf/spm) > length) mf = length; sprintf(d, "%s.hea", buf); if (sfile = wfdb_fopen(shfname, "r")) {/* open the segment header */ char sbuf[256]; int i; wfdb_fgets(sbuf, sizeof(sbuf), sfile);/* read & ignore a line */ while ((p = wfdb_fgets(sbuf, sizeof(sbuf), sfile)) && *sbuf != '#') { /* signal description line */ for (q = sbuf, i = 0; *q; q++) if (*q == ' ' && ++i == 8) break; q++; *(q + strlen(q) - 2) = '\0'; for (i = 0; i < nsig; i++) if (strcmp(si[i].desc, q) == 0) for (m = m0; m < mf; m++) map[i][m] = 1; } wfdb_fclose(sfile); t = tf; m0 = t/spm; } } wfdb_fclose(ifile); free(shfname); free(hfname); } } int *namax; void map_ann(char *record, WFDB_Anninfo *ai, int nann, int **map, int length) { int i; namax = malloc(nann * sizeof(int)); wfdbquiet(); /* suppress warnings if an annotator can't be opened */ for (i = 0; i < nann; i++) { char tstring[12]; int minutes, *na, *nq, *ne, *nv; WFDB_Annotation annot; WFDB_Time end_of_epoch; na = &map[4*i][0]; namax[i] = 1; /* Open the annotators one at a time, and do not quit if any fail to open. */ if (annopen(record, &ai[i], 1) < 0) continue; minutes = end_of_epoch = 0; iannsettime(1L); while (getann(0, &annot) >= 0) { if (annot.time > end_of_epoch) { if (*na > namax[i]) namax[i] = *na; minutes = annot.time/spm; na = &map[4*i][minutes]; nq = &map[4*i + 1][minutes]; ne = &map[4*i + 2][minutes]; nv = &map[4*i + 3][minutes]; if (++minutes > length) break; /* stop at the end of the record */ sprintf(tstring, "%d", 60*minutes); end_of_epoch = strtim(tstring); } switch (annot.anntyp) { case PVC: case FUSION: case VESC: case RONT: case FLWAV: (*nv)++; /* fall through, no break! */ case APC: case ABERR: case NPC: case SVPB: case NESC: case AESC: case SVESC: (*ne)++; /* fall through, no break! */ case NORMAL: case LBBB: case RBBB: case PACE: case UNKNOWN: case BBB: case LEARN: case PFUS: (*nq)++; /* fall through, no break! */ default: (*na)++; break; } } if (*na > namax[i]) namax[i] = *na; } return; } void write_map(int **map, int nsig, int nann, int length) { int i, imax = nsig + 4*nann - 1, t; for (t = 0; t < length; t++) { if (mflag == 0) printf("%d\t0\t", t); /* x0, y0 for histogram plots */ printf("%d\t", t+1); for (i = 0; i < imax; i++) printf("%d\t", map[i][t]); printf("%d\n", map[i][t]); } } #include "signal-colors.h" char *color(char *type) { int i; for (i = 0; ctab[i].name != NULL; i++) if (strcmp(type, ctab[i].name) == 0) return (ctab[i].color); return ("grey"); } void write_script(int **map, int nsig, int nann, int length) { char *p, tstring[2][30]; double ylow, ymax; int i, j; WFDB_Time tf = strtim("e"); strcpy(tstring[0], timstr(0)); for (p = tstring[0]; *p == ' '; p++) ; if (tf) strcpy(tstring[1], timstr(-tf)); else sprintf(tstring[1], "%d:%02d:00", length/60, length%60); ymax = 2*(nsig + 2*nann + 1); ylow = 0.01 * ymax; printf("#! /bin/sh\n" "cat >map.txt <= 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:", " -h print this usage summary", 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]); }