├── .gitignore ├── Makefile ├── check ├── lw-test ├── lw-test-output └── lw-test-params ├── client ├── css │ ├── images │ │ ├── ui-icons_444444_256x240.png │ │ ├── ui-icons_555555_256x240.png │ │ ├── ui-icons_777620_256x240.png │ │ ├── ui-icons_777777_256x240.png │ │ ├── ui-icons_cc0000_256x240.png │ │ └── ui-icons_ffffff_256x240.png │ ├── jquery-ui.min.css │ └── lightwave.css ├── doc │ ├── COPYING.html │ ├── about.html │ ├── about.txt │ ├── annotation-editing.html │ ├── annotations.html │ ├── browser-compatibility.html │ ├── buglist.html │ ├── client-install.html │ ├── client-query-strings.html │ ├── contacts.html │ ├── edit-log.html │ ├── faq.html │ ├── followed-link.html │ ├── lw-api.html │ ├── pnw.html │ ├── projects.html │ ├── server-install.html │ ├── thanks.html │ ├── to-do.html │ ├── topics.html │ └── what-is-lightwave.html ├── images │ ├── animation.svg │ └── lightwave.svg ├── js │ ├── jquery-ui.min.js │ ├── jquery.cookie.js │ ├── jquery.min.js │ └── lightwave.js └── lightwave.html ├── edit-notes ├── lw-server └── server ├── cgi.c ├── cgi.h ├── download.html ├── lightwave.c ├── lw-apache.conf ├── lw-scribe ├── patchann.c ├── sandbox.c ├── sandbox.h └── setrepos.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.orig 4 | *.rej 5 | 6 | /lightwave 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LWVERSION = 0.72 2 | # file: Makefile G. Moody 18 November 2012 3 | # Last revised: 25 January 2023 (version 0.72) 4 | # 'make' description file for building and installing LightWAVE 5 | # 6 | # *** It is not necessary to install LightWAVE in order to use it! 7 | # *** Point your browser to http://physionet.org/lightwave/ to do so, 8 | # *** or use it to open lightwave.html (in the 'client' directory). 9 | # 10 | # LightWAVE is a lightweight waveform and annotation viewer and editor. 11 | # 12 | # LightWAVE is modelled on WAVE, an X11/XView application I wrote and 13 | # maintained between 1989 and 2012. LightWAVE runs within any modern 14 | # web browser and does not require installation on the user's computer. 15 | # 16 | # This file, and the others in this directory, can be used to install 17 | # LightWAVE on your own web server. You might want to do this if your 18 | # connection to physionet.org is slow or intermittent, if you want to 19 | # use LightWAVE to work with local files, or if you want to participate 20 | # in LightWAVE's development. 21 | # 22 | # Prerequisites for building and using the LightWAVE server: 23 | # httpd (a properly configured web server, such as Apache) 24 | # libwfdb (from http://physionet.org/physiotools/wfdb.shtml) 25 | # libcurl (from http://curl.haxx.se/libcurl/) 26 | # 27 | # In addition, the LightWAVE scribe (a separate server-side CGI application 28 | # that receives edit logs transmitted from the LightWAVE client) requires 29 | # 'perl' and 'cgi.pm' (standard on Linux, available for all platforms from 30 | # http://search.cpan.org/). 31 | # 32 | # To build and install LightWAVE using this Makefile, you will also need 33 | # a few standard POSIX tools including 'gcc' (or another ANSI/ISO compiler) 34 | # 'make', 'cp', 'mkdir', 'mv', 'rm', 'sed', and 'tar' (standard on Linux and 35 | # Mac OS X, components of Cygwin on Windows). 36 | # 37 | # Install the three libraries where the compiler/linker will find them (on 38 | # Linux or MacOS X, /usr/lib is usually the best choice). 39 | # 40 | # If you are using Apache, make sure that the values of DocumentRoot, 41 | # ScriptAlias1, ScriptAlias2, ServerName, and User below match those given in 42 | # your Apache configuration file. 43 | # 44 | # "server/lw-apache.conf" is provided to illustrate settings you might use if 45 | # you have not previously configured Apache; it is meant as a supplement to the 46 | # standard Apache configuration file, which contains many more settings and 47 | # should not be edited unless you know what you are doing. If you decide to use 48 | # "lw-apache.conf", copy it into Apache's conf.d directory, which is used for 49 | # customized configuration modules; typically this directory is 50 | # /etc/httpd/conf.d or /etc/apache2/conf.d, but you may need to hunt around for 51 | # it. 52 | # 53 | # If you make any changes to Apache's configuration, restart Apache and verify 54 | # that it is (still) working before continuing. 55 | # 56 | # Return to this directory and type 'make' to build and install LightWAVE. 57 | # Then type 'make check' to run a basic test of the LightWAVE server. 58 | # 59 | # If you have installed LightWAVE on "myserver.com", start the LightWAVE client 60 | # by pointing your browser to http://myserver.com/lightwave/. If you have 61 | # installed LightWAVE on a standalone computer without a network connection, 62 | # use any of these URLs: 63 | # http://localhost/lightwave/ 64 | # http://127.0.0.1/lightwave/ 65 | # http://0.0.0.0/lightwave/ 66 | 67 | # LW_WFDB is the LightWAVE server's WFDB path, a space-separated list of 68 | # locations (data repositories) where the server will look for requested data. 69 | LW_WFDB = "/usr/local/database http://physionet.org/physiobank/database" 70 | 71 | # DocumentRoot is the web server's top-level directory of (HTML) content. 72 | # The values below and in your Apache configuration file should match. 73 | # Note that it does not end with '/'. 74 | DocumentRoot = /home/physionet/html 75 | 76 | # ServerName is the hostname of the web server, as specified in your Apache 77 | # configuration file. The default setting below attempts to guess your server's 78 | # hostname from the output of the 'hostname' command. Servers often have 79 | # multiple hostnames, however. If the output of 'hostname' does not match the 80 | # value of ServerName in your Apache configuration file, change the value below 81 | # to match the Apache configuration file. 82 | ServerName = `hostname` 83 | 84 | # ScriptAlias1 is the prefix of URLs for server scripts (CGI applications). 85 | # It should match the first argument of the ScriptAlias directive in your 86 | # Apache configuration file. 87 | ScriptAlias1 = /cgi-bin/ 88 | 89 | # ScriptAlias2 is the directory in which server scripts are to be installed. 90 | # It should match the second argument of the ScriptAlias directive in your 91 | # Apache configuration file. 92 | CGIDIR = /home/physionet/cgi-bin/ 93 | 94 | # User is the user who "owns" processes started by the web server. 95 | # It should match the value of User in your Apache configuration file. 96 | User = apache 97 | 98 | # LWCLIENTDIR is the directory for the installed LightWAVE client. 99 | LWCLIENTDIR = $(DocumentRoot)/lightwave 100 | # The client should be installed in a subdirectory of DocumentRoot, and the 101 | # server and scribe should go into CGIDIR. 102 | 103 | # LWCLIENTURL, LWSERVERURL, and LWSCRIBEURL are the URLs of the installed 104 | # LightWAVE client, server, and scribe. 105 | LWCLIENTURL = http://$(ServerName)/lightwave/ 106 | LWSERVERURL = http://$(ServerName)$(ScriptAlias1)lightwave 107 | LWSCRIBEURL = http://$(ServerName)$(ScriptAlias1)lw-scribe 108 | 109 | # LWTMP is a temporary directory for server-side backup of edit logs uploaded 110 | # from LightWAVE clients to the scribe, and annotation files created from the 111 | # edit logs by patchann. 112 | LWTMP = /ptmp/lw 113 | 114 | # Directory for installation of the WFDB software package; patchann is 115 | # installed there, where the scribe expects to find it. 116 | WFDBROOT = /usr/local 117 | 118 | # CC is the default C compiler. 119 | CC = gcc 120 | 121 | # CFLAGS is a set of options for the C compiler. 122 | CFLAGS = -O -DLWDIR=\"$(LWCLIENTDIR)\" -DLWVER=\"$(LWVERSION)\" \ 123 | -DLW_WFDB=\"$(LW_WFDB)\" 124 | 125 | # LDFLAGS is a set of options for the linker. 126 | LDFLAGS = -lwfdb 127 | 128 | # Install both the lightwave server and client on this machine. 129 | install: server scribe client 130 | @echo 131 | @echo "LightWAVE has been installed. If an HTTP server is running on" 132 | @echo "$(ServerName), run LightWAVE by pointing your web browser to" 133 | @echo " $(LWCLIENTURL)" 134 | 135 | # Check that the server is working. 136 | test: 137 | check/lw-test $(CGIDIR) 138 | 139 | # Install the lightwave client. 140 | client: clean FORCE 141 | mkdir -p $(LWCLIENTDIR) 142 | cp -pr client/* $(LWCLIENTDIR) 143 | rm -f $(LWCLIENTDIR)/lightwave.html 144 | baseurl=`echo "$(LWSERVERURL)" | cut -d/ -f1-3`; \ 145 | serverpath=`echo "$(LWSERVERURL)" | cut -d/ -f4-`; \ 146 | scribepath=`echo "$(LWSCRIBEURL)" | cut -d/ -f4-`; \ 147 | sed "s+'https://physionet.org'+'$$baseurl'+" \ 148 | $(LWCLIENTDIR)/js/lightwave.js 152 | sed "s/\[local\]/$(LWVERSION)/" $(LWCLIENTDIR)/index.html 154 | 155 | # Install the LightWAVE server. 156 | server: lightwave 157 | mkdir -p $(CGIDIR) 158 | install -m 755 lightwave $(CGIDIR) 159 | 160 | # Install the sandboxed LightWAVE server. 161 | sandboxed-server: sandboxed-lightwave 162 | mkdir -p $(CGIDIR) 163 | sudo install -m 4755 sandboxed-lightwave $(CGIDIR) 164 | 165 | # Install the LightWAVE scribe. 166 | scribe: patchann scribedir 167 | mkdir -p $(CGIDIR) 168 | sed s+/usr/local+$(WFDBROOT)+ $(CGIDIR)/lw-scribe 170 | chmod 755 $(CGIDIR)/lw-scribe 171 | 172 | # Set up a temporary directory on the server for backups of edit logs, and 173 | # make it writeable by the web server and the processes that it spawns. 174 | scribedir: 175 | [ -d $(LWTMP) ] || sudo mkdir -p $(LWTMP) 176 | sudo chmod 755 $(LWTMP) 177 | sudo cp -p server/download.html $(LWTMP) 178 | sudo chown $(User) $(LWTMP) 179 | 180 | # Compile the lightwave server. 181 | lightwave: server/lightwave.c server/cgi.c server/*.h 182 | $(CC) $(CFLAGS) server/lightwave.c server/cgi.c -o lightwave $(LDFLAGS) 183 | 184 | # Compile the sandboxed lightwave server. 185 | sandboxed-lightwave: server/lightwave.c server/cgi.c server/sandbox.c server/*.h 186 | $(CC) $(CFLAGS) -DSANDBOX -DLW_ROOT=\"$(LW_ROOT)\" \ 187 | server/lightwave.c server/cgi.c server/sandbox.c \ 188 | -o sandboxed-lightwave $(LDFLAGS) -lseccomp -lcap 189 | 190 | # Compile and install patchann. 191 | patchann: server/patchann.c 192 | $(CC) $(CFLAGS) server/patchann.c -o $(WFDBROOT)/bin/patchann $(LDFLAGS) 193 | 194 | # Make a tarball of sources. 195 | tarball: clean 196 | cd ..; tar cfvz lightwave-$(LWVERSION).tar.gz --exclude='.git*' lightwave 197 | 198 | # 'make clean': Remove unneeded files from package. 199 | clean: 200 | rm -f lightwave patchann *~ */*~ */*/*~ 201 | 202 | FORCE: 203 | -------------------------------------------------------------------------------- /check/lw-test: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # Test the LightWAVE server 3 | 4 | cd `dirname $0` 5 | if [ $# == 1 ] 6 | then 7 | LW=$1/lightwave 8 | else 9 | LW=lightwave 10 | fi 11 | 12 | cat >lw-test-params <lw-test-$$ 2>/dev/null 26 | if (diff -q lw-test-$$ lw-test-output) 27 | then 28 | echo The LightWAVE server appears to be running properly in command-line mode. 29 | echo Test it with the LightWAVE client by pointing your web browser to: 30 | echo " http://localhost/lightwave/" 31 | rm lw-test-$$ 32 | else 33 | echo The LightWAVE server is not running properly. 34 | echo The expected test output is in "check/lw-test-output". 35 | echo The output generated by the test is in "check/lw-test-$$". 36 | fi -------------------------------------------------------------------------------- /check/lw-test-output: -------------------------------------------------------------------------------- 1 | { "fetch": 2 | { "signal": 3 | [ 4 | { "name": "MLII", 5 | "units": "mV", 6 | "t0": 0, 7 | "tf": 360, 8 | "gain": 200, 9 | "base": 1024, 10 | "tps": 1, 11 | "scale": 1, 12 | "samp": [ 1094,0,0,0,0,0,0,0,0,-66,-46,-24,-8,7,10,11,3,3,2,-2,0,-2,-4,-4,-1,2,1,5,-4,1,0,5,5,3,-6,-3,-1,4,5,3,-4,-4,-3,2,0,1,2,0,1,1,0,2,5,4,1,-3,-5,-7,0,1,1,4,4,2,4,-2,-2,0,1,6,0,1,2,1,1,-1,2,2,-1,1,3,2,2,1,0,1,5,4,-2,-2,0,2,0,3,0,-2,-3,1,3,2,1,-5,-1,0,-2,0,-2,-1,-4,1,-2,0,-1,-2,-2,-1,-1,-2,-1,1,0,0,1,-1,-1,-2,-2,1,0,2,-2,-1,0,-3,0,-3,-2,-2,-2,3,-2,-1,-2,-3,2,1,1,-1,0,1,-3,0,-4,0,-3,2,-1,1,-1,-2,-1,0,-3,3,3,1,2,1,-2,2,0,1,3,0,-1,2,0,-4,0,-4,0,-1,3,0,1,-1,0,0,2,2,0,-5,-5,-5,2,0,0,-2,-2,-2,-2,-5,-6,-3,2,11,16,14,8,10,11,13,13,7,-8,-18,-40,-54,-57,-41,-35,-33,-28,-14,-8,3,8,12,10,14,23,26,15,4,0,4,5,4,5,8,9,8,11,6,10,8,6,9,6,8,6,10,10,13,9,13,10,5,1,3,5,5,5,1,1,4,0,4,4,1,-1,6,1,4,3,3,0,4,3,4,0,2,0,1,4,-2,-1,1,0,6,3,2,1,0,-2,2,2,5,-1,-1,0,1,-1,2,-3,1,-3,1,1,-2,-1,-5,0,0,-1,-5,-5,-4,-6,0,0,0,-3,-5,-7,-2,-1,-3,-1,1,-3,-6,-4,-8,-5,-5,-3,0,-1,-2,-1,-5,-7,5,1,-2,-1,-4,-4,2,1,-2,-5,-4,2,0,2,-1,-1,-2 ] 13 | }, 14 | { "name": "V1", 15 | "units": "mV", 16 | "t0": 0, 17 | "tf": 360, 18 | "gain": 200, 19 | "base": 1024, 20 | "tps": 1, 21 | "scale": 1, 22 | "samp": [ 1045,0,0,0,0,0,0,0,0,2,-5,-3,4,4,-5,-1,1,-1,-3,1,3,1,-5,-8,2,6,-1,-5,0,1,-1,-5,5,2,2,-4,-2,6,5,-3,-5,1,3,5,-2,-1,0,-5,-1,4,1,3,-2,-6,3,3,-3,-5,1,3,3,-2,2,0,0,-3,-3,2,6,1,-5,-2,3,3,-2,-4,3,-1,3,-4,1,4,-3,-3,-2,5,6,-4,-5,3,1,-2,-1,-1,4,-1,0,1,3,1,-6,-5,4,6,2,-3,0,-1,0,-1,0,3,-2,-3,-1,6,4,-4,-5,-3,3,5,1,-3,2,1,1,-3,-1,1,3,-3,1,0,2,-1,-6,-2,7,3,-1,-3,2,-1,0,-1,-1,3,3,-7,1,1,5,-2,-4,-6,4,7,-2,-3,1,0,-1,-3,-1,6,2,-5,-1,5,3,2,-8,-1,4,4,-1,-3,-1,1,-1,-1,-4,6,0,-6,1,0,5,-1,-8,-4,3,6,-3,-5,-2,0,2,-3,-6,3,3,-3,-5,-1,2,0,-7,-5,2,6,-3,-2,-6,3,3,-1,-3,4,3,-2,-4,0,10,2,-1,-5,9,7,3,0,-4,1,4,-2,-2,-3,5,0,-3,-1,3,4,-3,-6,1,5,6,0,-5,2,5,2,0,-2,2,2,1,-1,0,6,0,-7,-1,0,9,1,-5,-3,3,7,-3,-4,-1,3,5,-3,-2,5,-2,-3,-4,2,8,2,-7,-2,5,3,-1,-6,0,1,5,-3,-2,3,-1,-3,-5,6,6,-2,-4,-5,7,2,-2,-4,3,-2,1,-4,3,3,-4,-6,2,2,5,-1,-6,-1,4,3,-3,-4,2,3,-2,-2,2,3,-1,-7,-1,5,4,-5,-3,3,1,-1,-2,0,5,-3,-3,4,3,-1,-5,-3,4,2,-1,-2,1,-1 ] 23 | } 24 | ], 25 | "annotator": 26 | [ 27 | { "name": "atr", 28 | "annotation": 29 | [ 30 | { "t": 67, 31 | "a": "+", 32 | "s": 0, 33 | "c": 0, 34 | "n": 0, 35 | "x": "(B" 36 | }, 37 | { "t": 225, 38 | "a": "V", 39 | "s": 1, 40 | "c": 0, 41 | "n": 0, 42 | "x": null 43 | } 44 | ] 45 | } 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /check/lw-test-params: -------------------------------------------------------------------------------- 1 | fetch 2 | mitdb 3 | 200 4 | 0 5 | 1 6 | 7 | atr 8 | 9 | 0 10 | 1 11 | -------------------------------------------------------------------------------- /client/css/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bemoody/lightwave/86c858f9cfb667255a377e6c878da41fab84e490/client/css/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /client/css/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bemoody/lightwave/86c858f9cfb667255a377e6c878da41fab84e490/client/css/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /client/css/images/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bemoody/lightwave/86c858f9cfb667255a377e6c878da41fab84e490/client/css/images/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /client/css/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bemoody/lightwave/86c858f9cfb667255a377e6c878da41fab84e490/client/css/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /client/css/images/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bemoody/lightwave/86c858f9cfb667255a377e6c878da41fab84e490/client/css/images/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /client/css/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bemoody/lightwave/86c858f9cfb667255a377e6c878da41fab84e490/client/css/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /client/css/lightwave.css: -------------------------------------------------------------------------------- 1 | /* file: lightwave.css G. Moody 18 November 2012 2 | Last revised: 23 April 2019 version 0.68 3 | */ 4 | 5 | #top { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | color: #444; font-size: 100%; background: #fff; margin: 0 10px; } 7 | table, tbody, thead, tfoot { 8 | border: none; 9 | padding: 0px; 10 | margin: 0px; 11 | } 12 | th, td { 13 | padding: 0.25em; 14 | } 15 | .container { border:2px solid #ccc; width:90%; height: 100px; 16 | overflow-y: scroll; } 17 | .dtable { table-layout: fixed; } 18 | div.notice { width: 90%; margin: auto; padding: 2em; background: #cdf; } 19 | #tabs { 20 | padding: 0px; 21 | background: none; 22 | border-width: 0px; 23 | } 24 | #tabs .ui-tabs-nav { 25 | padding-left: 0px; 26 | background: url('../images/animation.svg'), 27 | url('../images/lightwave.svg'); 28 | background-position: top right; 29 | background-repeat: no-repeat; 30 | border-width: 0px 0px 1px 0px; 31 | -moz-border-radius: 0px; 32 | -webkit-border-radius: 0px; 33 | border-radius: 0px; 34 | } 35 | #tabs .ui-tabs-panel { 36 | background: #fff; 37 | border-width: 0px 1px 1px 1px; 38 | } 39 | .ui-slider { width: 10em; height: .2em; margin-top: .3em; } 40 | .ui-slider-handle { width: .7em !important; height: .55em !important; 41 | margin-top: .1em; } 42 | -------------------------------------------------------------------------------- /client/doc/COPYING.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | License information 7 | 8 | 9 | 10 |

License information

11 | 12 |

LightWAVE is free software; you can redistribute it and/or modify 13 | it under the terms of the GNU General Public 15 | License as published by the Free Software Foundation; either 16 | version 2 of the License, or (at your option) any later version. 17 | 18 |

19 | LightWAVE is distributed in the hope that it will be useful, but 20 | WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more details. 23 | 24 |

25 | You should have received a copy of the GNU General Public License along with 26 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 27 | Place - Suite 330, Boston, MA 02111-1307, USA. 28 | 29 |

30 | You may contact the author by e-mail (george@mit.edu) or postal mail 31 | (MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software, 32 | please visit PhysioNet (http://physionet.org/). 33 | 34 | -------------------------------------------------------------------------------- /client/doc/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | About LightWAVE 7 | 8 | 9 | 10 |

About LightWAVE

11 | 12 |

LightWAVE is a lightweight waveform and annotation viewer and editor. 13 | Use it to view any of the recordings of physiologic signals and time series 14 | in PhysioBank, together 15 | with their annotations (event markers). 16 | 17 |

LightWAVE runs within any modern web 18 | browser and does not require installation on the user's computer, tablet, or 19 | phone. LightWAVE is free software licensed under 20 | the GPL. 21 | 22 |

Getting started

23 | 24 |
    25 |
  1. On the Choose input tab, select one of PhysioBank's databases. 26 | (Learn more about them in PhysioBank's 27 | index.) 28 | 29 |
  2. Once you have chosen a database, a Record selector appears. Choose 30 | a record. 31 | 32 |
  3. Once you have chosen a record, LightWAVE's View/edit panel opens, 33 | showing the first 10 seconds of the record. 34 |
35 | 36 |

Controls:

37 | 38 |

To find out how any LightWAVE control works, move the pointer over it, 39 | and a tooltip with a brief description will appear after a short 40 | interval. Click (tap) a tab (Choose input, View/edit, etc.) to open it. 41 | 42 |

43 |
Choose input
44 |
Return here to select a different record.
45 | 46 |
View/edit
47 |
This panel contains navigation controls and indicators along its top edge. 48 | Click the button to start or pause autoplay. 49 | Below, the signal window shows an excerpt of the record named in the 50 | upper right corner. The time indicators along the lower edge of the signal 51 | window indicate the elapsed time from the beginning of the record, in hours, 52 | minutes, and seconds. The signal window is 10 seconds wide initially; use the 53 | slider below it at right to change its width. 54 | 55 |

56 | Use the navigation controls to view any other location in the record. For 57 | example, enter a time in the box next to Go to:, then press [Enter] or 58 | click (tap) the (refresh signal window) button. 59 | 60 |

Use the button to specify a search target (a type 61 | of annotation to be found), then use the 62 | and buttons to find the previous or next 63 | occurrence of the target. Clicking on opens a dialog 64 | box. Enter an annotation mnemonic, such as N, V, or A (hover over Search 65 | for: to see a tooltip that describes more options), and choose an 66 | annotation set to search if more than one set is available. 67 | 68 |

While autoplay is paused, click on any of the labels on the left side of the 69 | signal window to cycle among three display states (hidden, highlighted, and 70 | normal). At most one signal and one annotation set may be highlighted at a 71 | time. Change the scale of a highlighted signal using 72 | the (expand), (restore to default), 73 | and (shrink) buttons. Click on the red circle to hide 74 | or restore the grid, or the blue circle to hide or restore the annotation 75 | markers. 76 |

77 | 78 |
Tables
79 |
View details about the record, including text representations of the 80 | annotations and digitized signals.
81 | 82 |
Settings
83 |
Use the controls to modify LightWAVE's behavior. Your changes persist until 84 | LightWAVE is reloaded.
85 | 86 |
Help
87 |
The button shows this page. Follow the 88 | links in to learn more.
89 |
90 | 91 | -------------------------------------------------------------------------------- /client/doc/about.txt: -------------------------------------------------------------------------------- 1 | About LightWAVE 2 | 3 | A live version of LightWAVE is available for experimentation on PhysioNet at 4 | http://physionet.org/lightwave/ 5 | At that URL you can interact with the LightWAVE server via the LightWAVE client. 6 | 7 | You may also interact with the LightWAVE server directly by adding appropriate 8 | query strings to its URL (http://physionet.org/cgi-bin/lightwave), as 9 | described in "The LightWAVE Server Protocol", at 10 | http://physionet.org/lightwave/doc/lw-api.html 11 | 12 | More information about LightWAVE is available at 13 | http://physionet.org/lightwave/doc/about.html 14 | -------------------------------------------------------------------------------- /client/doc/annotation-editing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Editing annotations with LightWAVE 7 | 8 | 9 | 10 |

Editing annotations with LightWAVE

11 | 12 |

13 | LightWAVE allows you to create new sets of annotations, either from scratch or 14 | by copying and modifying an existing set of annotations. 15 | 16 |

17 | Simplifying the process of contributing annotations to PhysioBank and to 18 | PhysioNetWorks projects is a major motivation for the development of LightWAVE. 19 | We welcome contributions of corrected and supplementary annotations for 20 | PhysioBank's data collections (databases). We review all such contributions 21 | before posting them, with acknowledgement to their contributors. Similarly, 22 | many owners of PhysioNetWorks projects encourage such contributions to their 23 | projects. 24 | 25 |

26 |

Cookies, local storage, and backups

27 |

28 | Unless you enable editing, LightWAVE does not save any information on your 29 | computer. 30 | 31 |

32 | If you edit annotations, LightWAVE saves information on your computer in order 33 | to retain your annotation edits across sessions. For each annotation file that 34 | you have edited, your edits are saved (until you delete them manually) in an 35 | HTML5 local storage string with a key of the form 36 | LightWAVE-editlog|record|annotator. HTML5 37 | local storage is not intended to be readable by any software other than the 38 | web browser that created it; the difficulty of doing so depends on the 39 | browser's implementation of HTML5 local storage. 40 | 41 |

42 | If you use the button, the LightWAVE scribe 43 | copies the edits for the currently open annotation file from local storage to 44 | the LightWAVE host, and it sets a cookie (LWURL) that specifies the URL 45 | on the LightWAVE host where your edits have been backed up. If you use the 46 | public PhysioNet-hosted LightWAVE scribe, your edits are backed up to an 47 | unpublished private URL; although the URL is difficult to guess, this storage is 48 | not secure. If you use the PhysioNetWorks-hosted LightWAVE scribe, your edits 49 | are backed up to your personal workspace on PhysioNetWorks. 50 |

51 | 52 |

53 | Before starting to edit, launch LightWAVE from the server on which you will 54 | save your edits. 55 | 56 |

57 | Edit annotations using the 58 | PhysioNetWorks-hosted 59 | LightWAVE client if you wish to back up your edits securely in your 60 | PhysioNetWorks workspace (and share them if you wish). Use this version of 61 | LightWAVE to edit your own previously uploaded PhysioBank-compatible records. 62 | (If you don't have an account, you can 63 | create one in a minute or two -- it's 64 | free.) 65 | 66 |

67 | If you need to use a custom LightWAVE client, upload it to your PhysioNetWorks 68 | workspace and launch it from there. PhysioNetWorks projects can also host 69 | custom LightWAVE clients for the use of project members. 70 | 71 |

72 | Alternatively, you can edit existing PhysioBank records anonymously by 73 | launching PhysioNet's public LightWAVE 74 | client. Using this service, you can save your edits temporarily on 75 | PhysioNet for up to 10 days, and download them to your computer for 76 | permanent storage at your convenience. 77 | 78 |

79 | Note that there is currently no support for saving annotations created using 80 | local copies of LightWAVE (copies launched from file:// URLs). If you install 81 | your own LightWAVE server and scribe, you will be able to save edits made using 82 | LightWAVE clients launched from your web server. 83 | 84 |

Enabling editing

85 | 86 |

87 | To avoid unintended edits, annotation editing is disabled by default. To enable 88 | editing: 89 | 90 |

    91 |
  1. From the Choose input tab, select a database and a record. 92 |
  2. Open the Settings tab. 93 |
  3. [Optional] If you wish to create a new set of annotations from scratch rather than 94 | editing an existing set, click . 95 | (This is unnecessary if the open record is unannotated.) 96 |
  4. Choose if you use a mouse or trackball, 97 | or if you use a touch device (iPad or other 98 | tablet, or smart phone). The View/Edit tab opens automatically. 99 |
100 | 101 |

Choosing a set of annotations to edit

102 | 103 |

104 | The names of the available annotation sets appear in blue to the left of the 105 | signal window, and the set that is currently open for editing is highlighted. 106 | If there are two or more sets, the set nearest the top is open initially; this 107 | will be the new set if there is one. To choose a different set, click on or 108 | next to its name at the left of the signal window, until it is highlighted. 109 | (Clicking on the name repeatedly cycles between normal, highlighted, and hidden 110 | modes.) 111 | 112 |

113 | If you reopen a record that you edited in a previous session using the same 114 | computer and browser, your previous edits will be loaded. The current version 115 | of LightWAVE does not include a method for disabling this feature. You can view 116 | the original annotations (without your edits) by using a different browser. 117 | You can delete your edits by using your browser's controls for clearing local 118 | storage, but be certain that you wish to clear all local storage 119 | associated with the LightWAVE host before using such a feature. Another way 120 | to do this is to use (see below) to delete your changes 121 | one at a time. 122 | 123 |

124 | During your editing session, you may switch between annotation sets as often as 125 | you wish. Whenever you do so, the edits for the previously open annotation set 126 | are saved in the browser's local storage automatically. 127 | 128 |

Editing controls

129 | 130 |

131 | When editing is enabled, two clusters of editing controls appear on the 132 | View/edit tab, centered below the signal window: 133 | 134 | 135 | 136 | 163 | 179 | 180 |
137 | 140 | 161 |
141 | 144 | 146 | 147 | 150 | 152 | 153 | 155 | 156 | 157 | 158 | 159 | 160 |
162 |
164 |
165 | 167 | 169 | 171 |
173 | 175 | 176 | 177 |
178 |
181 | 182 |

The group of buttons at the left is the annotation palette, used to 183 | choose which type of annotation to insert. Its contents will vary depending on 184 | the types of annotations in the currently open annotation set. In the example 185 | above, the V annotation has been highlighted (selected). 186 | 187 |

The cluster at the right includes buttons for selecting annotations 188 | ( and ), for fine control of the 189 | edit marker position ( and ), 190 | for marking a location or a selected annotation (), and for 191 | reversing the effect of the last mark () or restoring it 192 | (). 193 | 194 |

The edit marker bar

195 | 196 |

When editing is enabled, the edit marker bar is usually visible 197 | in the signal window. The green bar runs vertically across the window, and open 198 | triangles appear at its ends. Above the signal window at the right, below the 199 | name of the currently open record, the time interval from the beginning of the 200 | record to the position of the bar appears in HH:MM:SS format. 201 | 202 |

The edit marker bar generally indicates the location where an edit will be 203 | made. 204 | 205 |

Types of edits

206 | 207 |

208 | There are four basic types of edits, each of which requires actions described 209 | below: 210 | 211 |

212 |
Inserting a new annotation
213 |
214 |
    215 |
  1. Select an annotation type from the palette. 216 |
  2. Move the edit marker bar to the desired location. 217 |
  3. Mark the location. 218 |
219 | Repeat steps 2 and 3 to insert additional annotations of the same type. 220 |

221 |

222 | 223 |
Modifying an existing annotation without repositioning it
224 |
225 |
    226 |
  1. Select an annotation type from the palette. 227 |
  2. Select the annotation to be modified. 228 |
  3. Mark the annotation. 229 |
230 | Repeat steps 2 and 3 to modify additional annotations in the same way. 231 |

232 |

233 | 234 |
Deleting an existing annotation
235 |
236 |
    237 |
  1. Select from the palette. 238 | (It changes to 239 | when selected.) 240 |
  2. Select the annotation to be deleted. 241 |
  3. Mark the annotation. 242 |
243 | Repeat steps 2 and 3 to delete additional annotations. 244 |

245 |

246 | 247 |
Repositioning an existing annotation
248 |
249 |
    250 |
  1. Select the annotation to be moved. 251 |
  2. Move the edit marker bar to the desired location. 252 |
  3. Mark the location. 253 |
254 |
255 |
256 | 257 |

Selecting an annotation type

258 | 259 |

260 | In the annotation palette, click or touch the annotation type you want to 261 | highlight it. 262 | 263 |

264 |

Expanding the annotation palette

265 | 266 | If the annotation type you want is not already in the palette, there 267 | are two ways to add it: 268 | 269 |
    270 |
  • Click or touch to 271 | open an annotation template. Fill in the template to add a new type to the 272 | palette. 273 | 274 |
  • Select an existing annotation in the signal window, then click or touch 275 | to copy it to the palette. 276 |
277 | 278 | When you add a new annotation type to the palette, it is selected immediately. 279 | If you try to add an annotation type that is already in the palette, it is 280 | selected, but no new button is added to the palette. Unused additions may be 281 | replaced by newer additions. 282 |
283 | 284 |
285 |

Annotation attributes

286 | 287 |

Annotations have five attributes that can be set in an annotation template: 288 | 289 |

290 |
Annotation type
291 |
292 | For existing annotations, the type is almost always a single character 293 | displayed in the signal window at the location of the annotation. (Exception: 294 | type N annotations are displayed as ; this reduces visual 295 | clutter in the common case of annotating cardiac activity, when N marks 296 | a normal beat.) 297 |
298 | 299 |
Aux string
300 |
301 | This field is usually empty, but it can be a character string providing 302 | details related to the type. For example, if type is 303 | + (indicating a change in cardiac rhythm), the aux string 304 | describes the new rhythm; if type is " (double quote 305 | character, indicating a note annotation), then aux is the content 306 | of the note. Avoid long aux strings; long comments can be 307 | continued across several annotations, incrementing num (below) 308 | to define the order. 309 |
310 | 311 |
Subtype
312 |
313 | An integer between -128 and 127 inclusive, usually empty or 0. 314 |
315 | 316 |
Chan
317 |
318 | An integer between 0 and 255 inclusive, usually empty or 0. 319 |
320 | 321 |
Num
322 |
323 | An integer between -128 and 127 inclusive, usually empty or 0. 324 |
325 |
326 | 327 |

328 | Although a standard 329 | set of annotation types and aux strings is shared among 330 | many projects, the subtype, chan, and num attributes 331 | have no standard meanings. Moreover, database creators may assign non-standard 332 | types and aux strings, particularly if the signals are 333 | not electrocardiograms. If you wish to contribute annotations to an existing 334 | database, please take care to familiarize yourself with its annotation 335 | conventions (which are usually outlined on the database's home page). 336 |

337 | 338 |

Moving the edit marker bar

339 | 340 |

341 | Using the mouse interface, move the pointer anywhere in the signal window; the 342 | edit marker will follow the pointer. 343 | 344 |

345 | Using the touch interface, touch near the desired location in the signal window; 346 | the edit marker appears at the last position touched. Repeat if necessary, then 347 | use and to make fine 348 | adjustments to the position of the bar. The position increment increases 349 | gradually if you use either of these buttons repeatedly; switching to the other 350 | one reverses direction and resets the position increment to its minimum value. 351 | 352 |

353 | If the marker does not move smoothly, you may have a more satisfactory 354 | experience using a different web browser. Note your network connection does 355 | not affect the speed of display updates, since all editing operations are 356 | performed by the browser, without communicating with the server. 357 | 358 |

359 | Using either interface, use and 360 | to move rapidly from one annotation to the next. (If 361 | the next annotation is off-screen, the signal window is redrawn, centered on the 362 | next annotation.) If there are no more annotations in the chosen direction, the 363 | edit marker appears at the expected position of the next annotation (determined 364 | by extrapolation). 365 | 366 |

Marking a location (inserting an annotation)

367 | 368 |

369 | Using the mouse interface, simply click once the marker bar is in the desired 370 | location to insert an annotation of the type chosen from the annotation palette. 371 | Keep the pointer well above or below any existing annotation label to avoid 372 | selecting and replacing the existing annotation rather than inserting a new one. 373 | 374 |

375 | Using the touch interface, touch once the marker bar is in 376 | the desired location. 377 | 378 |

Selecting an annotation

379 | 380 |

381 | Use and to select an 382 | annotation. A selection rectangle appears surrounding the label 383 | of the selected annotation. 384 | 385 |

Marking an annotation

386 | 387 |

388 | Use to change the selected annotation to the palette 389 | choice (or to delete it, if the palette choice was ). 391 | 392 | 393 |

Mouse shortcut: Selecting and marking an annotation

394 | 395 |

396 | If you have enabled mouse shortcuts on the Settings tab, press and hold 397 | the mouse button while the pointer is over an annotation label. The selection 398 | rectangle will appear around the annotation. Release the mouse button with the 399 | pointer inside the selection rectangle to change or delete the 400 | annotation. 401 | 402 |

Moving an annotation

403 | 404 |

405 | After selecting the annotation to be moved, use 406 | and to move the edit marker bar to the desired 407 | location, then touch or click to move the annotation. 408 | 409 |

Mouse shortcut: Selecting and moving an annotation

410 | 411 |

412 | If you have enabled mouse shortcuts on the Settings tab, press and hold 413 | the mouse button while the pointer is over an annotation label. The selection 414 | rectangle will appear around the annotation. Release the mouse button with the 415 | pointer outside the selection rectangle ("click and drag"). 416 | 417 |

418 | To move the annotation by less than the distance to the edge of the selection 419 | rectangle, simply move the pointer above or below the rectangle. 420 | 421 |

Correcting editing errors

422 | 423 |

424 | Editing errors are probably inevitable, but they are easy to correct. 425 | Use to restore the state before the most recent 426 | edit. You may use repeatedly to undo as many 427 | edits as you wish. If the edit log is visible, you will see that each 428 | edit is recorded as one line of the log; undone edits appear in red. 429 | 430 |

431 | If you undo an edit that you intended to keep, use 432 | to restore it. You may use repeatedly until there 433 | are no more undone edits. 434 | 435 |

Saving your edits

436 | 437 |

438 | If it is not already open, load the record you edited, and select the annotation 439 | set you wish to save (click on its name in the View tab to highlight 440 | it). On the Settings tab, click on . 441 | 442 |

443 | When you do this, your edits are transmitted to the LightWAVE scribe (a process 444 | that runs side-by-side with the LightWAVE server). The scribe merges your edits 445 | with the original version of the annotation set you have edited (if any) and 446 | writes the results to a PhysioBank-compatible (binary) annotation file on the 447 | LightWAVE server's host file system. 448 | 449 |

450 | If you have used PhysioNet's public LightWAVE client, a status message 451 | appears within a few seconds below the , like 452 | this: 453 | 454 |

455 | Edits for record ahadb/0201, annotator atr backed up successfully. 456 |
457 | Download (opens in another browser tab or window) 458 |
459 | 460 |

461 | Once your edits have been saved on the LightWAVE server, you may download the 462 | updated annotation file and save a copy of it on your computer. Click on the 463 | download link to access the edit logs and PhysioBank-compatible annotation files 464 | that you have backed up. 465 | 466 |

467 | If you have used the PhysioNetWorks-hosted LightWAVE client, your saved 468 | annotations are written to your workspace, where they are accessible only to 469 | you. For example, if your PhysioNetWorks user name is 470 | harry@gryffindor.hogwarts.ac.uk, and you have saved an edited set 471 | of fqrs annotations for record challenge/2013/set-a/a03, they 472 | will be accessible to you (only) at 473 | https://physionet.org/users/harry@gryffindor.hogwarts.ac.uk/works/database/challenge/2013/set-a/a03.fqrs. 474 | The LightWAVE scribe creates any missing directories as needed so that you will 475 | be able to access your uploaded annotations using LightWAVE for PhysioNetWorks, 476 | or using any other WFDB applications if you have authenticated yourself 477 | using pnwlogin (included in WFDB 10.5.14, August 2012, and later 478 | versions). 479 | 480 |

481 | To share a saved annotation file with others, copy it into a location 482 | accessible to those with whom you wish to share it. 483 | 484 | 485 | -------------------------------------------------------------------------------- /client/doc/annotations.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Standard annotations 7 | 8 | 9 | 10 |

Standard annotation types

11 | 12 |

13 | Most PhysioBank databases include one or more sets of annotations for 14 | each recording. Annotations are labels that point to specific locations within 15 | a recording and describe events at those locations. For example, many of the 16 | recordings that contain ECG signals have annotations that indicate the times 17 | of occurrence and types of each individual heart beat ("beat-by-beat 18 | annotations"). 19 | 20 |

21 | LightWAVE displays annotations on the View/edit tab in the signal window, 22 | and on the Tables tab if you check View annotations as text. 23 | Annotations can also be examined using many other programs available on 24 | PhysioNet. These programs display annotations using a common set of codes 25 | (mnemonics). Many of these programs, including LightWAVE, accept these codes 26 | as user input (for example, to select specific annotation types for analysis). 27 | 28 |

29 | The standard set of annotation codes was originally defined for ECGs, and 30 | includes both beat annotations and non-beat annotations. Most PhysioBank 31 | databases use these codes as described below. Refer to the documentation for 32 | each database to confirm if the annotation codes have their standard 33 | meanings. 34 | 35 |

36 | Beat annotations: 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
CodeDescription
NNormal beat (displayed as 40 | "·" by the PhysioBank ATM, LightWAVE, pschart, and psfd)
LLeft bundle branch block beat
RRight bundle branch block beat
BBundle branch block beat (unspecified)
AAtrial premature beat
aAberrated atrial premature beat
JNodal (junctional) premature beat
SSupraventricular premature or ectopic beat (atrial or nodal)
VPremature ventricular contraction
rR-on-T premature ventricular contraction
FFusion of ventricular and normal beat
eAtrial escape beat
jNodal (junctional) escape beat
nSupraventricular escape beat (atrial or nodal)
EVentricular escape beat
/Paced beat
fFusion of paced and normal beat
QUnclassifiable beat
?Beat not classified during learning
60 | 61 |

62 | Non-beat annotations: 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 79 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
CodeDescription
[Start of ventricular flutter/fibrillation
!Ventricular flutter wave
]End of ventricular flutter/fibrillation
xNon-conducted P-wave (blocked APC)
(Waveform onset
)Waveform end
pPeak of P-wave
tPeak of T-wave
uPeak of U-wave
`PQ junction
'J-point
^(Non-captured) pacemaker artifact
|Isolated QRS-like artifact 78 | [1]
~Change in signal quality 80 | [1]
+Rhythm change [2]
sST segment change [2]
TT-wave change [2]
*Systole
DDiastole
=Measurement annotation [2]
"Comment annotation [2]
@Link to external data [3]
90 | 91 |

Annotation attributes: Each instance of an annotation may have up 92 | to six attributes: 93 |

    94 |
  • time: the time within the recording (recorded in the annotation 95 | file as the sample number of the sample to which the annotation 96 | "points") 97 |
  • anntyp [sic]: a numeric annotation code (see 98 | ecgcodes.h for 99 | definitions, and see the definitions of astring and tstring in 100 | annot.c 101 | for the mappings between these numeric codes and the mnemonic codes and 102 | descriptions given above) 103 |
  • subtyp [sic], chan, num: three small integers 104 | (chan is between 0 and 255, and the others are between -128 and 127) 105 | that specify context-dependent attributes (see the documentation for each 106 | database for details) 107 |
  • aux: a free text string [2], up to 255 108 | characters long 109 |
110 | 111 |

rdann (text) format for annotations: If you 112 | check View annotations as text on the Tables tab, LightWAVE 113 | presents each annotation's attributes in one line, in left-to-right order, 114 | although aux is omitted if (as is most often true) it is empty. 115 | The time attribute appears twice (in the first column, converted to 116 | hours, minutes, seconds and milliseconds; and in the second column, as the 117 | sample number). This text format is called rdann format because it is 118 | also produced by 119 | rdann 120 | and accepted as input by 121 | wrann, 122 | two stand-alone applications from the 123 | WFDB Software Package. 124 | 125 |

PhysioBank-compatible annotation format: Within PhysioBank, 126 | annotations are stored in a compact binary format that requires only 2 bytes for 127 | most annotations. Details on this binary format are available 128 | here. This 129 | format can be read by C, C++, and Fortran applications using getann, 130 | and they can be written using putann, functions defined within the 131 | WFDB library. Matlab 132 | and Octave programs can read and write PhysioBank-compatible annotation files 133 | using any of these 134 | solutions. Alternatively, such files can be read and written by scripting 135 | language applications using rdann and wrann. The LightWAVE 136 | server reads PhysioBank-compatible annotation files and translates them to JSON 137 | for transmission to the LightWAVE client (see The 138 | LightWAVE Server Protocol). Annotations created using LightWAVE are 139 | collected by LightWAVE's scribe and stored in PhysioBank-compatible 140 | binary format. 141 | 142 |

143 | Footnotes: 144 | 145 |

    146 | 147 |
  1. 148 | In artifact and signal quality change annotations, each non-zero bit from the 149 | four least significant bits in the subtyp attribute of the annotation 150 | indicates that the corresponding signal contains noise (the least significant 151 | bit corresponds to signal 0, the uppermost in the signal window). The four high 152 | bits, if non-zero, indicate that the corresponding signals are unreadable 153 | (because of very high noise amplitude, very low signal amplitude, loss of 154 | signal, or some combination of these). These annotations, where they exist, 155 | reflect the expert annotator's subjective judgements only. In rdann 156 | text format, the subtyp field is the fourth column of output, to the 157 | right of the annotation code. [When displaying signal quality change 158 | annotations, the ATM, pschart, 159 | psfd, and WAVE do not show the annotation code (~) itself; 160 | rather, they display a string with one character (c for clean, 161 | n for noisy, or u for unreadable) for each signal 162 | (beginning with signal 0); this feature is not implemented in LightWAVE.] 163 | 164 | 165 |
  2. 166 | In rhythm, ST segment, and T-wave change annotations, and in measurement and 167 | comment annotations, the aux field contains an ASCII string (with 168 | prefixed byte count) describing the rhythm, ST segment, T-wave change, 169 | measurement, or the nature of the comment. By convention, the character that 170 | follows the byte count in the aux field of a + annotation is 171 | "(". The most commonly used aux rhythm labels are: 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 |
    StringDescription
    (ABAtrial bigeminy
    (AFIBAtrial fibrillation
    (AFLAtrial flutter
    (BVentricular bigeminy
    (BII2° heart block
    (IVRIdioventricular rhythm
    (NNormal sinus rhythm
    (NODNodal (A-V junctional) rhythm
    (PPaced rhythm
    (PREXPre-excitation (WPW)
    (SBRSinus bradycardia
    (SVTASupraventricular tachyarrhythmia
    (TVentricular trigeminy
    (VFLVentricular flutter
    (VTVentricular tachycardia
    191 | 192 | In a few cases, other aux rhythm labels have been used; see the 193 | documentation for the associated database for further information in such cases. 194 | In rdann (text) format, the aux string appears at the end of 195 | the line (following the sixth column of output) for any annotation that has a 196 | non-empty aux field; the prefixed byte count is not shown explicitly. 197 | In LightWAVE's signal window, and in similar plots made by other PhysioToolkit 198 | applications, if aux is present, it appears in place of the annotation 199 | code; by convention, aux rhythm labels appear below beat labels, and 200 | other aux strings appear above beat labels. When waveforms are shown 201 | at condensed scales such that aux strings might overlap, some of these 202 | programs automatically or optionally produce abbreviated versions of 203 | the aux strings in order to keep the display readable; refer to the 204 | documentation for each program for further details [LightWAVE does not do this]. 205 | 206 |
  3. The aux field of a link annotation contains a 207 | URL (a uniform resource locator, in the form 208 | http://machine.name/some/data). Link annotations may be used to 209 | associate extended text, images, or other data with an annotation file. If the 210 | aux field contains any whitespace, text following the first whitespace 211 | is descriptive (not part of the URL).
212 | 213 | 214 | -------------------------------------------------------------------------------- /client/doc/browser-compatibility.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | About LightWAVE 7 | 8 | 9 | 10 | 11 |

Browsers compatible with LightWAVE

12 | 13 |

[Last checked: 25 February 2013 (LightWAVE version 0.48)] 14 | 15 |

The LightWAVE client runs in a web browser and does not require any 16 | platform-specific components. The browser must support HTML5, JavaScript, and 17 | (for graphical output only) SVG. Recommended: Chrome, MSIE 9, and 18 | Safari currently provide the best performance because of their notably fast 19 | JavaScript engines. On older Windows systems, MSIE 7 or 8 (or even 6) 20 | with Google Chrome Frame are 21 | tolerable choices if you cannot install Chrome itself. On devices running 22 | Android 2.x and earlier, you will not have the Android 3 browser, and you may 23 | be unable to install Chrome 18; both Firefox 15 and Opera 12 are usable with 24 | limitations on Android 2.x, however, if you cannot update your Android 25 | platform. 26 | 27 |

The table below shows the oldest LightWAVE-compatible version of each 28 | browser. 29 | 30 |

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
Platform ▶
Browser
AndroidiOSLinuxMac OS XWindows
Android3n/an/an/an/a
Chrome1823777
Firefox15n/a444
MSIEn/an/an/an/a9 [1]
Opera12[2]12.1112.1412.14
Safarin/a5.0n/a5.15.1
51 |
52 | 53 |

[1] Although MSIE 6, 7, and 8 do not support SVG, 54 | you can use them to run LightWAVE if you install 55 | Google Chrome Frame (a free 56 | plugin that provides the missing support). Google Chrome Frame is optional for 57 | MSIE 9; it may provide faster display updating. 58 | 59 |

[2] Opera's browser for iOS, Opera Mini, 60 | does not support SVG as of its most recent version (7.0.5). Use Safari or 61 | Chrome on iOS. 62 | 63 | -------------------------------------------------------------------------------- /client/doc/buglist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LightWAVE bug list 7 | 8 | 9 | 10 |

LightWAVE bug list

11 | 12 |
  • Autoplay freezes after >10 minutes.
    13 | It may have been possible to fill the trace pool with redundant copies of 14 | the same, or nearly the same, data segment, thereby locking out any attempt 15 | to fetch the next data segment. Changes in the prefetch strategy in version 16 | 0.46 make this very unlikely. Exit the browser and reopen the record if this 17 | problem recurs. 18 | 19 |
  • LightWAVE doesn't work in Internet Explorer.
    20 | Yes, it does. But unless you have IE 9 or 10, you must install 21 | the free Google Chrome 22 | Frame plugin. The plugin is recommended but not required for IE 9. 23 | 24 | 25 | -------------------------------------------------------------------------------- /client/doc/client-install.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Installing a local copy of the LightWAVE client 7 | 8 | 9 | 10 |

    Installing a local copy of the LightWAVE client

    11 | 12 |

    13 | The LightWAVE client runs within the user's web browser, communicating with the 14 | LightWAVE server, lightwave, to obtain raw data that the client 15 | formats and presents to the user. 16 | 17 |

    It is not necessary to install LightWAVE in 18 | order to use it! Point your browser to http://physionet.org/lightwave/ 20 | to do so. 21 | 22 |

    23 | In normal use, both the client and the server are hosted on the same machine, 24 | and the user visits the URL of the client in order to use LightWAVE. For 25 | example, PhysioNet hosts a public server (http://physionet.org/cgi-bin/lightwave) and a public client (http://physionet.org/lightwave/). 28 | 29 |

    30 | It is also possible for the client and the server to be hosted on different 31 | machines. For example, a locally hosted LightWAVE client can make use of the 32 | public server on PhysioNet. There is no difference in the capabilities, 33 | speed, network usage, or computational requirements of the two setups, 34 | apart from the very brief time needed to load the client initially. 35 | 36 |

    37 | This technical note describes how to create the second setup, by obtaining 38 | the LightWAVE client so that it can be hosted on your own computer while 39 | interacting with the public LightWAVE server on PhysioNet. 40 | 41 | 42 |

    Who would want a local copy of the LightWAVE client?

    43 | 44 |

    45 | A locally hosted client is primarily of interest to developers wishing to 46 | create custom clients, since it allows them to modify the client code freely 47 | without needing to run a local web server, or to compile and install the 48 | LightWAVE server. The client does not require compiling, and it is portable 49 | across all popular platforms, including Android, IOS, Linux, Mac OS X, and 50 | Windows. The only software development tools needed are a text editor and a 51 | web browser. 52 | 53 |

    54 | If you want to run LightWAVE without a network connection, you will need to have 55 | a web server and the LightWAVE server running on your computer, and you will 56 | need to have downloaded suitable data (either from PhysioNet or another source 57 | of compatible data). This is not difficult, but it does require a more 58 | extensive set of software development tools including a C compiler. If your web 59 | server and development tools are already set up, installing the LightWAVE server 60 | can be completed in a few minutes. If you need to install the web server and 61 | tools, however, this may require an hour or longer, depending on your 62 | experience. See Installing a local copy of the 63 | LightWAVE server for detailed information. 64 | 65 |

    66 | Developers wishing to customize the LightWAVE server will also need a local 67 | web server and development tools, but if a network connection is available, 68 | they do not need to download data (since the LightWAVE server will do this 69 | on demand from PhysioNet as needed), unless they also wish to use data not 70 | available from PhysioNet or another compatible source. 71 | 72 |

    Download the LightWAVE sources

    73 | 74 |

    75 | The LightWAVE sources are available within the LightWAVE project on 76 | PhysioNetWorks. To obtain them: 77 | 78 |

      79 |
    1. If you have not already done so, go to 80 | https://physionet.org/users/ and 81 | create a personal PhysioNetWorks account. It's free and the process takes 82 | only a minute or two. 83 | 84 |
    2. Log in to your PhysioNetWorks account and look for the link to 85 | LightWAVE in the Projects section on your home page. Follow the 86 | link and look for the signup message ("Interested PhysioNetWorks members 87 | may obtain access to this project now by clicking here."). Click on 88 | the word "here" in the message to join the project. 89 | 90 |
    3. From the project's archives ( 92 | https://physionet.org/works/LightWAVE/files), find and download the most 93 | recent set of sources. Each version is available as a tarball (e.g., 94 | lightwave-0.43.tar.gz) that can be downloaded as a single file and 95 | unpacked, or you can download individual files from the most recent version 96 | from the lightwave subdirectory. For downloading hints, see How can I download 98 | binary files?; for instructions on unpacking tarballs, see How can I unpack a .tar.gz archive 100 | (a "tarball")?. If you choose to download individual files, note that you 101 | will need to create a complete copy of the client directory and its 102 | css, doc, and js subdirectories. 103 | 104 |
    105 | 106 |

    Testing the LightWAVE client

    107 | 108 | 109 |

    Your local copy of the LightWAVE client is ready to use once it has been 110 | unpacked. For example, if you have unpacked the tarball into 111 | /home/bob, point your web browser to 112 | file:///home/bob/lightwave/client/lightwave.html to begin using it. 113 | (Note, however, that you will not be able to save any annotation edits that 114 | you make using a LightWAVE client launched from a file:// URL. 115 | If you need to save edits, either copy your client to PhysioNetWorks and 116 | launch it from there, or set up a local LightWAVE server and scribe.) 117 | 118 |

    Use a Javascript-enabled web browser that supports HTML5 and SVG. See Browsers compatible with LightWAVE for 120 | details and recommendations. 121 | 122 |

    123 | If the client does not function correctly, check to be sure that its JavaScript 124 | (.js) files are installed in the js subdirectory (in the 125 | example above, /home/bob/lightwave/client/js/), and that 126 | http://physionet.org/ is accessible. 127 | 128 |

    129 | You can customize your LightWAVE client by editing lightwave.html, 130 | lightwave.css (in the css subdirectory), and 131 | lightwave.js (in the js subdirectory). Test your changes 132 | at any time by reloading lightwave.html in your web browser. 133 | 134 |

    135 | The other files in the css and js subdirectories are recent 136 | versions of the jQuery and jQuery UI libraries and style files; they may be 137 | replaced by any other recent versions of these files, which you can obtain from 138 | jquery.com and jqueryui.com. (Be sure to select mutually compatible versions 139 | of jQuery and jQuery UI if you replace any of these files; see jqueryui.com for 140 | information about compatibility.) 141 | 142 | -------------------------------------------------------------------------------- /client/doc/client-query-strings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Using query strings with the LightWAVE client 7 | 8 | 9 | 10 |

    Using query strings with the LightWAVE client

    11 | 12 |

    13 | By default, LightWAVE opens with the Choose input tab open. 14 | By adding an appropriately chosen query string to the LightWAVE URL, 15 | you can preload a database or a specific record into LightWAVE. 16 | 17 |

    18 | Here are several examples. Click on any of the URLs below to see how 19 | it works. 20 | 21 |

    22 |
    23 | http://physionet.org/lightwave?db=mitdb
    24 |
    25 | This example preloads the MIT-BIH Arrhythmia Database (mitdb) into the 26 | Choose input. After opening it, you can choose any record from 27 | the mitdb, but not from other databases. This feature might be used 28 | in documentation of a specific database.
    29 | 30 |
    31 | http://physionet.org/lightwave?db=mitdb&record=200
    32 |
    33 | In this example, the input is further restricted to record 200 of the 34 | mitdb. It is possible to examine any location in the record, but not 35 | to visit any other record, since the Choose input tab is not 36 | available. When the URL is first opened, LightWAVE displays a plot of 37 | the first 10 seconds of the record.
    38 | 39 |
    40 | http://physionet.org/lightwave?db=mitdb&record=200&t0=15:30
    41 |
    42 | This example also demonstrates how to open a record, but with an 43 | initial view of an arbitrary location (in this case, 15 minutes and 30 44 | seconds after the beginning of the record). As in the previous 45 | example, it is possible to navigate to any other location in the record. 46 |
    47 | 48 |

    49 | LightWAVE URLs with query strings may be useful for communicating with 50 | colleagues, since it's easy to send an email with a URL that opens a 51 | specific record at a point of interest. On PhysioNet, we are planning 52 | to incorporate such URLs in PhysioBank database home pages and in 53 | tutorials and reference materials. For example, this code can be inserted 54 | in a page based on the PhysioNet 55 | template to embed an instance of LightWAVE in it: 56 | 57 |

    58 | <iframe id="lightwave" width="100%"
    59 |   src="http://physionet.org/lightwave?db=mitdb&record=200&t0=12:34">
    60 | </iframe>
    61 | <script>
    62 |   $('#lightwave').attr('height', $(window).width() * 0.6 + 'px');
    63 |   $(window).on('resize', function() {
    64 |     $('#lightwave').attr('height', $(window).width() * 0.6 + 'px');
    65 |   });
    66 | </script>
    67 | 
    68 | 69 |

    70 | The snippet of JavaScript code ensures that the browser allocates sufficient 71 | room for LightWAVE on the page at load time, and resizes LightWAVE if the 72 | browser window is resized by the user. If you are not starting with the 73 | PhysioNet template, include this code in the HTML <head> section of 74 | the web page: 75 | 76 |

    77 | <meta http-equiv="X-UA-Compatible" content="chrome=1">
    78 | <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
    79 | 
    80 | 81 |

    82 | The first of these lines ensures that the page will load properly in MSIE if 83 | Google Chrome Frame is installed, and the second line line loads jQuery (needed 84 | by the code that follows the <:iframe> above). 85 | 86 | 87 | -------------------------------------------------------------------------------- /client/doc/contacts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Contacts 7 | 8 | 9 | 10 |

    Contacts

    11 | 12 |

    13 | The mailto: links on this page will work if your email client is 14 | properly registered as a helper application for your browser. If you 15 | haven't linked your email client to your browser, just send your comments 16 | to the email address given. We may contact you to request additional 17 | information. Please understand that we are unable to reply to all 18 | comments individually because of the volume of email we receive. 19 | 20 |

    Feature requests

    21 | 22 |

    23 | We welcome suggestions for new features or enhancements of existing features 24 | in LightWAVE. 25 | 26 |

    27 | Please check LightWAVE's to-do list to see if your 28 | proposed feature is already planned. If it isn't, send an email with the 29 | subject line Feature request to lightwave@physionet.org, and tell us about the 30 | feature you would like to see in a future version of LightWAVE. 31 | 32 |

    Bug reports

    33 | 34 |

    35 | Please check LightWAVE's bug list to see if the 36 | bug you found is already known. If it isn't, send an email with the subject 37 | line Bug report to lightwave@physionet.org, 38 | and tell us about it. Please include this information in your report: 39 |

      40 |
    • LightWAVE client and server version numbers (see the Settings tab) 41 |
    • Browser name and version number 42 |
    • Operating system and version number 43 |
    • Database and record names (see the Choose input tab) 44 |
    • What you did that triggered the bug 45 |
    • What you observed that was unexpected 46 |
    • What you expected to observe 47 |
    • Is the unexpected behavior reproducible? 48 |
    49 | 50 |

    Questions

    51 | 52 |

    Please have a look at the FAQ. If you haven't 53 | found an answer there, send your question by email 54 | to lightwave@physionet.org. 55 | We give priority in answering questions to those who have read the FAQ. How can 56 | we tell who has done so? That's easy; we look for the magic word in the subject 57 | line of the email. 58 | 59 | -------------------------------------------------------------------------------- /client/doc/edit-log.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LightWAVE edit logs 7 | 8 | 9 | 10 |

    LightWAVE edit logs

    11 | 12 |

    Background: How LightWAVE edit logs are created and processed

    13 | 14 |

    15 | The LightWAVE client maintains an edit log (a list of changes) for each 16 | annotation set that you edit. You can view the 10 most recent entries of the 17 | edit log for the currently open annotation set, in (reversed) order from newest 18 | to oldest, using on the Settings tab. 19 | Edit logs are kept in persistent HTML5 localstorage until you 20 | successfully back them up using on 21 | the Settings tab. 22 | 23 |

    24 | An edit log contains changes that may have been made over multiple sessions, to 25 | a single annotation file, from a single computer, browser, and LightWAVE 26 | client. Each line (after the first two, which are the header) contains 27 | information about a single edit. The lines appear in the log in order of the 28 | edit operations, from first to last (which will not match the order of the 29 | annotations unless the editing was done without any backtracking). 30 | 31 |

    32 | It is important to understand that (because of the "sandbox" security model 33 | enforced by all web browsers that support HTML5 localstorage) it is not possible 34 | to copy edit logs directly to the file system of the computer on which they were 35 | created, or between LightWAVE clients (not even across different browsers on the 36 | same computer). It is necessary to back up an edit log in order to share 37 | it with another browser, application, or computer. 38 | 39 |

    LightWAVE edit log format specification

    40 | 41 |

    42 | Since edit logs must be created and transmitted using JavaScript code in the 43 | LightWAVE client, they are written in text format. Each line ends with a 44 | DOS-style newline (CRLF: \r\n). The format has been designed to be 45 | compact and easily parsed. 46 | 47 |

    48 | Header 49 | 50 |

    51 | The first two lines form the header of the edit log. The first line contains a 52 | tag that identifies the file as an edit log, and it specifies the source record, 53 | the name of the annotation set, and the units of time; the second line is empty. 54 | The first line is in this format: 55 | 56 |

     57 | [LWEditLog-1.0] Record ahadb/0201, annotator atr (250 samples/second)
     58 | 
    59 | 60 |

    61 | The header in this example indicates that the edit log contains changes to 62 | ahadb/0201.atr, and that the interval between two timestamps that 63 | differ by 1 (a sample interval) is 1/250 second (4 milliseconds). 64 | 65 |

    66 | Body 67 | 68 |

    69 | Each remaining line in the edit log contains information about a single 70 | annotation, with a sequence of attributes of which only Ti is 71 | required: 72 | 73 |

    74 |
    EditType [optional]
    75 |
    Edits are either insertions or deletions. (A modification is 76 | recorded as a deletion followed by an insertion at the same location.) 77 | If no EditType is present, the edit is an insertion; a deletion is 78 | indicated by a hyphen ('-') as the first character in the line.
    79 | 80 |
    Ti [required]
    81 |
    The time of occurrence of an annotation, Ti, is expressed as the 82 | number of sample intervals from the beginning of the record until the 83 | instant to which the annotation applies. Thus an annotation placed 84 | precisely at the beginning of the record has a Ti of 0 (not 1). 85 |
      86 |
    • If Ti marks the beginning of a persistent condition, it may be 87 | followed by a hyphen (-) and Tf, a sub-attribute that indicates 88 | the time of the end of the condition. 89 |
    90 |
    91 | 92 |
    Anntype [optional]
    93 |
    This attribute, if present, follows a comma (,) that separates it 94 | from Ti or Tf. The annotation's Anntype is usually one of 95 | the single-character mnemonics 96 | defined here, 97 | but database creators may assign non-standard Anntypes. If 98 | the Anntype is not present, it is N (indicating a normal beat, 99 | displayed in the signal window of LightWAVE by ). 100 | If Anntype is present, it may be followed by a triplet of 101 | sub-attributes, {Subtype/Chan/Num}, which can include up to three small 102 | integers: 103 |
      104 |
    • Subtype [an integer between -128 and 127 inclusive; if missing, 0] 105 |
    • Chan [an integer between 0 and 255 inclusive; if missing, 0] 106 |
    • Num [an integer between -128 and 127 inclusive; if missing, 0] 107 |
    108 | For example, V{1/2/3} indicates Anntype =  V, 109 | Subtype = 1, Chan = 2, and 110 | Num = 3; S{//-2} indicates 111 | Anntype =  S, Subtype = 0, 112 | Chan = 0, and Num = -2. 113 |
    114 | 115 |
    Aux [optional]
    116 |
    This attribute consists of a string of up to 255 characters, which may not 117 | include non-printing characters, tabs, carriage returns, or linefeeds (except 118 | that space characters are allowed). If present, Anntype must also be 119 | present, and a comma (,) separates Anntype (or 120 | {Subtype/Chan/Num}) from Aux. Standard Aux definitions for 121 | cardiac rhythms are defined 122 | here; note 123 | that these definitions include unbalanced parentheses. 124 |
    125 |
    126 | 127 |

    128 | Interpreted examples of lines in the body of an edit log 129 | 130 |

    131 |
    12345,+,(B
    132 |
    133 | Inserted annotation at sample number 12345, anntype = + (a rhythm change), 134 | subtype = 0, chan = 0, num = 0, aux = '(B'. Note that subtype, chan, and 135 | num have defined values (of zero) although they are not explicitly specified. 136 |
    137 | 138 |
    -6789
    139 |
    140 | Deleted annotation at sample number 6789, anntype = N (a normal beat), 141 | subtype = 0, chan = 0, num = 0, aux empty. If the beat as described by 142 | these attributes (including those that are implicit) does not exist exactly 143 | as described, no deletion is made. 144 |
    145 | 146 |
    -875,V{1//2}
    147 |
    148 | Deleted annotation at sample number 875, anntype = V (a premature ventricular 149 | contraction), subtype = 1, chan = 0, num = 2, aux empty. 150 |
    151 | 152 |
    875,V{1//}
    153 |
    154 | Reinsertion of the previous deleted annotation; only the num attribute has 155 | changed (from 2 to 0). 156 |
    157 | 158 |
    99,",A long comment with ((,"&* random characters in it.
    159 |
    160 | Inserted annotation at sample number 99, anntype = " (a note), subtype = 0, 161 | chan = 0, num = 0, aux = 'A long comment with ((,"&* random characters in it.'. 162 |
    163 | 164 |
    31415-43210,",ECG disconnected
    165 |
    166 | Inserted annotation of a persistent condition beginning at sample number 167 | 31415 and ending at sample number 43210, anntype = " (a note), subtype = 0, 168 | chan = 0, num = 0, aux = 'ECG disconnected'.
    169 |
    170 | 171 | 172 |

    lw-scribe and patchann

    173 | 174 |

    175 | The LightWAVE scribe (lw-scribe) is a program that normally resides on 176 | the same host as the LightWAVE server, although it can run anywhere. When 177 | you use , the LightWAVE client transmits 178 | your edit logs to the LightWAVE scribe, which stores them in their native 179 | format in a queue for processing by patchann. 180 |

    181 | The patchann application reads a LightWAVE edit log from its standard 182 | input, and creates a PhysioBank annotation file containing the original 183 | annotations (if any), with additions, deletions, and modifications specified by 184 | the edit log. The annotation files created by patchann are typically 185 | saved in locations where they can be read using the LightWAVE server that 186 | supplied the data from which they were created, thus permitting you to view them 187 | (and, if necessary, edit them further) in another session, perhaps using a 188 | different browser or computer. Since they are in standard PhysioBank-compatible 189 | format, they can also be read by any WFDB application. 190 | 191 |

    192 | Since the first line of the edit log specifies the record and annotator names, 193 | patchann can find the original annotations automatically, if they 194 | exist, and load them into an in-memory annotation array. The program then 195 | applies the entries of the edit log one at a time, inserting additional 196 | annotations into the array and deleting annotations as needed from the array. 197 | An edit log deletion entry results in a deletion only if the array contains an 198 | exact match for the log entry. (If there is no match, this program issues a 199 | warning and continues to process the remaining log entries.) When all edit log 200 | entries have been processed, the program writes the contents of the array to a 201 | new annotation file, which can be distinguished from the original by the '_' 202 | appended to its annotator name. 203 | 204 |

    Like all of the LightWAVE software, lw-scribe and patchann are 205 | free open-source software; their sources are in the server directory of 206 | the LightWAVE package. lw-scribe is written in Perl, and it 207 | uses cgi.pm to handle HTTP uploading. The patchann application is 208 | written in C, and it uses the WFDB library to read the original annotations and 209 | to write the edited annotations. 210 | 211 | -------------------------------------------------------------------------------- /client/doc/faq.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frequently asked questions about LightWAVE 7 | 8 | 9 | 10 |

    Frequently asked questions about LightWAVE

    11 | 12 |

    What is LightWAVE?

    13 | 14 |

    LightWAVE is a lightweight waveform and annotation viewer and editor. 15 | Use it to view any of the recordings of physiologic signals and time series 16 | in PhysioBank, together 17 | with their annotations (event markers). 18 | 19 |

    LightWAVE runs within any modern web 20 | browser and does not require installation on the user's computer, tablet, or 21 | phone. LightWAVE is free software licensed under the GPL. 23 | 24 |

    For more, see the long version of this 25 | answer. 26 | 27 |

    Why is it called LightWAVE?

    28 | 29 |

    LightWAVE is modelled on WAVE, an X11/XView application I wrote and 30 | maintained between 1989 and 2012. (WAVE: Waveform and Annotation Viewer and Editor.) 31 | 32 |

    It's "light" because you can use it without needing to install it, unlike 33 | WAVE. 34 | 35 |

    Why was LightWAVE written?

    36 | 37 |

    At one time, WAVE was not difficult to install and run anywhere, because 38 | it's written in portable C, and the libraries that it uses (including X11 and 39 | XView) have been ported to all of the popular platforms (GNU/Linux, Mac OS X, 40 | other Unix-like platforms, and Windows). XView was abandoned by its authors 41 | years ago, however, and changes in the popular X11 server have resulted in 42 | chronic problems for all applications that rely on XView, including WAVE. 43 | Finding workarounds and patching XView to avoid newly introduced 44 | incompatibilities with updated X11 servers on different platforms has become 45 | increasingly difficult. WAVE has had a long run and was well liked by those who 46 | used it, but a replacement built on modern standards was clearly needed. 47 | 48 |

    Why didn't you write LightWAVE in the first place, then?

    49 | 50 |

    WAVE was designed before there were web browsers with fast JavaScript and 51 | SVG engines, mice with scrollwheels, smart phones, touch-input tablets, SVG, 52 | fast and inexpensive high-resolution graphics, JavaScript, Unicode, Wifi, 53 | TCP/IP, 64-bit CPUs in every toaster, the World Wide Web(!), and many of the 54 | user interface elements that are familiar today. OK, I'm kidding about TCP/IP. 55 | 56 |

    Developing a replacement for WAVE offers the opportunity to revisit 57 | design decisions that would be made differently given current technology. We 58 | are confident that even WAVE enthusiasts will like LightWAVE. 59 | 60 |

    How is LightWAVE different from WAVE?

    61 | 62 |

    In terms of function, they are very similar, although LightWAVE is still in 63 | development and lacks some important features of WAVE, notably related to 64 | control of external analysis programs. These features will appear in LightWAVE 65 | over the next several months. 66 | 67 |

    Architecturally, LightWAVE and WAVE are quite different. WAVE is a compiled 68 | application that communicates with the user via an X11 server running on the 69 | user's computer. Usually, WAVE runs on the same computer as the X11 server. 70 | WAVE accesses data, which may be stored locally or on a remote web server such 71 | as PhysioNet, via the WFDB and curl libraries. Customizing WAVE requires a 72 | good working knowledge of C, X11, XView, and the WFDB library, and a substantial 73 | (though free) software development toolkit. 74 | 75 |

    In contrast, LightWAVE consists of a front end (client) that runs in the 76 | user's web browser, communicating with a pair of back end (server) applications 77 | that run as components of a web server. Usually, the front end and back end run 78 | on different computers. The back end accesses data via the WFDB and curl 79 | libraries, in the same way as WAVE does. Customizing LightWAVE requires only a 80 | text editor. 81 | 82 |

    What's the magic word?

    83 | 84 |

    See the PhysioNet FAQ. 85 | 86 |

    George Moody
    87 | 17 January 2013
    88 | (revised 21 April 2013) 89 | 90 | -------------------------------------------------------------------------------- /client/doc/followed-link.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Explore this record using LightWAVE 7 | 8 | 9 | 10 |

    Explore this record using LightWAVE

    11 | 12 |

    LightWAVE is a lightweight waveform and annotation viewer and editor. 13 | Use it to explore the record named in the upper right corner. Use your web 14 | browser's Back button to return to the previous page when you're 15 | finished. 16 | 17 |

    Controls:

    18 | 19 |

    To find out how any LightWAVE control works, move the pointer over it, 20 | and a tooltip with a brief description will appear after a short 21 | interval. Click (tap) a tab (View/edit, Tables, etc.) to open it. 22 | 23 |

    24 |
    View/edit
    25 |
    This panel contains navigation controls and indicators along its top edge. 26 | Click the button to start or pause autoplay. 27 | Below, the signal window shows an excerpt of the record named in the 28 | upper right corner. The time indicators along the lower edge of the signal 29 | window indicate the elapsed time from the beginning of the record, in hours, 30 | minutes, and seconds. The signal window is 10 seconds wide initially; use the 31 | slider below it at right to change its width. 32 | 33 |

    34 | Use the navigation controls to view any other location in the record. For 35 | example, enter a time in the box next to Go to:, then press [Enter] or 36 | click (tap) the (refresh signal window) button. 37 | 38 |

    Use the button to specify a search target (a type 39 | of annotation to be found), then use the 40 | and buttons to find the previous or next 41 | occurrence of the target. Clicking on opens a dialog 42 | box. Enter an annotation mnemonic, such as N, V, or A (hover over Search 43 | for: to see a tooltip that describes more options), and choose an 44 | annotation set to search if more than one set is available. 45 | 46 |

    While autoplay is paused, click on any of the labels on the left side of the 47 | signal window to cycle among three display states (hidden, highlighted, and 48 | normal). At most one signal and one annotation set may be highlighted at a 49 | time. Change the scale of a highlighted signal using 50 | the (expand), (restore to default), 51 | and (shrink) buttons. Click on the red circle to hide 52 | or restore the grid, or the blue circle to hide or restore the annotation 53 | markers. 54 |

    55 | 56 |
    Tables
    57 |
    View details about the record, including text representations of the 58 | annotations and digitized signals.
    59 | 60 |
    Settings
    61 |
    Use the controls to modify LightWAVE's behavior. Your changes persist until 62 | LightWAVE is reloaded.
    63 | 64 |
    Help
    65 |
    The button shows this page. Follow the 66 | links in to learn more.
    67 |
    68 | 69 |

    When your browser loads this page, not all of LightWAVE's controls are 70 | available; for example, you cannot select a different record to view. To 71 | access all of LightWAVE's features, visit 72 | http://physionet.org/lightwave/. 73 | 74 | -------------------------------------------------------------------------------- /client/doc/pnw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LightWAVE and PhysioNetWorks 7 | 8 | 9 | 10 | 11 |

    LightWAVE and PhysioNetWorks

    12 | 13 |

    George Moody
    14 | Last revised: 22 February 2013 (0.48) 15 | 16 |

    17 | This page describes features that are not yet implemented on PhysioNetWorks! 18 |

    19 | 20 |

    PhysioNetWorks workspaces are 21 | available to members of the PhysioNet community for works in progress that will 22 | be made publicly available via PhysioNet when complete. Unlike other areas of 23 | PhysioNet, these workspaces are password-protected. 24 | 25 |

    LightWAVE can provide access to PhysioBank-compatible data within 26 | PhysioNetWorks projects to which you belong. 27 | 28 |

    Follow these steps to set up access: 29 | 30 |

      31 |
    1. If you are the owner of a private or shared PhysioNetWorks project 32 | that contains compatible data, go to your project's Manage page and 33 | click on . Follow the instructions to 34 | complete the setup. Repeat this step for any other private or shared projects 35 | with compatible data. 36 | 37 |
    2. From your PhysioNetWorks home page, click on . 39 | 40 |
    3. In the future, when you upload additional compatible data into one of your 41 | projects, go to its Manage page and click on . 43 |
    44 | 45 |

    Follow these steps to view PhysioNetWorks data: 46 | 47 |

      48 |
    1. 49 | There are two ways to access compatible data in PhysioNetWorks via LightWAVE: 50 | 51 | 62 | 63 |
    2. Log in to PhysioNetWorks if you haven't done so already. 64 |
    65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /client/doc/projects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LightWAVE projects 7 | 8 | 9 | 10 | 11 |

    Help us build LightWAVE

    12 | 13 |

    14 | We're looking for volunteers interested in working on the LightWAVE-related 15 | projects listed below. If you'd like to get started on one of them (or if 16 | you have your own idea about what LightWAVE needs), do this: 17 | 18 |

      19 | 20 |
    1. Create a PhysioNetWorks account if you don't have one already, 22 | and log into it. 23 | 24 |
    2. Log into PhysioNetWorks and join the 25 | LightWAVE 26 | project. 27 | 28 |
    3. If the project you want to do is marked [server development], 29 | read and follow the instructions for 30 | installing a local copy of the LightWAVE 31 | server. 32 | 33 |
    4. Otherwise, read and follow the instructions for 34 | installing a local copy of the LightWAVE 35 | client. 36 | 37 |
    5. Have a look at the code and make a plan for implementing your ideas. 38 | Consider carefully what existing code will need to be modified in order 39 | to link your code with it. 40 | 41 |
    6. Let us know what you plan to do by sending an email with the subject 42 | line Project plan to 43 | 44 | lightwave@physionet.org, and include this information: 45 |
        46 |
      • your name and contact information 47 |
      • what project you've chosen 48 |
      • which existing functions will be affected 49 |
      • an estimated timeline (be realistic!) 50 |
      51 | If someone else is already working on the project, we'll put you in 52 | touch with each other. 53 |
    54 | 55 |

    Unless marked [server development], the goals of these projects should 56 | be possible to accomplish entirely within the LightWAVE client. Client 57 | development requires programming in JavaScript, and can be done with only 58 | a text editor (although you can use whatever tools you like). Server 59 | development requires at least some programming in C and a set of development 60 | tools that you probably have already if you do any C programming on Linux. 61 | 62 |

    63 | LightWAVE is free software licensed under the GPL. 64 | Please do not contribute code that cannot be freely redistributed in source 65 | form. 66 | 67 |

    LightWAVE projects

    68 | 69 |

    LightWAVE as an off-line web application

    70 | 71 |

    Many users who might wish to use LightWAVE while off-line will find the 72 | currently necessary installation of a locally hosted web server to be 73 | challenging, particularly if they are unaccustomed to system administration. We 74 | seek a simplified alternative for such users. 75 | 76 |

    This project aims to provide a way to configure LightWAVE as 77 | an 78 | off-line web application, with the ability to preload and cache data while 79 | on-line, for later use off-line. For example, we might download a few sets of 80 | compressed JSON files (as in the server-side caching project below) to study on 81 | a long flight. 82 | 83 |

    It is also important to provide a way to save annotations created during 84 | off-line use for subsequent uploading and sharing when a network connection 85 | becomes available. 86 | 87 |

    A LightWAVE client optimized for smart phones

    88 | 89 |

    The LightWAVE client has been designed to be usable on both desktop and 90 | mobile devices. It is not possible, however, for a user to obtain the same 91 | pointing precision with a finger on a smart phone's touch screen as with a mouse 92 | or trackball on a large desktop display. Since annotation editing requires 93 | high-precision placement of markers, a user interface that is most efficient on 94 | a small touch display may be substantially different from one developed for the 95 | desktop. 96 | 97 |

    This project's goal is to develop a client that is optimized for smart 98 | phones and small tablets, with high-resolution displays and low-resolution touch 99 | input, either by modifying the existing LightWAVE client or starting from 100 | scratch. It is important to maintain compatibility with the standard LightWAVE 101 | server and with plugins for the existing client. 102 | 103 |

    Server-side caching for LightWAVE [server development]

    104 | 105 |

    We would like to investigate the possible benefits of storing server 106 | responses to common requests. The server reads data in response to each 107 | incoming request, reformatting them as JSON, which (on PhysioNet) Apache then 108 | compresses using gzip before transmission to the client. The server would 109 | require less computation if it were able to read gzip-compressed JSON data 110 | directly from local storage. 111 | 112 |

    One might begin by generating a set of compressed JSON files that cover an 113 | entire record at the time that record is first requested. Being able to do 114 | this efficiently might be useful in the Data import and Off-line 115 | editing projects as well. 116 | 117 |

    Repackaging the LightWAVE server as an Apache module or a FastCGI app 118 | [server development]

    119 | 120 |

    Unlike conventional CGI apps, Apache modules and FastCGI apps are 121 | persistent processes, saving disk I/O and computation time on the server 122 | since they do not need to be loaded and initialized each time their service 123 | is requested. The goal of this project is to investigate the efficiency 124 | gains that may be possible using these technologies for the LightWAVE server. 125 | 126 |

    Data import and export plugins for the LightWAVE client

    127 | 128 |

    Currently, users of LightWAVE who are able to collect their own data must 129 | upload them to PhysioNetWorks in order to view them with LightWAVE. 130 | (Alternatively, they can read data in PhysioBank-compatible format from local 131 | storage if they have a local web server and have installed a local copy of the 132 | LightWAVE server.) 133 | 134 |

    This project aims to simplify the process of viewing new data using 135 | LightWAVE, for example by providing plugins to convert data in common 136 | formats to compressed JSON files that can be read directly by the LightWAVE 137 | client, or uploaded to PhysioNetWorks and shared if desired. 138 | 139 |

    A longer-term goal is development of an interactive data import tool 140 | able to recognize common formats automatically, and to make plausible 141 | hypotheses about unrecognized formats, displaying the hypothesized signals 142 | and allowing the user to explore alternate assumptions about the format. 143 | 144 |

    We also aim to provide a similar tool for exporting data files in other 145 | useful formats (e.g., CSV, EDF/EDF+, .mat, and PhysioBank/WFDB) for use by 146 | other software. 147 | 148 |

    Ideally, this project should produce sets of import and export plugins 149 | for the most important formats, as well as templates for plugins and 150 | documentation for use by others needing to import or export files in other 151 | formats. 152 | 153 |

    Interactive filter design plugin for the LightWAVE client

    154 | 155 |

    The goal of this project is to develop a client-side plugin for LightWAVE 156 | that provides a graphical user interface to allow the user to experiment with 157 | FIR and IIR filters, applying them to a selected signal or group of signals, and 158 | observing their effects with little or no perceived delay. It should be 159 | possible to load predefined filters or create new ones, and apply them 160 | sequentially. Applications include user-adjustable filtering for visual noise 161 | reduction, and signal enhancement for event detection. 162 | 163 |

    Since the client receives the digitized signals as unscaled first 164 | differences, the filters might be applied within set_trace (which 165 | reconstructs the series of raw amplitudes) or within 166 | show_plot (which transforms the raw amplitudes into calibrated 167 | values). 168 | 169 |

    Data analysis plugins for the LightWAVE client: Beat detection in 170 | JavaScript

    171 | 172 |

    One of the design goals for LightWAVE is to provide a comfortable and 173 | efficient method of annotating PhysioBank-compatible data. Most often, we 174 | are interested in marking the times of occurrence of heart beats, usually 175 | by locating the QRS complexes in ECG signals. Automated QRS detectors 176 | can do this task quite well in clean signals, but require supervision since 177 | they can be misled by noise and artifact. It is also of interest to distinguish 178 | normal beats from abnormal (ectopic) beats, a task that is much more difficult 179 | than QRS detection. Beat annotations made by human experts and validated by 180 | comparison among experts are the gold standard and highly valued for research. 181 | 182 |

    Several QRS detectors written in C are included in the WFDB Software 183 | Package, and any of them might serve as a model for a JavaScript QRS detector 184 | function that could be run within the LightWAVE client as a first step in the 185 | process of developing a reference annotation file for a previously unannotated 186 | record. 187 | 188 |

    A plugin for QRS detection should be a protypical example of a class of 189 | plugins to perform other types of analysis in the LightWAVE client. Such 190 | plugins may be required to record their output as annotations, or to display 191 | their results using special-purpose graphical or tabular outputs. 192 | 193 |

    A multichannel programmable analog signal generator built on LightWAVE

    194 | 195 |

    The LightWAVE server offers a very simple API for retrieval of signals from 196 | PhysioBank and compatible data repositories. This project aims to develop an 197 | alternative LightWAVE client that can function as an analog signal generator for 198 | simulating realistic inputs to medical instruments, using either a multichannel 199 | DAC or an audio output. 200 | 201 |

    Typical audio output devices are not capable of producing output below the 202 | limits of human hearing (about 20 Hz), which makes direct real-time output of 203 | signals such as ECG, continuous blood pressure, and respiration impossible. A 204 | workaround is to produce amplitude- or pulse-width-modulated signals in 205 | software, convert them to analog form, and then to demodulate the AM or PWM 206 | signals with low-cost analog hardware. 207 | 208 | 209 | -------------------------------------------------------------------------------- /client/doc/server-install.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Installing a local copy of the LightWAVE server 7 | 8 | 9 | 10 |

    Installing a local copy of the LightWAVE server

    11 | 12 |

    13 | The LightWAVE server, lightwave, runs as a CGI application within a web 14 | (http) server. The web server collects user requests (typically made using the 15 | LightWAVE client running in a web browser on another computer) and forwards them 16 | to the LightWAVE server. The LightWAVE server parses the requests, obtains the 17 | requested data from local storage or from a data repository such as PhysioNet, 18 | constructs appropriate responses, and passes them back to the web server. 19 | The web server then relays the responses (typically after compression) to the 20 | users. 21 | 22 |

    It is not necessary to install LightWAVE in 23 | order to use it! Point your browser to http://physionet.org/lightwave/ 25 | to do so. 26 | 27 |

    In normal use, the client, the server, and the data repository are 28 | all hosted on the same machine, and the user visits the URL of the 29 | client in order to use LightWAVE. For example, PhysioNet hosts a 30 | public server 31 | (http://physionet.org/cgi-bin/lightwave), 32 | a public client 33 | (http://physionet.org/lightwave/), 34 | and a public data repository 35 | (http://physionet.org/physiobank/database). 36 | 37 |

    38 | It is also possible for the client, the server, and the data 39 | repository to be hosted on different machines. For example, a locally 40 | hosted LightWAVE client can make use of the public server and data 41 | repository on PhysioNet, or a locally hosted client and server can use 42 | a remote data repository (or even multiple data repositories hosted on 43 | multiple remote machines). 44 | 45 |

    46 | This technical note describes how to install the LightWAVE server so 47 | that it can be hosted on your own computer while interacting with the 48 | public data repository on PhysioNet. The final section describes how 49 | to set up your own data repository, using PhysioNet and its 50 | mirrors as a model. 51 | 52 |

    Who would want a locally hosted LightWAVE server?

    53 | 54 |

    55 | A locally hosted server and a local data repository are needed if you wish to 56 | use LightWAVE on a machine that has no Internet connection. (For example, some 57 | of the early development of LightWAVE was done on a notebook PC while flying 58 | across the Pacific!) 59 | 60 |

    61 | If you use a PhysioNet mirror 62 | that doesn't host its own LightWAVE server, a locally hosted server 63 | can be configured to use the mirror as its primary data repository, 64 | with the PhysioNet master server used only as a source of data that 65 | are not available on the mirror. 66 | 67 |

    68 | You might also find a locally hosted server convenient for working on 69 | your own data. (There are alternatives for this, including uploading 70 | your data to secure storage on 71 | PhysioNetWorks; this also 72 | makes it easy to share your data with colleagues anywhere.) 73 | 74 |

    75 | Finally, if you are interested in extending the capabilities of the 76 | server itself, you will need a local copy of it. Note that LightWAVE is 77 | free software licensed under the GPL, which 78 | means that you are free to copy, use, and modify it, but you may not 79 | distribute modified compiled versions of it unless you also make the 80 | source code for your modified versions available. 81 | 82 |

    Requirements for a LightWAVE server

    83 | 84 |

    85 | The LightWAVE server runs as a CGI application controlled by a web 86 | server. To date, the LightWAVE server has been compiled and installed 87 | successfully on several versions of Fedora and Ubuntu GNU/Linux, 88 | including both 32- and 64-bit versions, using the Apache web server. 89 | 90 |

    91 | The LightWAVE server code is contained in a single C source file with 92 | no explicit platform dependencies. All of the external libraries it 93 | uses are also platform-independent. You should be able to compile it 94 | successfully with any ANSI/ISO C compiler on any platform. (If you 95 | attempt to compile and run the LightWAVE server on Mac OS X or on 96 | Windows, please let me know if you were successful, and if so, please 97 | send me a brief description of what steps were needed.) 98 | 99 |

    100 | To receive edits made using LightWAVE clients hosted on the same web 101 | server, a second CGI application (lw-scribe, written in Perl) is needed. 102 | This application uses a command-line application (patchann, written in 103 | C) to merge edit logs with original annotation files and write new 104 | annotation files. lw-scribe and patchann are not needed unless you 105 | need to handle annotation edits. 106 | 107 |

    Download the LightWAVE sources

    108 | 109 |

    110 | The LightWAVE sources are available within the LightWAVE project on 111 | PhysioNetWorks. To obtain them: 112 | 113 |

      114 |
    1. If you have not already done so, go to 115 | https://physionet.org/users/ and 116 | create a personal PhysioNetWorks account. It's free and the process takes 117 | only a minute or two. 118 | 119 |
    2. Log in to your PhysioNetWorks account and look for the link to 120 | LightWAVE in the Projects section on your home page. Follow the 121 | link and look for the signup message ("Interested PhysioNetWorks members 122 | may obtain access to this project now by clicking here."). Click on 123 | the word "here" in the message to join the project. 124 | 125 |
    3. From the project's archives ( 127 | https://physionet.org/works/LightWAVE/files), find and download the most 128 | recent set of sources. Each version is available as a tarball (e.g., 129 | lightwave-0.43.tar.gz) that can be downloaded as a single file and 130 | unpacked, or you can download individual files from the most recent version 131 | from the lightwave subdirectory. For downloading hints, see How can I download 133 | binary files?; for instructions on unpacking tarballs, see How can I unpack a .tar.gz archive 135 | (a "tarball")?. If you choose to download individual files, note that you 136 | will need to create a complete copy of the lightwave 137 | directory tree in order to test your installation with the LightWAVE client. 138 |
    139 | 140 |

    Edit the Makefile

    141 | 142 |

    143 | In the lightwave directory, open Makefile in a text editor 144 | (not a word processor) and read it. The comments near the top of the 145 | file describe other free software components you may need to install 146 | before compiling and installing the LightWAVE server. 147 | 148 |

    149 | Currently, these components include: 150 | 151 |

      152 |
    • a properly configured web server, such as 153 | Apache 154 |
    • libcgi 155 |
    • libwfdb 156 |
    • libcurl 157 |
    • an ANSI/ISO C compiler, such as gcc 158 | and a few other standard POSIX tools including 'make', 'cp', 159 | 'mkdir', 'mv', 'rm', 'sed', and 'tar' (all standard on Linux and Mac OS X, 160 | components of Cygwin on Windows) 161 |
    • perl 162 | and CGI.pm (for lw-scribe 163 | only) 164 |
    165 | 166 |

    167 | (This list may be incomplete; consult the Makefile for any 168 | additions.) 169 | 170 |

    As the comments near the top of the file note, you may need to change the 171 | values of some parameters that are defined in Makefile to 172 | match your installation. 173 | 174 |

    Build and install the LightWAVE server

    175 | 176 |

    177 | Once you have checked the Makefile and made any necessary 178 | changes to it, open a terminal window, go to the directory that 179 | contains Makefile, and run the command 180 | 181 |

    182 |     make
    183 | 
    184 | 185 |

    186 | The output of this command should look something like this: 187 | 188 |

    189 |     gcc -g -DLWDIR=\"/home/physionet/html/lightwave\" -DLWVER=\"0.43\" -DLW_WFDB=\""/usr/local/database http://physionet.org/physiobank/database"\" server/lightwave.c -o lightwave -lcgi -lwfdb
    190 |     mkdir -p /home/physionet/cgi-bin
    191 |     cp -p lightwave /home/physionet/cgi-bin
    192 |     rm -f lightwave *~ */*~ */*/*~
    193 |     mkdir -p /home/physionet/html/lightwave
    194 |     mv client/lightwave.html .
    195 |     cp -pr client/* /home/physionet/html/lightwave
    196 |     sed s+http://physionet.org/cgi-bin/+/cgi-bin/+ \
    197 |      <client/js/lightwave.js >/home/physionet/html/lightwave/js/lightwave.js
    198 |     sed "s/\[local\]/0.43/" <lightwave.html >/home/physionet/html/lightwave/index.html
    199 |     mv lightwave.html client
    200 | 
    201 |     LightWAVE has been installed.  If an HTTP server is running,
    202 |     use LightWAVE by opening your web browser and visiting
    203 |         http://HOST/lightwave/
    204 |     (replacing HOST by the hostname of this server, or by localhost
    205 |     or 127.0.0.1 to run without a network connection).
    206 | 
    207 | 208 |

    Test the installed server

    209 | 210 |

    211 | From the lightwave directory, run this command: 212 | 213 |

    214 |     make test
    215 | 
    216 | 217 | The output of this command should look something like this: 218 | 219 |
    220 |     The LightWAVE server appears to be running properly in command-line mode.
    221 |     Test it with the LightWAVE client by pointing your web browser to:
    222 |         http://localhost/lightwave/
    223 | 
    224 | 225 |

    226 | If the test with the LightWAVE client doesn't work at all, check that 227 | Apache (or whatever web server you installed) is running. 228 | 229 |

    230 | If the LightWAVE client loads, but (after about 10 seconds) displays the 231 | message: 232 | 233 |

    234 |     The LightWAVE server at
    235 |     http://physionet.org/cgi-bin/lightwave
    236 |     is not responding properly.  Please check
    237 |     the network connection.  Select another server
    238 |     on the Settings tab if necessary.
    239 | 
    240 | 241 | and the suggestions in the message are not sufficient to solve the problem, 242 | check that: 243 | 244 |
      245 |
    • lightwave (the compiled LightWAVE server) is installed in a 246 | directory that is named as a directory containing CGI 247 | applications in the appropriate Apache configuration file 248 |
    • lightwave has appropriate ownership and permissions so 249 | it can be run by Apache 250 |
    • any shared libraries (DLLs) needed by lightwave, including 251 | libcgi, libwfdb, and libcurl, are in locations where Apache can find 252 | them, and they have appropriate ownership and permissions to 253 | be read by Apache 254 |
    255 | 256 |

    Using your locally hosted server

    257 | 258 |

    259 | If you have a network connection, you may use your server by connecting to 260 | it with a browser and LightWAVE client running on the same host. The 261 | server will use PhysioBank as its data repository by default. (This is the 262 | configuration that you tested in the previous section.) 263 | 264 |

    265 | If you have set up your own data repository (see below) on the same host 266 | as the server, you may use LightWAVE without a network connection, by 267 | connecting to the server with a browser and LightWAVE client also running 268 | on the same host. 269 | 270 |

    271 | In order to use your server from a different host, the LightWAVE client 272 | must be configured to use it rather than PhysioNet's public server. To 273 | do this, go to the client's Settings tab and set the server URL to the 274 | location of your server. This setting will persist only until you reload 275 | the LightWAVE client. You can change the default server URL by changing 276 | the value of server just below the initial comment block in 277 | client/js/lightwave.js; this change will affect all future sessions. 278 | 279 |

    Custom data repositories

    280 | 281 |

    282 | The LightWAVE server, in common with all applications that use the 283 | WFDB library (libwfdb), finds data by searching a list of 284 | locations (data repositories) known as the WFDB path. The 285 | standard list of locations is determined at the time the LightWAVE 286 | server is compiled, by the value of the constant LW_WFDB. 287 | This constant is defined in Makefile as 288 | 289 |

    290 |     /usr/local/database http:/physionet.org/physiobank/database
    291 | 
    292 | 293 |

    294 | If you wish to use a repository other than PhysioBank: 295 | 296 |

      297 |
    1. Choose a location for your repository. 298 |
        299 |
      • If the repository will be in local storage, the simplest choice is to put 300 | it in /usr/local/database (or the first component of your WFDB 301 | path, whatever that is). 302 |
      • Otherwise, add the location of the repository (which can be either a 303 | location in your local file system, or a URL) to the definition 304 | of LW_WFDB in Makefile, and recompile and install the 305 | server (run "make" as above). Be sure that your repository 306 | precedes PhysioBank in the WFDB path, or it will not be searched. 307 |
      308 | 309 |
    2. Make a copy 310 | of 311 | http://physionet.org/physiobank/database/DBS. Use a text 312 | editor (not a word processor) to edit it so that it includes the 313 | names of the database(s) in your repository, and put the 314 | modified DBS in the top-level directory of your 315 | repository. Important: database names may not include spaces! 316 | 317 |
    3. Make a copy 318 | of 319 | http://physionet.org/physiobank/database/wfdbcal. If your 320 | records include signal types that are not listed in it, use a 321 | text editor to add them. Put the modified wfdbcal in 322 | the top-level directory of your repository. 323 | 324 |
    4. For each database in your repository, make a subdirectory of your 325 | repository's top-level directory, with the name that you entered 326 | in DBS. Within the subdirectory: 327 |
        328 |
      • Store the 329 | PhysioBank-compatible records for your database. 330 |
      • Create plain text files named RECORDS (containing a list of 331 | record names) and ANNOTATORS (containing a list of annotator 332 | names), using those in the PhysioBank databases as models. 333 |
      334 |
    335 | 336 |

    Note that, on specific request, the LightWAVE server will read sets of 337 | annotations that are not listed in the ANNOTATORS file. The current 338 | LightWAVE client does not provide a user interface for specifying an unlisted 339 | annnotation set, however; as a result, annotation sets are not readily 340 | accessible unless their annotator names are listed in ANNOTATORS. 341 | It is harmless (though slightly inefficient) to list annotator names for 342 | non-existent annotation sets in ANNOTATORS. Make an empty 343 | ANNOTATORS file if your database is unannotated. 344 | 345 |

    346 | Notice that although your LightWAVE client lets you choose which LightWAVE 347 | server it uses, it does not let you choose which data 348 | repositories the server uses (since, by design, this is determined when the 349 | server is compiled, not at run-time). If you wish to use non-standard 350 | repositories, this can be done only by configuring your own LightWAVE server to 351 | do so. 352 | 353 | -------------------------------------------------------------------------------- /client/doc/thanks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Thanks! 7 | 8 | 9 | 10 |

    Thanks!

    11 | 12 |
      13 |
    • Ken Pierce created the ECG animation that appears next to the 14 | LightWAVE logo. 15 | 16 |
    • Diego Tognola found and reported JSON formatting errors produced 17 | by early versions of the LightWAVE server. 18 | 19 |
    • Tingting Zhu modified an early version of the LightWAVE client 20 | so that it could be used to collect signal quality annotations. 21 | 22 |
    • Roger Mark suggested several user interface improvements. 23 |
    24 | 25 |

    26 | George Moody
    27 | 5 February 2013 28 | -------------------------------------------------------------------------------- /client/doc/to-do.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LightWAVE to-do list 7 | 8 | 9 | 10 |

    LightWAVE to-do list

    11 | 12 |

    George Moody
    13 | Last revised: 29 May 2013 (0.63) 14 | 15 |

    16 | This is a list of currently planned features and enhancements for future 17 | releases of LightWAVE. 18 | 19 |


    20 | 21 |

    Client:

    22 | 23 |

    24 | Most of these items are features that can be implemented in 25 | lightwave.js. Please note that lightwave.js will be 26 | significantly reorganized during the next several alpha releases. 27 | 28 |

      29 |
    • tabbed interface 30 |
    • record sub-menus as on ATM (e.g., to provide access to MIMIC II numerics records) 31 |
    • checkboxes for annotators, allow selection/display of multiple annotators 32 |
    • toggle signal and annotator visibility 33 |
    • improve default vertical spacing of signals and annotators 34 |
    • highlight signals, annotators 35 |
    • resize individual signals 36 |
    • short-form database names 37 |
    • log server requests/responses 38 |
    • handle invalid samples properly in View/edit and Tables 39 |
    • "levels" popup as in WAVE 40 |
    • detect small screen (e.g. smart phone) and switch to alternate UI 41 |
    • alternate UI for smart phones, etc. 42 |
    • read and cache entire annotation file(s) 43 |
    • search for annotations (sequences of annotations?) 44 |
    • caching 45 |
    • switch to View/edit after selecting record 46 |
    • reorganize controls on View/edit panel 47 |
    • selective display on Tables panel 48 |
    • scrolling Google maps style 49 |
    • pan and zoom 50 |
    • user-adjustable filtering for visual noise reduction 51 |
    • display metadata ('info' request) 52 |
    • include segments in info and generate clickable record maps as in ATM 53 |
    • persistent annotations (rhythms, signal quality, ...) 54 |
        55 |
      • use tracks (color bars or patterned lines) to indicate major types 56 | of persistent annotations 57 |
      • if a persistent annotated characteristic begins before/ends after the 58 | display window, indicate it with a marker in the left/right margin 59 |
      60 |
    • annotation editing 61 |
        62 |
      • should be usable on touchscreens (iPad) as well as with mouse 63 | or trackball and normal screen 64 |
      • pull-out or pop-up annotation palette 65 |
          66 |
        • most recently-used/favorite annotations near top 67 |
        • selecting type from palette modifies cursor 68 |
        • palette should include deletion tool 69 |
        70 |
      • unlimited undo/redo 71 |
      • create new annotation set 72 |
          73 |
        • properly handle multiple new annotation sets 74 |
        75 |
      • select a different annotation set to edit 76 |
      • edit logs 77 |
          78 |
        • separate edit logs for each annotation set 79 |
        • save edit log(s) to localstorage 80 |
        • restore edit log(s) from localstorage 81 |
        • clear edit logs after save/reload/validate 82 |
        • warn user before reload/exit if there are unsaved edits 83 |
        • send edit log(s) to server 84 |
            85 |
          • on PhysioNetWorks: authenticated archival storage, accessible 86 | in future sessions by LightWAVE and downloadable for other uses 87 |
          • on PhysioNet: short-term storage identified by cookie 88 |
          89 |
        90 |
      • "snap" mode to move new annotations to local extremum automatically 91 |
          92 |
        • use gestures to move to nearest following/preceding local max/min 93 | or inflection point 94 |
        • adjustable filtering to make this feature usable even in presence 95 | of mains or HT noise 96 |
        97 |
      • enter custom annotations as NOTE + text 98 |
          99 |
        • creates entry in NOTE sub-palette 100 |
        • provide a method for promoting selected NOTE texts to first-class 101 | annotation types 102 |
        • users should be able to save their NOTE sub-palettes (by default) 103 | for use with future records (even with other projects) 104 |
        105 |
      • entry of persistent annotations marks onset, subtype (with predefined 106 | but extensible choices) 107 |
          108 |
        • next annot of same type, different subtype marks end in display 109 |
        • continuation markers at right edge should be draggable into the 110 | display, but the user should be prompted to enter new subtype 111 |
        • need "unknown"/"unclassified" subtype 112 |
        113 |
      114 |
    • bookmarking to save state 115 |
    • switch servers at run-time 116 |
    • display server and client versions 117 |
    • timeout and alert on nonresponsive server 118 |
    • look for success flag in server response 119 |
    • data importer (interface to uploader/reformatter) 120 |
    • data exporter (text, CSV, EDF, MATLAB, WFDB, others?) 121 |
    • calipers/dividers (set of draggable markers that are kept equidistant, 122 | for measuring time intervals and observing periodicity/prematurity) 123 |
    • make custom jquery-ui.min.js and jquery-ui.min.css omitting unused 124 | components 125 |
    126 | 127 |
    128 | 129 |

    Server:

    130 | 131 |

    132 | These tasks will be implemented in lightwave.c. Tasks noted 133 | with (*) will require coordinated client-side changes. 134 | 135 |

      136 |
    • collate DBS from all WFDB path components 137 |
    • * lw-scribe: receiver for (bulk) edit logs from clients 138 |
    • patchann: rebuild annotation files from edit logs (+ original files) 139 |
        140 |
      • authenticated uploads: archive in user's personal PNW workspace 141 |
      • anonymous uploads: short-term storage identified by cookie 142 |
      • * pages with links for user retrieval of edited annotation files 143 |
      • create/update ANNOTATIONS files 144 |
      145 |
    • disambiguate identically-named signals 146 |
    • short-form signal names 147 |
    • report info strings 148 |
    • return success flag in response 149 |
    • return version number with dblist 150 |
    • authenticated access to PhysioNetWorks data 151 |
    • * task dispatcher (e.g., accept requests to reformat uploaded 152 | data, run a QRS detector, etc.) 153 |
    154 | 155 |
    156 | 157 |

    Documentation:

    158 | 159 |

    160 | These topics need tutorials: 161 | 162 |

      163 |
    • Viewing and 164 | editing with LightWAVE 165 |
    • Simple installation for off-line use 166 |
    • Client customization guide 167 |
    168 | 169 |
    170 | 171 |

    Compatibility testing:

    172 | 173 |

    174 | See Browsers compatible with LightWAVE 175 | for current information on compatible browsers. I'd appreciate success/failure 176 | reports to fill in and expand the table on that page and/or to confirm the 177 | entries that are already in it. 178 | 179 |

    180 | I've compiled 32- and 64-bit versions of the LightWAVE server 181 | (lightwave.c) with gcc 4.5.1, linking them with 32- and 64-bit libwfdb 182 | 10.5.16, libcurl 7.21.0, and libcgi 1.0. I've tested them successfully with 183 | Apache 2.2.17 on 64-bit Fedora 14 and 32-bit Ubuntu 10.04. (I've also had 184 | success with several other versions of each of these components, and I have not 185 | discovered any combinations of them that did not work.) I'd be grateful to have 186 | reports on (in)compatibility of other HTTP servers and platforms with the 187 | LightWAVE server; in particular, if anyone attempts to compile and run the 188 | LightWAVE server on Mac OS X or on Windows, please let me know if you were 189 | successful, and if so, please send me a brief description of what steps were 190 | needed. 191 | 192 |


    193 | 194 |

    Other:

    195 | 196 |
      197 |
    • Consider producing highly compressed (turning-point compressed, or 198 | simply undersampled?) versions of all records for viewing as quick 199 | overviews of entire records or lengthy excerpts of them 200 |
    • Test Apache mod_filter/mod_deflate on PhysioNet servers 201 |
    • Integrate LightWAVE with PhysioBank ATM, PhysioBank Record Search, 202 | and PhysioNetWorks 203 |
    204 | 205 |

    206 | Also see LightWAVE projects. 207 | 208 | 209 | -------------------------------------------------------------------------------- /client/doc/topics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Help topics 7 | 8 | 9 | 10 |

    LightWAVE's documentation includes: 11 |

    25 | 26 |

    The state of the project 27 |

    34 | 35 |

    Technical notes: 36 | 37 |

    47 | 48 | 49 | Other documentation is currently limited to comments embedded in LightWAVE's 50 | source files (especially its Makefile). Sources are available in 51 | the LightWAVE project on PhysioNetWorks. 52 | 53 | 54 | -------------------------------------------------------------------------------- /client/doc/what-is-lightwave.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | What is LightWAVE? 7 | 8 | 9 | 10 |

    What is LightWAVE?

    11 | 12 |

    LightWAVE is a lightweight waveform and annotation viewer and editor. 13 | Use it to view any of the recordings of physiologic signals and time series 14 | in PhysioBank, together 15 | with their annotations (event markers). 16 | 17 |

    LightWAVE runs within any modern web 18 | browser and does not require installation on the user's computer, tablet, or 19 | phone. LightWAVE is free software licensed under the GPL. 21 | 22 |


    23 | 24 |

    LightWAVE's data repository is 25 | PhysioBank by default, 26 | or any set of files structured as in PhysioBank (with DBS, 27 | RECORDS, and ANNOTATORS text indices and WFDB-compatible 28 | binary data files). 29 | 30 |

    LightWAVE's back end is a CGI application written in C for speed and 31 | efficiency. It retrieves data from its data repository and delivers them in 32 | response to requests generated by the front end. It has been tested 33 | successfully using Apache (on Fedora 64-bit Linux and Ubuntu 32-bit Linux); it 34 | does not have explicit system dependencies and should be usable with Apache 35 | (and, possibly, with other web servers) on other platforms. The back end can 36 | also be run as an interactive or scripted command-line application for testing, 37 | debugging, and possibly other purposes I haven't dreamed up just yet. The 38 | source package contains a shell script (lw-test) that demonstrates 39 | command-line usage. The API is designed to ease development of alternative 40 | front ends with the aim of encouraging experimentation. 41 | 42 |

    LightWAVE's front end is a web application written in JavaScript. 43 | Optimum buzzword compliance, browser independence, and general cross-platform 44 | goodness, are achieved via HTML5, CSS, and JavaScript/jQuery-generated Ajax 45 | requests for JSON-formatted data transformed into SVG elements that are 46 | dynamically injected into the DOM. 47 | 48 |

    LightWAVE's data repository, back end, and front end can be located on the 49 | same computer or on separate computers. (The data repository may be split 50 | across multiple computers.) 51 | 52 |

    George Moody
    53 | 15 January 2013 54 | -------------------------------------------------------------------------------- /client/images/lightwave.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 28 | 33 | 38 | 44 | 46 | 49 | 52 | 53 | 54 | 62 | 67 | 72 | 78 | 80 | 83 | 86 | 87 | 88 | 89 | 111 | 113 | 114 | 116 | image/svg+xml 117 | 119 | 120 | 121 | 122 | 123 | 128 | 132 | 133 | 134 | 135 | 139 | 143 | 147 | 151 | 155 | 156 | 160 | 161 | 162 | 163 | 167 | 171 | 175 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /client/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2013 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD 11 | define(['jquery'], factory); 12 | } else if (typeof exports === 'object') { 13 | // CommonJS 14 | factory(require('jquery')); 15 | } else { 16 | // Browser globals 17 | factory(jQuery); 18 | } 19 | }(function ($) { 20 | 21 | var pluses = /\+/g; 22 | 23 | function encode(s) { 24 | return config.raw ? s : encodeURIComponent(s); 25 | } 26 | 27 | function decode(s) { 28 | return config.raw ? s : decodeURIComponent(s); 29 | } 30 | 31 | function stringifyCookieValue(value) { 32 | return encode(config.json ? JSON.stringify(value) : String(value)); 33 | } 34 | 35 | function parseCookieValue(s) { 36 | if (s.indexOf('"') === 0) { 37 | // This is a quoted cookie as according to RFC2068, unescape... 38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 39 | } 40 | 41 | try { 42 | // Replace server-side written pluses with spaces. 43 | // If we can't decode the cookie, ignore it, it's unusable. 44 | // If we can't parse the cookie, ignore it, it's unusable. 45 | s = decodeURIComponent(s.replace(pluses, ' ')); 46 | return config.json ? JSON.parse(s) : s; 47 | } catch(e) {} 48 | } 49 | 50 | function read(s, converter) { 51 | var value = config.raw ? s : parseCookieValue(s); 52 | return $.isFunction(converter) ? converter(value) : value; 53 | } 54 | 55 | var config = $.cookie = function (key, value, options) { 56 | 57 | // Write 58 | 59 | if (value !== undefined && !$.isFunction(value)) { 60 | options = $.extend({}, config.defaults, options); 61 | 62 | if (typeof options.expires === 'number') { 63 | var days = options.expires, t = options.expires = new Date(); 64 | t.setTime(+t + days * 864e+5); 65 | } 66 | 67 | return (document.cookie = [ 68 | encode(key), '=', stringifyCookieValue(value), 69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 70 | options.path ? '; path=' + options.path : '', 71 | options.domain ? '; domain=' + options.domain : '', 72 | options.secure ? '; secure' : '' 73 | ].join('')); 74 | } 75 | 76 | // Read 77 | 78 | var result = key ? undefined : {}; 79 | 80 | // To prevent the for loop in the first place assign an empty array 81 | // in case there are no cookies at all. Also prevents odd result when 82 | // calling $.cookie(). 83 | var cookies = document.cookie ? document.cookie.split('; ') : []; 84 | 85 | for (var i = 0, l = cookies.length; i < l; i++) { 86 | var parts = cookies[i].split('='); 87 | var name = decode(parts.shift()); 88 | var cookie = parts.join('='); 89 | 90 | if (key && key === name) { 91 | // If second argument (value) is a function it's a converter... 92 | result = read(cookie, value); 93 | break; 94 | } 95 | 96 | // Prevent storing a cookie that we couldn't decode. 97 | if (!key && (cookie = read(cookie)) !== undefined) { 98 | result[name] = cookie; 99 | } 100 | } 101 | 102 | return result; 103 | }; 104 | 105 | config.defaults = {}; 106 | 107 | $.removeCookie = function (key, options) { 108 | if ($.cookie(key) === undefined) { 109 | return false; 110 | } 111 | 112 | // Must not alter options, thus extending a fresh object... 113 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); 114 | return !$.cookie(key); 115 | }; 116 | 117 | })); 118 | -------------------------------------------------------------------------------- /client/lightwave.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LightWAVE [local] 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 36 | 37 | 38 |

    LightWAVE requires a Javascript-enabled browser. 40 | The PhysioBank ATM 41 | provides some of the functions of LightWAVE without Javascript. 42 | 43 | 44 |

    430 | 431 | 432 | 433 | -------------------------------------------------------------------------------- /edit-notes: -------------------------------------------------------------------------------- 1 | On opening an annotation file with annotator name aname for editing: 2 | if aname does not begin with 'new-' { 3 | if new-{aname} does not exist { 4 | annotations(new-{aname}) = annotations(aname) 5 | } 6 | aname = new-{aname} 7 | } 8 | if localeditlog(aname) exists { 9 | sync localeditlog(aname), servereditlog(aname) 10 | } 11 | 12 | On closing an annotation file with annotator name aname: 13 | update localeditlog(aname) 14 | 15 | Periodically (or on exit): 16 | sync all localeditlogs with server 17 | -------------------------------------------------------------------------------- /lw-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # file: lw-server B. Moody 14 June 2022 3 | # 4 | # HTTP server for viewing local files using LightWAVE 5 | 6 | import argparse 7 | import http.server 8 | import os 9 | import shutil 10 | import sys 11 | import tempfile 12 | 13 | 14 | def script_dir(): 15 | return os.path.dirname(os.path.realpath(__file__)) 16 | 17 | 18 | def lightwave_server(db_paths, server_path, client_path, port): 19 | if not db_paths: 20 | db_paths = ['.'] 21 | for path in db_paths: 22 | records_path = os.path.join(path, 'RECORDS') 23 | if not os.path.isfile(records_path): 24 | sys.exit('{} not found'.format(records_path)) 25 | 26 | if server_path: 27 | server_path = shutil.which(server_path) 28 | else: 29 | server_path = os.path.join(script_dir(), 'lightwave') 30 | server_path = os.path.realpath(server_path) 31 | if not os.access(server_path, os.X_OK): 32 | sys.exit('{} not found or not executable'.format(server_path)) 33 | 34 | if not client_path: 35 | client_path = os.path.join(script_dir(), 'client') 36 | client_path = os.path.realpath(client_path) 37 | if not os.path.isdir(client_path): 38 | sys.exit('{} not found'.format(client_path)) 39 | 40 | with tempfile.TemporaryDirectory() as tmp_dir: 41 | cgi_dir = 'cgi-bin' 42 | client_dir = 'lightwave' 43 | home_page = 'lightwave.html' 44 | dbroot_dir = 'data' 45 | 46 | os.mkdir(os.path.join(tmp_dir, cgi_dir)) 47 | os.symlink(server_path, os.path.join(tmp_dir, cgi_dir, 'lightwave')) 48 | os.symlink(client_path, os.path.join(tmp_dir, client_dir)) 49 | 50 | db_list = '' 51 | os.mkdir(os.path.join(tmp_dir, dbroot_dir)) 52 | for path in db_paths: 53 | name = os.path.basename(os.path.abspath(path)) 54 | path = os.path.realpath(path) 55 | os.symlink(path, os.path.join(tmp_dir, dbroot_dir, name)) 56 | db_list += '{}\t{}\n'.format(name, path) 57 | 58 | os.environ['WFDB'] = os.path.join(tmp_dir, dbroot_dir) 59 | os.environ['LIGHTWAVE_DBLIST'] = db_list 60 | os.environ['QUERY_STRING'] = '' 61 | 62 | address = ('127.0.0.1', port) 63 | handler = http.server.CGIHTTPRequestHandler 64 | try: 65 | cls = http.server.ThreadingHTTPServer 66 | except AttributeError: 67 | cls = http.server.HTTPServer 68 | server = cls(address, handler) 69 | port = server.server_address[1] 70 | 71 | url = 'http://localhost:{}/{}/{}'.format(port, client_dir, home_page) 72 | print('LightWAVE server running at:\n {}'.format(url)) 73 | 74 | os.chdir(tmp_dir) 75 | server.serve_forever() 76 | 77 | 78 | def main(): 79 | ap = argparse.ArgumentParser() 80 | ap.add_argument('dbpath', metavar='DIRECTORY', nargs='*', 81 | help='source database (default: current directory)') 82 | ap.add_argument('-P', '--port', type=int, default=0, metavar='PORT', 83 | help='local port number for HTTP server') 84 | ap.add_argument('--server', metavar='PROGRAM', 85 | help='path to lightwave executable') 86 | ap.add_argument('--client', metavar='DIRECTORY', 87 | help='path to lightwave client files') 88 | opts = ap.parse_args() 89 | try: 90 | lightwave_server(opts.dbpath, opts.server, opts.client, opts.port) 91 | except KeyboardInterrupt: 92 | sys.exit(1) 93 | 94 | 95 | if __name__ == '__main__': 96 | main() 97 | -------------------------------------------------------------------------------- /server/cgi.c: -------------------------------------------------------------------------------- 1 | /* file: cgi.h B. Moody 22 February 2019 2 | 3 | Simple functions for parsing CGI environment variables 4 | Copyright (C) 2019 Benjamin Moody 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or (at 9 | your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "cgi.h" 25 | 26 | static struct { 27 | char *name; 28 | char **values; 29 | size_t n_values; 30 | size_t scan_index; 31 | } *query_params; 32 | 33 | static size_t n_query_params; 34 | 35 | #define XALLOC0(arr, n) do { \ 36 | void *p_ = calloc((n), sizeof((arr)[0])); \ 37 | assert(p_ != NULL); \ 38 | (arr) = p_; \ 39 | } while (0) 40 | 41 | #define XREALLOC(arr, n) do { \ 42 | void *p_ = (arr); \ 43 | size_t n_ = (n); \ 44 | size_t m_ = sizeof((arr)[0]); \ 45 | assert(n_ <= ((size_t) -1 / m_)); \ 46 | p_ = realloc(p_, n_ * m_); \ 47 | assert(p_ != NULL); \ 48 | (arr) = p_; \ 49 | } while (0) 50 | 51 | void cgi_init(void) 52 | { 53 | } 54 | 55 | void cgi_end(void) 56 | { 57 | } 58 | 59 | static int xdigitvalue(char c) 60 | { 61 | if (c >= '0' && c <= '9') 62 | return c - '0'; 63 | if (c >= 'A' && c <= 'F') 64 | return c - 'A' + 0xa; 65 | if (c >= 'a' && c <= 'f') 66 | return c - 'a' + 0xa; 67 | return -1; 68 | } 69 | 70 | static char *url_decode(const char *str, size_t len) 71 | { 72 | char *s; 73 | size_t i, j, dlen; 74 | 75 | for (i = dlen = 0; i < len; i++) { 76 | if (str[i] == '%' && i + 2 < len 77 | && xdigitvalue(str[i + 1]) >= 0 78 | && xdigitvalue(str[i + 2]) >= 0) 79 | i += 2; 80 | dlen++; 81 | } 82 | XALLOC0(s, dlen + 1); 83 | 84 | for (i = j = 0; i < len; i++) { 85 | if (str[i] == '%' && i + 2 < len 86 | && xdigitvalue(str[i + 1]) >= 0 87 | && xdigitvalue(str[i + 2]) >= 0) { 88 | s[j] = (xdigitvalue(str[i + 1]) << 4 89 | | xdigitvalue(str[i + 2])); 90 | i += 2; 91 | } 92 | else if (str[i] == '+') 93 | s[j] = ' '; 94 | else 95 | s[j] = str[i]; 96 | j++; 97 | } 98 | assert(j == dlen); 99 | s[j] = 0; 100 | return s; 101 | } 102 | 103 | static void parse_param(const char *str, size_t len) 104 | { 105 | const char *val; 106 | size_t nlen, vlen, i, n; 107 | char *nstr, *vstr; 108 | 109 | val = memchr(str, '=', len); 110 | if (val) { 111 | assert(val >= str && val < str + len); 112 | nlen = val - str; 113 | val++; 114 | vlen = len - nlen - 1; 115 | } 116 | else { 117 | nlen = len; 118 | val = ""; 119 | vlen = 0; 120 | } 121 | 122 | nstr = url_decode(str, nlen); 123 | vstr = url_decode(val, vlen); 124 | 125 | for (i = 0; i < n_query_params; i++) { 126 | if (!strcmp(query_params[i].name, nstr)) { 127 | n = query_params[i].n_values; 128 | XREALLOC(query_params[i].values, n + 1); 129 | query_params[i].values[n] = vstr; 130 | query_params[i].n_values++; 131 | free(nstr); 132 | return; 133 | } 134 | } 135 | 136 | i = n_query_params; 137 | XREALLOC(query_params, i + 1); 138 | query_params[i].name = nstr; 139 | XALLOC0(query_params[i].values, 1); 140 | query_params[i].values[0] = vstr; 141 | query_params[i].n_values = 1; 142 | query_params[i].scan_index = 0; 143 | n_query_params++; 144 | } 145 | 146 | void cgi_process_form(void) 147 | { 148 | const char *qstr; 149 | size_t len; 150 | 151 | query_params = NULL; 152 | n_query_params = 0; 153 | 154 | qstr = getenv("QUERY_STRING"); 155 | if (!qstr) 156 | return; 157 | 158 | while (*qstr) { 159 | len = strcspn(qstr, ";&"); 160 | if (len > 0) 161 | parse_param(qstr, len); 162 | qstr += len; 163 | if (*qstr) 164 | qstr++; 165 | else 166 | break; 167 | } 168 | } 169 | 170 | char *cgi_param(const char *name) 171 | { 172 | size_t i; 173 | for (i = 0; i < n_query_params; i++) 174 | if (query_params && query_params[i].name 175 | && !strcmp(name, query_params[i].name) 176 | && query_params[i].values) 177 | return query_params[i].values[0]; 178 | return NULL; 179 | } 180 | 181 | char *cgi_param_multiple(const char *name) 182 | { 183 | size_t i; 184 | 185 | for (i = 0; i < n_query_params; i++) { 186 | if (query_params && query_params[i].name 187 | && !strcmp(name, query_params[i].name) 188 | && query_params[i].values) { 189 | if (query_params[i].scan_index < query_params[i].n_values) { 190 | query_params[i].scan_index++; 191 | return query_params[i].values[query_params[i].scan_index - 1]; 192 | } 193 | else { 194 | query_params[i].scan_index = 0; 195 | return NULL; 196 | } 197 | } 198 | } 199 | 200 | return NULL; 201 | } 202 | -------------------------------------------------------------------------------- /server/cgi.h: -------------------------------------------------------------------------------- 1 | /* file: cgi.h B. Moody 22 February 2019 2 | 3 | Simple functions for parsing CGI environment variables 4 | Copyright (C) 2019 Benjamin Moody 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or (at 9 | your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #ifndef LIGHTWAVE_CGI_H 21 | #define LIGHTWAVE_CGI_H 22 | 23 | void cgi_init(void); 24 | void cgi_end(void); 25 | void cgi_process_form(void); 26 | char *cgi_param(const char *name); 27 | char *cgi_param_multiple(const char *name); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /server/download.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LightWAVE backup files 6 | 12 | 13 | 14 |

    LightWAVE backup files

    15 | 16 |

    17 | This page contains links to two temporary directories where files are 18 | kept for up to 10 days after you back them up from LightWAVE: 19 | 20 |

    21 |
    logs
    22 |
    This directory contains edit logs that you have backed up within the past 23 | 10 days. Their names are formed from the names of the annotation files with 24 | which they are associated. For example, if you edited the qrs annotation set 25 | for record slpdb/slp60, the associated edit log is 26 | logs/slpdb-slp60.qrs.log. Note that any '/' (path separator) 27 | characters in the record name are replaced by '-' characters in the name of the 28 | edit log. Edit logs are text files. After the first two lines (a header and an 29 | empty line), each edit appears on a line of the log, in the same order in which 30 | you made the edits (oldest first). This order is opposite to the order in which 31 | they can be displayed within LightWAVE (newest first). 32 | 33 |
    database
    34 |
    This directory contains PhysioBank-compatible binary annotation 35 | files that include your edits. The annotation files are stored within 36 | subdirectories that have the same structure as the PhysioBank data 37 | archive. For example, if you created a new annotation file for record 38 | challenge/2013/set-b/b01, you will find it within 39 | database/challenge/2013/set-b/, and its name will be b01.new 40 | (or b01.new1, etc.). If you edited an existing annotation file from 41 | PhysioBank, its name will have an appended underscore, to distinguish it from 42 | the original. For example, if you edited mitdb/100.atr, your edited 43 | version will be stored in database/mitdb/, and its name will 44 | be 100.atr_. 45 |
    46 | 47 |

    48 | To preserve these files it is essential to copy them to permanent 49 | storage! 50 | Transfer them to another location within 10 days of backing them up here. 51 | After 10 days, they are automatically deleted and cannot be recovered. 52 | 53 |

    54 | Note, however, that LightWAVE preserves the edit logs in local storage (on your 55 | computer) indefinitely, and the annotation files can be recreated from the edit 56 | logs. If your backup files are deleted from this server, you can simply back 57 | them up again if you have not deleted them from your browser's local storage. 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /server/lw-apache.conf: -------------------------------------------------------------------------------- 1 | # Sample Apache configuration to be added to Apache's conf.d directory 2 | # (typically /etc/httpd/conf.d or /etc/apache2/conf.d) 3 | 4 | 5 | ServerAdmin webmaster@localhost 6 | #ServerName example.org 7 | 8 | 9 | 10 | FilterDeclare COMPRESS 11 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/html 12 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/plain 13 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/css 14 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/xml 15 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/javascript 16 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/javascript 17 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/json 18 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xml 19 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xhtml+xml 20 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/rss+xml 21 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/atom+xml 22 | FilterProvider COMPRESS DEFLATE resp=Content-Type $image/svg+xml 23 | FilterProvider COMPRESS DEFLATE resp=Content-Type $image/x-icon 24 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/x-font-ttf 25 | FilterProvider COMPRESS DEFLATE resp=Content-Type $font/opentype 26 | FilterChain COMPRESS 27 | FilterProtocol COMPRESS DEFLATE change=yes 28 | 29 | 30 | 31 | AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml 32 | AddOutputFilterByType DEFLATE application/javascript application/json 33 | AddOutputFilterByType DEFLATE application/xml application/rss+xml 34 | AddOutputFilterByType DEFLATE image/svg+xml 35 | 36 | 37 | BrowserMatch ^Mozilla/4 gzip-only-text/html 38 | BrowserMatch ^Mozilla/4\.0[678] no-gzip 39 | BrowserMatch \bMSIE !no-gzip !gzip-only-text/html 40 | 41 | 42 | DocumentRoot /home/physionet/html 43 | 44 | HeaderName HEADER.html 45 | Options Indexes Includes FollowSymLinks 46 | IndexOptions FancyIndexing IconHeight IconWidth SuppressHTMLPreamble 47 | AllowOverride FileInfo Indexes 48 | order allow,deny 49 | allow from all 50 | 51 | 52 | ScriptAlias /cgi-bin/ /home/physionet/cgi-bin/ 53 | 54 | AllowOverride None 55 | Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch 56 | # see comment below 57 | # Header set Access-Control-Allow-Origin "*" 58 | Order allow,deny 59 | Allow from all 60 | 61 | 62 | Alias /lw/ /ptmp/lw/ 63 | 64 | Options -Indexes 65 | AllowOverride None 66 | 67 | 68 | Options +Indexes 69 | IndexIgnore .. 70 | AllowOverride None 71 | 72 | 73 | # Logging section 74 | LogLevel warn 75 | ErrorLog /var/log/apache2/error.log 76 | CustomLog /var/log/apache2/access.log combined 77 | 78 | 79 | 80 | # By default, Apache will not allow annotation backups except from LightWAVE 81 | # clients that were loaded from the web server host. (For example, a local 82 | # copy of the LightWAVE client loaded from a file:// URL would not be allowed 83 | # to back up its annotations.) Uncomment the "Header set ..." directive above 84 | # to permit "foreign" clients to do annotation backups -- but doing this may 85 | # open your server to malicious uploads if it is on the open Internet, so 86 | # be careful! 87 | 88 | -------------------------------------------------------------------------------- /server/lw-scribe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -T 2 | # file: lw-scribe G. Moody 26 March 2013 3 | # Last revised: 23 April 2019 version 0.68 4 | # Receive and save annotations uploaded from a LightWAVE client 5 | 6 | $ENV{PATH} = '/usr/local/bin:/usr/bin:/bin'; 7 | 8 | use CGI qw/:standard/; 9 | use CGI::Cookie; 10 | use File::Path qw/make_path/; 11 | use strict; 12 | use warnings; 13 | 14 | # ------------------------------------------------------------------------------ 15 | # Site-specific code: customize as appropriate. 16 | 17 | # Create a working directory, $wdir, for the output files. 18 | my %cookies = CGI::Cookie->fetch; 19 | my $lwurl = 0; 20 | if ($cookies{'LWURL'}) { 21 | ($lwurl) = ($cookies{'LWURL'}->value =~ m{^(/lw/[-_.\@A-Za-z0-9]{3,32})$}); 22 | } 23 | unless ($lwurl) { 24 | # This block is based on sub unique_id (CGI Programming with Perl, ch. 11) 25 | 26 | # use Apache's mod_unique_id if available 27 | my ($id) = (($ENV{UNIQUE_ID} // '') =~ /^([-\@A-Za-z0-9]+)$/); 28 | unless ($id) { 29 | require Digest::MD5; 30 | 31 | my $md5 = new Digest::MD5; 32 | my $remote = 'local'; 33 | if (defined $ENV{REMOTE_ADDR} && defined $ENV{REMOTE_PORT}) { 34 | $remote = $ENV{REMOTE_ADDR} . $ENV{REMOTE_PORT}; 35 | } 36 | $id = $md5->md5_base64(time, $$, $remote); 37 | $id =~ tr|+/=|-_.|; 38 | } 39 | $lwurl = "/lw/$id"; 40 | ($lwurl) = ($lwurl =~ m{^(/lw/[-_.\@A-Za-z0-9]{3,32})$}); 41 | } 42 | my $lwc = CGI::Cookie->new(-name=>'LWURL', -value=>$lwurl, -expires=>'+10d'); 43 | my $wdir = "/ptmp$lwurl"; 44 | unless (-d $wdir) { 45 | make_path($wdir); 46 | } 47 | 48 | # Change the next line if patchann is not in the standard location. 49 | my $patchann = '/usr/local/bin/patchann'; 50 | 51 | # It should not be necessary to modify anything below this line. 52 | # ------------------------------------------------------------------------------ 53 | 54 | # success200 is executed if log and annotation files are created successfully 55 | sub success200() { 56 | system("ln -sf /ptmp/lw/download.html $wdir/index.html"); 57 | print header(-type=>'application/json', -charset=>'utf-8', 58 | -cookie=>$lwc, -status => "200 OK"); 59 | print "{\"url\": \"$lwurl\"}\n"; 60 | exit; 61 | } 62 | 63 | # err404 is executed if there is a fatal error 64 | sub err404() { 65 | print header(-charset=>'utf-8', -cookie=>$lwc, -status => "404 Not Found"); 66 | exit; 67 | } 68 | 69 | unless (-d $wdir) { err404(); } # nonexistent working directory, quit 70 | 71 | my $fh = upload("file"); 72 | my ($ofile) = (param("file") =~ /^([-a-zA-Z0-9._+]{1,128})$/); 73 | unless ($fh && defined $ofile) { err404(); } # no file upload, quit 74 | 75 | my $data; 76 | my $dbdir = "$wdir/database"; 77 | my $logdir = "$wdir/logs"; 78 | 79 | unless (-d $logdir) { 80 | make_path($logdir); 81 | unless (-d $logdir) { err404(); } # can't create directory for logs 82 | } 83 | 84 | unless (-d $dbdir) { 85 | make_path($dbdir); 86 | unless (-d $dbdir) { err404(); } # can't create directory for annotations 87 | } 88 | 89 | if (open LOG, ">$logdir/$ofile") { 90 | while (read($fh, $data, 1024)) { 91 | print LOG $data; 92 | } 93 | close(LOG); 94 | if ($ofile eq "empty.txt") { 95 | unlink $logdir . "/empty.txt"; 96 | success200(); 97 | } 98 | 99 | chdir($dbdir); 100 | unless (system("$patchann <$logdir/$ofile")) { 101 | success200(); # successful exit 102 | } 103 | } 104 | err404(); # quit if log or annotation file can't be written 105 | -------------------------------------------------------------------------------- /server/patchann.c: -------------------------------------------------------------------------------- 1 | /* file: patchann.c G. Moody 27 March 2013 2 | Last revised: 13 May 2013 3 | Create or patch a PhysioBank-compatible annotation file from a LightWAVE editlog 4 | 5 | Copyright (C) 2012-2013 George B. Moody 6 | 7 | This program is free software; you can redistribute it and/or modify it under 8 | the terms of the GNU General Public License as published by the Free Software 9 | Foundation; either version 2 of the License, or (at your option) any later 10 | version. 11 | 12 | This program is distributed in the hope that it will be useful, but WITHOUT ANY 13 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 14 | PARTICULAR PURPOSE. See the GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License along with 17 | this program; if not, write to the Free Software Foundation, Inc., 59 Temple 18 | Place - Suite 330, Boston, MA 02111-1307, USA. 19 | 20 | You may contact the author by e-mail (george@mit.edu) or postal mail 21 | (MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software, 22 | please visit PhysioNet (http://www.physionet.org/). 23 | _______________________________________________________________________________ 24 | 25 | This program reads a LightWAVE edit log from its standard input and creates a 26 | PhysioBank annotation file containing the original annotations (if any), with 27 | additions, deletions, and modifications specified by the edit log. Think of it 28 | as 'patch' [http://en.wikipedia.org/wiki/Patch_(Unix)]for annotation files. 29 | 30 | The first line of the edit log specifies the record and annotator names, 31 | allowing this program to find the original annotations automatically, if they 32 | exist, and to load them into an in-memory annotation array. The program then 33 | applies the entries of the edit log one at a time, inserting additional 34 | annotations into the array and deleting annotations as needed from the array. 35 | An edit log deletion entry results in a deletion only if the array contains an 36 | exact match for the log entry. (If there is no match, this program issues a 37 | warning and continues to process the remaining log entries.) When all edit log 38 | entries have been processed, the program writes the contents of the array to a 39 | new annotation file. If the annotator name does not begin with 'new' or end 40 | with '_', a set of original annotations exists, a '_' is appended to the name of 41 | the new annotation file so that they can be distinguished from each other. 42 | 43 | LightWAVE edit log format spec: 44 | http://physionet.org/lightwave/doc/edit-log-format.html 45 | PhysioBank annotation file format spec: 46 | http://physionet.org/physiotools/wag/annot-5.htm 47 | _______________________________________________________________________________ 48 | 49 | */ 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | /* In-memory annotation array 57 | 58 | The in-memory annotation array is a doubly-linked list of elements, beginning 59 | with aphead and ending with aptail. aphead is the root element of the array 60 | and never contains an annotation; aptail marks the end of the array and 61 | contains the last annotation unless the array is empty. 62 | 63 | At all times, the contents of the array are kept in canonical order (sorted 64 | first by time, then by chan, and then by num). This property of the array is 65 | established and maintained by insert_ann(). For efficiency, a 48-bit sort key 66 | (t0) is created by insert_ann by concatenating the 32-bit time with the 8-bit 67 | chan and the 8-bit num fields of the annotations. 68 | 69 | Although the output annotation file is sorted in canonical order, note that 70 | this program does not require that its inputs (neither the original annotation 71 | file or the edit log) are in canonical order, so it is not necessary to use 72 | sortann to reorder either its input or its output. 73 | */ 74 | 75 | struct ax { /* in-memory annotation array structure */ 76 | long long t0; /* array is ordered by t0 (48 bits needed) */ 77 | int anntyp; /* annotation type, as in WFDB_Annotation */ 78 | char subtyp; /* annotation subtype, as in WFDB_Annotation */ 79 | char *aux; /* annotation aux string, as in WFDB_Annotation */ 80 | struct ax *next, *prev; /* successor and predecessor pointers */ 81 | }; 82 | 83 | struct ax *aphead; /* head of annotation array (aphead->next points to the 84 | first annotation; aphead does not contain an 85 | annotation) */ 86 | struct ax *aptail; /* tail of annotation array (contains an annotation 87 | unless aptail = aphead, i.e., unless the array is 88 | empty) */ 89 | 90 | char *record, *annotator, logtext[500]; 91 | double sps; 92 | WFDB_Annotation annot; /* current annotation to be processed */ 93 | 94 | int delete_ann(), insert_ann(), get_log_entry(), parse_log_header(); 95 | 96 | int main(int argc, char **argv) { 97 | char *abuf, buf[256], *fname, *oaname = NULL, *p, *pname = argv[0]; 98 | FILE *afile; 99 | int match = 0, n; 100 | struct ax *ap, *apthis; 101 | WFDB_Anninfo ai; 102 | 103 | if (parse_log_header() == 0) { 104 | fprintf(stderr, "%s: can't parse input file (format error)\n", pname); 105 | exit(1); 106 | } 107 | 108 | SUALLOC(aphead, sizeof(struct ax), 1); 109 | aptail = aphead; 110 | wfdbquiet(); 111 | ai.name = annotator; 112 | ai.stat = WFDB_READ; 113 | SUALLOC(oaname, strlen(annotator) + 2, 1); 114 | if (strncmp(annotator, "new", 3) && annotator[strlen(annotator)-1] != '_') 115 | sprintf(oaname, "%s_", annotator); 116 | else 117 | sprintf(oaname, "%s", annotator); 118 | if (annopen(record, &ai, 1) == 0){ /* input annotator opened successfully */ 119 | while (getann(0, &annot) == 0) /* read the original annotations */ 120 | (void)insert_ann(); /* copy them into the in-memory array */ 121 | ai.name = oaname; /* use oaname only if input annotator exists */ 122 | } 123 | /* Failure to open the original annotation file is not an error (it simply 124 | means that the edit log will be used to create an entirely new set of 125 | annotations). Failure to open the output is fatal, however. */ 126 | ai.stat = WFDB_WRITE; 127 | if (annopen(record, &ai, 1) != 0) { /* can't open output annotator */ 128 | fprintf(stderr, "%s: can't write output annotation file '%s.%s\n", 129 | record, ai.name); 130 | SFREE(oaname); 131 | SFREE(record); 132 | SFREE(annotator); 133 | wfdbquit(); 134 | exit(2); 135 | } 136 | 137 | /* read the edit log and merge it with the in-memory array */ 138 | while (n = get_log_entry()) { 139 | switch (n) { 140 | case 1: insert_ann(); break; 141 | case 2: delete_ann(); break; 142 | default: break; /* warn about bad input, continue processing */ 143 | } 144 | } 145 | 146 | /* Write the in-memory array to the output annotation file */ 147 | ap = aphead->next; 148 | while (ap) { 149 | SFREE(ap->prev); 150 | annot.anntyp = ap->anntyp; 151 | annot.subtyp = ap->subtyp; 152 | annot.chan = ((ap->t0) >> 8) & 255; 153 | annot.num = ((ap->t0) & 255) - 128; 154 | annot.time = (ap->t0) >> 16; 155 | annot.aux = ap->aux; 156 | putann(0, &annot); 157 | ap = ap->next; 158 | SFREE(annot.aux); 159 | } 160 | SFREE(aptail); 161 | 162 | /* Create or update ANNOTATORS. */ 163 | for (p = record + strlen(record) - 1; p > record && *p != '/'; p--) 164 | ; 165 | *p = '\0'; /* what's left of record is the directory name */ 166 | SUALLOC(fname, strlen(record) + 12, 1); 167 | sprintf(fname, "%s/ANNOTATORS", record); /* pathname of ANNOTATORS */ 168 | SUALLOC(abuf, strlen(ai.name) + 30, 1); 169 | sprintf(abuf, "%s\tcreated using LightWAVE\n", ai.name); 170 | if (afile = fopen(fname, "r")) { 171 | while (!match && fgets(buf, sizeof(buf), afile)) 172 | if (strncmp(abuf, buf, strlen(ai.name) + 1) == 0) match = 1; 173 | fclose(afile); 174 | } 175 | if (!match && (afile = fopen(fname, "a"))) { 176 | fprintf(afile, "%s", abuf); 177 | fclose(afile); 178 | } 179 | SFREE(abuf); 180 | SFREE(fname); 181 | 182 | SFREE(oaname); 183 | SFREE(record); 184 | SFREE(annotator); 185 | 186 | wfdbquit(); 187 | exit(0); 188 | } 189 | 190 | /* Get the record and annotator names, and the sampling frequency, from 191 | the header. Return 1 if successful, 0 otherwise. */ 192 | int parse_log_header() { 193 | char *p, *q; 194 | 195 | /* Read and parse header line 1. */ 196 | fgets(logtext, sizeof(logtext), stdin); 197 | 198 | if (strncmp(logtext, "[LWEditLog-1.0] Record ", 23)) return 0; 199 | 200 | for (p = q = logtext+23; *q && *q != ','; q++) 201 | ; 202 | if (*q) *q = '\0'; 203 | 204 | if (strncmp(q+1, " annotator ", 11)) return 0; 205 | SSTRCPY(record, p); 206 | 207 | for (p = q = q+12; *q && *q != ' '; q++) 208 | ; 209 | if (*q) *q = '\0'; 210 | if (*(q+1) != '(') { 211 | SFREE(record); 212 | return 0; 213 | } 214 | SSTRCPY(annotator, p); 215 | 216 | for (p = q = q+2; *q && *q != ' '; q++) 217 | ; 218 | if (*q) *q = '\0'; 219 | if (strncmp(q+1, "samples/second)", 15)) { 220 | SFREE(record); 221 | SFREE(annotator); 222 | return 0; 223 | } 224 | sscanf(p, "%lf", &sps); 225 | 226 | /* Read header line 2, which should be empty. */ 227 | fgets(logtext, sizeof(logtext), stdin); 228 | if (*logtext != '\r' && *logtext != '\n') { 229 | SFREE(record); 230 | SFREE(annotator); 231 | return 0; 232 | } 233 | 234 | return 1; /* success! */ 235 | } 236 | 237 | int get_log_entry() { 238 | char p[500], *q; 239 | int edittype, i, j, len; 240 | WFDB_Time ti, tf; 241 | 242 | /* Read the next log entry, return 0 if no more entries. */ 243 | if (!fgets(logtext, sizeof(logtext), stdin)) return 0; 244 | 245 | /* Fill in the default fields of annot. */ 246 | annot.anntyp = NORMAL; 247 | annot.subtyp = annot.chan = annot.num = 0; 248 | annot.aux = NULL; 249 | 250 | /* Copy the entry to p and replace the line ending with a null (logtext will 251 | not be modified below). */ 252 | len = strlen(logtext); 253 | strncpy(p, logtext, len + 1); 254 | if (p[len - 1] == '\n') { 255 | if (p[len - 2] == '\r') p[len - 2] = '\0'; 256 | else p[len - 1] = '\0'; 257 | } 258 | 259 | /* Identify the action associated with this log entry. */ 260 | if (logtext[0] == '-') { edittype = 2; i = 1; } /* deletion */ 261 | else { edittype = 1; i = 0; } /* insertion */ 262 | 263 | /* Parse the rest of the entry to fill in the time and any non-default 264 | fields of annot. */ 265 | 266 | /* find and isolate digits */ 267 | q = p+i; 268 | for ( ; p[i]; i++) 269 | if (p[i] < '0' || p[i] > '9') break; 270 | p[i] = '\0'; 271 | if (strlen(q) == 0 || sscanf(q, "%ld", &ti) != 1 || ti < 0) return -1; 272 | annot.time = ti; 273 | 274 | /* is there anything else on this line? */ 275 | if (logtext[i] == '\r' || logtext[i] == '\n') 276 | return edittype; /* nothing more to parse */ 277 | 278 | else if (logtext[i] != ',' && logtext[i] != '-') return -1; 279 | /* something else is there but it can't be parsed */ 280 | 281 | else if (logtext[i] == '-') { 282 | /* it looks like tf is there */ 283 | q = p + i+1; 284 | for (++i; p[i]; i++) 285 | if (p[i] < '0' || p[i] > '9') break; 286 | p[i] = '\0'; 287 | if (strlen(q) == 0 || sscanf(q, "%ld", &tf) != 1 || tf < ti) 288 | return -1; 289 | 290 | /* no support for tf in the WFDB library yet; warn, but continue 291 | parsing the rest of this log entry */ 292 | fprintf(stderr, "(warning): no support for tf at %ld", annot.time); 293 | 294 | if (logtext[i] == '\r' || logtext[i] == '\n') 295 | return edittype; /* nothing more to parse */ 296 | } 297 | 298 | /* next should be anntype */ 299 | q = p + i+1; 300 | /* look for end of anntype, but skip the first character since it 301 | might be '{' or ',' if a non-standard type was defined */ 302 | for (i += 2; p[i] && p[i] != ',' && p[i] != '{'; i++) 303 | ; 304 | p[i] = '\0'; 305 | annot.anntyp = strann(q); 306 | if (annot.anntyp == NOTQRS) { 307 | /* unrecognized type string: set anntyp to NOTE, copy string to aux */ 308 | annot.anntyp = NOTE; 309 | *(q-1) = strlen(q); /* aux strings have byte count prefix */ 310 | SSTRCPY(annot.aux, q-1); 311 | } 312 | 313 | /* is (subtype/chan/num) present? */ 314 | if (logtext[i] == '{') { 315 | for (j = ++i; p[j] && p[j] != '/'; j++) 316 | ; 317 | if (p[j] != '/') return -1; 318 | if (j > i) { p[j] = '\0'; annot.subtyp = atoi(p+i); } 319 | i = j+1; 320 | 321 | for (j = i; p[j] && p[j] != '/'; j++) 322 | ; 323 | if (p[j] != '/') return -1; 324 | if (j > i) { p[j] = '\0'; annot.chan = atoi(p+i); } 325 | i = j+1; 326 | 327 | for (j = i; p[j] && p[j] != '}'; j++) 328 | ; 329 | if (p[j] != '}') return -1; 330 | if (j > i) { p[j] = '\0'; annot.num = atoi(p+i); } 331 | i = j+1; 332 | } 333 | 334 | /* is aux present? */ 335 | if (logtext[i] == ',') { 336 | int len = strlen(p+i+1), maxlen = 255; 337 | 338 | if (annot.aux) { /* true if type was unrecognized, see above */ 339 | unsigned char *s = annot.aux + strlen(annot.aux) - 1; 340 | p[i--] = ':'; /* prefix user-specified aux with type and colon */ 341 | while (s > annot.aux) 342 | p[i--] = *s--; /* safe (annot.aux is a substring of p[0..i]) */ 343 | } 344 | len = strlen(p+i+1); 345 | if (len > 255) { /* check length and truncated if necessary */ 346 | fprintf(stderr, "(warning): aux will be truncated at %ld", 347 | annot.time); 348 | len = 255; 349 | p[i+len+1] == '\0'; 350 | } 351 | p[i] = len; /* aux strings have byte count prefix */ 352 | SSTRCPY(annot.aux, p+i); 353 | } 354 | 355 | return edittype; /* 1 (insertion), or 2 (deletion) */ 356 | } 357 | 358 | /* Insert annot into the in-memory annotation array in time/chan/num order. */ 359 | int insert_ann() { 360 | struct ax *ap, *apthis; 361 | 362 | /* Allocate memory for the annotation to be inserted. */ 363 | SUALLOC(apthis, sizeof(struct ax), 1); 364 | 365 | /* Load the fields of annot into *apthis. */ 366 | apthis->anntyp = annot.anntyp; 367 | apthis->subtyp = annot.subtyp; 368 | apthis->t0 = (annot.time << 16) | 369 | ((annot.chan & 255) << 8) | ((annot.num + 128) & 255); 370 | if (annot.aux && *(annot.aux)) SSTRCPY(apthis->aux, annot.aux); 371 | 372 | /* Find the correct position for *apthis and insert it there. */ 373 | for (ap = aptail; ap; ap = ap->prev) { 374 | if (ap->t0 < apthis->t0) { /* insert apthis between ap and ap->next */ 375 | apthis->prev = ap; /* link apthis to its predecessor ... */ 376 | apthis->next = ap->next; /* and to its successor (may be null) */ 377 | apthis->prev->next = apthis; /* link its predecessor to apthis */ 378 | if (apthis->next) /* if apthis has a successor ... */ 379 | apthis->next->prev = apthis; /* link it back to apthis */ 380 | else aptail = apthis; /* otherwise apthis is now the tail */ 381 | break; 382 | } 383 | } 384 | return 1; 385 | } 386 | 387 | /* delete an exact match of annot, if it exists, from the in-memory array */ 388 | int delete_ann() { 389 | long long t0; 390 | struct ax *ap; 391 | 392 | t0 = (annot.time << 16) | ((annot.chan&255) << 8) | ((annot.num+128) & 255); 393 | for (ap = aptail; ap; ap = ap->prev) { 394 | if (t0 == ap->t0 && 395 | annot.anntyp == ap->anntyp && annot.subtyp == ap->subtyp && 396 | (!annot.aux && !ap->aux) || 397 | (annot.aux && ap->aux && !strcmp(annot.aux, ap->aux))) { 398 | ap->prev->next = ap->next; /* link predecessor to successor */ 399 | if (ap->next) /* if ap has a successor ... */ 400 | ap->next->prev = ap->prev; /* link successor to predecessor */ 401 | else aptail = ap->prev; /* otherwise predecessor is now the tail */ 402 | SFREE(ap->aux); 403 | SFREE(ap); 404 | return 1; 405 | } 406 | } 407 | return 0; 408 | } 409 | -------------------------------------------------------------------------------- /server/sandbox.c: -------------------------------------------------------------------------------- 1 | /* file: sandbox.c B. Moody 22 February 2019 2 | Last revised: 26 July 2023 version 0.72 3 | 4 | Simple sandbox for the LightWAVE server 5 | Copyright (C) 2019 Benjamin Moody 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or (at 10 | your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, but 13 | WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #define _GNU_SOURCE 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #ifndef SYS_SECCOMP 37 | # define SYS_SECCOMP 1 38 | #endif 39 | 40 | #define FAIL(msg) do { \ 41 | fprintf(stderr, "sandboxed-lightwave: %s\n", msg); \ 42 | abort(); \ 43 | } while (0) 44 | #define FAILERR(msg) do { \ 45 | perror("sandboxed-lightwave: " msg); \ 46 | abort(); \ 47 | } while (0) 48 | 49 | static void set_hard_rlimit(int resource, rlim_t value) 50 | { 51 | struct rlimit rlim; 52 | if (getrlimit(resource, &rlim) != 0) 53 | FAILERR("cannot get resource limits"); 54 | if (rlim.rlim_max == RLIM_INFINITY || rlim.rlim_max > value) 55 | rlim.rlim_max = value; 56 | if (rlim.rlim_cur == RLIM_INFINITY || rlim.rlim_cur > value) 57 | rlim.rlim_cur = value; 58 | if (setrlimit(resource, &rlim) != 0) 59 | FAILERR("cannot set resource limits"); 60 | } 61 | 62 | static void pr_str(const char *str) 63 | { 64 | if (str) 65 | write(STDERR_FILENO, str, strlen(str)); 66 | } 67 | 68 | static void pr_hex(unsigned long value) 69 | { 70 | char c; 71 | if (value >= 16) pr_hex(value / 16); 72 | else pr_str("0x"); 73 | c = (value % 16 < 10 ? '0' + value % 16 : 'a' + value % 16 - 10); 74 | write(STDERR_FILENO, &c, 1); 75 | } 76 | 77 | static void handle_sigsys(int signum, siginfo_t *info, void *context0) 78 | { 79 | ucontext_t *context = context0; 80 | if (info->si_code == SYS_SECCOMP) { 81 | pr_str("*** blocked system call "); pr_hex(info->si_syscall); 82 | pr_str(" arch="); pr_hex(info->si_arch); 83 | #ifdef __x86_64__ 84 | pr_str(" rdi="); pr_hex(context->uc_mcontext.gregs[REG_RDI]); 85 | pr_str(" rsi="); pr_hex(context->uc_mcontext.gregs[REG_RSI]); 86 | pr_str(" rdx="); pr_hex(context->uc_mcontext.gregs[REG_RDX]); 87 | pr_str(" r10="); pr_hex(context->uc_mcontext.gregs[REG_R10]); 88 | pr_str(" r8="); pr_hex(context->uc_mcontext.gregs[REG_R8]); 89 | pr_str(" r9="); pr_hex(context->uc_mcontext.gregs[REG_R9]); 90 | #elif defined(__i386__) 91 | pr_str(" ebx="); pr_hex(context->uc_mcontext.gregs[REG_EBX]); 92 | pr_str(" ecx="); pr_hex(context->uc_mcontext.gregs[REG_ECX]); 93 | pr_str(" edx="); pr_hex(context->uc_mcontext.gregs[REG_EDX]); 94 | pr_str(" esi="); pr_hex(context->uc_mcontext.gregs[REG_ESI]); 95 | pr_str(" edi="); pr_hex(context->uc_mcontext.gregs[REG_EDI]); 96 | pr_str(" ebp="); pr_hex(context->uc_mcontext.gregs[REG_EBP]); 97 | #endif 98 | pr_str(" ["); 99 | pr_str(seccomp_syscall_resolve_num_arch(info->si_arch, 100 | info->si_syscall)); 101 | pr_str("]\n"); 102 | } 103 | raise(signum); 104 | } 105 | 106 | void lightwave_sandbox() 107 | { 108 | uid_t effectiveuid = geteuid(); 109 | uid_t realuid = getuid(); 110 | gid_t realgid = getgid(); 111 | char *rootdir, *dbcalfile; 112 | struct sigaction sa; 113 | scmp_filter_ctx ctx; 114 | cap_t no_capabilities; 115 | 116 | /* chdir and chroot into $LIGHTWAVE_ROOT, so only files in that 117 | directory can be read */ 118 | rootdir = getenv("LIGHTWAVE_ROOT"); 119 | if (!rootdir) { 120 | #ifdef LW_ROOT 121 | rootdir = LW_ROOT; 122 | #else 123 | FAIL("LIGHTWAVE_ROOT not set"); 124 | #endif 125 | } 126 | 127 | if (seteuid(realuid) != 0) 128 | FAILERR("cannot set effective user ID"); 129 | if (setregid(realgid, realgid) != 0) 130 | FAILERR("cannot set real/effective group ID"); 131 | 132 | /* If $LIGHTWAVE_WFDBCAL is set, use it as the path to a 133 | calibration file stored outside the root directory. */ 134 | dbcalfile = getenv("LIGHTWAVE_WFDBCAL"); 135 | if (dbcalfile) { 136 | if (!freopen(dbcalfile, "r", stdin)) 137 | FAILERR("cannot read $LIGHTWAVE_WFDBCAL"); 138 | setenv("WFDBCAL", "-", 1); 139 | } 140 | 141 | if (chdir(rootdir) != 0) 142 | FAILERR("cannot chdir to $LIGHTWAVE_ROOT"); 143 | 144 | if (effectiveuid == 0) { 145 | if (seteuid(0) != 0) 146 | FAILERR("cannot set effective user ID"); 147 | if (chroot(".") != 0) 148 | FAILERR("cannot chroot to $LIGHTWAVE_ROOT"); 149 | if (setreuid(realuid, realuid) != 0) 150 | FAILERR("cannot set real/effective user ID"); 151 | } 152 | else { 153 | if (unshare(CLONE_NEWUSER) != 0) 154 | FAILERR("cannot create user namespace"); 155 | if (chroot(".") != 0) 156 | FAILERR("cannot chroot to $LIGHTWAVE_ROOT"); 157 | no_capabilities = cap_init(); 158 | if (cap_set_proc(no_capabilities) != 0) 159 | FAILERR("cannot set process capabilities"); 160 | cap_free(no_capabilities); 161 | } 162 | 163 | if (prctl(PR_SET_NO_NEW_PRIVS, 1UL, 0UL, 0UL, 0UL) != 0) 164 | FAILERR("cannot set no-new-privs"); 165 | 166 | /* resource limits */ 167 | set_hard_rlimit(RLIMIT_CORE, 0); 168 | set_hard_rlimit(RLIMIT_FSIZE, 0); 169 | set_hard_rlimit(RLIMIT_SIGPENDING, 256); 170 | set_hard_rlimit(RLIMIT_MEMLOCK, 1024 * 1024); 171 | set_hard_rlimit(RLIMIT_NOFILE, 256); 172 | set_hard_rlimit(RLIMIT_MSGQUEUE, 0); 173 | set_hard_rlimit(RLIMIT_CPU, 60); 174 | set_hard_rlimit(RLIMIT_NPROC, 1000); 175 | set_hard_rlimit(RLIMIT_AS, 512 * 1024 * 1024); 176 | 177 | /* handle SIGSYS by displaying an error message and exiting */ 178 | sa.sa_sigaction = &handle_sigsys; 179 | sigemptyset(&sa.sa_mask); 180 | sa.sa_flags = SA_SIGINFO; 181 | sigaction(SIGSYS, &sa, NULL); 182 | 183 | /* all system calls not whitelisted below will raise SIGSYS */ 184 | if ((ctx = seccomp_init(SCMP_ACT_TRAP)) == NULL) 185 | FAIL("seccomp_init failed"); 186 | 187 | /* permit following system calls with any arguments */ 188 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); 189 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); 190 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0); 191 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0); 192 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); 193 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0); 194 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); 195 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getcwd), 0); 196 | seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); 197 | 198 | /* permit open(..., O_RDONLY) and openat(AT_FDCWD, ..., O_RDONLY) 199 | (openat without AT_FDCWD would allow a local attacker to escape 200 | from an outer chroot environment; the same goes for fchdir or 201 | any of the other *at() functions.) */ 202 | seccomp_rule_add_exact 203 | (ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1, 204 | SCMP_A1(SCMP_CMP_EQ, O_RDONLY)); 205 | seccomp_rule_add_exact 206 | (ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 2, 207 | SCMP_A0(SCMP_CMP_EQ, (uint32_t) AT_FDCWD), 208 | SCMP_A2(SCMP_CMP_EQ, O_RDONLY)); 209 | 210 | /* deny newfstatat(fd, ..., ..., AT_EMPTY_PATH) 211 | (could allow a local attacker to examine files outside an outer 212 | chroot environment; unfortunately seccomp can't distinguish 213 | empty from non-empty paths. If a working fstat() function is 214 | actually needed, it must be implemented using the fstat system 215 | call rather than newfstatat.) */ 216 | seccomp_rule_add_exact 217 | (ctx, SCMP_ACT_ERRNO(ENOSYS), SCMP_SYS(newfstatat), 2, 218 | SCMP_A0(SCMP_CMP_NE, (uint32_t) AT_FDCWD), 219 | SCMP_A3(SCMP_CMP_EQ, AT_EMPTY_PATH)); 220 | 221 | /* permit mmap(..., PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, ...) 222 | (typically lightwave doesn't allocate any huge blocks of memory 223 | that would make this necessary, but it's good future-proofing) */ 224 | seccomp_rule_add_exact 225 | (ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 2, 226 | SCMP_A2(SCMP_CMP_MASKED_EQ, ~(PROT_READ | PROT_WRITE), 0), 227 | SCMP_A3(SCMP_CMP_EQ, (MAP_ANONYMOUS | MAP_PRIVATE))); 228 | 229 | /* activate the filter */ 230 | if (seccomp_load(ctx) != 0) 231 | FAIL("seccomp_load failed"); 232 | } 233 | -------------------------------------------------------------------------------- /server/sandbox.h: -------------------------------------------------------------------------------- 1 | /* file: sandbox.c B. Moody 22 February 2019 2 | 3 | Simple sandbox for the LightWAVE server 4 | Copyright (C) 2019 Benjamin Moody 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or (at 9 | your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, but 12 | WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #ifndef LIGHTWAVE_SANDBOX_H 21 | #define LIGHTWAVE_SANDBOX_H 22 | 23 | #ifndef SANDBOX 24 | #include 25 | static void lightwave_sandbox() 26 | { 27 | if (geteuid() == 0 || getegid() == 0) { 28 | fprintf(stderr, "lightwave: refusing to run as superuser\n"); 29 | abort(); 30 | } 31 | } 32 | #else 33 | void lightwave_sandbox(); 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /server/setrepos.c: -------------------------------------------------------------------------------- 1 | /* file: setrepos.c G. Moody 17 February 2013 2 | Last revised: 23 April 2019 3 | 4 | This file defines the LightWAVE server's setrepos() macro, which is used to 5 | define the set of data repositories accessible via the server. LW_WFDB can be 6 | defined at compile time to reset the WFDB path. The value of LW_WFDB is 7 | a space-separated ordered list of components, which may be either directory 8 | names or URL prefixes. Each component is a data repository. For example, 9 | LW_WFDB may be defined as: 10 | 11 | LW_WFDB="/usr/local/database http://physionet.org/physiobank/database" 12 | 13 | Since spaces separate components, names of data repositories may not contain 14 | spaces. See http://physionet.org/physiotools/wpg/database-path.htm for details. 15 | 16 | If LW_WFDB is not defined, the LightWAVE server's WFDB path is the value of 17 | the WFDB environment variable, if set, or else the default WFDB path (the value 18 | of DEFWFDB as defined in at the time the WFDB library was 19 | compiled). 20 | */ 21 | 22 | #ifdef LW_WFDB 23 | #define setrepos() do { \ 24 | if (!getenv("WFDB")) \ 25 | setwfdb(LW_WFDB); \ 26 | } while (0) 27 | #else 28 | #define setrepos() 29 | #endif 30 | --------------------------------------------------------------------------------