#!/usr/bin/perl # file: chart G. Moody 7 October 2000 # Last revised: 8 February 2008 # _____________________________________________________________________________ # The Chart-O-Matic (CGI program to display PhysioBank data in a web browser) # Copyright (C) 2000-2008 George B. Moody # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # You may contact the author by e-mail (george@mit.edu) or postal mail # (MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software, # please visit PhysioNet (http://www.physionet.org/). # _____________________________________________________________________________ use CGI qw(:standard); use CGI::Carp 'fatalsToBrowser'; $| = 1; # _____________________________________________________________________________ # Configuration constants: customize as needed for your installation $PNH = '/home/physionet/html/'; # root of the http document directory $PBD = $PNH . 'physiobank/database/'; # directory containing databases $PTMP = '/ptmp/charts/'; # cache for generated charts $CONVERT = '/usr/bin/convert'; # path to 'convert' from ImageMagick $MAIL = '/bin/mail'; # path to command-line 'mail' utility $PSCHART = '/usr/bin/pschart'; # path to 'pschart' from WFDB package $TIMETOSEC = '/usr/bin/time2sec'; # path to 'time2sec' from WFDB package # _____________________________________________________________________________ # Make sure that PTMP exists and is world-readable and -writable! umask 0; mkdir "$PTMP", 0777; # Read the list of databases and their descriptions (once only). if (!$databases[0]) { read_dblist(); } # Decide what is to be done next. if (param) { $action = param('doit'); # the name of the button that was clicked, if any } else { $action = "Select database"; # default: choose a database on entry } if ($action eq "Select database") { choose_db(); } else { # database has been chosen; get lists of records and annotators $database = param('database'); if (!param('record')) { choose_record(); # pick one of each (also time and scale) $action = "Continue"; } $record = param('record'); if ($record =~ /.+\n/) { chop($record); } $annotator = param('annotator'); $tstart = param('tstart'); $width = param('width'); if ($action ne 'Continue') { output_results(); # depending on which button clicked, show or email } } # _____________________________________________________________________________ # Read the list of available databases. sub read_dblist { $dblistfile = $PBD . 'DBS-with-signals'; if (open(DBS,$dblistfile)) { @dblist = ; $i = 0; foreach $d (@dblist) { @fields = split(/\t+/,$d); chop($fields[1]); $dblist[$i++] = $fields[0]; $dblabels{$fields[0]} = $fields[1] . " (" . $fields[0] . ")"; } } else { @dblist = (''); } } # _____________________________________________________________________________ # Show the database choice form. sub choose_db { print header, start_html(-dtd=>'-//IETF//DTD HTML//EN', -bgcolor=>'white',-title=>'Chart-O-Matic'); print_header(); print start_form, table({-border=>0,-bgcolor=>'#d0d0ff',-width=>'100%'}, Tr({-align=>CENTER,-valign=>CENTER}, [ th('Database:'. popup_menu(-name=>'database', -value=>[@dblist], -labels=>{%dblabels}). submit(-name=>'doit',-value=>'Continue')) ] )), end_form; print h2('Chart-O-Matic: View signals and annotations'); print < The Chart-O-Matic allows you to obtain a "chart recording" of a portion of a PhysioBank record. Please choose a database from the list above, then click on Continue. Once you have done so, you will be able to choose a record, an annotator (if available), the starting time, and the size of the chart recording. You will be able to view the chart with your web browser, or you can have it sent to you via e-mail.

A key for the annotation codes is available here. To view the annotations, including the times of occurrence and other attributes, as text, try the rdann-O-Matic. To view the digitized waveforms as text, try the rdsamp-O-Matic.


EOF print_footer(); } # _____________________________________________________________________________ # Read the list of records for the chosen database. sub read_rlist { $rlistfile = $PBD . $database . '/RECORDS'; if (open(RECORDS,$rlistfile)) { @rlist = ; } else { @rlist = (''); } } # _____________________________________________________________________________ # Read the list of annotators for the chosen database. sub read_alist { $alistfile = $PBD . $database . '/ANNOTATORS'; if (open(ANNOTATORS,$alistfile)) { @annotators = ; $i = 0; foreach $a (@annotators) { @fields = split(/\t+/,$a); chop($fields[1]); $alist[$i++] = $fields[0]; $adlist{$fields[0]} = $fields[0] . " (" . $fields[1] . ")"; } $alist[$i++] = ''; $adlist{''} = "(no annotations)"; } else { $alist[0] = ''; $adlist{''} = ('(no annotations)'); } } # _____________________________________________________________________________ # Show the record/annotator/start/chart width choice form. sub choose_record { read_rlist(); read_alist(); print header, start_html(-dtd=>'-//IETF//DTD HTML//EN', -bgcolor=>'white',-title=>'Chart-O-Matic ('. $dblabels{$database}.")"); print_header(); print start_form, table({-border=>0,-bgcolor=>'#d0d0ff',-width=>'100%'}, Tr({-valign=>CENTER}, [ th({-align=>RIGHT}, 'Database:') . td({-align=>LEFT}, '" . $dblabels{$database} . "", submit(-name=>'doit', -value=>'Select database')), th({-align=>RIGHT},'Record:') . td({-align=>LEFT}, popup_menu(-name=>'record', -values=>[@rlist])) . td({-align=>RIGHT}, 'Convert signals to text'), th({-align=>RIGHT},'Annotator:') . td({-align=>LEFT}, popup_menu(-name=>'annotator', -values=>[@alist], -labels=>{%adlist})) . td({-align=>RIGHT}, 'Convert annotations to text'), th({-align=>RIGHT},'Start time:') . td({-align=>LEFT},textfield(-name=>'tstart', -size=>40, -value=>'0')) . td({-align=>RIGHT}, 'Annotation key'), th({-align=>RIGHT},'Chart width:') . td({-align=>LEFT},radio_group(-name=>'width', -value=>['small', 'medium','large'], -default=>'medium')), td({-align=>RIGHT}, submit(-name=>'doit',-value=>'Show chart')) . td({-align=>LEFT},submit(-name=>'doit', -value=>'E-mail chart to:'), textfield(-name=>'email',-size=>20)) . td({-align=>RIGHT},submit(-name=>'doit', -value=>'Chart-O-Matic Help')) ] )), hidden(-name=>'database',-value=>$database), end_form; } # _____________________________________________________________________________ # Show instructions for filling in the record/annotator choice form. sub print_help { print h2('Chart-O-Matic: View signals and annotations'); print < Please choose a record and an annotator from the lists shown above (Choose "(no annotations)" if you wish to view signals without annotations.)

To view the requested chart with your web browser, click on Show chart. Use your browser's Save or Save As features to copy the chart to a file on your disk if you wish to save it, or use the link provided above the chart to download a high-resolution PostScript version of the chart.

To obtain the requested chart in PostScript format via e-mail, enter your e-mail address in the space provided above, then click on E-mail chart to:.

In the start time field, enter the elapsed time from the beginning of the record, in hh:mm:ss format. For example, to begin two minutes from the beginning of the record, enter 2:0 in the start time field (leading zero digits may be omitted). Depending on the chart width you choose, the chart will show 5, 10, or 15 seconds of data beginning at the specified time. If you choose a medium or large plot, you may need to use your web browser's horizontal scroll bar to view the right-hand side of the plot.

A key for the annotation codes is available here. To view the annotations, including the times of occurrence and other attributes, as text, try the rdann-O-Matic. To view the digitized waveforms as text, try the rdsamp-O-Matic.

The chart output is generated by pschart, which is freely available in portable C source form and in precompiled binary versions for several popular operating systems. You may run pschart on your own computer to obtain charts from PhysioNet and other sources in PostScript form.


EOF } # _____________________________________________________________________________ # Convert the $tstart string into elapsed time in seconds. sub strtim{ if ($tstart =~ /\[.+/) { # $tstart is absolute; convert using time2sec $t2sfile = $PTMP . "t2s.$$"; unless (fork) { open(STDOUT, ">$t2sfile"); exec($TIMETOSEC, "-r", $database . "/" . $record, $tstart); } wait; open(T2SFILE, $t2sfile); $sec = ; chop($sec); close(T2SFILE); unlink $t2sfile; } else { # $tstart is relative; convert to seconds here ($t1,$t2,$t3,$t4) = split(/:/,$tstart); if ($t4 eq "") { if ($t3 eq "") { if ($t2 eq "") { $sec = $t1; } else { $sec = 60 * $t1 + $t2; } } else { $sec = 3600 * $t1 + 60 * $t2 + $t3; } } else { $sec = 86400 * $t1 + 3600 * $t2 + 60 * $t3 + $t4; } } return $sec; } # _____________________________________________________________________________ # Show or email the selected chart. sub output_results { set_chart_variables(); # Since the user can pass any strings into this program, make sure that # the variables used to generate the file name have no characters other # than letters, digits, and underscores. (Record names can also have a # '.' -- this is needed for EDF input.) $sdb = $database; $sdb =~ tr/A-Za-z0-9_/-/cs; $srec = $record; $srec =~ tr/A-Za-z0-9_./-/cs; $sann = $annotator; $sann =~ tr/A-Za-z0-9_/-/cs; $stime = strtim(); $stime =~ tr/A-Za-z0-9_/-/cs; $swid = $width; $swid =~ tr/A-Za-z0-9_/-/cs; if ($action ne 'E-mail chart to:') { # show chart in browser window $orientation = "-l"; # this is a hack -- we just want the default # (portrait) orientation in this case, so we # repeat the -l (show signal names) option } else { # email chart to user $orientation = "-L"; # use landscape mode in this case } $chname = $sdb . "-" . $srec . "-" . $sann . "-" . $stime . "-" . $swid . $orientation; # Check if the requested chart is already in the cache. $tofile = $PTMP . $chname . ".ps"; if (!open(TOFILE, $tofile)) { # Create a PostScript version of the requested chart using pschart. # First, create a script for pschart. $tifile = $PTMP . $chname . ".script"; open(TIFILE, ">$tifile"); $psrec = $record; $psrec =~ tr+/A-Za-z0-9_./+-+cs; # preserve any '/' in record name print TIFILE $sdb . "/" . $psrec . " " . $stime . "\n"; close(TIFILE); unless (fork) { # Run pschart using the script, and put the output into the cache. open(STDOUT, ">$tofile"); exec($PSCHART, "-a", $sann, "-E", "-t", "25", "-v", "10", "-c", "", "-C", "-G", "-H", "-l", $orientation, "-P", $size, "-m", "20", "20", "5", "5", "-M", "-n", "0", "-S", "4", "2", "-T", "", "-CG", "1", ".5", ".5", "-Cs", "0", "0", "0", "-w", "0.5", $tifile); } # Wait until pschart is finished before opening its output file. wait; open(TOFILE, $tofile); } # Read the pschart output from the cache. $postscript = ; if (!$postscript) { choose_record(); print h1('No output!'); print < No output was generated. This might occur if the record or annotator name is incorrect, or if the start time is improperly formatted. Please check and correct your entries above.

If you are using one of the PhysioNet mirrors, it may not be able to generate chart output; in this case, try using the Chart-O-Matic on the master PhysioNet server.


EOF print_footer(); } else { # pschart was successful! if ($action ne 'E-mail chart to:') { # show chart in browser window # Convert the PostScript to PNG. $tpfile = $PTMP . $chname .".png"; if (!open(TPFILE, $tpfile)) { unless (fork) { exec($CONVERT, "-density", "100x100", $tofile, $tpfile); } # Wait for convert to finish writing the PNG file. wait; } else { close(TPFILE); } # Calculate starting times for forward/backward buttons on chart. $tnext = $stime + $wsec; $tprev = $stime - $wsec; if ($tprev < 0) { $tprev = 0; } # Output the HTML to create the page containing the chart. output_html(); } else { output_email(); } } unlink($tifile); } # _____________________________________________________________________________ # Set chart specification variables. sub set_chart_variables { $database = param('database'); $record = param('record'); if ($record =~ /.+\n/) { chop($record); } $annotator = param('annotator'); $tstart = param('tstart'); $width = param('width'); if (!$database) { $database = "mitdb"; } if (!$record) { $record = "100"; } if (!$annotator) { $annotator = ""; } if (!$tstart) { $tstart = 0; } if (!$width) { $width = "medium"; } if (!$action) { $action = "Continue"; } if ($width eq "small") { $size = "180x150"; $wsec = 5; } if ($width eq "medium") { $size = "300x150"; $wsec = 10; } if ($width eq "large") { $size = "425x150"; $wsec = 15; } } # _____________________________________________________________________________ # Generate the HTML to display the chart in the window sub output_html { choose_record(); if ($action eq 'Chart-O-Matic Help') { print_help(); print_footer(); return; } print < Back $wsec seconds Record $database/$record Ahead $wsec seconds EOF print "Download a high-resolution"; print < of this chart

EOF print "


"; print end_html; } # _____________________________________________________________________________ # Send the output as email. sub output_email { my $email = param('email'); # Check that the user entered a plausible email address. if ($email =~ /^([-\w.]+)@([-\w]+)\.([-\w.]+)$/) { my $subject = 'Chart of record ' . $database . '/' . $record . ', annotator ' . $annotator; system($MAIL . " -s '$subject' $email <$tofile"); choose_record(); print h1('Data sent'); print < The requested data have been sent to $email. EOF } else { choose_record(); print h1('E-mail address needed'); print < If you wish to transmit the requested data via e-mail, enter your e-mail address in the space provided on the form, then click again on E-mail chart to:.

If you wish to view the requested data with your web browser, click on Show chart. The data will appear in another window.


EOF } print_footer(); } # _____________________________________________________________________________ # Boilerplate header. sub print_header { if (open(HEADER, $PNH . "links-physiobank.html")) { while (
) { print $_; } } } # _____________________________________________________________________________ # Boilerplate footer. sub print_footer { print <

Please e-mail your comments and suggestions to webmaster\@physionet.org, or post them to:

PhysioNet
MIT Room E25-505A
77 Massachusetts Avenue
Cambridge, MA 02139 USA

Updated Wednesday, 1 November 2006 at 20:31 EST EOF print end_html; }