\input texinfo @c -*-texinfo-*- @comment %**start of header (This is for running Texinfo on a region.) @setfilename wpg @settitle WFDB Programmer's Guide @setchapternewpage odd @dircategory Libraries @direntry * WFDB library: (wpg). The Waveform Database library. @end direntry @comment %**end of header (This is for running Texinfo on a region.) @titlepage @sp 5 @center @titlefont{WFDB Programmer's Guide} @sp 4 @center Tenth Edition @center (revised and with additions for WFDB library version VERSION) @center LONGDATE @sp 5 @center George B. Moody @center Harvard-MIT Division of Health Sciences and Technology @page @vskip 0pt plus 1filll Copyright @copyright{} 1980 -- 2014 George B. Moody @sp 2 The most recent versions of the software described in this guide may be downloaded from @uref{http://physionet.org/}. @ifnothtml See @uref{http://physio@-net.org/physio@-tools/@-wpg/} for an HTML version of this guide. @end ifnothtml @sp 2 Permission is granted to make and distribute verbatim copies of this guide provided that the copyright notice and this permission notice are preserved on all copies. Permission is granted to copy and distribute modified versions of this guide under the conditions for verbatim copying and under the conditions that follow in this paragraph. Each copy of the resulting derived work must contain a notice that it is a modified version of this guide. The notice must state which edition of this guide was the source for the derived work, and it must credit the authors of this guide and of the modifications. The entire resulting derived work must be distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this guide into another language, under the above conditions for modified versions. The author would appreciate receiving copies of any modified or translated versions of this guide for reference purposes. @end titlepage @contents @node Top, Overview, (dir), (dir) @ifinfo This guide documents the Waveform Database interface library (the WFDB library). This file contains the text of the Tenth Edition of the @cite{WFDB Programmer's Guide} (LONGDATE), with revisions for release VERSION of the WFDB library. @end ifinfo @menu * Overview:: What is the WFDB library? * Usage:: How to compile and run a program that uses the WFDB library. * Functions:: Call and return syntax of each function, with descriptions and program examples. * Data Types:: Simple and compound types, including annotator, calibration, and signal information structures, and annotation structures. * Annotation Codes:: Table of codes, descriptions of mapping macros. * Database Files:: A description of the standard file types, and notes about reading from nonstandard sources. * Examples:: Annotated example programs. * Exercises:: Test yourself. * Glossary:: So you think you know what `time' is, eh? A guide for the perplexed. * Installation:: Notes on installing the WFDB software package. * WFDB Applications:: Brief descriptions of the WFDB application programs provided with the WFDB software package. * Extensions:: Notes on extending the capabilities of the WFDB library. * Sources:: Where to get the WFDB software package, databases of ECGs and other signals, and related items. * Answers:: Don't read them until you've done the exercises! * Recent Changes:: New material, not included in the most recent printed edition of this guide. Indices * Concept Index:: An item for each concept. * Function and Macro Index:: An item for each WFDB library function and macro. * Copying:: You can make copies of this guide. Here are the terms for doing so. @end menu @node Overview, Usage, Top, Top @comment node-name, next, previous, up @unnumbered Preface @cindex WFDB library @cindex MIT DB @cindex AHA DB This guide documents the Waveform Database interface library (the @dfn{WFDB library}), a package of C-callable functions that provide clean and uniform access to digitized, annotated signals stored in a variety of formats. These functions were originally designed for use with databases of electrocardiograms, including the MIT-BIH Arrhythmia Database (@dfn{MIT DB}) and the AHA Database for the Evaluation of Ventricular Arrhythmia Detectors (@dfn{AHA DB}). In February 1990, the predefined annotation set was expanded to accommodate the needs of the European ST-T Database (@dfn{ESC DB}). The WFDB library is sufficiently general, however, to be useful for dealing with any similar collection of digitized signals, which may or may not be annotated. The WFDB library has evolved to support the development of numerous other databases that include signals such as blood pressure, respiration, oxygen saturation, EEG, as well as ECGs. Among these multi-parameter databases are the MIT-BIH Polysomnographic Database, the MGH/Marquette Foundation Waveform Database, and the MIMIC Database. Thus the WFDB library is considerably more than an @emph{ECG} database interface. This guide describes how to write C-language programs that use databases of ECGs and other signals. A standard set of such programs is included in the WFDB Software Package, and is described in the @cite{WFDB Applications Guide}; other documents describe the databases themselves, and existing programs that use them (@pxref{Sources}, for information about obtaining these and related items). There are a few important concepts that should be well understood before going further. These concepts include @dfn{records}; @dfn{signals}, @dfn{samples}, and @dfn{time}; and @dfn{annotations}. @menu * Concepts 1:: Records (``tapes'') and record names. * Concepts 2:: Signals, samples, and time. * Concepts 3:: Annotations and annotation files. @emph{If this is your first exposure to the WFDB library, study the three nodes above before going on.} * Applications:: Examples of programs based on the WFDB library. * Guide:: What's in this guide, and where. What you need to know to get started. Acknowledgments, where to send your comments, and how to get your very own printed copy of this guide. @end menu @node Concepts 1, Concepts 2, Overview, Overview @unnumberedsec Records @html @end html @cindex record @cindex record name @cindex tape The databases for which the WFDB library was designed consist of a small number of @dfn{records}, each of which is quite large (typically a megabyte or more). Before 1990, database records usually originated as multi-channel analog tape recordings that had been digitized and stored as disk files. For this historical reason, they are sometimes referred to as @dfn{tapes}, although most newly created records are digitally recorded onto disk media. Each record contains a continuous recording from a single subject. A typical application program accesses only a single record, and most (if not all) of the access within the record is sequential. Much less frequently, it may be of interest to compare the contents of several records, or to select sets of records. These databases are therefore qualitatively different from those for which conventional database management software is written. @cindex file names Records are identified by @dfn{record names} of up to 20 characters (the limit is @code{MAXRNL}, defined in @file{}). For example, record names in the MIT DB are three-digit numbers, those in the AHA DB are four-digit numbers, and those in the ESC DB are four-digit numbers prefixed by the letter @samp{e}. You may create database records with names containing letters, digits, and underscores. Case is significant in record names that contain letters, even in environments such as MS-Windows for which case translation is normally performed by the operating system on file names; thus @samp{e0104} is the name of a record found in the ESC DB, whereas @samp{E0104} is not. A record is comprised of several files, which contain signals, annotations, and specifications of signal attributes; each file belonging to a given record normally includes the record name as the first part of its name. A record is an extensible collection of files, which need not all be located in the same directory, or even on the same physical device. Thus it is possible, for example, to create a local disk file of your own annotations for a record read from a web server or a CDROM, and to treat your file as part of the record. @node Concepts 2, Concepts 3, Concepts 1, Overview @unnumberedsec Signals, Samples, and Time @cindex adu @cindex gain @cindex physical units @cindex sample @cindex sampling frequency @cindex sample interval @cindex scales (time and amplitude) @cindex signal @cindex units (ADC) @cindex units (physical) Signals are commonly understood to be functions of time obtained by observation of physical variables. In this guide, a @dfn{signal} is defined more restrictively as a finite sequence of integer @dfn{samples}, usually obtained by digitizing a continuous observed function of time at a fixed @dfn{sampling frequency} expressed in Hz (samples per second). The time interval between any pair of adjacent samples in a given signal is a @dfn{sample interval}; all sample intervals for a given signal are equal. The integer value of each sample is usually interpreted as a voltage, and the units are called analog-to-digital converter units, or @dfn{adu}. The @dfn{gain} defined for each signal specifies how many adus correspond to one @dfn{physical unit} (usually one millivolt, the nominal amplitude of a normal QRS complex on a body-surface ECG lead roughly parallel to the mean cardiac electrical axis). All signals in a given record are usually sampled at the same frequency, but not necessarily at the same gain (@pxref{Multi-Frequency Records}, for exceptions to this rule). MIT DB records are sampled at 360 Hz; AHA and ESC DB records are sampled at 250 Hz. @cindex sample number @cindex time The @dfn{sample number} is an attribute of a sample, defined as the number of samples of the same signal that precede it; thus the sample number of the first sample in each signal is zero. Within this guide, the units of @dfn{time} are sample intervals; hence the ``time'' of a sample is synonymous with its sample number. Samples having the same sample number in different signals of the same record are treated as simultaneous. In truth, they are usually not @emph{precisely} simultaneous, since most multi-channel digitizers sample signals in ``round-robin'' fashion. If this subtlety makes a difference to you, you should be prepared to compensate for inter-signal sampling skew in your programs. @node Concepts 3, Applications, Concepts 2, Overview @unnumberedsec Annotations @cindex annotation @cindex annotator @cindex @code{atr} @cindex beat label @cindex label (beat) @cindex QRS label @cindex reference annotations MIT DB records are each 30 minutes in duration, and are @dfn{annotated} throughout; by this we mean that each beat (QRS complex) is described by a label called an @dfn{annotation}. Typically an @dfn{annotation file} for an MIT DB record contains about 2000 beat annotations, and smaller numbers of rhythm and signal quality annotations. AHA DB records are either 35 minutes or 3 hours in duration, and only the last 30 minutes of each record are annotated. ESC DB records are each 2 hours long, and are annotated throughout. The ``time'' of an annotation is simply the sample number of the sample with which the annotation is associated. Annotations may be associated with a single signal, if desired. Like samples in signals, annotations are kept in time and signal order in annotation files (but @pxref{Annotation Order}, for exceptions to this rule). No more than one annotation in a given annotation file may be associated with any given sample of any given signal. There may be many annotation files associated with the same record, however; they are distinguished by @dfn{annotator names}. The annotator name @file{atr} is reserved to identify @dfn{reference annotation files} supplied by the developers of the databases to document correct beat labels. You may use other annotator names (which may contain letters, digits and underscores, as for record names) to identify annotation files that you create. You may wish to adopt the convention that the annotator name is the name of the file's creator (a program or a person). Annotations are visible to the WFDB library user as C structures, the fields of which specify time, beat type, and several user-definable variables. The WFDB library performs efficient conversions between these structures and a compact bit-packed representation used for storage of annotations in annotation files. @node Applications, Guide, Concepts 3, Overview @unnumberedsec Applications Some typical uses of the WFDB library are these: @cindex annotation editor @cindex waveform editor @itemize @bullet @item A @emph{waveform editor}, such as @code{wave} (see the @uref{http://@-physionet.@-org/@-physio@-tools/@-wug/, @cite{WAVE User's Guide}}), reads the digitized signals of a database record and displays them with annotations superimposed on the waveforms. Such a program allows the user to select any portion of the signals for display at various scales, and to add, delete, or correct annotations. @cindex digital filter @cindex filter (digital) @item @emph{Signal processing programs} (e.g., @pxref{Example 7}) apply digital filters to the signals of a database record and then record the filtered signals as a new record. Similar programs perform sampling frequency conversion. @item @emph{Analysis programs} (e.g., @pxref{Example 10}) read the digitized signals, analyze them, and then record their own annotations. @cindex annotation comparator @cindex comparator (annotation) @item An @emph{annotation comparator}, such as @code{bxb} (@pxref{WFDB Applications}), reads two or more sets of annotations corresponding to a given record, and tabulates discrepancies between them. If the reference annotations supplied with the database are compared in this way with annotations produced using an analysis program, this comparison is a means of establishing the accuracy of the analysis program's output. @end itemize The WFDB library provides the means for programs such as those described above to select a database record, read and write signals, read and write annotations, jump to arbitrary points in the record, and determine attributes of the signals such as the sampling frequency. The library also provides a variety of other more specialized services for programs that need them. The library defines an interface between programs and the database that is sufficiently powerful, general, and efficient to eliminate the need for @emph{ad hoc} user-written database I/O. @node Guide, , Applications, Overview @unnumberedsec About this Guide You should have a good grasp of the C language in order to make the best use of this guide. If ANSI C prototypes, used here to document the WFDB library functions, are unfamiliar to you, see pp. 217--218 in the second edition of @cite{The C Programming Language} by Kernighan and Ritchie, Prentice Hall, 1988. (This is the famous @cite{K&R}; all @cite{K&R} references in this guide include page numbers for the second edition. Newcomers to C should have a copy for ready reference while reading this guide.) It may also be helpful to have a copy of a database directory, such as the @uref{http://@-physionet.@-org/@-physio@-bank/@-data@-base/@-html/@-mitdbdir/, @cite{MIT-BIH Arrhythmia Database Directory}}. The @uref{http://@-physionet.@-org/@-physio@-tools/@-wag/, @cite{WFDB Applications Guide}} will be useful as a reference for existing WFDB library-based applications (available from PhysioNet, @uref{http://@-physio@-net.@-org/}). You should have access to a computer that has the WFDB library and at least one or two database records on-line, or access to the World Wide Web, where database records can be obtained from PhysioNet and other sources. (If you are installing the WFDB library on a new computer for the first time, please read the installation notes supplied with the WFDB library first, or @pxref{Installation, , Installing the WFDB Software Package}, then return here.) You should know how to create a C source file using your favorite editor, and you should know how to compile it and how to run the resulting executable program. Resist all temptation to plunge into the esoteric details of file formats. (Those who find such details irresistible will find them in Section 5 of the @cite{WFDB Applications Guide}; note, however, that support for new file formats is added to the WFDB library from time to time, so that the information you find there may be incomplete.) The WFDB library provides an efficient means of reading and writing files in many formats; it is not a trivial task to duplicate it, and time spent doing so is time that could be spent doing something useful, enjoyable, or possibly both. If you really think you need to understand the file formats in order to translate them into whatever the ECGWhizz Model 666 needs, consider instead writing a format translator using the WFDB library to read the files; then you will at least have a program that requires only recompilation with a new version of the WFDB library when file formats change. @emph{In extremis}, use @file{rdann} and @file{rdsamp} --- available from PhysioNet in source and ready-to run formats --- to translate files into text format. Chapter 1 of this guide begins with a simple example program that reads a few samples from a database record. The C version of this program is also translated into a variety of other languages supported by the WFDB library itself or by separately available bindings. This example should help you understand the mechanics of compiling and using a program that does something with an ECG database. Chapter 2 introduces the library functions themselves, with a number of brief examples; you may wish to skim through this material on a first reading to get acquainted with what is available, and then refer to it as needed while writing your programs. Data structures for annotations and for signal and annotator attributes are described in chapter 3. Chapter 4 contains a table of annotation types and descriptions of several annotation-mapping macros. Database files and related topics are discussed in chapter 5, which can be skipped on a first reading. Chapter 6 contains additional example programs that illuminate a few of the darker corners of the WFDB library. The glossary defines the ordinary-sounding words such as @dfn{signal} that have specialized meanings in this guide; such words are @dfn{emphasized} in their first appearances in order to warn you that you should look them up in the glossary on a first reading (@pxref{Glossary}). If the WFDB library has not yet been installed on your system, @pxref{Installation, , Installing the WFDB Software Package}. Another appendix (@pxref{WFDB Applications}) includes brief descriptions of the application programs that are distributed with the WFDB library as part of the WFDB software package. @cindex operating systems (supported) Another appendix discusses porting the WFDB library to new machines or operating systems, and includes notes on adding support for new file formats, annotation codes, and other enhancements (@pxref{Extensions}). The WFDB library has been written with portability in mind. It runs on a wide variety of machines and operating systems, including Unix (BSD 4.x, System V, SunOS, Solaris, HP-UX, OSF/1, Version 7, XENIX, VENIX, ULTRIX, GNU/Linux, FreeBSD, OpenBSD, IRIX, AIX, AUX, Darwin, Mac OS X, SCO, Coherent, and more), MS-DOS, MS-Windows, VMS, and classic Mac OS. This guide was written for Unix users (with notes for MS-Windows and MS-DOS users where differences exist), but others should find only minor differences. At the end of the guide is a list of sources for databases and other materials that may be useful to readers (@pxref{Sources}), and a log of changes made to this library since 1999 (@pxref{Recent Changes}). Many friends have contributed to the development of the WFDB library. Thanks to Paul Albrecht, Ted Baker, Phil Devlin, Scott Greenwald, Thomas Heldt, Isaac Henry, David Israel, Roger Mark, Joe Mietus, Warren Muldrow, Ikaro Silva, Wei Zong, and especially to Paul Schluter, whose elegant 8080 assembly language functions inspired these (long live @code{getann}!). Pat Hamilton and Bob Farrell contributed ports, to classic Mac OS and the MS 32-bit Windows environments, respectively. Jose Garcia Moros and Salvador Olmos contributed Matlab/Octave reimplementations of a useful subset of the WFDB library. Jonas Carlson wrote, documented, and contributed a set of Matlab wrappers for the WFDB library, and Michael Craig created and contributed the WFDB Toolbox for Matlab. Isaac Henry contributed SWIG wrappers that provide interfaces for a variety of scripting languages, as well as a set of translations of the C example programs in this guide into the languages supported by the SWIG wrappers. Mike Dakin provided the first implementation of NETFILES (HTTP client support in the WFDB library) based on the W3C's @code{libwww}. Following the W3C's decision to end development of @code{libwww}, Benjamin Moody reimplemented NETFILES using @code{libcurl}, and also implemented variable-layout records. Bug reports (and in some cases fixes) have been contributed by Omar Abdala, Winton Baker, David Brooks, Bob Farrell, Virginia Faro-Maza, Ion Gazta@~naga, Fred Geheb, Mathias Gruber, Thomas Heldt, Isaac Henry, Justin Leo Chang Loong, Benjamin Moody, Guido Muesch, Joonas Paalasmaa, Tony Ricke, Dave Schaffer, Dan Scott, Allavatam Venugopal, Mauro Villarroel, Andrew Walsh, Piotr Wlodarek, and Yinqi Zhang. Thanks also to the many readers of earlier versions of this guide; if this edition answers your questions, it is because someone else has already asked them, and hounded the author until he produced comprehensible answers. Before May, 1999, and the release of version 10.0.0 of the library, the WFDB library was known as the DB library, and this guide was the @cite{ECG Database Programmer's Guide}. The name of the library was changed because of confusion caused by the proliferation of another library with the same name (a reimplementation of the Berkeley Unix DBM library). The names of this guide, and of the @cite{WFDB Applications Guide} (formerly the @cite{ECG Database Applications Guide}), have been changed in view of the increasingly broad range of applications in which the library is being used. The first edition of this guide was written as a tutorial for MIT students using the ECG databases for a variety of signal-processing and analysis projects. The guide, and the WFDB library itself, have been extensively revised since they first appeared in 1981. Your comments and suggestions are welcome. Please send them to: @display PhysioNet <@email{wfdb@@physionet.org}> MIT Room E25-505A Cambridge, MA 02139 USA @end display @ifnotinfo @cindex Emacs Info @cindex GNU emacs @cindex Info (GNU emacs) If you use the GNU @code{emacs} editor, you can peruse a hypertext version of this guide using @code{info} if it has been installed on your system; among its many other features, @code{emacs} makes it easy to copy code from the examples into your own programs. Installation instructions are included in the WFDB Software Package; type @kbd{C-h i} within GNU @code{emacs} to start up @code{info} (@pxref{Sources}, for information about obtaining GNU @code{emacs}). @end ifnotinfo @ifnothtml An HTML version of this guide, suitable for viewing using any web browser, is included with the WFDB Software Package. The latest version may always be viewed at @uref{http://@-physio@-net.@-org/@-physio@-tools/@-wpg/} using your web browser. @end ifnothtml You can format and print copies of this guide using TeX if you have it (see @file{makefile} in the @file{doc} directory of the library distribution for instructions on doing so). You may obtain preformatted versions in PDF and PostScript formats from @uref{http://physionet.org/}. @node Usage, Functions, Overview, Top @chapter Using the WFDB Library This chapter gives a brief overview of the steps needed to compile, load, and run a program that uses the WFDB library. It assumes that you are able to log onto a Unix-based computer on which the WFDB Software Package has been installed (@pxref{Installation, , Installing the WFDB Software Package}), and that you know how to create a source file using a text editor such as @code{emacs} or @code{vi}. If you are using an MS-DOS system, there are a few differences noted below. @menu * print samples:: A trivial example program. * compiling:: Compiling a C program with the WFDB library. * other languages:: Using the WFDB library with C++, Fortran, Java, Perl, Python, C#, or Matlab. * WFDB path:: WFDB library environment variables. * running example:: Running the example program. * name restrictions:: Names to avoid when writing your programs. * WFDB path syntax:: Customizing the WFDB path. * exercises 1:: A few questions. @end menu @node print samples, compiling, Usage, Usage @section A Trivial Example Program in C Suppose we wish to print the first ten samples of record @file{100s}. (Record @file{100s} is the first minute of MIT-BIH Arrhythmia Database record @file{100}, supplied as a sample in the @code{data} directory of all source distributions of the WFDB Software Package.) We might begin by creating a source file called @file{psamples.c} that contains: @example #include #include main() @{ int i; WFDB_Sample v[2]; WFDB_Siginfo s[2]; if (isigopen("100s", s, 2) < 2) exit(1); for (i = 0; i < 10; i++) @{ if (getvec(v) < 0) break; printf("%d\t%d\n", v[0], v[1]); @} exit(0); @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/psamples.c} for a copy of this program.) All programs that use the WFDB library @emph{must} include the statement @example #include @end example @noindent which defines function interfaces, data types (such as the @code{WFDB_Sample} and @code{WFDB_Siginfo} types used in this example), and a few useful constants. (Most MS-DOS C compilers accept @samp{/} as a directory separator. If you prefer to use the non-portable @samp{\} under MS-DOS, remember to quote it: @samp{#include }.) The functions used in the example are described in detail in the next chapter, and the data types are described in the following chapter (@pxref{Data Types}). For now, note that @code{isigopen} prepares a record to be read by @code{getvec}, which reads a sample from each of the two signals each time it is called. Note that in some cases it may be important to insure that all memory allocated by the WFDB library is freed before the program exits; in the example program, this can be done by adding the line @example wfdbquit(); @end example @noindent just above @code{exit(0);} @ifhtml (@pxref{wfdbquit, , @code{wfdbquit}}). @end ifhtml @ifinfo (@pxref{wfdbquit}). @end ifinfo @iftex (@pxref{wfdbquit, , @code{wfdbquit}}). @end iftex @node compiling, other languages, print samples, Usage @comment node-name, next, previous, up @section Compiling a Program with the WFDB Library @html @end html @cindex WFDB library (compiling with) @cindex compiling @cindex loader options The WFDB library is developed and tested using @code{gcc}, the GNU C/C++ compiler, but careful attention has been given to making it usable with any ANSI/ISO C compiler. Since @code{gcc} is free, high quality, and supported, it is highly recommended that you use it for compiling your WFDB applications. To compile the example program using @code{gcc} on a Unix shell, we can say: @example gcc -o psamples psamples.c `wfdb-config --cflags --libs` @end example @noindent to produce an executable program called @code{psamples}. (Your C compiler may be named @file{cc}, @file{acc}, @file{CC}, or something else, rather than @file{gcc}.) Note that this command contains backticks (`), not apostrophes ('). The program @command{wfdb-config} is part of the WFDB Software Package, and tells the compiler where to find the header files (such as @file{wfdb/wfdb.h}) and the library itself (such as @file{libwfdb.so}). Writing the @command{wfdb-config} command in backticks means that the output of the command is inserted into the @code{gcc} command line. So if the WFDB library is installed in @file{/usr/local/lib}, then the command above is equivalent to: @example gcc -o psamples psamples.c -I/usr/local/include -L/usr/local/lib -lwfdb @end example @noindent In addition to being shorter to type, it's a good idea to use @command{wfdb-config} so that your program can be compiled on other systems, regardless of where the WFDB library has been installed. You may use any other compiler options you choose, but @samp{`wfdb-config --cflags --libs`} must appear in the @command{cc} command line following any and all source (@file{*.c}) and object (@file{*.o}) file names, in order to instruct the loader to search the WFDB library for any functions that the program needs (in this case, @code{isigopen} and @code{getvec}). Some programs will need additional libraries, and the corresponding @samp{-l} options can usually be given before or after the @command{wfdb-config} command. Under MS-Windows, @file{gcc} is included in the freely available Cygwin software development system (@uref{http://@-www@-.cygwin@-.com/}), and also in the freely available MinGW package (@uref{http://@-www@-.mingw@-.org/}). An MS-DOS version of @file{gcc} is available in the free djgpp package (@uref{http://@-www@-.delorie@-.com/@-djgpp/}). These are used within a Cygwin terminal emulator window or an MS-DOS box in exactly the same way as described above for C compilers on all other platforms. For most purposes, Cygwin is recommended, since it provides a Unix-compatible standard C library (@code{cygwin1.dll}), so that applications behave exactly as they do on all other platforms. WAVE can only be built under Windows in this way. When building WFDB-based plugins for use with .NET applications or others such as Matlab that rely on the native Windows C library, however, the WFDB library must be recompiled to use the native library. This can be done using either MinGW gcc, or Cygwin gcc with its @code{-mno-cygwin} option. If you choose to use an incompatible proprietary compiler, you are on your own! You may be able to create a linkable version of the WFDB library from the sources in the @file{lib} directory of the WFDB source tree using a proprietary compiler, but doing so is unsupported (see your compiler's documentation). If you are not able to build the WFDB library using your compiler, you can compile the library sources together with the source file(s) for your application. It may be easiest to copy the library sources (both the @file{*.c} and the @file{*.h} files) into the same directory as the application sources. If you follow this approach, find the directory that contains @file{stdio.h} on your system and make a @file{wfdb} subdirectory within that directory, then copy the WFDB library's @file{*.h} files into the @file{wfdb} subdirectory (this is necessary so that statements of the form @samp{#include } will be handled properly by your compiler). For example, to compile @file{psamples.c} with Microsoft C/C++, set up the WFDB library source files as just described, then use this command: @example cl psamples.c wfdbio.c signal.c annot.c calib.c wfdbinit.c @end example @noindent With Borland C/C++ or Turbo C or C++, substitute @samp{bcc} or @samp{tcc}, respectively, for @samp{cl} in the command above. You will find that some WFDB applications do not need to be compiled with all of the WFDB library sources (for example, @file{psamples} needs only @file{wfdbio.c} and @file{signal.c}); in such cases, you may omit the unneeded sources for faster compilation and smaller executable binaries. @node other languages, WFDB path, compiling, Usage @comment node-name, next, previous, up @section Using the WFDB library with other languages Bindings and wrappers are available so that programs written in a number of other programming languages can make use of the WFDB library. @unnumberedsubsec C++ bindings @cindex C++ bindings If you prefer to write your applications in C++, you may do so, but note that the WFDB library is written in C. (Most C++ compilers can be run in ANSI/ISO C compatibility mode in order to compile the WFDB library itself.) Each C++ source file that uses WFDB library functions must include @file{}, in order to instruct your compiler to use C conventions for argument passing and to use unmangled names for the WFDB library functions. In order for this to work, your C++ compiler should predefine @samp{__cplusplus} or @samp{c_plusplus}; if it predefines neither of these symbols, modify @file{} so that the symbols @samp{wfdb_CPP} and @samp{wfdb_PROTO} are defined at the top of the file, or define @samp{__cplusplus} in each of your source files before including @file{}. Compile and link your program using whatever standard methods are supported by your compiler for linking C++ programs with C libraries. See your compiler manual for further information. @unnumberedsubsec Fortran wrappers @cindex Fortran bindings @cindex wrappers for Fortran A set of wrapper functions is also available for those who wish to use the WFDB library together with applications written in Fortran. These functions, defined in @uref{http://physionet.org/physiotools/wfdb/fortran/wfdbf.c, @file{wfdbf.c}}, provide a thin `wrapper' around the WFDB library functions, by accepting Fortran-compatible arguments (there are no structures, and all arguments are passed by reference rather than by value). For example, here is the Fortran equivalent of the example program in the previous section: @example integer i, v(2), g i = isigopen("100s"//CHAR(0), 2) do i = 1, 10 g = getvec(v) write (6,3) v(1), v(2) 3 format("v(1) = ", i4, " v(2) = ", i4) end do end @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/fortran/fsamples.f} for a copy of this program; an extensively commented version of this program is also available, at @uref{http://physionet.org/physiotools/wfdb/fortran/example.f}.) @noindent To compile this program using @command{gfortran} (the GNU Fortran compiler), save it as @file{fsamples.f} in the current directory, copy @file{wfdbf.c} (which can be found in the same directory as @file{wfdb.h}, usually @file{/usr/local/include/wfdb}) to the current directory, then type: @example gfortran -o fsamples -DFIXSTRINGS fsamples.f wfdbf.c `wfdb-config --libs` @end example @noindent The Fortran wrapper functions are not discussed in this guide; for further information, refer to @uref{http://physionet.org/physiotools/wfdb/fortran/README,fortran/README} in the WFDB Software Package. @unnumberedsubsec SWIG wrappers for Java, Perl, Python, and other languages @cindex SWIG wrappers @cindex Java wrappers @cindex Perl wrappers @cindex Python wrappers @cindex C# wrappers Isaac Henry has contributed WFDB wrappers for Java, Perl, and Python, as well as for .NET languages such as C#, created using the Simplified Wrapper Interface Generator (SWIG, @uref{http://www.swig.org/}). Using these wrappers, the example program can be written in any of these languages: @b{Java:} @example import wfdb.*; public class psamples @{ static @{ System.loadLibrary("wfdbjava"); @} public static void main(String argv[]) @{ WFDB_SiginfoArray siarray = new WFDB_SiginfoArray(2); if (wfdb.isigopen ("100s", siarray.cast(), 2) < 2) System.exit(1); WFDB_SampleArray v = new WFDB_SampleArray(2); for (int i = 0; i < 10; i++) @{ if (wfdb.getvec(v.cast()) < 0) break; System.out.println("\t" + v.getitem(0) +"\t"+ v.getitem(1)); @} @} @} @end example @b{Perl:} @example package wfdb; use wfdb; $siarray = new wfdb::WFDB_SiginfoArray(2); if ($nsig = isigopen("100s", $siarray->cast(), 2) < 2) @{ exit(1); @} $v = new wfdb::WFDB_SampleArray(2); for ($i=0; $i < 10; $i++) @{ if (getvec($v->cast()) < 0) @{ exit(2); @} print "\t", $v->getitem(0), "\t", $v->getitem(1), "\n"; @} @end example @b{Python:} @example import wfdb, sys def main(argv): siarray = wfdb.isigopen("100s") if siarray.nsig < 2: sys.exit(1) v = wfdb.WFDB_SampleArray(2) for i in range(0,10): if wfdb.getvec(v.cast()) < 0: sys.exit(2) print "\t%d\t%d" % (v[0], v[1]) if __name__ == "__main__": main(sys.argv[1:]) @end example @b{C#:} @example using System; using Wfdb; public class psamples @{ static void Main(string[] argv) @{ WFDB_SiginfoArray siarray = new WFDB_SiginfoArray(2); if (wfdb.isigopen("100s", siarray.cast(), 2) < 2) Environment.Exit(1); WFDB_SampleArray v = new WFDB_SampleArray(2); for (int i = 0; i < 10; i++) @{ if (wfdb.getvec(v.cast()) < 0) break; Console.WriteLine("\t" + v.getitem(0) + "\t" + v.getitem(1)); @} @} @} @end example All SWIG wrappers for the WFDB library are generated using a single interface file, @file{wfdb.i}. In principle, this file might be used to generate wrappers for other programming languages supported by SWIG, including several versions of LISP, Modula-3, PHP, Ruby, and Tcl. @unnumberedsubsec Matlab toolbox @cindex Matlab toolbox @cindex Toolbox for Matlab @cindex WFDB toolbox for Matlab The WFDB Toolbox for Matlab, contributed by Michael Craig and available from @uref{http://@-physionet.@-org/@-physiotools/@-matlab/@-wfdb-swig-matlab/}, is a collection of WFDB applications implemented as functions in Matlab, built on the SWIG Java wrappers for the WFDB library. For example, using it in Matlab, one can read and plot the first five seconds of the same signals as in the example program above, by: @example r = rdsamp('100s', 'maxt', ':5'); plot(r(:,1), r(:,2)); @end example The WFDB Toolbox for Matlab replaces the @code{wfdb_tools} library of wrapper functions contributed by Jonas Carlson, since current versions of Matlab are no longer compatible with the @code{wfdb_tools} library. The WFDB Software Package includes @code{wfdb2mat}, an application that converts all or any desired segment of a signal file into a @code{.mat} file that can be read directly by Matlab. Jesus Olivan Palacios has written a tutorial (available at @uref{http://www.@-neurotraces.@-com/@-scilab/@-sciteam/}) that includes a detailed section on using the WFDB Software Package with Scilab (an open-source scientific software package for numerical computations, with a language similar to that of Matlab, available from @uref{http://@-www-rocq.inria.fr/@-scilab/}). The methods described in this tutorial can also be adapted for use with GNU Octave (another free language that is mostly compatible with Matlab, available from @uref{http://@-www.gnu.org/@-software/@-octave/}). Also available is a reimplementation of a useful subset of the WFDB library in native m-code (contributed by Jose Garcia Moros and Salvador Olmos) at @uref{http://@-physio@-net.@-org/@-physio@-tools/@-matlab/}. @node WFDB path, running example, other languages, Usage @comment node-name, next, previous, up @section The Database Path and Other Environment Variables @html @end html @cindex database path (setting) @cindex path (database) @cindex directories for WFDB files @cindex WFDB files (finding) @cindex finding WFDB files @cindex @code{WFDB} (environment variable) @cindex @code{WFDBCAL} (environment variable) @cindex @code{WFDBGVMODE} (environment variable) @cindex @code{WFDBANNSORT} (environment variable) @cindex WFDB path WFDB applications make use of several @dfn{environment variables}, which are named @code{WFDB}, @code{WFDBCAL}, @code{WFDBGVMODE}, and @code{WFDBANNSORT}. If these variables have not been otherwise defined by the user, their values are those given by @code{DEFWFDB}, @code{DEFWFDBCAL}, @code{DEFWFDBGVMODE}, and @code{DEFWFDBANNSORT} (defined in @file{wfdblib.h} at the time the WFDB library was compiled). Unless you have a non-standard setup, you may not need to set these variables, but it will be helpful to read this section to understand how they influence the behavior of WFDB applications. When WFDB applications @emph{read} database files, they must be able to find them in various locations that may vary from system to system. The WFDB library refers to a character string that consists of an ordered list of locations to be searched @emph{for input files}. This string is called the @dfn{database path}, or the @dfn{WFDB path}. On most systems, the environment variable @code{WFDB}, if set, specifies the value of the WFDB path, and overrides the default value. If you need to use a non-default WFDB path, you must set the @code{WFDB} environment variable appropriately before running any WFDB applications, so that the WFDB path can be examined by the running program. The WFDB software package includes easily customizable shell scripts (batch files) that illustrate how to do this for popular shells and command interpreters; see @emph{setwfdb}(1), in the @cite{WFDB Applications Guide}. (Under classic Mac OS, for which the concept of environment variables is foreign, the WFDB path may be set only by using @code{DEFWFDB}.) For further information, @pxref{WFDB path syntax}. @cindex calibration file @cindex @code{WFDBCAL} (environment variable) The shell scripts that set @code{WFDB} also set the @code{WFDBCAL} environment variable, which is important if you make use of records that contain signals other than ECGs. @code{WFDBCAL} names a @dfn{calibration file} located in one of the directories named by @code{WFDB}. (The symbol @code{DEFWFDBCAL} is usually defined in @file{wfdblib.h} to specify the name of a default calibration file, to be used by the WFDB library if @code{WFDBCAL} has not been set.) Each signal type may be represented by an entry in the calibration file. Entries specify the characteristics of any calibration pulses that may be present, and customary scales for plotting the signals. @cindex annotation (canonical order) @cindex canonical order of annotations The other environment variables are less frequently used than @code{WFDB} and @code{WFDBCAL}, and in most cases, the compiled-in defaults will be appropriate (@pxref{Annotation Order}, and @pxref{Multi-Frequency Records}, for details). @node running example, name restrictions, WFDB path, Usage @section Running the Example Program If @code{WFDB} is properly set, MIT DB record @file{100s} is on-line and readable, and the example program was compiled correctly, it can be run by typing @example psamples @end example @noindent (Try @samp{./psamples} if @samp{psamples} doesn't work.) Its output will appear as: @example 995 1011 995 1011 995 1011 995 1011 995 1011 995 1011 995 1011 995 1011 1000 1008 997 1008 @end example The left column contains samples from signal 0, and the right column contains those from signal 1. @node name restrictions, WFDB path syntax, running example, Usage @section A Note on Identifiers @cindex function name restrictions @cindex external identifiers (restrictions) @cindex restrictions on function and variable names @cindex variable name restrictions External identifiers that begin with the underscore (@samp{_}) character are reserved under the rules of ANSI C to the compiler and libraries. In order to make the WFDB library as portable as possible, its own external identifiers do not begin with underscores (since otherwise they might conflict with external identifiers used by a standard library). External identifiers beginning with @samp{wfdb_} are reserved for the use of the WFDB library. These names are used for functions and global variables that are intended for the private use of the WFDB library; your programs should not need to use them. You should avoid defining functions or global variables with such names in your programs. External identifiers beginning with @samp{WFDB_} are used for constants and data types defined within @file{}. Use these identifiers as needed in your programs, but avoid redefining them. @node WFDB path syntax, exercises 1, name restrictions, Usage @section More About the WFDB Path @cindex WFDB path When a WFDB file must be opened for input, the WFDB library attempts to locate it by attaching each of the components of the WFDB path (one at a time) as a prefix to the file name. If two or more matching files exist in different locations in the WFDB path, the WFDB library opens only the file that resides in the first of these locations. Any other matching files are effectively invisible to WFDB applications unless the WFDB path is rearranged. @cindex database path (default) @cindex WFDB path The default WFDB path is specified at the time the WFDB library is compiled, by defining a value for the symbol @code{DEFWFDB} in @file{wfdblib.h}. Current versions of the WFDB library are compiled with a three-component default WFDB path; the first component is empty (i.e., it refers to the current directory), the second component names the @dfn{system-wide database directory} (which contains the sample WFDB files supplied with the WFDB software package), and the third component is @uref{http://physionet.org/physiobank/data@-base} (referring to the PhysioBank data archives). Note that this default may be changed at the time the WFDB library is compiled. Normally, however, this means that any record available from PhysioBank is readable by any WFDB application provided that PhysioBank is accessible from the user's computer and that the database name is included in the record name (for example, @file{slpdb/slp60} or @file{nsrdb/16265}). Under Unix and VMS, the WFDB path can be given as a colon-separated list of prefixes, in the format used for the Bourne shell's @code{PATH} variable. Under MS-Windows, MS-DOS, and classic Mac OS, the WFDB path can be given in the format used for the MS-DOS @code{PATH} variable, with semicolons used to separate prefixes (colons retain their customary meanings, as drive letter suffixes under MS-DOS, or as directory separators on the Macintosh). Alternatively, components of the WFDB path may be separated by whitespace (under any operating system); this also implies that embedded spaces are not permitted within path components. @strong{For this reason, avoid using directories with names such as @file{My Documents}, or their subdirectories, to store WFDB files.} When WFDB applications @emph{write} database files, these files are generally written to the current directory. (As an example, an application that analyzes one or more signals in a record may record its findings in an annotation file in the current directory.) If the record name (as provided by the application to the WFDB library) contains path information, however, output files are written to the corresponding subdirectory of the current directory. (For example, if a WFDB application writes an annotation file for record @code{edb/e0103}, the file will be written in the @code{edb} subdirectory of the current directory. The @code{edb} subdirectory will be created by the WFDB library if does not exist already. This feature was introduced in WFDB library version 10.2.0.) Note particularly that the current directory is @emph{not} necessarily part of the WFDB path. If you modify your WFDB path, you must explicitly include an empty (null) component, which corresponds to the current directory, in order to be sure that your WFDB applications can read any WFDB files that you have previously written. In most cases, this null component should be the first in the WFDB path. Thus, if you write into the current directory a modified version of an existing WFDB file, any later actions that would read this file will read your modified version rather than the original. @cindex NETFILES @cindex curl @cindex libcurl The WFDB path may contain @uref{http://} and @code{ftp://} URL prefixes (other schema, such as @code{file://} and @code{https://}, may also be supported if they are supported by your version of @code{libcurl}). If NETFILES support is not compiled into the WFDB library, any WFDB path components containing @file{://} are ignored. (These features were first introduced in WFDB library version 10.1.0.) @cindex indirect WFDB path @cindex file containing WFDB path @cindex database path file (indirect) If the WFDB library finds that the value assigned to the WFDB path is of the form @samp{@@@var{file}}, it replaces that value with the contents of the specified @var{file}. (This feature was first introduced in WFDB library version 8.0.) Indirect WFDB path files may be nested up to ten levels (this arbitrary limit is imposed to avoid infinite recursion if the contents of the indirect file are incorrect). This method of indirect assignment is useful under classic Mac OS, where recompilation of the WFDB library would otherwise be necessary in order to change the WFDB path. It may also be useful under MS-DOS to reduce the need for environment space, or if the length of the command needed to set the @code{WFDB} environment variable would otherwise approach or exceed the 128-byte limit for MS-DOS commands. If a WFDB header file (@pxref{Database Files}) specifies that a signal file is to be found in a directory that is not already in the WFDB path, that directory is appended to the end of the WFDB path; in this case, if the WFDB path is not set, it is created with an initial null component followed by the directory that contains the signal file. (This feature was first introduced in WFDB library version 6.2.) The string @samp{%r} is replaced by the current record name wherever it appears in the WFDB path; @samp{%@var{N}r} is replaced by the first @var{N} digits of the record name, if @var{N} is a non-zero digit. For example, if (under Unix) the WFDB path is @samp{:/cdrom/mimicdb/%3r:/cdrom/mitdb}, a request to read a file associated with record 055n will cause the WFDB library to look first in the current directory (since the WFDB path begins with an empty component), then in @samp{/cdrom/mimicdb/055}, and then in @samp{/cdrom/mitdb}. If @samp{%} is followed by any character other than @samp{r} or a non-zero digit followed by @samp{r}, that character is used as is in the WFDB path; thus a literal @samp{%} can be included in the WFDB path by `escaping' it as @samp{%%}. (Substitutions of @samp{%}-strings in the WFDB path were first introduced in WFDB library version 9.7.) @node exercises 1, , WFDB path syntax, Usage @section Exercises These exercises should require only a few minutes. If you work through them, you will have an opportunity to become acquainted with a few of the most common errors in using the WFDB library. @enumerate @item Compile the example program in this chapter and run it. If the WFDB Software Package has not already been installed on your system, download and install the most recent version from PhysioNet first (@pxref{Installation, , Installing the WFDB Software Package}). @item Find out where database records are kept on your system. What records are available locally? @item Modify the example program so that you can specify the record to be opened, either as a command-line argument or by having the program prompt you to type a record name. If you are unfamiliar with command-line argument processing, @pxref{Example 2}. @item Use the modified version of the example to read samples from records @file{mitdb/200}, @file{edb/e0103}, @file{slpdb/slp04}, and @file{mimicdb/237/237}. The last two of these records have 4 and 6 signals respectively, so you will need to make a few additional changes to the program in order to read these records successfully. @item Once again using the modified version of the example, what happens if you omit the path information from one of the records in the previous exercise (for example, if you try to open @file{e0103} instead of @file{edb/e0103}? Figure out how to set the WFDB path so that the program will work properly in this case. (Hint: use the application @file{wfdbwhich}, included with the WFDB Software Package, to find the header file for record @file{edb/e0103}; this information will help you to determine how to set the WFDB path.) @item If you use MS-DOS or MS-Windows, explore and explain what happens in the previous exercise if you type the record name using upper-case letters, or if you type a @samp{\} (backslash) instead of @samp{/} (forward slash). (Hint: record names are @emph{not} filenames!) @item What happens when you compile the example program as shown, but with the @code{#include} statement omitted? with the @samp{-lwfdb} (@samp{-link wfdb}, etc.) omitted? @item What is the type of the argument to @code{getvec}? Why can't @code{getvec} simply return the value it reads, as in @samp{v = getvec()}? @end enumerate @node Functions, Data Types, Usage, Top @chapter WFDB Library Functions @cindex WFDB library functions @cindex library functions @cindex functions in the WFDB library This chapter describes the functions that are available to programs compiled with the WFDB library. The functions are introduced in several groups, with examples to illustrate their usage. @menu * introduction to functions:: General notes on functions. This node discusses arguments, return codes, and the organization of this section of the guide. The remainder of the nodes in this section describe functions for: * selecting:: Selecting database records (opening files). * special I/O modes:: Setting the input sampling frequency and more. * signal and annotation I/O:: Reading and writing signals and annotations. * non-sequential:: Non-sequential access to WFDB files. * conversion:: Time and other conversion functions. * calibration:: Calibrating signals. * miscellaneous functions:: Attribute-reading and other functions. * memory allocation macros:: Memory allocation with error-checking. @end menu @node introduction to functions, selecting, Functions, Functions @unnumberedsec About these functions @cindex arguments @cindex function arguments @cindex pointer arguments Each function description begins with an ANSI C function prototype, which specifies the types of any arguments as well as the type of the quantity returned by the function (see @cite{K&R}, pp. 217--218). Note that many of these functions take pointer arguments. These can be traps for newcomers to C. Study the examples carefully! Often a function will return information to the caller in a variable or structure to which the pointer argument points. @strong{It is necessary in such cases for the caller to allocate storage for the variables or structures and to initialize the pointers so that they point to the allocated storage. If you fail to do so, the compiler probably will not warn you of your error; instead your program will fail mysteriously, probably with a core dump and an ``illegal memory reference'' error message.} @cindex errors @cindex function return codes @cindex return codes With few exceptions, WFDB library functions return integers that indicate success or failure. The tables that follow the function prototypes list the possible returns and their meanings. By convention, a return code of @ifhtml -1 @end ifhtml @ifinfo -1 @end ifinfo @iftex @minus{}1 @end iftex indicates end-of-file on input files, and no error message is printed. Other negative return codes signify other types of errors, and are usually accompanied by descriptive messages on the standard error output (but @ifhtml @pxref{wfdbquiet and wfdbverbose, , @code{wfdbquiet} and @code{wfdbverbose}}). @end ifhtml @ifinfo @pxref{wfdbquiet and wfdbverbose}). @end ifinfo @iftex @pxref{wfdbquiet and wfdbverbose, , @code{wfdbquiet} and @code{wfdbverbose}}). @end iftex Zero may indicate success or failure, depending on context (see the descriptions of the individual functions below). Positive codes (returned by only a few functions) always indicate success. A comprehensive discussion of database files appears later in this guide (@pxref{Database Files}). Most readers should not need to learn about the gruesome details of how the data are actually stored. You should know, however, that there are files that contain digitized signals, other files that contain annotations, and still others (called @dfn{header} files) that describe attributes of the signals such as sampling frequency. The database path lists directories in which database files are found; the WFDB library functions can find them given only the record (and annotator) names, provided that @code{WFDB} has been properly set (@pxref{WFDB path}). WFDB library functions responsible for opening signal files find them by reading the header file (which contains their names) first. The first two sections of this chapter describes functions that extract information from header files in order to gain access to signal and annotation files, and functions that control how these files are read and written. The following two sections describe functions that read and write signal and annotation files. Many readers will not need to go any further; the remaining sections deal with special-purpose functions that exist to serve unusual applications. @page @node selecting,special I/O modes,introduction to functions,Functions @section Selecting Database Records @cindex selecting database records @cindex opening database files @cindex initialization @menu * annopen:: Opening input and output annotation files. * isigopen:: Opening input signal files. * osigopen:: Opening output signal files. * osigfopen:: Opening output signal files by name. * wfdbinit:: @code{annopen} and @code{isigopen} in one function. @end menu @c @group @node annopen, isigopen, selecting, selecting @unnumberedsubsec annopen @findex annopen @cindex annotation files (opening) @cindex creating annotation files @cindex opening annotation files @example int annopen(char *@var{record}, const WFDB_Anninfo *@var{aiarray}, unsigned int @var{nann}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-3} Failure: unable to open input annotation file @item @t{-4} Failure: unable to open output annotation file @item @t{-5} Failure: illegal @code{stat} (in @var{aiarray}) specified for annotation file @item @t{-6} Failure: unable to sort output annotations (only if @var{nann} is 0) @item @t{-7} Failure: error writing annotation file (only if @var{nann} is 0) @end table @c @end group @noindent This function opens input and output annotation files for a selected record. If @var{record} begins with @samp{+}, previously opened annotation files are left open, and the record name is taken to be the remainder of @var{record} after discarding the @samp{+}. Otherwise, @code{annopen} closes any previously opened annotation files, and takes all of @var{record} as the record name. @var{aiarray} is a pointer to an array of @code{WFDB_Anninfo} structures @ifnotinfo (@pxref{WFDB_Anninfo structures, , Annotator Information Structures}), @end ifnotinfo @ifinfo (@pxref{WFDB_Anninfo structures}), @end ifinfo one for each annotator to be opened. @var{nann} is the number of @code{WFDB_Anninfo} structures in @var{aiarray}. The caller must fill in the @code{WFDB_Anninfo} structures to specify the names of the annotators, and to indicate which annotators are to be read, and which are to be written. Input and output annotators may be listed in any order in @var{aiarray}. @dfn{Annotator numbers} (for both input and output annotators) are assigned in the order in which the annotators appear in @var{aiarray}. For example, this code fragment @example @dots{} char *record = "100s"; WFDB_Anninfo a[3]; a[0].name = "a"; a[0].stat = WFDB_READ; a[1].name = "b"; a[1].stat = WFDB_WRITE; a[2].name = "c"; a[2].stat = WFDB_READ; if (annopen(record, a, 3) < 0) @dots{} @end example @noindent attempts to open three annotation files for record @file{100s}. Annotator @file{a} becomes input annotator 0, @file{b} becomes output annotator 0, and @file{c} becomes input annotator 1. Thus @code{getann(1, &annot)} @ifnotinfo (@pxref{getann, , @code{getann}}) @end ifnotinfo @ifinfo (@pxref{getann}) @end ifinfo will read an annotation from annotator @file{c}, and @code{putann(0, &annot)} will write an annotation for annotator @file{b}. Input annotation files will be found if they are located in any of the directories specified by @code{WFDB} (@pxref{WFDB path}); output annotators are created in the current directory. Several of the example programs in chapter 6 illustrate the use of @code{annopen}; for example, @pxref{Example 1}. To close all annotation files and check that any output annotations were written successfully, invoke @code{annopen("", NULL, 0)}. (Prior to WFDB library version 10.7.0, this would always return 0.) This can also be useful to force open annotation files to be closed without closing open signal files. @c @group @node isigopen, osigopen, annopen, selecting @unnumberedsubsec isigopen @findex isigopen @cindex signal files (opening) @cindex opening signal files @example int isigopen(char *@var{record}, WFDB_Siginfo *@var{siarray}, int @var{nsig}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success: the returned value is the number of input signals (i.e., the number of valid entries in @var{siarray}) @item @t{ 0} Failure: no input signals available @item @t{-1} Failure: unable to read header file (probably incorrect record name) @item @t{-2} Failure: incorrect header file format @item @t{-3} Failure: internal limits exceeded (all signal files closed) @end table @c @end group @noindent This function opens input signal files for a selected record. If @var{record} begins with @samp{+}, previously opened input signal files are left open, and the record name is taken to be the remainder of @var{record} after discarding the @samp{+}. Otherwise, @code{isigopen} closes any previously opened input signal files, and takes all of @var{record} as the record name. If the record name is @samp{-}, @code{isigopen} reads the standard input rather than a @file{hea} file. @var{Siarray} is a pointer to an array of @code{WFDB_Siginfo} structures @ifnotinfo (@pxref{WFDB_Siginfo structures, , Signal Information Structures}), @end ifnotinfo @ifinfo (@pxref{WFDB_Siginfo structures}), @end ifinfo one for each signal to be opened. As a special case, if @var{nsig} is 0, @var{siarray} can be @code{NULL}. In this case, @code{isigopen} closes any open input signals, then returns the number of signals in @var{record} without opening them. Use this feature to determine the amount of storage needed for signal-related variables, as in the example below, or to force open input signal files to be closed without closing open annotation or output signal files. This action also sets internal WFDB library variables that record the base time and date, the length of the record, and the sampling and counter frequencies, so that time conversion functions such as @code{strtim} that depend on these quantities will work properly. If @code{nsig} is greater than 0, @code{isigopen} normally returns the number of input signals it actually opened, which may be less than @var{nsig} but is never greater than @var{nsig}. The caller must allocate storage for the @code{WFDB_Siginfo} structures; @code{isigopen} will fill them in with information about the signals. @dfn{Signal numbers} are assigned in the order in which signals are specified in the @file{hea} file for the record; on return from @code{isigopen}, information for signal @var{i} will be found in @var{siarray[i]}. For example, we can read the @code{gain} attributes of each signal in record @file{100s} like this: @c @group @example #include #include main() @{ int i, nsig; WFDB_Siginfo *siarray; nsig = isigopen("100s", NULL, 0); if (nsig < 1) exit(1); siarray = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); nsig = isigopen("100s", siarray, nsig); for (i = 0; i < nsig; i++) printf("signal %d gain = %g\n", i, siarray[i].gain); exit(0); @} @end example @c @end group @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/pgain.c} for a copy of this program.) @noindent This program, unlike the example in the previous chapter, does not assume that the number of signals is known. The first invocation of @code{isigopen} determines this number (and the program quits if there are no signals). Next, the program allocates the array for the signal information, and then it opens the signals using the second invocation of @code{isigopen}, passing in the pointer @code{siarray} and the number of signals determined from the first call (@code{nsig}). An error message is produced if @code{isigopen} is unable to open @emph{any} of the signals listed in the header file. It is not considered an error if only some of the signals can be opened, however. A signal will not be opened if its signal file is unreadable, if an input buffer cannot be allocated for it, or if opening all of the signals in its group would exceed the limits defined by @var{nsig}. (Note, however, that most records have only one signal group; as a consequence, @code{isigopen} fails if @var{nsig} is less than the total number of signals in such cases.) If necessary, the caller can inspect the file names and signal descriptions in @var{siarray} to determine which signals were opened; @ifnotinfo @pxref{WFDB_Siginfo structures, , Signal Information Structures}. @end ifnotinfo @ifinfo @pxref{WFDB_Siginfo structures}. @end ifinfo Several of the example programs in chapter 6 illustrate the use of @code{isigopen}; for example, @pxref{Example 5}. If the overall set of open signals exceeds internal limits (for example, the total number of samples per frame is greater than @code{INT_MAX}), @code{isigopen} returns -3 and closes @emph{all} previously-opened input signals. If @var{nsig} is less than 0, @code{isigopen} fills in up to @ifnotinfo @minus{}@var{nsig} @end ifnotinfo @ifinfo -@var{nsig} @end ifinfo members of @var{siarray}, based on information from the header file for @var{record}, but @emph{no signals are actually opened}. The value returned in this case is the number of signals named in the @file{hea} file. Note, however, that there is no guarantee that all (or indeed any) of the signals named in the @file{hea} file are available to be opened. The features described in this paragraph were first introduced in version 4.4 of the WFDB library. @c @group @node osigopen, osigfopen, isigopen, selecting @unnumberedsubsec osigopen @findex osigopen @cindex signal files (creating) @cindex creating signal files @example int osigopen(char *@var{record}, WFDB_Siginfo *@var{siarray}, unsigned int @var{nsig}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success: the returned value is the number of output signals; this number should match @var{nsig} @item @t{-1} Failure: unable to read header file @item @t{-2} Failure: incorrect header file format @item @t{-3} Failure: unable to open output signal(s) @end table @c @end group @noindent This function opens output signal files. Use it only if signals are to be @emph{written} using @code{putvec}. The signal specifications, including the file names, are read from the header file for a specified record. Unmodified MIT or AHA database @file{hea} files cannot be used, since @code{osigopen} would attempt to overwrite the (write-protected) signal files named within. If @var{record} begins with @samp{+}, previously opened output signal files are left open, and the record name is taken to be the remainder of @var{record} after discarding the @samp{+}. Otherwise, @code{osigopen} closes any previously opened output signal files, and takes all of @var{record} as the record name. If the record name is @samp{-}, @code{osigopen} reads the standard input rather than a @file{hea} file. @var{siarray} is a pointer to an uninitialized array of @code{WFDB_Siginfo} structures; @var{siarray} must contain at least @var{nsig} members. The caller must allocate storage for the @code{WFDB_Siginfo} structures. On return, @code{osigopen} will have filled in the @code{WFDB_Siginfo} structures with the signal specifications. No more than @var{nsig} (additional) output signals will be opened by @code{osigopen}, even if the header file contains specifications for more than @var{nsig} signals. For example, this code fragment @example @dots{} WFDB_Siginfo s[2]; int i, nsig; nsig = osigopen("8l", s, 2); for (i = 0; i < nsig; i++) printf("signal %d will be written into `%s'\n", i, s[i].fname); @dots{} @end example @noindent creates 2 output signals named @file{data0} and @file{data1} (@pxref{Piped and Local Records}). @xref{Example 6}, and @pxref{Example 7}, for illustrations of the use of @code{osigopen}. As a special case, if @var{nsig} is 0, @var{siarray} can be @code{NULL}. This can be useful to force open output signal files to be closed without closing open annotation or input signal files. @c @group @node osigfopen, wfdbinit, osigopen, selecting @unnumberedsubsec osigfopen @findex osigfopen @cindex signal files (creating) @cindex creating signal files @example int osigfopen(const WFDB_Siginfo *@var{siarray}, unsigned int @var{nsig}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success: the returned value is the number of output signals; this number should match @var{nsig} @item @t{-2} Failure: error in signal specification (@code{fname} or @code{desc} too long, illegal @code{fmt} or @code{bsize}, or incorrect signal group assignment) @item @t{-3} Failure: unable to open output signal(s) @item @t{-4} Failure: error writing signal file (only if @var{nsig} is 0) @end table @c @end group @noindent This function opens output signals as does @code{osigopen}, but the signal specifications, including the signal file names, are supplied by the caller to @code{osigfopen}, rather than read from a header file as in @code{osigopen}. Any previously open output signals are closed by @code{osigfopen}. @var{siarray} is a pointer to an array of @code{WFDB_Siginfo} structures @ifnotinfo (@pxref{WFDB_Siginfo structures, , Signal Information Structures}), @end ifnotinfo @ifinfo (@pxref{WFDB_Siginfo structures}), @end ifinfo one for each signal to be opened. @var{nsig} is the number of @code{WFDB_Siginfo} structures in @var{siarray}. Before invoking @code{osigfopen}, the caller must fill in the fields of the @code{WFDB_Siginfo} structures in @var{siarray} (@pxref{Data Types}; the @code{initval}, @code{nsamp}, and @code{cksum} fields may be left uninitialized, however). To make a multiplexed signal file, specify the same @code{fname} and @code{group} for each signal to be included (@pxref{Multiplexed Signal Files}). For ordinary (non-multiplexed) signal files, specify a unique @code{fname} and @code{group} for each signal. @xref{Example 8}, for an illustration of the use of @code{osigfopen}. To close all output signal and header files and check that they were written successfully, invoke @code{osigfopen(NULL, 0)}. (Prior to WFDB library version 10.7.0, this would always return 0.) This can also be useful to force open output signal files to be closed without closing open annotation or input signal files. @c @group @node wfdbinit, , osigfopen, selecting @unnumberedsubsec wfdbinit @findex wfdbinit @example int wfdbinit(char *@var{record}, const WFDB_Anninfo *@var{aiarray}, unsigned int @var{nann}, WFDB_Siginfo *@var{siarray}, unsigned int @var{nsig}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success: the returned value is the number of input signals (i.e., the number of valid entries in @var{siarray}) @item @t{ 0} Annotation files opened successfully, input signals unavailable (not an error for programs that don't need them; no error message is printed if @var{nsig} is 0) @item @t{-1} Failure: unable to read header file (probably incorrect record name) @item @t{-2} Failure: incorrect header file format @item @t{-3} Failure: unable to open input annotation file @item @t{-4} Failure: unable to open output annotation file @item @t{-5} Failure: illegal @code{stat} (in @var{aiarray}) specified for annotation file @ifnotinfo (@pxref{WFDB_Anninfo structures, , Annotator Information Structures}) @end ifnotinfo @ifinfo (@pxref{WFDB_Anninfo structures}) @end ifinfo @end table @c @end group @noindent This function opens database files other than output signal files for a selected record. The code @example n = wfdbinit(record, a, na, s, ns); @end example @noindent is exactly equivalent to @example n = annopen(record, a, na); if (n == 0) n = isigopen(record, s, ns); @end example @noindent Avoid using @code{wfdbinit} and @code{setifreq} in the same program. @xref{Example 9}, for an illustration of the use of @code{wfdbinit}. @ifnotinfo @xref{osigopen, , @code{osigopen}}, and @pxref{osigfopen, , @code{osigfopen}}, @end ifnotinfo @ifinfo @xref{osigopen}, and @pxref{osigfopen}, @end ifinfo for methods of opening output signal files. @page @node special I/O modes, signal and annotation I/O, selecting, Functions @section Special I/O Modes @menu * setifreq:: Setting the input sampling frequency. * getifreq:: Determining the input sampling frequency. * setgvmode:: Setting the resolution for a multifrequency record and how invalid samples are represented. * getgvmode:: Determining the getvec operating mode. * getspf:: Determining the number of samples per frame. * setiafreq:: Setting the time resolution of input annotations. * getiafreq:: Determining the time resolution of input annotations. * getiaorigfreq:: Determining the underlying time resolution of an annotation file. * setafreq:: Setting the time resolution for output annotations. * getafreq:: Determining the time resolution for output annotations. @end menu @c @group @node setifreq, getifreq, special I/O modes, special I/O modes @unnumberedsubsec setifreq @findex setifreq (10.2.6) @cindex interpolation @cindex decimation @cindex resampling @example void setifreq(WFDB_Frequency @var{frequency}) @end example @noindent @c @end group @noindent This function sets the current input sampling frequency (in samples per second per signal). It should be invoked after opening the input signals (using @code{isigopen} or @code{wfdbinit}), and before using any of @code{getvec}, @code{putann}, @code{isigsettime}, @code{isgsettime}, @code{timstr}, @code{mstimstr}, or @code{strtim}. @emph{Note that the operation of @code{getframe} is unaffected by @code{setifreq}.} Use @code{setifreq} when your application requires input samples at a specific frequency. After invoking @code{setifreq}, @code{getvec} resamples the digitized signals from the input signals at the desired frequency (@pxref{getvec}), and all of the WFDB library functions that accept or return times in sample intervals automatically convert between the actual sampling intervals and those corresponding to the desired frequency. This slightly elaborated version of the example program from the previous chapter invokes @code{setifreq}, passing it the desired sampling frequency from the command line, then prints the samples in record 100s, beginning 1 second (@code{t0}) and ending 2 seconds (@code{t1}) from the beginning of the record: @example #include #include main(int argc, char **argv) @{ WFDB_Frequency f = (WFDB_Frequency)0; WFDB_Sample v[2]; WFDB_Siginfo s[2]; WFDB_Time t, t0, t1; if (argc > 1) sscanf(argv[1], "%lf", &f); if (f <= (WFDB_Frequency)0) f = sampfreq("100s"); if (isigopen("100s", s, 2) < 1) exit(1); setifreq(f); t0 = strtim("1"); isigsettime(t0); t1 = strtim("2"); for (t = t0; t <= t1; t++) @{ if (getvec(v) < 0) break; printf("%d\t%d\n", v[0], v[1]); @} exit(0); @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/psamplex.c} for a copy of this program. Compile it as shown in the previous chapter, then run it using a command such as @samp{psamplex 100}.) The QRS detector in chapter 6 also illustrates the use of @code{setifreq} (@pxref{Example 10}). @noindent Avoid using @code{wfdbinit} and @code{setifreq} in the same program. @c @group @node getifreq, setgvmode, setifreq, special I/O modes @unnumberedsubsec getifreq @findex getifreq (10.2.6) @example WFDB_Frequency getifreq(void) @end example @noindent @strong{Return:} @table @asis @item @t{(WFDB_Frequency)} the input sampling frequency @end table @c @end group @noindent This function returns the current input sampling frequency (in samples per second per signal), which is either the raw sampling frequency for the record (as would be returned by @code{sampfreq}, @pxref{sampfreq}), or the frequency chosen using a previous invocation of @code{setifreq}. @c @group @node setgvmode, getgvmode, getifreq, special I/O modes @unnumberedsubsec setgvmode @findex setgvmode (9.0) @cindex interpolation @cindex decimation @cindex resampling @cindex multi-frequency records (reading) @cindex padding @cindex invalid samples @cindex missing samples @cindex samples (invalid or missing) @example void setgvmode(int *@var{mode}) @end example @noindent @c @end group @noindent This function sets the mode used by @code{getvec} when reading a multi-frequency record (@pxref{Multi-Frequency Records}). If @var{mode} is @code{WFDB_LOWRES}, @code{getvec} decimates any signals sampled at multiples of the frame rate, so that one sample is returned per signal per frame (i.e., the oversampled signals are resampled by simple averaging of the samples for each signal within each frame). If @var{mode} is @code{WFDB_HIGHRES}, each sample of any oversampled signal is returned by successive invocations of @code{getvec}, and each sample of any signal sampled at a lower frequency is returned by two or more successive invocations of @code{getvec} (i.e., the less frequently sampled signals are resampled using zero-order interpolation). @code{getvec} operates in @code{WFDB_LOWRES} mode by default. @code{WFDB_LOWRES} and @code{WFDB_HIGHRES} are defined in @file{}. In WFDB library version 9.6 and later versions, @code{setgvmode} also affects how annotations are read and written. If @code{setgvmode(WFDB_HIGHRES)} is invoked @emph{before} using @code{annopen}, @code{wfdbinit}, @code{getvec}, @code{sampfreq}, @code{strtim}, or @code{timstr}, then all @code{WFDB_Time} data (including the @code{time} attributes of annotations read by @code{getann} or written by @code{putann}) visible to the application are in units of the high-resolution sampling intervals. (Otherwise, @code{WFDB_Time} data are in units of frame intervals.) Version 10.4 and later versions of the WFDB library support two modes of handling invalid or missing samples. By default, @code{getframe}, @code{getvec}, and @code{sample} return the special value @code{WFDB_INVALID_SAMPLE} in such cases. If @var{mode} is @w{@code{WFDB_GVPAD} + @code{WFDB_LOWRES}} or @w{@code{WFDB_GVPAD} + @code{WFDB_HIGHRES}}, however, these functions replicate the previous valid sample whenever they encounter an invalid or missing sample, which may simplify the design of applications such as digital filters. The constant @code{WFDB_GVPAD} is defined in @file{}. @c @group @node getgvmode, getspf, setgvmode, special I/O modes @unnumberedsubsec getgvmode @findex getgvmode (10.5.3) @cindex interpolation @cindex decimation @cindex resampling @cindex multi-frequency records (reading) @cindex padding @cindex invalid samples @cindex missing samples @cindex samples (invalid or missing) @example int setgvmode(void) @end example @noindent @c @end group @noindent This function returns the operating mode used by @code{getvec}. If the returned value has the @code{WFDB_HIGHRES} bit set, @code{getvec} is operating in high-resolution mode; if the returned value has the @code{WFDB_GVPAD} bit set, @code{getvec} replicates the previous valid sample whenever it encounters an invalid or missing sample, rather than returning the value @code{WFDB_INVALID_SAMPLE}. The constants @code{WFDB_HIGHRES}, @code{WFDB_GVPAD}, and @code{WFDB_INVALID_SAMPLE} are defined in @file{}. @c @group @node getspf, setiafreq, getgvmode, special I/O modes @unnumberedsubsec getspf @findex getspf (9.6) @cindex interpolation @cindex decimation @cindex resampling @cindex multi-frequency records (reading) @example int getspf(void) @end example @noindent @strong{Return:} @table @asis @item @t{(int)} the number of samples per signal per frame @end table @c @end group @noindent Unless the application is operating in @code{WFDB_HIGHRES} mode (@pxref{setgvmode}) and has then opened a multi-frequency record, this function returns 1. For the case of a multi-frequency record being read in high resolution mode, however, @code{getspf} returns the number of samples per signal per frame (hence @code{sampfreq(NULL)/getspf()} is the number of frames per second). @node setiafreq, getiafreq, getspf, special I/O modes @unnumberedsubsec setiafreq @findex setiafreq (10.6.0) @cindex interpolation @cindex decimation @cindex resampling @cindex multi-frequency records (reading) @example void setiafreq(WFDB_Annotator @var{an}, WFDB_Frequency @var{frequency}) @end example @noindent @noindent This function sets the time resolution (number of ticks per second) used by @code{getann} and @code{ungetann} for the given input annotator. By default, the time resolution equals the input sampling frequency (@pxref{getifreq}) at the time @code{annopen} is called. After calling this function, the @code{time} fields of subsequent annotations will be scaled according to the new time resolution. @node getiafreq, getiaorigfreq, setiafreq, special I/O modes @unnumberedsubsec getiafreq @findex getiafreq (10.6.0) @cindex interpolation @cindex decimation @cindex resampling @cindex multi-frequency records (reading) @example WFDB_Frequency getiafreq(WFDB_Annotator @var{an}) @end example @noindent @strong{Return:} @table @asis @item @t{(WFDB_Frequency)>0.} Success: the annotation time resolution in Hz @item @t{(WFDB_Frequency)-2.} Failure: incorrect annotator number specified @end table @noindent This function returns the current time resolution of the given input annotator. The time resolution equals the input sampling frequency by default, but may be changed (@pxref{setiafreq}). @node getiaorigfreq, setafreq, getiafreq, special I/O modes @unnumberedsubsec getiaorigfreq @findex getiaorigfreq (10.6.0) @cindex interpolation @cindex decimation @cindex resampling @cindex multi-frequency records (reading) @example WFDB_Frequency getiaorigfreq(WFDB_Annotator @var{an}) @end example @noindent @strong{Return:} @table @asis @item @t{(WFDB_Frequency)>0.} Success: the annotation time resolution in Hz @item @t{(WFDB_Frequency)0.} Failure: the annotation time resolution is not defined @item @t{(WFDB_Frequency)-2.} Failure: incorrect annotator number specified @end table @noindent This function returns the original time resolution for the given input annotator, if it was specified by the application that created the annotation file (@pxref{setafreq}). @noindent If the application that created the annotation file did not specify a time resolution, @code{getiaorigfreq} returns zero. (In this case, the time resolution is assumed to equal the record's frame frequency.) @node setafreq, getafreq, getiaorigfreq, special I/O modes @unnumberedsubsec setafreq @findex setafreq (10.4.5) @cindex interpolation @cindex decimation @cindex resampling @cindex resolution @example void setafreq(WFDB_Frequency @var{frequency}) @end example @noindent @c @end group @noindent This function sets the time resolution, in ticks per second, for any output annotation files created after it has been invoked. By default, the time resolution is equal to the input sampling frequency (and @code{setifreq} invokes @code{setafreq} to maintain this behavior if the input sampling frequency is changed). @noindent This function has no effect on output annotation files that are already open when it is invoked, nor on annotations read from input annotation files (for which the @code{time} fields always are expressed in units equal to the input sample intervals). @c @group @node getafreq, , setafreq, special I/O modes @unnumberedsubsec getafreq @findex getafreq (10.4.5) @example WFDB_Frequency getafreq(void) @end example @noindent @strong{Return:} @table @asis @item @t{>0} output annotation time resolution, if previously set by @code{setafreq} @item @t{0} otherwise @end table @c @end group @noindent This function returns the current output annotation time resolution in ticks per second if it has been set using @code{getafreq}. @page @node signal and annotation I/O, non-sequential, special I/O modes, Functions @section Reading and Writing Signals and Annotations @cindex signal I/O @menu * getvec:: Reading input signals. * getframe:: Reading input signals from multifrequency records. * putvec:: Writing output signals. * getann:: Reading annotations. * ungetann:: Pushing an annotation onto an input stream. * putann:: Writing annotations. @end menu @c @group @node getvec, getframe, signal and annotation I/O, signal and annotation I/O @unnumberedsubsec getvec @findex getvec @cindex reading signals @cindex signals (reading) @example int getvec(WFDB_Sample *@var{vector}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success; the returned value is the number of input signals (the number of valid entries in @var{vector}) @item @t{-1} End of data (contents of @var{vector} not valid) @item @t{-3} Failure: unexpected physical end of file @item @t{-4} Failure: checksum error (detected only at end of file) @end table @c @end group @noindent This function reads a sample from each input signal. The caller should allocate storage for an array of @code{WFDB_Sample}s (integers) and pass a pointer to this array to @code{getvec}. (The length of the array must be no less than the number of input signals, as obtained from @code{isigopen} or @code{wfdbinit}.) On return, @var{vector[i]} contains the next sample from signal @var{i}. For example, this modified version of the example from chapter 1 reads and prints the first ten samples of each available input signal: @example #include #include main() @{ int i, j, nsig; WFDB_Sample *v; WFDB_Siginfo *s; nsig = isigopen("100s", NULL, 0); if (nsig < 1) exit(1); s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); if (isigopen("100s", s, nsig) != nsig) exit(1); v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); for (i = 0; i < 10; i++) @{ if (getvec(v) < 0) break; for (j = 0; j < nsig; j++) printf("%8d", v[j]); printf("\n"); @} exit(0); @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/exgetvec.c} for a copy of this program.) Notice how the value returned by the first invocation of @code{isigopen} is used to determine how many input signals there are. Several of the example programs in chapter 6 illustrate the use of @code{getvec}; for example, @pxref{Example 6}. @findex setifreq (10.2.6) @cindex interpolation @cindex decimation @cindex resampling If @code{setifreq} has been used to modify the input sampling rate, @code{getvec} resamples the input signals at the desired rate, using linear interpolation between the pair of samples nearest in time to that of the sample to be returned. The results will generally be satisfactory, provided that the original signals do not contain frequencies near or above the Nyquist limit (half of the desired sampling frequency). If this is a concern, you may wish to low-pass filter the input signals using, for example, @samp{fir} (see the @cite{WFDB Applications Guide}) before resampling them. If you use @code{setifreq} to @emph{increase} the sampling frequency by a large factor, you may wish to filter the resampled signals within your application to remove harmonics of the original sampling frequency introduced by resampling. @c @group @node getframe, putvec, getvec, signal and annotation I/O @unnumberedsubsec getframe @findex getframe (9.0) @cindex reading signals @cindex signals (reading) @cindex frames (reading) @cindex multifrequency records @example int getframe(WFDB_Sample *@var{vector}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success; the returned value is the number of input signals @item @t{-1} End of data (contents of @var{vector} not valid) @item @t{-3} Failure: unexpected physical end of file @item @t{-4} Failure: checksum error (detected only at end of file) @end table @c @end group @noindent This function reads a vector of samples, including at least one sample from each open input signal. If all signals are sampled at the same frequency, only one sample is read from each signal. Otherwise, signals sampled at multiples of the frame frequency are represented by two or more consecutive elements of the returned @var{vector}. For example, if the frame frequency is 125 Hz, signal 0 is sampled at 500 Hz, and the remaining 3 signals are sampled at 125 Hz each, then the returned @var{vector} has 7 valid components: the first 4 are samples of signal 0, and the remaining 3 are samples of signals 1, 2, and 3. The caller should allocate storage for an array of @code{WFDB_Sample}s (integers) and pass a pointer to this array to @code{getframe}. The length of @var{vector} must be determined by summing the values of the @code{spf} (samples per frame) fields in the @code{WFDB_Siginfo} structures associated with the input signals @ifnotinfo (@pxref{isigopen, , @code{isigopen}}). @end ifnotinfo @ifinfo (@pxref{isigopen}). @end ifinfo @c @group @node putvec, getann, getframe, signal and annotation I/O @unnumberedsubsec putvec @findex putvec @cindex signals (writing) @cindex writing signals @example int putvec(const WFDB_Sample *@var{vector}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success: the returned value is the number of output signals (the number of entries in @var{vector} that were written) @item @t{ 0} Slew rate too high for one or more signals (difference format only; the DC level(s) will be corrected as soon as the slew rate permits) @item @t{-1} Failure: write error @end table @c @end group @noindent This function writes a sample to each input signal. The caller should fill an array of @code{WFDB_Sample}s with the samples and pass a pointer to this array to @code{putvec}. (The length of the array must be no less than the number of output signals, as given to @code{osigfopen} or @code{osigopen}.) On entry, @var{vector[i]} contains the next sample from signal @var{i}. For example, this modified version of the previous example @ifnotinfo (@pxref{getvec, , @code{getvec}}) @end ifnotinfo @ifinfo (@pxref{getvec}) @end ifinfo copies the first ten samples of each available input signal: @example #include #include main() @{ int i, j, nsig; WFDB_Sample *v; WFDB_Siginfo *s; nsig = isigopen("100s", NULL, 0); if (nsig < 1) exit(1); s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); if (isigopen("100s", s, nsig) != nsig || osigopen("8l", s, nsig) != nsig) exit(1); v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); for (i = 0; i < 10; i++) if (getvec(v) < 0 || putvec(v) < 0) break; wfdbquit(); exit(0); @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/exputvec.c} for a copy of this program.) All programs that write signals or annotations @emph{must} invoke @code{wfdbquit} to close the output files properly @ifnotinfo (@pxref{wfdbquit, , @code{wfdbquit}}). @end ifnotinfo @ifinfo (@pxref{wfdbquit}). @end ifinfo This example uses record @file{8l} (@pxref{Piped and Local Records}) for the output signal specifications; the output signal files will be named @file{data0} and @file{data1} in the current directory. Several of the example programs in chapter 6 illustrate the use of @code{putvec}; for example, @pxref{Example 6}. Note that prior to WFDB library version 10.7.0, @code{putvec} would modify the input vector in some cases (the @var{vector} argument was not declared as @code{const}.) @cindex annotation I/O @c @group @node getann, ungetann, putvec, signal and annotation I/O @unnumberedsubsec getann @findex getann @cindex annotations (reading) @cindex reading annotations @example int getann(WFDB_Annotator @var{an}, WFDB_Annotation *@var{annot}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} End of file (@var{*annot} is not valid) @item @t{-2} Failure: incorrect annotator number specified @item @t{-3} Failure: unexpected physical end of file @end table @c @end group @noindent This function reads the next annotation from the input annotator specified by @var{an} into the annotation structure @ifnotinfo (@pxref{WFDB_Annotation structures, , Annotation Structures}) @end ifnotinfo @ifinfo (@pxref{WFDB_Annotation structures}) @end ifinfo pointed to by @var{annot}. The caller must allocate storage for the annotation structure. Input annotators are numbered 0, 1, 2, etc. This short program uses @code{getann} to read the contents of the reference (@file{atr}) annotation file for record @file{100s}: @example #include #include main() @{ WFDB_Anninfo a; WFDB_Annotation annot; a.name = "atr"; a.stat = WFDB_READ; if (annopen("100s", &a, 1) < 0) exit(1); while (getann(0, &annot) == 0) printf("%s %s\n", mstimstr(annot.time), annstr(annot.anntyp)); exit(0); @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/exgetann.c} for a copy of this program.) @ifnotinfo @xref{WFDB_Anninfo structures, , Annotator Information Structures}, @end ifnotinfo @ifinfo @xref{WFDB_Anninfo structures}, @end ifinfo for information on the contents of the @code{WFDB_Anninfo} structure, and @ifnotinfo @pxref{timstr and strtim, , @code{mstimstr}}, and @pxref{annstr and strann, , @code{annstr}}, @end ifnotinfo @ifinfo @pxref{timstr and strtim}, and @pxref{annstr and strann}, @end ifinfo for details of the functions used to print portions of the annotations read by @code{getann} in this example. @c @group @node ungetann, putann, getann, signal and annotation I/O @unnumberedsubsec ungetann @findex ungetann (5.3) @cindex annotations (reading) @cindex reading annotations @cindex unreading annotations @example int ungetann(WFDB_Annotator @var{an}, const WFDB_Annotation *@var{annot}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: push-back buffer full (@code{*@var{annot}} was not pushed back) @item @t{-2} Failure: incorrect annotator number specified @end table @c @end group @noindent This function arranges for the annotation structure pointed to by @var{annot} to be the next one read by @code{getann} from input annotator @var{an}. The pushed-back annotation need not necessarily be one originally read by @code{getann}. No more than one annotation may be pushed back at a time for each input annotator. (This function was first introduced in WFDB library version 5.3.) @c @group @node putann, , ungetann, signal and annotation I/O @unnumberedsubsec putann @findex putann @cindex annotations (writing) @cindex writing annotations @example int putann(WFDB_Annotator @var{an}, const WFDB_Annotation *@var{annot}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: write error @item @t{-2} Failure: incorrect annotator number specified @end table @c @end group @noindent This function writes the next annotation for the output annotator specified by @var{an} from the annotation structure pointed to by @var{annot}. Output annotators are numbered 0, 1, 2, etc. The caller must fill in all fields of the annotation structure. Using version 9.7 and later versions of the WFDB library, annotations may be written in any order (@pxref{Annotation Order}). Earlier versions require that annotations be supplied to @code{putann} in canonical order, and return an error code of @t{-3} if an out-of-order annotation is supplied. All programs that write signals or annotations @emph{must} invoke @code{wfdbquit} to close the output files properly @ifnotinfo (@pxref{wfdbquit, , @code{wfdbquit}}). @end ifnotinfo @ifinfo (@pxref{wfdbquit}). @end ifinfo Several of the example programs in chapter 6 illustrate the use of @code{putann}; for example, @pxref{Example 1}. @page @node non-sequential, conversion, signal and annotation I/O, Functions @section Non-Sequential Access to WFDB Files @cindex random access @cindex non-sequential access @cindex skipping through WFDB files The next three functions permit random access to signal and annotation files. It is not possible, however, to skip backwards on piped input. @menu * isigsettime:: Setting time of next samples read. * isgsettime:: As above, but for one signal group only. * tnextvec:: Finding the next valid sample in a signal. * iannsettime:: Setting time of next annotations read. * sample:: A random access interface to input signals. @end menu @c @group @node isigsettime, isgsettime, non-sequential, non-sequential @unnumberedsubsec isigsettime @findex isigsettime @cindex signals (non-sequential access) @example int isigsettime(WFDB_Time @var{t}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: EOF reached or improper seek @end table @c @end group @noindent This function resets the input signal file pointers so that the next samples returned from @code{getvec} will be those with sample number |@var{t}|. Only the magnitude of @var{t} is significant, not its sign; hence values returned by @code{strtim} can always be used safely as arguments to @code{isigsettime} @ifnotinfo (@pxref{timstr and strtim, , @code{timstr} and @code{strtim}}). @end ifnotinfo @ifinfo (@pxref{timstr and strtim}). @end ifinfo This function will fail if a pipe is used for input and |@var{t}| is less than the current sample number. @xref{Example 7}, and @pxref{Example 9}, for illustrations of the use of @code{isigsettime}. @c @group @node isgsettime, tnextvec, isigsettime, non-sequential @unnumberedsubsec isgsettime @findex isgsettime @cindex signals (non-sequential access) @example int isgsettime(WFDB_Group @var{sgroup}, WFDB_Time @var{t}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: EOF reached or improper seek @item @t{-2} Failure: incorrect signal group number specified @end table @c @end group @noindent This function does the job of @code{isigsettime}, but only for the signal group specified by @var{sgroup}. This function may be of use if more than one record is open simultaneously (@pxref{Multiple Record Access}). @c @group @node tnextvec, iannsettime, isgsettime, non-sequential @unnumberedsubsec tnextvec @findex tnextvec @cindex signals (non-sequential access) @cindex finding valid samples @cindex skipping gaps in signals @cindex invalid samples (skipping) @cindex valid samples (searching for) @example WFDB_Time tnextvec(WFDB_Signal @var{s}, WFDB_Time @var{t}) @end example @noindent @strong{Return:} @table @asis @item @t{>= 0} Time of the next valid sample of signal @var{s} at or after @var{t} @item @t{-1} Failure: EOF reached or improper seek @end table @c @end group @noindent This function resets the input signal file pointers so that the next samples read by @code{getvec} will include the next valid sample of the specified signal @var{s} occurring at or after @var{t}. Use @code{tnextvec} to skip lengthy gaps in a signal of interest efficiently. @c @group @node iannsettime, sample, tnextvec, non-sequential @unnumberedsubsec iannsettime @findex iannsettime @cindex annotations (non-sequential access) @example int iannsettime(WFDB_Time @var{t}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: EOF reached or improper seek @item @t{-3} Failure: unexpected physical end of file @end table @c @end group @noindent This function resets the input annotation file pointers so that the next annotation read by @code{getann} from each input annotation file will be the first occurring on or after sample number |@var{t}| in that file. Only the magnitude of @var{t} is significant, not its sign; hence values returned by @code{strtim} can always be used safely as arguments to @code{iannsettime} @ifnotinfo (@pxref{timstr and strtim, , @code{timstr} and @code{strtim}}). @end ifnotinfo @ifinfo (@pxref{timstr and strtim}). @end ifinfo This function will fail if a pipe is used for input and |@var{t}| is less than the time of the most recent annotation read from the pipe. @xref{Example 9}, for an illustration of the use of @code{iannsettime}. @c @group @node sample, , iannsettime, non-sequential @unnumberedsubsec sample and sample_valid @findex sample (10.3.0) @findex sample_valid (10.3.0) @example WFDB_Sample sample(WFDB_Signal @var{s}, WFDB_Time @var{t}) int sample_valid(void) @end example @noindent @strong{Return:} @table @asis @item @i{ n} (from @code{sample}): The value (in raw adus) of sample number @var{t} in open signal @var{s},if successful, or the value of the previous successfully read sample. @item @t{ 1} (from @code{sample_valid}): The most recent value returned by @code{sample} was valid @item @t{ 0} (from @code{sample_valid}): The most recent @var{t} given to @code{sample} follows the end of the record @item @t{-1} (from @code{sample_valid}): The most recent value returned by @code{sample} was invalid (because signal @var{s} is not available at time @var{t}) @end table @c @end group @noindent The @code{sample} function allows the caller to read samples of the currently open input signals in any order. The first argument is a signal number (a non-negative integer between 0 and @var{nsig}-1, where @var{nsig} is the number of open input signals), and the second is a time, expressed as a non-negative sample number. If @code{sample} is invoked with valid input arguments, the companion function @code{sample_valid} returns 1. The @code{sample_valid} function can be used to check the results of the most recent invocation of @code{sample}. If @code{sample_valid} returns 1, the latest value returned by @code{sample} is valid. @code{sample_valid} returns 0 if @code{sample} has most recently returned a padded value following the end of the record. This allows code that uses @code{sample} to use the condition @samp{sample_valid() != 0} (or simply @samp{sample_valid()} to determine if more samples are available. If @code{sample_valid} returns -1, the most recent value returned by @code{sample} was @code{WFDB_INVALID_SAMPLE} (because the requested signal @var{s} was unavailable at the requested time @var{t}). Use the condition @samp{sample_valid() > 0} to check if the most recent value returned by @code{sample} is a valid value (e.g., suitable for inclusion in a running average or similar calculation). For an example of the use of @code{sample} and @code{sample_valid}, @pxref{Example 7}. Be sure to call @code{wfdbquit} before exiting from any program that uses @code{sample}, to be certain that dynamically allocated memory used by @code{sample} is freed. @page @node conversion, calibration, non-sequential, Functions @section Conversion Functions @cindex conversion functions Functions in this section perform various useful conversions: between annotation codes and printable strings, between times in sample intervals and printable strings, between Julian dates and printable strings, and between ADC units and physical units. @menu * annstr and strann:: annotation code <-> string * timstr and strtim:: time in sample intervals <-> HH:MM:SS string * datstr and strdat:: Julian date <-> DD/MM/YYYY string * aduphys and physadu:: ADC units <-> physical units @end menu @c @group @node annstr and strann, timstr and strtim, conversion, conversion @unnumberedsubsec annstr, anndesc, and ecgstr @findex ecgstr @findex annstr (5.3) @findex anndesc (5.3) @cindex annotation code (conversion to and from string) @cindex string (conversion to and from annotation code) @cindex mnemonic (annotation) @cindex conversion between annotation code and string @example char *annstr(int @var{code}) char *anndesc(int @var{code}) char *ecgstr(int @var{code}) @end example @noindent @strong{Return:} @table @asis @item @t{(char *)} pointer to a printable string that describes the code, or @code{NULL} @end table @c @end group @cindex modification label @noindent These functions translate the annotation code specified by their argument into a string (@pxref{Annotation Codes}). Illegal or undefined codes are translated by @code{annstr} and @code{ecgstr} into decimal numerals surrounded by brackets (e.g., @samp{[55]}); @code{anndesc} returns @code{NULL} in such cases. The strings returned by @code{annstr} are mnemonics (usually only one character), which may be modified either by @code{setannstr} or by the presence of @dfn{modification labels} in an input annotation file @ifnotinfo (@pxref{annstr and strann, , @code{setannstr}}). @end ifnotinfo @ifinfo (@pxref{annstr and strann}). @end ifinfo The strings returned by @code{anndesc} are brief descriptive strings, usually those given in the table of annotation codes (@pxref{Annotation Codes}). The strings returned by @code{ecgstr} are usually the same as those returned by @code{annstr}, but they can be modified only by @code{setecgstr}, and not by the presence of modification labels as for @code{annstr}. The intent is that @code{ecgstr} should be used rather than @code{annstr} only when it is necessary that a fixed set of mnemonics be used, independent of any modification labels. Here is a little program that prints a table of the codes, mnemonic strings, and descriptions: @example #include #include #include main() @{ int i; printf("Code\tMnemonic\tDescription\n"); for (i = 1; i <= ACMAX; i++) @{ printf("%3d\t%s", i, annstr(i)); if (anndesc(i) != NULL) printf("\t\t%s", anndesc(i)); printf("\n"); @} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/exannstr.c} for a copy of this program.) @code{ACMAX} is defined in @file{}. The range from 1 through @code{ACMAX} includes all legal annotation codes; if you run this program, you will find some undefined but legal annotation codes in this range. @xref{Example 3}, for another illustration of the use of @code{annstr}. (@code{annstr} and @code{anndesc} were first introduced in WFDB library version 5.3.) @c @group @unnumberedsubsec strann and strecg @findex strecg @findex strann (5.3) @cindex annotation code (conversion to and from string) @cindex string (conversion to and from annotation code) @cindex mnemonic (annotation) @cindex conversion between annotation code and string @example int strann(const char *@var{string}) int strecg(const char *@var{string}) @end example @noindent @strong{Return:} @table @asis @item @t{(int)} annotation code @end table @c @end group @noindent These functions translate the null-terminated ASCII character strings to which their arguments point into annotation codes. Illegal strings are translated into @code{NOTQRS}. Input strings for @code{strann} and @code{strecg} should match those returned by @code{annstr} and @code{ecgstr} respectively. @xref{Example 9}, for an illustration of the use of @code{strann}. (@code{strann} was first introduced in WFDB library version 5.3.) @c @group @unnumberedsubsec setannstr, setanndesc, and setecgstr @findex setannstr (5.3) @findex setanndesc (5.3) @findex setecgstr @cindex annotation code strings (setting) @cindex changing annotation code strings @cindex setting annotation code strings @example int setannstr(int @var{code}, const char *@var{string}) int setanndesc(int @var{code}, const char *@var{string}) int setecgstr(int @var{code}, const char *@var{string}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: illegal @code{code} @end table @c @end group @noindent These functions modify translation tables used by functions that convert between annotation codes and strings. @code{setannstr} modifies the table shared by @code{annstr} and @code{strann}; @code{setanndesc} modifies the table used by @code{anndesc}; and @code{setecgstr} modifies the table shared by @code{ecgstr} and @code{strecg}. They may be used to redefine strings for defined annotation codes as well as to define strings for undefined annotation codes. For example, @code{setannstr(NORMAL, "\\267")} redefines the string for normal beats as a PostScript bullet, @samp{@bullet{}} (@code{NORMAL} is defined in @file{}). @cindex modification label An important difference between @code{setannstr} (or @code{setanndesc}) and @code{setecgstr} is that @code{annopen} and @code{wfdbinit} insert modification labels in any output annotation files that are created @emph{after} invoking @code{setannstr} or @code{setanndesc}; @code{setecgstr} does not have this side effect. By using @code{setannstr} before @code{annopen}, a WFDB application may create annotation files with self-contained code tables, which can be read properly by other WFDB applications without the need to inform them explicitly about non-standard codes. For this scheme to work as intended, all custom code mnemonics and descriptions must be defined before the output annotation files are opened. By passing a negative value as @var{code} to @code{setannstr} or @code{setanndesc}, the translation for @ifnotinfo @minus{}@var{code} @end ifnotinfo @ifinfo -@var{code} @end ifinfo can be modified without triggering the generation of a modification label. This feature can be useful for programs that use alternate sets of mnemonics or descriptions for speakers of different languages. Note that it is possible, though not desirable, to define identical strings for two or more codes; the behavior of @code{strann} and @code{strecg} in such cases is implementation-dependent. (@code{setannstr} and @code{setanndesc} were first introduced in WFDB library version 5.3.) @node timstr and strtim, datstr and strdat, annstr and strann, conversion @html @end html @ifnotinfo @sp 2 @end ifnotinfo The next three functions convert between ``standard time format'' strings and times in units of sample intervals. Normally they should be invoked after @code{isigopen}, @code{wfdbinit}, or @code{sampfreq}, any of which will determine the duration of a sample interval and the base time from a header file, or after defining these quantities using @code{setsampfreq} and @code{setbasetime}. If this is not done, or if these time-conversion functions are used after @code{wfdbquit}, they will perform conversions in units of seconds (i.e., the sample interval is taken to be one second in such cases). @c @group @unnumberedsubsec [ms]timstr @findex timstr @findex mstimstr @cindex time (conversion to and from string) @cindex string (conversion to and from time) @cindex elapsed time @cindex conversion between time and string @example char *timstr(WFDB_Time @var{t}) char *mstimstr(WFDB_Time @var{t}) @end example @noindent @strong{Return:} @table @asis @item @t{(char *)} pointer to a string that represents the time @end table @c @end group @noindent These functions convert times or time intervals into null-terminated ASCII strings. If the argument, @var{t}, is greater than zero, it is treated as a time interval, and converted directly into @var{HH:MM:SS} format by @code{timstr}, or to @var{HH:MM:SS.SSS} format by @code{mstimstr}, with leading zero digits and colons suppressed. If @var{t} is zero or negative, it is taken to represent negated elapsed time from the beginning of the record, and it is converted to a time of day using the base time for the record as indicated by the @file{hea} file or the caller @ifnotinfo (@pxref{setbasetime, , @code{setbasetime}}); @end ifnotinfo @ifinfo (@pxref{setbasetime}); @end ifinfo in this case, if the base time is defined, the string will contain all digits even if there are leading zeroes, it will include the date if a base date is defined, and it will be marked as a time of day by being bracketed (e.g., @samp{[08:45:00 23/04/1989]}). The result of the conversion is truncated to a multiple of a second by @code{timstr}, or to a multiple of a millisecond by @code{mstimstr}. Note in each case that the returned pointer addresses static data (shared by @code{timstr} and @code{mstimstr}), the contents of which are overwritten by subsequent calls. @xref{Example 3}, for an illustration of the use of @code{mstimstr}; also @pxref{Example 5}, for an example of the use of @code{timstr}. @c @group @unnumberedsubsec strtim @findex strtim @cindex time (conversion to and from string) @cindex string (conversion to and from time) @cindex elapsed time @cindex conversion between time and string @example WFDB_Time strtim(const char *@var{string}) @end example @noindent @strong{Return:} @table @asis @item @t{(WFDB_Time) >0} number of sample intervals corresponding to the argument interpreted as a time interval @item @t{(WFDB_Time) <0} (negated) elapsed time in sample intervals from the beginning of the record, corresponding to the argument interpreted as a time of day @item @t{(WFDB_Time) 0} a legal return if the argument matches the base time; otherwise an error return indicating an incorrectly formatted argument @end table @c @end group @noindent @cindex standard time format (examples) This function converts an ASCII string in @dfn{standard time format} to a time in units of sample intervals. Examples of standard time format: @table @code @item 2:14.875 2 minutes + 14.875 seconds @item [13:6:0] 13:06 (1:06 PM) @item [8:0:0 1] 8 AM on the day following the base date @item [12:0:0 1/3/1992] noon on 1 March 1992 @item 143 143 seconds (2 minutes + 23 seconds) @item 4:02:01 4 hours + 2 minutes + 1 second @item s12345 12345 sample intervals @item c350.5 counter value 350.5 @item e time of the end of the record (if defined) @item i time of the next sample in input signal 0 @item o (the letter `o') time of the next sample in output signal 0 @end table If the argument is bracketed (as in the second, third, and fourth examples), it is taken as a time of day, and @code{strtim} uses the base time defined by the header file or by the caller @ifnotinfo (@pxref{setbasetime, , @code{setbasetime}}); @end ifnotinfo @ifinfo (@pxref{setbasetime}); @end ifinfo in this case, the value returned is zero or negative (and can be converted into elapsed time from the beginning of the record by simply negating it). If the argument is not bracketed, it is taken as a time interval, and converted directly into a positive number of sample intervals. These notations match those used by @code{timstr} and @code{mstimstr}, which are (approximately) inverse functions of @code{strtim}; in fact, for MIT DB and AHA DB records (and any others with sampling frequencies below 1 KHz), @code{strtim(mstimstr(@var{t}))} = @var{t}, for any @var{t}. The @samp{s}-format (as in the seventh example above) is provided to allow ``conversion'' of time intervals already expressed in sample intervals. The similar @samp{c}-format converts counter values @ifnotinfo (@pxref{counter conversion, , @code{getcfreq}}) @end ifnotinfo @ifinfo (@pxref{counter conversion}) @end ifinfo into sample intervals. The length of the record in sample intervals can be obtained using @code{strtim("e")}, which evaluates to zero if this quantity is undefined. The sample number of the next sample to be read or written can be determined using @code{strtim("i")} or @code{strtim("o")}. If the argument string is incorrectly formatted, @code{strtim} returns zero (indistinguishable from a correct input that evokes a zero output); this may be considered a feature. Several of the programs in chapter 6 illustrate the use of @code{strtim} (for example, @pxref{Example 7}). @node datstr and strdat, aduphys and physadu, timstr and strtim, conversion The next two functions convert between Julian dates and ASCII strings. Julian dates as defined by astronomers begin at noon GMT; these begin at midnight local time. @c @group @unnumberedsubsec datstr @findex datstr @cindex date (conversion to and from string) @cindex Julian date (conversion to and from string) @cindex string (conversion to and from Julian date) @cindex conversion between Julian date and string @example char *datstr(WFDB_Date @var{date}) @end example @noindent @strong{Return:} @table @asis @item @t{(char *)} pointer to a string that represents the date @end table @c @end group @noindent This function converts the Julian date represented by @var{date} into an ASCII string in the form @var{DD/MM/YYYY}. @c @group @unnumberedsubsec strdat @findex strdat @cindex date (conversion to and from string) @cindex Julian date (conversion to and from string) @cindex string (conversion to and from Julian date) @cindex conversion between Julian date and string @example WFDB_Date strdat(const char *@var{string}) @end example @noindent @strong{Return:} @table @asis @item @t{(WFDB_Date)} Julian date corresponding to the argument @end table @c @end group @noindent This function converts @var{string} into a Julian date. The argument should be in the format used by @code{datstr}; if @var{string} is improperly formatted, @code{strdat} returns zero. Note that dates such as @samp{15/3/89} refer to the first century A.D., not the twentieth. For example, the interval in days between the events commemorated by the French and American national holidays is @code{strdat("14/7/1789")} -- @code{strdat("4/7/1776")}. @sp 2 @node aduphys and physadu, , datstr and strdat, conversion The next four functions convert between analog-to-digital converter (ADC) units and physical units, using as a conversion factor the gain for the specified input signal. The first two (@code{aduphys} and @code{physadu}) are general-purpose functions that convert absolute levels (i.e., they account for non-zero @code{baseline} values); the last two (@code{adumuv} and @code{muvadu}) are for use with millivolt-dimensioned signals only, and convert potential differences (i.e., @code{adumuv(@var{s}, 0)} = @code{muvadu(@var{s}, 0)} = 0 for all @var{s}, irrespective of the @code{baseline} values specified in the header file). Normally, these functions should be invoked after @code{isigopen} or @code{wfdbinit}, either of which will determine the gain from the @file{hea} file. If this is not done, or if the header file indicates that the gain is uncalibrated, or if the specified input signal is not currently open, a gain of @code{WFDB_DEFGAIN} (defined in @file{}) ADC units per millivolt, and a baseline of zero, are assumed. If the physical units @ifnotinfo (@pxref{WFDB_Siginfo structures, , Signal Information Structures}) @end ifnotinfo @ifinfo (@pxref{WFDB_Siginfo structures}) @end ifinfo are not millivolts, @code{adumuv} and @code{muvadu} convert to and from thousandths of the defined physical units. Note that @code{adumuv} and @code{muvadu} deal exclusively with integers, but @code{aduphys} returns and @code{physadu} accepts double-precision floating point physical values. @c @group @unnumberedsubsec aduphys @findex aduphys (6.0) @cindex adu (conversion to and from physical units) @cindex physical units (conversion to and from adus) @cindex conversion between adus and physical units @example double aduphys(WFDB_Signal @var{s}, WFDB_Sample @var{a}) @end example @noindent @strong{Return:} @table @asis @item @t{(double)} physical value corresponding to a sample value of @var{a} ADC units @end table @c @end group @noindent This function converts the sample value @var{a} from ADC units to physical units, based on the @code{gain} and @code{baseline} for input signal @var{s}. (@code{aduphys} was first introduced in WFDB library version 6.0.) @c @group @unnumberedsubsec physadu @findex physadu (6.0) @cindex adu (conversion to and from physical units) @cindex physical units (conversion to and from adus) @cindex conversion between adus and physical units @example WFDB_Sample physadu(WFDB_Signal @var{s}, double @var{v}) @end example @noindent @strong{Return:} @table @asis @item @t{(WFDB_Sample)} sample value, in ADC units, corresponding to @var{v}, in physical units @end table @c @end group @noindent This function converts the value @var{v} from physical units to ADC units, based on the @code{gain} and @code{baseline} for input signal @var{s}. (@code{physadu} was first introduced in WFDB library version 6.0.) @c @group @unnumberedsubsec adumuv @findex adumuv @cindex adu (conversion to and from voltage) @cindex voltage (conversion to and from adus) @cindex conversion between adus and voltage @example int adumuv(WFDB_Signal @var{s}, WFDB_Sample @var{a}) @end example @noindent @strong{Return:} @table @asis @item @t{(int)} number of microvolts corresponding to @var{a} ADC units @end table @c @end group @noindent This function converts the potential difference @var{a} from ADC units to microvolts, based on the @code{gain} for input signal @var{s}. @c @group @unnumberedsubsec muvadu @findex muvadu @cindex adu (conversion to and from voltage) @cindex voltage (conversion to and from adus) @cindex conversion between adus and voltage @example WFDB_Sample muvadu(WFDB_Signal @var{s}, int @var{v}) @end example @noindent @strong{Return:} @table @asis @item @t{(int)} number of ADC units corresponding to @var{v} microvolts @end table @c @end group @noindent This function converts the potential difference @var{v} from microvolts to ADC units, based on the @code{gain} for input signal @var{s}. @page @node calibration, miscellaneous functions, conversion, Functions @section Calibration Functions @cindex calibration functions @cindex calibration list Functions in this section are used to determine specifications for calibration pulses and customary scales for plotting signals. All of them make use of the @dfn{calibration list}, which is maintained in memory and which contains entries for various types of signals. @menu * calopen:: read a calibration file into list * getcal:: retrieve calibration data from list * putcal:: append calibration data to list * newcal:: write calibration list to a file * flushcal:: discard contents of calibration list @end menu @c @group @node calopen, getcal, calibration, calibration @unnumberedsubsec calopen @findex calopen (6.0) @cindex calibration file (reading) @cindex reading calibration files @cindex @code{WFDBCAL} (environment variable) @example int calopen(const char *@var{file}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: insufficient memory for calibration list @item @t{-2} Failure: unable to open calibration file @end table @c @end group @noindent This function reads the specified calibration @var{file} (which must be located in one of the directories specified by @code{WFDB}, @pxref{WFDB path}) into the calibration list. If @var{file} is @code{NULL}, the file named by @code{WFDBCAL} is read. Normally, the current contents of the calibration list are discarded before reading the calibration file; if @var{file} begins with @samp{+}, however, the @samp{+} is stripped from the file name and the contents of the file are appended to the current calibration list. If @var{file} is @samp{-}, @code{calopen} reads the standard input rather than a calibration file. (This function was first introduced in WFDB library version 6.0.) @c @group @node getcal, putcal, calopen, calibration @unnumberedsubsec getcal @findex getcal (6.0) @cindex calibration (retrieving) @cindex retrieving calibration data @example int getcal(const char *@var{desc}, const char *@var{units}, WFDB_Calinfo *@var{cal}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success; @code{*@var{cal}} contains the requested data @item @t{-1} Failure: no match found @end table @c @end group @noindent This function attempts to find calibration data for signals of type @var{desc}, having physical units as given by @var{units}. If successful, it fills in the contents of the @code{WFDB_Calinfo} structure @ifnotinfo (@pxref{WFDB_Calinfo structures, , Calibration Information Structures}) @end ifnotinfo @ifinfo (@pxref{WFDB_Calinfo structures}) @end ifinfo pointed to by @var{cal}. The caller must allocate storage for the @code{WFDB_Calinfo} structure, and must not modify the contents of the strings addressed by the @code{sigtype} and @code{units} fields of the @code{WFDB_Calinfo} structure after @code{getcal} returns. @code{getcal} returns data from the first entry in the calibration list that contains a @code{sigtype} field that is either an exact match or a prefix of @var{desc}, and a @code{units} field that is an exact match of @var{units}; if either @var{desc} or @var{units} is @code{NULL}, however, it is ignored for the purpose of finding a match. @code{getcal} cannot succeed unless the calibration list has been initialized by a previous invocation of @code{calopen} or @code{putcal}. (This function was first introduced in WFDB library version 6.0.) @c @group @node putcal, newcal, getcal, calibration @unnumberedsubsec putcal @findex putcal (6.0) @cindex calibration (storing) @cindex storing calibration data @example int putcal(const WFDB_Calinfo *@var{cal}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: insufficient memory @end table @c @end group @noindent This function adds the @code{WFDB_Calinfo} structure pointed to by @var{cal} to the end of the calibration list. (This function was first introduced in WFDB library version 6.0.) @c @group @node newcal, flushcal, putcal, calibration @unnumberedsubsec newcal @findex newcal (6.0) @cindex calibration list (writing) @cindex writing calibration list @example int newcal(const char *@var{file}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: unable to open @var{file} @end table @c @end group @noindent This function creates a new calibration @var{file} (in the current directory) containing the contents of the calibration list (which is not modified). @var{file} must satisfy the standard conditions for a WFDB file name, i.e., it may contain letters, digits, or underscores. (This function was first introduced in WFDB library version 6.0.) @c @group @node flushcal, , newcal, calibration @unnumberedsubsec flushcal @findex flushcal (6.0) @cindex calibration list (discarding) @cindex discarding calibration list @cindex emptying calibration list @cindex flushing calibration list @example void flushcal() @end example @c @end group @noindent This function discards the current calibration list and returns the memory that it occupied to the heap. Note that @code{wfdbquit} does @emph{not} perform the function of @code{flushcal}. (This function was first introduced in WFDB library version 6.0.) @page @node miscellaneous functions, memory allocation macros, calibration, Functions @section Miscellaneous WFDB Functions @menu * newheader:: Creating a @file{hea} file for a new WFDB record. * setheader:: Creating or changing a @file{hea} file. * setmsheader:: Creating a @file{hea} for a multi-segment record. * getseginfo:: Get information about segments of a multi-segment record. * wfdbquit:: Closing WFDB files. * iannclose and oannclose:: Closing annotation files. * wfdbquiet and wfdbverbose:: Suppressing error messages from the WFDB library. * wfdberror:: Retrieving error messages from the WFDB library. * wfdbmemerr:: Setting WFDB library behavior on memory errors. * sampfreq:: Reading the sampling frequency of a WFDB record. * setsampfreq:: Setting the sampling frequency. * setbasetime:: Setting the base time. * findsig:: Finding a signal by name. * counter conversion:: Functions for reading and setting counter conversion parameters. * setwfdb:: Dynamically changing the database path. * getwfdb:: Reading the database path. * resetwfdb:: Restoring the initial database path. * wfdbfile:: Obtaining the pathname of a WFDB file. * wfdbflush:: Flushing buffered output annotations and samples. * getinfo:: Reading info strings from a @file{hea} file. * putinfo:: Writing info strings into a @file{hea} file. * setinfo:: Opening an info file for output. * wfdb_freeinfo:: Freeing memory allocated for info strings. * setibsize:: Setting the default input buffer size. * setobsize:: Setting the default output buffer size. * wfdbgetskew:: Reading intersignal skew. * wfdbsetskew:: Recording intersignal skew. * wfdbgetstart:: Reading the prolog size in a signal file. * wfdbsetstart:: Recording the prolog size in a signal file. * wfdbputprolog:: Writing a prolog to a signal file. @end menu @c @group @node newheader, setheader, miscellaneous functions, miscellaneous functions @unnumberedsubsec newheader @findex newheader @cindex header files (creating) @cindex creating header files @example int newheader(char *@var{record}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: unable to create header file @end table @c @end group @cindex record names (restrictions) @noindent This function creates a @file{hea} file (in the current directory, unless @var{record} includes path information). Use @code{newheader} just after you have finished writing the signal files, but before calling @code{wfdbquit}. If @var{record} begins with @samp{+}, the @samp{+} is discarded and the remainder of @var{record} is taken as the record name. Otherwise, all of @var{record} (excluding any path information) is taken to be the record name. If the record name is @samp{-}, the header file is written to the standard output. Record names may include letters in lower or upper case, digits, and underscores (@samp{_}); they may not include any other characters. If @var{record} does not conform to these requirements, @code{newheader} will return @ifnotinfo @minus{}1; @end ifnotinfo @ifinfo -1; @end ifinfo @pxref{Example 8}, for an illustration of the use of @code{newheader} to check the validity of a record name. For compatibility with the widest range of operating systems, keep record names short (6 characters or less) and avoid those that are distinguished by case alone. To avoid confusion with MIT DB and AHA DB records, do not use three- or four-digit record names. After calling @code{newheader}, you can call @code{putinfo} to add info strings to the header file. To close the header file, along with output signal files, and check that they were written successfully, invoke @code{osigfopen(NULL, 0)} @ifnotinfo (@pxref{osigfopen, , @code{osigfopen}}). @end ifnotinfo @ifinfo (@pxref{osigfopen}). @end ifinfo @c @group @node setheader, setmsheader, newheader, miscellaneous functions @unnumberedsubsec setheader @findex setheader (5.0) @cindex header files (creating) @cindex header files (modifying) @cindex creating header files @cindex modifying header files @example int setheader(char *@var{record}, const WFDB_Siginfo *@var{siarray}, unsigned int @var{nsig}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: unable to create header file @end table @c @end group @noindent This function creates or recreates a header file (in the current directory) for the specified @var{record}, based on the contents of the first @var{nsig} members of @var{siarray}. The preferred way to create a header file for a new record is using @code{newheader}, which records signal checksum and length variables maintained by @code{putvec}. The intended use of @code{setheader} is for editing header files, e.g., to change recorded signal gains from a calibration program, or to add signal descriptions or ``info'' strings. In the following code fragment, the header file for record @file{old} is used to create a header file for record @file{new}: @example @dots{} int nsig, status; WFDB_Siginfo *s; nsig = isigopen("old", NULL, 0); s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); nsig = isigopen("old", s, -nsig); if (nsig > 0) @{ s[0].gain = 100.0; status = setheader("new", s, (unsigned int)nsig); @} @dots{} @end example @noindent The header file for record @file{new} will contain the same signal information as that for record @file{old}, except that the @code{gain} for signal 0 will have been changed as shown. Any ``info'' strings in the @file{hea} file for record @file{old} must be copied explicitly; @ifnotinfo @pxref{getinfo, , @code{getinfo}}, and @pxref{putinfo, , @code{putinfo}}. @end ifnotinfo @ifinfo @pxref{getinfo}, and @pxref{putinfo}. @end ifinfo (This function was first introduced in WFDB library version 5.0.) To close the header file, along with output signal files, and check that they were written successfully, invoke @code{osigfopen(NULL, 0)} @ifnotinfo (@pxref{osigfopen, , @code{osigfopen}}). @end ifnotinfo @ifinfo (@pxref{osigfopen}). @end ifinfo @c @group @node setmsheader, getseginfo, setheader, miscellaneous functions @unnumberedsubsec setmsheader @findex setmsheader (9.1) @cindex header files (creating) @cindex multi-segment records (creating) @cindex creating header files @example int setmsheader(char *@var{record}, char *@var{snarray}[], unsigned int @var{nsegments}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: illegal record name, or no segments specified, or header not writable @item @t{-2} Failure: segment name too long, or insufficient memory @item @t{-3} Failure: attempt to nest multi-segment records, or unreadable segment header @item @t{-4} Failure: segment length unspecified, or numbers of signals or sampling frequencies don't match between segments @end table @c @end group @noindent This function creates a header file (in the current directory) for a multi-segment @var{record} (@pxref{Multi-Segment Records}. @var{snarray} contains the names of the segments, each of which must be an existing (single-segment) record; @var{nsegments} specifies the number of segments in @var{snarray}. Once a header has been created by @code{setmsheader}, any WFDB application can read the concatenated signal files of the constituent segment simply by opening the multi-segment record (using @code{isigopen} or @code{wfdbinit}). Note that the signal files themselves are not modified in any way, nor are they copied; rather, the other WFDB library functions that read signals (@code{getvec}, @code{getframe}, @code{isigsettime}, and @code{isgsettime}) automatically switch among the signal files of the segments as required. For an example of the use of @code{setmsheader}, see @file{app/wfdbcollate.c} in the WFDB Software Package. (This function was first introduced in WFDB library version 9.1.) To close the header file, along with output signal files, and check that they were written successfully, invoke @code{osigfopen(NULL, 0)} @ifnotinfo (@pxref{osigfopen, , @code{osigfopen}}). @end ifnotinfo @ifinfo (@pxref{osigfopen}). @end ifinfo @c @group @node getseginfo, wfdbquit, setmsheader, miscellaneous functions @unnumberedsubsec getseginfo @findex getseginfo (10.5.4) @cindex multi-segment header (reading) @cindex segments (in multi-segment records) @example int getseginfo(WFDB_Segment **@var{psegarray}) @end example @noindent @strong{Return:} @table @asis @item @t{(int)} number of segments belonging to the current input record @end table @c @end group @noindent Invoking @code{getseginfo(@var{psegarray})}, where @var{psegarray} is declared to be of type @code{**WFDB_Seginfo}, sets @var{*psegarray} so that it points to an array of @code{WFDB_Seginfo} structures that describe the segments of the currently open (multi-segment) record. The return value indicates the number of segments (i.e., the number of valid @code{WFDB_Seginfo} structures in @var{*psegarray}. If there is no current input record, or if the current input record is not a multi-segment record, this function returns 0 and does not modify @var{*psegarray}. contain the segments' names, lengths, and starting sample numbers. @c @group @node wfdbquit, iannclose and oannclose, getseginfo, miscellaneous functions @unnumberedsubsec wfdbquit @findex wfdbquit @cindex closing WFDB files @cindex flushing WFDB I/O @cindex I/O (completing) @cindex annotation (canonical order) @cindex canonical order of annotations @example void wfdbquit(void) @end example @c @end group @noindent This function closes all open WFDB files and frees any memory allocated by other WFDB library functions. It also resets the following: @itemize @bullet @item the WFDB path (in versions 10.5.7 and later) @item the factors used for converting between samples, seconds, and counter values (reset to 1), the base time (reset to 0, i.e., midnight), and the base counter value (reset to 0); @ifnotinfo @pxref{timstr and strtim, , @code{timstr} and @code{strtim}} @end ifnotinfo @ifinfo (@pxref{timstr and strtim}) @end ifinfo @item the parameters used for converting between adus and physical units (reset to @code{WFDB_DEFGAIN} adu/mV, a quantity defined in @file{}); @ifnotinfo @pxref{aduphys and physadu, , @code{aduphys} and @code{physadu}} @end ifnotinfo @ifinfo (@pxref{aduphys and physadu}) @end ifinfo @item internal variables used to determine output signal specifications; @ifnotinfo @pxref{newheader, , @code{newheader}}. @end ifnotinfo @ifinfo @pxref{newheader}. @end ifinfo @end itemize If any annotations have been written out-of-order (@pxref{Annotation Order}), this function attempts to run @file{sortann} (see the @cite{WFDB Applications Guide}) as a subprocess to restore the annotations to canonical order. If this cannot be done, it prints a warning message indicating that the annotations are not in order, and providing instructions for putting them in order. Programs that do not write annotations or signals need not use @code{wfdbquit}. Note, however, that several WFDB library functions allocate memory that is maintained for later use by the library. This is not generally a problem, since these functions also free such memory if it is no longer needed on a subsequent call; thus these `memory leaks' do not grow over time. Virtually all operating systems reclaim memory allocated by user-level applications on exit, so that a small and self-limiting leak is not a problem. Nevertheless, there are embedded systems and other environments in which memory is not reclaimed when a user application exits, and in these cases it is best to invoke @code{wfdbquit()} on exit from any WFDB application, even those that do not write output using the library. In an ANSI/ISO C environment, this can be ensured by including the line @example atexit(wfdbquit); @end example @noindent early in the code, before the first exit. @c @group @node iannclose and oannclose, wfdbquiet and wfdbverbose, wfdbquit, miscellaneous functions @unnumberedsubsec iannclose @findex iannclose (9.1) @cindex closing annotation files @cindex annotation (canonical order) @cindex canonical order of annotations @example void iannclose(WFDB_Annotator @var{an}) @end example @c @end group @noindent This function closes the annotation file associated with input annotator @var{an}. It was first introduced in WFDB library version 9.1. @c @group @unnumberedsubsec oannclose @findex oannclose (9.1) @example void oannclose(WFDB_Annotator @var{an}) @end example @c @end group @noindent This function closes the annotation file associated with output annotator @var{an}. It was first introduced in WFDB library version 9.1. If any annotations have been written out-of-order (@pxref{Annotation Order}), this function attempts to run @file{sortann} (see the @cite{WFDB Applications Guide}) as a subprocess to restore the annotations to canonical order. If this cannot be done, it prints a warning message indicating that the annotations are not in order, and providing instructions for putting them in order. @c @group @node wfdbquiet and wfdbverbose, wfdberror, iannclose and oannclose, miscellaneous functions @unnumberedsubsec wfdbquiet @findex wfdbquiet @cindex error suppression @cindex suppressing errors @example @code{void wfdbquiet(void)} @end example @c @end group @noindent This function suppresses error reporting on the standard error output from the WFDB library functions. @c @group @unnumberedsubsec wfdbverbose @findex wfdbverbose (4.0) @example @code{void wfdbverbose(void)} @end example @c @end group @noindent This function can be used to restore normal error reporting after using @code{wfdbquiet}. (This function was first introduced in WFDB library version 4.0.) @c @group @node wfdberror, wfdbmemerr, wfdbquiet and wfdbverbose, miscellaneous functions @unnumberedsubsec wfdberror @findex wfdberror (4.5) @example @code{char *wfdberror(void)} @end example @noindent @strong{Return:} @table @asis @item @t{(char *)} pointer to error string @end table @c @end group @noindent This function returns a pointer to a string containing the text of the most recent WFDB library error message (or to a string containing the WFDB library version number, if there have been no errors). Function @code{wfdberror} is primarily intended for use in applications for which the standard error output is unavailable or inadequate, such as in X Window System applications. (Note that this function may be unnecessary for MS-Windows applications, since the MS-Windows version of the WFDB library generates a message box for error messages, unless @code{wfdbquiet} has been used to silence them.) This function was first introduced in WFDB library version 4.5. Versions earlier than 9.4 return an empty string rather than the library version number if there have been no errors. @c @group @node wfdbmemerr, sampfreq, wfdberror, miscellaneous functions @unnumberedsubsec wfdbmemerr @findex wfdbmemerr (10.4.6) @example @code{void wfdbmemerr(int exit_on_error)} @end example @noindent @c @end group @noindent This function sets how the WFDB library behaves in the event of a memory allocation error. If @var{exit_on_error} is true (any non-zero value), then such an event causes the WFDB library to emit an appropriate error message and then terminate the running program. (This behavior is the default.) If @var{exit_on_error} is false (zero), a subsequent memory allocation error will cause the WFDB library function in which it occurs to continue running if possible (after emitting an error message as above). This function was first introduced in WFDB library version 10.4.6. @c @group @node sampfreq, setsampfreq, wfdbmemerr, miscellaneous functions @unnumberedsubsec sampfreq @findex sampfreq @example WFDB_Frequency sampfreq(char *@var{record}) @end example @noindent @strong{Return:} @table @asis @item @t{(WFDB_Frequency)>0.} Success: the returned value is the sampling frequency in Hz @item @t{(WFDB_Frequency)-1.} Failure: unable to read header file @item @t{(WFDB_Frequency)-2.} Failure: incorrect header file format @end table @c @end group @noindent This function determines the sampling frequency (in Hz) for the record specified by its argument. If its argument is NULL, @code{sampfreq} returns the currently defined sampling frequency, if any. It also sets the internal variables used by the time-conversion functions @ifnotinfo (@pxref{timstr and strtim, , @code{timstr} and @code{strtim}}) @end ifnotinfo @ifinfo (@pxref{timstr and strtim}) @end ifinfo for converting between sample intervals and seconds. @xref{Example 3}, for an illustration of the use of @code{sampfreq}. Note that the value returned by @code{sampfreq} for a multifrequency record depends on the current @code{getvec} mode @ifnotinfo (@pxref{setgvmode, , @code{setgvmode}}). @end ifnotinfo @ifinfo (@pxref{setgvmode}). @end ifinfo @c @group @node setsampfreq, setbasetime, sampfreq, miscellaneous functions @unnumberedsubsec setsampfreq @findex setsampfreq @cindex sampling frequency (changing) @cindex changing sampling frequency @example int setsampfreq(WFDB_Frequency @var{freq}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: illegal frame frequency specified (@var{freq} must not be negative) @end table @c @end group @noindent This function sets the frame frequency used by the time-conversion functions @ifnotinfo (@pxref{timstr and strtim, , @code{timstr} and @code{strtim}}). @end ifnotinfo @ifinfo (@pxref{timstr and strtim}). @end ifinfo Use @code{setsampfreq} before creating a new @file{hea} file @ifnotinfo (@pxref{newheader, , @code{newheader}}). @end ifnotinfo @ifinfo (@pxref{newheader}). @end ifinfo Note that despite the name of this function, the argument specifies the record @emph{frame} frequency, not the @emph{sampling} frequency; the two are not equivalent when reading or writing a multi-frequency record (@pxref{Multi-Frequency Records}). @xref{Example 8}, for an illustration of the use of @code{setsampfreq}. @c @group @node setbasetime, findsig, setsampfreq, miscellaneous functions @unnumberedsubsec setbasetime @findex setbasetime @cindex current time @cindex time of day (setting) @cindex base time (setting) @example int setbasetime(char *@var{string}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: incorrect string format @end table @c @end group @noindent This function sets the base time used by the time-conversion functions @code{timstr} and @code{strtim}. Its argument is a null-terminated ASCII string in @var{HH:MM:SS} format. An optional base date in @var{dd/mm/yyyy} format can follow the time in @var{string}; if present, the date should be separated from the time by a space or tab character. If @var{string} is empty or @code{NULL}, the current date and time are read from the system clock. Use @code{setbasetime} after defining the sampling frequency and before creating a header file. (In versions 10.5.8 and later, it is not necessary to define the sampling frequency first.) @ifnotinfo (@pxref{newheader, , @code{newheader}}). @end ifnotinfo @ifinfo (@pxref{newheader}). @end ifinfo @xref{Example 8}, for an illustration of the use of @code{setbasetime}. There is no @code{getbasetime} function; use @code{mstimstr(0)} at any time after opening a record to convert the base date and time into a string. @sp 2 @c @group @node findsig, counter conversion, setbasetime, miscellaneous functions @unnumberedsubsec findsig @findex findsig (10.4.12) @cindex signal name @cindex signal number @cindex finding signal by name @cindex signal lookup @example int findsig(const char *@var{string}) @end example @noindent @strong{Return:} @table @asis @item @t{ WFDB_Signal} Success @item @t{-1} Failure: signal not found @end table @c @end group @noindent This function converts its argument to an input signal number. If @var{string} is numeric and can be interpreted as a valid input signal number, it is taken as such; otherwise, it is assumed to be a signal name, and if it is an exact match to the @code{desc} field of a currently open input signal's @code{siginfo} structure, @code{findsig} returns the corresponding signal number. If two or more signals have identical matching names, @code{findsig} returns the lowest matching signal number. @sp 2 @c @group @node counter conversion, setwfdb, findsig, miscellaneous functions @cindex base counter value @cindex counter (base) @cindex counter frequency @cindex counter value @cindex frequency (counter) @cindex tape counter Database records are sometimes obtained from analog tapes for which a tape counter is available. Since many analog tape recorders lack elapsed time indicators, it is often useful to identify events in the analog tape using counter values. A similar situation may arise if a chart recording or other hard copy with numbered pages is to be compared with a database record. To simplify cross-referencing between the analog tape or chart and the digital database record, the WFDB library supports conversion of counter values (or page numbers) to time. For this to be possible, the counter must be linear (i.e., it must change at the same rate throughout the tape; this is not true of those that count the number of revolutions of the supply or take-up reel), and the base counter value (the counter value or page number corresponding to sample 0) and the counter frequency (the difference between counter values separated by a one-second interval, or the reciprocal of the number of seconds per page) must be defined. The following four functions, first introduced in WFDB library version 5.2, are used to obtain or set the values of these parameters. @unnumberedsubsec getcfreq @findex getcfreq (5.2) @example WFDB_Frequency getcfreq(void) @end example @noindent @strong{Return:} @table @asis @item @t{(WFDB_Frequency)} the counter frequency in Hz @end table @c @end group @noindent This function returns the currently-defined counter frequency. The counter frequency is set by the functions that read header files, or by @code{setcfreq}. If the counter frequency has not been defined explicitly, @code{getcfreq} returns the sampling frequency. @c @group @unnumberedsubsec setcfreq @findex setcfreq (5.2) @example void setcfreq(WFDB_Frequency @var{freq}) @end example @c @end group @noindent This function sets the counter frequency. Use @code{setcfreq} before creating a @file{hea} file @ifnotinfo (@pxref{newheader, , @code{newheader}}). @end ifnotinfo @ifinfo (@pxref{newheader}). @end ifinfo The effect of @code{setcfreq} is nullified by later invoking any of the functions that read header files. If @var{freq} is zero or negative, the counter frequency is treated as equivalent to the sampling frequency. @c @group @unnumberedsubsec getbasecount @findex getbasecount (5.2) @example double getbasecount(void) @end example @noindent @strong{Return:} @table @asis @item @t{(double)} base counter value @end table @c @end group @noindent This function returns the base counter value, which is set by the functions that read header files, or by @code{setbasecount}. If the base counter value has not been set explicitly, @code{getbasecount} returns zero. @c @group @unnumberedsubsec setbasecount @findex setbasecount (5.2) @example void setbasecount(double @var{count}) @end example @c @end group @noindent This function sets the base counter value. Use @code{setbasecount} before creating a header file @ifnotinfo (@pxref{newheader, , @code{newheader}}). @end ifnotinfo @ifinfo (@pxref{newheader}). @end ifinfo The effect of @code{setbasecount} is nullified by later invoking any of the functions that read @file{hea} files. @c @group @node setwfdb, getwfdb, counter conversion, miscellaneous functions @unnumberedsubsec setwfdb @findex setwfdb @cindex database path (changing) @cindex changing the WFDB path @cindex WFDB path @example void setwfdb(const char *@var{string}) @end example @c @end group @cindex NETFILES @noindent This function may be used to set or change the database path (@pxref{WFDB path}) within a running program. The argument points to a null-terminated string that specifies the desired database path (but see the next paragraph for an exception). The string contains a list of locations where input files may be found. These locations may be absolute directory names (such as @samp{/usr/local/database} under Unix, or @samp{d:/database} under MS-DOS), relative directory names (e.g., @code{../mydata}), or URL prefixes (e.g., @samp{http://physio@-net.org/physio@-bank/database}). If NETFILES support is unavailable, any URL prefixes in the string are ignored. The special form @samp{.} refers to the current directory. Entries in the list may be separated by whitespace or by semicolons; under Unix, colons may also be used as separators. An empty component, indicated by an initial or terminal separator, or by two consecutive separators, will be understood to specify the current directory (which may also be indicated by a component consisting of a single @samp{.}). If the string is empty or @code{NULL}, the database path is limited to the current directory. @cindex WFDB path @cindex indirect WFDB path @cindex file containing WFDB path @cindex database path file (indirect) If @var{string} begins with @samp{@@}, the remaining characters of @var{string} are taken as the name of a file from which the WFDB path is to be read. This file may contain either the WFDB path, as described in the previous paragraph, or another indirect WFDB path specification. Indirect WFDB path specifications may be nested no more than ten levels deep (an arbitrary limit imposed to avoid infinite recursion). Evaluation of indirect WFDB paths is deferred until @code{getwfdb} is invoked, either explicitly or by the WFDB library while attempting to open an input file (e.g., using @code{annopen} or @code{isigopen}). (The features described in this paragraph were first introduced in WFDB library version 8.0.) @ifinfo @xref{getwfdb} @end ifinfo @ifnotinfo @xref{getwfdb, , @code{getwfdb}} @end ifnotinfo for an example of the use of @code{setwfdb}. @c @group @node getwfdb, resetwfdb, setwfdb, miscellaneous functions @unnumberedsubsec getwfdb @findex getwfdb @cindex database path (reading) @cindex reading the WFDB path @cindex WFDB path @example char *getwfdb(void) @end example @noindent @strong{Return:} @table @asis @item @t{(char *)} pointer to the database path string @end table @c @end group @noindent This function returns the current database path. For example, this code fragment @example @dots{} char *oldp, *newp; oldp = getwfdb(); if (newp = malloc(strlen("/usr/mydb;") + strlen(oldp) + 1)) @{ sprintf(newp, "/usr/mydb;%s", oldp); setwfdb(newp); @} @dots{} @end example @noindent adds the directory @samp{/usr/mydb} to the beginning of the database path. (The standard @samp{/} directory separator can be used, even under MS-DOS; if you elect to use the alternate @samp{\}, remember to quote it within a C string as @samp{\\}.) @c @group @node resetwfdb, wfdbfile, getwfdb, miscellaneous functions @unnumberedsubsec resetwfdb @findex resetwfdb (10.5.7) @cindex WFDB path (resetting) @example void resetwfdb(void) @end example @noindent @c @end group @noindent This function restores the WFDB path to its initial value (either the first value returned by @code{getwfdb} in the current process, or @code{NULL}). @c @group @node wfdbfile, wfdbflush, getwfdb, miscellaneous functions @unnumberedsubsec wfdbfile @findex wfdbfile (4.3) @cindex filenames of WFDB files (obtaining) @cindex pathnames of WFDB files (obtaining) @cindex NETFILES @cindex WFDB path @example char *wfdbfile(const char *@var{type}, char *@var{record}) @end example @noindent @strong{Return:} @table @asis @item @t{(char *)} pointer to a filename, or @code{NULL} @end table @c @end group @noindent This function attempts to locate an existing WFDB file by searching the database path (@pxref{WFDB path}). Normally, the file is specified by its @var{type} (e.g., @file{hea}, or an annotator name such as @file{atr}) and by the @var{record} to which it belongs. A file that does not include a record name as part of its name can be found by @code{wfdbfile} if the name is passed in the @var{type} variable and @var{record} is @code{NULL}. The string returned by @code{wfdbfile} includes the appropriate component of the database path; since the database path may include empty or non-absolute components, the string is not necessarily an absolute pathname. If the WFDB library has been compiled with NETFILES support, and the WFDB path includes one or more URL prefixes, the string returned may be a URL rather than a pathname. If the file cannot be found, @code{wfdbfile} returns @code{NULL}. (This function was first introduced in WFDB library version 4.3.) @c @group @node wfdbflush, getinfo, wfdbfile, miscellaneous functions @unnumberedsubsec wfdbflush @findex wfdbflush @cindex flushing output annotations and samples @example void wfdbflush(void) @end example @c @end group @noindent This function brings database output files up-to-date by forcing any output annotations or samples that are buffered to be written to the output files. @c @group @node getinfo, putinfo, wfdbflush, miscellaneous functions @unnumberedsubsec getinfo @findex getinfo (4.0) @cindex header info (reading) @cindex info (in header files) @example char *getinfo(char *@var{record}) @end example @noindent @strong{Return:} @table @asis @item @t{(char *)} pointer to an ``info'' string, or @code{NULL} @end table @c @end group @noindent If @var{record} is not @code{NULL}, @code{getinfo} reads the first ``info'' string for @var{record}. If @var{record} is @code{NULL}, then @code{getinfo} reads the next available info string for the currently open record. Info strings are null-terminated and do not contain newline characters. Some records may contain no info strings; others may contain more than one info string. They may be stored within @file{hea} or @file{info} files; if any are contained in a record's @file{hea} file, they are returned first. @noindent For example, the following code fragment may be used to read and print all of the info for record @file{100s}: @example @dots{} char *info; if (info = getinfo("100s")) do @{ puts(info); @} while (info = getinfo(NULL)); @dots{} @end example @noindent If the @file{hea} file was opened by another WFDB function, such as @code{isigopen}, @code{annopen}, @code{sampfreq}, or @code{wfdbinit}, the @var{record} argument can be @code{NULL} even for the first @code{getinfo}, like this: @example @dots{} char *info; WFDB_Frequency sps; if (sps = sampfreq("100s") while (info = getinfo(NULL)) puts(info); @dots{} @end example (This function was first introduced in WFDB library version 4.0.) @c @group @node putinfo, setinfo, getinfo, miscellaneous functions @unnumberedsubsec putinfo @findex putinfo (4.0) @cindex header info (writing) @cindex info (in header files) @example int putinfo(const char *@var{s}) @end example @noindent @strong{Return:} @table @asis @item @t{ 0} Success @item @t{-1} Failure: header not initialized @end table @c @end group @noindent This function writes @var{s} as an ``info'' string associated with the current output record. If @code{setinfo} has been called more recently than @code{newheader} or @code{setheader}, the info string is written to the @file{info} file; otherwise, it is written to the @file{hea} file, unless none of these three functions has been called (which results in an error). The string argument, @var{s}, must be null-terminated and should not contain newline characters. No more than 254 characters may be written in a single invocation of @code{putinfo}. Two or more info strings may be written to the same header or info file by successive invocations of @code{putinfo}. (This function was first introduced in WFDB library version 4.0.) @c @group @node setinfo, wfdb_freeinfo, putinfo, miscellaneous functions @unnumberedsubsec setinfo @findex setinfo (10.5.11) @cindex info file (creating) @example int setinfo(char *@var{record}) @end example @noindent @strong{Return:} @table @asis @item @t{0} Success @item @t{-1} Failure: illegal record name, or info file could not be opened @item @t{-2} Failure: error writing info file (only if @var{record} is @code{NULL}) @end table @c @end group @noindent This function opens the @file{info} file for the specified @var{record} for writing using @code{putinfo}. If @var{record}.@code{info} does not exist in the current directory, it is created; otherwise, it is opened for appending (i.e., so that anything @code{putinfo} writes is added at the end of the file rather than overwriting any existing contents). The file opened by @code{setinfo} can be used to store arbitrary information associated with @var{record}, without altering @var{record}'s header file. (@code{setinfo} was first introduced in WFDB library version 10.5.11.) To close the @file{info} file and check that it was written successfully, invoke @code{setinfo(NULL)}. (Prior to WFDB library version 10.7.0, this would always return 0.) @c @group @node wfdb_freeinfo, setibsize, setinfo, miscellaneous functions @unnumberedsubsec wfdb_freeinfo @findex wfdb_freeinfo (10.5.11) @cindex info (memory deallocation) @cindex memory deallocation @cindex freeing memory @example void wfdb_freeinfo() @end example @c @end group @noindent This function releases memory allocated by @code{getinfo}, and closes the file opened by @code{setinfo}, if any. After calling it, @code{getinfo} behaves as it does on its initial call, (re)reading the info for the specified record (or the currently open record, if no record is specified). @code{wfdb_freeinfo} is invoked by @code{wfdbquit}. (This function was first introduced in WFDB library version 10.5.11.) @c @group @node setibsize, setobsize, wfdb_freeinfo, miscellaneous functions @unnumberedsubsec setibsize @findex setibsize (5.0) @cindex buffer size (setting) @cindex input buffer size @cindex @code{getvec} buffer size @example int setibsize(int @var{size}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success: the returned value is the new input buffer size in bytes @item @t{-1} Failure: buffer size could not be changed @item @t{-2} Failure: illegal value for @var{size} @end table @c @end group @noindent This function can be used to change the default size of the input buffers allocated by @code{getvec}. It cannot be used while input signals are open (i.e., after invoking @code{isigopen} or @code{wfdbinit} and before invoking @code{wfdbquit}). If @var{size} is positive, the default input buffers will be @var{size} bytes; if @var{size} is zero, the system default buffer size (@code{BUFSIZ}) is used. Note that the default buffer size has no effect on reading signals for which an explicit buffer size is given in the header file, i.e., those for which the @code{bsize} field of the @code{WFDB_Siginfo} structure @ifnotinfo (@pxref{WFDB_Siginfo structures, , Signal Information Structures}) @end ifnotinfo @ifinfo (@pxref{WFDB_Siginfo structures}) @end ifinfo is non-zero. (This function was first introduced in WFDB library version 5.0.) @c @group @node setobsize, wfdbgetskew, setibsize, miscellaneous functions @unnumberedsubsec setobsize @findex setobsize (5.0) @cindex buffer size (setting) @cindex output buffer size @cindex @code{putvec} buffer size @example int setobsize(int @var{size}) @end example @noindent @strong{Return:} @table @asis @item @t{>0} Success: the returned value is the new output buffer size in bytes @item @t{-1} Failure: buffer size could not be changed @item @t{-2} Failure: illegal value for @var{size} @end table @c @end group @noindent This function can be used to change the default size of the output buffers allocated by @code{putvec}. It cannot be used while output signals are open (i.e., after invoking @code{osigopen} or @code{osigfopen} and before invoking @code{wfdbquit}). If @var{size} is positive, the default output buffers will be @var{size} bytes; if @var{size} is zero, the system default buffer size (@code{BUFSIZ}) is used. Note that the default buffer size has no effect on writing signals for which an explicit buffer size is given in the @file{hea} file read by @code{osigopen}, or in the @code{bsize} field of the @code{WFDB_Siginfo} structure @ifnotinfo (@pxref{WFDB_Siginfo structures, , Signal Information Structures}) @end ifnotinfo @ifinfo (@pxref{WFDB_Siginfo structures}) @end ifinfo passed to @code{osigfopen}. (This function was first introduced in WFDB library version 5.0.) @c @group @node wfdbgetskew, wfdbsetskew, setobsize, miscellaneous functions @unnumberedsubsec wfdbgetskew @findex wfdbgetskew (9.4) @cindex intersignal skew @cindex skew @example int wfdbgetskew(WFDB_Signal @var{s}) @end example @noindent @strong{Return:} @table @asis @item @t{(int)} the skew (in frames) for input signal @var{s} @end table @c @end group @noindent This function returns the @dfn{skew} (as recorded in the @file{hea} file, but in frame intervals rather than in sample intervals) of the specified input signal, or 0 if @var{s} is not a valid input signal number. Since sample vectors returned by @code{getvec} or @code{getframe} are already corrected for skew, @code{wfdbgetskew} is useful primarily for programs that need to rewrite existing @file{hea} files, where it is necessary to preserve the previously recorded skews. The following code fragment demonstrates how this can be done: @example char *record; int nsig; WFDB_Signal s; static WFDB_Siginfo *si; @dots{} if ((nsig = isigopen(record, NULL, 0)) < 1) exit(1); si = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); if (si == NULL || isigopen(record, siarray, nsig)!= nsig) exit(1); for (s = 0; s < nsig; s++) @{ wfdbsetskew(s, wfdbgetskew(s)); wfdbsetstart(s, wfdbgetstart(s)); @} setheader(record, siarray, (unsigned)nsig); @end example @noindent Note that this function does not @emph{determine} the skew between signals; the problem of doing so is not possible to solve in the general case. @code{wfdbgetskew} merely reports what has previously been determined by other means and recorded in the header file for the input record. (This function was first introduced in WFDB library version 9.4.) @c @group @node wfdbsetskew, wfdbgetstart, wfdbgetskew, miscellaneous functions @unnumberedsubsec wfdbsetskew @findex wfdbsetskew (9.4) @cindex intersignal skew @cindex skew @example void wfdbsetskew(WFDB_Signal @var{s}, int @var{skew}) @end example @c @end group @noindent This function sets the specified @var{skew} (in frames) to be recorded by @code{newheader} or @code{setheader} for signal @var{s}. For an example of the use of @code{wfdbsetskew}, @pxref{wfdbgetskew}. Note that @code{wfdbsetskew} has no effect on the skew correction performed by @code{getframe} (or @code{getvec}), which is determined solely by the skews that were recorded in the header file at the time the input signals were opened. (This function was first introduced in WFDB library version 9.4.) @c @group @node wfdbgetstart, wfdbsetstart, wfdbsetskew, miscellaneous functions @unnumberedsubsec wfdbgetstart @findex wfdbgetstart (9.4) @cindex byte offset @cindex cruft (in signal files) @cindex prolog (in signal files) @cindex start of sample data @example long wfdbgetstart(WFDB_Signal @var{s}) @end example @noindent @strong{Return:} @table @asis @item @t{(long)} the length of the prolog of the file that contains input signal @var{s} @end table @c @end group @noindent This function returns the number of bytes in the @dfn{prolog} of the signal file that contains the specified input signal, as recorded in the header file. Note that @code{wfdbgetstart} does not @emph{determine} the length of the prolog by inspection of the signal file; it merely reports what has been determined by other means and recorded in the @file{hea} file. Since the prolog is not readable using the WFDB library, and since functions such as @code{isigopen} and @code{isigsettime} take the prolog into account when calculating byte offsets for @code{getframe} and @code{getvec}, @code{wfdbgetstart} is useful primarily for programs that need to rewrite existing @file{hea} files, where it is necessary to preserve the previously recorded byte offsets. For an example of how this can be done, @pxref{wfdbgetskew}. (This function was first introduced in WFDB library version 9.4.) @c @group @node wfdbsetstart, wfdbputprolog, wfdbgetstart, miscellaneous functions @unnumberedsubsec wfdbsetstart @findex wfdbsetstart (9.4) @cindex byte offset @cindex cruft (in signal files) @cindex prolog (in signal files) @cindex start of sample data @example void wfdbsetstart(WFDB_Signal @var{s}, long @var{bytes}) @end example @c @end group @noindent This function sets the specified prolog length (@var{bytes}) to be recorded by @code{newheader} or @code{setheader} for signal @var{s}. For an example of the use of @code{wfdbsetstart}, @pxref{wfdbgetskew}. Note that @code{wfdbsetstart} has no effect on the calculations of byte offsets within signal files as performed by @code{isigsettime}, which are determined solely by the contents of the @file{hea} file at the time the signals were opened. (This function was first introduced in WFDB library version 9.4.) @c @group @node wfdbputprolog, , wfdbgetstart, miscellaneous functions @unnumberedsubsec wfdbputprolog @findex wfdbputprolog (10.4.15) @cindex byte offset @cindex cruft (in signal files) @cindex prolog (in signal files) @cindex start of sample data @example void wfdbputprolog(const char *@var{prolog}, long @var{bytes}, WFDB_Signal @var{s}) @end example @c @end group @noindent This function writes the specified @var{prolog} of length @var{bytes} to the signal file for the specified output signal @var{s}, and invokes @code{wfdbsetstart(}@var{s, bytes}@code{)}. (This function was first introduced in WFDB library version 10.4.15.) @page @node memory allocation macros, , miscellaneous functions, Functions @section memory allocation macros @menu * MEMERR:: Handling a memory allocation error. * SFREE:: Safely release allocated memory. * SUALLOC:: Safely allocate memory using a new pointer. * SALLOC:: Safely allocate memory, reusing a pointer. * SREALLOC:: Safely expand a chunk of allocated memory. * SSTRCPY:: Safely copy a string into a new buffer. @end menu These macros use the standard ANSI/ISO C functions @code{calloc()}, @code{realloc()}, @code{free()}, @code{strlen()}, and @code{strcpy()} to handle dynamic memory allocation tasks for the WFDB library. They can also be used by applications that include @file{wfdb/wfdb.h}, where they are defined. These macros provide safe handling of insufficient memory and double free errors (either condition results in a descriptive error message, which by default is followed by an @code{exit(1)} to end the process with a signal to the parent shell or other process). @c @group @node MEMERR, SFREE, memory allocation macros, memory allocation macros @unnumberedsubsec MEMERR @findex MEMERR (10.5.0) @cindex insufficient memory @cindex memory errors @cindex allocation errors @example MEMERR(@var{object_name}, size_t @var{n_elements}, size_t @var{element_size}) @end example @c @end group @noindent This macro uses @code{wfdb_error} to send a short error message of the form @code{WFDB: can't allocate (}@var{n_elements}*@var{element_size} @code{) bytes for }@var{object_name}@code{)}. Unless @code{wfdbmemerr(1)} has been invoked previously, the process that invoked @code{MEMERR} exits immediately. @c @group @node SFREE, SUALLOC, MEMERR, memory allocation macros @unnumberedsubsec SFREE @findex SFREE (10.5.0) @cindex release allocated memory @cindex freeing allocated memory @cindex memory deallocation @example SFREE(@var{object} *@var{pointer}) @end example @c @end group @noindent This macro releases memory previously allocated to the @var{object} addressed by the specified @var{pointer}, somewhat more safely than by invoking the standard @code{free()} function. On completion, @var{pointer} is set to NULL. @code{SFREE} does nothing if @var{pointer} is initially NULL (unlike @code{free()}, which may cause the process to crash). If @code{SFREE} receives a non-NULL pointer, it passes that pointer to @code{free()}, which may cause a crash if the pointer does not point to a block of memory that was previously allocated using one of the macros below, or directly using @code{malloc()}, @code{calloc()}, or @code{realloc()}. @c @group @node SUALLOC, SALLOC, SFREE, memory allocation macros @unnumberedsubsec SUALLOC @findex SUALLOC (10.5.0) @cindex allocating memory @cindex memory allocation @example SUALLOC(@var{object_name}, size_t @var{n_elements}, size_t @var{element_size}) @end example @c @end group @noindent This macro allocates memory sufficient for @var{n_elements} items of @var{element_size} bytes each, and sets the pointer given by @var{object_name} to point to the allocated memory. If there is not enough available memory, @code{SUALLOC} invokes @code{MEMERR} (above). The newly allocated memory block is filled with zeroes. @code{SUALLOC} does not check to see if @var{object_name} already points to allocated memory, which will lead to memory leaks if so. @c @group @node SALLOC, SREALLOC, SUALLOC, memory allocation macros @unnumberedsubsec SALLOC @findex SALLOC (10.5.0) @cindex allocating memory @cindex memory allocation @example SALLOC(@var{object_name}, size_t @var{n_elements}, size_t @var{element_size}) @end example @c @end group @noindent This macro allocates memory sufficient for @var{n_elements} items of @var{element_size} bytes each, and sets the pointer given by @var{object_name} to point to the allocated memory. If there is not enough available memory, @code{SALLOC} invokes @code{MEMERR} (above). The newly allocated memory block is filled with zeroes. Unless @var{object_name} is initially NULL, @code{SALLOC} frees it using @code{SFREE} before allocating the requested memory. @c @group @node SREALLOC, SSTRCPY, SALLOC, memory allocation macros @unnumberedsubsec SREALLOC @findex SREALLOC (10.5.0) @cindex allocating memory @cindex expanding allocated memory @cindex memory allocation @cindex reallocating memory @example SREALLOC(@var{object_name}, size_t @var{n_elements}, size_t @var{element_size}) @end example @c @end group @noindent This macro allocates memory sufficient for @var{n_elements} items of @var{element_size} bytes each, and sets the pointer given by @var{object_name} to point to the allocated memory. If there is not enough available memory, @code{SREALLOC} invokes @code{MEMERR} (above). Use @code{SREALLOC} to expand a previously allocated block of memory, preserving its contents. @code{SREALLOC} usually allocates a new block of the desired size, moving the contents of the previously allocated block into the beginning of the new block and then freeing the original block. Pointers to locations in the original block will no longer be valid in this case. The portion of the newly allocated block that extends beyond the previous contents is uninitialized. If @var{object_name} is initially NULL, @code{SUALLOC}, @code{SALLOC}, and @code{SREALLOC} are functionally equivalent, except that @code{SREALLOC} does not fill the allocated block with zeroes. @c @group @node SSTRCPY, , SREALLOC, memory allocation macros @unnumberedsubsec SSTRCPY @findex SSTRCPY (10.5.0) @cindex allocating strings @cindex string allocation @example SSTRCPY(char *destination, char *source) @end example @c @end group @noindent This macro copies the @var{source} string (including a trailing null character) into newly-allocated memory, and it sets @var{destination} to point to the copy. If @var{destination} is not NULL on entry, @code{SSTRCPY} uses @code{SFREE} to release the previously allocated memory. @node Data Types, Annotation Codes, Functions, Top @chapter Data Types @noindent @emph{Simple data types} used by the WFDB library are defined in @file{}. These include: @table @code @item WFDB_Sample @cindex @code{WFDB_Sample} (defined) a signed integer type (at least 32 bits) used to represent sample values, in units of adus. @item WFDB_Time @cindex @code{WFDB_Time} (defined) a signed integer type (at least 32 bits) used to represent times and time intervals, in units of sample intervals. Only the magnitude is significant; the sign of a @code{WFDB_Time} variable indicates how it is to be printed by @code{timstr} or @code{mstimstr}. The definition of @code{WFDB_Time} depends on whether the @code{WFDB_LARGETIME} macro is defined (@pxref{Large time values}). @item WFDB_Date @cindex @code{WFDB_Date} (defined) a signed integer type (at least 32 bits) used to represent Julian dates, in units of days. @item WFDB_Frequency @cindex @code{WFDB_Frequency} (defined) a floating point type used to represent sampling and counter frequencies, in units of Hz. @item WFDB_Gain @cindex @code{WFDB_Gain} (defined) a floating point type used to represent signal gains, in units of adus per physical unit. @item WFDB_Group @cindex @code{WFDB_Group} (defined) an unsigned integer type used to represent signal group numbers. @item WFDB_Signal @cindex @code{WFDB_Signal} (defined) an unsigned integer type used to represent signal numbers. @item WFDB_Annotator @cindex @code{WFDB_Annotator} (defined) an unsigned integer type used to represent annotator numbers. @end table @noindent @emph{Composite data types} used by the WFDB library are also defined in @file{}. These types, described in detail in the following sections, include: @table @code @item WFDB_Siginfo an object containing the name and global attributes of a given signal. @item WFDB_Calinfo an object containing calibration specifications for signals of a given type. @item WFDB_Anninfo an object containing the name and attributes of a given annotator. @item WFDB_Annotation an object describing one or more attributes of one or more signals at a given time. @end table @menu * WFDB_Siginfo structures:: Signal names and attributes. * WFDB_Calinfo structures:: Signal calibration specifications. * WFDB_Anninfo structures:: Annotator names and file types. * WFDB_Annotation structures:: Annotation contents. * WFDB_Seginfo structures:: Segment information. * Limits of numeric types:: Maximum and minimum values. * Displaying numeric values:: Converting numbers to strings. * Parsing numeric values:: Converting strings to numbers. * Large time values:: Handling very long records. @end menu @node WFDB_Siginfo structures, WFDB_Calinfo structures, Data Types, Data Types @section Signal Information Structures @cindex @code{WFDB_Siginfo} structure (defined) @cindex signal information structure @cindex information structure (signal) @cindex structure (signal information) @cindex attributes of signals (global) The @var{siarray} argument for @code{isigopen}, @code{osigopen}, @code{wfdbinit}, and @code{osigfopen} is a pointer to an array of objects of type @code{WFDB_Siginfo}. The first three of these functions fill in the @code{WFDB_Siginfo} objects to which @var{siarray} points, but the caller must supply initialized @code{WFDB_Siginfo} objects to @code{osigfopen}. Each object specifies the attributes of a signal: @table @code @item char *fname @cindex signal file name a pointer to a null-terminated string that names the file in which samples of the associated signal are stored. Input signal files are found by prefixing @code{fname} with each of the components of the database path in turn (@pxref{WFDB path}). @code{fname} may include relative or absolute path specifications if necessary; the use of an absolute pathname, combined with an initial null component in @code{WFDB}, reduces the time needed to find the signal file to a minimum. If @code{fname} is @samp{-}, it refers to the standard input or output. @item char *desc @cindex signal file description a pointer to a null-terminated string without embedded newlines (e.g., @samp{ECG lead V1} or @samp{trans-thoracic impedance}). The length of the @code{desc} string is restricted to a maximum of @code{WFDB_MAXDSL} (defined in @file{}) characters, not including the null. @item char *units @cindex physical units @cindex units (physical) @cindex scale (amplitude) a pointer to a null-terminated string without embedded whitespace. The string specifies the physical units of the signal; if NULL, the units are assumed to be millivolts. The length of the @code{units} string is restricted to a maximum of @code{WFDB_MAXUSL} (defined in @file{}) characters (not including the null). @item WFDB_Gain gain @cindex gain @cindex adu @cindex units (ADC) @cindex scale (amplitude) the number of analog-to-digital converter units (adus) per physical unit (see previous item) relative to the original analog signal; for an ECG, this is roughly equal to the amplitude of a normal QRS complex. If @code{gain} is zero, no amplitude calibration is available; in this case, a @code{gain} of @code{WFDB_DEFGAIN} (defined in @file{}) may be assumed. @item WFDB_Sample initval @cindex initial value of signal the initial value of the associated signal (i.e., the value of sample number 0). @item WFDB_Group group @cindex signal group @cindex multiplexed signal file the signal group number. All signals in a given group are stored in the same file. If there are two or more signals in a group, the file is called a @dfn{multiplexed signal file}. Group numbers begin at 0; arrays of @code{WFDB_Siginfo} structures are always kept ordered with respect to the group number, so that signals belonging to the same group are described by consecutive entries in @var{siarray}. @item int fmt @cindex signal file format @cindex format (signal file) @cindex difference format @cindex first difference the signal storage format. The most commonly-used formats are format 8 (8-bit first differences), format 16 (16-bit amplitudes), and format 212 (pairs of 12-bit amplitudes bit-packed into byte triplets). See @file{} for a complete list of supported formats. All signals belonging to the same group must be stored in the same format. @item int spf @cindex multi-frequency records @cindex frame rate @cindex oversampled signals @cindex signals (oversampled) @cindex samples per frame the number of samples per frame. This is 1, for all except oversampled signals in multi-frequency records, for which @code{spf} may be any positive integer. Note that non-integer values are not permitted (thus the frame rate must be chosen such that all sampling frequencies used in the record are integer multiples of the frame rate). @item int bsize @cindex block size the block size, in bytes. For signal files that reside on Unix character device special files (or their equivalents), the @code{bsize} field indicates how many bytes must be read or written at a time (@pxref{Special Files}). For ordinary disk files, @code{bsize} is zero. All signals belonging to a given group have the same @code{bsize}. @item int adcres @cindex resolution @cindex ADC resolution the ADC resolution in bits. Typical ADCs have resolutions between 8 and 16 bits inclusive. @item int adczero @cindex ADC zero the ADC output given an input that falls exactly at the center of the ADC range (normally 0 VDC). Bipolar ADCs produce two's complement output; for these, @code{adczero} is usually zero. For the MIT DB, however, an offset binary ADC was used, and @code{adczero} was 1024. @item int baseline @cindex baseline @cindex isoelectric level @cindex physical zero level the value of ADC output that would map to 0 physical units input. The value of @code{adczero} is not synonymous with that of @code{baseline} (the isoelectric or physical zero level of the signal); the @code{baseline} is a characteristic of the @emph{signal}, while @code{adczero} is a characteristic of the @emph{digitizer}. The value of @code{baseline} need not necessarily lie within the output range of the ADC; for example, if the @code{units} are @samp{degrees_Kelvin}, and the ADC range is 200--300 degrees Kelvin, @code{baseline} corresponds to absolute zero, and lies well outside the range of values actually produced by the ADC. @item long nsamp @cindex length of signal file @cindex duration of signal file @cindex signal file length the number of samples in the signal. (Exception: in multi-frequency records, @code{nsamp} is the number of samples divided by @code{spf}, see above, i.e., the number of frames.) All signals in a given record must have the same @code{nsamp}. If @code{nsamp} is zero, the number of samples is unspecified, and the @code{cksum} (see the next item) is not used; this is useful for specifying signals that are obtained from pipes, for which the length may not be known. @item int cksum @cindex checksum of signal file @cindex signal file checksum a 16-bit checksum of all samples. This field is not usually accessed by application programs; @code{newheader} records checksums calculated by @code{putvec} when it creates a new @file{hea} file, and @code{getvec} compares checksums that it calculates against @code{cksum} at the end of the record, provided that the entire record was read through without skipping samples. @end table @cindex signal number The number of @code{WFDB_Siginfo} structures in @var{siarray} is given by the @var{nsig} argument of the functions that open signal files. Input and output signal numbers are assigned beginning with 0 in the order in which the signals are given in @var{siarray}. Note that input signal 0 and output signal 0 are distinct. Input signal numbers are supplied to @code{aduphys}, @code{physadu}, @code{adumuv}, and @code{muvadu} in their first arguments. @xref{Example 5}, for an illustration of how to read signal specifications from @code{WFDB_Siginfo} structures. @node WFDB_Calinfo structures, WFDB_Anninfo structures, WFDB_Siginfo structures, Data Types @section Calibration Information Structures @cindex @code{WFDB_Calinfo} structure (defined) @cindex calibration information structure The @var{cal} argument for @code{getcal} and @code{putcal} is a pointer to an object of type @code{WFDB_Calinfo}. A @code{WFDB_Calinfo} object contains information about signals of a specified type: @table @code @item char *sigtype @cindex signal type a pointer to a null-terminated string without embedded tabs or newlines. This field describes the type(s) of signals to which the calibration specifications apply. Usually, @code{sigtype} is an exact match to (or a prefix of) the @code{desc} field of the @code{WFDB_Siginfo} object that describes a matching signal. @item char *units @cindex physical units @cindex units (physical) a pointer to a null-terminated string without embedded whitespace. This field specifies the physical units of signals to which the calibration specifications apply. Usually, the @code{units} field of a @code{WFDB_Calinfo} structure must exactly match the @code{units} field of the @code{WFDB_Siginfo} structure that describes a matching signal. @item double scale @cindex scale (amplitude) @cindex plotting scale @cindex display scale the customary plotting scale, in physical units per centimeter. WFDB applications that produce graphical output may use @code{scale} as a default. Except in unusual circumstances, signals of different types should be plotted at equal multiples of their respective @code{scale}s. @item double low @itemx double high @cindex calibration pulse limits @cindex pulse limits (calibration) values (in physical units) corresponding to the low and high levels of a calibration pulse. If the signal is AC-coupled (see below), @code{low} is zero, and @code{high} is the pulse amplitude. @item int caltype @cindex AC-coupled signals @cindex DC-coupled signals @cindex calibration pulse shape @cindex pulse shape (calibration) a small integer that specifies the shape of the calibration pulse (see @file{} for definitions). @code{caltype} is even if signals of the corresponding @code{sigtype} are AC-coupled, and odd if they are DC-coupled. @end table @cindex calibration list The calibration list is a memory-resident linked list of @code{WFDB_Calinfo} structures. It is accessible only via @code{calopen}, @code{getcal}, @code{putcal}, @code{newcal}, and @code{flushcal}. @node WFDB_Anninfo structures, WFDB_Annotation structures, WFDB_Calinfo structures, Data Types @section Annotator Information Structures @cindex @code{WFDB_Anninfo} structure (defined) @cindex annotator information structure @cindex annotator name @cindex attributes of annotators @cindex AHA-format annotation file @cindex WFDB-format annotation file @cindex standard annotation file @cindex format (annotation file) @cindex information structure (annotator) @cindex structure (annotator information) The @var{aiarray} argument for @code{annopen} and @code{wfdbinit} is a pointer to an array of objects of type @code{WFDB_Anninfo}. Each member of the array contains information provided to @code{annopen} and @code{wfdbinit} about an annotation file associated with the record: @table @code @item char *name @cindex annotator name the annotator name. The name @file{atr} is reserved for a @dfn{reference annotation file} supplied by the creator of the database record to document its contents as accurately and thoroughly as possible. You may use other annotator names to identify annotation files that you create; unless there are compelling reasons not to do so, follow the convention that the annotator name is the name of the file's creator (a program or a person). To avoid confusion, do not use @samp{dat}, @samp{data@var{n}}, @samp{d@var{n}}, or @samp{hea} (all of which are commonly used as parts of WFDB file names) as annotator names. The special name @samp{-} refers to the standard input or output. Other annotator names may contain upper- or lower-case letters, digits, and underscores. Annotation files are normally created in the current directory and found in any of the directories in the database path (@pxref{WFDB path}). @item int stat @cindex @code{WFDB_AHA_READ} @cindex @code{WFDB_AHA_WRITE} @cindex @code{WFDB_READ} @cindex @code{WFDB_WRITE} the file type/access code. Usually, @code{stat} is either @code{WFDB_READ} or @code{WFDB_WRITE}, to specify standard (``WFDB format'') annotation files to be read by @code{getann} or to be written by @code{putann}. Both MIT DB and AHA DB annotation files can be (and generally are) stored in WFDB format. The symbols @code{WFDB_READ} and @code{WFDB_WRITE} are defined in @file{}. An AHA-format annotation file can be read by @code{getann} or written by @code{putann} if the @code{stat} field is set to @code{WFDB_AHA_READ} or @code{WFDB_AHA_WRITE} before calling @code{annopen} or @code{wfdbinit} (@pxref{Example 2}). Other formats may be supported via a similar mechanism; consult @file{} for more information. @end table @cindex annotator number The number of @code{WFDB_Anninfo} objects in @var{aiarray} is given by the @var{nann} argument of @code{annopen} and @code{wfdbinit}. The annotation-reading function, @code{getann}, knows the annotators by number only; @code{annopen} and @code{wfdbinit} assign input annotator numbers beginning with 0 in the order in which they are given in the array of @code{WFDB_Anninfo} objects. Output annotator numbers used by @code{putann} also start at 0; note that input annotator 0 and output annotator 0 are distinct. Annotator numbers are supplied to @code{getann} and @code{putann} in their first arguments. @ifnotinfo @xref{annopen, , @code{annopen}}, @end ifnotinfo @ifinfo @xref{annopen}, @end ifinfo for an example of how to set the contents of an array of @code{WFDB_Anninfo} objects. @node WFDB_Annotation structures, WFDB_Seginfo structures, WFDB_Anninfo structures, Data Types @section Annotation Structures @cindex @code{WFDB_Annotation} structure (defined) @cindex annotation structure @cindex structure (annotation) @cindex attributes of signals (local) The @var{annot} argument of @code{getann} and @code{putann} is an object of type @code{WFDB_Annotation} containing these fields: @table @code @item long time @cindex annotation time @cindex time of annotation @cindex reference point (on QRS) time of the annotation, in samples from the beginning of the record. The times of beat annotations in the @file{atr} files for the MIT DB generally coincide with the R-wave peak in signal 0; for the AHA DB, they generally coincide with the PQ-junction. @item char anntyp @cindex annotation code field @cindex annotation type @cindex type (annotation) annotation code; an integer between 1 and @code{ACMAX}. @xref{Annotation Codes}, for a list of legal annotation codes. @code{ACMAX} is defined in @file{}. @item signed char subtyp @itemx unsigned char chan @itemx signed char num @cindex annotation subtype @cindex subtype (annotation) @cindex user-defined fields in annotation @cindex signal (associating annotation with) numbers between @ifnotinfo @minus{}128 @end ifnotinfo @ifinfo -128 @end ifinfo and 127. In MIT DB @file{atr} files, the @code{subtyp} field is used with noise and artifact annotations to indicate which signals are affected (@pxref{Annotation Codes}). The @code{chan} field is intended to indicate the signal to which the annotation is attached. More than one annotation may be written with the same @code{time} if the @code{num} or @code{chan} fields are distinct and in ascending order. The semantics of the @code{chan} field are unspecified, however; users may assign any desired meaning, which need not have anything to do with signal numbers. In user-created annotation files, these fields can be used to store arbitrary small integers. The @code{subtyp} field requires no space in a standard annotation file unless it is non-zero; the @code{chan} and @code{num} fields require no space unless they have changed since the previous annotation. @item char *aux @cindex annotation @code{aux} string @cindex @code{aux} string (annotation) a free text string. The first byte is interpreted as an @code{unsigned char} that specifies the number of bytes that follow (up to 255). In MIT DB @file{atr} files, the @code{aux} field is used with rhythm change annotations to specify the new rhythm, and with comment annotations to store the text of the comment @ifnotinfo (@pxref{Annotation Codes}). @end ifnotinfo The string can contain arbitrary binary data, including embedded nulls. It is unwise to store anything but ASCII strings, however, if the annotation file may be transported to a system with a different architecture (e.g., on which multiple-byte quantities may have different sizes or byte layouts). The @code{aux} field requires no space in a standard annotation file if it is @code{NULL}. Note that conversion of annotation files to other formats may entail truncation or loss of the @code{aux} string. Note also that the @code{aux} pointer returned by @code{getann} points to a small static buffer (separately allocated for each input annotator beginning with WFDB library version 9.4) that may be overwritten by subsequent calls. @end table @xref{Example 3}, for a short program that examines the contents of a @code{WFDB_Annotation}. @node WFDB_Seginfo structures, Limits of numeric types, WFDB_Annotation structures, Data Types @section Segment Information Structures @cindex @code{WFDB_Seginfo} structure (defined) @cindex segment information structure @cindex structure (segment) Objects of type @code{WFDB_Seginfo} contain these fields: @table @code @item char recname[WFDB_MAXRNL+1] @cindex segment name Segment name (the name of the simple record corresponding to a segment of a multi-segment record), unless @code{recname} has the special value '@code{~}'. In the latter case, the segment is a gap (i.e., it corresponds to an interval during which no signals are available). @item WFDB_Time nsamp @cindex segment length Segment length in samples. @item WFDB_Time samp0 @cindex segment start Number of samples that precede the segment in the multi-segment record to which it belongs. If the segment is opened as an individual record, its @code{n}th sample has sample number @code{n-1}, just as for any record. If the record to which the segment belongs is opened, the @code{n}th sample in the segment has sample number @code{n-1+samp0}. @end table @node Limits of numeric types, Displaying numeric values, WFDB_Seginfo structures, Data Types @section Limits of Numeric Types @cindex minimum values of numeric types @cindex maximum values of numeric types @cindex range of numeric types @cindex precision of numeric types @noindent It is sometimes useful to refer to the minimum or maximum possible value for a particular data type. Although the WFDB library has certain minimum requirements for its numeric types, the actual range of @emph{possible} values depends on your C compiler, and the CPU and operating system where your program is running. Note that the range of values for a particular @emph{signal} is usually smaller than the possible range of values that can be stored in a @code{WFDB_Sample} variable. If you want to know the maximum and minimum values for a particular signal, refer to the @code{adcres} and @code{adczero} fields of the of the @code{WFDB_Siginfo} structure @ifnotinfo (@pxref{WFDB_Siginfo structures, , Signal Information Structures}). @end ifnotinfo @ifinfo (@pxref{WFDB_Siginfo structures}). @end ifinfo The following macros (defined in @file{}) can be used to determine the limits of @emph{integer} types. In order to use these macros, your program must also include the statement @code{#include }. @table @code @findex WFDB_SAMPLE_MIN (10.6.0) @cindex @code{WFDB_Sample} (minimum and maximum value) @item WFDB_SAMPLE_MIN Smallest value that can be stored as a @code{WFDB_Sample}. @findex WFDB_SAMPLE_MAX (10.6.0) @item WFDB_SAMPLE_MAX Largest value that can be stored as a @code{WFDB_Sample}. @findex WFDB_DATE_MIN (10.6.0) @cindex @code{WFDB_Date} (minimum and maximum value) @item WFDB_DATE_MIN Smallest value that can be stored as a @code{WFDB_Date}. @findex WFDB_DATE_MAX (10.6.0) @item WFDB_DATE_MAX Largest value that can be stored as a @code{WFDB_Date}. @findex WFDB_TIME_MIN (10.6.0) @cindex @code{WFDB_Time} (minimum and maximum value) @item WFDB_TIME_MIN Smallest value that can be stored as a @code{WFDB_Time}. @findex WFDB_TIME_MAX (10.6.0) @item WFDB_TIME_MAX Largest value that can be stored as a @code{WFDB_Time}. @findex WFDB_GROUP_MAX (10.6.0) @cindex @code{WFDB_Group} (maximum value) @item WFDB_GROUP_MAX Largest value that can be stored as a @code{WFDB_Group}. @findex WFDB_SIGNAL_MAX (10.6.0) @cindex @code{WFDB_Signal} (maximum value) @item WFDB_SIGNAL_MAX Largest value that can be stored as a @code{WFDB_Signal}. @findex WFDB_ANNOTATOR_MAX (10.6.0) @cindex @code{WFDB_Annotator} (maximum value) @item WFDB_ANNOTATOR_MAX Largest value that can be stored as a @code{WFDB_Annotator}. @end table @noindent The following macros can be used to determine the limits and precision of @emph{floating-point} types. In order to use these macros, your program must also include the statement @code{#include }. @table @code @findex WFDB_FREQUENCY_MAX (10.6.0) @cindex @code{WFDB_Frequency} (range and precision) @item WFDB_FREQUENCY_MAX Largest finite value that can be represented as a @code{WFDB_Frequency}. @findex WFDB_FREQUENCY_DIG (10.6.0) @item WFDB_FREQUENCY_DIG Number of decimal digits of precision of a @code{WFDB_Frequency}. @findex WFDB_FREQUENCY_MAX_10_EXP (10.6.0) @item WFDB_FREQUENCY_MAX_10_EXP Largest finite power of 10 that can be represented as a @code{WFDB_Frequency}. @findex WFDB_FREQUENCY_EPSILON (10.6.0) @item WFDB_FREQUENCY_EPSILON The difference between 1.0 and the smallest value greater than 1.0 that can be represented as a @code{WFDB_Frequency}. @findex WFDB_GAIN_MAX (10.6.0) @cindex @code{WFDB_Gain} (range and precision) @item WFDB_GAIN_MAX Largest finite value that can be represented as a @code{WFDB_Gain}. @findex WFDB_GAIN_DIG (10.6.0) @item WFDB_GAIN_DIG Number of decimal digits of precision of a @code{WFDB_Gain}. @findex WFDB_GAIN_MAX_10_EXP (10.6.0) @item WFDB_GAIN_MAX_10_EXP Largest finite power of 10 that can be represented as a @code{WFDB_Gain}. @findex WFDB_GAIN_EPSILON (10.6.0) @item WFDB_GAIN_EPSILON The difference between 1.0 and the smallest value greater than 1.0 that can be represented as a @code{WFDB_Gain}. @end table @node Displaying numeric values, Parsing numeric values, Limits of numeric types, Data Types @section Displaying Numeric Values @cindex displaying numeric values @cindex converting numbers to strings @cindex @code{printf} (using WFDB data types) @cindex @code{fprintf} (using WFDB data types) @cindex @code{sprintf} (using WFDB data types) @cindex @code{WFDB_Sample} (printing using @code{printf}) @cindex @code{WFDB_Time} (printing using @code{printf}) @cindex @code{WFDB_Frequency} (printing using @code{printf}) @cindex @code{WFDB_Gain} (printing using @code{printf}) @findex WFDB_Pd_SAMP (10.7.0) @findex WFDB_Pi_SAMP (10.7.0) @findex WFDB_Po_SAMP (10.7.0) @findex WFDB_Pu_SAMP (10.7.0) @findex WFDB_Px_SAMP (10.7.0) @findex WFDB_PX_SAMP (10.7.0) @findex WFDB_Pd_TIME (10.7.0) @findex WFDB_Pi_TIME (10.7.0) @findex WFDB_Po_TIME (10.7.0) @findex WFDB_Pu_TIME (10.7.0) @findex WFDB_Px_TIME (10.7.0) @findex WFDB_PX_TIME (10.7.0) @findex WFDB_Pe_FREQ (10.7.0) @findex WFDB_PE_FREQ (10.7.0) @findex WFDB_Pf_FREQ (10.7.0) @findex WFDB_Pg_FREQ (10.7.0) @findex WFDB_PG_FREQ (10.7.0) @findex WFDB_Pe_GAIN (10.7.0) @findex WFDB_PE_GAIN (10.7.0) @findex WFDB_Pf_GAIN (10.7.0) @findex WFDB_Pg_GAIN (10.7.0) @findex WFDB_PG_GAIN (10.7.0) @noindent To display numeric values on the screen, or convert them to strings, it is often convenient to use the standard @code{printf}, @code{fprintf}, or @code{sprintf} functions. Each of these functions requires you to specify the data type as part of the ``format'' string (for example, to display an @code{int}, you might write @code{printf("%d", x)}, but to display a @code{long int}, you might write @code{printf("%ld", x)}). The macros listed below can be used to display @code{WFDB_Sample}, @code{WFDB_Time}, @code{WFDB_Frequency}, and @code{WFDB_Gain} values, regardless of which standard C data types these represent. Using these macros can help to ensure your program is portable to other operating systems and C compilers. Each macro expands to a string constant (such as @code{"d"} or @code{"ld"}), which does not include the leading @samp{%} character. For example, to display a table of time and sample values, we might write: @example WFDB_Time t = 0; WFDB_Sample v[2]; while (getvec(v) > 0) @{ printf("%10"WFDB_Pd_TIME" %6"WFDB_Pd_SAMP" %6"WFDB_Pd_SAMP"\n", t, v[0], v[1]); t++; @} @end example @noindent The following macros are defined in @file{}: @multitable {@t{WFDB_PX_SAMP}} {@t{WFDB_Frequency}} {Unsigned base 16} {@samp{3.600000E+02}} @headitem Macro @tab Argument type @tab Format @tab Example output @comment ---------------- WFDB_Sample ---------------- @item @t{WFDB_Pd_SAMP} @tab @t{WFDB_Sample} @tab Base 10 @tab @samp{995} @item @t{WFDB_Pi_SAMP} @tab @t{WFDB_Sample} @tab Base 10 @tab @samp{995} @item @t{WFDB_Po_SAMP} @tab @t{WFDB_Sample} @tab Unsigned base 8 @tab @samp{1743} @item @t{WFDB_Pu_SAMP} @tab @t{WFDB_Sample} @tab Unsigned base 10 @tab @samp{995} @item @t{WFDB_Px_SAMP} @tab @t{WFDB_Sample} @tab Unsigned base 16 @tab @samp{3e3} @item @t{WFDB_PX_SAMP} @tab @t{WFDB_Sample} @tab Unsigned base 16 @tab @samp{3E3} @comment ---------------- WFDB_Time ---------------- @item @t{WFDB_Pd_TIME} @tab @t{WFDB_Time} @tab Base 10 @tab @samp{650000} @item @t{WFDB_Pi_TIME} @tab @t{WFDB_Time} @tab Base 10 @tab @samp{650000} @item @t{WFDB_Po_TIME} @tab @t{WFDB_Time} @tab Unsigned base 8 @tab @samp{2365420} @item @t{WFDB_Pu_TIME} @tab @t{WFDB_Time} @tab Unsigned base 10 @tab @samp{650000} @item @t{WFDB_Px_TIME} @tab @t{WFDB_Time} @tab Unsigned base 16 @tab @samp{9eb10} @item @t{WFDB_PX_TIME} @tab @t{WFDB_Time} @tab Unsigned base 16 @tab @samp{9EB10} @comment ---------------- WFDB_Frequency ---------------- @item @t{WFDB_Pe_FREQ} @tab @t{WFDB_Frequency} @tab Exponential @tab @samp{3.600000e+02} @item @t{WFDB_PE_FREQ} @tab @t{WFDB_Frequency} @tab Exponential @tab @samp{3.600000E+02} @item @t{WFDB_Pf_FREQ} @tab @t{WFDB_Frequency} @tab Fixed-point @tab @samp{360.000000} @item @t{WFDB_Pg_FREQ} @tab @t{WFDB_Frequency} @tab Automatic @tab @samp{360} @item @t{WFDB_PG_FREQ} @tab @t{WFDB_Frequency} @tab Automatic @tab @samp{360} @comment ---------------- WFDB_Gain ---------------- @item @t{WFDB_Pe_GAIN} @tab @t{WFDB_Gain} @tab Exponential @tab @samp{2.000000e+02} @item @t{WFDB_PE_GAIN} @tab @t{WFDB_Gain} @tab Exponential @tab @samp{2.000000E+02} @item @t{WFDB_Pf_GAIN} @tab @t{WFDB_Gain} @tab Fixed-point @tab @samp{200.000000} @item @t{WFDB_Pg_GAIN} @tab @t{WFDB_Gain} @tab Automatic @tab @samp{200} @item @t{WFDB_PG_GAIN} @tab @t{WFDB_Gain} @tab Automatic @tab @samp{200} @end multitable (The @samp{d} and @samp{i} formats are equivalent, and are provided for symmetry with @code{scanf}. For more information, see the documentation of your C compiler.) @node Parsing numeric values, Large time values, Displaying numeric values, Data Types @section Parsing Numeric Values @cindex parsing numeric values @cindex converting strings to numbers @cindex @code{scanf} (using WFDB data types) @cindex @code{fscanf} (using WFDB data types) @cindex @code{sscanf} (using WFDB data types) @cindex @code{WFDB_Sample} (parsing using @code{scanf}) @cindex @code{WFDB_Time} (parsing using @code{scanf}) @cindex @code{WFDB_Frequency} (parsing using @code{scanf}) @cindex @code{WFDB_Gain} (parsing using @code{scanf}) @findex WFDB_Sd_SAMP (10.7.0) @findex WFDB_Si_SAMP (10.7.0) @findex WFDB_So_SAMP (10.7.0) @findex WFDB_Su_SAMP (10.7.0) @findex WFDB_Sx_SAMP (10.7.0) @findex WFDB_SX_SAMP (10.7.0) @findex WFDB_Sd_TIME (10.7.0) @findex WFDB_Si_TIME (10.7.0) @findex WFDB_So_TIME (10.7.0) @findex WFDB_Su_TIME (10.7.0) @findex WFDB_Sx_TIME (10.7.0) @findex WFDB_SX_TIME (10.7.0) @findex WFDB_Se_FREQ (10.7.0) @findex WFDB_SE_FREQ (10.7.0) @findex WFDB_Sf_FREQ (10.7.0) @findex WFDB_Sg_FREQ (10.7.0) @findex WFDB_SG_FREQ (10.7.0) @findex WFDB_Se_GAIN (10.7.0) @findex WFDB_SE_GAIN (10.7.0) @findex WFDB_Sf_GAIN (10.7.0) @findex WFDB_Sg_GAIN (10.7.0) @findex WFDB_SG_GAIN (10.7.0) @noindent To convert text input to a numeric value, it is often convenient to use the standard @code{scanf}, @code{fscanf}, or @code{sscanf} functions. Each of these functions requires you to specify the data type as part of the ``format'' string (for example, to read an integer and store it as an @code{int}, you might write @code{scanf("%d", &x)}, but to store it as a @code{long int}, you might write @code{scanf("%ld", &x)}). As with the @code{printf}-style macros described in the previous section, the macros listed below can be used to convert a string to a @code{WFDB_Sample}, @code{WFDB_Time}, @code{WFDB_Frequency}, or @code{WFDB_Gain} value, regardless of which standard C data types these represent. Each macro expands to a string constant (such as @code{"d"} or @code{"ld"}), which does not include the leading @samp{%} character. For example, to read a table of time and sample values, we might write: @example WFDB_Time t; WFDB_Sample v[2]; char buf[1000]; while (fgets(buf, sizeof(buf), stdin)) @{ if (sscanf(buf, "%"WFDB_Sd_TIME"%"WFDB_Sd_SAMP"%"WFDB_Sd_SAMP", &t, &v[0], &v[1]) == 3) @{ putvec(v); @} @} @end example @noindent The following macros are defined in @file{}: @multitable {@t{WFDB_PX_SAMP}} {@code{WFDB_Frequency *}} {Base 8, 10, or 16} @headitem Macro @tab Argument type @tab Format @comment ---------------- WFDB_Sample ---------------- @item @t{WFDB_Sd_SAMP} @tab @code{WFDB_Sample *} @tab Base 10 @item @t{WFDB_Si_SAMP} @tab @code{WFDB_Sample *} @tab Base 8, 10, or 16 @item @t{WFDB_So_SAMP} @tab @code{WFDB_Sample *} @tab Unsigned base 8 @item @t{WFDB_Su_SAMP} @tab @code{WFDB_Sample *} @tab Unsigned base 10 @item @t{WFDB_Sx_SAMP} @tab @code{WFDB_Sample *} @tab Unsigned base 16 @item @t{WFDB_SX_SAMP} @tab @code{WFDB_Sample *} @tab Unsigned base 16 @comment ---------------- WFDB_Time ---------------- @item @t{WFDB_Sd_TIME} @tab @code{WFDB_Time *} @tab Base 10 @item @t{WFDB_Si_TIME} @tab @code{WFDB_Time *} @tab Base 8, 10, or 16 @item @t{WFDB_So_TIME} @tab @code{WFDB_Time *} @tab Unsigned base 8 @item @t{WFDB_Su_TIME} @tab @code{WFDB_Time *} @tab Unsigned base 10 @item @t{WFDB_Sx_TIME} @tab @code{WFDB_Time *} @tab Unsigned base 16 @item @t{WFDB_SX_TIME} @tab @code{WFDB_Time *} @tab Unsigned base 16 @comment ---------------- WFDB_Frequency ---------------- @item @t{WFDB_Se_FREQ} @tab @code{WFDB_Frequency *} @tab Decimal @item @t{WFDB_SE_FREQ} @tab @code{WFDB_Frequency *} @tab Decimal @item @t{WFDB_Sf_FREQ} @tab @code{WFDB_Frequency *} @tab Decimal @item @t{WFDB_Sg_FREQ} @tab @code{WFDB_Frequency *} @tab Decimal @item @t{WFDB_SG_FREQ} @tab @code{WFDB_Frequency *} @tab Decimal @comment ---------------- WFDB_Gain ---------------- @item @t{WFDB_Se_GAIN} @tab @code{WFDB_Gain *} @tab Decimal @item @t{WFDB_SE_GAIN} @tab @code{WFDB_Gain *} @tab Decimal @item @t{WFDB_Sf_GAIN} @tab @code{WFDB_Gain *} @tab Decimal @item @t{WFDB_Sg_GAIN} @tab @code{WFDB_Gain *} @tab Decimal @item @t{WFDB_SG_GAIN} @tab @code{WFDB_Gain *} @tab Decimal @end multitable (The @samp{x} and @samp{X} formats are equivalent, as are the @samp{e}, @samp{E}, @samp{f}, @samp{g}, and @samp{G} formats, and are provided for symmetry with @code{printf}. For more information, see the documentation of your C compiler.) @node Large time values, , Parsing numeric values, Data Types @section Large Time Values @cindex large time values @cindex @code{WFDB_Time} (64-bit) @cindex @code{WFDB_LARGETIME} @findex WFDB_LARGETIME @noindent The @code{WFDB_Time} type is defined as a signed integer type of at least 32 bits, which means that it can represent sample numbers up to 2,147,483,647. By default, @code{WFDB_Time} is defined as an alias for the standard C @code{long int}, and many existing applications have been written with the assumption that @code{WFDB_Time} and @code{long int} are interchangeable. However, it is quite possible for a record to be longer than 2,147,483,647 samples (about 25 days of recording at @w{1 kHz}) and it is useful to be able to process such records on machines where a @code{long int} is only 32 bits. If you are using a modern C compiler, with WFDB library version 10.7.0 or later, it is possible to define @code{WFDB_Time} as @code{long long int} instead of @code{long int}. This lets your program work with sample numbers as large as 9,223,372,036,854,775,807, even on a 32-bit machine. To do this, add the following line at the very beginning of your source file, @emph{before} including @file{}: @example #define WFDB_LARGETIME @end example @noindent If your program consists of multiple @file{*.c} files, be sure to do the same for each file. Alternatively, you can define this macro on the C compiler command line (e.g., @code{-DWFDB_LARGETIME} if you are using @command{gcc}). When doing this, you will also need to ensure that your program handles large time values consistently, by using the @code{WFDB_Time} data type rather than @code{long} or @code{long long}, using @code{WFDB_TIME_MAX} rather than @code{LONG_MAX} or @code{LLONG_MAX}, using @code{WFDB_Pd_TIME} rather than @code{"ld"} or @code{"lld"}, and so forth. @node Annotation Codes, Database Files, Data Types, Top @chapter Annotation Codes @menu * Mapping macros:: Macros for mapping annotation codes. @end menu @cindex ECG annotation code @cindex annotation code @cindex code (annotation) Application programs that deal with annotations should include the line @example #include @end example @noindent which provides the symbolic definitions of annotation codes given in the first column of the table below. (The second column of the table shows the strings returned by @code{annstr} and @code{ecgstr}.) @display @emph{Beat annotation codes:} @t{NORMAL N } Normal beat @t{LBBB L } Left bundle branch block beat @t{RBBB R } Right bundle branch block beat @t{BBB B } Bundle branch block beat (unspecified) @t{APC A } Atrial premature beat @t{ABERR a } Aberrated atrial premature beat @t{NPC J } Nodal (junctional) premature beat @t{SVPB S } Supraventricular premature or ectopic beat (atrial or nodal) @t{PVC V } Premature ventricular contraction @t{RONT r } R-on-T premature ventricular contraction @t{FUSION F } Fusion of ventricular and normal beat @t{AESC e } Atrial escape beat @t{NESC j } Nodal (junctional) escape beat @t{SVESC n } Supraventricular escape beat (atrial or nodal) [1] @t{VESC E } Ventricular escape beat @t{PACE / } Paced beat @t{PFUS f } Fusion of paced and normal beat @t{UNKNOWN Q } Unclassifiable beat @t{LEARN ? } Beat not classified during learning @emph{Non-beat annotation codes:} @t{VFON [ } Start of ventricular flutter/fibrillation @t{FLWAV ! } Ventricular flutter wave @t{VFOFF ] } End of ventricular flutter/fibrillation @t{NAPC x } Non-conducted P-wave (blocked APC) [4] @t{WFON ( } Waveform onset [4] @t{WFOFF ) } Waveform end [4] @t{PWAVE p } Peak of P-wave [4] @t{TWAVE t } Peak of T-wave [4] @t{UWAVE u } Peak of U-wave [4] @t{PQ ` } PQ junction @t{JPT ' } J-point @t{PACESP ^ } (Non-captured) pacemaker artifact @t{ARFCT | } Isolated QRS-like artifact [2] @t{NOISE ~ } Change in signal quality [2] @t{RHYTHM + } Rhythm change [3] @t{STCH s } ST segment change [1,3] @t{TCH T } T-wave change [1,3,4] @t{SYSTOLE * } Systole [1] @t{DIASTOLE D } Diastole [1] @t{MEASURE = } Measurement annotation [1,3] @t{NOTE " } Comment annotation [3] @t{LINK @@ } Link to external data [5] @end display @c @group @noindent @strong{Notes:} @enumerate @item Codes @code{SVESC}, @code{STCH}, and @code{TCH} were first introduced in WFDB library version 4.0. Codes @code{SYSTOLE}, @code{DIASTOLE}, and @code{MEASURE} were first introduced in WFDB library version 7.0. @cindex @code{subtyp} (in @code{NOISE} annotation) @cindex noisy signals (annotating) @item In MIT and ESC DB @file{atr} files, each non-zero bit in the @code{subtyp} field indicates that the corresponding signal contains noise (the least significant bit corresponds to signal 0). @cindex annotation @code{aux} string @cindex @code{aux} string (annotation) @item The @code{aux} field contains an ASCII string (with prefixed byte count) describing the rhythm, ST segment, T-wave change, measurement, or the nature of the comment. By convention, the character that follows the byte count in the @code{aux} field of a @code{RHYTHM} annotation is @samp{(}. See the @cite{MIT-BIH Arrhythmia Database Directory} for a list of rhythm annotation strings. @item Codes @code{WFON}, @code{WFOFF}, @code{PWAVE}, @code{TWAVE}, and @code{UWAVE} were first introduced in DB library version 8.3. The @samp{p} mnemonic now assigned to @code{PWAVE} was formerly assigned to @code{NAPC}, and the @samp{t} mnemonic now assigned to @code{TWAVE} was formerly assigned to @code{TCH}. The obsolete codes @code{PQ} (designating the PQ junction) and @code{JPT} (designating the J-point) are still defined in @file{}, but are identical to @code{WFON} and @code{WFOFF} respectively. @item The @code{LINK} code was first introduced in WFDB library version 9.6. The @code{aux} field of a @code{LINK} annotation contains a URL (a uniform resource locator, in the form @file{http://machine.name/some/data}, suitable for passing to a Web browser such as Netscape or Mosaic). @code{LINK} annotations may be used to associate extended text, images, or other data with an annotation file. If the @code{aux} field contains any whitespace, text following the first whitespace is taken as descriptive text to be displayed by a WFDB browser such as @code{WAVE}. @end enumerate @c @end group @cindex @code{NOTQRS} (annotation code) The annotation codes in the table above are the predefined values of the @code{anntyp} field in a @code{WFDB_Annotation}. Other values in the range of 1 to @code{ACMAX} (defined in @file{}) are legal but do not have preassigned meanings. The constant @code{NOTQRS}, also defined in @file{}, is not a legal value for @code{anntyp}, but is a possible output of the macros discussed below. @node Mapping macros, , Annotation Codes, Annotation Codes @section Macros for Mapping Annotation Codes @cindex macros @cindex annotation code mapping @cindex mapping annotation codes Application programs that use the macros described in this section should include the line @example #include @end example @noindent which will make their definitions, and those in @file{}, available. @table @code @findex isann @cindex annotation code (legal) @cindex legal annotation code @item isann(@var{c}) true (1) if @var{c} is a legal annotation code, false (0) otherwise @cindex QRS annotation code @findex isqrs @item isqrs(@var{c}) true (1) if @var{c} denotes a QRS complex, false (0) otherwise @findex map1 @item map1(@var{c}) maps @var{c} into one of the set @{@code{NOTQRS}, @code{NORMAL}, @code{PVC}, @code{FUSION}, @code{LEARN}@} @findex map2 @item map2(@var{c}) maps @var{c} into one of the set @{@code{NOTQRS}, @code{NORMAL}, @code{SVPB}, @code{PVC}, @code{FUSION}, @code{LEARN}@} @findex annpos (6.0) @item annpos(@var{c}) maps @var{c} into one of the set @{@code{APUNDEF}, @code{APSTD}, @code{APHIGH}, @code{APLOW}, @code{APATT}, @code{APAHIGH}, @code{APALOW}@} (see @file{} for definitions of these symbols; this macro was first introduced in WFDB library version 6.0) @end table @findex setisqrs (6.0) @findex setmap1 (6.0) @findex setmap2 (6.0) @findex setannpos (6.0) If you define your own annotation codes, you may wish to modify the tables used by the macros above. The file @file{} also defines @code{setisqrs(@var{c}, @var{x})}, @code{setmap1(@var{c}, @var{x})}, @code{setmap2(@var{c}, @var{x})}, and @code{setannpos(@var{c}, @var{x})} for this purpose. In each case, @var{x} is the value to be returned when the corresponding mapping macro is invoked with an argument of @var{c}. (These macros were first introduced in WFDB library version 6.0.) The macros below convert between AHA and MIT annotation codes; they are also defined in @file{}. @findex ammap @findex mamap @cindex AHA annotation code @table @code @item ammap(@var{a}) maps @var{a} (an AHA annotation code) into an MIT annotation code (one of the set @{@code{NORMAL}, @code{PVC}, @code{FUSION}, @code{RONT}, @code{VESC}, @code{PACE}, @code{UNKNOWN}, @code{VFON}, @code{VFOFF}, @code{NOISE}, @code{NOTE}@}), or @code{NOTQRS} @item mamap(@var{c}, @var{s}) maps @var{c} (an MIT annotation code) into an AHA annotation code (one of the set @{@samp{N}, @samp{V}, @samp{F}, @samp{R}, @samp{E}, @samp{P}, @samp{Q}, @samp{[}, @samp{]}, @samp{U}, @samp{O}@}); @var{s} is the MIT annotation @code{subtyp} (significant only if @var{c} is @code{NOISE}) @end table @node Database Files, Examples, Annotation Codes, Top @chapter Database Files @cindex WFDB path The WFDB library has been constructed to provide a standard interface between the database files and application programs. Alternate means of access to database files is strongly discouraged, since file formats may change. Database files are located in the directories specified by @code{WFDB} (@pxref{WFDB path}). Recall that a WFDB record is not a file; rather, it is an extensible @emph{collection} of database files (@pxref{Concepts 1, , Records}). Thus, for example, record 100 of the MIT-BIH Arrhythmia Database consists of the files named @file{100.hea}, @file{100.dat}, and @file{100.atr} in the @file{mitdb} directory of the MIT-BIH Arrhythmia Database CDROM (or in PhysioBank, within @uref{http://@-physio@-net.@-org/@-physio@-bank/@-data@-base/@-mitdb/}), together with any additional files in other directories that you may have associated with record 100 (such as your own annotation file). All files associated with a given record include the record name as the first part of the file name. No explicit action (other than choosing the file name, and locating the file in the WFDB path) is needed in order to associate a new file with an existing WFDB record. To find the location of a database file easily, you can use @file{wfdbwhich}, an application included with the WFDB Software Package. Type @code{wfdbwhich} for brief instructions on its use, or see the @cite{WFDB Applications Guide}. @section File Types @cindex file types There are four types of files supported by the WFDB library: @menu * Header Files:: contain signal file names and attributes. * Signal Files:: contain signals. * Annotation Files:: contain annotations. * Calibration Files:: contain signal calibration specifications. The other topics in this section deal with special types of database access: * EDF Files:: a popular format readable by WFDB 10.4.5 and later. * AHA Format Files:: not used for on-line WFDB records. * Standard I/O:: pipes and I/O redirection for WFDB files. * Multiplexed Signal Files:: signal groups. * Multi-Frequency Records:: signals sampled at different frequencies within within a given record. * Multi-Segment Records:: concatenated records. * Multiple Record Access:: how to have more than one record open. * Special Files:: signal I/O using block and character devices. * Piped and Local Records:: records for use with user-created signals. * NETFILES:: input directly from web and FTP servers. * Annotation Order:: the canonical order for annotations: how and why to break the rules, and how to deal with the consequences of doing so. @end menu @node Header Files, Signal Files, Database Files, Database Files @unnumberedsubsec Header Files @cindex @code{hea} file @dfn{Header files} have names of the form @file{@var{record}.hea}, where @var{record} is the record name. (MIT DB records are named 100--124 and 200--234 with some numbers missing. AHA DB records are named 1001--1010, 2001--2010, 3001--3010, 4001--4010, 5001--5010, 6001--6010, 7001--7010, and 8001--8010. ESC DB records are named e0103--e1304, with many numbers missing.) Header files are text files, with lines terminated by ASCII carriage-return/line-feed pairs, created by @code{newheader}, @code{setheader}, or @code{setmsheader}, from which @code{isigopen}, @code{osigopen}, and @code{wfdbinit} read the names of the signal files and their attributes as given in the array of @code{WFDB_Siginfo} objects; @code{sampfreq} also reads a header file to determine the sampling frequency used for a record. @node Signal Files, Annotation Files, Header Files, Database Files @unnumberedsubsec Signal Files @cindex signal file @dfn{Signal files} usually have names of the form @file{@var{record}.dat}. (The @code{.dat} suffix is conventional, but not required; any file name acceptable to the operating system is permissible.) Signal files are binary, and usually contain either 16-bit amplitudes (format 16), pairs of 12-bit amplitudes bit-packed into byte triplets (format 212), or 8-bit first differences (format 8). (See @file{} for information about other formats that are supported.) The functions that read and write signal files perform appropriate transformations so that the samples visible to the application program are always amplitudes of type @code{int} (at least 16 bits), regardless of the signal file format. @node Annotation Files, Calibration Files, Signal Files, Database Files @unnumberedsubsec Annotation Files @cindex annotation file @dfn{Annotation files} have names of the form @file{@var{record}.@var{annotator}}. Those named @file{@var{record}.atr} are reference annotation files (assumed to be correct). Annotation files are binary, and contain records of variable length that average slightly over 16 bits per annotation. @node Calibration Files, EDF Files, Annotation Files, Database Files @unnumberedsubsec Calibration Files @cindex calibration file Unlike header, signal, and annotation files, @dfn{calibration files} are not associated with individual records. A calibration file is needed only if you have records containing signals other than ECGs; in this case, it is likely that a single calibration file will be adequate for use with all of your records. Calibration files are text files, with lines terminated by ASCII carriage-return/line-feed pairs, created by @code{newcal}, from which @code{calopen} reads the calibration list @ifnotinfo (@pxref{WFDB_Calinfo structures, , Calibration Information Structures}). @end ifnotinfo @ifinfo (@pxref{WFDB_Calinfo structures}). @end ifinfo The WFDB Software Package includes a standard calibration file, @file{wfdbcal}, in the @file{data} directory. @node EDF Files, AHA Format Files, Calibration Files, Database Files @unnumberedsubsec EDF Files @cindex EDF @cindex European Data Format European Data Format (EDF) was defined in 1990, and it has become a very widely supported open format for exchange of recorded physiologic signals, especially polysomnograms. EDF files encapsulate functional equivalents of header and signal files, and EDF+ files can also include annotation streams (stored as signals). EDF files begin with an embedded (text) header containing specifications of the signals and a limited amount of demographic information, followed by the binary samples of the signals. Within each block of samples, typically one second to one minute in length, all samples of the first signal are stored consecutively, followed by all samples of the second signal, etc. EDF files can be read directly using WFDB library version 10.4.5 and later. The name of the file, which must include a '.' and cannot end in '.hea', can be passed as a record name to any WFDB library function that accepts record names. Although the WFDB library does not support EDF as an output format, the @file{mit2edf} application included in the WFDB Software Package can convert anything readable by the WFDB library into EDF. EDF+, defined in 2003, is backwards-compatible with EDF (any EDF reader, including the WFDB library, can read EDF+), but the additional features of EDF+, including methods for reading annotations and recognizing signal discontinuities (which are marked by annotations), are available only from EDF+-specific readers. The WFDB library does not currently include built-in support for the additional features of EDF+, but annotation streams are available as signals, so it is possible for an application using the WFDB library to provide its own means of decoding annotation streams as they are read. Further information about EDF and EDF+ is available at @uref{http://www.edfplus.info/}. @node AHA Format Files, Standard I/O, EDF Files, Database Files @unnumberedsubsec AHA Format Files @cindex AHA format The ``AHA Format'' was defined in 1980 for storage of database records on 9-track digital tape. Signal files in AHA format are in format 16, with two signals multiplexed into one file (@pxref{Multiplexed Signal Files}), and may be read and written using @code{getvec} and @code{putvec}. AHA-format annotation files are binary, and contain fixed-length (16-byte) annotation records. An annotation file in AHA format may be read or written using @code{getann} or @code{putann}, if the @code{stat} field of the @code{WFDB_Anninfo} object is set to @code{WFDB_AHA_READ} or @code{WFDB_AHA_WRITE} before opening the file. @code{annopen} recognizes the format of input annotation files automatically and prints a warning if the format does not match what was expected on the basis of @code{stat}. AHA format annotation files may be converted to standard format without loss of information, and doing so reduces the storage requirement by a factor of eight. Yet another format has been used more recently for distribution of AHA DB files on floppy diskettes and CDROMs. This format is compatible with neither the original AHA format nor with any of the formats supported directly by the WFDB library. Programs @file{a2m} and @file{ad2m}, supplied with the WFDB Software Package, can convert files in this format (as well as those in the original AHA format) to the standard formats. @node Standard I/O, Multiplexed Signal Files, AHA Format Files, Database Files @section Using Standard I/O for Database Files @cindex standard I/O (as WFDB files) @cindex pipes (as WFDB files) If @samp{-} is supplied as a record name to any of the functions that read or write header files, the @file{hea} file is taken to be the standard input or output, as appropriate. If the name of a signal file is specified in the @file{hea} file (or in the array of @code{WFDB_Siginfo} objects passed to @code{osigfopen}) as @samp{-}, the standard input (output) is used by @code{getvec} (@code{putvec}). If the name of an annotator is given in the array of @code{WFDB_Anninfo} objects as @samp{-}, the standard input (output) is used by @code{getann} (@code{putann}). If the name of a calibration file is given as @samp{-}, the standard input (output) is used by @code{calopen} (@code{newcal}). Under MS-DOS, these features may not always be usable, since the standard input and output are usually opened in ``text'' mode (which is unsuitable for binary database files). Although the WFDB library does not forbid the use of the standard input or output for more than one function (e.g., as both a signal file and an annotation file), such use is in general a gross error that is likely to lead to unintended results. @node Multiplexed Signal Files, Multi-Frequency Records, Standard I/O, Database Files @section Multiplexed Signal Files @cindex multiplexed signal file @cindex signal group Multiplexed signal files may be identified by examining the @code{group} fields of the array of @code{WFDB_Siginfo} objects returned by @code{isigopen} or @code{wfdbinit}. Signals belonging to the same group are multiplexed together in the same file. If all signals in a given signal file have been sampled at the same frequency, and there are @var{n} signals in the file, then each group of @var{n} successive samples in that file contains a sample from each signal, always in the same order (but @pxref{Multi-Frequency Records}). Multiplexed signal files can be useful if the storage device is sequential-access only (e.g., 9-track tape), if the storage device has lengthy seek times (e.g., optical disk), if many signals must be recorded and Unix's per-process limit on open files would otherwise be exceeded, or if very high speed is required while the file is being created (because of sampling constraints). CDROM signal files, and those available from PhysioNet, are multiplexed unless the record contains only one signal. @node Multi-Frequency Records, Multi-Segment Records, Multiplexed Signal Files, Database Files @section Multi-Frequency Records @cindex multi-frequency records @cindex sampling frequency @cindex frame (of samples) @cindex sample frame @cindex frequency multiplier When signals of different types are recorded simultaneously and for lengthy periods, it may be appropriate to choose different sampling frequencies in order to reduce the storage requirements for signals of limited bandwidth. The support for multi-frequency records provided in WFDB library version 9.0 (and later versions) allows application programs to read and write records containing signals digitized at multiple sampling frequencies. In a multi-frequency record, a @dfn{frame} of samples contains one @emph{or more} samples from each signal. The @dfn{frame rate} (base sampling frequency) of the record, as recorded in the header file and as normally returned by @code{sampfreq}, is defined as the number of frames per second. Signals sampled at multiples of the frame rate are referred to as @dfn{oversampled signals}. For each signal, a frequency multiplier specifies how many samples are included in each frame. The frequency multiplier (1 by default) is an integer, encoded within the format field in the header file, and specified in the @code{spf} field of the @code{WFDB_Siginfo} structure for the signal. A frame can be read as it was written @ifnotinfo (@pxref{getframe, , @code{getframe}}) @end ifnotinfo @ifinfo (@pxref{getframe}) @end ifinfo by an application that has been written to make use of multi-frequency records. Applications that are not ``multi-frequency aware'' can still read signals using the standard @code{getvec} interface, which returns (as always) one sample per signal on each invocation. By default, @code{getvec} reads multi-frequency records in @dfn{low-resolution} mode. In this mode, each oversampled signal is resampled at the frame rate by averaging all of its samples in each frame. The function @code{setgvmode} can be used to select @dfn{high-resolution} mode, in which @code{getvec} replicates samples of signals digitized at less than the maximum sampling frequency (i.e., using zero-order interpolation) so that each sample of an oversampled signal appears in at least one sample vector returned by @code{getvec}. In this mode, @code{sampfreq} returns the number of samples per signal returned by @code{getvec} per second of the record. Furthermore (when using WFDB library version 9.6 and later versions), all time quantities passed to and from the WFDB library functions are understood to be in units of these shorter sampling intervals; thus, for example, @code{getann} converts times in frame numbers (as recorded in annotation files) into times in sample numbers before filling in the caller's annotation structure, and @code{putann} converts times in sample numbers into times in frame numbers before writing annotations into annotation files. This permits applications that are not ``multi-frequency aware'' to read multi-frequency records with the highest possible resolution. @cindex @code{WFDBGVMODE} (environment variable) The operating mode used by @code{getvec}, if not specified by an explicit call to @code{setgvmode}, is determined by the value of the environment variable @code{WFDBGVMODE} if it is set, and otherwise by the value of @code{DEFWFDBGVMODE} in @file{wfdblib.h} at the time the library was compiled.. In either case, a value of 0 selects low-resolution mode, and any other value selects high-resolution mode. @node Multi-Segment Records, Multiple Record Access, Multi-Frequency Records, Database Files @section Multi-Segment Records @cindex multi-segment records @cindex concatenating records @cindex nested records @cindex layout segment @cindex fixed layout @cindex variable layout A multi-segment record consists of two or more concatenated segments. Each segment is an ordinary WFDB record, with its own header file and signal file(s). There are two types of multi-segment records: @dfn{fixed-layout} records, in which all signals must appear in the same order within each segment (signals may not be omitted, added, or swapped), and the gain and baseline of any given signal may not change from segment to segment; and @dfn{variable-layout} records, which are not bound by these constraints. If the first segment of a multi-segment record has a length of zero samples, that segment is a @dfn{layout segment}, and the record is a variable-layout record. Version 9.1 of the WFDB library is the first to support reading and writing fixed-layout multi-segment records, and version 10.3.17 is the first to support reading variable-layout multi-segment records. In both types of multi-segment records, the sampling frequency of any given signal must be the same in each segment. Segments of multi-segment records must be ordinary records (it is not permitted to nest one multi-segment record within another, for example), and the length of each segment must be specified (the WFDB library does not impose this requirement on ordinary records that are not part of a multi-segment record). There are no other restrictions on segments; specifically, it is permitted to mix segments with different storage formats, and for any segment to appear more than once. A special header file (created either manually or by using @code{setmsheader}) specifies the record name for each segment in a multi-segment record. Once this special header exists, the multi-segment record can be read by any WFDB application. Note that only the signal files of the segments are ``linked'' by the multi-segment record's header; annotation files associated with the individual segments are @emph{not} readable as part of the multi-segment record (although an annotation file associated directly with the multi-segment record can be created and read just as for an ordinary record). From the point of view of a WFDB application, reading a multi-segment record is exactly like reading an ordinary record; specifically, @code{isigsettime} works as expected, permitting jumps forward and backward between as well as within segments. Unlike ordinary segments, no signal file is associated with a layout segment; only the header file is needed. In the header file of a layout segment, all of the signals present in any of the other segments of the record are listed, in the desired order and with the desired gains and baselines. When the WFDB library reads the record, @code{getframe} assembles the frame, scaling and shifting each component as needed. If any signals are missing during a segment, the resulting gaps are filled with the sample value @code{WFDB_INVALID_SAMPLE}. In this way, WFDB applications do not need to be aware of signal changes; rather, they may read variable-layout records as if they were ordinary (fixed-layout) records. WFDB applications generally assume fixed-layout records, i.e., that the number and types of available signals (and their sampling frequencies, gains, and baselines) are constant throughout the record. These conditions do not always apply in clinical settings, in which signals may be added, removed, or recalibrated to meet clinical needs, resulting in variable-layout recordings. A variable-layout recording may be divided into segments such that each segment is a fixed-layout WFDB record. The segments can then be reassembled into a multi-segment WFDB record. Version 10.3.17, and later versions, of the WFDB library contain code in @code{signal.c} to create a virtual fixed-layout record on the fly when reading a variable-layout record. This feature can also be used to transform ordinary records on the fly, if it is desirable to rearrange, delete, duplicate, scale, or shift signals. @node Multiple Record Access, Special Files, Multi-Segment Records, Database Files @section Simultaneous Access to Multiple Records @cindex simultaneous records @cindex access to multiple records @cindex multiple record access Selection functions that accept @var{record} arguments (@code{annopen}, @code{isigopen}, @code{osigopen}, and @code{wfdbinit}) normally close any active database files of the types with which each deals before proceeding. The argument @code{+}@var{record} is synonymous with @var{record}, but has the effect of causing these functions to leave any active files open. (For convenience, the other functions that accept @var{record} arguments --- @code{sampfreq}, @code{newheader}, and @code{setheader} --- also treat @var{record} and @code{+}@var{record} as synonymous, but without any noticeable effect.) The restrictions on the total numbers of signals and annotation files still apply. If the sampling frequencies or lengths of the records do not match, a warning message will be produced (unless @code{wfdbquiet} was invoked). The time-conversion functions @ifnotinfo (@pxref{timstr and strtim, , @code{timstr} and @code{strtim}}) @end ifnotinfo @ifinfo (@pxref{timstr and strtim}) @end ifinfo will continue to use the sampling frequency and base time defined for the first record that was opened, unless these attributes are reset by @code{sampfreq}, @code{setsampfreq}, or @code{setbasetime}. Function @code{calopen} uses the @samp{+} convention for calibration file names. Although it normally creates the calibration list from scratch each time it is called, it retains the current calibration list if the calibration file name is prefixed by @samp{+}. @node Special Files, Piped and Local Records, Multiple Record Access, Database Files @section Signals That Are Not Stored in Disk Files @cindex character devices (as signal files) @cindex nine-track tape @cindex reading 9-track tape @cindex signal file (on tape) @cindex special files (as signal files) @cindex Unix character devices (as signal files) @cindex writing 9-track tape The @code{fname} component of a @code{WFDB_Siginfo} object can be any string acceptable as a file name to your operating system. Under Unix, for example, signals can be read from (or written to) `special' files such as @file{/dev/rmt0} (the raw tape drive). If I/O must be performed in fixed-size blocks (such as for Unix character devices), the @code{bsize} component of the @code{WFDB_Siginfo} object must contain the appropriate block size in bytes. In such cases, the WFDB library must obtain (using @code{malloc} (see @cite{K&R}, page 167) an amount of memory equal to the size of one block when the signal file is first opened. For large programs running on 16-bit machines, this can cause problems if signal files with large block sizes are read. (In such cases, @code{isigopen} or @code{osigopen} will not open the signal file if there is not enough memory to allocate a buffer.) Under Unix, if this problem occurs, use the ``piped records'' (@pxref{Piped and Local Records}) instead. The usual method is to read or write the signal file using a utility such as Unix's @code{dd} and to pipe the data to or from the application program. Although this approach is flexible, there are a few drawbacks: @enumerate @item While reading piped input, the standard input cannot be used for other purposes by the application program. Interactive programs can avoid problems by opening @file{/dev/tty} for I/O, however. @item Programs that use @code{isigsettime} or @code{isgsettime} cannot perform backward skips on piped input, and forward skips can be quite slow. @item Additional system resources (computation time, process slots, and memory) are needed when using pipes, in comparison with the usual method of operation. @end enumerate Several special-purpose header files allow application programs to read data directly from 9-track tape. When the WFDB Software Package is installed, these files are copied into the @file{tape} subdirectory of the system-wide database directory. The record names associated with most of these header files (@file{tape/512}, @file{tape/1024}, @file{tape/4096}, @file{tape/10240}) specify the block size in bytes. These use 16-bit format, 250 Hz samples, 12 bit ADC with zero ADC offset, two signals multiplexed into one, and data to be read from @file{/dev/rmt0}. Record @file{tape/6144d} uses 8-bit difference format, 6144 bytes/block, and is otherwise similar to the others. Records @file{tape/ahatape} and @file{tape/mittape} can be used to read or write an AHA-format signal file on a 9-track tape that has been positioned to the beginning of the correct file; the signal file for these is @file{/dev/nrmt0} (the non-rewinding raw tape drive). If the tape density is encoded into the tape drive name on your system, additional header files may be needed. @node Piped and Local Records, NETFILES, Special Files, Database Files @section Piped and Local Records @cindex piped record @cindex record (piped) @cindex signal file (piped) @cindex standard I/O (as WFDB files) @dfn{Piped record} header files allow application programs to read signals from the standard input, or write them to the standard output. Record @file{8} specifies 8-bit format, a 10-bit ADC, zero ADC offset, and two signals sampled at 250 Hz, both of which are to be acquired from the standard input, or written to the standard output. Record @file{16} specifies 16-bit format and a 12-bit ADC, and is otherwise identical to record @file{8}. ADCs from several manufacturers can produce output in the format specified by record @file{16}; thus such output can be piped directly into an application program using record @file{16}. Signal files in AHA format also match these specifications. Piped records for reading or writing other numbers of signals are provided in the @file{pipe} subdirectory of the system-wide database directory; they are named @file{pipe/8x@var{n}} and @file{pipe/16x@var{n}}, where @var{n} is the number of signals (@var{n} = 1, 2, @dots{}, 16; piped record header files can be created with larger numbers of signals (use the existing files as a model). @cindex local record @cindex signal file (local) Application programs may also read or write signal files in the current directory using @dfn{local record} header files. Record @file{16l} (``one-six-ell'') specifies up to sixteen format 16 files, and record @file{8l} (``eight-ell'') specifies up to sixteen format 8 files, named @file{data0}, @file{data1}, @file{data2}, @dots{}, @file{data@var{n}} in the current directory. When opened using @code{isigopen} or @code{wfdbinit}, these signal files will be readable by @code{getvec} as signals 0, 1, 2, @dots{} 16 respectively. These files should be created by the user, with the use of @code{putvec}. It is necessary to create only as many signal files as will be used; if, for example, only one signal is needed, only @file{data0} need be created. @node NETFILES, Annotation Order, Piped and Local Records, Database Files @section NETFILES @cindex NETFILES @cindex URL @cindex http @cindex ftp @cindex WFDB path @cindex curl @cindex libcurl If the symbol @code{WFDB_NETFILES} is defined at the time the WFDB library is compiled, then input files located on remote web (HTTP) and FTP servers can be read directly. This capability is implemented using the @code{libcurl} library (which is available on many of the platforms supported by the WFDB library). NETFILES support, if available, is transparent to WFDB applications. To make use of this feature, simply link to the NETFILES-enabled WFDB library (the necessary @code{libcurl} functions will be loaded automatically), and incorporate one or more URL prefixes in the WFDB path. @cindex WFDB path @cindex PhysioBank In current versions of the WFDB library, the default WFDB path (defined in the WFDB library source file @code{wfdblib.h}, and used as the WFDB path if the WFDB environment variable is undefined) is @samp{. /usr/database http://physio@-net.org/physio@-bank/data@-base}. (The second component, after the @samp{.} that specifies the current directory, may vary, depending on your platform and the choices made during installation.) The third component is a URL prefix pointing to PhysioBank, an on-line archive for a wide variety of standard databases of physiologic signals. For example, the MIT-BIH Polysomnographic Database is kept in @uref{http://physio@-net.org/physio@-bank/data@-base/slpdb}, so it is possible to read record @code{slp37} of that database directly from PhysioBank by passing @code{slpdb/slp37} as the @var{record} argument to @code{wfdbinit} (or @code{isigopen}, @code{annopen}, etc.). The current implementation of @code{libcurl} permits input from @code{http://} URLs in much the same way that local files are read, provided that the remote web server supports HTTP 1.1 range requests (most, including PhysioNet's, do). This means that it is not necessary to download an entire file in order to examine part of it, and you may notice little or no speed difference between local file and network file input for many applications. If the remote server does not support range requests, however, or if input is from an @code{ftp://} URL, the current implementations download the entire file to memory, so you may notice a significant startup delay if the file is long and your network connection is slow, or if the file does not fit into physical memory. Currently, NETFILES support is limited to input files; as always, any output files created by the WFDB library are written into the current directory, unless the record name contains local path information. NETFILES support was introduced in WFDB library version 10.0.1. @node Annotation Order, , NETFILES, Database Files @section Annotation Order @cindex annotation order @cindex canonical order of annotations @cindex order of annotations WFDB applications may generally assume (and most of them do assume) that all annotations in any given annotation file are in @dfn{canonical order}. Successful use of @code{iannsettime} requires that this assumption be correct. The earliest versions of the WFDB library (before version 6.1) defined canonical order as time order. Versions 6.1 through 10.4.11 define canonical order as @code{time} and @code{chan} order, and versions 10.4.12 and later also use @code{num} for ordering (thus annotations are arranged first in @code{time} order, and any simultaneous annotations are arranged according to the value of their @code{num} fields, from smallest to largest, and those with identical @code{num} fields are similarly arranged in @code{chan} order). @cindex location (of annotation) @cindex annotation location @cindex virtual array of annotations @cindex annotation (canonical order) @cindex canonical order of annotations The combination of the @code{time}, @code{num}, and @code{chan} fields of an annotation defines a unique @dfn{location} in a virtual array of annotations which an annotation file represents. No two annotations may occupy the same location in this virtual array. This restriction was enforced by versions of the WFDB library earlier than version 9.7. In these versions of the WFDB library, @code{putann} required that annotations be written in canonical order, and refused to write any out-of-order annotations supplied to it. @cindex @code{sortann} Current versions of the WFDB library do not impose this requirement. In version 9.7 and later versions, @code{putann} accepts and records out-of-order annotations and multiple annotations that occupy the same location. If any such annotations have been written, the completed annotation file is rewritten in canonical order by @code{wfdbquit} or @code{oannclose}. This is accomplished by running @file{sortann} (see the @cite{WFDB Applications Guide}) as a separate process using the ANSI C @code{system} function. If this function is not available, or if @file{sortann} cannot be run, @code{wfdbquit} (or @code{oannclose}) emits a warning message describing how to post-process the annotations to put them into canonical order. @cindex changing an annotation @cindex deleting an annotation @cindex annotation (changing or deleting) Although it is possible using current versions of the WFDB library to write two or more annotations to the same location, @emph{only the last annotation written to any given location is retained} in the canonically-ordered annotation file. Thus that an application that generates an annotation file can change the @code{anntyp}, @code{subtyp}, or @code{aux} fields of a previously-written annotation simply by writing another annotation to the same location (i.e, with the same @code{time}, @code{num}, and @code{chan} fields). As a special case, an application may @emph{delete} a previously-written annotation by writing a @code{NOTQRS} annotation to the same location. To move an annotation to a different location (i.e., to change its @code{time}, @code{num}, or @code{chan} fields), it is necessary to delete it from the original location, and then to insert it at the desired location, using two separate invocations of @code{putann}. @cindex unsorted annotation files @cindex @code{WFDBANNSORT} (environment variable) In unusual circumstances, an unsorted annotation file may be useful (for example, as an aid for debugging the application that produced it; @file{rdann} can be used to list all of the annotations in such a file, in the order in which they were written). In some environments, the use of the ANSI C @code{system} function may be a security problem, and you may wish to avoid automatic sorting of annotations for this reason. Set the environment variable @code{WFDBANNSORT} to 0 at run time, or define the symbol @code{DEFWFDBANNSORT} as 0 when compiling the WFDB library, if you wish to suppress automatic annotation sorting by @code{wfdbquit} and @code{oannclose}. @node Examples, Exercises, Database Files, Top @chapter Programming Examples @cindex examples @cindex programming examples The programs in this chapter are useful as models for a variety of applications that use the WFDB library. The line numbers are for reference only; they are not part of the programs. Any of these examples can be compiled (under Unix) using a command of the form @example cc @var{file.c} -lwfdb @end example @noindent or, if the WFDB library or its @code{*.h} files are not in the standard locations: @example cc `wfdb-config --cflags` @var{file.c} `wfdb-config --libs` @end example @noindent where @var{file.c} is the name of the file containing the source; @pxref{Usage, , Using the WFDB Library}, for further information. The sources for these examples are included in the WFDB Software Package, within the @file{examples} directory. @menu * Example 1:: An annotation filter. * Example 2:: A WFDB-to-AHA format annotation translator. * Example 3:: An annotation printer. * Example 4:: Generating an R-R interval histogram. * Example 5:: A program that prints signal specifications. * Example 6:: A differentiator. * Example 7:: A general-purpose FIR filter. * Example 8:: Creating a new WFDB record. * Example 9:: A signal averager. * Example 10:: A QRS detector. @end menu @node Example 1, Example 2, Examples, Examples @unnumberedsec Example 1: An Annotation Filter The following program copies an annotation file, changing all QRS annotations to @code{NORMAL} and deleting all non-QRS annotations. @example @i{1} #include @i{2} #include @i{3} #include @i{4} @i{5} main() @i{6} @{ @i{7} WFDB_Anninfo an[2]; @i{8} char record[8], iann[10], oann[10]; @i{9} WFDB_Annotation annot; @i{10} @i{11} printf("Type record name: "); @i{12} fgets(record, 8, stdin); record[strlen(record)-1] = '\0'; @i{13} printf("Type input annotator name: "); @i{14} fgets(iann, 10, stdin); iann[strlen(iann)-1] = '\0'; @i{15} printf("Type output annotator name: "); @i{16} fgets(oann, 10, stdin); oann[strlen(oann)-1] = '\0'; @i{17} an[0].name = iann; an[0].stat = WFDB_READ; @i{18} an[1].name = oann; an[1].stat = WFDB_WRITE; @i{19} if (annopen(record, an, 2) < 0) exit(1); @i{20} while (getann(0, &annot) == 0) @i{21} if (isqrs(annot.anntyp)) @{ @i{22} annot.anntyp = NORMAL; @i{23} if (putann(0, &annot) < 0) break; @i{24} @} @i{25} wfdbquit(); @i{26} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example1.c} for a copy of this program.) @c @group @noindent @strong{Notes:} @table @emph @item Line 2: All programs that use the WFDB library must include @file{}. @item Line 3: The @code{#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 @file{}, one of which will be needed in line 22. @item Line 7: Since there will be two annotators (one each for input and output), the array of @code{WFDB_Anninfo} objects has two members. @item Line 9: This structure will be filled in by @code{getann}, modified, and passed to @code{putann} for output. @item 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 @code{fgets} copies into the string) with a null. String arguments to WFDB library functions should not include newline characters. @item Lines 17--18: Pointers to the character arrays (strings) containing the annotator names are filled into the @code{name} fields of the array of @code{WFDB_Anninfo} objects. Note that the @code{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 @cite{K&R}, pp. 97--100. The input annotator is to be read, the output annotator is to be written. @code{WFDB_READ} and @code{WFDB_WRITE} are defined in @file{}. @item Line 19: Note that the first and second arguments of @code{annopen} are the names of the respective arrays; thus @code{annopen} receives pointers rather than values in its argument list. @item Line 20: An annotation is read from annotator 0 into @code{annot}. The @samp{&} is necessary since @code{getann} requires a pointer to the structure in order to be able to modify its contents. When @code{getann} returns a negative value, no more annotations remain to be read and the loop ends. @item Line 21: The macro @code{isqrs} is defined in @file{}; @code{isqrs(@var{x})} is true if @var{x} is an annotation code that denotes a QRS complex, false if @var{x} is not a QRS annotation code. @item Line 22: @code{NORMAL} is defined in @file{}. @item Line 23: The call to @code{putann} now writes the modified annotation in the output annotator 0 file. As for @code{getann}, a pointer to @code{annot} must be passed using the @samp{&} operator. @item Line 25: All files are closed prior to exiting. This is mandatory since the program creates an output file with @code{putann}. @end table @c @end group @node Example 2, Example 3, Example 1, Examples @unnumberedsec Example 2: An Annotation Translator This program translates the @file{atr} annotations for the record named in its argument into an AHA-format annotation file with the annotator name @file{aha}. @example @i{1} #include @i{2} #include @i{3} @i{4} main(argc, argv) @i{5} int argc; @i{6} char *argv[]; @i{7} @{ @i{8} WFDB_Anninfo an[2]; @i{9} WFDB_Annotation annot; @i{10} @i{11} if (argc < 2) @{ @i{12} fprintf(stderr, "usage: %s record\n", argv[0]); @i{13} exit(1); @i{14} @} @i{15} an[0].name = "atr"; an[0].stat = WFDB_READ; @i{16} an[1].name = "aha"; an[1].stat = WFDB_AHA_WRITE; @i{17} if (annopen(argv[1], an, 2) < 0) exit(2); @i{18} while (getann(0, &annot) == 0 && putann(0, &annot) == 0) @i{19} ; @i{20} wfdbquit(); @i{21} exit(0); @i{22} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example2.c} for a copy of this program.) @noindent @strong{Notes:} @table @emph @item Lines 4--6: If this doesn't look familiar, see @cite{K&R}, pp. 114--115. @item Lines 11--14: This is the standard idiom for producing those cryptic error messages for which Unix programs are notorious; @code{argv[0]} is the name by which the program was invoked. @item Lines 15--16: These lines set up the annotator information. Input annotator 0 is the @file{atr} annotation file, and output annotator 0 will be written in AHA format. @item Line 17: If we can't read the input or write the output, quit with an error message from @code{annopen}. @item Line 18: Here's where the work is done. The format translation is handled entirely by @code{getann} and @code{putann}. The loop ends normally when @code{getann} reaches the end of the input file, or prematurely if there is a read or write error. @item 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. @end table @node Example 3, Example 4, Example 2, Examples @unnumberedsec 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. @example @i{1} #include @i{2} #include @i{3} @i{4} main(argc, argv) @i{5} int argc; @i{6} char *argv[]; @i{7} @{ @i{8} WFDB_Anninfo a; @i{9} WFDB_Annotation annot; @i{10} @i{11} if (argc < 3) @{ @i{12} fprintf(stderr, "usage: %s annotator record\n", argv[0]); @i{13} exit(1); @i{14} @} @i{15} a.name = argv[1]; a.stat = WFDB_READ; @i{16} (void)sampfreq(argv[2]); @i{17} if (annopen(argv[2], &a, 1) < 0) exit(2); @i{18} while (getann(0, &annot) == 0) @i{19} printf("%s (%"WFDB_Pd_TIME") %s %d %d %d %s\n", @i{20} timstr(-(annot.time)), @i{21} annot.time, @i{22} annstr(annot.anntyp), @i{23} annot.subtyp, annot.chan, annot.num, @i{24} (annot.aux != NULL && *annot.aux > 0) ? @i{25} (char *) annot.aux+1 : ""); @i{26} exit(0); @i{27} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example3.c} for a copy of this program.) @noindent @strong{Notes:} @table @emph @item Line 16: The invocation of @code{sampfreq} here sets the internal variables needed by @code{timstr} below. @item 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 @code{timstr(annot.time)} instead, we would obtain the elapsed time from the beginning of the record. @item Lines 24--25: This expression evaluates to an empty string unless the @code{aux} string is non-empty. It makes the assumption that @code{aux} is a printable ASCII string; the printable part follows the length byte. @end table @node Example 4, Example 5, Example 3, Examples @unnumberedsec 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. @example @i{1} #include @i{2} #include @i{3} #include @i{4} @i{5} main(argc, argv) @i{6} int argc; @i{7} char *argv[]; @i{8} @{ @i{9} int rr, *rrhist, rrmax; @i{10} WFDB_Time t; @i{11} WFDB_Anninfo a; @i{12} WFDB_Annotation annot; @i{13} void *calloc(); @i{14} @i{15} if (argc < 3) @{ @i{16} fprintf(stderr, "usage: %s annotator record\n", argv[0]); @i{17} exit(1); @i{18} @} @i{19} a.name = argv[1]; a.stat = WFDB_READ; @i{20} if (annopen(argv[2], &a, 1) < 0) exit(2); @i{21} if ((rrmax = (int)(3*sampfreq(argv[2]))) <= 0) exit(3); @i{22} if ((rrhist = (int *)calloc(rrmax+1, sizeof(int))) == NULL) @{ @i{23} fprintf(stderr, "%s: insufficient memory\n", argv[0]); @i{24} exit(4); @i{25} @} @i{26} while (getann(0, &annot) == 0 && !isqrs(annot.anntyp)) @i{27} ; @i{28} t = annot.time; @i{29} while (getann(0, &annot) == 0) @i{30} if (isqrs(annot.anntyp)) @{ @i{31} if ((rr = annot.time - t) > rrmax) rr = rrmax; @i{32} rrhist[rr]++; @i{33} t = annot.time; @i{34} @} @i{35} for (rr = 1; rr < rrmax; rr++) @i{36} printf("%4d %s\n", rrhist[rr], mstimstr((WFDB_Time)rr)); @i{37} printf("%4d %s (or longer)\n", rrhist[rr], mstimstr((WFDB_Time)rr)); @i{38} exit(0); @i{39} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example4.c} for a copy of this program.) @c @group @noindent @strong{Notes:} @table @emph @item Lines 21--25: Here we allocate storage for the histogram. The value returned by @code{sampfreq}, if positive, specifies the number of sample intervals per second; we will allocate 3 seconds' worth of bins, initialized to zero. See @cite{K&R}, page 167, for a description of @code{calloc}. @item Lines 26--28: This code sets @code{t} to the time of the first annotated beat in the record. @item Lines 29--34: Here we read the remainder of the annotations, skipping any non-beat annotations. The difference between the values of @code{annot.time} for consecutive beat annotations defines an R-R interval (@code{rr}). Each possible value of @code{rr} up to @code{rrmax} is assigned a bin in @code{rrhist}. Intervals longer than 3 seconds (@code{rrmax}) are counted in the bin corresponding to @code{rr} = @code{rrmax}. @item 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 @code{rr} starts at 0 rather than 1 in line 35?) @end table @c @end group @node Example 5, Example 6, Example 4, Examples @unnumberedsec Example 5: Reading Signal Specifications This program reads the signal specifications of the record named as its argument: @example @i{1} #include @i{2} #include @i{3} @i{4} main(argc, argv) @i{5} int argc; @i{6} char *argv[]; @i{7} @{ @i{8} WFDB_Siginfo *s; @i{9} int i, nsig; @i{10} @i{11} if (argc < 2) @{ @i{12} fprintf(stderr, "usage: %s record\n", argv[0]); @i{13} exit(1); @i{14} @} @i{15} nsig = isigopen(argv[1], NULL, 0); @i{16} if (nsig < 1) exit(2); @i{17} s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); @i{18} if (s == NULL) @{ @i{19} fprintf(stderr, "insufficient memory\n"); @i{20} exit(3); @i{21} @} @i{22} if (isigopen(argv[1], s, nsig) != nsig) exit(2); @i{23} printf("Record %s\n", argv[1]); @i{24} printf("Starting time: %s\n", timstr(0L)); @i{25} printf("Sampling frequency: %g Hz\n", sampfreq(argv[1])); @i{26} printf("%d signals\n", nsig); @i{27} for (i = 0; i < nsig; i++) @{ @i{28} printf("Group %d, Signal %d:\n", s[i].group, i); @i{29} printf(" File: %s\n", s[i].fname); @i{30} printf(" Description: %s\n", s[i].desc); @i{31} printf(" Gain: "); @i{32} if (s[i].gain == 0.) @i{33} printf("uncalibrated; assume %g", WFDB_DEFGAIN); @i{34} else printf("%g", s[i].gain); @i{35} printf(" adu/%s\n", s[i].units ? s[i].units : "mV"); @i{36} printf(" Initial value: %d\n", s[i].initval); @i{37} printf(" Storage format: %d\n", s[i].fmt); @i{38} printf(" I/O: "); @i{39} if (s[i].bsize == 0) printf("can be unbuffered\n"); @i{40} else printf("%d-byte blocks\n", s[i].bsize); @i{41} printf(" ADC resolution: %d bits\n", s[i].adcres); @i{42} printf(" ADC zero: %d\n", s[i].adczero); @i{43} if (s[i].nsamp > 0L) @{ @i{44} printf(" Length: %s (%ld sample intervals)\n", @i{45} timstr(s[i].nsamp), s[i].nsamp); @i{46} printf(" Checksum: %d\n", s[i].cksum); @i{47} @} @i{48} else printf(" Length undefined\n"); @i{49} @} @i{50} exit(0); @i{51} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example5.c} for a copy of this program.) @noindent @strong{Notes:} @table @emph @item Line 15: The command-line argument, @code{argv[1]}, is the record name. The number of signals listed in the header file for the record is returned by @code{isigopen} as @code{nsig}. If @code{nsig} < 1, @code{isigopen} will print an error message; in this case the program can't do anything useful, so it exits. @item Line 17: We allocate @code{nsig} signal information (@code{WFDB_Siginfo}) objects. @item Line 22: On the second invocation of @code{isigopen}, we pass the pointer to the signal information objects and the number of signals we expect to open. @code{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 @code{nsig}, and the program exits. @item Line 24: Invoking @code{timstr} with an argument of zero (here written @samp{0L} to emphasize to the compiler that the argument is a @code{long} integer) will obtain the starting time of the record. If no starting time is defined, @code{timstr} will return ``@code{0:00:00}''. @item Lines 31--34: Notice how a zero value for @code{gain} is interpreted. @item Line 35: If the @code{units} field is NULL, the physical units are assumed to be millivolts (``mV''). @item Lines 38--40: If @code{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 @code{bsize}. @item 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 @code{timstr} in line 39 is positive, it is interpreted as a time interval. The checksum is defined only if the record length is defined. @end table @node Example 6, Example 7, Example 5, Examples @unnumberedsec Example 6: A Differentiator @cindex digital filter @cindex filter (digital) The program below inverts and differentiates the signals read by @code{getvec} and writes the results with @code{putvec}. The output is readable as record @file{dif}. A wide variety of simple digital filters can be modelled on this example; @pxref{Example 7}, for a more general approach. @example @i{1} #include @i{2} #include @i{3} @i{4} main(argc, argv) @i{5} int argc; @i{6} char *argv[]; @i{7} @{ @i{8} WFDB_Siginfo *s; @i{9} int i, nsig, nsamp=1000; @i{10} WFDB_Sample *vin, *vout; @i{11} @i{12} if (argc < 2) @{ @i{13} fprintf(stderr, "usage: %s record\n", argv[0]); exit(1); @i{14} @} @i{15} if ((nsig = isigopen(argv[1], NULL, 0)) <= 0) exit(2); @i{16} s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); @i{17} vin = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); @i{18} vout = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); @i{19} if (s == NULL || vin == NULL || vout == NULL) @{ @i{20} fprintf(stderr, "insufficient memory\n"); @i{21} exit(3); @i{22} @} @i{23} if (isigopen(argv[1], s, nsig) != nsig) exit(2); @i{24} if (osigopen("8l", s, nsig) <= 0) exit(3); @i{25} while (nsamp-- > 0 && getvec(vin) > 0) @{ @i{26} for (i = 0; i < nsig; i++) @i{27} vout[i] -= vin[i]; @i{28} if (putvec(vout) < 0) break; @i{29} for (i = 0; i < nsig; i++) @i{30} vout[i] = vin[i]; @i{31} @} @i{32} (void)newheader("dif"); @i{33} wfdbquit(); @i{34} exit(0); @i{35} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example6.c} for a copy of this program.) @noindent @strong{Notes:} @table @emph @item 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 @code{osigopen} prints an error message. @item 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 @code{getvec} fails before 1000 samples have been read, the loop ends prematurely. @item Lines 26--27: For each signal, we compute the negated first difference by subtracting the new sample from the previous sample. @item Line 28: One sample of each output signal is written here. @item Lines 29--30: The new input samples are copied into the output sample vector in preparation for the next iteration. @item Line 32: This step is optional. It creates a header file for a new record to be called @file{dif}, which we can then open with another program if we want to read the signals that this program has written. Since the @var{record} argument for @code{osigopen} was @file{8l}, we can also read these files using record @file{8l}; one reason for making a new @file{hea} file here is that the @file{hea} file for @file{8l} may not necessarily indicate the proper sampling frequency for these signals. @item Line 33: Since the program writes output signals, it must invoke @code{wfdbquit} to close the files properly. @end table @node Example 7, Example 8, Example 6, Examples @unnumberedsec Example 7: A General-Purpose FIR Filter @cindex digital filter @cindex filter (digital) This program illustrates the use of @code{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 @samp{filter}, it might be used by @example filter 100 5:0 20 .2 .2 .2 .2 .2 @end example @noindent which would apply a five-point moving average (rectangular window) filter to 20 seconds of record @file{100}, beginning 5 minutes into the record. The output of the program is readable as record @file{out}, for which a header file is created in the current directory. @example @i{1} #include @i{2} #include @i{3} @i{4} main(argc, argv) @i{5} int argc; @i{6} char *argv[]; @i{7} @{ @i{8} double *c, one = 1.0, vv, atof(); @i{9} int i, j, nc = argc - 4, nsig; @i{10} WFDB_Time nsamp, t, t0, t1; @i{11} static WFDB_Sample *v; @i{12} static WFDB_Siginfo *s; @i{13} @i{14} if (argc < 4) @{ @i{15} fprintf(stderr, @i{16} "usage: %s record start duration [ coefficients ... ]\n", @i{17} argv[0]); @i{18} exit(1); @i{19} @} @i{20} if (nc < 1) @{ @i{21} nc = 1; c = &one; @i{22} @} @i{23} else if ((c = (double *)calloc(nc, sizeof(double))) == NULL) @{ @i{24} fprintf(stderr, "%s: too many coefficients\n", argv[0]); @i{25} exit(2); @i{26} @} @i{27} for (i = 0; i < nc; i++) @i{28} c[i] = atof(argv[i+4]); @i{29} if ((nsig = isigopen(argv[1], NULL, 0)) < 1) @i{30} exit(3); @i{31} s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); @i{32} v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); @i{33} if (s == NULL || v == NULL) @{ @i{34} fprintf(stderr, "insufficient memory\n"); @i{35} exit(4); @i{36} @} @i{37} if (isigopen(argv[1], s, nsig) != nsig) @i{38} exit(5); @i{39} t0 = strtim(argv[2]); @i{40} if (t0 < (WFDB_Time)0) t0 = -t0; @i{41} (void)sample(0, t0); @i{42} if (!sample_valid()) @{ @i{43} fprintf(stderr, "%s: inappropriate value for start time\n", @i{44} argv[0]); @i{45} exit(6); @i{46} @} @i{47} if ((nsamp = strtim(argv[3])) < 1) @{ @i{48} fprintf(stderr, "%s: inappropriate value for duration\n", @i{49} argv[0]); @i{50} exit(7); @i{51} @} @i{52} t1 = t0 + nsamp; @i{53} if (osigopen("16l", s, nsig) != nsig) @i{54} exit(8); @i{55} @i{56} for (t = t0; t < t1 && sample_valid(); t++) @{ @i{57} for (j = 0; j < nsig; j++) @{ @i{58} for (i = 0, vv = 0.; i < nc; i++) @i{59} if (c[i] != 0.) vv += c[i]*sample(j, t+i); @i{60} v[j] = (WFDB_Sample)vv; @i{61} @} @i{62} if (putvec(v) < 0) break; @i{63} @} @i{64} @i{65} (void)newheader("out"); @i{66} wfdbquit(); @i{67} exit(0); @i{68} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example7.c} for a copy of this program.) @noindent @strong{Notes:} @table @emph @item Lines 20--22: If no coefficients are provided on the command line, the program will simply copy the selected segment of the input signals. @item 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 @code{double} format and stored in the coefficient vector. @item Lines 29--40: The record name is @code{argv[1]}, and the start time is @code{argv[2]}; if the record can't be opened, the program exits. See the previous example for details on how @code{isigopen} is used. If the user provides an absolute start time @ifnotinfo (@pxref{timstr and strtim, , @code{timstr} and @code{strtim}}), @end ifnotinfo @ifinfo (@pxref{timstr and strtim}), @end ifinfo the negative value returned by @code{strtim} is converted to a sample number in line 40. @item Lines 41--46: Here, @code{sample} is invoked only for its side effect; if any samples can be read from the specified record beginning at sample number @code{t0}, then @code{sample(0, 0L)} returns a valid sample, so that the value returned by @code{sample_valid} is true (1). If not, the program exits. @item Lines 47--52: The @var{duration} argument should be a time interval in @var{HH:MM:SS} format; @code{strtim} converts it to the appropriate number of samples, and @code{t1} is set to the calculated end time in line 52. @item Lines 53--54: The output signals will be written to files in the current directory according to the specifications for record @file{16l} (@pxref{Piped and Local Records}). If we can't write as many output signals as there are input signals, the program exits. @item Lines 56--63: 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 59, we retrieve an input sample, multiply it by a filter coefficient, and add it to a running sum. The sum (@code{vv}) is initialized to zero in line 58 before we begin, and is converted to a @code{WFDB_Sample} in line 60 when we are finished. Once an entire output sample vector ia ready, it is written in line 62. The entire process is repeated until we reach input sample number @code{t1}, or we run out of input samples. @item Line 65: The program creates a header file for record @file{out}, using the signal specifications from record @file{16l} and the sampling frequency from the input record. @end table @node Example 8, Example 9, Example 7, Examples @unnumberedsec Example 8: Creating a New Database Record @cindex creating a record @cindex records (creating) 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 @file{hea} file for the new record. Details of data acquisition are hardware-dependent and are not shown here. @example @i{1} #include @i{2} #include @i{3} @i{4} main() @i{5} @{ @i{6} char answer[32], record[8], directory[32]; @i{7} int i, nsig = 0; @i{8} WFDB_Time nsamp, t; @i{9} double freq = 0.; @i{10} char **filename, **description, **units; @i{11} WFDB_Sample *v; @i{12} WFDB_Siginfo *s; @i{13} @i{14} do @{ @i{15} printf("Choose a record name [up to 6 characters]: "); @i{16} fgets(record, 8, stdin); record[strlen(record)-1] = '\0'; @i{17} @} while (newheader(record) < 0); @i{18} do @{ @i{19} printf("Number of signals to be recorded [>0]: "); @i{20} fgets(answer, 32, stdin); sscanf(answer, "%d", &nsig); @i{21} @} while (nsig < 1); @i{22} s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); @i{23} v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); @i{24} filename = (char **)malloc(nsig * sizeof(char *)); @i{25} description = (char **)malloc(nsig * sizeof(char *)); @i{26} units = (char **)malloc(nsig * sizeof(char *)); @i{27} if (s == NULL || v == NULL || filename == NULL || @i{28} description == NULL || units == NULL) @{ @i{29} fprintf(stderr, "insufficient memory\n"); @i{30} exit(1); @i{31} @} @i{32} for (i = 0; i < nsig; i++) @{ @i{33} if ((filename[i] = (char *)malloc(32)) == NULL || @i{34} (description[i] = (char *)malloc(32)) == NULL || @i{35} (units[i] = (char *)malloc(32)) == NULL) @{ @i{36} fprintf(stderr, "insufficient memory\n"); @i{37} exit(1); @i{38} @} @i{39} @} @i{40} do @{ @i{41} printf("Sampling frequency [Hz per signal, > 0]: "); @i{42} fgets(answer, 32, stdin); sscanf(answer, "%lf", &freq); @i{43} @} while (setsampfreq(freq) < 0); @i{44} do @{ @i{45} printf("Length of record (H:M:S): "); @i{46} fgets(answer, 32, stdin); @i{47} @} while ((nsamp = strtim(answer)) < 1L); @i{48} printf("Directory for signal files [up to 30 characters]: "); @i{49} fgets(directory, 32, stdin); @i{50} directory[strlen(directory)-1] = '\0'; @i{51} printf("Save signals in difference format? [y/n]: "); @i{52} fgets(answer, 32, stdin); @i{53} s[0].fmt = (answer[0] == 'y') ? 8 : 16; @i{54} printf("Save all signals in one file? [y/n]: "); @i{55} fgets(answer, 32, stdin); @i{56} if (answer[0] == 'y') @{ @i{57} sprintf(filename[0], "%s/d.%s", directory, record); @i{58} for (i = 0; i < nsig; i++) @{ @i{59} s[i].fname = filename[0]; @i{60} s[i].group = 0; @i{61} @} @i{62} @} @i{63} else @{ @i{64} for (i = 0; i < nsig; i++) @{ @i{65} sprintf(filename[i], "%s/d%d.%s", directory,i,record); @i{66} s[i].fname = filename[i]; @i{67} s[i].group = i; @i{68} @} @i{69} @} @i{70} for (i = 0; i < nsig; i++) @{ @i{71} s[i].fmt = s[0].fmt; s[i].bsize = 0; @i{72} printf("Signal %d description [up to 30 characters]: ", i); @i{73} fgets(description[i], 32, stdin); @i{74} description[i][strlen(description[i])-1] = '\0'; @i{75} s[i].desc = description[i]; @i{76} printf("Signal %d units [up to 20 characters]: ", i); @i{77} fgets(units[i], 22, stdin); @i{78} units[i][strlen(units[i])-1] = '\0'; @i{79} s[i].units = (*units[i]) ? units[i] : "mV"; @i{80} do @{ @i{81} printf(" Signal %d gain [adu/%s]: ", i, s[i].units); @i{82} fgets(answer, 32, stdin); @i{83} sscanf(answer, "%lf", &s[i].gain); @i{84} @} while (s[i].gain < 0.); @i{85} do @{ @i{86} printf(" Signal %d ADC resolution in bits [8-16]: ", i); @i{87} fgets(answer, 32, stdin); @i{88} sscanf(answer, "%d", &s[i].adcres); @i{89} @} while (s[i].adcres < 8 || s[i].adcres > 16); @i{90} printf(" Signal %d ADC zero level [adu]: ", i); @i{91} fgets(answer, 32, stdin); @i{92} sscanf(answer, "%d", &s[i].adczero); @i{93} @} @i{94} if (osigfopen(s, nsig) < nsig) exit(1); @i{95} printf("To begin sampling, press RETURN; to specify a\n"); @i{96} printf(" start time other than the current time, enter\n"); @i{97} printf(" it in H:M:S format before pressing RETURN: "); @i{98} fgets(answer, 32, stdin); answer[strlen(answer)-1] = '\0'; @i{99} setbasetime(answer); @i{100} @i{101} adinit(); @i{102} @i{103} for (t = 0; t < nsamp; t++) @{ @i{104} for (i = 0; i < nsig; i++) @i{105} v[i] = adget(i); @i{106} if (putvec(v) < 0) break; @i{107} @} @i{108} @i{109} adquit(); @i{110} @i{111} (void)newheader(record); @i{112} wfdbquit(); @i{113} exit(0); @i{114} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example8.c} for a copy of this program.) @noindent @strong{Notes:} @table @emph @item Lines 14--17: This code uses @code{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. @item Lines 57--62: This code generates a file name and initializes the @code{fname} and @code{group} fields of the array of @code{WFDB_Siginfo} objects so that all signals will be saved in one file. @item Lines 63--69: This code generates unique file names and groups for each signal. @item Lines 70--93: Here, information specific to individual signals is gathered. @item 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 @code{osigfopen}. @item Lines 95--99: Just before sampling begins, we set the base time. Note that an empty string argument for @code{setbasetime} gives us the current time read from the system clock. @item 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. @item Lines 103--107: Here is where the samples are acquired (using hardware-dependent code not shown here) and recorded (using @code{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 @code{register} and pointer variables if necessary. In an extreme case the entire loop, possibly including @code{putvec} itself, can be written in assembly language; since it is only a small fraction of the entire program, doing so is within reason. @item 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. @item Line 111: All of the information needed to generate the header file has been stored in WFDB library internal data structures by @code{osigfopen} and @code{putvec}; we call @code{newheader} here (before @code{wfdbquit}) to create the new @file{hea} file. @item Line 112: It is still necessary to use @code{wfdbquit} to close the signal file(s), even after calling @code{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 @code{wfdbquit}.) @end table @node Example 9, Example 10, Example 8, Examples @unnumberedsec 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). @example @i{1} #include @i{2} #include @i{3} #include @i{4} @i{5} main(argc, argv) @i{6} int argc; @i{7} char *argv[]; @i{8} @{ @i{9} int btype, i, j, nbeats = 0, nsig, hwindow, window; @i{10} WFDB_Time stoptime = 0; @i{11} long **sum; @i{12} WFDB_Anninfo a; @i{13} WFDB_Annotation annot; @i{14} WFDB_Sample *v, *vb; @i{15} WFDB_Siginfo *s; @i{16} @i{17} if (argc < 3) @{ @i{18} fprintf(stderr, @i{19} "usage: %s annotator record [beat-type from to]\n", @i{20} argv[0]); @i{21} exit(1); @i{22} @} @i{23} a.name = argv[1]; a.stat = WFDB_READ; @i{24} if ((nsig = isigopen(argv[2], NULL, 0)) < 1) exit(2); @i{25} s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); @i{26} v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); @i{27} vb = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); @i{28} sum = (long **)malloc(nsig * sizeof(long *)); @i{29} if (s == NULL || v == NULL || vb == NULL || sum == NULL) @{ @i{30} fprintf(stderr, "%s: insufficient memory\n", argv[0]); @i{31} exit(2); @i{32} @} @i{33} if (wfdbinit(argv[2], &a, 1, s, nsig) != nsig) exit(3); @i{34} hwindow = strtim(".05"); window = 2*hwindow + 1; @i{35} for (i = 0; i < nsig; i++) @i{36} if ((sum[i]=(long *)calloc(window,sizeof(long))) == NULL) @{ @i{37} fprintf(stderr, "%s: insufficient memory\n", argv[0]); @i{38} exit(2); @i{39} @} @i{40} btype = (argc > 3) ? strann(argv[3]) : NORMAL; @i{41} if (argc > 4) iannsettime(strtim(argv[4])); @i{42} if (argc > 5) @{ @i{43} if ((stoptime = strtim(argv[5])) < 0L) @i{44} stoptime = -stoptime; @i{45} if (s[0].nsamp > 0L && stoptime > s[0].nsamp) @i{46} stoptime = s[0].nsamp; @i{47} @} @i{48} else stoptime = s[0].nsamp; @i{49} if (stoptime > 0L) stoptime -= hwindow; @i{50} while (getann(0, &annot) == 0 && annot.time < hwindow) @i{51} ; @i{52} do @{ @i{53} if (annot.anntyp != btype) continue; @i{54} isigsettime(annot.time - hwindow - 1); @i{55} getvec(vb); @i{56} for (j = 0; j < window && getvec(v) > 0; j++) @i{57} for (i = 0; i < nsig; i++) @i{58} sum[i][j] += v[i] - vb[i]; @i{59} nbeats++; @i{60} @} while (getann(0, &annot) == 0 && @i{61} (stoptime == 0L || annot.time < stoptime)); @i{62} if (nbeats < 1) @{ @i{63} fprintf(stderr, "%s: no `%s' beats found\n", @i{64} argv[0], annstr(btype)); @i{65} exit(4); @i{66} @} @i{67} printf("Average of %d `%s' beats:\n", nbeats, annstr(btype)); @i{68} for (j = 0; j < window; j++) @i{69} for (i = 0; i < nsig; i++) @i{70} printf("%g%c", (double)sum[i][j]/nbeats, @i{71} (i == nsig-1) ? '\n' : '\t'); @i{72} exit(0); @i{73} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example9.c} for a copy of this program.) @noindent @strong{Notes:} @table @emph @item 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). @item Lines 35--39: Here we allocate memory for the @code{sum} vectors that will be used to store the running totals. See @cite{K&R}, page 167, for a description of @code{calloc}. @item 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 @code{NORMAL} QRS complexes. @item 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 @code{getann} to be the first annotation that occurs after the chosen start time. @item 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 @file{hea} file for the record, @code{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. @item 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. @item Lines 52--61: Here we read annotations (the first is already in @code{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 @code{vb} vector, which will be used as a crude baseline estimate; read @code{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). @item Lines 62--71: This is the output section. If no beats of type @code{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. @end table @node Example 10, , Example 9, Examples @unnumberedsec Example 10: A QRS Detector @cindex QRS detector @cindex detector (QRS) @cindex digital filter @cindex filter (digital) 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'', @cite{Computers in Cardiology} @b{6}:37-42 (1979). @example @i{1} #include @i{2} #include @i{3} #include @i{4} @i{5} #define abs(A) ((A) >= 0 ? (A) : -(A)) @i{6} @i{7} main(argc, argv) @i{8} int argc; @i{9} char *argv[]; @i{10} @{ @i{11} int filter, time=0, slopecrit, sign, maxslope=0, nsig, nslope=0, @i{12} qtime, maxtime, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, @i{13} ms160, ms200, s2, scmax, scmin = 0; @i{14} WFDB_Anninfo a; @i{15} WFDB_Annotation annot; @i{16} WFDB_Sample *v; @i{17} WFDB_Siginfo *s; @i{18} @i{19} if (argc < 2) @{ @i{20} fprintf(stderr, "usage: %s record [threshold]\n", argv[0]); @i{21} exit(1); @i{22} @} @i{23} a.name = "qrs"; a.stat = WFDB_WRITE; @i{24} @i{25} if ((nsig = isigopen(argv[1], NULL, 0)) < 1) exit(2); @i{26} s = (WFDB_Siginfo *)malloc(nsig * sizeof(WFDB_Siginfo)); @i{27} v = (WFDB_Sample *)malloc(nsig * sizeof(WFDB_Sample)); @i{28} if (s == NULL || v == NULL) @{ @i{29} fprintf(stderr, "%s: insufficient memory\n", argv[0]); @i{30} exit(2); @i{31} @} @i{32} if (wfdbinit(argv[1], &a, 1, s, nsig) != nsig) exit(2); @i{33} if (sampfreq((char *)NULL) < 240. || @i{34} sampfreq((char *)NULL) > 260.) @i{35} setifreq(250.); @i{36} if (argc > 2) scmin = muvadu(0, atoi(argv[2])); @i{37} if (scmin < 1) scmin = muvadu(0, 1000); @i{38} slopecrit = scmax = 10 * scmin; @i{39} ms160 = strtim("0.16"); ms200 = strtim("0.2"); s2 = strtim("2"); @i{40} annot.subtyp = annot.chan = annot.num = 0; annot.aux = NULL; @i{41} (void)getvec(v); @i{42} t9 = t8 = t7 = t6 = t5 = t4 = t3 = t2 = t1 = v[0]; @i{43} @i{44} do @{ @i{45} filter = (t0 = v[0]) + 4*t1 + 6*t2 + 4*t3 + t4 @i{46} - t5 - 4*t6 - 6*t7 - 4*t8 - t9; @i{47} if (time % s2 == 0) @{ @i{48} if (nslope == 0) @{ @i{49} slopecrit -= slopecrit >> 4; @i{50} if (slopecrit < scmin) slopecrit = scmin; @i{51} @} @i{52} else if (nslope >= 5) @{ @i{53} slopecrit += slopecrit >> 4; @i{54} if (slopecrit > scmax) slopecrit = scmax; @i{55} @} @i{56} @} @i{57} if (nslope == 0 && abs(filter) > slopecrit) @{ @i{58} nslope = 1; maxtime = ms160; @i{59} sign = (filter > 0) ? 1 : -1; @i{60} qtime = time; @i{61} @} @i{62} if (nslope != 0) @{ @i{63} if (filter * sign < -slopecrit) @{ @i{64} sign = -sign; @i{65} maxtime = (++nslope > 4) ? ms200 : ms160; @i{66} @} @i{67} else if (filter * sign > slopecrit && @i{68} abs(filter) > maxslope) @i{69} maxslope = abs(filter); @i{70} if (maxtime-- < 0) @{ @i{71} if (2 <= nslope && nslope <= 4) @{ @i{72} slopecrit += ((maxslope>>2) - slopecrit) >> 3; @i{73} if (slopecrit < scmin) slopecrit = scmin; @i{74} else if (slopecrit > scmax) slopecrit = scmax; @i{75} annot.time = strtim("i") - (time - qtime) - 4; @i{76} annot.anntyp = NORMAL; (void)putann(0, &annot); @i{77} time = 0; @i{78} @} @i{79} else if (nslope >= 5) @{ @i{80} annot.time = strtim("i") - (time - qtime) - 4; @i{81} annot.anntyp = ARFCT; (void)putann(0, &annot); @i{82} @} @i{83} nslope = 0; @i{84} @} @i{85} @} @i{86} t9 = t8; t8 = t7; t7 = t6; t6 = t5; t5 = t4; @i{87} t4 = t3; t3 = t2; t2 = t1; t1 = t0; time++; @i{88} @} while (getvec(v) > 0); @i{89} @i{90} wfdbquit(); @i{91} exit(0); @i{92} @} @end example @noindent (See @uref{http://physionet.org/physiotools/wfdb/examples/example10.c} for a copy of this program.) @noindent @strong{Notes:} @table @emph @item Line 5: A macro that evaluates to the absolute value of its argument. @item Lines 11--12: The names of these variables match those in the original Pascal program. @item 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 @code{setifreq} to specify that we want @code{getvec} to give us data resampled at 250 Hz. The output annotation file is created in line 35 only after invoking @code{setifreq} if necessary. @item 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 @code{scmin} and @code{scmax} are lower and upper bounds for the adaptive threshold @code{slopecrit}. @item 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. @cindex digital filter @cindex filter (digital) @item Lines 45--46: This FIR filter differentiates and low-pass filters the input signal. @item Lines 47--56: Here we adjust the threshold if more than two seconds have elapsed since a QRS was detected. In line 49, @code{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). @item 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 @code{maxtime} to 160 msec, and save the sign of the slope and the current time relative to the previous beat. @item 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 @code{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 @code{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. @item Lines 86--88: At the end of the loop, the samples are shifted through the @code{t@var{n}} variables and another sample is read. @end table @node Exercises, Glossary, Examples, Top @unnumbered Exercises These exercises are based on the material in the previous chapters. Answers to some of them are at the back of the book, but try to work through them first. @enumerate @item Type in the first program from the previous chapter, compile it, and run it. If you know that you will need to read WFDB files from non-standard locations, remember to set and export the environment variable @code{WFDB} (@pxref{WFDB path}). It is a good idea to include this step in your @file{.profile}, @file{.cshrc}, or @file{autoexec.bat}. As input, try record @file{100s}, input annotator @file{atr}, and output annotator @file{normal}. The program should finish in five seconds or less. The annotations will have been written into a file called @file{100s.nor} in the current directory. Now type ``@code{rdann -r 100s -a atr}'' and observe the output for a few seconds, then try ``@code{rdann -r 100s -a nor}'' and notice the difference. @item Modify the program from the previous exercise so that the non-QRS annotations are put into a second output annotation file. Remember that you will need three annotation files in all (one input and two output). @item The next five short exercises are to be worked out on paper, although you may wish to check your work on the computer. All of them assume that we are given a signal sampled at 100 Hz with the following specifications: @example fname = "signal.dat" desc = "BP" units = "mmHg" gain = 10 initval = 80 group = 0 fmt = 212 spf = 1 bsize = 0 adcres = 12 adczero = 0 baseline = -300 nsamp = 1000000 cksum = 3109 @end example For starters, convert a sample value of 280 into physical units. @item Convert 120 mmHg into adus. @item What are the maximum and minimum possible sample values in adu? in mmHg? @item How large is @file{signal.dat}, in bytes? How much space could we save if we converted it to format 8 (eight-bit first-differences)? What is the maximum slew rate (in mmHg/second) that we can represent in that format? @item Oops! We have just discovered that the maximum slew rate in our signal is 1500 mmHg/sec. Is there any way to store it at full precision in one of the supported formats, that saves space compared to its present format? @item Figure out how to plot or display the first 1000 points from signal 0 of a record in amplitude vs. time format. You may wish to begin with the example program from the first chapter. Arrange for the record name to be read from the command line (see @cite{K&R}, pp. 114--115, if you don't know how to do this). @item Try plotting VCGs by modifying the program from the previous exercise to plot pairs of samples from each of two signals rather than sample number/value pairs. @item Modify the program from the previous exercise, or Example 2 from the previous chapter, so that you can specify a segment of the record to be processed with start and end times. For example, the command @example @var{your-program record} 10:0 10:10 @end example should skip the first ten minutes, then process the next ten seconds of signals from @var{record}. @item Using @code{isigsettime} on a format 8 signal introduces a random offset into the signal, since the contents of a format 8 signal file are first differences rather than amplitudes. For an AC-coupled signal such as an ECG, this is usually inconsequential, but a DC-coupled signal such as a blood pressure signal is usually useful only if absolute levels are known. If we store such a signal in format 8, we must read it sequentially from the beginning in order to get correct sample values. If we intend to do a lot of non-sequential processing of such a signal, it may be worthwhile to build a table containing the correct sample values at periodic intervals; then we can use @code{isigsettime} to skip to a sample in the table, and read forward sequentially from that point. Write a program to build such a table, and wrappers for @code{isigsettime} and @code{getvec} to give random access to format 8 signal files without introducing offset errors. On your system, how many sample intervals should be allowed between table entries in order to obtain an @code{isigsettime} equivalent that executes in an average of 100 msec or less? @item This exercise and the next assume that you have access to the web, so that you can obtain the freely available input records needed from PhysioNet. Since the 360 Hz sampling frequency used in the MIT-BIH Arrhythmia Database is an integer multiple of the 60 Hz mains frequency, it is quite easy to design a 60 Hz notch filter that can be applied to the database to suppress power-line interference (for example, by averaging pairs of samples that are 180 degrees out of phase). Write a program that filters two input signals and writes out the filtered data using @code{putvec} (@pxref{Example 7}, for a model program). Try it out on MIT DB record @file{122} (if you have a NETFILES-enabled WFDB library, use the default WFDB path, and open record @file{mitdb/122}; otherwise, download the record from @uref{http://physio@-net.org/physio@-bank/data@-base/mitdb/}.) Use your programs from the previous exercises to display your output and compare it with the original signals. @item (Non-trivial) Write a QRS detector that is independent of sampling frequency without using @code{setifreq}. Some useful constants (for adult human ECGs): average normal QRS duration = 80 milliseconds, average QRS amplitude = 1 millivolt, average R-R interval = 1 second; assume that upper and lower limits for these quantities are within a factor of 3 of the average values. Run your detector on MIT-BIH Arrhythmia Database record @file{200}. (If you have a NETFILES-enabled WFDB library, use the default WFDB path, and open record @file{mitdb/200}; otherwise, download the record from @uref{http://physio@-net.org/physio@-bank/data@-base/mitdb/}.) Read the documentation on the annotation comparator, @file{bxb}, and figure out how to use it to compare the annotation file produced by your program against the reference annotator @file{atr}. How does your detector compare to Example 10? @end enumerate @node Glossary, Installation, Exercises, Top @appendix Glossary @table @emph @item AC-coupled signal @cindex AC-coupled signal (defined) A signal, such as an ECG, for which only variations in level, rather than absolute levels, are significant. Such signals are usually passed through high-pass filters before they are digitized, in order to remove any DC component (baseline offset), so that the gain can be chosen optimally for the range of variation in the signal. @item ADC @cindex ADC (defined) Analog-to-digital converter. @item ADC resolution @cindex ADC resolution (defined) @cindex resolution The number of significant bits per sample. Typical ADCs yield between 8 and 16 bits of resolution. @item ADC zero @cindex ADC zero (defined) The value produced by the ADC given a 0 volt input. For bipolar ADCs, this value is usually 0, but for the unipolar (offset binary) converter used for the MIT DB, the ADC zero was 1024. @item adu @cindex adu (defined) The unit of amplitude for samples. @item AHA DB @cindex AHA DB (defined) The American Heart Association Database for the Evaluation of Ventricular Arrhythmia Detectors, consisting of 80 records identified by four-digit record names. @item AHA format @cindex AHA format (defined) The format formerly used for interchange of AHA DB and MIT DB records on 9-track tape between institutions, not used for on-line files because it is relatively wasteful of storage space compared to other WFDB-compatible formats. (q.v.). @item Annotation @cindex annotation (defined) A label, associated with a particular sample, which describes a feature of the signal at that time. Most annotations are QRS annotations and indicate the QRS type (normal, PVC, SVPB, etc.). Annotations are written by @code{putann} and read by @code{getann}. @item Annotation code @cindex annotation code (defined) An integer in the range of 1 to @code{ACMAX} (defined in @file{}) inclusive, which denotes an event type. @item Annotation file @cindex annotation file (defined) A set of annotations in time order. @item Annotator name @cindex annotator name (defined) A name associated with an annotation file. The annotation file name is constructed from the record name by appending a @samp{.} and the annotator name. On CDROMs and MS-DOS file systems, the annotator name is restricted to three characters. @item Annotator [number] @cindex annotator number (defined) An integer by which an annotation file, once opened, is known. Input annotators and output annotators each have their own series of annotator numbers assigned in serial order beginning with 0. @item Application program In this guide, a program that uses the WFDB library to do something. @item @file{atr} @cindex @code{atr} (defined) The annotator name for the reference annotation files (originally, @file{atruth}, i.e., the "truth" annotations). @item Base counter value @cindex base counter value (defined) @cindex counter (base) The counter value (q.v.) that corresponds to sample 0. The base counter value is read by @code{getbasecount}, and set by @code{setbasecount} (or by any of the functions that read header files). If not defined explicitly, the base counter value is taken to be 0. @item Base time @cindex base time (defined) The time of day that corresponds to sample 0 in a given record. For MIT, AHA, and ESC DB records, the base time was not recorded and is taken to be 0:0:0 (midnight). @item Baseline [amplitude] @cindex baseline amplitude (defined) The sample value that corresponds to the baseline (isoelectric level or physical zero level) in the signal. This quantity may drift during the record for a variety of reasons, in which case the @code{baseline} field of the @code{WFDB_Siginfo} object that describes the signal is only an approximation. The baseline is @emph{not} the same as the ADC zero (q.v.), which is a fixed characteristic of the digitizer. @item Calibration file @cindex calibration file (defined) A file containing data used to build a calibration list (q.v.). @item Calibration list @cindex calibration list (defined) A memory-resident linked list of @code{WFDB_Calinfo} objects @ifnotinfo (@pxref{WFDB_Calinfo structures, , Calibration Information Structures}). @end ifnotinfo @ifinfo (@pxref{WFDB_Calinfo structures}). @end ifinfo Each such structure specifies the size and type of the calibration pulse, and the customary plotting scale, for a particular type of signal. @item CDROM @cindex CDROM (defined) A read-only medium used for distribution of a number of databases readable using the WFDB library, including the ESC and Long-Term ST databases, among others. CDROMs are physically identical in appearance to audio compact disks. @item Closing [a record] The process of completing I/O associated with a record. @item Counter frequency @cindex counter frequency (defined) @cindex frequency (counter) The difference between counter values (q.v.) that are separated by an interval of one second. The counter frequency is constant throughout any given record. It may be undefined, in which case it is treated as equivalent to the sampling frequency (q.v.) by the WFDB library. The counter frequency is read by @code{getcfreq}, and set by @code{setcfreq} (or by any of the functions that read header files). @item Counter value @cindex counter value (defined) @cindex tape counter A number that serves as a time reference, in a record for which a counter frequency is defined. A counter value may be converted to the time in seconds from the beginning of the record by subtracting the base counter value (q.v.) and dividing the remainder by the counter frequency. The units of @samp{c}-prefixed @code{strtim} arguments are counter values. @item Database files Those files (annotation files, header files, signal files, and calibration files) that are accessed via the WFDB library. @item Database path @cindex WFDB path @cindex database path (defined) The names of the directories in which header, annotation, and calibration files are kept. (Signal files may be located in these directories or elsewhere; header files specify their locations.) To modify the database path, the environment variable @code{WFDB} must be set by the user and exported accordingly. @item DC-coupled signal @cindex DC-coupled signal (defined) A signal, such as a blood pressure signal, for which absolute levels are significant. Such signals must be digitized without being passed through high-pass filters, in order to preserve absolute levels. @item ESC DB @cindex ESC DB (defined) The European ST-T Database, consisting of 90 records identified by @samp{e}-prefixed four-digit record names. @item Frame @cindex frame (defined) A set of samples, containing all samples that occur within a given frame interval. For an ordinary record, a frame contains exactly one sample of each signal; for a multi-frequency record, a frame contains @emph{at least} one sample of each signal, and more than one sample of each oversampled signal (q.v.). @item Frame interval @cindex frame interval (defined) A time interval during which at least one sample exists for each signal. For an ordinary record, the frame interval and the sampling interval are identical. For a multi-frequency record, the frame interval is chosen to be an integer multiple of each sampling frequency used. @item Frame rate @cindex frame rate (defined) The basic sampling frequency defined for a multi-frequency record; the reciprocal of the frame interval. The frame rate is usually the lowest sampling frequency used for any signal included in the record. @item Gain @cindex gain (defined) In this context, the number of adus (q.v.) per physical unit, referred to the original analog signal. Gain in this sense is directly proportional to the degree of amplification (the usual meaning of the word) of the analog signal prior to digitization. Gain may vary between signals in a record. @item @file{hea} @cindex @code{hea} (defined) The suffix (extension) that designates a header file (originally @file{header}). @item header file @cindex header file (defined) A file accessible via the WFDB library that describes the signal files associated with a given database record. A header file has a name of the form `@var{record}@code{.hea}', where @var{record} is the record name (q.v.). @item High-resolution mode @cindex high-resolution mode (defined) An alternative mode for reading a multi-frequency record using @code{getvec}, that can be selected using @code{setgvmode}. In high-resolution mode, @code{getvec} replicates samples of signals digitized at less than the maximum sampling frequency, so that each sample of any oversampled signals appear in at least one sample vector. @item Info string @cindex info string (defined) Free text within a header file. Info strings can be read using @code{getinfo} and written using @code{putinfo}. @item Local record @cindex local record (defined) A record for which the signal files reside in the current directory, typically used for user-created signals. Records @file{8l} and @file{16l} are local records. @item Location [of an annotation] @cindex location (of annotations) @cindex annotation location (defined) Every annotation has @code{time}, @code{num}, and @code{chan} attributes that define its location within a virtual array of annotations. See @dfn{Canonical order of annotations}. @item Canonical order of annotations @cindex annotation (canonical order) @cindex canonical order of annotations Normally, annotations are arranged in time order within an annotation file. Annotations that have identical @code{time} attributes are arranged in @code{num} and @code{chan} order. Annotations that have identical @dfn{locations} (i.e., identical @code{time}, @code{num}, and @code{chan} attributes) should not normally occur in a single annotation file; if this does happen, the last annotation at any given location is treated as a replacement of any previous annotations at that location. @item Low-resolution mode @cindex low-resolution mode (defined) The default mode for reading a multi-frequency record using @code{getvec}. In low-resolution mode, @code{getvec} returns one sample per signal per frame, by decimating any oversampled signals to the frame rate. @item MIT DB @cindex MIT DB (defined) The Massachusetts Institute of Technology--Beth Israel Hospital Arrhythmia Database, consisting of 48 records identified by three-digit record names. @item MIT format @cindex MIT format (defined) One of a set of WFDB-compatible formats for header, signal, and annotation files that were first used for the MIT-BIH Arrhythmia Database. The term WFDB-compatible format should be used unless referring specifically to the formats used for the MIT DB (single-segment header format, signal format 212, and bit-packed annotation format). @item Modification label @cindex modification label (defined) An ``invisible'' annotation at the beginning of an annotation file. A modification label defines an annotation mnemonic and a corresponding description. When @code{annopen} (or @code{wfdbinit}) opens an annotation file that contains modification labels, it automatically calls @code{setannstr} and @code{setanndesc} to add the mnemonics and descriptions to the translation tables used by @code{annstr}, @code{strann}, and @code{anndesc}. When @code{annopen} (or @code{wfdbinit}) creates an annotation file, it automatically generates modification labels, for each annotation code that has been (re)defined using @code{setannstr} or @code{setanndesc}. For this reason, you should normally make all of your calls to @code{setannstr} and @code{setanndesc} @emph{before} calling @code{annopen} or @code{wfdbinit}. (An exception is if you are simply @emph{translating} mnemonics and descriptions into another language, rather than @emph{redefining} them.) Version 5.3 and later versions of the WFDB library support reading and writing modification labels; earlier versions read modification labels as @code{NOTE} annotations. @item Multi-frequency record @cindex multi-frequency record (defined) A record containing signals sampled at two or more sampling frequencies. Version 9.0 and later versions of the WFDB library support reading and writing multi-frequency records. @item Multi-segment record @cindex multi-segment record (defined) A composite record that is the concatenation of two or more ordinary (single-segment) records. Multi-segment records do not have their own signal files (the signal files of their constituent segments are read when it is necessary to read signals of multi-segment records), but they have their own header files (created using @code{setmsheader}), and may have their own annotation files as well (annotation files for the constituent segments of a multi-segment record are @emph{not} concatenated automatically when the record is read). The WFDB Software Package includes @code{wfdbcollate}, an application that can create multi-segment records from sets of single-segment records. Version 9.1 and later versions of the WFDB library support reading and writing multi-segment records. @item Multiplexed signal file @cindex multiplexed signal file (defined) A set of vectors in time order, each consisting of two or more integer samples, thus representing an equal number of signals. @item NETFILES @cindex NETFILES (defined) @cindex curl @cindex libcurl WFDB files made available by an FTP or HTTP (web) server; readable by applications linked with a NETFILES-enabled WFDB library. A NETFILES-enabled WFDB library can be created by compiling the WFDB library sources with the symbol @code{WFDB_NETFILES} defined (to anything; its value is not important, only that it is defined) and then linking them with the @code{libcurl} library available from @uref{http://curl.haxx.se/}. @item 9-track tape @cindex nine-track tape (defined) A medium used for archival storage of WFDB records, which was once nearly universally available on minicomputers and larger systems. The important parameters are tape density (typically 800 or 1600 bpi) and block size (typically some multiple of 512 bytes). Higher tape density and larger block size permit more data to be stored on a tape. @item Opening [a database record or a file] The process of making a database record or a file accessible, if necessary by creating it. @item Oversampled signal @cindex oversampled signal (defined) In a multi-frequency record, any signal recorded at a sampling frequency greater than the frame rate (q.v.). @item Physical unit @cindex physical unit (defined) The natural unit of measurement of the original analog signal (e.g., millivolts, liters per second, degrees). To convert samples into physical units, subtract the ADC zero and divide the remainder by the gain. @item Physical zero @cindex physical zero (defined) The level (in physical units) that corresponds to the baseline (in adu), normally zero physical units. For example, physical zero for a pressure signal with units of mmHg is 0 mmHg. @item PhysioNet @cindex PhysioNet The home of the WFDB library, and a source for recorded physiologic signals and software for use with them. All materials on PhysioNet are freely available. The main PhysioNet server is @uref{http://@-physio@-net.@-org/}, located at MIT in Cambridge, Massachusetts; PhysioNet mirror sites are located around the world (see @uref{http://@-physio@-net.@-org/@-mirrors/} for a list). @item Piped record @cindex piped record (defined) A database record for which a signal file is designated as @file{-}, signifying that it is to be read from the standard input or written to the standard output. Records @file{8} and @file{16} are piped records, as are those defined within the @file{pipe} subdirectory of the system-wide database directory (q.v.) @item Prolog @cindex prolog (defined) @cindex byte offset @cindex cruft (in signal files) @cindex prolog (in signal files) @cindex start of sample data Extraneous bytes at the beginning of a signal file that are not to be read as samples. Signal files created using the WFDB library do not contain prologs, but signal files created using other means may contain prologs. To read such a signal file using the WFDB library, provided that the sample data are in a supported format, it is sufficient to record the length of the prolog (in bytes) in the appropriate locations in a header file that names the signal file. If you need to create such a header file, refer to the description of the byte offset field in @cite{header(5)} (the specification of the header file format in the @cite{WFDB Applications Guide}) or @pxref{wfdbsetstart}. @item Record @cindex record (defined) An extensible set of files that may include signal files, annotation files, and a header file, all of which are associated with the same original signals. Only the header file is mandatory. Although records are sometimes called tapes for historical reasons, records are now more commonly maintained on CDROMs or magnetic disks than on tape. @item Record name @cindex record name (defined) A character string that identifies a database record. Record names of MIT DB records are 3-digit numerals, those of AHA DB records are 4-digit numerals, and those of ESC DB records are 4-digit numerals with a prefixed @samp{e}. Record names may contain up to @code{WFDB_MAXRNL} (defined in @file{}) characters, including any combination of letters, digits, and underscores. Case (the difference between @samp{e} and @samp{E}, for example) is significant in record names, even under operating systems such as MS-DOS that do not treat case as significant in file names. @item Reference annotation file @cindex reference annotation file (defined) An annotation file supplied by the creator of a record to document its contents as accurately and thoroughly as possible. The annotator name @file{atr} is reserved for reference annotation files. @item Sample @cindex sample (defined) An integer (of at least 16 bits) that corresponds to a voltage measured at a given instant by an analog-to-digital converter. Samples are written by @code{putvec} and read by @code{getvec}. @item Sample interval @cindex sample interval (defined) The unit of time; the interval between consecutive samples of a given signal. @item Sample number @cindex sample number (defined) An attribute of a sample defined as the number of samples of the same signal that precede it; thus the first sample of any signal has sample number 0. Sample numbers are long integers (32 bits). Samples that have the same sample number in different signals of a given record may be treated as having been observed simultaneously. @item Sampling frequency @cindex sampling frequency (defined) The number of samples of a given signal that represent one second of the original analog signal. The sampling frequency is constant throughout a signal file, and is the same for all signals in a given record. @item Signal @cindex signal (defined) A continuously varying function of time that is approximated by discrete samples. @item Signal file @cindex signal file (defined) A set of samples in time order, which represent a signal or signal group. Signal files usually have names of the form @var{record}@code{.dat}, but this is only a convention and is not required. @item Signal group @cindex signal group (defined) A set of signals that are multiplexed together and stored in the same file. It is possible to reset input pointers for all signals in a given signal group @ifnotinfo (@pxref{isgsettime, , @code{isgsettime}}), @end ifnotinfo @ifinfo (@pxref{isgsettime}), @end ifinfo but not independently for individual signals within a signal group. @item Signal group number @cindex signal group number (defined) A number by which a signal file, once opened, is known. @item Signal number @cindex signal number (defined) An integer by which a signal, once opened, is known. Input and output signals each have their own series of signal numbers assigned in serial order beginning with 0. @item Skew @cindex skew (defined) @cindex intersignal skew The time difference between samples having the same sample number but belonging to different signals. Ideally the skew is zero (or less than one sample interval), but in some cases this is not so. For example, if the signals were originally recorded on multitrack analog tape, very small differences in the azimuth of the recording and playback heads may result in measurable skew among signals. If the skew can be measured (for example, by reference to features of two signals with a known time difference), it can be recorded in the header file for a record; once this has been done, @code{getvec} and @code{getframe} correct for skew automatically. If you need to correct for skew, see @cite{skewedit(1)} and @cite{header(5)} (in the @cite{WFDB Applications Guide}), or @pxref{wfdbsetskew}. Prospectively, if you anticipate that skew may be a problem, it is a good idea to apply an easily identifiable synchronization pulse to all your inputs simultaneously while recording; you can then locate this pulse in each digitized signal and use these measurements to correct for skew. @item Standard time format @cindex standard time format (defined) Any string format legal as an argument for @code{strtim} @ifnotinfo (@pxref{timstr and strtim, , @code{timstr} and @code{strtim}}). @end ifnotinfo @ifinfo (@pxref{timstr and strtim}). @end ifinfo @item System-wide database directory @cindex system-wide database directory (defined) @cindex WFDB path @cindex database path The directory that contains local copies of the default WFDB calibration file, WFDB sample record @code{100s}, and local, piped, and tape header files. This directory is created when the WFDB Software Package is installed, and by default it is included in the WFDB path (as the second component, following the user's current directory). It is called the ``system-wide'' database directory because it is shared by all users of the system on which it resides. Under Unix, the system-wide database directory is usually @file{/usr/database} or @file{/usr/local/database}; under MS-DOS or MS-Windows, it is usually @file{c:\database}. @item Tape @cindex tape (defined) A database record. @item Time @cindex time (defined) In this guide, synonymous with sample number (q.v.). Thus the ``time of an annotation'' is the sample number of the sample to which the annotation ``points''. @item WFDB-compatible format @cindex WFDB-compatible format (defined) Any of the standard formats readable and writable by the WFDB library, for storage of WFDB records in PhysioBank and on CDROMs. @item WFDB library A set of functions (subroutines), able to read and write database files, callable by C and C++ programs, and described in this guide. @item WFDB path The database path (q.v.). @end table @node Installation, WFDB Applications, Glossary, Top @appendix Installing the WFDB Software Package This appendix briefly describes how to install the WFDB Software Package on a new system. The package includes C-language sources for the WFDB library and for a variety of applications (@pxref{WFDB Applications}) including @code{WAVE}, sources for this manual, the @cite{WFDB Applications Guide}, and the @cite{WAVE User's Guide}, and a one-minute sample record (@samp{100s}). The latest version of the WFDB Software Package can always be downloaded in source form from @uref{http://physio@-net.org/physio@-tools/@-wfdb.shtml}, the WFDB home page on PhysioNet. Binaries for popular operating systems and development snapshots are also usually available there. The process for installing the package is the same on all platforms, and is documented in detail in the quick-start guides for the popular platforms that can be found on the WFDB home page. In brief: @cindex curl @cindex libcurl @enumerate @item @emph{Install any prerequisites needed for your platform.} These include @code{gcc} (the GNU Compiler Collection), related software development tools such as @code{make}, the @code{libcurl} library (if NETFILES support is desired), the XView libraries (needed for WAVE only), and X11 (needed by XView). All of these components are free (open-source) software available for all popular platforms, including GNU/Linux, Mac OS X, MS Windows, and Unix. The quick start guides list recommended packages and where to find them. @item @emph{Download and unpack the WFDB Software Package.} Versions for all platforms are built from a single package of portable sources; the most recent package is always available at @uref{http://physio@-net.org/physio@-tools/wfdb.tar.gz}. @item @emph{Configure the package for your system.} The @code{configure} script creates a customized building procedure for your system and allows you a few choices about where to install the package. @item @emph{Make and verify a test build.} The package includes a set of test scripts that are run to verify basic operations of the WFDB library and many of the applications, permitting them to be tested before installation. @item @emph{Make, install, and test a final build.} @end enumerate See the quick start guide for your platform for detailed step-by-step instructions. @emph{Important:} Although you may be able to compile the WFDB Software Package using a proprietary compiler, this is @emph{not supported}. @unnumberedsec The WFDB library and languages other than C If you wish to use or develop WFDB applications in C, C++ or Fortran, everything necessary is included in the WFDB Software Package. The separate @code{wfdb-swig} package provides wrappers for the WFDB library so that it can be used by applications written in a variety of other languages supported by SWIG, the Simplified Wrapper Interface Generator. At the time of writing, the languages that are known to work with @code{wfdb-swig} wrappers are Perl, Python,, Java, and C#. Other languages supported by SWIG include Guile, mzScheme, PHP, Ruby, and Tcl. You will need to install @code{wfdb-swig} wrappers for your platform and language after installing the WFDB Software Package. Download the @code{wfdb-swig} package from @uref{http://physio@-net.org/physio@-tools/wfdb-swig.shtml}, the @code{wfdb-swig} home page on PhysioNet, and build and install the wrappers you need following the instructions on that page. The WFDB Toolbox for Matlab provides a set of WFDB applications for Matlab, based on the SWIG Java wrappers; install it with one click from the link you will find on its home page, @uref{http://@-physionet.org/@-physiotools/@-matlab/@-wfdb-swig-matlab/}. @node WFDB Applications, Extensions, Installation, Top @appendix WFDB Application Programs This appendix briefly describes the application programs that are included with the WFDB Software Package. Except where noted otherwise, these applications are usable on all systems for which the WFDB library is available. For details on using these programs, refer to the @cite{WFDB Applications Guide}. (On Unix systems, the contents of the @cite{Applications Guide} may also be available as on-line @code{man} pages.) @menu * Using:: Notes on using these programs. * Annotation I/O:: Programs that read, write, summarize, and otherwise process annotation files. * Evaluation:: Programs for evaluating the performance of ECG analysis programs. * Signal processing:: Programs that read, excerpt, reformat, resample, filter, combine, analyze, acquire, and replay signals. * Graphics:: Programs for viewing or plotting signals and annotations. @end menu @node Using, Annotation I/O, WFDB Applications, WFDB Applications @unnumberedsec How to use these programs These programs are kept in directories that vary from system to system; they may not be in the default search path. If you cannot find them, consult an expert (such as the person who installed the WFDB library on your system). If you use these programs often, you may wish to include the directory in which they are kept in your search path. @cindex WFDB path To use any of these programs, you will need to set the database path first (@pxref{WFDB path}), unless the default database path (@samp{. /usr/database http://physio@-net.org/physio@-bank/data@-base}) is suitable. Programs that accept @emph{time} arguments or commands (usually shown as @var{from} and @var{to} below) use @code{strtim} to convert these strings into sample intervals; hence they accept any of the varieties of standard time format described earlier @ifnotinfo (@pxref{timstr and strtim, , @code{timstr} and @code{strtim}}). @end ifnotinfo @ifinfo (@pxref{timstr and strtim}). @end ifinfo Programs that accept annotation mnemonics as arguments or commands (usually shown as @var{code} below) use @code{strann} to interpret them; for a list of legal mnemonics, @pxref{Annotation Codes}. Where record or annotator names are required as command arguments, they are indicated below as @var{record} or @var{annotator}. In the remainder of this appendix, you will find usage examples and capsule descriptions of the standard WFDB application programs. The square brackets (`@var{[ ]}') in some of the usage examples surround arguments that may be omitted; the brackets themselves are not to be included in the command line. Where an ellipsis (`@var{...}') appears, it indicates that the previous argument may be repeated. If invoked without any arguments, or with a @samp{-h} (help) option, most of these programs print a brief synopsis of how they are used. @node Annotation I/O, Evaluation, Using, WFDB Applications @unnumberedsec Annotation File Processing @example ann2rr -a @var{annotator} -r @var{record [ options ... ]} rr2ann -a @var{annotator} -r @var{record [ options ... ]} rdann -a @var{annotator} -r @var{record [} -f @var{from} -t @var{to} -p @var{type ... ]} wrann -a @var{annotator} -r @var{record} sumann -a @var{annotator} -r @var{record} tach -a @var{annotator} -r @var{record [ options ... ]} @end example Programs @file{ann2rr} and @file{rr2ann} respectively list RR (inter-beat) intervals in text format from an annotation file, and create an annotation file from a text-format list of RR intervals. The program @file{rdann} is an annotation printer similar to the one shown in chapter 6 (@pxref{Example 3}). The optional @var{from} and @var{to} arguments (in standard time format) specify a portion of the annotation file to be printed, and one or more @var{type} arguments (annotation mnemonics) can be given to restrict the output to annotations that are of the specified type(s). The output of @file{rdann} can be converted back into an annotation file by providing it as the standard input of @file{wrann}. This can be useful for editing annotation files in some cases; they can be converted to ASCII format by @file{rdann}, edited using any text editor, and converted back into annotation files by @file{wrann}. A summary of the contents of an annotation file can be obtained using @file{sumann}. The summary includes the number of annotations of each type, and the duration and number of episodes of each rhythm and signal quality. @file{tach} generates a uniformly sampled, smoothed, instantaneous heart rate sequence from an annotation file. @node Evaluation, Signal processing, Annotation I/O, WFDB Applications @unnumberedsec Evaluation of ECG Analyzers @cindex annotation comparator @cindex comparator (annotation) @example bxb -r @var{record} -a @var{reference-annotator test-annotator [ options ... ]} rxr -r @var{record} -a @var{reference-annotator test-annotator [ options ... ]} mxm -r @var{record} -a @var{reference-annotator test-annotator [ options ... ]} epic -r @var{record} -a @var{reference-annotator test-annotator [ options ... ]} sumstats @var{file} plotstm @var{file} ecgeval nst @var{[ options ... ]} @end example The motivation for developing the MIT and AHA databases was to provide material for evaluating the accuracy of arrhythmia detectors, particularly with respect to ventricular arrhythmias. Between 1984 and 1987, the Association for the Advancement of Medical Instrumentation (AAMI) sponsored the development of a recommended practice (designated ECAR) for using the databases for this purpose. The aim of ECAR was to specify the evaluation methodology in sufficient detail to permit reproducible testing, and to encourage informed comparisons of the performance of ventricular arrhythmia detectors in the analysis of these standard test recordings. More recently, the AAMI has developed, and ANSI has adopted as American National Standards, a standard (ANSI/AAMI EC38:1998) for ambulatory electrocardiographs, and a companion standard (ANSI/AAMI EC57:1998) for testing and reporting performance results of cardiac rhythm and ST segment measurement algorithms. EC38 and EC57 specify standard protocols for evaluating the automated analysis algorithms that are included in many such devices. These protocols include those developed for the earlier recommended practice, and extend them to evaluation of supraventricular arrhythmia and ischemia detection. EC38 and EC57 specify the use of @file{bxb}, @file{rxr}, @file{mxm}, and @file{epic} to perform evaluations, and further specifies the use of the MIT DB (as well as two other databases formerly available on the MIT-BIH Arrhythmia Database CDROM and now freely available on PhysioNet), the AHA DB, and (for devices that perform analysis of the ST segment) the ESC DB. If you are interested in this subject, obtain copies of the American National Standards for @cite{Ambulatory Electrocardiographs} (ANSI/AAMI EC38:1998) and for @cite{Testing and Reporting Performance Results of Cardiac Rhythm and ST Segment Measurements Algorithms} (ANSI/AAMI EC57:1998; @pxref{Sources}). @cindex QRS detector @cindex detector (QRS) To evaluate an arrhythmia detector using this software, obtain for each WFDB record to be used in the test an annotation file containing the detector's analysis of each beat. These are referred to as the `test' annotation files (or the `algorithm' annotation files, in EC38 and EC57). The placement of the beat annotations must match those in the reference annotations within 150 msec; thus it is not necessary to place annotations precisely at the PQ junction (as in the AHA DB reference annotations) or on the major local extremum (as in the MIT DB reference annotations). If the detector is capable of shut-down (i.e., if it inhibits its QRS detection function during periods that it judges are unreadable), the test annotation files should include a @code{NOISE} annotation with @code{subtyp = -1} at the beginning of each period of shut-down, and a @code{NOISE} annotation with any other @code{subtyp} at the end of each such period. (If the record ends while the detector is shut down, the annotation file should include a final `end of shut-down' annotation as above to permit correct shut-down accounting.) If the detector is capable of ventricular fibrillation detection, the test annotation files should also include @code{VFON} and @code{VFOFF} annotations; it is not necessary to mark flutter waves (use @code{FLWAV} annotations to do so if desired). See the @code{man} page for @file{epic}, in the @cite{WFDB Applications Guide}, for information on marking atrial fibrillation, ischemic ST episodes, and ST deviation measurements in test annotation files. Any annotations that appear in the first five minutes of an annotation file are treated as belonging to the detector's learning period, and are not used in the evaluation. The evaluation software examines such annotations only to determine the detector's state (normal, shut down, or in VF) at the beginning of the test period. @cindex annotation comparator @cindex comparator (annotation) Program @file{bxb} implements the beat-by-beat comparison algorithm described in EC38 (section 4.2.14.2.2) and EC57 (section 4.3.2). By default, the output is in a self-explanatory matrix format. The @samp{-L} option, which must be followed by two file names, specifies that the output of @file{bxb} should be written in line format, for further processing by @file{sumstats}. The line-format output includes column headings only if the output file must be created from scratch. In this way, @file{bxb} can be used repeatedly to build up a line-format tables for multiple records. Among the other options is @samp{-o}, which causes @file{bxb} to generate an output annotation file (with annotator name @file{bxb}) indicating agreements and discrepancies between the input annotators. @file{rxr} can be used to performed the run-by-run comparison described in EC38 (section 5.2.14) and in EC57 (sections 4.4.3 and 4.4.4). @file{mxm} compares heart rate, HRV, or other measurements, as described in EC38 (section 4.2.14.2.3). @file{epic} evaluates VF and AF detection, and ST analysis, as described in EC38 (sections 5.2.14), and EC57 (sections 4.5 and 4.6). These programs also accept a @samp{-L} option to produce line-format output as for @file{bxb}. @file{sumstats} derives the record-by-record, episode-by-episode, and aggregate performance statistics described in EC38 (section 4.2.14.3) and in EC57 (sections 3.5.2 and 3.5.3) from line-format output files produced by @file{bxb}, @file{rxr}, @file{mxm}, and @file{epic}. The input file must include the column headings so that @file{sumstats} can recognize the file type. The output includes a copy of the input, with aggregate statistics appended at the end. @file{plotstm} generates a PostScript scatter plot of ST measurement comparisons gathered by @file{epic}, as described in EC57 (section 4.6.2). The easiest way to use these programs is to run @file{ecgeval}, which generates a script (batch) file to run @file{bxb}, @file{rxr}, etc., for each record in a database. See @cite{Evaluating ECG Analyzers} (in the @cite{WFDB Applications Guide}) for details. @cindex noise stress test By adding noise to annotated ECG records, the noise tolerance of an arrhythmia detector can be measured. This idea was described by the author, along with W.K. Muldrow and R.G. Mark, in ``A noise stress test for arrhythmia detectors'', @cite{Computers in Cardiology} @strong{11}:381-384 (1984). Program @file{nst} adds calibrated amounts of noise to ECGs (or other signals), generating an output record in WFDB format. @file{nst} was used to generate the graded series of noisy ECG records in the MIT-BIH Noise Stress Test Database (see @uref{http://physio@-net.org/physio@-bank/data@-base/nstdb/}). These records are among those specified as standard test material by EC38 (section 4.2.14.2) and EC57 (section 3.2). @node Signal processing, Graphics, Evaluation, WFDB Applications @unnumberedsec Signal Processing Applications @example rdsamp -r @var{record [ options ... ]} wrsamp -r @var{record [ options ... ]} snip -i @var{input-record} -n @var{new-record [ options ... ]} xform -i @var{input-record [ options ... ]} fir @var{[ options ... ]} -c @var{coefficient ...} sigamp -r @var{record [ options ...]} sqrs -r @var{record [ options ... ]} sample @var{[ options ... ]} calsig -r @var{record [ options ... ]} @end example @file{rdsamp} prints samples from the specified record; @samp{-f} and @samp{-t} options may be used to specify a range of sample numbers, and a subset of signal numbers may be selected using the @samp{-s} option. The output of @file{rdsamp}, or any similar text, can be converted into a WFDB record using @file{wrsamp}. To copy an excerpt of a longer record, use @file{snip}, which creates new header and signal files for @var{new-record} in the current directory. The beginning and end of the excerpt are specified using @samp{-f} and @samp{-t} options as for @file{rdsamp}. Annotator names may follow a @samp{-a} option; in this case excerpts from the specified annotation files are copied as well (the annotations are appropriately time-shifted). @file{xform} is a more general version of @file{snip}; its main uses are for reformatting, rescaling, and sampling rate conversion. You may create a @file{hea} file specifying the desired format, sampling frequency, ADC zero levels, signal gains, etc., and supply it to @file{xform} using the @samp{-o} option; if you do not do so, @file{xform} obtains the required information interactively. @file{xform} accepts all of the options used by @file{snip}, as well as several others. @cindex digital filter @cindex filter (digital) Program @file{fir} is a general-purpose FIR filter for WFDB records, similar to the one discussed in chapter 6 (@pxref{Example 7}). @file{sigamp} measures signal amplitudes (either baseline-corrected RMS amplitudes or peak-to-peak amplitudes); it may be useful for calibrating signals (together with @file{calsig}) or for determining signal gains for @file{nst}. @cindex QRS detector @cindex detector (QRS) @file{sqrs} is a slightly modified version of the QRS detector discussed in chapter 6 (@pxref{Example 10}). Options allow specification of the signal and interval to be analyzed and the detection threshold. Program @file{sample} is an MS-DOS application that uses a Microstar Laboratories DAP 1200- or 2400-series ISA (AT bus) analog interface board (@pxref{Sources}) to generate database records from analog signals, or to generate analog signals from database records. If you wish to use other hardware for these purposes, refer to chapter 6 (@pxref{Example 8}) and to the source for @file{sample} as models. If you create your own database records using @file{sample} or other means, program @file{calsig} may be useful for determining signal gains and offsets if your signals include standard calibration pulses or identifiable signal levels. @file{calsig} incorporates two independent algorithms for measuring calibration pulses; it rewrites header files based on its measurements. @node Graphics, , Signal processing, WFDB Applications @unnumberedsec Graphical Applications @example wave -r @var{record [ }-a @var{annotator ]} pschart @var{[ [ options ... ] script ... ]} psfd @var{[ [ options ... ] script ... ]} @end example @cindex ECG waveform editor @cindex waveform editor @file{wave} is an X Window System client application for viewing and editing WFDB records. (@file{wave} is included in the WFDB software package.) @file{wave} can be run on all popular platforms, including FreeBSD, GNU/Linux, Mac OS X, MS Windows, Solaris, and other systems for which X11 servers are available. Run @file{wave} without any arguments to obtain instructions for printing its on-line manual. @file{pschart} and @file{psfd} produce annotated ``chart recordings'' and ``full-disclosure'' plots that can be printed on PostScript devices. These programs were used to prepare the @cite{MIT-BIH Arrhythmia Database Directory} and the @cite{European ST-T Database Directory}. The popular Chart-O-Matic web service (@uref{http://physionet.org/cgi-bin/chart}) creates browser-viewable plots using @file{pschart}. @node Extensions, Sources, WFDB Applications, Top @appendix Extensions This section may be helpful if you wish to extend the capabilities of the WFDB library, or if you wish to port it to another environment. In order to make use of the information in this section, you should have the WFDB library sources (@pxref{Sources}). The sources are distributed among four `include' (@file{.h}) files and five @file{.c} files: @display @t{wfdb.h } Constant and structure definitions, and function prototypes @t{ecgcodes.h } Annotation codes @t{ecgmap.h } Annotation code mapping macros @t{wfdblib.h } External definitions for private WFDB library functions @t{wfdbinit.c } Functions @code{wfdbinit}, @code{wfdbquit}, and @code{wfdbflush} @t{signal.c } Functions for signals @t{calib.c } Functions for signal calibration @t{annot.c } Functions for annotations @t{wfdbio.c } Low-level I/O and operating system-dependent functions @end display The first three of these files are the standard `include' files that are usually obtained by @samp{#include } statements. When modifying the WFDB library, however, make any necessary changes in the copies of these files that are kept in the library source directory. Install the modified versions of the @file{.h} files in the system's @file{include} directory after installing the modified WFDB library. The cleanest mechanism for adding additional fields to @file{hea} files is to include them in `info' strings @ifnotinfo (@pxref{getinfo, , @code{getinfo}}), @end ifnotinfo @ifinfo (@pxref{getinfo}), @end ifinfo rather than by modifying the code that reads and writes @file{hea} files (in @file{signal.c}). A common problem is the need to import signal files generated by other software. Often this problem can be solved by writing a format conversion program that uses input functions provided with the other software to read the signal files, and @code{putvec} to write them in one of the formats supported by the WFDB library. This solution is unlikely to be satisfactory if you have many large signal files to import, however, and you may wish to arrange for @code{getvec} to read the imported files directly. This may be done by defining a new signal file format, as outlined below. To define a new format for signal files, choose a numeric code to represent your format. (Values between 900 and 999 are reserved for user-defined signal file format codes.) In @file{wfdb.h}, add your format code to @code{FMT_LIST} and increment @code{NFMTS}. In @file{signal.c}, define functions (macros if possible for efficiency) for reading and writing single samples; these should be named @code{r@var{nnn}} and @code{w@var{nnn}}, where @var{nnn} is your format code. Follow the examples in @file{signal.c}; it will almost certainly be easier to make use of the existing macros @code{r8} and @code{w8} than to begin from scratch. Add additional @code{case} statements in @code{getvec} and @code{putvec}, again following the existing models. You will also need to add a @code{case} in @code{isgsettime}, including a formula to determine the number of bytes needed per sample, given the number of signals multiplexed. (All currently-defined formats use fixed-length encoding. If you wish to implement variable-length encoding, it may be easiest to implement an indexed-search method for @code{isgsettime} in such cases.) If the ADC resolution exceeds the number of bits in a C @code{int} on your system, change the @code{typedef} for @samp{WFDB_Sample} in @file{} as necessary; be aware that this change is likely to require additional changes to application programs (use @file{lint} or an ANSI C compiler to check your code). Although the WFDB library generally assumes that signal files are ``pure'', it is possible to read imported signal files that contain prologs (data that precede the first sample). To do so, you must construct a header file in which the @code{format} fields encode the length of the prolog in bytes (you can do this manually, or use @code{wfdbsetstart}, @pxref{wfdbsetstart}, for this purpose). For example, a signal file with a 512-byte prolog followed by format 16 samples would be specified using @samp{16+512} in the @code{format} field or fields (if the file contains more than one signal, the @code{format} fields for all signals in the file must be identical). Note that this facility is provided only for signal file import; the WFDB library is not equipped to create signal files with embedded prologs. In a similar fashion, though with substantially more effort in most cases, you may define a new format for annotation files. Add additional @code{stat} values for reading and writing to the list in @file{wfdb.h}. In @file{annot.c}, add additional @code{case} statements and code to @code{annopen}, @code{getann}, @code{putann}, and @code{wfdb_anclose}. If you are designing a new format, you may wish to specify a `magic number' with which your files will begin, to allow @code{annopen} to recognize the format automatically; a good choice of such a number is one in which the first byte is non-zero (to distinguish it from AHA format files) and the high six bits of the second byte are zero (to distinguish it from WFDB format files). Some users may wish to define additional annotation codes. An easy and portable way to accomplish this is to use @code{setannstr} and @code{setanndesc} within programs that create your annotation files, before opening them using @code{annopen} (or @code{wfdbinit}). Annotation files created in this way contain modification labels at the beginning that document the non-standard code definitions, and that permit them to be read properly by standard WFDB applications. Another solution is to modify the WFDB library. This method has the disadvantage that all of your applications that read annotation files must be recompiled, and they may no longer read standard annotation files properly. If despite this disadvantage you prefer to modify the WFDB library, begin by defining symbolic names and numeric values for your new codes in @file{ecgcodes.h}. (Values between 42 and 49 are reserved for user-defined annotation codes. Unused values less than 42 may be assigned in future versions of the WFDB library, and values greater than 49 are reserved to indicate the presence of optional fields such as @code{subtyp}.) Next, decide how the new codes are to be mapped by @code{isqrs}, @code{map1}, @code{map2}, @code{mamap}, and @code{annpos}, and set the appropriate entries in each of the code map arrays in @file{ecgmap.h}. Finally, add mnemonic and descriptive strings for the new codes in the @code{cstring}, @code{astring}, and @code{tstring} arrays in @file{annot.c}. The modular design of the library makes it fairly easy to remove unneeded functionality in order to conserve memory for special applications. The @file{calib.c} package is not referenced by any other WFDB library modules. For signal processing applications that do not involve annotations, the entire @file{annot.c} package may be removed (with trivial modifications to the functions in @file{wfdbinit.c}). If you wish to add functions to the library, you will find that it will be easier to maintain your modified version and to merge updates if you preserve the existing arrangement of functions, which requires no global variables. Rather than defining global variables, consider implementing query functions (global-scope functions that read or write local variables). If you wish to define new types of binary files, consider using the low-level I/O routines in @file{wfdbio.c} for reading and writing them in a machine-independent format. @cindex WFDB path Porting the WFDB library to another environment is a straightforward operation if an ANSI C compiler is available in the target environment. Since all direct access to database files is performed using the (private) function @code{wfdb_open}, it is possible to include file name translation in that function if needed, to accommodate file naming schemes that may be imposed by the operating system or other requirements. If the notion of environment variables is foreign to the target environment, @code{getwfdb} can be modified to read the WFDB path from a file. You may wish to modify the private function @code{wfdb_error} (which is responsible for all error reporting from WFDB library functions) if the `standard error output' is unavailable or inadequate for use in the target environment. All of these functions are contained within @file{wfdbio.c}; it is unlikely that any other code will require changes for a port. If you encounter errors while compiling @file{signal.c}, you may wish to try using the functions provided in that file as alternatives to the standard macros @code{r16} and @code{w16}; the fully-expanded versions of these macros are quite complex and are known to cause difficulty for at least one (now obsolete) C compiler. (Define the symbol @samp{BROKEN_CC} while compiling @file{signal.c} in order to obtain the function versions of @code{r16} and @code{w16}.) While compiling @file{signal.c}, it may be necessary to disable code optimization for some C compilers; no current compilers are known to have such limitations, however. @node Sources, Answers, Extensions, Top @appendix Sources This section is a compendium of sources for databases and related materials that may be useful to readers of this guide. Please send any corrections to the author (@email{wfdb@@physionet.org}). @table @emph @cindex MIT DB @cindex waveform editor @cindex XView toolkit @item WFDB Programmer's Guide (this guide) @itemx WFDB Applications Guide @itemx WAVE User's Guide @itemx MIT-BIH Arrhythmia Database @itemx MIT-BIH Arrhythmia Database Directory @itemx MIT-BIH Polysomnographic Database @itemx MIMIC Database @itemx MGH/Marquette Foundation Waveform Database CROMs @item Long-Term ST Database @itemx Other reference databases of physiologic signals @itemx WFDB Software Package @itemx XView toolkit (needed for WAVE) @sp 1 @display WWW: @uref{http://physionet.org/} @end display PhysioNet offers free access via the web to large collections of recorded physiologic signals and related open-source software. The PhysioNet web site is a public service of the PhysioNet Resource funded by the National Institutes of Health's NIBIB and NIGMS. The master PhysioNet web server is located at MIT in Cambridge, Massachusetts; about ten public mirrors are located elsewhere in the US and around the world (see @uref{http://@-physio@-net.@-org/@-mirrors/} for a list). @cindex CDROM @cindex ESC DB @item European ST-T Database CDROM @itemx European ST-T Database Directory @itemx VALE Database Directory @sp 1 @display National Research Council (CNR) Institute of Clinical Physiology Dept. of Bioengineering and Medical Informatics via Trieste, 41 56126 PISA, Italy email: @email{taddei@@ifc.pi.cnr.it} telephone: +39 050 501145 telefax: +39 050 503596 @end display Over half of this database has been contributed to PhysioNet (see above), from which it may be downloaded freely. @cindex AHA DB @item AHA Database for Evaluation of Ventricular Arrhythmia Detectors @sp 1 @display ECRI 5200 Butler Pike Plymouth Meeting, PA 19462 USA email: @email{bduffin@@ecri.org} WWW: @uref{http://www.ecri.org/} telephone: +1 610 825 6000 @end display @item American National Standard ANSI/AAMI EC38:1998, Ambulatory Electrocardiographs @itemx American National Standard ANSI/AAMI EC57:1998 Testing and Reporting Performance @itemx @ @ @ Results of Cardiac Rhythm and ST Segment Measurement Algorithms @sp 1 @display Association for the Advancement of Medical Instrumentation 1110 N Glebe Road, Suite 220 Arlington, VA 22201 USA WWW: @uref{http://www.aami.org/} telephone: +1 703 525 4890 telefax: +1 703 276 0793 @end display @item Computers in Cardiology @sp 1 @display WWW: @uref{http://www.cinc.org/} @end display CinC is the major scientific meeting at which current research in ECG signal processing and modelling is discussed; the proceedings of the conference are probably the single best source of information in print about these topics. CinC conferences have taken place annually since 1974, usually in September. They are usually convened in North America and in Europe in alternate years. The deadline for submission of abstracts is on or about 1 May each year. Proceedings of CinC conferences since 2006 are available on-line at @uref{http://cinc.mit.edu/archives/}, and usually appear about a month after the date of the conference. CinC will be in in Bologna (Italy) in 2008, and in Park City, Utah (USA) in 2009. Since 2000, Computers in Cardiology and PhysioNet have jointly sponsored an annual series of open challenges that invite participants to address topics of interest to researchers and clinicians, often involving the study of WFDB-compatible data sets made available via PhysioNet, and culminating with research presentations and awards at CinC. See @uref{http://physionet.org/challenge/} for further information. @item Proceedings of Computers in Cardiology (ISSN 0276-6574) @sp 1 CinC proceedings from 2001 to present are available on-line at @uref{http://www.cinc.org/archives/}. IEEE members can find CinC proceedings from 1988 to the present using IEEEXplore (@uref{http://ieeexplore.ieee.org/}). Many universities provide access to these services for their students, faculty, and staff. Printed volumes of CinC proceedings are available from: @display IEEE Customer Service 445 Hoes Lane P.O. Box 1331 Piscataway, NJ 08855-1331 USA email: @email{customer.service@@ieee.org} WWW: @uref{http://www.ieee.org/ieeestore/} telephone: 1 800 678 IEEE (USA and Canada) or +1 732 981 0060 telefax: +1 732 981 9667 @end display @cindex GNU emacs @item GNU emacs @itemx gcc (the GNU portable C/C++ compiler) @itemx ghostscript @itemx GNU tar @itemx GNU gzip (free and improved replacement for `compress') @itemx Larry Wall's `patch' program, with GNU revisions @itemx GNU groff, gtbl, and related text formatting utilities @itemx GNU info and makeinfo (standalone hypertext browser and formatter) @sp 1 @display Free Software Foundation 51 Franklin Street, Fifth Floor Boston, MA 02110-1301 USA email: @email{gnu@@gnu.org} WWW: @uref{http://www.gnu.org/} telephone: +1 617 542 5942 @end display GNU software is included in all Linux distributions (in fact, since Linux is the name of the kernel only, and the largest component of these distributions is actually GNU software, it is proper to refer to them as GNU/Linux distributions). GNU software for all popular (and many other) operating systems is available on CDROM or tape from the address above, and is also freely available by anonymous FTP from @code{ftp.gnu.org} and and many other archive sites. Please support the FSF with a donation if you use GNU software. @item @TeX{} for Unix systems @sp 1 This software is available by anonymous FTP from CTAN (Comprehensive TeX Archive Network) mirrors, including @code{ftp.tex.ac.uk}, @code{ftp.dante.de}, and @code{ctan.tug.org}. Many of the sources of GNU software (above) also make @TeX{}, etc. available. CTAN is indexed on the World Wide Web (one such index is @uref{http://www.ctan.org/}). The Unix @TeX{} distribution is also distributed on CDROM and in other tape formats by the Free Software Foundation (address above) and others. It is also included with most GNU/Linux distributions (see below). Several commercial implementations of @TeX{} for MS-DOS, MS-Windows, and Mac OS are widely available; visit the web site of the @TeX{} Users Group (below) for pointers. @item General information on @TeX{} @sp 1 @display TeX Users Group PO Box 2311 Portland, OR 97208-2311 USA WWW: @uref{http://www.tug.org/} email: @email{office@@tug.org} telephone: +1 503 223 3960 telefax: +1 503 223 9994 @end display @cindex curl @cindex libcurl @item libcurl @sp 1 @display WWW: @uref{http://curl.haxx.se/} @end display The @code{libcurl} library is a modern and enhanced replacement for the @code{libwww} libraries (see below). It provides the low-level functions needed to support the WFDB library's (optional) NETFILES capability. @cindex libwww @cindex W3C libwww @item W3C libwww libraries @sp 1 @display WWW: @uref{http://www.w3.org/Library/} @end display The @code{libwww} libraries, created and maintained by the World Wide Web Consortium, were used in the original implementation of NETFILES. Since these libraries are no longer maintained, the WFDB library now uses @code{libcurl} instead. @cindex X11 @cindex X Window System @item X11 (the X Window System, Version 11) @sp 1 @display email: @code{xorg_info@@x.org} WWW: @uref{http://www.x.org/} telephone: +1 781 376 8200 telefax: +1 781 376 9358 @end display Sources for XView are available from PhysioNet. @cindex GNU/Linux @cindex Linux @item GNU/Linux GNU/Linux is a POSIX-compliant reimplementation of the Unix operating system, written by Linus Torvalds and a cast of thousands. It runs on Intel 386, 486, and Pentium PCs, among others. For information about GNU/Linux, visit the web site of the Linux Documentation Project: @display WWW: @uref{http://www.linuxdoc.org/} @end display GNU/Linux is freely available by anonymous FTP in source and binary form from many sites, including: @display www.kernel.org metalab.unc.edu ftp.funet.fi @end display Many low-cost (typically US$10 to US$30) distributions of GNU/Linux on CDROMs are widely available. Among the more popular are: @display Debian (non-commercial) WWW: @uref{http://www.debian.org/} Fedora (non-commercial) WWW: @uref{http://fedoraproject.org/} Gentoo (non-commercial) WWW: @uref{http://www.gentoo.org/} Knoppix (non-commercial, live CD) WWW: @uref{http://www.knoppix.org/} Mandriva WWW: @uref{http://www.mandriva.com/} Red Hat WWW: @uref{http://www.redhat.com/} Slackware (non-commercial) WWW: @uref{http://www.slackware.com/} SuSE (Novell) WWW: @uref{http://www.novell.com/linux/} Ubuntu WWW: @uref{http://www.ubuntu.com/} @end display @item Compilers and software development systems Any ANSI/ISO C compiler can be used to compile the WFDB library and applications that use it. Under Unix and GNU/Linux, high-quality free compilers and development tools are universally available and taken for granted. Even if you must work in the MS-Windows or MS-DOS environment, however, there is no reason to purchase expensive, bloated, and inferior proprietary compilers and software development systems, since there are several excellent, highly recommended, and completely free alternatives based on the GNU C/C++ compiler (gcc). Using any of these packages does not limit you to creating free (open source) software, although you are certainly encouraged to do so. If you wish to develop and sell proprietary software using @code{gcc}, this is certainly possible, with fewer and less severe restrictions than you will encounter if using a commercial compiler. @cindex Cygwin @item Cygwin WWW: @uref{http://www.cygwin.com/} This is a freely available software development platform for MS-Windows 9x/NT/2000/ME/XP, based on GNU @code{gcc} and a large set of Unix utilities developed by the GNU project and ported to MS-Windows by Cygnus Software (now part of Red Hat, see above). Cygwin itself is open-source software and is highly recommended in preference to commercial C/C++ compilers if you must work in the MS-Windows environment. The WFDB Software Package binaries available via PhysioNet are created using Cygwin. @cindex MinGW @item MinGW WWW: @uref{http://www.mingw.org/} This is another freely available software development platform for MS-Windows, also based on @code{gcc} and many of the same utilities as Cygwin. The difference is that applications built by MinGW gcc use the native MS-Windows C library, while those built by Cygwin gcc generally use Cygwin's Unix-compatible standard C library. @cindex djgpp @item djgpp WWW: @uref{http://www.delorie.com/djgpp/} A freely available software development platform for MS-DOS, including @code{gcc}, a free 32-bit DOS extender, and many of the same utilties as Cygwin and MinGW. @item Microstar DAP analog interface boards for PCs @sp 1 @display Microstar Laboratories 2265 116th Avenue N.E. Bellevue, WA 98004 USA email: @email{info@@mstarlabs.com/} WWW: @uref{http://www.mstarlabs.com/} telephone: +1 425 453 2345 telefax: +1 425 453 3199 @end display @cindex Web browser @item Web browsers @sp 1 The most popular Web browsers may be downloaded at these locations: @display Firefox FTP: @code{ftp.mozilla.org} WWW: @uref{http://www.mozilla.org/firefox/} Google Chrome WWW: @uref{http://www.google.com/chrome/} MS Internet Explorer FTP: @code{ftp.microsoft.com} WWW: @uref{http://www.microsoft.com/} Opera FTP: @code{ftp.opera.com} WWW: @uref{http://www.opera.com/} @end display @end table @node Answers, Recent Changes, Sources, Top @unnumbered Answers to Selected Exercises @table @asis @item 3. @iftex 280 adu = (280 adu @minus{}(@minus{}300 adu)) / 10 adu/mmHg = 58 mmHg. @end iftex @ifinfo 280 adu = (280 adu - (-300 adu)) / 10 adu/mmHg = 58 mmHg. @end ifinfo @ifhtml 280 adu = (280 adu - (-300 adu)) / 10 adu/mmHg = 58 mmHg. @end ifhtml @item 4. @iftex 120 mmHg = 120 mmHg * 10 adu/mmHg + ( @minus{}300 adu) = 900 adu. @end iftex @ifinfo 120 mmHg = 120 mmHg * 10 adu/mmHg + (- 300 adu) = 900 adu. @end ifinfo @ifhtml 120 mmHg = 120 mmHg * 10 adu/mmHg + (- 300 adu) = 900 adu. @end ifhtml @item 5. The range of sample values is @iftex @minus{}2047 to +2047 adu, or @minus{}174.7 to +234.7 mmHg. The special value @minus{}2048 @end iftex @ifinfo -2047 to +2047 adu, or -174.7 to +234.7 mmHg. The special value -2048 @end ifinfo @ifhtml -2047 to +2047 adu, or -174.7 to +234.7 mmHg. The special value -2048 @end ifhtml adu, if found in the input, is replaced with @code{WFDB_INVALID_SAMPLE} (-32768) to indicate missing or out-of-range samples. @item 6. We don't know how big @file{signal.dat} is, because we don't know how many other signals are multiplexed with the @code{BP} signal. If there are no others, @file{signal.dat} is 1,500,000 bytes (@code{nsamp} * 1.5 bytes/sample). One-third of the space occupied by @file{signal.dat} could be saved if it were converted to format 8. The maximum slew rate representable in format 8 is 127 adu/sample interval * 100 sample intervals/sec / 10 adu/mmHg = 1270 mmHg/sec. @item 7. One way to save a little space is to resample the signal at 120 Hz, and then change to format 8 (maximum slew rate = 1524 mmHg/sec). This can be done using @file{xform}; it reduces the storage requirement by one-fifth. @item 8. If you have installed PhysioToolkit's @code{plt} package, a simple solution is to write the sample numbers and values on the standard output in two-column ASCII format. The plotting is then performed by the pipeline: @example @var{your-program} | plt 0 1 @end example @end table @node Recent Changes, Concept Index, Answers, Top @comment node-name, next, previous, up @unnumbered Recent Changes This section contains a brief summary of changes to the WFDB library and to this guide since the first printing of the tenth edition of this guide in June, 1999. See @file{NEWS}, in the top-level directory of the WFDB Software Package distribution, for information on any more recent changes that may not be described here. @unnumberedsec WFDB 10.7 @unnumberedsubsec New features in version 10.7 The WFDB library now supports storing signals in compressed form, using the FLAC (Free Lossless Audio Codec) algorithm. Compressed signal files are designated by the the format code 508, 516, or 524 (depending on the signal resolution.) In order to read and write signals in these formats, the FLAC library and header files must be installed when compiling the WFDB Software Package. If the symbol @code{WFDB_LARGETIME} is defined before including @code{wfdb.h}, then the type @code{WFDB_Time} is defined as a @code{long long} rather than a @code{long} (@pxref{Large time values}). New macros, defined in @file{wfdb.h}, can be used to construct format strings for @code{printf()} (@pxref{Displaying numeric values}) and @code{scanf()} (@pxref{Parsing numeric values}), independent of the underlying data types. If high-resolution mode is enabled using @code{setgvmode()}, then @code{sampfreq(@var{r})} will return the highest sampling frequency of any signal in record @var{r}, and subsequent calls to @code{mstimstr()} and @code{strtim()} will likewise interpret sample and frame numbers just as if the record were actually opened using @code{isigopen()}. If an error occurs while writing an output file, the library usually cannot detect the error until the file is closed. (For example, after calling @code{putvec()}, the output samples will be buffered, but there might not be enough disk space to store them.) In order to check that the output was written successfully, an application can call @samp{osigfopen(NULL, 0)} (for signal and header files), @samp{annopen("", NULL, 0)} (for annotation files), or @samp{setinfo(NULL)} (for info files). These function calls also work with previous versions of the library, but would always return 0. In calibration files, the signal description @samp{*} is treated as a wildcard entry that will match any signal with the specified units. When reading a multi-frequency record using @code{getvec()} or @code{sample()}, the resampled values will be correctly rounded to the nearest integer, and will be calculated correctly for large numbers of samples per frame. The Fortran wrapper functions have improved support for working with ``native Fortran'' string data (when @code{FIXSTRINGS} is defined), allowing Fortran programs to work with strings containing embedded spaces. These functions will now avoid buffer overflows, both with and without @code{FIXSTRINGS}. The macros @code{SALLOC}, @code{SUALLOC}, @code{SREALLOC}, @code{SSTRCPY}, and @code{MEMERR} will avoid evaluating their arguments more than once (except for the pointer argument.) These macros will now handle zero-byte allocations consistently, and will check for integer overflows. @code{osigopen()} permits the @var{siarray} argument to be @code{NULL} (like @code{isigopen()}.) @unnumberedsubsec Bugs fixed in version 10.7.0 (10 June 2022) A number of corner cases have been fixed in @code{getframe()}. It now correctly handles skewed signals in variable-layout records, records with multiple signal files, and records with signal file prologs. It also correctly handles multi-segment records with signal file prologs, and multi-segment records where some signal files are missing. The @code{wfdbgetskew()} and @code{wfdbsetiskew()} functions now work as documented. Functions for reading header, calibration, and info files will now correctly handle lines of any length, rather than being limited to 255 or 126 characters per line. In the WFDB path, colons can be used within the ``authority'' portion of a URL, in order to designate a port number (@samp{http://example.org:8080/}), an IPv6 address (@samp{http://[::1]/}), or a password (@samp{http://x:y@@example.org/}). Colons that appear after the third slash in a URL are treated as path separators, as usual. Many library functions accept an argument that is a pointer to a string, array, or structure of some kind. If the object pointed to is not modified, the argument is now declared with the @code{const} qualifier. Previously, the @code{putvec()} function would sometimes modify the array provided by the caller; now, the array is never modified. The @code{sample()} function will return correct results when samples are retrieved in random order. Previously, if the caller requested sample 1, followed by sample 5000, followed by sample 2, @code{sample()} would return sample 1, followed by sample 4998, followed by sample 5001. A multi-segment record in which the first segment has non-zero length, but the first segment @emph{header} file is missing length or checksum fields, is now handled correctly as a fixed-layout record. @code{setgvmode()} will handle @code{WFDB_GVPAD}, as documented; this was broken in WFDB library versions 10.5.3 through 10.6.2. @code{getwfdb()} will no longer add or modify existing environment variables. In previous versions of the library, the first time @code{getwfdb()} was called, it would set the @code{WFDB}, @code{WFDBCAL}, @code{WFDBANNSORT}, and @code{WFDBGVMODE} environment variables, if these variables were not already defined. @code{setwfdb()} will correctly handle indirect paths (e.g., @samp{@@file}). @code{timstr()} or @code{mstimstr()} would, in some cases, return an incorrect date following a call to @code{setbasetime()} or @code{datstr()}. This has been fixed. @unnumberedsec WFDB 10.6 @unnumberedsubsec Changes in version 10.6.2 (8 March 2019) Changes to the internal functions @code{get_ann_table()} and @code{put_ann_table()} ensure sensible and consistent behavior if a custom annotation type is defined (using @code{setannstr()}) but no description is provided (using @code{setanndesc()}). Prior to version 10.6.0, the library would typically set the description to @code{"(null)"} in this case, but this was not guaranteed. In version 10.6.0, this behavior was changed in an attempt to fix the undefined behavior, but the result was an annotation file that could not be read correctly. Version 10.6.2 fixes both of these problems; annotation files written by any older version can be read by version 10.6.2, and annotation files written by version 10.6.2 can be read by any older version. Changes in @code{isgsettime()} and @code{isgsetframe()} avoid incorrect behavior if the specified time value is large enough to cause integer overflow. @unnumberedsubsec Changes in version 10.6.1 (28 November 2018) The internal function @code{edfparse()} will correctly calculate the ADC resolution of EDF signals, and will correctly interpret a negative ``number of data records'' (meaning that the length of the record was unknown at the time the header was written.) @code{sample()} now returns @code{WFDB_INVALID_SAMPLE} if the requested sample is not in the buffer and cannot be read (i.e., @code{isigsettime()} fails.) @code{sample_valid()} returns 0 in this case. In previous versions of the library, @code{sample()} would call @code{exit()} in this situation, forcing the program to exit immediately. In some circumstances, @code{isigopen()} will now return -3; unlike a return value of -1 or -2, this indicates that no new signals have been opened and all previously-opened signals have been closed. The implementation of NETFILES has been optimized in several ways, to avoid making unnecessary HTTP requests, and to avoid unnecessarily disconnecting from and reconnecting to a remote server, especially when opening a remote file for the first time. The library will now honor HTTP redirections. (Previously, the @code{libwww}-based implementation would follow redirections properly, but the @code{libcurl}-based implementation would not.) Up to five redirections will be followed for a particular file, and (if range requests are used) the new URL will be cached for up to five minutes. The internal function @code{readheader()} will correctly parse ``minimum version'' requirements (denoted by @code{#wfdb} at the start of a header file.) The library will cope better with records that are incorrectly formatted or partially unreadable: @code{getskewedframe()} will avoid crashing if a signal file ends unexpectedly in a multi-segment record; @code{readheader()} will reject segment names beginning with `+'; @code{isigopen()} will reject records or segments where the total number of samples per frame, or the maximum signal skew, is too large; @code{isgsetframe()} and @code{getskewedframe()} will treat an unreadable segment as an error. Several bugs have been fixed in @code{isigopen()}. It will now correctly handle the sitation where @code{nsig} is zero or negative, the record has multiple segments, and the first segment header cannot be read or contains no signals. It will correctly handle some situations where a record contains three or more signal groups, and not all signals can be opened (for example, when the first signal file contains more than @code{nsig} signals, but the second and third signal files together contain @code{nsig} or fewer signals.) When multiple records are opened at once (``+ mode''), it will correctly honor the limit provided by the caller, and will not open more than @code{nsig} new signals. The internal function @code{wfdb_fopen()} will treat any path containing @code{://} as a possible URL, and will correctly handle non-URL paths that end with @code{:} or @code{:/}. @unnumberedsubsec Changes in version 10.6.0 (26 January 2018) The new functions @code{getiafreq()} and @code{setiafreq()} allow the application to change the time scale for input annotations, as @code{setifreq()} does for input signals. The new function @code{getiaorigfreq()} returns the ``native'' time resolution of the input file. New macros, defined in @file{wfdb.h}, can be used to determine the limits of the numeric types used by the WFDB library. For example, @code{WFDB_SAMPLE_MAX} is the maximum value of a @code{WFDB_Sample} variable. @file{wfdb.h} now includes prototypes for the internal functions @code{wfdb_me_fatal()} and @code{wfdb_error()}, which are used by the @code{MEMERR} macro. Support for using @code{libwww} to read remote files has been removed. @code{libwww} support was introduced with version 10.0.1 in 2000, and was still supported as an alternative after @code{libcurl} support was added in version 10.3.16. However, @code{libwww} has not been actively developed for many years; we don't recommend its use anymore, and removing support is necessary in order to simplify and add new features to WFDB in the future. The WFDB library now supports reading variable-layout, @emph{multi-frequency} records, provided that each signal's sampling frequency is constant across the entire record. Although previous versions of the library were able to read such records in some cases, the behavior of @code{getvec()} and @code{isigsettime()} was buggy and inconsistent. When reading a variable-layout multi-segment record, values returned by @code{getvec()}, @code{getframe()}, or @code{sample()} are rounded to the nearest integer. If that value is outside the range of a @code{WFDB_Sample} variable, the value @code{WFDB_SAMPLE_MIN} or @code{WFDB_SAMPLE_MAX} is returned. When reading a signal from an EDF file in which the minimum representable value is greater than zero (corresponding to a negative baseline), the @code{baseline} returned by @code{isigopen()} is correctly rounded to the nearest integer. In previous versions, the baseline was off by 1, causing applications to calculate an incorrect physical value. When reading a signal file in format 311, the file may end with two samples encoded as three bytes (so the total number of samples is @math{3n+2}, and the total number of bytes is @math{4n+3}.) Files created by the WFDB library itself do not use this format (an extra zero sample will be added in this case, for backwards compatibility), but such files may be created by other applications. @code{isigsettime()} works correctly when reading a multi-segment record with multiple signal files per segment. @code{setifreq()} works correctly if there are no input signals open. @code{tnextvec()} returns -1 if it reaches the end of the record without finding a valid sample. Previously, in the case of a fixed-layout record, it would return zero in this case. @code{getann()} correctly handles annotation files with huge @code{time} values (where the unscaled value exceeds the range of a @code{WFDB_Time} variable.) If the @emph{scaled} value exceeds that range, it is replaced with @code{WFDB_TIME_MIN} or @code{WFDB_TIME_MAX}. @code{getann()} will also correctly round the @code{time} value if it is negative, and will correctly handle non-NOTE annotations at time @emph{zero}. If an input annotation file has no explicit time resolution, but the application calls @code{setifreq()} before @code{annopen()}, the input annotations will be rescaled accordingly. (In previous versions of the library, this would only work for annotation files with an explicit time resolution.) When writing signals in format 212, 310, or 311, if the total number of samples is not a multiple of 2 or 3, @code{wfdbquit()} will correctly write out the remaining samples, adding padding if necessary. @code{wfdbflush()} will do likewise, provided that the output is a regular file and there is no mandatory block size. When writing an annotation file, the time resolution and annotation type definitions will not be written until the first time @code{putann()} is called for that annotator. As a result, it is possible to call @code{setafreq()}, @code{setannstr()}, or @code{setanndesc()}, after calling @code{annopen()} and before the first @code{putann()}. @code{putann()} correctly handles consecutive annotations that are more than 2,147,483,647 samples apart. The Fortran wrapper functions have been updated for compatibility with modern Fortran compilers on 64-bit systems. Password-protected remote files can now be accessed when WFDB_PAGESIZE is set to zero. @unnumberedsec WFDB 10.5 @unnumberedsubsec Changes in version 10.5.24 (28 May 2015) The environment variable @code{WFDBPASSWORD} is used for user/password information, in place of the former (inflexible and insecurely implemented) @code{PNWUSER} and @code{PNWPASS} variables. The environment variable @code{CURL_CA_BUNDLE} defines the set of certificate authorities that are trusted to issue certificates for web servers, if any @code{https://} entries are used in the WFDB path. If the environment variable @code{WFDB_NET_DEBUG} is set, then whenever the WFDB library requests or receives data from a remote server, details of the operation will be written to the standard error output. On most platforms, the library is now installed in @file{/usr/local/lib} by default, rather than @file{/usr/local/lib64} (as in 10.5.23) or @file{/usr/lib64} (as in previous versions.) @unnumberedsubsec Changes in version 10.5.23 (13 March 2014) Changes in @file{configure}, @file{Makefile.tpl}, @file{conf/linux.def}, and @file{conf/linux-slib.def} simplify installation of shared WFDB libraries and the applications that use them on Linux platforms. (WFDB library version 10.5.22 was identical to version 10.5.21.) @unnumberedsubsec Changes in version 10.5.21 (18 November 2013) In previous releases, WFDB library function @code{strtim()} did not always handle bracketed string inputs properly. Thanks to Benjamin Moody, who reported the problem and provided a patch to fix it. @unnumberedsubsec Changes in version 10.5.20 (2 September 2013) Absolute pathnames are not tested in @code{wfdb_open()} unless the WFDB path contains an empty component. @unnumberedsubsec Changes in version 10.5.19 (21 July 2013) This release includes fixes in lib/signal.c for several bugs that sometimes caused @code{findsig()}, @code{getvec()}, and @code{sample()} to return incorrect values when reading variable-layout multi-segment records with missing signals. @unnumberedsubsec Changes in version 10.5.18 (16 February 2013) Function @code{wfdb_addtopath} now works properly if the path contained only one component on entry. @unnumberedsubsec Changes in version 10.5.17 (2 January 2013) The WFDB library internal function @code{wfdb_addtopath()} (in @file{lib/wfdbio.c}) adds the path component of its string argument (i.e., everything except the file name itself) to the WFDB path, inserting it there if it is not already in the path. This function is called by another WFDB library internal function, @code{wfdb_open()}, which finds and opens files for all other WFDB library functions that open files; @code{wfdb_open()} passes the paths of files it successfully opens to @code{wfdb_addtopath()}, permitting other files in the same locations to be found. In previous releases, @code{wfdb_addtopath()} appended the new path component to the end of the WFDB path. As originally noted by David Brooks, this is suboptimal since the files comprising a given record are most often kept in the same directory. In this release, the new component is inserted at the head of the path, or as the second path component if @code{"."} (the current directory) is the first component, thus improving the likelihood that subsequent files to be opened will be found in the first or second location @code{wfdb_open()} checks. @unnumberedsubsec Changes in version 10.5.16 (27 September 2012) A bug in WFDB library versions 10.5.11 through 10.5.15 resulted in an attempt to close an already-closed header file after invoking @code{putinfo()}. Thanks to Benjamin Moody for identifying the bug and contributing code to correct it (in @file{lib/signal.c} and @file{lib/wfdbinit.c}). @unnumberedsubsec Changes in version 10.5.15 (25 September 2012) Changes to the internal function @code{readheader()} in WFDB library version 10.5.14 made the library unable to open EDF files. This bug has been fixed. @unnumberedsubsec Changes in version 10.5.14 (13 August 2012) WFDB applications can now read shared and private PhysioNetWorks projects securely, just as they have been able to read PhysioBank data since version 10.0.1 (November 1999). Low-level functions @code{wfdb_open()} (in @file{lib/wfdbio.c}) and @code{readheader()} (in @file{lib/signal.c}) incorporate changes to implement this new capability, as well as another new feature that allows record names to be specified using absolute pathnames or URLs. As always, a final @file{.hea} is not considered to be part of a record name, but it may be included or omitted as desired. @unnumberedsubsec Changes in version 10.5.13 (13 May 2012) Versions of the WFDB library up to 10.5.10 ignored embedded empty lines within the @code{info} sections of @file{.hea} files, but versions 10.5.10 and 10.5.11 treat them as markers of the end of the @code{info} section. This version restores the previous treatment of embedded empty lines. Thanks to Justin Leo Cheang Loong for reporting this issue. @unnumberedsubsec Changes in version 10.5.12 (25 April 2012) When called with a NULL argument, @code{getinfo()} sometimes behaves differently in WFDB library version 10.5.11 than it does in previous versions. This release restores the previous behavior. Thanks to Benjamin Moody for reporting this issue. @unnumberedsubsec Changes in version 10.5.11 (6 April 2012) This release of the WFDB library introduces support for @file{.info} files. These are files containing @code{info} strings in the same format as those that can be stored in @file{.hea} files. The @file{.info} file for a record named @file{record} is named @file{record.info}, and it may be located anywhere in the WFDB path. Function @code{getinfo()}, as in previous releases, returns the first info string belonging to the record named as its argument, or the next info string belonging to the previously specified record if its argument is NULL. Beginning with this release, successive calls to @code{getinfo()} return the next info string contained in the record's @file{.info} file if it exists, and if there are no more in the record's @file{.hea} file. Function @code{putinfo()} writes an info string to the currently open output @file{.hea} file, unless function @code{setinfo()} (new in this release) has been invoked, in which case the info string is appended to the record's @file{.info} file in the current directory. @code{getinfo()} reads all of the record's info strings the first time it is invoked, returning them one at a time; @code{wfdb_freeinfo()} frees the memory allocated for @code{getinfo()}'s info strings and closes the @file{.info} file opened by @file{putinfo()}, if any. After invoking @code{wfdb_freeinfo()}, a subsequent call to @code{getinfo()} reads the info strings again (or those of a different record, if a new record has been opened). Virginia Faro-Maza identified and corrected a bug in WFDB library function @code{iannsettime()}, in @file{lib/annot.c}, that caused some annotations to be missed when two or more annotation files are open simultaneously. WFDB library function @code{isigopen()}, in @file{lib/signal.c}, was reverted to that of version 10.5.9. Benjamin Moody contributed patches for @file{app/snip.c} to ensure that the output will be written using a format that can accommodate the sample range. The signal calibration file, @file{data/wfdbcal}, has been updated with new definitions. @unnumberedsubsec Changes in version 10.5.10 (15 November 2011) The WFDB library function @code{isigopen()}, in @file{lib/signal.c}, searches each component of the WFDB path for the signal file(s) named in the associated header file, until a match is found. Since signal files are usually located in the same directories as header files, they can be located most quickly by looking first in those directories. Thanks to David Brooks for suggesting this optimization and for a sample implementation. Attempts to set the default size for HTTP range requests (using the environment variable @code{WFDB_PAGESIZE}) were ignored in previous versions of the WFDB library when compiled with libcurl. Thanks again to David for noting this limitation, which has been eliminated in this release by a change in @code{www_init()} (in @file{lib/wfdbio.c}). @unnumberedsubsec Changes in version 10.5.9 (10 September 2011) When an application passes an array containing @code{WFDB_INVALID_SAMPLE} values to @code{putvec()}, the function translates these into the corresponding invalid-sample sentinel values used by the file format. (This is the inverse of the transformation done by @code{getvec()} and @code{getframe()}, so the effect is that at the application level, invalid samples are always represented by the value @code{WFDB_INVALID_SAMPLE}.) Previous versions of the library did not perform this transformation correctly for formats 80 and 160 (they used a value of zero rather than -128 or -32768, respectively.) This bug was mainly an issue for programs such as @file{snip} and @file{xform} that read and modify existing data files. Thanks to Benjamin Moody for identifying these problems and providing patches to remedy them. @unnumberedsubsec Changes in version 10.5.8 (12 March 2011) Previous versions of the WFDB library did not behave properly if @code{setbasetime()} was invoked before @code{setsampfreq()}. Also, when dealing with time-of-day strings, previous versions of @code{mstimstr()} and @code{strtim()} round the base time to a number of samples since midnight, and do not work correctly if the number of samples per day is not an integer. Benjamin Moody contributed patches to @file{lib/signal.c} that remedy these deficiencies. The patches include three new internal functions (@code{fstrtim()}, @code{ftimstr()}, and @code{fmstimstr()}) which are equivalent to the WFDB library functions @code{strtim()}, @code{timstr()}, and @code{mstimstr()}, but take a second argument specifying the sampling frequency. These internal functions are used by @code{setbasetime()} and @code{setheader()} to record the base time with millisecond precision, independent of the actual sampling frequency, and independent of the effects of @code{setifreq()}, if any. Moreover, @code{mstimstr()} returns a string representation of the base time plus the given number of sampling intervals, @code{mstimstr(0)} returns the exact base time, and @code{strtim()} returns the sample number that is closest to the given time. In addition to being more precise, both functions now work correctly even if the number of samples per day is not an integer. Applications using this version of the WFDB library may call @code{setbasetime()} and @code{setsampfreq()} in either order. @unnumberedsubsec Changes in version 10.5.7 (16 December 2010) When opening records with the same name in different directories successively within a single process, the persistence of WFDB path changes made by WFDB library function @code{wfdb_addtopath()} interfered with locating the correct files in the second and subsequent records. The solution included addition of a new WFDB library function, @code{resetwfdb()}, which restores the WFDB path to the value returned by the first invocation of @code{getwfdb()} in the current process (or @code{NULL} if @code{getwfdb()} has not been invoked); library function @code{wfdbquit()} now invokes @code{resetwfdb()}. In addition, the safe-string copy macro @code{SSTRCPY} (defined in @code{wfdb.h}) now properly handles the case of copying null pointers. Thanks to Benjamin Moody for identifying the problem, providing test inputs, and contributions to the solution. @unnumberedsubsec Changes in version 10.5.6 (29 November 2010) WFDB records with names of the form @file{nnn/nnn} can now be identified using the short form @file{nnn/} in applications built using the WFDB library (e.g., @code{rdsamp -r mimicdb/037/} and @code{rdsamp -r mimicdb/037/037} are now equivalent). In @file{}, the maximum lengths of record names, units strings, and signal description (@file{.desc}) strings have been increased (to 50, 50, and 100 characters respectively). In @file{lib/wfdbio.c}, the maximum length of a WFDB file name (including path information) has been increased to 1024 characters. (WFDB library version 10.5.5 was identical to version 10.5.4.) @unnumberedsubsec Changes in version 10.5.4 (13 July 2010) Function @code{getseginfo()} has been introduced in WFDB library version 10.5.4, to allow applications to obtain information about the segments belonging to the current (multi-segment) input record. In previous versions, integer arithmetic overflow was possible when converting format 32 samples using @code{aduphys()}, if the difference between the baseline value and the sample to be converted exceeded the range of signed 32-bit integers. Although @code{rdsamp} does not use @code{aduphys()} similar code in @code{rdsamp} also exhibited this problem, which has now been corrected; thanks to Ikaro Silva for reporting it and providing a test case. @unnumberedsubsec Changes in version 10.5.3 (22 June 2010) Function @code{getgvmode()} has been introduced in WFDB library version 10.5.3, to allow querying the current operating mode of @code{getvec()}. @unnumberedsubsec Changes in version 10.5.2 (18 April 2010) When reading annotations of multifrequency records opened in high-resolution mode, a time shift was introduced by @code{getann()} in WFDB library versions 10.4.5 to 10.5.1. This problem has been corrected in 10.5.2. Certain malformed segment .hea files were able to cause null pointer errors in @code{isigopen()}. This problem has been corrected. Thanks to Mauro Villarroel for reporting this problem together with a test case. @unnumberedsubsec Changes in version 10.5.1 (19 March 2010) In version 10.5.0, signals in formats 80 and 160 with amplitudes of 0 were incorrectly treated as invalid by @code{getframe()} and @code{getvec()}. Thanks to Isaac Henry for reporting this problem, which has been corrected in @file{lib/signal.c}. @unnumberedsubsec Changes in version 10.5.0 (16 March 2010) The WFDB library now supports signals with 24 and 32 bits of precision, using new formats 24 and 32, as well as BDF and BDF+ files (24-bit EDF and EDF+ variants), so that WFDB applications can now read these formats. Note that these formats, unlike all previously defined formats, require more than 16 bits per sample value. If the WFDB software is compiled on a 16-bit platform (unusual except for embedded processors), the excess high bits of signals in these formats are not read on input, and they are replaced by zeroes on output, unless the @code{WFDB_Sample} data type has been redefined as @code{long} (in @file{wfdb.h}). This is not done by default since it would increase memory and computational requirements unnecessarily in embedded applications that do not require 24- or 32-bit precision. Since support for extended precision samples cannot be introduced without this limitation in backward compatibility for 16-bit platforms, the minor version number of the library has been incremented to 5. Most users will not be affected by this change, however, apart from the new functionality it provides. Memory allocation macros that have been defined previously in @file{wfdblib.h} have been moved to @file{wfdb.h} so they are accessible to WFDB applications, including user-written applications. For information about using these macros (MEMERR, SFREE, SUALLOC, SALLOC, SREALLOC, and SSTRCPY), @pxref{memory allocation macros}. A buffer overflow in the WFDB library's internal function @code{edfparse()} (in @file{signal.c}) has been corrected, thanks to a bug report and patch from Joonas Paalasmaa. @unnumberedsec WFDB 10.4 @unnumberedsubsec Changes in version 10.4.25 (21 January 2010) In WFDB library versions 10.4.5 through 10.4.24, @code{strtim("e")} did not work properly when the open input record was an EDF file and high resolution input mode had not been selected. This problem has been corrected. (WFDB library version 10.4.24 was identical to version 10.4.23.) @unnumberedsubsec Changes in version 10.4.23 (7 August 2009) Several changes in @file{lib/signal.c} eliminate unintended interactions that occurred in previous versions between the input modes that can be selected using @code{setgvmode()}. The effects of these interactions were first observed in newly written code; existing WFDB applications were apparently unaffected. On Cygwin/Windows, the WFDB library is now installed in both @file{/usr/bin} and @file{/usr/lib}, to simplify building user applications that use the WFDB library and to simplify running compiled WFDB applications outside of the Cygwin environment. @unnumberedsubsec Changes in version 10.4.22 (28 July 2009) When reading multifrequency records in WFDB_LOWRES mode, invalid sample values occurring in signals that need to be decimated were not always handled properly in WFDB library function @code{getvec()}, resulting in spurious artifacts during intervals that contained a mixture of valid and invalid samples. This bug has been corrected; thanks to Omar Abdala for a report that brought this problem to light. A declaration for WFDB library function @code{findsig()} has been added to @file{wfdb.h}. Thanks to Thomas Heldt for reporting a warning that resulted from its omission in previous versions. @unnumberedsubsec Changes in version 10.4.21 (14 May 2009) Changes in @file{lib/Makefile.tpl} were needed in order to pass configuration constants to the functions added to @file{lib/wfdbio.c} in 10.4.20, but were omitted from that release; they have been included in this version. @unnumberedsubsec Changes in version 10.4.20 (4 May 2009) Several new functions have been added to @file{lib/wfdbio.c}, to make configuration constants accessible at run time; these are needed by the WFDB Toolkit for Matlab. (WFDB library version 10.4.19 was identical to version 10.4.18.) @unnumberedsubsec Changes in version 10.4.18 (15 March 2009) The WFDB library no longer reports spurious checksum errors when reading EDF files, which don't have checksums. @unnumberedsubsec Changes in version 10.4.17 (5 March 2009) Internal WFDB library function @code{wfdb_checkname} now allows hyphens (@code{-}) within record and annotator names. @unnumberedsubsec Changes in version 10.4.16 (3 March 2009) WFDB library function @code{strtim} now rounds rather than truncating when the sampling frequency is not an integer. @unnumberedsubsec Changes in version 10.4.15 (26 February 2009) WFDB library function @code{mstimstr} now outputs time to the nearest millisecond, rather than truncating its calculation to the next lowest number of milliseconds. A new WFDB library function, @code{wfdbputprolog}, can write a prolog at the beginning of a signal file. WFDB applications ignore embedded prologs. @unnumberedsubsec Changes in version 10.4.14 (23 February 2009) WFDB library function @code{setwfdb()} now exports the WFDB library environment variables (@code{WFDB}, @code{WFDBCAL}, @code{WFDBANNSORT,} and @code{WFDBGVMODE}) on all modern platforms, so that a process started by a WFDB application inherits the WFDB environment of its parent. (This is not possible on platforms that do not provide a working @code{putenv()} function, such as MacOS 9 and earlier, and 16-bit versions of MS-Windows.) Some previous versions also included this capability, but the older implementation caused a memory leak, and it was disabled in version 10.4.6. Thanks to Omar Abdala and Dan Scott for reporting the problem and help in identifying its cause. @unnumberedsubsec Changes in version 10.4.13 (16 February 2009) A new WFDB library function, @code{tnextvec()}, finds the next valid sample from a chosen signal, occurring at or after a specified time. This function is particularly useful when reading variable-layout multi-segment records that may have lengthy gaps in signals of interest. For portability, the WFDB library has always stored parameters such as sampling frequency and gain as strings rather than as floating-point numbers. Although the resultant loss of precision has been almost always negligible, it has been observable in the specific case of converting very large time intervals from sample intervals to seconds, when the sampling frequency in Hz cannot be represented exactly in binary (e.g., if the sampling frequency is once per minute, or 1/60 Hz). This situation cannot be wholly avoided, but it can be minimized. Changes in this release, in @code{setheader()} and in functions that record time units in annotation files, increase the precision with which non-integer parameters are recorded, so that loss of precision as a result of converting them to and from strings should almost never be observable. @unnumberedsubsec Changes in version 10.4.12 (20 January 2009) The rule for sorting annotations within a file has been changed to allow a much larger number of simultaneous annotations (i.e., annotations in a given annotation file with identical @code{time} fields) than was previously possible. Since version 6.1, the WFDB library sorts simultaneous annotations according to the value of their @code{chan} fields, allowing for 256 simultaneous annotations at any given time. Beginning in version 10.4.12, the library sorts simultaneous annotations according to their @code{num} fields, then sorts those with identical @code{num} fields according to their @code{chan} fields, allowing up to 65,536 simultaneous annotations at any given time. A new WFDB library function, @code{findsig}, returns the signal number of the input signal matching its string argument, or -1 if no such input signal exists. If the string argument could be interpreted as an input signal number, it is taken as such; otherwise, the string argument must be an exact match to a signal name (@code{desc} field in the @code{siginfo} structure). Previous versions of WFDB library function @code{setifreq} entered an infinite loop if invoked (contrary to specifications) before opening an input record. @code{setifreq} now detects the error, emits an appropriate warning, and returns. If a WFDB application that uses WFDB library version 10.4.5 through 10.4.11 attempted to read an annotation file before reading the sampling frequency of the associated record (for example, by invoking @code{isigopen} or @code{sampfreq}), the annotation times might all appear to be zero. This may occur when reading annotations created using WFDB 10.4.5 or later. The times supplied when creating the file are correctly written but may be incorrectly read in these cases. This problem was corrected in this release; thanks to Thomas Heldt for reporting it and providing a reproducible example of it. (WFDB library version 10.4.11 was identical to version 10.4.10.) @unnumberedsubsec Changes in version 10.4.10 (31 October 2008) EDF digital maximum and minimum values are now read properly in 64-bit builds; previous versions had a bug in @code{edfparse} (an internal WFDB library function defined in @file{lib/signal.c}) that did not appear in 32-bit builds. Thanks to Joe Mietus for reporting the problem. @unnumberedsubsec Changes in version 10.4.9 (10 October 2008) The WFDB library once again correctly interprets a hyphen (@code{-}) (used in place of a record name, annotator name, signal file name, or calibration file name) as a reference to the standard input or output, for platforms that support POSIX standard I/O (@pxref{Standard I/O}). This feature was broken in 10.4.5 as a side effect of changes in @code{wfdb_open} (an internal WFDB library function defined in @file{lib/wfdbio.c}). (WFDB library version 10.4.8 was identical to version 10.4.7.) @unnumberedsubsec Changes in version 10.4.7 (15 July 2008) Yinqi Zhang reported and contributed a fix for a memory leak in @code{make_vsd()} (an internal WFDB library function defined in @file{signal.c}). @unnumberedsubsec Changes in version 10.4.6 (9 April 2008) The WFDB functions @code{setafreq()} and @code{getafreq()} (for setting and getting the time resolution of newly-created output annotation files in ticks per second) were new in version 10.4.5, but were undocumented. They are now described in this Guide, and wrappers for these functions are now included in @file{fortran/wfdbf.c}. An important change in the WFDB library: memory allocation errors are now treated as fatal by default (in previous versions, the functions that encountered them returned error values that permitted the application to handle them). These errors occur when there is insufficient memory available to the WFDB library. To obtain the old behavior, in which the calling function will continue execution if possible after a memory allocation error, invoke @code{wfdbmemerr(0)}. By default, however, such an error will cause the process to terminate. In either case, the WFDB library emits an appropriate error message to aid in troubleshooting. New macros for handling dynamically allocated memory are defined in @file{lib/wfdblib.h} and used throughout the WFDB library, eliminating most known memory leaks. Three known leaks remain (in @code{setecgstr()}, @code{setannstr()}, and @code{setanndesc()}); these are documented and harmless in current applications. Thanks to Yinqi Zhang for reporting a leak in @code{copysi()} (an internal WFDB library function defined in @file{signal.c}), which prompted the cleanup. WFDB functions @code{strecg()}, @code{setecgstr()}, @code{strann()}, @code{setannstr()}, and @code{setanndesc()} now handle NULL string inputs properly. (Previous versions passed NULL strings to @code{strcmp()}, with undesirable results.) @unnumberedsubsec Changes in version 10.4.5 (6 February 2008) Bob Farrell and Tony Ricke chased down and provided fixes for memory leaks in several WFDB library functions, and also provided revisions to permit additional type checking and to avoid type mismatch warnings. Changes in the build system make it easier to build WFDB using Cygwin gcc (with or without the Cygwin POSIX library). When creating annotation files, if the input sampling frequency differs from the frame rate of the input record (either because of using @code{WFDB_HIGHRES} mode while reading a multifrequency record, or because of having used @code{setifreq()} to modify the sampling frequency), a comment is written to the beginning of the annotation file indicating the resolution of the annotation times in ticks per second (thus allowing the application to store its annotations with whatever time resolution is desired). When reading an annotation file, if such a resolution comment is found, getann adjusts the times of annotations to match the currently defined sampling frequency. The resolutions are kept independently for each annotation file, so (for example) @code{bxb} can compare two annotation files written with different resolutions. The ability to set the time resolution of annotation files has required a minor change in the semantics of @code{setifreq()}. It is now necessary to invoke @code{setifreq()} before creating an annotation file that will have a resolution matching the (modified) input sampling frequency. Since @code{setifreq()} must be invoked after opening the input signals, this implies that @code{wfdbinit()} cannot be used to open both input signals and output annotation files if @code{setifreq()} is to be used; rather, the sequence should be @code{isigopen()}, @code{setifreq()}, and finally @code{annopen()}. If a string that includes a @samp{.} is supplied to a WFDB library function where a record name is expected, the WFDB library assumes that it is the name of a file located in the WFDB path. If the name ends in @samp{.hea}, the file is assumed to be a WFDB-format header file, and its record name is assumed to be the first part of the string, exclusive of the @samp{.hea'}. This version also includes support for reading EDF files natively. If a string supplied as a record name contains a @samp{.} but does not end in @samp{.hea}, it is assumed to be the record name of an EDF file of the same name. (WFDB library versions 10.4.3 and 10.4.4 were identical to version 10.4.2.) @unnumberedsubsec Changes in version 10.4.2 (4 May 2006) Mathias Gruber reported a line in wfdbio.c that used void pointer arithmetic (permitted as an extension by gcc but not allowed by ANSI/ISO C or most other C compilers). This operation has been replaced by ANSI/ISO C-conformant code. @unnumberedsubsec Changes in version 10.4.1 (6 April 2006) A bug caused incorrect output from WFDB library function strtim() when called with the argument "i", following use of setifreq() to change the effective sampling frequency, resulting in incorrect output from example 10 in the WFDB Programmer's Guide. This has now been corrected. @unnumberedsubsec Changes in version 10.4.0 (2 March 2006) Version 10.4.0 and later versions of the WFDB library are intended to be compiled using ANSI/ISO C (and C++) compilers only; previous versions also supported the use of traditional (K&R) C compilers. The most obvious change resulting from this decision is in the use of prototypes in function declarations, an innovation of ANSI C that permits better error-checking by compilers. The ANSI/ISO C standard is now more than 15 years old, and it has been over 10 years since a C compiler that does not support function prototypes was used for development of the WFDB library. Code in @file{wfdbio.c} that provides limited support for compilers that do not provide an ANSI/ISO C library has been retained for now, and @file{wfdb.h} still includes a set of K&R C function declarations; both of these features are deprecated, however, and may be removed in future versions of the WFDB library. Users who still need to use a K&R C compiler to compile the library itself may find 'unprotoize' (included in the GNU gcc distribution) to be helpful. The mapping of lowest expressible sample values to @code{WFDB_INVALID_SAMPLE} performed by @code{getframe()} (in @file{lib/signal.c}) did not work properly for signal formats 80 and 160 (in which samples are recorded as unsigned integers); this has now been corrected. The symbol @code{WFDB_GVPAD} is newly defined in @file{}. It may be added to @code{WFDB_HIGHRES} or @code{WFDB_LOWRES} and given as input to @code{setgvmode()}. The effect of doing so is that missing samples, and samples recognized as invalid, are replaced by @code{getframe()}, @code{getvec()}, and @code{sample()} with the most recently read valid values rather than by the special value @code{WFDB_INVALID_SAMPLE}. This behavior allows applications such as digital filters to remain ignorant of missing data without significant performance penalties. @code{sample()} now checks that its signal number input is valid, and returns @code{WFDB_INVALID_SAMPLE} if not. In previous versions, @code{sample} returned a sample from signal 0 if the requested signal was unavailable. @code{sample_valid()} now returns -1 in the case of a signal that becomes unavailable before the end of the record (previous versions returned 1 in this case). The FIR filter example (@pxref{Example 7}) now works properly. The previous version always began processing the input at sample 0, regardless of start time specified in its argument list. @unnumberedsec WFDB 10.3 @unnumberedsubsec Changes in version 10.3.17 (20 August 2005) This version is the first to support reading variable-layout records (multi-segment records in which the number, arrangement, gains, and baselines of the signals may vary from one segment to the next; @pxref{Multi-Segment Records}). Rounding errors in the WFDB library's @code{mstimstr} function have been reduced. Previous versions did not always round appropriately when the sampling frequency was much less than 1 Hz. The maximum length for a record name (@code{WFDB_MAXRNL}, defined in @file{wfdb.h}) has been increased from 11 to 20. A new constant, @code{WFDB_INVALID_SAMPLE}, is now defined in @file{wfdb.h}. It is used to identify padding inserted to fill in for missing data. When writing in any format that uses fewer than 16 bits per sample, @code{putvec} maps @code{WFDB_INVALID_SAMPLE} to the lowest (most negative) value expressible in that format; when reading a signal file in such a format, @code{getframe} performs the inverse mapping, so that missing data can be identified regardless of the data format. A side effect of this change is that (for example) any samples that had the most negative value (for example, -2048 in a format 212 signal file) are now flagged as invalid. To treat such samples as invalid is reasonable, however, since these occur only when the input level falls below the working range of the analog-to-digital converter. @unnumberedsubsec Changes in version 10.3.16 (13 June 2005) Benjamin Moody has added an interface between the WFDB library and @code{libcurl} as an alternative to the existing @code{libwww} interface, and has updated @file{configure} and @file{conf/*.def} to search for and use @code{libcurl} if it is available. The primary advantages of @code{libcurl} over @code{libwww} are that @code{libcurl} is smaller and faster, it supports access to password-protected files, and it is actively maintained. Both libraries are freely available on all popular platforms. Isaac Henry has updated @code{configure} to support building a native MS-Windows version of the WFDB library using either Cygwin gcc or MinGW gcc. A number of minor changes, mostly involving conditional use of @file{malloc.h}, @file{stdlib.h}, and @file{string.h}, were made to eliminate warnings from gcc 4.x. @unnumberedsubsec Changes in version 10.3.15 (31 January 2005) Rules for generating the binary tarball for MS-Windows have been fixed so that the Cygwin DLLs are now included with correct permissions. Installation of shared libraries under GNU/Linux requires an extra step if SELinux is enabled (as under Fedora Core 2 and later); this has been incorporated into @file{conf/linux-slib.def}. @unnumberedsubsec Changes in version 10.3.14 (29 December 2004) Guido Muesch reported that @code{getspf()} did not always return correct results if the frame frequency does not have an exact representation as a double precision floating point number. This problem has now been corrected. @unnumberedsubsec Changes in version 10.3.13 (5 May 2004) Using an indirect WFDB path (i.e., setting the WFDB environment variable to a value such as @samp{@@FILE}, where @samp{FILE} contains the desired path) was broken in WFDB library versions 10.3.9 through 10.3.12; it now works again, thanks to a patch contributed by Fred Geheb. @unnumberedsubsec Changes in version 10.3.12 (9 March 2004) Okko Willeboordse pointed out an incompatibility between the native MS-Windows API and the ANSI/ISO C library function @code{mkdir}, which is used by the WFDB library. This does not present a problem when compiling the WFDB library using the supported Cygwin/gcc compiler under MS-Windows, nor does any related problem occur on any other platform. It should now be a little easier to compile the WFDB library using unsupported compilers, thanks to a new @code{MKDIR} macro that hides the incompatibility (see @file{lib/wfdblib.h0}). Piotr Wlodarek initiated a discussion about memory leaks in the WFDB library, citing as an example the 'trivial example program in C' from this Guide, which does not free memory it allocates in @code{isigopen()} when reading the signal specifications. This problem can be avoided by invoking @code{wfdbquit()} in the example program, just before exiting. Further discussion of this point has been added to this Guide following the presentation of the 'trivial example', and in the description of @code{wfdbquit}. @unnumberedsubsec Changes in version 10.3.11 (17 October 2003) In @file{signal.c}, several bugs have been identified and fixed. Thanks to Piotr Wlodarek, who found a buffer overrun in @code{isigopen}. Also, @code{isgsettime} sometimes performed incorrect seeks on multifrequency records that had been opened in high-resolution mode; this has been fixed, together with a related bug that caused the value returned by @code{strtim("e")} to be calculated incorrectly in some such cases. @unnumberedsubsec Changes in version 10.3.10 (3 August 2003) In version 10.3.9, the functions @code{setannstr}, @code{setanndesc}, and @code{setecgstr} did not contain necessary checks to avoid invoking @code{strcmp} with a @code{NULL} argument. These checks have been added in version 10.3.10. Thanks to Thomas Heldt for reporting this problem. @unnumberedsubsec Changes in version 10.3.9 (16 July 2003) The WFDB library functions @code{setwfdb}, @code{setannstr}, @code{setanndesc}, and @code{setecgstr} now copy their input string arguments, so that it is no longer necessary for WFDB applications to keep these strings valid. If you have created applications that rely on being able to modify these strings, it will be necessary to invoke the corresponding functions again before such changes will take effect within the WFDB library. Previous versions of the WFDB library function @code{putinfo} did not flush their output until either a new header file was created (via @code{setheader} or @code{newheader}) or the process exited. This has now been corrected, and putinfo output is now flushed before @code{putinfo} returns. Thanks to Jonas Carlson for reporting this problem. @unnumberedsubsec Changes in version 10.3.8 (12 July 2003) The WFDB library function @code{setbasetime()} now properly accepts arguments specifying midnight (e.g., @samp{0:0:0}), which previous versions rejected, and the function @code{setheader()} records such times correctly in the @file{.hea} files it creates. (WFDB library version 10.3.7 was identical to 10.3.6.) @unnumberedsubsec Changes in version 10.3.6 (7 April 2003) The fix applied in @code{isigclose()} in 10.3.5 was incomplete but is now (really!) fixed. Applications that use @code{sample()} should call @code{wfdbquit()} to be certain that @code{sample}'s buffer is freed before exiting. Some long-standing problems in the code (in @file{lib/wfdbio.c}) that handles http range requests for NETFILES-enabled versions of the library have been partially addressed. The underlying issue is that http servers do not always return the range of bytes requested; when this happens, it is not difficult to determine that there is a problem, but it is tricky to figure out what to do about it. Based on experiments with several different http servers, the strategy for handling such problems within the WFDB NETFILES code has been improved substantially, but there may be further room for improvement. @unnumberedsubsec Changes in version 10.3.5 (31 March 2003) Fixed a bug in WFDB library function @code{isigclose()} (in @file{lib/signal.c}) that had caused @code{sample()}'s buffer to be freed inappropriately when switching segments in a multi-segment record. Thanks to Dave Schaffer for the bug report and for a test case that illustrated the bug. (WFDB library versions 10.3.3 and 10.3.4 were identical to 10.3.2.) @unnumberedsubsec Changes in version 10.3.2 (25 February 2003) Fixed a WFDB library bug that caused annotation sorting to fail if a new header file had been written. Thanks to Winton Baker for reporting this problem and for providing an example that illustrated the bug. (WFDB library version 10.3.1 was identical to 10.3.0.) @unnumberedsubsec Changes in version 10.3.0 (26 November 2002) Fixed bugs in @file{lib/signal.c} that caused improper accounting of signal group numbers when reading from two or more records at the same time (as in @file{nst}), a bug that caused a segfault in @file{nst}, and a bug that referenced uninitialized memory in @code{newheader} if @code{nsig} = 0. The WFDB Software Package has been ported to Mac OS X (Darwin), version 10.2 (the port should also work under 10.1 but this has not been tested and will not be supported). It is now possible to generate a shared WFDB library (DLL) under MS-Windows using Cygwin/gcc. Added functions @code{sample} and @code{sample_valid} to the WFDB library (in @file{lib/signal.c}). @code{sample(s, t)} returns the sample at time (sample number) @code{t} from signal @code{s}, handling all necessary buffering internally and allowing the caller to treat the signal file as a virtual array of randomly accessible samples. @code{sample_valid} can be invoked to check if the most recent value returned by @code{sample} was valid (e.g., to see if the end of the input was reached). For an example of the use of these functions, see @file{app/wqrs.c}. @unnumberedsec WFDB 10.2 @unnumberedsubsec Changes in version 10.2.9 (27 October 2002) Fixed a bug in example 9 in this guide (introduced in version 10.2.0). Updated @file{lib/wfdbdll.def} and the @file{Makefile.dos} files in several directories. These have not been tested in recent years and may need further revisions; feedback is welcome. Corrected persistent problems with generating PDF versions of the manuals for the desired page size, and added hyperlinks to the PDF version of this guide. (WFDB library version 10.2.8 was identical to 10.2.7.) @unnumberedsubsec Changes in version 10.2.7 (14 October 2002) Added a workaround to @code{wfdb_fclose} (in @file{lib/wfdbio.c}) so that closing @code{stdin} after using @code{freopen} doesn't trigger a core dump. If out-of-order annotations were written and automatic annotation sorting was suppressed, the warning produced by @code{oannclose} (in @file{lib/annot.c}) once again includes the correct @code{sortann} command needed to put the annotations into order. (This feature was broken by a previous revision.) @unnumberedsubsec Changes in version 10.2.6 (24 June 2002) @findex setifreq (10.2.6) @findex getifreq (10.2.6) @cindex interpolation @cindex resampling The new functions @code{setifreq} and @code{getifreq} allow an application to choose any convenient sampling frequency for reading input signals. Samples read from signal files using @code{getvec} are buffered, resampled, and delivered to the calling application as if the original signals had been sampled at the desired frequency. Times expressed in sample intervals passed to or from other WFDB library functions (@code{getann}, @code{putann}, @code{mstimstr}, @code{timstr}, and @code{strtim}) are rescaled as needed to match intervals corresponding to the chosen frequency. Thanks to Pat Hamilton for the inspiration! The WFDB library now records the base time with millisecond precision (previous versions did so with one-second precision), and @file{xform} provides starting times to the library function @code{setbasetime} with millisecond precision. Thanks to Allavatam Venugopal for providing examples that illustrated the need for these features. Fixed deskewing buffer initialization in @code{getframe}, broken by the 10.2.0 update, which introduced an infinite loop when reading a record that requires skew correction starting at sample 0. Thanks to Andrew Walsh for finding an example that triggered this bug. Fixed rounding errors in @code{adumuv}, @code{muvadu}, and @code{physadu}. Previous versions rounded negative values toward zero; to obtain consistent conversions, however, it is necessary to round all values down (e.g., from -1.5 to -2 rather than up to -1). Fixed a memory leak in @code{wfdb_fclose} (in @file{lib/wfdbio.h}). Thanks to Ion Gazta@~naga. @unnumberedsubsec Changes in version 10.2.5 (10 March 2002) Additions and fixes in @file{wfdbf.c} (the Fortran wrappers for the WFDB library). @unnumberedsubsec Changes in version 10.2.4 (20 December 2001) Code in @file{wfdbio.c} that required the use of the string @code{header} to identify a header file has been revised so that the standard @code{hea} is now usable for this purpose in all cases. @unnumberedsubsec Changes in version 10.2.3 (14 December 2001) Portability fixes in @file{wfdblib.h}. (WFDB library version 10.2.2 was identical to 10.2.1.) @unnumberedsubsec Changes in version 10.2.1 (16 November 2001) @cindex WFDB path Most users will no longer need to set the WFDB path explicitly, as a result of several minor changes in the default path and in the installer for the WFDB Software Package. The environment variable @code{WFDBNOSORT} was replaced by @code{WFDBANNSORT}, and the environment variable @code{WFDBGVMODE} was introduced (@pxref{Annotation Order}, and @pxref{Multi-Frequency Records}, for details). @unnumberedsubsec Changes in version 10.2.0 (15 October 2001) There are no longer any fixed limits on the numbers of signals or annotation files that can be opened simultaneously, or on the number of samples per signal per frame. In previous versions of the WFDB library, the symbols @code{WFDB_MAXSIG}, @code{WFDB_MAXANN}, and @code{WFDB_MAXSPF} (all defined in @file{}) specified limits on these parameters that could be modified only by recompiling the WFDB library. These symbols are still defined for compatibility with older applications that use them (typically to determine the size of static arrays). @cindex WFDB path Since version 10.1.1, record names may include path information (see the notes for version 10.1.1 below), but if such names are used to generate names of WFDB output files, the user has been required to ensure that the target directory exists. This requirement is eliminated in version 10.2.0. If an output file is specified to be located in a non-existent directory, the WFDB library will attempt to create the directory (including, if necessary, any non-existent parent directories). This feature simplifies the use of record names that include directory information, as is common when reading data from a CDROM or a web server such as PhysioNet. For example, using the WFDB path (@file{. http://@-physio@-net.@-org/@-physio@-bank/@-data@-base}), if the current directory, @file{.}, does not contain a subdirectory named @file{mitdb}, the command: @example sqrs -r mitdb/100 @end example @noindent will read its input from @uref{http://@-physio@-net.@-org/@-physio@-bank/@-data@-base/@-mitdb/}, will create a directory named @file{mitdb} within the current directory, and will write its output annotation file (@file{100.qrs}) into this newly-created directory. If we then use the command: @example rdann -r mitdb/100 -a qrs @end example @noindent the header file is still read from the remote directory, but the annotation file is read from @file{./mitdb}. (The programs @file{sqrs} and @file{rdann} are standard applications that use the WFDB library; see the @cite{WFDB Applications Guide} for details.) Also new is the WFDB test suite (located in the @file{checkpkg} directory of the WFDB source tree, at the same level as the @file{lib} directory containing the WFDB library sources). This set of programs can be used to help verify that a newly-installed version of the WFDB library behaves properly. @unnumberedsec WFDB 10.1 @unnumberedsubsec Changes in version 10.1.6 (1 August 2001) The WFDB library requires that the record name specified in the first line of a header file must match the name of the record with which the header file is associated (this is done in order to detect corrupted or erroneously renamed header files). Version 10.1.6 requires that only the final portion of the record name (stripped of any path information) must match. @unnumberedsubsec Changes in version 10.1.5 (11 June 2000) More changes in the @file{make} description files, for Cygwin compatibility. @unnumberedsubsec Changes in version 10.1.4 (6 June 2000) The symbol @code{WFDB_NETFILES} replaces the old @code{NETFILES}. @unnumberedsubsec Changes in version 10.1.3 (26 April 2000) More changes in the @file{make} description files, to support a configuration script. @unnumberedsubsec Changes in version 10.1.2 (11 March 2000) Changes in the @file{make} description files. @unnumberedsubsec Changes in version 10.1.1 (30 January 2000) @cindex WFDB path Record names may contain (absolute or relative) path information as a prefix, and if (as a result) an input file is found in a location that does not appear explicitly in the WFDB path, that location is appended to the end of the WFDB path. For example, if the WFDB path is @file{. http://@-physio@-net.@-org/@-physio@-bank/@-data@-base}, and the record name @file{mitdb/100} is supplied to @code{wfdbinit}, the WFDB library will find the header file at @uref{http://@-physio@-net.@-org/@-physio@-bank/@-data@-base/@-mitdb/@-100.hea}, and will then add @uref{http://@-physio@-net.@-org/@-physio@-bank/data@-base/@-mitdb/} to the end of the WFDB path so that the signal file (specified as @file{100.dat} in the header file) can be found. @unnumberedsubsec Changes in version 10.1.0 (15 January 2000) @cindex NETFILES Version 10.1.0 supports a new signal file format (311), and contains numerous minor changes in the NETFILES support code introduced in 10.0.1. @unnumberedsec WFDB 10.0 @unnumberedsubsec Changes in version 10.0.1 (19 November 1999) @cindex NETFILES @cindex libwww @cindex W3C libwww Beginning with version 10.0.1, the WFDB library supports reading not only local files, but also remote files made available by web (HTTP) or FTP servers. To make use of this feature, link your application with both the WFDB library and the @code{libwww} library (freely available for all versions of Unix, and for most recent versions of MS Windows, from @uref{http://www.w3.org/Library}, or from @uref{http://@-www.physio@-net.org/physio@-tools/libwww/}). (In some cases, notably under GNU/Linux, @code{libwww} is linked together with the dynamically-loaded version of the WFDB library, so that you do not need to link @code{libwww} explicitly.) All access to remote files is read-only. If you do not wish to allow access to remote files, or if @code{libwww} is not available for your OS, simply do not define the symbol NETFILES when compiling the WFDB library. For further details, see @file{wfdbio.c} in the WFDB library sources. @cindex WFDB path The WFDB environment variable may now contain whitespace (space, tab, or newline characters) as path component separators under any OS. Multiple consecutive whitespace characters are treated as a single path component separator. Use a @samp{.} to specify the current directory as a path component when using whitespace as a path component separator. A semicolon (@samp{;}) is also acceptable as a path component separator under any OS. A colon (@samp{:}) is still acceptable as a path component separator under Unix (Linux, etc.), provided only that the colon is not immediately followed by @samp{//}. If the WFDB path includes components of the forms @file{http://somewhere.net/mydata} or @file{ftp://somewhere.else/yourdata}, the sequence @samp{://} is explicitly recognized as part of a URL prefix (under any OS), and the @samp{:} and @samp{/} characters within the @samp{://} are not interpreted further. Note that the MS-DOS @samp{\} is @emph{not} acceptable as an alternative to @samp{/} in a URL prefix. To make WFDB paths containing URL prefixes more easily (human) readable, use whitespace for path component separators. Previous versions of the WFDB library that were compiled for environments other than MS-DOS used file names in the format @var{type.record}. This file name format is no longer supported. @unnumberedsubsec Changes in version 10.0.0 (25 June 1999) Beginning with version 10.0.0, the name of the library is WFDB. All earlier versions were named DB. All library symbols have been similarly renamed, with @code{WFDB} and @code{wfdb} replacing @code{DB} and @code{db} everywhere, in names of library functions, constants, type and structure definitions, library source file names, and names of environment variables (e.g., the DB environment variable is now the WFDB environment variable). Version 10.0.0 of the WFDB library is functionally identical with the final release (version 9.7.4) of the DB library, except for the name changes. It should be possible to recompile existing applications written for DB library version 9.x without modification, and to link them with WFDB library version 10.0.0. This is possible because two sets of @code{#include} files are provided with the WFDB library. The first set, accessible via @code{#include }, works with applications written as described in this guide. The alternate set, accessible via @code{#include }, is compatible with DB 9.x applications as described in previous editions of this guide. @node Concept Index, Function and Macro Index, Recent Changes, Top @unnumbered Concept Index @printindex cp @node Function and Macro Index, Copying, Concept Index, Top @unnumbered Function and Macro Index For a number of entries below, the function name is followed by the version number of the WFDB library in which the function first appeared. Functions for which no such number appears have been present in all numbered versions of the WFDB library. @printindex fn @node Copying, , Function and Macro Index, Top @ifinfo WFDB Programmer's Guide Tenth Edition (revised and with additions for WFDB library version VERSION) LONGDATE George B. Moody Copyright (C) 1980 -- 2011 George B. Moody Permission is granted to make and distribute verbatim copies of this guide provided that the copyright notice and this permission notice are preserved on all copies. @ignore Permission is granted to process this file through TeX and to print the results, provided that the printed document carries a copying permission notice identical to this one except for the removal of this paragraph (this paragraph not being relevant to the printed document). @end ignore Permission is granted to copy and distribute modified versions of this guide under the conditions for verbatim copying and under the conditions that follow in this paragraph. Each copy of the resulting derived work must contain a notice that it is a modified version of this guide. The notice must state which edition of this guide was the source for the derived work, and it must credit the authors of this guide and of the modifications. The entire resulting derived work must be distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this guide into another language, under the above conditions for modified versions. The author would appreciate receiving copies of any modified or translated versions of this guide for reference purposes. @end ifinfo @bye