This is wpg, produced by makeinfo version 4.0b from wpg.tex.

INFO-DIR-SECTION Libraries
START-INFO-DIR-ENTRY
* WFDB library: (wpg).		The Waveform Database library.
END-INFO-DIR-ENTRY


File: wpg,  Node: Example 1,  Next: Example 2,  Prev: Examples,  Up: Examples

Example 1:  An Annotation Filter
================================

   The following program copies an annotation file, changing all QRS
annotations to `NORMAL' and deleting all non-QRS annotations.

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3  #include <wfdb/ecgmap.h>
      4
      5  main()
      6  {
      7      WFDB_Anninfo an[2];
      8      char record[8], iann[10], oann[10];
      9      WFDB_Annotation annot;
     10
     11      printf("Type record name: ");
     12      fgets(record, 8, stdin); record[strlen(record)-1] = '\0';
     13      printf("Type input annotator name: ");
     14      fgets(iann, 10, stdin); iann[strlen(iann)-1] = '\0';
     15      printf("Type output annotator name: ");
     16      fgets(oann, 10, stdin); oann[strlen(oann)-1] = '\0';
     17      an[0].name = iann; an[0].stat = WFDB_READ;
     18      an[1].name = oann; an[1].stat = WFDB_WRITE;
     19      if (annopen(record, an, 2) < 0) exit(1);
     20      while (getann(0, &annot) == 0)
     21          if (isqrs(annot.anntyp)) {
     22              annot.anntyp = NORMAL;
     23              if (putann(0, &annot) < 0) break;
     24          }
     25      wfdbquit();
     26  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example1.c'
for a copy of this program.)

*Notes:*

_Line 2:_
     All programs that use the WFDB library must include
     `<wfdb/wfdb.h>'.

_Line 3:_
     The `#include' statement makes available not only the mapping
     macros, one of which will be used in line 21, but also the
     annotation code symbols in `<wfdb/ecgcodes.h>', one of which will
     be needed in line 22.

_Line 7:_
     Since there will be two annotators (one each for input and output),
     the array of `WFDB_Anninfo' objects has two members.

_Line 9:_
     This structure will be filled in by `getann', modified, and passed
     to `putann' for output.

_Lines 11-16:_
     The record name and the annotator names are filled into the
     character arrays.  The code in lines 12, 14, and 16 illustrates a
     C idiom for reading a string of limited length; the second
     statement in each of these lines replaces the trailing newline
     character (which `fgets' copies into the string) with a null.
     String arguments to WFDB library functions should not include
     newline characters.

_Lines 17-18:_
     Pointers to the character arrays (strings) containing the annotator
     names are filled into the `name' fields of the array of
     `WFDB_Anninfo' objects. Note that the `name' fields are only
     pointers and do not contain storage for the strings themselves.
     If this is not clear to you, review the discussion of pointers and
     arrays in `K&R', pp.  97-100.  The input annotator is to be read,
     the output annotator is to be written.  `WFDB_READ' and
     `WFDB_WRITE' are defined in `<wfdb/wfdb.h>'.

_Line 19:_
     Note that the first and second arguments of `annopen' are the
     names of the respective arrays; thus `annopen' receives pointers
     rather than values in its argument list.

_Line 20:_
     An annotation is read from annotator 0 into `annot'.  The `&' is
     necessary since `getann' requires a pointer to the structure in
     order to be able to modify its contents.  When `getann' returns a
     negative value, no more annotations remain to be read and the loop
     ends.

_Line 21:_
     The macro `isqrs' is defined in `<wfdb/ecgmap.h>'; `isqrs(X)' is
     true if X is an annotation code that denotes a QRS complex, false
     if X is not a QRS annotation code.

_Line 22:_
     `NORMAL' is defined in `<wfdb/ecgcodes.h>'.

_Line 23:_
     The call to `putann' now writes the modified annotation in the
     output annotator 0 file.  As for `getann', a pointer to `annot'
     must be passed using the `&' operator.

_Line 25:_
     All files are closed prior to exiting.  This is mandatory since the
     program creates an output file with `putann'.


File: wpg,  Node: Example 2,  Next: Example 3,  Prev: Example 1,  Up: Examples

Example 2: An Annotation Translator
===================================

   This program translates the `atr' annotations for the record named
in its argument into an AHA-format annotation file with the annotator
name `aha'.

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3
      4  main(argc, argv)
      5  int argc;
      6  char *argv[];
      7  {
      8      WFDB_Anninfo an[2];
      9      WFDB_Annotation annot;
     10
     11      if (argc < 2) {
     12          fprintf(stderr, "usage: %s record\n", argv[0]);
     13          exit(1);
     14      }
     15      an[0].name = "atr"; an[0].stat = WFDB_READ;
     16      an[1].name = "aha"; an[1].stat = WFDB_AHA_WRITE;
     17      if (annopen(argv[1], an, 2) < 0) exit(2);
     18      while (getann(0, &annot) == 0 && putann(0, &annot) == 0)
     19          ;
     20      wfdbquit();
     21      exit(0);
     22  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example2.c'
for a copy of this program.)

*Notes:*

_Lines 4-6:_
     If this doesn't look familiar, see `K&R', pp. 114-115.

_Lines 11-14:_
     This is the standard idiom for producing those cryptic error
     messages for which Unix programs are notorious; `argv[0]' is the
     name by which the program was invoked.

_Lines 15-16:_
     These lines set up the annotator information.  Input annotator 0
     is the `atr' annotation file, and output annotator 0 will be
     written in AHA format.

_Line 17:_
     If we can't read the input or write the output, quit with an error
     message from `annopen'.

_Line 18:_
     Here's where the work is done.  The format translation is handled
     entirely by `getann' and `putann'.  The loop ends normally when
     `getann' reaches the end of the input file, or prematurely if
     there is a read or write error.

_Line 21:_
     Since we have carefully defined non-zero exit codes for the various
     errors that this program might encounter, we also define this
     successful exit here.  If this program is run as part of a Unix
     shell script, the exit codes are accessible to the shell, which
     can determine what to do next as a result.  If this line were
     omitted (as in example 1), the exit code would be undefined.


File: wpg,  Node: Example 3,  Next: Example 4,  Prev: Example 2,  Up: Examples

Example 3: An Annotation Printer
================================

   This program prints annotations in readable form.  Its first
argument is an annotator name, and its second argument is a record name.

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3
      4  main(argc, argv)
      5  int argc;
      6  char *argv[];
      7  {
      8      WFDB_Anninfo a;
      9      WFDB_Annotation annot;
     10
     11      if (argc < 3) {
     12          fprintf(stderr, "usage: %s annotator record\n", argv[0]);
     13          exit(1);
     14      }
     15      a.name = argv[1]; a.stat = WFDB_READ;
     16      (void)sampfreq(argv[2]);
     17      if (annopen(argv[2], &a, 1) < 0) exit(2);
     18      while (getann(0, &annot) == 0)
     19          printf("%s (%ld) %s %d %d %d %s\n",
     20                 timstr(-(annot.time)),
     21                 annot.time,
     22                 annstr(annot.anntyp),
     23                 annot.subtyp, annot.chan, annot.num,
     24                 (annot.aux != NULL && *annot.aux > 0) ?
     25                  annot.aux+1 : "");
     26      exit(0);
     27  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example3.c'
for a copy of this program.)

*Notes:*
_Line 16:_
     The invocation of `sampfreq' here sets the internal variables
     needed by `timstr' below.

_Line 20:_
     This line gives the annotation time as a time of day.  If the base
     time is omitted in the header file, or if we used
     `timstr(annot.time)' instead, we would obtain the elapsed time from
     the beginning of the record.

_Lines 24-25:_
     This expression evaluates to an empty string unless the `aux'
     string is non-empty.  It makes the assumption that `aux' is a
     printable ASCII string; the printable part follows the length byte.


File: wpg,  Node: Example 4,  Next: Example 5,  Prev: Example 3,  Up: Examples

Example 4: Generating an R-R Interval Histogram
===============================================

   This program reads an annotation file, determines the intervals
between beat annotations (assumed to be the R-R intervals), and
accumulates a histogram of them.

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3  #include <wfdb/ecgmap.h>
      4
      5  main(argc, argv)
      6  int argc;
      7  char *argv[];
      8  {
      9      int rr, *rrhist, rrmax;
     10      long t;
     11      WFDB_Anninfo a;
     12      WFDB_Annotation annot;
     13      void *calloc();
     14
     15      if (argc < 3) {
     16          fprintf(stderr, "usage: %s annotator record\n", argv[0]);
     17          exit(1);
     18      }
     19      a.name = argv[1]; a.stat = WFDB_READ;
     20      if (annopen(argv[2], &a, 1) < 0) exit(2);
     21      if ((rrmax = (int)(3*sampfreq(argv[2]))) <= 0) exit(3);
     22      if ((rrhist = (int *)calloc(rrmax+1, sizeof(int))) == NULL) {
     23          fprintf(stderr, "%s: insufficient memory\n", argv[0]);
     24          exit(4);
     25      }
     26      while (getann(0, &annot) == 0 && !isqrs(annot.anntyp))
     27          ;
     28      t = annot.time;
     29      while (getann(0, &annot) == 0)
     30          if (isqrs(annot.anntyp)) {
     31              if ((rr = annot.time - t) > rrmax) rr = rrmax;
     32              rrhist[rr]++;
     33              t = annot.time;
     34          }
     35      for (rr = 1; rr < rrmax; rr++)
     36          printf("%4d %s\n", rrhist[rr], mstimstr((long)rr));
     37      printf("%4d %s (or longer)\n", rrhist[rr], mstimstr((long)rr));
     38      exit(0);
     39  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example4.c'
for a copy of this program.)

*Notes:*
_Lines 21-25:_
     Here we allocate storage for the histogram.  The value returned by
     `sampfreq', if positive, specifies the number of sample intervals
     per second; we will allocate 3 seconds' worth of bins, initialized
     to zero.  See `K&R', page 167, for a description of `calloc'.

_Lines 26-28:_
     This code sets `t' to the time of the first annotated beat in the
     record.

_Lines 29-34:_
     Here we read the remainder of the annotations, skipping any
     non-beat annotations.  The difference between the values of
     `annot.time' for consecutive beat annotations defines an R-R
     interval (`rr').  Each possible value of `rr' up to `rrmax' is
     assigned a bin in `rrhist'.  Intervals longer than 3 seconds
     (`rrmax') are counted in the bin corresponding to `rr' = `rrmax'.

_Lines 35-37:_
     The histogram is printed as a two-column table, with the number of
     intervals in the first column and the length of the interval (with
     millisecond resolution) in the second column. (What happens if
     `rr' starts at 0 rather than 1 in line 35?)


File: wpg,  Node: Example 5,  Next: Example 6,  Prev: Example 4,  Up: Examples

Example 5: Reading Signal Specifications
========================================

   This program reads the signal specifications of the record named as
its argument:

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3
      4  main(argc, argv)
      5  int argc;
      6  char *argv[];
      7  {
      8      WFDB_Siginfo *s;
      9      int i, nsig;
     10
     11      if (argc < 2) {
     12          fprintf(stderr, "usage: %s record\n", argv[0]);
     13          exit(1);
     14      }
     15      nsig = isigopen(argv[1], NULL, 0);
     16      if (nsig < 1) exit(2);
     17      s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo));
     18      if (s == NULL) {
     19          fprintf(stderr, "insufficient memory\n");
     20	        exit(3);
     21      }
     22      if (isigopen(argv[1], s, nsig) != nsig) exit(2);
     23      printf("Record %s\n", argv[1]);
     24      printf("Starting time: %s\n", timstr(0L));
     25      printf("Sampling frequency: %g Hz\n", sampfreq(argv[1]));
     26      printf("%d signals\n", nsig);
     27      for (i = 0; i < nsig; i++) {
     28          printf("Group %d, Signal %d:\n", s[i].group, i);
     29          printf(" File: %s\n", s[i].fname);
     30          printf(" Description: %s\n", s[i].desc);
     31          printf(" Gain: ");
     32          if (s[i].gain == 0.)
     33              printf("uncalibrated; assume %g", WFDB_DEFGAIN);
     34          else printf("%g", s[i].gain);
     35          printf(" adu/%s\n", s[i].units ? s[i].units : "mV");
     36          printf(" Initial value: %d\n", s[i].initval);
     37          printf(" Storage format: %d\n", s[i].fmt);
     38          printf(" I/O: ");
     39          if (s[i].bsize == 0) printf("can be unbuffered\n");
     40          else printf("%d-byte blocks\n", s[i].bsize);
     41          printf(" ADC resolution: %d bits\n", s[i].adcres);
     42          printf(" ADC zero: %d\n", s[i].adczero);
     43          if (s[i].nsamp > 0L) {
     44              printf(" Length: %s (%ld sample intervals)\n",
     45                     timstr(s[i].nsamp), s[i].nsamp);
     46              printf(" Checksum: %d\n", s[i].cksum);
     47          }
     48          else printf(" Length undefined\n");
     49      }
     50      exit(0);
     51  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example5.c'
for a copy of this program.)

*Notes:*

_Line 15:_
     The command-line argument, `argv[1]', is the record name.  The
     number of signals listed in the header file for the record is
     returned by `isigopen' as `nsig'.  If `nsig' < 1, `isigopen' will
     print an error message; in this case the program can't do anything
     useful, so it exits.

_Line 17:_
     We allocate `nsig' signal information (`WFDB_Siginfo') objects.

_Line 22:_
     On the second invocation of `isigopen', we pass the pointer to the
     signal information objects and the number of signals we expect to
     open.  `isigopen' returns the number of signals it is able to
     open;  if any of those named in the header file are unreadable,
     the return value will not match `nsig', and the program exits.

_Line 24:_
     Invoking `timstr' with an argument of zero (here written `0L' to
     emphasize to the compiler that the argument is a `long' integer)
     will obtain the starting time of the record.  If no starting time
     is defined, `timstr' will return "`0:00:00'".

_Lines 31-34:_
     Notice how a zero value for `gain' is interpreted.

_Line 35:_
     If the `units' field is NULL, the physical units are assumed to be
     millivolts ("mV").

_Lines 38-40:_
     If `bsize' is zero, I/O can be performed in blocks of any
     reasonable size;  otherwise it must be performed in blocks of
     exactly the specified `bsize'.

_Lines 43-48:_
     If the length of the record is defined, it is printed in both
     hours, minutes, and seconds, and in sample intervals.  Since the
     argument of `timstr' in line 39 is positive, it is interpreted as
     a time interval.  The checksum is defined only if the record
     length is defined.


File: wpg,  Node: Example 6,  Next: Example 7,  Prev: Example 5,  Up: Examples

Example 6: A Differentiator
===========================

   The program below inverts and differentiates the signals read by
`getvec' and writes the results with `putvec'.  The output is readable
as record `dif'.  A wide variety of simple digital filters can be
modelled on this example; *note Example 7::, for a more general
approach.

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3
      4  main(argc, argv)
      5  int argc;
      6  char *argv[];
      7  {
      8      WFDB_Siginfo *s;
      9      int i, nsig, nsamp=1000;
     10      WFDB_Sample *vin, *vout;
     11
     12      if (argc < 2) {
     13          fprintf(stderr, "usage: %s record\n", argv[0]); exit(1);
     14      }
     15      if ((nsig = isigopen(argv[1], NULL, 0)) <= 0) exit(2);
     16      s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo));
     17      vin = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample));
     18      vout = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample));
     19      if (s == NULL || vin == NULL || vout == NULL) {
     20          fprintf(stderr, "insufficient memory\n");
     21          exit(3);
     22      }
     23      if (isigopen(argv[1], s, nsig) != nsig) exit(2);
     24      if (osigopen("8l", s, nsig) <= 0) exit(3);
     25      while (nsamp-- > 0 && getvec(vin) > 0) {
     26          for (i = 0; i < nsig; i++)
     27              vout[i] -= vin[i];
     28          if (putvec(vout) < 0) break;
     29          for (i = 0; i < nsig; i++)
     30              vout[i] = vin[i];
     31      }
     32      (void)newheader("dif");
     33      wfdbquit();
     34      exit(0);
     35  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example6.c'
for a copy of this program.)

*Notes:*

_Line 24:_
     Here we attempt to open as many output signals as there are input
     signals; if we cannot do so, the program exits after `osigopen'
     prints an error message.

_Line 25:_
     The main loop of the program begins here.  If 1000 samples can be
     read from each signal, the loop will end normally; if `getvec'
     fails before 1000 samples have been read, the loop ends
     prematurely.

_Lines 26-27:_
     For each signal, we compute the negated first difference by
     subtracting the new sample from the previous sample.

_Line 28:_
     One sample of each output signal is written here.

_Lines 29-30:_
     The new input samples are copied into the output sample vector in
     preparation for the next iteration.

_Line 32:_
     This step is optional.  It creates a header file for a new record
     to be called `dif', which we can then open with another program if
     we want to read the signals that this program has written.  Since
     the RECORD argument for `osigopen' was `8l', we can also read
     these files using record `8l'; one reason for making a new `hea'
     file here is that the `hea' file for `8l' may not necessarily
     indicate the proper sampling frequency for these signals.

_Line 33:_
     Since the program writes output signals, it must invoke `wfdbquit'
     to close the files properly.


File: wpg,  Node: Example 7,  Next: Example 8,  Prev: Example 6,  Up: Examples

Example 7:  A General-Purpose FIR Filter
========================================

   This program illustrates the use of `sample' to obtain random access
to signals, a technique that is particularly useful for implementing
digital filters.  The first argument is the record name, the second and
third arguments are the start time and the duration of the segment to
be filtered, and the rest of the arguments are finite-impulse-response
(FIR) filter coefficients.  For example, if this program were compiled
into an executable program called `filter', it might be used by
     filter 100 5:0 20 .2 .2 .2 .2 .2

which would apply a five-point moving average (rectangular window)
filter to 20 seconds of record `100', beginning 5 minutes into the
record.  The output of the program is readable as record `out', for
which a header file is created in the current directory.

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3
      4  main(argc, argv)
      5  int argc;
      6  char *argv[];
      7  {
      8      double *c, one = 1.0, vv, atof();
      9      int i, j, nc = argc - 4, nsig;
     10      long nsamp, t;
     11      static WFDB_Sample *v;
     12      static WFDB_Siginfo *s;
     13
     14      if (argc < 4) {
     15          fprintf(stderr,
     16            "usage: %s record start duration [ coefficients ... ]\n",
     17                  argv[0]);
     18          exit(1);
     19      }
     20      if (nc < 1) {
     21          nc = 1; c = &one;
     22      }
     23      else if ((c = (double *)calloc(nc, sizeof(double))) == NULL) {
     24          fprintf(stderr, "%s: too many coefficients\n", argv[0]);
     25          exit(2);
     26      }
     27      for (i = 0; i < nc; i++)
     28          c[i] = atof(argv[i+4]);
     29      if ((nsig = isigopen(argv[1], NULL, 0)) < 1)
     30          exit(3);
     31      s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo));
     32      v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample));
     33      if (s == NULL || v == NULL) {
     34          fprintf(stderr, "insufficient memory\n");
     35          exit(3);
     36      }
     37      if (isigopen(argv[1], s, nsig) != nsig)
     38          exit(3);
     39      if (isigsettime(strtim(argv[2])) < 0)
     40          exit(4);
     41      if ((nsamp = strtim(argv[3])) < 1) {
     42          fprintf(stderr, "%s: inappropriate value for duration\n",
     43                  argv[0]);
     44          exit(5);
     45      }
     46      if (osigopen("16l", s, nsig) != nsig)
     47          exit(6);
     48
     49      (void)sample(0, 0L);
     50      for (t = 0; t < nsamp && sample_valid(); t++) {
     51          for (j = 0; j < nsig; j++) {
     52              for (i = 0, vv = 0.; i < nc; i++)
     53                  if (c[i] != 0.) vv += c[i]*sample(j, t+i);
     54              v[j] = (int)vv;
     55          }
     56          if (putvec(v) < 0) break;
     57      }
     58
     59      (void)newheader("out");
     60      wfdbquit();
     61      exit(0);
     62  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example7.c'
for a copy of this program.)

*Notes:*

_Lines 20-22:_
     If no coefficients are provided on the command line, the program
     will simply copy the selected segment of the input signals.

_Lines 23-28:_
     If there are more coefficients than there are samples in the
     circular buffer, or if memory cannot be allocated for the
     coefficient vector, the program cannot work properly, so it exits
     with an error message.  In lines 27 and 28, the ASCII strings that
     represent the coefficients are converted to `double' format and
     stored in the coefficient vector.

_Lines 29-40:_
     The record name is `argv[1]', and the start time is `argv[2]'; if
     the record can't be opened, or the start time is inappropriate, the
     program exits.  See the previous example for details on how
     `isigopen' is used.

_Lines 41-45:_
     The DURATION argument should be a time interval in HH:MM:SS
     format; `strtim' converts it to the appropriate number of samples.

_Lines 46-47:_
     The output signals will be written to files in the current
     directory according to the specifications for record `16l' (*note
     Piped and Local Records::).  If we can't write as many output
     signals as there are input signals, the program exits.

_Line 49:_
     Here, `signal' is invoked only for its side effect;  assuming that
     any samples can be read from the specified record, `sample(0, 0L)'
     returns a valid sample, so that the value returned by
     `sample_valid()' is true (1).

_Lines 50-57:_
     Here's where the work is done.  The outer loop is executed once per
     sample vector, the middle loop once per signal, and the inner loop
     once per coefficient.  In line 53, we retrieve an input sample,
     multiply it by a filter coefficient, and add it to a running sum.
     The sum (`vv') is initialized to zero in line 52 before we begin,
     and is converted to an `int' in line 54 when we are finished.  Once
     an entire sample vector has been filtered, it is written out in
     line 56.  The entire process is repeated up to `nsamp' times, or
     until we run out of input samples.

_Line 59:_
     The program creates a header file for record `out', using the
     signal specifications from record `16l' and the sampling frequency
     from the input record.


File: wpg,  Node: Example 8,  Next: Example 9,  Prev: Example 7,  Up: Examples

Example 8:  Creating a New Database Record
==========================================

   This program creates a new record from scratch.  It asks the user for
information about the signals to be sampled, then records them, and
finally creates a `hea' file for the new record.  Details of data
acquisition are hardware-dependent and are not shown here.

       1  #include <stdio.h>
       2  #include <wfdb/wfdb.h>
       3
       4  main()
       5  {
       6      char answer[32], record[8], directory[32];
       7      int i, nsig = 0;
       8      long nsamp, t;
       9      double freq = 0.;
      10      char **filename, **description, **units;
      11      WFDB_Sample *v;
      12      WFDB_Siginfo *s;
      13
      14      do {
      15          printf("Choose a record name [up to 6 characters]: ");
      16          fgets(record, 8, stdin); record[strlen(record)-1] = '\0';
      17      } while (newheader(record) < 0);
      18      do {
      19          printf("Number of signals to be recorded [>0]: ");
      20          fgets(answer, 32, stdin); sscanf(answer, "%d", &nsig);
      21      } while (nsig < 1);
      22      s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo));
      23      v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample));
      24      filename = (char **)malloc(nsig * sizeof(char *));
      25      description = (char **)malloc(nsig * sizeof(char *));
      26      units = (char **)malloc(nsig * sizeof(char *));
      27      if (s == NULL || v == NULL || filename == NULL ||
      28          description == NULL || units == NULL) {
      29          fprintf(stderr, "insufficient memory\n");
      30          exit(1);
      31      }
      32      for (i = 0; i < nsig; i++) {
      33          if ((filename[i] = (char *)malloc(32)) == NULL ||
      34              (description[i] = (char *)malloc(32)) == NULL ||
      35              (units[i] = (char *)malloc(32)) == NULL) {
      36              fprintf(stderr, "insufficient memory\n");
      37              exit(1);
      38          }
      39      }
      40      do {
      41          printf("Sampling frequency [Hz per signal, > 0]: ");
      42          fgets(answer, 32, stdin); sscanf(answer, "%lf", &freq);
      43      } while (setsampfreq(freq) < 0);
      44      do {
      45          printf("Length of record (H:M:S): ");
      46          fgets(answer, 32, stdin);
      47      } while ((nsamp = strtim(answer)) < 1L);
      48      printf("Directory for signal files [up to 30 characters]: ");
      49      fgets(directory, 32, stdin);
      50      directory[strlen(directory)-1] = '\0';
      51      printf("Save signals in difference format? [y/n]: ");
      52      fgets(answer, 32, stdin);
      53      s[0].fmt = (answer[0] == 'y') ? 8 : 16;
      54      printf("Save all signals in one file? [y/n]: ");
      55      fgets(answer, 32, stdin);
      56      if (answer[0] == 'y') {
      57          sprintf(filename[0], "%s/d.%s", directory, record);
      58          for (i = 0; i < nsig; i++) {
      59               s[i].fname = filename[0];
      60               s[i].group = 0;
      61          }
      62      }
      63      else {
      64          for (i = 0; i < nsig; i++) {
      65               sprintf(filename[i], "%s/d%d.%s", directory,i,record);
      66               s[i].fname = filename[i];
      67               s[i].group = i;
      68          }
      69      }
      70      for (i = 0; i < nsig; i++) {
      71          s[i].fmt = s[0].fmt; s[i].bsize = 0;
      72          printf("Signal %d description [up to 30 characters]: ", i);
      73          fgets(description[i], 32, stdin);
      74          description[i][strlen(description[i])-1] = '\0';
      75          s[i].desc = description[i];
      76          printf("Signal %d units [up to 20 characters]: ", i);
      77          fgets(units[i], 22, stdin);
      78          units[i][strlen(units[i])-1] = '\0';
      79          s[i].units = (*units[i]) ? units[i] : "mV";
      80          do {
      81              printf(" Signal %d gain [adu/%s]: ", i, s[i].units);
      82              fgets(answer, 32, stdin);
      83              sscanf(answer, "%lf", &s[i].gain);
      84          } while (s[i].gain < 0.);
      85          do {
      86              printf(" Signal %d ADC resolution in bits [8-16]: ",i);
      87              fgets(answer, 32, stdin);
      88              sscanf(answer, "%d", &s[i].adcres);
      89          } while (s[i].adcres < 8 || s[i].adcres > 16);
      90          printf(" Signal %d ADC zero level [adu]: ", i);
      91          fgets(answer, 32, stdin);
      92          sscanf(answer, "%d", &s[i].adczero);
      93      }
      94      if (osigfopen(s, nsig) < nsig) exit(1);
      95      printf("To begin sampling, press RETURN;  to specify a\n");
      96      printf(" start time other than the current time, enter\n");
      97      printf(" it in H:M:S format before pressing RETURN: ");
      98      fgets(answer, 32, stdin); answer[strlen(answer)-1] = '\0';
      99      setbasetime(answer);
     100
     101      adinit();
     102
     103      for (t = 0; t < nsamp; t++) {
     104          for (i = 0; i < nsig; i++)
     105              v[i] = adget(i);
     106          if (putvec(v) < 0) break;
     107      }
     108
     109      adquit();
     110
     111      (void)newheader(record);
     112      wfdbquit();
     113      exit(0);
     114  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example8.c'
for a copy of this program.)

*Notes:*
_Lines 14-17:_
     This code uses `newheader' to determine if a legal record name was
     entered (since we don't want to digitize the signals and then find
     out that we can't create the header file).  The header file
     created in line 17 will be overwritten in line 111.

_Lines 57-62:_
     This code generates a file name and initializes the `fname' and
     `group' fields of the array of `WFDB_Siginfo' objects so that all
     signals will be saved in one file.

_Lines 63-69:_
     This code generates unique file names and groups for each signal.

_Lines 70-93:_
     Here, information specific to individual signals is gathered.

_Line 94:_
     If the signal files can't be created, this program can do nothing
     else useful, so it quits with an error message from `osigfopen'.

_Lines 95-99:_
     Just before sampling begins, we set the base time.  Note that an
     empty string argument for `setbasetime' gives us the current time
     read from the system clock.

_Line 101:_
     What goes here will be hardware dependent.  Typically it is
     necessary to set up a timer for the ADC, allocate DMA buffers,
     specify interrupt vectors, and initiate the first conversion(s).
     This program might also be used to create a database record from
     prerecorded data in a non-supported format; in this case, we might
     simply open the file containing the prerecorded data here.

_Lines 103-107:_
     Here is where the samples are acquired (using hardware-dependent
     code not shown here) and recorded (using `putvec').  At high
     sampling frequencies, it is critical to make this code as fast as
     possible.  It could be made faster by judicious use of `register'
     and pointer variables if necessary.  In an extreme case the entire
     loop, possibly including `putvec' itself, can be written in
     assembly language; since it is only a small fraction of the entire
     program, doing so is within reason.

_Line 109:_
     This final piece of hardware-dependent code typically clears the
     ADC control register, stops the timer, and frees any system
     resources such as DMA channels or interrupts.

_Line 111:_
     All of the information needed to generate the header file has been
     stored in WFDB library internal data structures by `osigfopen' and
     `putvec';  we call `newheader' here (before `wfdbquit') to create
     the new `hea' file.

_Line 112:_
     It is still necessary to use `wfdbquit' to close the signal
     file(s), even after calling `newheader'.  (In fact, it would be
     possible, though not likely to be useful, to record more samples
     and to generate another header file before calling `wfdbquit'.)


File: wpg,  Node: Example 9,  Next: Example 10,  Prev: Example 8,  Up: Examples

Example 9: A Signal Averager
============================

   The following program is considerably more complex than the previous
examples in this chapter.  It reads an annotation file (for which the
annotator name is specified in its first argument, and the record name
in the second argument) and selects beats of a specified type to be
averaged.  The program selects segments of the signals that are within
50 milliseconds of the time of the specified beat annotations,
subtracts a baseline estimate from each sample, and calculates an
average waveform (by default, the average normal QRS complex).

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3  #include <wfdb/ecgmap.h>
      4
      5  main(argc, argv)
      6  int argc;
      7  char *argv[];
      8  {
      9      int btype, i, j, nbeats = 0, nsig, hwindow, window;
     10      long stoptime = 0L, **sum;
     11      WFDB_Anninfo a;
     12      WFDB_Annotation annot;
     13      WFDB_Sample *v, *vb;
     14      WFDB_Siginfo *s;
     15      void *calloc();
     16
     17      if (argc < 3) {
     18          fprintf(stderr,
     19                  "usage: %s annotator record [beat-type from to]\n",
     20                  argv[0]);
     21          exit(1);
     22      }
     23      a.name = argv[1]; a.stat = WFDB_READ;
     24      if ((nsig = isigopen(argv[2], NULL, 0)) < 1) exit(2);
     25      s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo));
     26      v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample));
     27      vb = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample));
     28      sum = (long **)malloc(nsig * sizeof(long *));
     29      if (s == NULL || v == NULL || vb == NULL || sum == NULL) {
     30          fprintf(stderr, "%s: insufficient memory\n", argv[0]);
     31          exit(2);
     32      }
     33      if (wfdbinit(argv[2], &a, 1, s, nsig) != nsig) exit(3);
     34      hwindow = strtim(".05"); window = 2*hwindow + 1;
     35      for (i = 0; i < nsig; i++)
     36          if ((sum[i]=(long *)calloc(window,sizeof(long))) == NULL) {
     37              fprintf(stderr, "%s: insufficient memory\n", argv[0]);
     38              exit(2);
     39          }
     40      btype = (argc > 3) ? strann(argv[3]) : NORMAL;
     41      if (argc > 4) iannsettime(strtim(argv[4]));
     42      if (argc > 5) {
     43          if ((stoptime = strtim(argv[5])) < 0L)
     44              stoptime = -stoptime;
     45          if (s[0].nsamp > 0L && stoptime > s[0].nsamp)
     46              stoptime = s[0].nsamp;
     47      }
     48      else stoptime = s[0].nsamp;
     49      if (stoptime > 0L) stoptime -= hwindow;
     50      while (getann(0, &annot) == 0 && annot.time < hwindow)
     51          ;
     52      do {
     53          if (annot.anntyp != btype) continue;
     54          isigsettime(annot.time - hwindow - 1);
     55          getvec(vb);
     56          for (j = 0; j < window && getvec(v) > 0; j++)
     57              for (i = 0; i < nsig; i++)
     58                  sum[i][j] += v[i] - vb[i];
     59          nbeats++;
     60      } while (getann(0, &annot) == 0 &&
     61               (stoptime == 0L || annot.time < stoptime));
     62      if (nbeats < 1) {
     63          fprintf(stderr, "%s: no `%s' beats found\n",
     64                  argv[0], annstr(btype));
     65          exit(4);
     66      }
     67      printf("Average of %d `%s' beats:\n", nbeats, annstr(btype));
     68      for (j = 0; j < window; j++)
     69          for (i = 0; i < nsig; i++)
     70              printf("%g%c", (double)sum[i][j]/nbeats,
     71                     (i == nsig-1) ? '\n' : '\t');
     72      exit(0);
     73  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example9.c'
for a copy of this program.)

*Notes:*

_Line 34:_
     The "half-window" is 50 milliseconds wide, and the "window" (the
     duration of a segment to be entered into the average) is one
     sample more than twice that amount (i.e., 50 milliseconds to
     either side of the fiducial point defined by the annotation).

_Lines 35-39:_
     Here we allocate memory for the `sum' vectors that will be used to
     store the running totals.  See `K&R', page 167, for a description
     of `calloc'.

_Line 40:_
     If a third argument is present on the command line, it is taken as
     an annotation code mnemonic for the desired beat type;  otherwise,
     the program will average `NORMAL' QRS complexes.

_Line 41:_
     If a fourth argument is present on the command line, it is taken
     as the start time; we arrange for the first annotation to be read
     by `getann' to be the first annotation that occurs after the chosen
     start time.

_Lines 42-49:_
     This code similarly determines when the averaging should stop.
     Unless no stop time was specified on the command line and the
     signal length is not defined in the `hea' file for the record,
     `stoptime' will have a positive value in line 49, which makes a
     tiny adjustment so that if a beat annotation occurs within 50
     milliseconds of the end of the averaging period, the beat will not
     be included in the average.

_Lines 50-51:_
     This code addresses the (admittedly unlikely) prospect that the
     first annotation(s) might occur within the first 50 milliseconds
     of the record; any such annotations will be excluded from the
     average.

_Lines 52-61:_
     Here we read annotations (the first is already in `annot' when we
     enter the loop, and subsequent annotations are read in line 60);
     select the desired ones (line 53); skip to the correct spot in the
     signals (line 54; the sample selected there is the one just before
     the beginning of the window); read a sample from each signal (line
     55) into the `vb' vector, which will be used as a crude baseline
     estimate; read `window' samples from each signal (line 56),
     subtracting the baseline from each and adding the result into the
     running totals; update a beat counter (line 59); and check for
     loop termination conditions (line 61).

_Lines 62-71:_
     This is the output section.  If no beats of type `btype' were
     found, obviously no average can be printed; note that the message
     goes to the standard error output, so the user will notice it even
     if the standard output has been redirected to a file.  In the
     usual case, the averages are printed out as a table, with a column
     allocated to each signal.  Note the cast in line 70 (necessary to
     preserve precision), and the trick used in line 71 to print a tab
     after each column but the last in each line.


File: wpg,  Node: Example 10,  Prev: Example 9,  Up: Examples

Example 10: A QRS Detector
==========================

   This program reads a single ECG signal, attempts to detect QRS
complexes, and records their locations in an annotation file.  The
detector algorithm is based on a Pascal program written by W.A.H.
Engelse and C. Zeelenberg, "A single scan algorithm for QRS-detection
and feature extraction", `Computers in Cardiology' 6:37-42 (1979).

      1  #include <stdio.h>
      2  #include <wfdb/wfdb.h>
      3  #include <wfdb/ecgcodes.h>
      4
      5  #define abs(A)  ((A) >= 0 ? (A) : -(A))
      6
      7  main(argc, argv)
      8  int argc;
      9  char *argv[];
     10  {
     11      int filter, time=0, slopecrit, sign, maxslope=0, nsig, nslope=0,
     12          qtime, maxtime, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9,
     13          ms160, ms200, s2, scmax, scmin = 0;
     14      WFDB_Anninfo a;
     15      WFDB_Annotation annot;
     16      WFDB_Sample *v;
     17      WFDB_Siginfo *s;
     18
     19      if (argc < 2) {
     20          fprintf(stderr, "usage: %s record [threshold]\n", argv[0]);
     21          exit(1);
     22      }
     23      a.name = "qrs"; a.stat = WFDB_WRITE;
     24
     25      if ((nsig = isigopen(argv[1], NULL, 0)) < 1) exit(2);
     26      s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo));
     27      v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample));
     28      if (s == NULL || v == NULL) {
     29          fprintf(stderr, "%s: insufficient memory\n", argv[0]);
     30          exit(2);
     31      }
     32      if (wfdbinit(argv[1], &a, 1, s, nsig) != nsig) exit(2);
     33      if (sampfreq((char *)NULL) < 240. ||
     34          sampfreq((char *)NULL) > 260.)
     35          setifreq(250.);
     36      if (argc > 2) scmin = muvadu(0, atoi(argv[2]));
     37      if (scmin < 1) scmin = muvadu(0, 1000);
     38      slopecrit = scmax = 10 * scmin;
     39      ms160 = strtim("0.16"); ms200 = strtim("0.2"); s2 = strtim("2");
     40      annot.subtyp = annot.chan = annot.num = 0; annot.aux = NULL;
     41      (void)getvec(v);
     42      t9 = t8 = t7 = t6 = t5 = t4 = t3 = t2 = t1 = v[0];
     43
     44      do {
     45          filter = (t0 = v[0]) + 4*t1 + 6*t2 + 4*t3 + t4
     46                  - t5         - 4*t6 - 6*t7 - 4*t8 - t9;
     47          if (time % s2 == 0) {
     48              if (nslope == 0) {
     49                  slopecrit -= slopecrit >> 4;
     50                  if (slopecrit < scmin) slopecrit = scmin;
     51              }
     52              else if (nslope >= 5) {
     53                  slopecrit += slopecrit >> 4;
     54                  if (slopecrit > scmax) slopecrit = scmax;
     55              }
     56          }
     57          if (nslope == 0 && abs(filter) > slopecrit) {
     58              nslope = 1; maxtime = ms160;
     59              sign = (filter > 0) ? 1 : -1;
     60              qtime = time;
     61          }
     62          if (nslope != 0) {
     63              if (filter * sign < -slopecrit) {
     64                  sign = -sign;
     65                  maxtime = (++nslope > 4) ? ms200 : ms160;
     66              }
     67              else if (filter * sign > slopecrit &&
     68                       abs(filter) > maxslope)
     69                  maxslope = abs(filter);
     70              if (maxtime-- < 0) {
     71                  if (2 <= nslope && nslope <= 4) {
     72                      slopecrit += ((maxslope>>2) - slopecrit) >> 3;
     73                      if (slopecrit < scmin) slopecrit = scmin;
     74                      else if (slopecrit > scmax) slopecrit = scmax;
     75                      annot.time = strtim("i") - (time - qtime) - 4;
     76                      annot.anntyp = NORMAL; (void)putann(0, &annot);
     77                      time = 0;
     78                  }
     79                  else if (nslope >= 5) {
     80                      annot.time = strtim("i") - (time - qtime) - 4;
     81                      annot.anntyp = ARFCT; (void)putann(0, &annot);
     82                  }
     83                  nslope = 0;
     84              }
     85          }
     86          t9 = t8; t8 = t7; t7 = t6; t6 = t5; t5 = t4;
     87          t4 = t3; t3 = t2; t2 = t1; t1 = t0; time++;
     88      } while (getvec(v) > 0);
     89
     90      wfdbquit();
     91      exit(0);
     92  }

(See `http://www.physionet.org/physiotools/wfdb/examples/example10.c'
for a copy of this program.)

*Notes:*

_Line 5:_
     A macro that evaluates to the absolute value of its argument.

_Lines 11-12:_
     The names of these variables match those in the original Pascal
     program.

_Lines 33-35:_
     Most of this program is independent of sampling frequency, but the
     filter (lines 45-46) and the threshold are as specified by the
     authors of the original program for human ECGs sampled at 250 Hz
     (e.g., the AHA DB).  If the sampling frequency of the input record
     is significantly different, we use `setifreq' to specify that we
     want `getvec' to give us data resampled at 250 Hz.

_Lines 36-38:_
     The threshold is actually a slope criterion (with units of
     amplitude/time); these lines normalize the threshold with respect
     to the signal gain.  The default value is used unless the user
     supplies an acceptable alternative.  The variables `scmin' and
     `scmax' are lower and upper bounds for the adaptive threshold
     `slopecrit'.

_Lines 41-42:_
     Here we read the first sample and copy it into the variables that
     will be used to store the ten most recent samples.

_Lines 45-46:_
     This FIR filter differentiates and low-pass filters the input
     signal.

_Lines 47-56:_
     Here we adjust the threshold if more than two seconds have elapsed
     since a QRS was detected.  In line 49, `slopecrit' is set to 15/16
     of its previous value if no slopes have been found;  in line 53,
     it is set to 17/16 of its previous value if 5 or more slopes were
     found (suggesting the presence of noise).

_Lines 57-61:_
     If the condition in line 48 is satisfied, we may have found the
     beginning of a QRS complex.  We record that a slope has been
     found, set the timer `maxtime' to 160 msec, and save the sign of
     the slope and the current time relative to the previous beat.

_Lines 62-85:_
     This code is executed once we have found a slope.  Each time the
     filter output crosses the threshold, we record another slope and
     begin looking for a threshold crossing of the opposite sign (lines
     63-66), which must occur within a specified time.  We record the
     maximum absolute value of the filter in `maxslope' (lines 67-69)
     for eventual use in updating the threshold (lines 72-74).  Once a
     sufficient interval has elapsed following the last threshold
     crossing (line 70), if there were between 2 and 4 slopes, we have
     (apparently) found a QRS complex, and the program records a
     `NORMAL' annotation (lines 75-76).  If there were 5 or more
     slopes, the program records an artifact annotation (lines 80-81).
     If only 1 slope was found, it is assumed to represent a baseline
     shift and no output is produced.

_Lines 86-88:_
     At the end of the loop, the samples are shifted through the `tN'
     variables and another sample is read.

