Final cleanup.

This commit is contained in:
Timothy Allen 2018-01-05 09:59:09 +02:00
parent b070cc51ce
commit 4d77fac610

View File

@ -1,6 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
''' Utility to convert data from a glucometer into charts. '''
__author__ = 'Timothy Allen'
__email__ = 'tim@treehouse.org.za'
__license__ = 'MIT'
''' Included are the Noto Sans and IcoGluco font sets.
Noto Sans is licensed under the SIL Open Font License version 1.1
@ -12,11 +15,6 @@
licensed under Creative Commons BY 3.0, <http://creativecommons.org/licenses/by/3.0/>
'''
__author__ = 'Timothy Allen'
__email__ = 'tim@treehouse.org.za'
__license__ = 'MIT'
# TODO: comments -- unicode/images/np.array
# TODO: weekly graph with each day's figures as a different-coloured line
import argparse
@ -40,30 +38,32 @@ import re
from scipy import interpolate
from scipy.special import binom
import sys
import pprint
# Constants for units
''' Constants for units '''
UNIT_MGDL = 'mg/dL'
UNIT_MMOLL = 'mmol/L'
VALID_UNITS = [UNIT_MGDL, UNIT_MMOLL]
# When averaging, set the period to this number of minutes
''' When averaging, set the period to this number of minutes '''
INTERVAL = 15
# Maximum gluclose value to display (TODO: mmol/mg)
GRAPH_MAX = 21
GRAPH_MIN = 1
''' Set the default high and low in mmol/L; it will be reset to mg/dL if neccessary '''
DEFAULT_HIGH = 8
DEFAULT_LOW = 4
''' Maximum glucose value to display '''
GRAPH_MAX_MMOLL = 21
GRAPH_MIN_MMOLL = 0
GRAPH_MAX_MGDL = 400
GRAPH_MIN_MGDL = 0
# Colour for below-target maxmins
''' Colour for below-target maxmins '''
RED = '#d71920'
# Colour for above-target maxmins
'''' Colour for above-target maxmins '''
YELLOW = '#f1b80e'
# Colour for graph lines
''' Colour for graph lines '''
BLUE = '#02538f'
# Colour for median glucose box
''' Colour for median glucose box '''
GREEN = '#009e73'
# Colour for median A1c box
''' Colour for median A1c box '''
BOXYELLOW = '#e69f00'
def main():
@ -71,8 +71,6 @@ def main():
raise Exception(
'Unsupported Python version, please use at least Python 3.2')
pp = pprint.PrettyPrinter(depth=6)
args = parse_arguments()
''' This could be done directly from glucometerutils instead of via CSV '''
@ -82,17 +80,17 @@ def main():
for row in rows:
row = parse_entry(row, args.icons)
# If we're on the default values for units, highs and lows, check that the average
# value is under 35 (assuming that average mmol/L < 35 and average mg/dL > 35)
''' If we're on the default values for units, highs and lows, check that the average
value is under 35 (assuming that average mmol/L < 35 and average mg/dL > 35) '''
if args.units == UNIT_MMOLL and (args.high == DEFAULT_HIGH or args.low == DEFAULT_LOW):
mean = round(np.mean([l.get('value') for l in rows]), 1)
if mean > 35:
args.units = UNIT_MGDL
args.high = convert_glucose_unit(args.high, UNIT_MMOLL)
args.low = convert_glucose_unit(args.low, UNIT_MMOLL)
args.graph_max = convert_glucose_unit(args.graph_max, UNIT_MMOLL)
args.graph_min = convert_glucose_unit(args.graph_min, UNIT_MMOLL)
''' Manually specify max and min for mg/dL '''
args.graph_max = GRAPH_MAX_MGDL
args.graph_min = GRAPH_MIN_MGDL
''' Fill in gaps that might exist in the data, in order to smooth the curves and fills '''
''' We're using 8 minute gaps in order to have more accurate fills '''
@ -111,9 +109,13 @@ def main():
rcParams['font.sans-serif'] = ['Calibri','Verdana','Geneva','Arial','Helvetica','DejaVu Sans','Bitstream Vera Sans','sans-serif']
rcParams['mathtext.default'] = 'regular'
# Load custom fonts for the icon sets
''' Load custom fonts for the icon sets
At present, backend_pdf does not parse unicode correctly, and unicode
characters from many fonts that lack proper glyph names are massed together
and printed as the same character. The IcoGluco font, generated from Noto Sans and
custom icons on IcoMoon, works around this. '''
if args.icons:
args.customfont = import_font('fonts/icogluco.ttf') # Works
args.customfont = import_font('fonts/icogluco.ttf')
#args.customfont = import_font('fonts/OpenSansEmoji.ttf') # Alternate working font
nrows = args.graphs_per_page
@ -302,18 +304,16 @@ def main():
def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
pp = pprint.PrettyPrinter(depth=6)
(x, y, z, p, q) = (list(), list(), list(), list(), list())
for (key, value) in sorted(data.items()):
# Time
''' Time '''
a = key
if 'maxmin' in transforms:
# If a max and a min exists, initialise them to y and z
''' If a max and a min exists, initialise them to y and z '''
b = value.get('max')
c = value.get('min')
else:
# Glucose and comment
''' Glucose and comment '''
b = value.get('value')
c = value.get('comment', '')
x.append(a)
@ -342,7 +342,7 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
if args.units == UNIT_MMOLL:
y_tick_freq = 2
else:
y_tick_freq = convert_glucose_unit(2, UNIT_MMOLL)
y_tick_freq = 50
''' Formatting for axis labels, using date calculations from above '''
ax.set_xlabel('Time', fontsize=9)
@ -373,7 +373,8 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
''' Use SciPy's interp1d for linear transforming '''
if not maxmin:
f = interpolate.interp1d(x, y, kind='linear')
x = np.linspace(x.min(), x.max(), 50) # 50 is number of points to make between x.max & x.min
''' 50 is number of points to make between x.max & x.min '''
x = np.linspace(x.min(), x.max(), 50)
y = f(x)
elif transform == 'spline' and transforms.get(transform) is True:
@ -425,9 +426,6 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
zorder=40, bbox=dict(facecolor=BOXYELLOW, edgecolor='#e69f00', alpha=0.7, pad=8), \
)
# XXX At present, backend_pdf does not parse unicode correctly, and all recent
# unicode chacters that lack proper glyph names are massed together and printed
# as the same character
if args.units == UNIT_MMOLL:
y_offset = 6
else:
@ -454,7 +452,6 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
elif key == 'Food':
symbol += '\N{GREEN APPLE}'
symbol += '$'
print(symbol)
ax.annotate(
symbol,
xy=(x_pos, args.graph_max-y_offset),
@ -495,9 +492,9 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
return ax
def import_font(fontname):
''' Turns a relative font path into a matplotlib font property. '''
basedir = os.path.dirname(os.path.abspath(__file__))
fontdir = os.path.join(basedir, 'fonts')
fontpath = os.path.join(fontdir, fontname)
fontpath = os.path.join(basedir, fontname)
if not os.path.exists(fontpath):
raise UserError("Font %s does not exist" % fontpath)
prop = fm.FontProperties(fname=fontpath)
@ -528,7 +525,6 @@ def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
ctype = relevant.group(1)
cvalue = relevant.group(2)
#cvalue = re.sub('(\d+)(\.\d+)?', '\g<1>', cvalue)
''' Convert floating point-style strings (2.0) to integer-style strings (2) '''
try:
cvalue = int(float(cvalue))
@ -541,13 +537,6 @@ def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
if re.search('Long', ctype) is not None:
cvalue += 'L'
# XXX At present, backend_pdf does not parse unicode correctly, and all recent
# unicode chacters that lack proper glyph names are massed together and printed
# as the same character
# XXX Alternatives include replacing the glyph with an image, or a Path
#ctype = re.sub('Food', '$\mathcal{\N{GREEN APPLE}}$', ctype, flags=re.IGNORECASE)
#ctype = re.sub('Rapid-acting insulin', '$\mathcal{\N{SYRINGE}}^\mathrm{'+cvalue+'}$', ctype, flags=re.IGNORECASE)
#ctype = re.sub('Long-acting insulin', '$\mathcal{\N{SYRINGE}}^\mathrm{'+cvalue+'}$', ctype, flags=re.IGNORECASE)
ctype = re.sub('Rapid-acting insulin', 'Insulin', ctype, flags=re.IGNORECASE)
ctype = re.sub('Long-acting insulin', 'Insulin', ctype, flags=re.IGNORECASE)
@ -800,11 +789,15 @@ def parse_arguments():
args.pagesize = verify_pagesize(args.pagesize)
args.units = verify_units(args.units, args.high, args.low)
if args.units == UNIT_MMOLL:
args.graph_max = GRAPH_MAX
args.graph_min = GRAPH_MIN
args.graph_max = GRAPH_MAX_MMOLL
args.graph_min = GRAPH_MIN_MMOLL
else:
args.graph_max = convert_glucose_unit(GRAPH_MAX, UNIT_MMOLL)
args.graph_min = convert_glucose_unit(GRAPH_MIN, UNIT_MMOLL)
args.graph_max = GRAPH_MAX_MGDL
args.graph_min = GRAPH_MIN_MGDL
''' If the user specified the units but not the high or low targets, set them now '''
if args.high == DEFAULT_HIGH or args.low == DEFAULT_LOW:
args.high = convert_glucose_unit(args.high, UNIT_MMOLL)
args.low = convert_glucose_unit(args.low, UNIT_MMOLL)
''' Ensure we have a valid number of graphs_per_page '''
if not isinstance(args.graphs_per_page, int) or args.graphs_per_page < 1: