diff -Naur --exclude Makefile --exclude info wfdb-10.4.24/app/rdsamp.c wfdb-10.4.25/app/rdsamp.c --- wfdb-10.4.24/app/rdsamp.c 2009-04-30 15:30:35.000000000 -0400 +++ wfdb-10.4.25/app/rdsamp.c 2009-11-06 12:13:54.000000000 -0500 @@ -1,5 +1,5 @@ /* file: rdsamp.c G. Moody 23 June 1983 - Last revised: 20 April 2009 + Last revised: 6 November 2009 ------------------------------------------------------------------------------- rdsamp: Print an arbitrary number of samples from each signal @@ -44,9 +44,10 @@ int argc; char *argv[]; { - char *record = NULL, *search = NULL, *prog_name(); - int highres = 0, i, isiglist, nsig, nosig = 0, pflag = 0, s, *sig = NULL, - timeunits = SECONDS, vflag = 0; + char *record = NULL, *search = NULL, *escapify(), *prog_name(); + char *invalid, *snfmt, *tfmt, *tnfmt, *tufmt, *vfmt, speriod[16], tustr[16]; + int cflag = 0, highres = 0, i, isiglist, nsig, nosig = 0, pflag = 0, s, + *sig = NULL, timeunits = SECONDS, vflag = 0; WFDB_Frequency freq; WFDB_Sample *v; WFDB_Siginfo *si; @@ -56,6 +57,9 @@ pname = prog_name(argv[0]); for (i = 1 ; i < argc; i++) { if (*argv[i] == '-') switch (*(argv[i]+1)) { + case 'c': /* output in CSV format */ + cflag = 1; + break; case 'f': /* starting time */ if (++i >= argc) { (void)fprintf(stderr, "%s: time must follow -f\n", pname); @@ -78,14 +82,6 @@ } maxl = i; break; - case 'r': /* record name */ - if (++i >= argc) { - (void)fprintf(stderr, "%s: record name must follow -r\n", - pname); - exit(1); - } - record = argv[i]; - break; case 'P': /* output in high-precision physical units */ ++pflag; /* (fall through to case 'p') */ case 'p': /* output in physical units specified */ @@ -97,6 +93,14 @@ else if (*(argv[i]+2) == 'S') timeunits = SAMPLES; else timeunits = SECONDS; break; + case 'r': /* record name */ + if (++i >= argc) { + (void)fprintf(stderr, "%s: record name must follow -r\n", + pname); + exit(1); + } + record = argv[i]; + break; case 's': /* signal list follows */ isiglist = i+1; /* index of first argument containing a signal # */ while (i+1 < argc && *argv[i+1] != '-') { @@ -118,7 +122,6 @@ } search = argv[i]; break; - case 't': /* end time */ if (++i >= argc) { (void)fprintf(stderr, "%s: time must follow -t\n",pname); @@ -164,12 +167,10 @@ if (to > 0L && (to = strtim(argv[to])) < 0L) to = -to; if (nosig) { /* print samples only from specified signals */ -#ifndef lint if ((sig = (int *)malloc((unsigned)nosig*sizeof(int))) == NULL) { (void)fprintf(stderr, "%s: insufficient memory\n", pname); exit(2); } -#endif for (i = 0; i < nosig; i++) { if ((s = findsig(argv[isiglist+i])) < 0) { (void)fprintf(stderr, "%s: can't read signal '%s'\n", pname, @@ -181,12 +182,10 @@ nsig = nosig; } else { /* print samples from all signals */ -#ifndef lint if ((sig = (int *)malloc((unsigned)nsig*sizeof(int))) == NULL) { (void)fprintf(stderr, "%s: insufficient memory\n", pname); exit(2); } -#endif for (i = 0; i < nsig; i++) sig[i] = i; } @@ -204,142 +203,252 @@ if (maxl && (to == 0L || to > from + maxl)) to = from + maxl; - /* Print column headers if '-v' option selected. */ - if (vflag) { - char *p, *r, *t; - int j, l; + /* Adjust timeunits if starting time is undefined. */ + if (timeunits == TIMSTR || timeunits == HHMMSS) { + char *p = timstr(0L); + if (*p != '[') timeunits = HHMMSS; + else if (strlen(p) < 16) timeunits = SHORTTIMSTR; + else if (freq > 1.0) timeunits = MSTIMSTR; + } + if (timeunits == HOURS) freq *= 3600.; + else if (timeunits == MINUTES) freq *= 60.; - if (pflag == 0) (void)printf(" sample #"); - else if (timeunits == SAMPLES) (void)printf("sample interval"); - else if (timeunits == TIMSTR || timeunits == HHMMSS) { - p = timstr(0L); - if (*p != '[') - timeunits = HHMMSS; - else if (strlen(p) < 16) - timeunits = SHORTTIMSTR; - if (timeunits == HHMMSS) - printf(" Elapsed time"); - else if (timeunits == SHORTTIMSTR) - printf(" Time"); + /* Set formats for output. */ + if (cflag) { /* CSV output selected */ + snfmt = ",'%s'"; + if (pflag) { /* output in physical units */ + switch (timeunits) { + case SAMPLES: tnfmt = "'sample interval'"; + sprintf(tustr, "'%g sec'", 1./freq); + tufmt = tustr; break; + case SHORTTIMSTR: tnfmt = "'Time'"; + tufmt = "'hh:mm:ss.mmm'"; break; + case TIMSTR: tnfmt = "'Time and date'"; + tufmt = "'hh:mm:ss dd/mm/yyyy'"; break; + case MSTIMSTR: tnfmt = "'Time and date'"; + tufmt = "'hh:mm:ss.mmm dd/mm/yyyy'"; break; + case HHMMSS: tnfmt = "'Elapsed time'"; + tufmt = "'hh:mm:ss.mmm'"; break; + case HOURS: tnfmt = "'Elapsed time'"; + tufmt = "'hours'"; break; + case MINUTES: tnfmt = "'Elapsed time'"; + tufmt = "'minutes'"; break; + default: + case SECONDS: tnfmt = "'Elapsed time'"; + tufmt = "'seconds'"; break; + } + invalid = ",-"; + if (pflag > 1) /* output in high-precision physical units */ + vfmt = ",%.8lf"; + else + vfmt = ",%.3lf"; + } + else { /* output in raw units */ + tnfmt = "'sample #'"; + // sprintf(tufmt, "'%g sec'", 1./freq); + tfmt = "%ld"; + vfmt = ",%d"; + } + } + else { /* output in tab-separated columns selected */ + if (pflag) { /* output in physical units */ + switch (timeunits) { + case SAMPLES: tnfmt = "sample interval"; + sprintf(speriod, "(%g", 1./freq); + speriod[10] = '\0'; + sprintf(tustr, "%10s sec)", speriod); + tufmt = tustr; break; + case SHORTTIMSTR: tnfmt = " Time"; + tufmt = "(hh:mm:ss.mmm)"; break; + case TIMSTR: tnfmt = " Time Date "; + tufmt = "(hh:mm:ss dd/mm/yyyy)"; break; + case MSTIMSTR: tnfmt = " Time Date "; + tufmt = "(hh:mm:ss.mmm dd/mm/yyyy)"; break; + case HHMMSS: tnfmt = " Elapsed time"; + tufmt = " hh:mm:ss.mmm"; break; + case HOURS: tnfmt = " Elapsed time"; + tufmt = " (hours)"; break; + case MINUTES: tnfmt = " Elapsed time"; + tufmt = " (minutes)"; break; + default: + case SECONDS: tnfmt = " Elapsed time"; + tufmt = " (seconds)"; break; + } + if (pflag > 1) { /* output in high-precision physical units */ + snfmt = "\t%15s"; + invalid = "\t -"; + vfmt = "\t%15.8lf"; + } else { - if (freq > 1.0) { - timeunits = MSTIMSTR; - (void)printf(" "); - } - (void)printf(" Time Date "); + snfmt = "\t%7s"; + invalid = "\t -"; + vfmt = "\t%7.3lf"; } } - else (void)printf(" Elapsed time"); - if ((t = malloc((strlen(record)+30) * sizeof(char))) == NULL) { - fprintf(stderr, "%s: insufficient memory\n", pname); - exit(2); + else { /* output in raw units */ + snfmt = "\t%7s"; + tnfmt = " sample #"; + tfmt = "%15ld"; + vfmt = "\t%7d"; } - for (r = record+strlen(record); r > record; r--) - if (*r == '/') { - r++; - break; - } + } + + /* Print column headers if '-v' option selected. */ + if (vflag) { + char *p, *t; + int j, l; + + (void)printf(tnfmt); + for (i = 0; i < nsig; i++) { - /* Check if a default signal description was provided. Note that - if the description comes from the header file, the record name - may include path information that might not match that in - 'record' (if any); so we compare only the final part of the - description (p+j below) against the expected description (t). */ - (void)sprintf(t, "%s, signal %d", r, sig[i]); + /* Check if a default signal description was provided by looking + for the string ", signal " in the desc field. If so, replace it + with a shorter string. */ p = si[sig[i]].desc; - j = strlen(p) - strlen(t); - if (j > 0 && strcmp(p+j, t) == 0) { - (void)sprintf(t, "sig %d", sig[i]); - p = t; - } - l = strlen(p); - if (pflag > 1) { - if (l > 15) p += l - 15; - (void)printf("\t%15s", p); + if (strstr(p, ", signal ")) { + char *t; + if (t = malloc(10*sizeof(char))) { + (void)sprintf(t, "sig %d", sig[i]); + p = t; + } } - else { - if (l > 7) p+= l - 7; - (void)printf("\t%7s", p); + if (cflag == 0) { + l = strlen(p); + if (pflag > 1) { + if (l > 15) p += l - 15; + } + else { + if (l > 7) p+= l - 7; + } } + else + p = escapify(p); + (void)printf(snfmt, p); } + (void)printf("\n"); } /* Print data in physical units if '-p' option selected. */ if (pflag) { - char *p, *fmt = pflag > 1 ? "\t%15.8lf" : "\t%7.3f"; + char *p; - if (timeunits == HOURS) freq *= 3600.; - else if (timeunits == MINUTES) freq *= 60.; /* Print units as a second line of column headers if '-v' selected. */ if (vflag) { char s[12]; - switch (timeunits) { - case TIMSTR: (void)printf("(hh:mm:ss dd/mm/yyyy)"); break; - case MSTIMSTR: (void)printf("(hh:mm:ss.mmm dd/mm/yyyy)"); break; - case SHORTTIMSTR: (void)printf("(hh:mm:ss.mmm)"); break; - case HHMMSS: (void)printf(" hh:mm:ss.mmm"); break; - case HOURS: (void)printf(" (hours)"); break; - case MINUTES: (void)printf(" (minutes)"); break; - default: - case SECONDS: (void)printf(" (seconds)"); break; - case SAMPLES: (void)sprintf(s, "(%g", 1./freq); - (void)printf("%10s sec)", s); - } + (void)printf(tufmt); + for (i = 0; i < nsig; i++) { - char ustring[16]; - int len; - p = si[sig[i]].units; if (p == NULL) p = "mV"; - len = strlen(p); - if (pflag > 1) { if (len > 13) len = 13; } - else if (len > 5) len = 5; - ustring[0] = '('; - strncpy(ustring+1, p, len); - ustring[len+1] = '\0'; - (void)printf(pflag > 1 ? "\t%14s)" : "\t%6s)", ustring); + if (cflag == 0) { + char ustring[16]; + int len; + + len = strlen(p); + if (pflag > 1) { if (len > 13) len = 13; } + else if (len > 5) len = 5; + ustring[0] = '('; + strncpy(ustring+1, p, len); + ustring[len+1] = '\0'; + (void)printf(pflag > 1 ? "\t%14s)" : "\t%6s)", ustring); + } + else { + p = escapify(p); + (void)printf(",'%s'", p); + } } + (void)printf("\n"); } while ((to == 0L || from < to) && getvec(v) >= 0) { - switch (timeunits) { - case TIMSTR: (void)printf("%s", timstr(-from)); break; - case SHORTTIMSTR: - case MSTIMSTR: (void)printf("%s", mstimstr(-from)); break; - case HHMMSS: (void)printf("%15s", from == 0L ? - "0:00.000" : mstimstr(from)); break; - case SAMPLES: (void)printf("%15ld", from); break; - default: - case SECONDS: (void)printf("%15.3lf", (double)from/freq); break; - case MINUTES: (void)printf("%15.5lf", (double)from/freq); break; - case HOURS: (void)printf("%15.7lf", (double)from/freq); break; + if (cflag == 0) { + switch (timeunits) { + case TIMSTR: (void)printf("%s", timstr(-from)); break; + case SHORTTIMSTR: + case MSTIMSTR: (void)printf("%s", mstimstr(-from)); break; + case HHMMSS: (void)printf("%15s", from == 0L ? + "0:00.000" : mstimstr(from)); break; + case SAMPLES: (void)printf("%15ld", from); break; + default: + case SECONDS: (void)printf("%15.3lf",(double)from/freq); break; + case MINUTES: (void)printf("%15.5lf",(double)from/freq); break; + case HOURS: (void)printf("%15.7lf",(double)from/freq); break; + } + } + else { + switch (timeunits) { + case TIMSTR: + for (p = timstr(-from); *p == ' '; p++) + ; + (void)printf("'%s'", p); break; + case SHORTTIMSTR: + case MSTIMSTR: + for (p = mstimstr(-from); *p == ' '; p++) + ; + (void)printf("'%s'", p); break; + case HHMMSS: + if (from == 0L) printf("'0:00.000'"); + else { + for (p = mstimstr(from); *p == ' '; p++) + ; + (void)printf("'%s'", p); break; + } + break; + case SAMPLES: (void)printf("%ld", from); break; + default: + case SECONDS: (void)printf("%.3lf",(double)from/freq); break; + case MINUTES: (void)printf("%.5lf",(double)from/freq); break; + case HOURS: (void)printf("%.7lf",(double)from/freq); break; + } } + from++; for (i = 0; i < nsig; i++) { if (v[sig[i]] != WFDB_INVALID_SAMPLE) - (void)printf(fmt, + (void)printf(vfmt, (double)(v[sig[i]] - si[sig[i]].baseline)/si[sig[i]].gain); else - (void)printf(pflag > 1 ? "\t%15s" : "\t%7s", "-"); + (void)printf(invalid); } (void)printf("\n"); } } - else { + else { /* output in raw units */ while ((to == 0L || from < to) && getvec(v) >= 0) { - (void)printf("%15ld", from++); + (void)printf(tfmt, from++); for (i = 0; i < nsig; i++) - (void)printf("\t%7d", v[sig[i]]); + (void)printf(vfmt, v[sig[i]]); (void)printf("\n"); } } - exit(0); /*NOTREACHED*/ + exit(0); +} + +char *escapify(char *s) +{ + char *p = s, *q = s, *r; + int c = 0; + + while (*p) { + if (*p == '\'' || *p == '\\') + c++; + p++; + } + if (c > 0 && (p = r = calloc(p-s+c, sizeof(char))) != NULL) { + while (*q) { + if (*q == '\'' || *q == '\\') + *q++ = '\\'; + *p++ = *q; + } + q = r; + } + return (q); } char *prog_name(s) @@ -365,6 +474,7 @@ static char *help_strings[] = { "usage: %s -r RECORD [OPTIONS ...]\n", "where RECORD is the name of the input record, and OPTIONS may include:", + " -c use CSV (comma-separated value) output format", " -f TIME begin at specified time", " -h print this usage summary", " -H read multifrequency signals in high resolution mode", diff -Naur --exclude Makefile --exclude info wfdb-10.4.24/app/sigamp.c wfdb-10.4.25/app/sigamp.c --- wfdb-10.4.24/app/sigamp.c 2006-02-25 22:00:46.000000000 -0500 +++ wfdb-10.4.25/app/sigamp.c 2009-11-13 12:03:06.000000000 -0500 @@ -1,9 +1,9 @@ /* file: sigamp.c G. Moody 30 November 1991 - Last revised: 25 February 2006 + Last revised: 13 November 2009 ------------------------------------------------------------------------------- sigamp: Measure signal amplitudes -Copyright (C) 1991-2006 George B. Moody +Copyright (C) 1991-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 @@ -36,28 +36,42 @@ #include #define NMAX 300 +#define _STRING(X) #X +#define STRING(X) _STRING(X) + +/* values for timeunits */ +#define SECONDS 1 +#define MINUTES 2 +#define HOURS 3 +#define TIMSTR 4 +#define MSTIMSTR 5 +#define SHORTTIMSTR 6 +#define HHMMSS 7 +#define SAMPLES 8 char *pname; int namp; int nsig; int pflag; /* if non-zero, print physical units */ +int qflag; /* if non-zero, suppress output of trimmed means */ +int timeunits = SECONDS; int vflag; /* if non-zero, print individual measurements */ int *v0, *vmax, *vmin, *vv; double **amp, *vmean, *vsum; long dt1, dt2, dtw; +WFDB_Anninfo ai; +WFDB_Frequency sfreq; +WFDB_Siginfo *si; +WFDB_Time from = 0L, to = 0L, t; -main(argc, argv) -int argc; -char *argv[]; +main(int argc, char **argv) { - char *p, *record = NULL, *prog_name(); - int i, j, jlow, jhigh, nmax = NMAX, ampcmp(), getptp(), getrms(); - long from = 0L, to = 0L, t; - static int gvmode = 0; - static WFDB_Siginfo *si; - static WFDB_Anninfo ai; - void help(); + char *p, *record = NULL, *prog_name(char *s); + int gvmode = 0, i, j, jlow, jhigh, nmax = NMAX, + ampcmp(), getptp(WFDB_Time t), getrms(WFDB_Time t); + void help(void), printamp(WFDB_Time t); + /* Interpret command-line arguments. */ pname = prog_name(argv[0]); for (i = 1; i < argc; i++) { if (*argv[i] == '-') switch(*(argv[i]+1)) { @@ -106,6 +120,15 @@ break; case 'p': pflag = 1; + if (*(argv[i]+2) == 'd') timeunits = TIMSTR; + else if (*(argv[i]+2) == 'e') timeunits = HHMMSS; + else if (*(argv[i]+2) == 'h') timeunits = HOURS; + else if (*(argv[i]+2) == 'm') timeunits = MINUTES; + else if (*(argv[i]+2) == 'S') timeunits = SAMPLES; + else timeunits = SECONDS; + break; + case 'q': + qflag = 1; break; case 'r': if (++i >= argc) { @@ -154,6 +177,7 @@ exit(1); } + /* Finish initialization. */ if (gvmode == 0 && (p = getenv("WFDBGVMODE"))) gvmode = atoi(p); setgvmode(gvmode|WFDB_GVPAD); @@ -177,13 +201,26 @@ (void)fprintf(stderr, "%s: insufficient memory\n", pname); exit(3); } - } + } + if ((sfreq = sampfreq(NULL)) <= 0.0) + sfreq = WFDB_DEFFREQ; + + /* Adjust timeunits if starting time is undefined. */ + if (timeunits == TIMSTR || timeunits == HHMMSS) { + char *p = timstr(0L); + if (*p != '[') timeunits = HHMMSS; + } + if (timeunits == HOURS) sfreq *= 3600.; + else if (timeunits == MINUTES) sfreq *= 60.; + if (from > 0L) from = strtim(argv[(int)from]); if (to > 0L) to = strtim(argv[(int)to]); if (from < 0L) from = -from; if (to < 0L) to = -to; if (from >= to) to = strtim("e"); - if (ai.name) { + + /* Collect amplitudes, print them if qflag or vflag set. */ + if (ai.name) { /* windows are relative to annotations */ WFDB_Annotation annot; if (dtw > 0L) { @@ -214,21 +251,12 @@ (to == 0L || annot.time < to)) { if (map1(annot.anntyp) == NORMAL) { if (getptp(annot.time) < 0) break; - if (vflag) { - (void)printf("%s", mstimstr(annot.time)); - if (!pflag) - for (i = 0; i < nsig; i++) - (void)printf("\t%g", amp[i][namp]); - else - for (i = 0; i < nsig; i++) - (void)printf("\t%g", amp[i][namp]/si[i].gain); - (void)printf("\n"); - } + if (vflag || qflag) printamp(annot.time); namp++; } } } - else { + else { /* windows are consecutive nonoverlapping segments */ if (dt1 > 0L) { (void)fprintf(stderr, "%s: -d option must be used with -a;\n", pname); @@ -239,40 +267,31 @@ if (from > 0L && isigsettime(from) < 0L) exit(2); for (t = from; namp < nmax && (to == 0L || t < to); namp++, t += dtw) { if (getrms(t) < 0) break; - if (vflag) { - (void)printf("%s", mstimstr(t)); - if (!pflag) - for (i = 0; i < nsig; i++) - (void)printf("\t%g", amp[i][namp]); - else - for (i = 0; i < nsig; i++) - (void)printf("\t%g", amp[i][namp]/si[i].gain); - (void)printf("\n"); - } - } + if (vflag || qflag) printamp(t); + } } - jlow = namp/20; - jhigh = namp - jlow; - if (vflag) - (void)printf("Trimmed mean"); - for (i = 0; i < nsig; i++) { - double a; - qsort((char*)amp[i], namp, sizeof(double), ampcmp); - for (a = 0.0, j = jlow; j < jhigh; j++) - a += amp[i][j]; - a /= jhigh - jlow; - if (!pflag) - (void)printf("\t%g", a); - else - (void)printf("\t%g", a/si[i].gain); + /* Calculate trimmed means of collected amplitudes unless -q option set. */ + if (qflag == 0) { + jlow = namp/20; + jhigh = namp - jlow; + if (vflag) + (void)printf("Trimmed mean"); + for (i = 0; i < nsig; i++) { + double a; + + qsort((char*)amp[i], namp, sizeof(double), ampcmp); + for (a = 0.0, j = jlow; j < jhigh; j++) + a += amp[i][j]; + a /= jhigh - jlow; + (void)printf("\t%g", pflag ? a/si[i].gain : a); + } + (void)printf("\n"); } - (void)printf("\n"); - exit(0); /*NOTREACHED*/ + exit(0); } -int getrms(t) -long t; +int getrms(WFDB_Time t) { int i, v; long tt; @@ -316,8 +335,7 @@ return (0); } -int getptp(t) -long t; +int getptp(WFDB_Time t) { int i; long tt; @@ -343,16 +361,30 @@ return (0); } -int ampcmp(p1, p2) -double *p1, *p2; +void printamp(WFDB_Time t) +{ + int i; + + switch (timeunits) { + case TIMSTR: printf("%s",mstimstr(-t)); break; + case HHMMSS: printf("%s", mstimstr(t)); break; + case SAMPLES: printf("%ld", t); break; + default: printf("%lf", t/sfreq); break; + } + for (i = 0; i < nsig; i++) + (void)printf("\t%g", pflag ? amp[i][namp]/si[i].gain : + amp[i][namp]); + (void)printf("\n"); +} + +int ampcmp(double *p1, double *p2) { if (*p1 > *p2) return (1); else if (*p1 == *p2) return (0); else return (-1); } -char *prog_name(s) -char *s; +char *prog_name(char *s) { char *p = s + strlen(s); @@ -381,17 +413,25 @@ " -f TIME begin at specified time", " -h print this usage summary", " -H read multifrequency signals in high resolution mode", - " -n NMAX make up to NMAX measurements per signal (default: 300)", - /* default NMAX is defined as 300 above */ + " -n NMAX make up to NMAX measurements per signal (default: NMAX = " + STRING(NMAX) ")", " -p print results in physical units (default: ADC units)", + " -p may be followed by a character to choose a time format:", + " -pd print time of day and date if known", + " -pe print elapsed time as ::", + " -ph print elapsed time in hours", + " -pm print elapsed time in minutes", + " -ps print elapsed time in seconds (default)", + " -pS print elapsed time in sample intervals", + " -q quick mode: print individual measurements only", " -t TIME stop at specified time", - " -v verbose mode: print individual measurements", + " -v verbose mode: print individual measurements and trimmed means", " -w DTW set RMS amplitude measurement window", " default: DTW = 1 (second)", NULL }; -void help() +void help(void) { int i; diff -Naur --exclude Makefile --exclude info wfdb-10.4.24/app/wrsamp.c wfdb-10.4.25/app/wrsamp.c --- wfdb-10.4.24/app/wrsamp.c 2009-01-20 23:07:42.000000000 -0500 +++ wfdb-10.4.25/app/wrsamp.c 2010-01-21 15:11:33.000000000 -0500 @@ -1,8 +1,9 @@ /* file: wrsamp.c G. Moody 10 August 1993 - Last revised: 20 January 2009 + Last revised: 21 January 2010 + ------------------------------------------------------------------------------- wrsamp: Select fields or columns from a file and generate a WFDB record -Copyright (C) 1993-2009 George B. Moody +Copyright (C) 1993-2010 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 @@ -22,8 +23,6 @@ please visit PhysioNet (http://www.physionet.org/). _______________________________________________________________________________ -Portions of this program were derived from the `field' utility described in -"The UNIX System" by S.R. Bourne, pp. 227-9 (Addison-Wesley, 1983). */ #include @@ -33,27 +32,208 @@ /* The following definition yields dither with a triangular PDF in (-1,1). */ #define DITHER (((double)rand() + (double)rand())/RAND_MAX - 1.0) -#define isfsep(c) ((fsep && ((c)==fsep)) || ((c)==' ' || (c)=='\t')) +/* Dynamic memory allocation macros */ +#define MEMERR(P, N, S) \ + { wfdb_error("WFDB: can't allocate (%ld*%ld) bytes for %s\n", \ + (size_t)N, (size_t)S, #P); \ + exit(1); } +#define SFREE(P) { if (P) { free (P); P = 0; } } +#define SUALLOC(P, N, S) { if (!(P = calloc((N), (S)))) MEMERR(P, (N), (S)); } +#define SALLOC(P, N, S) { SFREE(P); SUALLOC(P, (N), (S)) } +#define SREALLOC(P, N, S) { if (!(P = realloc(P, (N)*(S)))) MEMERR(P,(N),(S)); } +#define SSTRCPY(P, Q) { if (Q) { \ + SALLOC(P, (size_t)strlen(Q)+1,1); strcpy(P, Q); } } char *pname; +char *read_line(FILE *ifile, char rsep) +{ + static char *buf; + int c; + static size_t i = 0, length = 0; + + while ((c = getc(ifile)) != rsep) { + if (i >= length) { + if (length == 0) length = 512; + length *= 2; + SREALLOC(buf, length+2, sizeof(char)); + } + if (c == EOF) { + free(buf); + return (buf = NULL); + } + buf[i++] = c; + } + buf[i] = '\0'; + i = 0; + return (buf); +} + +int line_has_alpha(char *line) +{ + char *p; + + for (p = line; *p; p++) + if (('A' <= *p && *p <= 'Z') || ('a' <= *p && *p <= 'z')) + return (1); + return (0); +} + +int line_has_tab(char *line) +{ + char *p; + + for (p = line; *p; p++) + if (*p == '\t') + return (1); + return (0); +} + +struct parsemode { + char *delim; /* characters that delimit tokens */ + char collapse; /* if non-zero, collapse consecutive delimiters */ + char esc; /* if non-zero, next character is literal unless null */ + char *quotepair[]; /* pairs of characters that open and close tokens */ +}; + +struct tokenarray { + int ntokens; /* number of tokens */ + int maxtokens; /* number of allocated token pointers */ + char *token[]; /* token pointers */ +}; + +typedef struct parsemode Parsemode; +typedef struct tokenarray Tokenarray; + +/* The function parseline() parses its first argument, a null-terminated string +('line'), into a Tokenarray ('ta'). On return, the token pointers (ta->token[]) +address locations within 'line', and the character that immediately follows +each token is replaced with a null. + +The second argument of parseline, ta, is a pointer to a Tokenarray (defined +above). If ta is NULL on entry, parseline allocates and returns a Tokenarray +that is sufficiently large to accomodate all of the tokens in 'line'. +Otherwise, parseline returns the number of tokens found in ta->ntokens, and the +token array on return contains either ta->ntokens or ta->maxtokens valid +elements, whichever is smaller. If ta->ntokens > ta->maxtokens, the token +array contains pointers to the first ta->maxtokens tokens. + +The third and final argument of parseline, pmode, is a pointer to a Parsemode +(also defined above). The caller can set up pmode to specify which characters +in 'line' are token delimiters, which pairs of characters can be used to quote +a token that may have embedded delimiters, whether to treat consecutive +delimiters as if they surround an empty token or as a single delimiter, and +which character acts as an escape (to cause the next character to be treated as +a literal character in a token, rather than as a delimiter, quote, or escape +character). + +If pmode is NULL, parseline behaves as if called with pmode = defpmode. If +pmode->delim is NULL, parseline sets pmode->delim = defpmode.delim. +*/ + +Parsemode defpmode = { + " \t\r\n,", /* delimiter characters can be space, tab, CR, LF, or comma */ + 1, /* collapse consecutive delimiters */ + '\\', /* treat any character following a backslash as a literal */ + { "''", "\"\"", "()", "[]", "{}", "<>", NULL } /* quote characters */ +}; + +Tokenarray *parseline(char *line, Tokenarray *ta, Parsemode *pmode) +{ + int i, n = 0, state = 0; + char d, *p = line-1, *q = NULL; + + if (ta == NULL) { + int m = (strlen(line) + 1)/2; + if (ta = (Tokenarray *)malloc(sizeof(int)*2 + sizeof(char *)*m)) + ta->maxtokens = m; + else + return (NULL); + } + + if (pmode == NULL) + pmode = &defpmode; + else if (pmode->delim == NULL) + pmode->delim = defpmode.delim; + + while (*(++p)) { /* for each character in the line */ + + /* is *p an escape character? */ + if (pmode->esc && *p == pmode->esc) { + if (*(p+1) == '\0') + break; + if (state == 0) { /* start a new token */ + state = 1; + ta->token[n++] = p; + } + p++; /* include the next character in the token */ + continue; + } + + /* is *p the character needed to complete a quoted string? */ + if (q) { + if (*p == *q) { *p = '\0'; q = NULL; } + continue; + } + + /* is *p a delimiter character? */ + i = 0; + while (d = pmode->delim[i++]) { + if (*p == d) { + *p = '\0'; /* replace delimiter with null */ + if (state == 0) { /* not in a token */ + if (pmode->collapse == 0) + ta->token[n++] = p; /* count an empty token */ + } + state = 0; + break; + } + } + + /* is *p an open-quote character? */ + i = 0; + while (q = pmode->quotepair[i++]) { /* q is an open-quote character */ + if (*p == *q) { /* *p is first character of a quoted string */ + if (state == 0) { /* start a new token */ + ta->token[n++] = p+1; + state = 1; + } + q++; /* *q is now the matching close-quote character */ + break; + } + } + + if (d == '\0' && q == NULL) { /* p must be part of a token */ + if (state == 0) { + ta->token[n++] = p; /* start a new token */ + state = 1; + } + } + } + + ta->ntokens = n; + return (ta); +} + main(argc, argv) int argc; char *argv[]; { - char **ap, *cp, **desc, **fp = NULL, fsep = '\0', *ifname = "(stdin)", - *l = NULL, ofname[40], *p, *record = NULL, rsep = '\n', *prog_name(); - char *gain = "", *scale = ""; + char **ap, *cp, **desc, *gain = "", *ifname = "(stdin)", + *line = NULL, ofname[40], *p, *record = NULL, rsep = '\n', + *scale = "", sflag = 0, trim = 0, **units, *prog_name(); + static char btime[25]; double freq = WFDB_DEFFREQ, *scalef, v; #ifndef atof double atof(); #endif - int c, cf = 0, dflag = 0, *fv = NULL, i, lmax = 1024, mf; + int c, cf = 0, dflag = 0, format = 16, *fv = NULL, i, labels, mf, zflag = 0; FILE *ifile = stdin; long t = 0L, t0 = 0L, t1 = 0L; #ifndef atol long atol(); #endif + Tokenarray *ta; WFDB_Sample *vout; WFDB_Siginfo *si; unsigned int nf = 0; @@ -113,11 +293,8 @@ ifname = argv[i]; break; case 'l': - if (++i >= argc || (lmax = atoi(argv[i])) < 1) { - (void)fprintf(stderr, "%s: max line length must follow -l\n", - pname); - exit(1); - } + if (++i >= argc) --i; + (void)fprintf(stderr, "%s: -l is obsolete, ignored\n", pname); break; case 'o': if (++i >= argc) { @@ -127,6 +304,14 @@ } record = argv[i]; break; + case 'O': + if (++i >= argc) { + (void)fprintf(stderr, "%s: output format must follow -O\n", + pname); + exit(1); + } + format = atoi(argv[i]); + break; case 'r': if (++i >= argc) { (void)fprintf(stderr, "%s: line separator must follow -r\n", @@ -137,11 +322,12 @@ break; case 's': if (++i >= argc) { - (void)fprintf(stderr, "%s: field separator must follow -s\n", + (void)fprintf(stderr, "%s: field separator(s) must follow -s\n", pname); exit(1); } - fsep = argv[i][0]; + sflag = 1; + defpmode.delim = argv[i]; break; case 't': if (++i >= argc || (t1 = atol(argv[i])) <= 0L) { @@ -158,6 +344,9 @@ } scale = argv[i]; break; + case 'z': /* ignore column 0 */ + zflag = 1; + break; default: (void)fprintf(stderr, "%s: unrecognized option %s\n", pname, argv[i]); @@ -165,59 +354,88 @@ } } - /* allocate storage for arrays */ -#ifndef lint - l = calloc(lmax, sizeof(char)); - fp = (char **)calloc(lmax, sizeof(char *)); - fv = (int *)calloc(argc - i, sizeof(int)); -#endif - if (l == NULL || fv == NULL || fp == NULL) { - (void)fprintf(stderr, "%s: insufficient memory\n", pname); + /* read the first line of the input file */ + if ((line = read_line(ifile, rsep)) == NULL) { + if (rsep != '\n') { + if (record == NULL) + (void)fprintf(stderr, + "%s: use -o to specify the output record name", + pname); + else + (void)fprintf(stderr, + "%s: no record separators in input\n" + "Try specifying a different separator after the -r option.\n", + pname); + } + else + (void)fprintf(stderr, "%s: no newlines in input\n", pname); exit(3); } - - /* read arguments into fv[...] */ - while (i < argc) { - if (sscanf(argv[i++], "%d", &fv[nf]) != 1 || - fv[nf] < 0 || fv[nf] >= lmax) { - (void)fprintf(stderr, "%s: unrecognized argument %s\n", - pname, argv[--i]); - exit(1); + + /* unless -s was given, note if it contains any tab characters */ + if (sflag == 0 && line_has_tab(line)) + defpmode.delim = "\t"; + + /* note if it contains any alphabetic characters */ + labels = line_has_alpha(line); + + /* parse it into tokens */ + ta = parseline(line, NULL, NULL); + + /* read selected column numbers into fv[...] */ + if (i < argc) { + SUALLOC(fv, argc - i, sizeof(int)); + while (i < argc) { + if (sscanf(argv[i++], "%d", &fv[nf]) != 1 || + fv[nf] < 0 || fv[nf] >= ta->ntokens) { + (void)fprintf(stderr, "%s: unrecognized argument %s\n", + pname, argv[--i]); + exit(1); + } + nf++; } - nf++; } - - if (nf < 1) { - help(); - exit(1); + /* if no columns were specified, copy all columns (or all except 0) */ + else { + int i, j; + + nf = ta->ntokens - zflag; + SUALLOC(fv, nf, sizeof(int)); + for (i = 0, j = zflag; j <= nf; i++, j++) + fv[i] = j; } - if ((vout = malloc(nf * sizeof(WFDB_Sample))) == NULL || - (si = malloc(nf * sizeof(WFDB_Siginfo))) == NULL || - (desc = malloc(nf * sizeof(char *))) == NULL || - (scalef = malloc(nf * sizeof(double))) == NULL) { - (void)fprintf(stderr, "%s: insufficient memory\n", pname); - exit(2); - } - /* open output file */ + /* allocate arrays */ + SUALLOC(vout, nf, sizeof(WFDB_Sample)); + SUALLOC(si, nf, sizeof(WFDB_Siginfo)); + SUALLOC(scalef, nf, sizeof(double)); + + /* open the output record */ if (record == NULL) (void)sprintf(ofname, "-"); else (void)sprintf(ofname, "%s.dat", record); for (i = 0; i < nf; i++) { si[i].fname = ofname; - if ((desc[i] = malloc((strlen(ifname)+20) * sizeof(char))) == NULL) { - (void)fprintf(stderr, "%s: insufficient memory\n", pname); - exit(2); + si[i].desc = NULL; + if (labels) { /* set the signal descriptions from the column headings */ + char *p = ta->token[fv[i]], *q; + + while (*p == ' ') p++; + q = p + strlen(p); + while (*(q-1) == ' ') q--; + *q = '\0'; + SSTRCPY(si[i].desc, p); + } + else { + char tdesc[16]; + + (void)sprintf(tdesc, "column %d", fv[i]); + SSTRCPY(si[i].desc, tdesc); } - if (ifile == stdin) - (void)sprintf(desc[i], "column %d", fv[i]); - else - (void)sprintf(desc[i], "%s, column %d", ifname, fv[i]); - si[i].desc = desc[i]; si[i].units = ""; si[i].group = 0; - si[i].fmt = 16; + si[i].fmt = format; si[i].spf = 1; si[i].bsize = 0; si[i].adcres = WFDB_DEFRES; @@ -238,81 +456,86 @@ while (*scale != '\0' && *scale != ' ') scale++; } + + if (labels) { /* read the second line of input */ + if ((line = read_line(ifile, rsep)) == NULL) { + (void)fprintf(stderr, "%s: no input data\n", pname); + exit(4); + } + if (line_has_alpha(line)) { + free(ta); + ta = parseline(line, NULL, NULL); + /* Copy units strings after trimming any surrounding spaces, + parentheses, or brackets, and after replacing embedded spaces + with underscores. */ + for (i = 0; i < nf; i++) { + char *p = ta->token[fv[i]], *q; + + while (*p == ' ') p++; + if (*p == '(' || *p == '[') p++; + q = p + strlen(p); + while (*(q-1) == ' ') q--; + if (*(q-1) == ')' || *(q-1) == ']') q--; + *q = '\0'; + while (--q > p) + if (*q == ' ') *q = '_'; + si[i].units = NULL; + SSTRCPY(si[i].units, p); + } + line = read_line(ifile, rsep); + } + } + + /* discard any additional lines containing text */ + while (line_has_alpha(line)) + line = read_line(ifile, rsep); + if (osigfopen(si, nf) < nf || setsampfreq(freq) < 0) exit(2); - /* read and copy input */ - nf--; - cp = l; - ap = fp; - *ap++ = cp; - do { - c = getc(ifile); - if (cp >= l + lmax) { - (void)fprintf(stderr, - "%s: line %ld truncated (> %d bytes)\n", - pname, t, lmax); - while (c != rsep && c!= EOF) - c = getc(ifile); - } - if (c == rsep || c == EOF) { - if (cp == l && c == EOF) break; - *cp++ = 0; - mf = ap - fp; - if (cf) { - if (cf < 0) cf = mf; - else if (cf != mf) - (void)fprintf(stderr, - "%s: line %ld has incorrect field count\n", - pname, t); - } - if (t >= t0) { /* write data from this line */ - for (i = 0; i <= nf; i++) { - if ((p = fp[fv[i]]) == NULL) { - (void)fprintf(stderr, - "%s: line %ld, column %d missing\n", - pname, t, fv[i]); - vout[i] = WFDB_INVALID_SAMPLE; - } - else if (sscanf(p, "%lf", &v) != 1) { - if (strcmp(p, "-") != 0) - (void)fprintf(stderr, - "%s: line %ld, column %d improperly formatted\n", - pname, t, fv[i]); - vout[i] = WFDB_INVALID_SAMPLE; - } - else { - v *= scalef[i]; - if (dflag) v += DITHER; - if (v >= 0) vout[i] = (WFDB_Sample)(v + 0.5); - else vout[i] = (WFDB_Sample)(v - 0.5); - } - } - if (putvec(vout) < 0) break; - for (i = 0; i <= nf; i++) - fp[fv[i]] = NULL; - } - if (c == EOF || (++t >= t1 && t1 > 0L)) break; - cp = l; - ap = fp; - *ap++ = cp; - } - else if (isfsep(c)) { - if (cp > l) { - *cp++ = 0; - *ap++ = cp; - } - do { - c = getc(ifile); - } while (c != rsep && c != EOF && isfsep(c)); - (void)ungetc(c, ifile); + /* skip any unwanted samples at the beginning */ + for (t = 0; t < t0; t++) + line = read_line(ifile, rsep); + + /* pick up base time if it's there */ + if (zflag) { + if (*line == '[') { + strncpy(btime, line+1, 23); + setbasetime(btime); + } + else if (*(line+1) == '[') { + strncpy(btime, line+2, 23); + setbasetime(btime); } - else *cp++ = c; - } while (c != EOF); + } + + /* read and copy samples */ + while (line != NULL && (t1 == 0L || t++ < t1)) { + free(ta); + ta = parseline(line, NULL, NULL); + for (i = 0; i < nf; i++) { + double v; + + if (sscanf(ta->token[fv[i]], "%lf", &v) == 1) { + // if (strcmp(ta->token[fv[i]], "-")) { + v *= scalef[i]; + if (dflag) v+= DITHER; + if (v >= 0) vout[i] = (WFDB_Sample)(v + 0.5); + else vout[i] = (WFDB_Sample)(v - 0.5); + } + else + vout[i] = WFDB_INVALID_SAMPLE; + } + if (putvec(vout) < 0) break; + line = read_line(ifile, rsep); + } + + /* write the header */ + (void)setsampfreq(freq); if (record != NULL) (void)newheader(record); wfdbquit(); - exit(0); /*NOTREACHED*/ + exit(0); } char *prog_name(s) @@ -336,7 +559,7 @@ } static char *help_strings[] = { - "usage: %s [OPTIONS ...] COLUMN [COLUMN ...]\n", + "usage: %s [OPTIONS ...] [COLUMN ...]\n", "where COLUMN selects a field to be copied (leftmost field is column 0),", "and OPTIONS may include:", " -c check that each input line contains the same number of fields", @@ -346,18 +569,34 @@ " -G GAIN specify gain(s) to be written to header file (default: 200)", " -h print this usage summary", " -i FILE read input from FILE (default: standard input)", - " -l LEN read up to LEN characters in each line (default: 1024)", " -o RECORD save output in RECORD.dat, and generate a header file for", " RECORD (default: write to standard output in format 16, do", " not generate a header file)", - " -r RSEP interpret RSEP as the input line separator (default: \\n)", - " -s FSEP interpret FSEP as the input field separator (default: space", - " or tab)", + " -O FORMAT save output in the specified FORMAT (default: 16)", + " -r RSEP interpret RSEP as the input line separator (default: linefeed)", + " -s FSEP interpret any character in FSEP as an input field separator", + " (default: space, tab, carriage-return, or comma) ", " -t N stop copying at line N (default: end of input file)", " -x SCALE multiply inputs by SCALE factor(s) (default: 1)", + " -z don't copy column 0 unless explicitly specified", + "", + "The input is a text file with a sample of each signal on each line. Samples", + "can be separated by tabs, spaces, commas, or any combination of these.", + "Consecutive separators are equivalent to single separators.", + "", "To specify different GAIN or SCALE values for each output signal, provide", "a quoted list of values, e.g., -G \"100 50\" defines the gain for signal 0", "as 100, and the gain for signal 1 (and any additional signals) as 50.", + "", + "If the first input line contains any alphabetic characters, it is assumed", + "to contain column headings, which are copied as signal descriptions into", + "RECORD.hea. If the second input line also contains alphabetic characters,", + "it is assumed to specify the physical units for each column, which are", + "copied as units strings into RECORD.hea. The first line containing samples", + "is line 0.", + "", + "If no COLUMN is specified, all fields are copied in order; column 0 can be", + "omitted in this case by using the '-z' option.", NULL }; @@ -366,6 +605,14 @@ int i; (void)fprintf(stderr, help_strings[0], pname); - for (i = 1; help_strings[i] != NULL; i++) + 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 */ + } + } } diff -Naur --exclude Makefile --exclude info wfdb-10.4.24/checkpkg/appcheck wfdb-10.4.25/checkpkg/appcheck --- wfdb-10.4.24/checkpkg/appcheck 2007-12-27 12:15:09.000000000 -0500 +++ wfdb-10.4.25/checkpkg/appcheck 2010-01-21 14:30:01.000000000 -0500 @@ -1,6 +1,6 @@ #!/bin/sh # file: appcheck G. Moody 7 September 2001 -# Last revised: 27 December 2007 +# Last revised: 21 January 2010 # # This script checks the basic functionality of most of the WFDB applications # in the 'app' directory. These programs are not (yet) tested by this script: @@ -111,7 +111,7 @@ echo Testing wrsamp ... F=100w.dat -wrsamp -o 100w -f 2 -F 360 -x 200 1 2