/* file: sigamp.c G. Moody 30 November 1991
Last revised: 27 April 2020
-------------------------------------------------------------------------------
sigamp: Measure signal amplitudes
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
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
#include
#include
#define isqrs
#define map2
#define ammap
#define mamap
#define annpos
#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;
WFDB_Time dt1, dt2, dtw;
WFDB_Anninfo ai;
WFDB_Frequency sfreq;
WFDB_Siginfo *si;
WFDB_Time from = 0L, to = 0L, t;
main(int argc, char **argv)
{
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)) {
case 'a':
if (++i >= argc) {
(void)fprintf(stderr, "%s: annotator name must follow -a\n",
pname);
exit(1);
}
ai.name = argv[i];
break;
case 'd':
if (++i >= argc-1) {
(void)fprintf(stderr, "%s: DT1 and DT2 must follow -d\n",
pname);
exit(1);
}
/* save arg list index, convert arguments to samples later (after
record has been opened and sampling frequency is known) */
dt1 = i++;
break;
case 'f':
if (++i >= argc) {
(void)fprintf(stderr, "%s: start time must follow -f\n",
pname);
exit(1);
}
/* save arg list index, convert argument to samples later */
from = i;
break;
case 'h': /* help requested */
help();
exit(0);
break;
case 'H': /* operate in WFDB_HIGHRES mode */
gvmode = WFDB_HIGHRES;
break;
case 'n':
if (++i >= argc) {
(void)fprintf(stderr,
"%s: number of measurements must follow -n\n",
pname);
exit(1);
}
if ((nmax = atoi(argv[i])) < 1) nmax = NMAX;
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) {
(void)fprintf(stderr, "%s: record name must follow -r\n",
pname);
exit(1);
}
record = argv[i];
break;
case 't':
if (++i >= argc) {
(void)fprintf(stderr, "%s: stop time must follow -t\n",
pname);
exit(1);
}
/* save arg list index, convert argument to samples later */
to = i;
break;
case 'v':
vflag = 1;
break;
case 'w':
if (++i >= argc) {
(void)fprintf(stderr, "%s: window size must follow -w\n",
pname);
exit(1);
}
/* save arg list index, convert argument to samples later */
dtw = i;
break;
default:
(void)fprintf(stderr, "%s: unrecognized option %s\n",
pname, argv[i]);
exit(1);
break;
}
else {
(void)fprintf(stderr, "%s: unrecognized argument %s\n",
pname, argv[i]);
exit(1);
break;
}
}
if (record == NULL) {
help();
exit(1);
}
/* Finish initialization. */
if (gvmode == 0 && (p = getenv("WFDBGVMODE")))
gvmode = atoi(p);
setgvmode(gvmode|WFDB_GVPAD);
if ((nsig = isigopen(record, NULL, 0)) <= 0) exit(2);
if ((si = malloc(nsig * sizeof(WFDB_Siginfo))) == NULL ||
(v0 = malloc(nsig * sizeof(int))) == NULL ||
(vmax = malloc(nsig * sizeof(int))) == NULL ||
(vmin = malloc(nsig * sizeof(int))) == NULL ||
(vv = malloc(nsig * sizeof(int))) == NULL ||
(amp = malloc(nsig * sizeof(double *))) == NULL ||
(vmean = malloc(nsig * sizeof(double))) == NULL ||
(vsum = malloc(nsig * sizeof(double))) == NULL) {
(void)fprintf(stderr, "%s: insufficient memory\n", pname);
exit(3);
}
if (isigopen(record, si, nsig) != nsig) exit(2);
for (i = 0; i < nsig; i++) {
if (si[i].gain == 0.0) si[i].gain = WFDB_DEFGAIN;
if ((amp[i] = malloc(nmax * sizeof(double))) == NULL) {
(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");
/* Collect amplitudes, print them if qflag or vflag set. */
if (ai.name) { /* windows are relative to annotations */
WFDB_Annotation annot;
if (dtw > 0L) {
(void)fprintf(stderr,
"%s: -a and -w options cannot be used together;\n",
pname);
(void)fprintf(stderr, " -w option is being ignored\n");
}
if (annopen(record, &ai, 1) < 0) exit(2);
if (dt1 == 0L) dt1 = -(dt2 = strtim("0.05"));
else {
char *p = argv[(int)dt1+1];
if (*p == '-') dt2 = -strtim(p+1);
else dt2 = strtim(p);
p = argv[(int)dt1];
if (*p == '-') dt1 = strtim(p+1);
else dt1 = -strtim(p);
if (dt1 > dt2) {
WFDB_Time temp = dt1;
dt1 = dt2; dt2 = temp;
}
}
if (dt1 == dt2) dt2++;
if (iannsettime(from) < 0) exit(2);
while (getann(0, &annot) == 0 && namp < nmax &&
(to == 0L || annot.time < to)) {
if (map1(annot.anntyp) == NORMAL) {
if (getptp(annot.time) < 0) break;
if (vflag || qflag) printamp(annot.time);
namp++;
}
}
}
else { /* windows are consecutive nonoverlapping segments */
if (dt1 > 0L) {
(void)fprintf(stderr,
"%s: -d option must be used with -a;\n", pname);
(void)fprintf(stderr, " -d option is being ignored\n");
}
if (dtw == 0L || (dtw = strtim(argv[(int)dtw])) <= 0L)
dtw = strtim("1");
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 || qflag) printamp(t);
}
}
/* 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");
}
exit(0);
}
int getrms(WFDB_Time t)
{
int i, v;
WFDB_Time tt;
if (getvec(vv) < nsig) return (-1);
for (i = 0; i < nsig; i++) {
vmean[i] = v0[i] = vv[i];
vsum[i] = 0.0;
}
for (tt = 1; tt < dtw; tt++) {
if (getvec(vv) < nsig) return (-1);
for (i = 0; i < nsig; i++)
vmean[i] += vv[i];
}
if (isigsettime(t) < 0 || getvec(vv) < nsig) return (-1);
/* The quantity vv[i] - v0[i] is normally zero, but may be non-zero if
the signals are stored in difference format (since isigsettime may
introduce a DC error in this case). In the calculation of vmean[i]
below, this quantity is used to correct any such error. */
for (i = 0; i < nsig; i++) {
vmean[i] = vmean[i]/dtw + vv[i] - v0[i];
v = vv[i] - vmean[i];
vsum[i] += (double)v*v;
}
for (tt = 1; tt < dtw; tt++) {
if (getvec(vv) < nsig) return (-1);
for (i = 0; i < nsig; i++) {
v = vv[i] - vmean[i];
vsum[i] += (double)v*v;
}
}
for (i = 0; i < nsig; i++)
amp[i][namp] = sqrt(vsum[i]/dtw);
return (0);
}
int getptp(WFDB_Time t)
{
int i;
WFDB_Time tt;
if (isigsettime(t + dt1) < 0) return (-1);
if (getvec(vmin) < nsig) return (-1);
for (i = 0; i < nsig; i++)
vmax[i] = vmin[i];
for (tt = dt1; tt < dt2; tt++) {
if (getvec(vv) < nsig) return (-1);
for (i = 0; i < nsig; i++) {
if (vv[i] < vmin[i]) vmin[i] = vv[i];
else if (vv[i] > vmax[i]) vmax[i] = vv[i];
}
}
for (i = 0; i < nsig; i++)
amp[i][namp] = vmax[i] - vmin[i];
return (0);
}
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("%"WFDB_Pd_TIME, 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(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 [OPTIONS ...]\n",
"where OPTIONS may include any of:",
" -a ANN specify annotator, measure amplitudes near QRS annotations",
" -d DT1 DT2 set measurement window relative to QRS annotations",
" defaults: DT1 = 0.05 (seconds before annotation);",
" DT2 = 0.05 (seconds after annotation)",
" -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: 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 and trimmed means",
" -w DTW set RMS amplitude measurement window",
" default: DTW = 1 (second)",
NULL
};
void help(void)
{
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]);
}