Cleanups.

This commit is contained in:
Timothy Allen 2017-12-18 20:15:15 +02:00
parent d0dcc15d44
commit b743174a31

View File

@ -5,39 +5,48 @@
# Author: Timothy Allen <tim@treehouse.org.za> # Author: Timothy Allen <tim@treehouse.org.za>
# License: MIT # License: MIT
# #
# TODO Convert to python (see # https://www.physicsforums.com/threads/gnuplot-how-to-find-the-area-under-a-curve-integrate.382070/
# )
use strict; use strict;
use warnings; use warnings;
use Getopt::Long; use Getopt::Long;
use IPC::Open3; use IPC::Open3;
use Time::Piece; use Time::Piece;
use Data::Dumper;
my $error = "Usage: $0 --input <CSV file> --output <output PDF> [--max <n.n>] [--low <n.n>] [--high <n.n>]\n"; my $error = "Usage: $0 --input <CSV file> --output <output PDF> [--max <n.n>] [--low <n.n>] [--high <n.n>]\n";
my @lines; my @lines;
my %seen; my @sorted_lines;
my @data; my @data;
my @avg_data; my @avg_data;
my %intervals;
my %seen;
my $a1c_calc; my $a1c_calc;
my $page_size;
my $gnuplot_data; my $gnuplot_data;
my $total_graphs; my $total_graphs;
my $count_graphs = 0; my $count_graphs = 0;
my $page_number = 0; my $page_number = 0;
my $interval = 15; # The number of minutes to average points for the area range graph
my $input = ""; my $input = '';
my $output = ""; my $output = '';
# set these values either in mmol/L or mg/dL (don't mix them) # set these values either in mmol/L or mg/dL (don't mix them)
my $max_glucose = 8; my $max_glucose = 8;
my $min_glucose = 4; my $min_glucose = 4;
my $graph_max = 21; my $graph_max = 21;
my $units = 'mmol/L'; my $units = '';
my $page = 'a4';
my $days_per_page = 2; my $days_per_page = 2;
GetOptions ("input=s" => \$input, # The name of the CSV file from which to read values GetOptions ("input=s" => \$input, # The name of the CSV file from which to read values
"output=s" => \$output, # The name of the PDF file to output "output=s" => \$output, # The name of the PDF file to output
"high:f" => \$max_glucose, # The high end of your target blood glucose level "high:f" => \$max_glucose, # The high end of your target blood glucose level
"low:f" => \$min_glucose, # The low end of your target blood glucose level "low:f" => \$min_glucose, # The low end of your target blood glucose level
"max:i" => \$graph_max, # The highest displayed glucose level on each graph "max:i" => \$graph_max, # The highest displayed glucose level on each graph
"units:s" => \$units, # mmol/L or mg/dL "units:s" => \$units, # mmol/L or mg/dL
"graphs:i" => \$days_per_page) # The number of days printed on each page "pagesize:s" => \$page, # size of page to print
"graphs:i" => \$days_per_page) # The number of days printed on each page
or die $error; or die $error;
open( my $ifh, '<:encoding(UTF-8)', $input ) open( my $ifh, '<:encoding(UTF-8)', $input )
@ -51,9 +60,20 @@ while ( my $row = <$ifh> ) {
close( $ifh ) close( $ifh )
or warn "close failed: $!"; or warn "close failed: $!";
if ( $page =~ /a4/i ) {
$page_size = "29.7cm,21.0cm";
} elsif ( $page =~ /letter/i ) {
$page_size = "11in,8.5in";
} elsif ( $page =~ /\d+(cm|in),\d+/ ) {
$page_size = $page;
} else {
# A4 size default
$page_size = "29.7cm,21.0cm";
}
# Set up basic gnuplot options for reading the CSV data # Set up basic gnuplot options for reading the CSV data
push @data, qq( push @data, qq(
set terminal pdf size 29.7cm,21.0cm enhanced font 'Calibri,14' linewidth 1 set terminal pdf size $page_size enhanced font 'Calibri,14' linewidth 1
#set output '$output' #set output '$output'
); );
@ -144,13 +164,6 @@ foreach my $d ( sort keys %seen ) {
$count_graphs++; $count_graphs++;
# Algorithms for calculating HbA1c
if ( $units =~ /mg/i ) {
$a1c_calc = "(AVG + 46.7) / 28.7";
} else { # Assume mmol/L
$a1c_calc = "(AVG + 2.59) / 1.59";
}
push @data, qq( push @data, qq(
set title "Daily Glucose Summary for $title" font "Calibri,18" set title "Daily Glucose Summary for $title" font "Calibri,18"
set xlabel "Time" offset 0,-0.25 set xlabel "Time" offset 0,-0.25
@ -168,11 +181,6 @@ AVG_LABEL = gprintf("Average glucose: %.2f", AVG)
set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3 set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3
set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center
#A1C = $a1c_calc
#A1C_LABEL = gprintf("Average A1c: %.1f", A1C)
#set object 3 rect at graph 0.07, graph 0.9 fc ls 5 fs transparent solid 0.5 front size char strlen(A1C_LABEL), char 3
#set label 3 A1C_LABEL at graph 0.07, graph 0.9 front center
#plot \$SmoothData$label using 1:2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : $count_graphs ) with linespoints ls 120 lc variable #plot \$SmoothData$label using 1:2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : $count_graphs ) with linespoints ls 120 lc variable
plot \$SmoothData$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable plot \$SmoothData$label using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable
@ -200,18 +208,66 @@ plot 1/0
} }
# Output data averages by hour of the day # Output data averages by hour of the day
@sorted_lines = ();
push @data, qq( push @data, qq(
\$DataAvg << EOD \$DataAvg << EOD
"timestamp","blood glucose","meal","method","comment"); "timestamp","blood glucose","meal","method","comment");
my @sorted_rows;
foreach my $row (@lines) { foreach my $row (@lines) {
if ( $row =~ s#^"\d{4}-\d{2}-\d{2} #"#ms ) { if ( $row =~ s#^"\d{4}-\d{2}-\d{2} #"#ms ) {
push @sorted_rows, $row; push @sorted_lines, $row;
} }
} }
push @data, sort @sorted_rows; push @data, sort @sorted_lines;
push @data, qq(EOD); push @data, qq(EOD);
# Output min/max for each time interval
foreach my $row ( @sorted_lines ) {
$row =~ s/"//g;
my ( $time, $value ) = split /,/, $row;
my ( $hour, $minute, $second ) = split /:/, $time;
$time = sprintf( "%02d:%02d:00", $hour, int($minute/$interval)*$interval );
# Override the current minimum values for this interval if it
# exists; otherwise, set it
if ( exists ( $intervals{$time}{min} ) ) {
if ( $intervals{$time}{min} < $value ) {
$intervals{$time}{min} = $value;
}
} else {
$intervals{$time}{min} = $value;
}
# Override the current maximum values for this interval if it
# exists; otherwise, set it
if ( exists ( $intervals{$time}{max} ) ) {
if ( $intervals{$time}{max} > $value ) {
$intervals{$time}{max} = $value;
}
} else {
$intervals{$time}{max} = $value;
}
}
$Data::Dumper::Sortkeys = 1;
#die Dumper(\%intervals);
push @data, qq(
\$DataMaxMin << EOD
"timestamp","max","min");
foreach my $time ( sort keys %intervals ) {
warn $time;
push @data, qq("$time","$intervals{$time}{max}","$intervals{$time}{min}");
}
push @data, qq(EOD);
# Standardise units for gnuplot's A1C calculations
if ( $units =~ /mg/i ) {
$units = 'mg/dL';
} elsif ( $units =~ /mmol/i ) {
$units = 'mmol/L';
} else {
$units = '';
}
push @data, qq( push @data, qq(
reset reset
set datafile separator "," set datafile separator ","
@ -225,16 +281,41 @@ stats \$DataAvg using 2
MeanTotal = STATS_mean MeanTotal = STATS_mean
set xdata time set xdata time
set table \$DataAvgTable
#avg(x) = g
#min(x) = x<g
#max(x) = x>g
#f(x) = g
#fit f(x) \$DataAvg using 1:(\$2>MeanTotal?\$2:'') via t, g
#plot f(x) smooth mcsplines
#plot \$DataAvg using 1:(\$2>MeanTotal?\$2:'') every $count_graphs/2 lc 2, \$DataAvg using 1:(\$2<MeanTotal?\$2:'') every $count_graphs/2 lc 1
#samples(x) = \$0 > 4 ? 5 : (\$0+1)
#avg5(x) = (shift5(x), (back1+back2+back3+back4+back5)/samples(\$0))
#shift5(x) = (back5 = back4, back4 = back3, back3 = back2, back2 = back1, back1 = x)
## Initialize a running sum
#init(x) = (back1 = back2 = back3 = back4 = back5 = sum = 0)
#plot sum = init(0), \$DataAvg using 1:2 title 'data' lw 2 lc rgb 'forest-green', '' using 1:(avg5(\$2)) pt 7 ps 0.5 lw 1 lc rgb "blue"
plot \$DataAvg using 1:2 smooth mcsplines
unset table
set table \$SmoothDataAvg set table \$SmoothDataAvg
plot \$DataAvg using 1:2 smooth bezier plot \$DataAvg using 1:2 smooth bezier
unset table unset table
set table \$TableDataAvg
plot \$DataAvg using 1:2 smooth mcsplines
unset table
undefine \$DataAvg undefine \$DataAvg
# Convert DataMaxMin from CSV to table
set table \$DataMaxMinTable
plot \$DataMaxMin using 1:2:3 with table
unset table
reset reset
set datafile separator whitespace set datafile separator whitespace
@ -252,6 +333,8 @@ set xrange ["00:00":"23:58"]
set style line 100 dt 3 lw 1 lc rgb "#202020" set style line 100 dt 3 lw 1 lc rgb "#202020"
set style line 101 dt 1 lw 1 lc rgb "#202020" set style line 101 dt 1 lw 1 lc rgb "#202020"
set linetype 110 lc rgb "red" set linetype 110 lc rgb "red"
set linetype 111 lc rgb "#B0B0B0"
set style fill transparent solid 0.5 noborder
set lmargin 12 set lmargin 12
set rmargin 10 set rmargin 10
@ -267,24 +350,43 @@ set xtics left tc rgb "#000000"
set ytics 2 tc rgb "#000000" set ytics 2 tc rgb "#000000"
set grid ytics ls 100 front set grid ytics ls 100 front
set object 1 rect from graph 0, first $min_glucose to graph 1,first $max_glucose fc ls 6 fs solid 0.2 back set object 1 rect from graph 0, first $min_glucose to graph 1,first $max_glucose fc ls 6 fs solid 0.05 back
AVG = MeanTotal AVG = MeanTotal
AVG_LABEL = gprintf("Average glucose: %.2f", AVG) AVG_LABEL = gprintf("Average glucose: %.2f", AVG)
set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3 set object 2 rect at graph 0.9, graph 0.9 fc ls 2 fs transparent solid 0.5 front size char strlen(AVG_LABEL), char 3
set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center set label 2 AVG_LABEL at graph 0.9, graph 0.9 front center
A1C = $a1c_calc A1C = 0
if (A1C == 0 && '$units' eq 'mg/dL') {
A1C = (MeanTotal + 46.7) / 28.7
}
if (A1C == 0 && '$units' eq 'mmol/L') {
A1C = (MeanTotal + 2.59) / 1.59
}
# mg/dL numbers tend to be higher than 35
if (A1C == 0 && MeanTotal >= 35) {
A1C = (MeanTotal + 46.7) / 28.7
}
# mmol/L numbers tend to be lower than 35
if (A1C == 0 && MeanTotal < 35) {
A1C = (MeanTotal + 2.59) / 1.59
}
A1C_LABEL = gprintf("Average A1c: %.1f", A1C) A1C_LABEL = gprintf("Average A1c: %.1f", A1C)
set object 3 rect at graph 0.07, graph 0.9 fc ls 5 fs transparent solid 0.5 front size char strlen(A1C_LABEL), char 3 set object 3 rect at graph 0.07, graph 0.9 fc ls 4 fs transparent solid 0.5 front size char strlen(A1C_LABEL), char 3
set label 3 A1C_LABEL at graph 0.07, graph 0.9 front center set label 3 A1C_LABEL at graph 0.07, graph 0.9 front center
plot \$SmoothDataAvg using ( strftime("%H:%M:%S", \$1) ):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable #plot \$SmoothDataAvg using ( strftime("%H:%M:%S", \$1) ):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable
#plot \$TableDataAvg using (strftime("%H:%M:%S", \$1)):2 with lines lw 1 lc 5, \$SmoothDataAvg using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable #plot \$DataAvgTable using (strftime("%H:%M:%S", \$1)):2 with points lc 5 ps 0.5 pt 37, \$SmoothDataAvg using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable
plot \$DataMaxMinTable using (strftime("%H:%M:%S", \$1)):2:3 with filledcurves lc 111, \$SmoothDataAvg using (strftime("%H:%M:%S", \$1)):2:( \$2 > $max_glucose || \$2 < $min_glucose ? 110 : 1 ) with lines lw 3 lc variable
undefine \$DataAvg undefine \$DataAvg
undefine \$DataMaxMin
undefine \$DataMaxMinTable
undefine \$SmoothDataAvg undefine \$SmoothDataAvg
undefine \$TableDataAvg undefine \$DataAvgTable
# Add an x grid # Add an x grid
set multiplot previous set multiplot previous
@ -302,7 +404,7 @@ plot 1/0
push @data, qq( push @data, qq(
unset multiplot unset multiplot
#test test
); );
# run the data through gnuplot # run the data through gnuplot
@ -342,4 +444,4 @@ close( $ofh )
#print GNUPLOT $gnuplot_data; #print GNUPLOT $gnuplot_data;
#close(GNUPLOT); #close(GNUPLOT);
# vim: set expandtab shiftwidth=4 softtabstop=4 : # vim : set expandtab shiftwidth=4 softtabstop=4 tw=1000 :