Fix bugs that occur when switching to mg/dL units.
This commit is contained in:
parent
9ed89e7f8f
commit
7087b5299a
@ -9,7 +9,7 @@ __license__ = 'MIT'
|
|||||||
# TODO: comments -- unicode
|
# TODO: comments -- unicode
|
||||||
# TODO: prettify
|
# TODO: prettify
|
||||||
# TODO: weekly graph with each day's figures as a different-coloured line
|
# TODO: weekly graph with each day's figures as a different-coloured line
|
||||||
# TODO: verify either set of units (mmol,mg/dl) works with the data
|
# TODO: verify either set of units (mmol/L,mg/dl) works with the data
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import csv
|
import csv
|
||||||
@ -39,17 +39,20 @@ 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
|
INTERVAL = 15
|
||||||
# Maximum gluclose value to display (TODO: mmol/mg)
|
# Maximum gluclose value to display (TODO: mmol/mg)
|
||||||
GRAPH_MAX = 21
|
GRAPH_MAX = 21
|
||||||
|
GRAPH_MIN = 1
|
||||||
|
DEFAULT_HIGH = 8
|
||||||
|
DEFAULT_LOW = 4
|
||||||
|
|
||||||
# Set colour for below-target maxmins
|
# Colour for below-target maxmins
|
||||||
RED = '#d71920'
|
RED = '#d71920'
|
||||||
# Set colour for above-target maxmins
|
# Colour for above-target maxmins
|
||||||
YELLOW = '#f1b80e'
|
YELLOW = '#f1b80e'
|
||||||
# Set colour for graph lines
|
# Colour for graph lines
|
||||||
BLUE = '#02538f'
|
BLUE = '#02538f'
|
||||||
# Set colour for median glucose box
|
# Colour for median glucose box
|
||||||
GREEN = '#009e73'
|
GREEN = '#009e73'
|
||||||
# Set colour for median A1c box
|
# Colour for median A1c box
|
||||||
BOXYELLOW = '#e69f00'
|
BOXYELLOW = '#e69f00'
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -64,10 +67,22 @@ def main():
|
|||||||
''' This could be done directly from glucometerutils instead of via CSV '''
|
''' This could be done directly from glucometerutils instead of via CSV '''
|
||||||
with open(args.input_file, 'r', newline='') as f:
|
with open(args.input_file, 'r', newline='') as f:
|
||||||
rows = from_csv(f)
|
rows = from_csv(f)
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
row = parse_entry(row, args.icons)
|
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 args.units == UNIT_MMOLL and (args.high == DEFAULT_HIGH or args.low == DEFAULT_LOW):
|
||||||
|
mean = round(np.mean([l['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)
|
||||||
|
|
||||||
|
|
||||||
''' Fill in gaps that might exist in the data, in order to smooth the curves and fills '''
|
''' 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 '''
|
''' We're using 8 minute gaps in order to have more accurate fills '''
|
||||||
rows = fill_gaps(rows, interval=dt.timedelta(minutes=10))
|
rows = fill_gaps(rows, interval=dt.timedelta(minutes=10))
|
||||||
@ -100,7 +115,7 @@ def main():
|
|||||||
data = {}
|
data = {}
|
||||||
for row in rows:
|
for row in rows:
|
||||||
mpdate = dt.datetime.combine(rows[0]['date'], row.get('date').time())
|
mpdate = dt.datetime.combine(rows[0]['date'], row.get('date').time())
|
||||||
data[mdates.date2num(mpdate)] = {
|
data[mdates.date2num(mpdate)] = {
|
||||||
'value' : row.get('value'),
|
'value' : row.get('value'),
|
||||||
'comment' : row.get('comment'),
|
'comment' : row.get('comment'),
|
||||||
}
|
}
|
||||||
@ -110,7 +125,7 @@ def main():
|
|||||||
intervaldata = {}
|
intervaldata = {}
|
||||||
for i in intervals:
|
for i in intervals:
|
||||||
mpdate = dt.datetime.combine(rows[0]['date'], i)
|
mpdate = dt.datetime.combine(rows[0]['date'], i)
|
||||||
intervaldata[mdates.date2num(mpdate)] = {
|
intervaldata[mdates.date2num(mpdate)] = {
|
||||||
'max' : intervals.get(i).get('max'),
|
'max' : intervals.get(i).get('max'),
|
||||||
'min' : intervals.get(i).get('min'),
|
'min' : intervals.get(i).get('min'),
|
||||||
}
|
}
|
||||||
@ -129,10 +144,10 @@ def main():
|
|||||||
ax.axhspan(args.low, args.high, facecolor='#0072b2', edgecolor='#a8a8a8', alpha=0.2, zorder=5)
|
ax.axhspan(args.low, args.high, facecolor='#0072b2', edgecolor='#a8a8a8', alpha=0.2, zorder=5)
|
||||||
|
|
||||||
''' The maxmined curve of maximum and minimum values '''
|
''' The maxmined curve of maximum and minimum values '''
|
||||||
generate_plot(intervaldata,
|
generate_plot(intervaldata,
|
||||||
ax=ax,
|
ax=ax,
|
||||||
transforms={'spline':False, 'maxmin':True},
|
transforms={'spline':False, 'maxmin':True},
|
||||||
args=args,
|
args=args,
|
||||||
color='#979797',
|
color='#979797',
|
||||||
alpha=0.5,
|
alpha=0.5,
|
||||||
)
|
)
|
||||||
@ -140,8 +155,8 @@ def main():
|
|||||||
generate_plot(data,
|
generate_plot(data,
|
||||||
ax=ax,
|
ax=ax,
|
||||||
transforms={'bezier':True, 'avga1c':a_median, \
|
transforms={'bezier':True, 'avga1c':a_median, \
|
||||||
'color':[RED, BLUE, RED], 'boundaries':[0, args.low, args.high, GRAPH_MAX]},
|
'color':[RED, BLUE, RED], 'boundaries':[args.graph_min, args.low, args.high, args.graph_max]},
|
||||||
args=args,
|
args=args,
|
||||||
color=BLUE,
|
color=BLUE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -166,11 +181,11 @@ def main():
|
|||||||
day = monday + dt.timedelta(days=dow)
|
day = monday + dt.timedelta(days=dow)
|
||||||
if row.get('date').date() == day.date():
|
if row.get('date').date() == day.date():
|
||||||
weekrows.append(row)
|
weekrows.append(row)
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
for row in weekrows:
|
for row in weekrows:
|
||||||
mpdate = dt.datetime.combine(monday, row.get('date').time())
|
mpdate = dt.datetime.combine(monday, row.get('date').time())
|
||||||
data[mdates.date2num(mpdate)] = {
|
data[mdates.date2num(mpdate)] = {
|
||||||
'value' : row.get('value'),
|
'value' : row.get('value'),
|
||||||
'comment' : row.get('comment'),
|
'comment' : row.get('comment'),
|
||||||
}
|
}
|
||||||
@ -179,7 +194,7 @@ def main():
|
|||||||
intervaldata = {}
|
intervaldata = {}
|
||||||
for i in intervals:
|
for i in intervals:
|
||||||
mpdate = dt.datetime.combine(monday.date(), i)
|
mpdate = dt.datetime.combine(monday.date(), i)
|
||||||
intervaldata[mdates.date2num(mpdate)] = {
|
intervaldata[mdates.date2num(mpdate)] = {
|
||||||
'max' : intervals.get(i).get('max'),
|
'max' : intervals.get(i).get('max'),
|
||||||
'min' : intervals.get(i).get('min'),
|
'min' : intervals.get(i).get('min'),
|
||||||
}
|
}
|
||||||
@ -200,19 +215,19 @@ def main():
|
|||||||
ax.axhspan(args.low, args.high, facecolor='#0072b2', edgecolor='#a8a8a8', alpha=0.2, zorder=5)
|
ax.axhspan(args.low, args.high, facecolor='#0072b2', edgecolor='#a8a8a8', alpha=0.2, zorder=5)
|
||||||
|
|
||||||
''' The maxmined curve of maximum and minimum values '''
|
''' The maxmined curve of maximum and minimum values '''
|
||||||
generate_plot(intervaldata,
|
generate_plot(intervaldata,
|
||||||
ax=ax,
|
ax=ax,
|
||||||
transforms={'spline':False, 'maxmin':True, 'avga1c':a_median},
|
transforms={'spline':False, 'maxmin':True, 'avga1c':a_median},
|
||||||
args=args,
|
args=args,
|
||||||
color='#979797',
|
color='#979797',
|
||||||
alpha=0.5,
|
alpha=0.5,
|
||||||
)
|
)
|
||||||
|
|
||||||
generate_plot(data,
|
generate_plot(data,
|
||||||
ax=ax,
|
ax=ax,
|
||||||
transforms={'bezier':True, \
|
transforms={'bezier':True, \
|
||||||
'color':[RED, BLUE, RED], 'boundaries':[0, args.low, args.high, GRAPH_MAX]},
|
'color':[RED, BLUE, RED], 'boundaries':[args.graph_min, args.low, args.high, args.graph_max]},
|
||||||
args=args,
|
args=args,
|
||||||
color=BLUE,
|
color=BLUE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -231,7 +246,7 @@ def main():
|
|||||||
for row in rows:
|
for row in rows:
|
||||||
if row.get('date').date() == day.date():
|
if row.get('date').date() == day.date():
|
||||||
mpdate = dt.datetime.combine(day.date(), row.get('date').time())
|
mpdate = dt.datetime.combine(day.date(), row.get('date').time())
|
||||||
data[mdates.date2num(mpdate)] = {
|
data[mdates.date2num(mpdate)] = {
|
||||||
'value' : row.get('value'),
|
'value' : row.get('value'),
|
||||||
'comment' : row.get('comment'),
|
'comment' : row.get('comment'),
|
||||||
}
|
}
|
||||||
@ -251,18 +266,18 @@ def main():
|
|||||||
''' Draw the target range '''
|
''' Draw the target range '''
|
||||||
ax.axhspan(args.low, args.high, facecolor='#0072b2', edgecolor='#a8a8a8', alpha=0.2, zorder=5)
|
ax.axhspan(args.low, args.high, facecolor='#0072b2', edgecolor='#a8a8a8', alpha=0.2, zorder=5)
|
||||||
|
|
||||||
generate_plot(data,
|
generate_plot(data,
|
||||||
ax=ax,
|
ax=ax,
|
||||||
transforms={'spline':True, 'label':True, 'avgglucose':g_median, 'avga1c':a_median},
|
transforms={'spline':True, 'label':True, 'avgglucose':g_median, 'avga1c':a_median},
|
||||||
args=args,
|
args=args,
|
||||||
color=BLUE,
|
color=BLUE,
|
||||||
|
|
||||||
)
|
)
|
||||||
''' For max higher than target high '''
|
''' For max higher than target high '''
|
||||||
generate_plot(data,
|
generate_plot(data,
|
||||||
ax=ax,
|
ax=ax,
|
||||||
transforms={'spline':True, 'fill':True},
|
transforms={'spline':True, 'fill':True},
|
||||||
args=args,
|
args=args,
|
||||||
)
|
)
|
||||||
|
|
||||||
''' Save the graph to the output PDF if we're at the end of the page '''
|
''' Save the graph to the output PDF if we're at the end of the page '''
|
||||||
@ -308,13 +323,17 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
|
|||||||
x_min = mdates.date2num(firstminute)
|
x_min = mdates.date2num(firstminute)
|
||||||
x_max = mdates.date2num(lastminute)
|
x_max = mdates.date2num(lastminute)
|
||||||
ax.set_xlim(x_min, x_max)
|
ax.set_xlim(x_min, x_max)
|
||||||
ax.set_ylim(0, GRAPH_MAX)
|
ax.set_ylim(args.graph_min, args.graph_max)
|
||||||
''' Calculate the time intervals in 2 hour segments '''
|
''' Calculate the time intervals in 2 hour segments '''
|
||||||
xtimes = []
|
xtimes = []
|
||||||
time = firstminute
|
time = firstminute
|
||||||
while time < lastminute:
|
while time < lastminute:
|
||||||
xtimes.append(time)
|
xtimes.append(time)
|
||||||
time += dt.timedelta(hours=2)
|
time += dt.timedelta(hours=2)
|
||||||
|
if args.units == UNIT_MMOLL:
|
||||||
|
y_tick_freq = 2
|
||||||
|
else:
|
||||||
|
y_tick_freq = convert_glucose_unit(2, UNIT_MMOLL)
|
||||||
|
|
||||||
''' Formatting for axis labels, using date calculations from above '''
|
''' Formatting for axis labels, using date calculations from above '''
|
||||||
ax.set_xlabel('Time', fontsize=9)
|
ax.set_xlabel('Time', fontsize=9)
|
||||||
@ -322,16 +341,16 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
|
|||||||
ax.grid(axis='x', color = '#f0f0f0', zorder=1)
|
ax.grid(axis='x', color = '#f0f0f0', zorder=1)
|
||||||
ax.set_xticks(xtimes)
|
ax.set_xticks(xtimes)
|
||||||
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
|
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
|
||||||
ax.xaxis.set_ticks_position('none')
|
ax.xaxis.set_ticks_position('none')
|
||||||
for tick in ax.xaxis.get_major_ticks():
|
for tick in ax.xaxis.get_major_ticks():
|
||||||
tick.label1.set_horizontalalignment('left')
|
tick.label1.set_horizontalalignment('left')
|
||||||
|
|
||||||
ax.set_ylabel('Blood Glucose (' + args.units + ')', fontsize=9)
|
ax.set_ylabel('Blood Glucose (' + args.units + ')', fontsize=9)
|
||||||
ax.set_ybound(0, GRAPH_MAX)
|
ax.set_ybound(args.graph_min, args.graph_max)
|
||||||
ax.grid(axis='y', color = '#d0d0d0', linestyle = (1,(0.5,2)), zorder=1)
|
ax.grid(axis='y', color = '#d0d0d0', linestyle = (1,(0.5,2)), zorder=1)
|
||||||
ax.set_yticks([a for a in range(0, GRAPH_MAX, 2)])
|
ax.set_yticks([a for a in range(int(args.graph_min), int(args.graph_max), int(y_tick_freq))])
|
||||||
ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%d"))
|
ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%d"))
|
||||||
ax.yaxis.set_ticks_position('none')
|
ax.yaxis.set_ticks_position('none')
|
||||||
|
|
||||||
|
|
||||||
if 'maxmin' in transforms and transforms['maxmin'] is True:
|
if 'maxmin' in transforms and transforms['maxmin'] is True:
|
||||||
@ -347,19 +366,17 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
|
|||||||
f = interpolate.interp1d(x, y, kind='linear')
|
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
|
x = np.linspace(x.min(), x.max(), 50) # 50 is number of points to make between x.max & x.min
|
||||||
y = f(x)
|
y = f(x)
|
||||||
|
|
||||||
elif transform == 'spline' and transforms[transform] is True:
|
elif transform == 'spline' and transforms[transform] is True:
|
||||||
''' Use SciPy's UnivariateSpline for transforming (s is transforming factor) '''
|
''' Use SciPy's UnivariateSpline for transforming (s is transforming factor) '''
|
||||||
|
if args.units == UNIT_MMOLL:
|
||||||
|
s = 8
|
||||||
|
else:
|
||||||
|
s = convert_glucose_unit(12, UNIT_MMOLL)
|
||||||
if not maxmin:
|
if not maxmin:
|
||||||
curve = interpolate.UnivariateSpline(x=x, y=y, k=3, s=8)
|
curve = interpolate.UnivariateSpline(x=x, y=y, k=3, s=s)
|
||||||
y = curve(x)
|
y = curve(x)
|
||||||
#else:
|
|
||||||
# TODO Apply spline to each curve?
|
|
||||||
#curve1 = interpolate.UnivariateSpline(x=x, y=y, k=3, s=5)
|
|
||||||
#curve2 = interpolate.UnivariateSpline(x=x, y=z, k=3, s=5)
|
|
||||||
#y = curve1(x)
|
|
||||||
#z = curve2(x)
|
|
||||||
|
|
||||||
elif transform == 'bezier' and transforms[transform] is True:
|
elif transform == 'bezier' and transforms[transform] is True:
|
||||||
''' Create bezier function for transforming (s is transforming factor) '''
|
''' Create bezier function for transforming (s is transforming factor) '''
|
||||||
def bezier(points, s=100):
|
def bezier(points, s=100):
|
||||||
@ -369,13 +386,11 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
|
|||||||
for t in np.linspace(0, 1, s):
|
for t in np.linspace(0, 1, s):
|
||||||
u = np.power(t, r) * np.power(1 - t, n - r - 1) * b
|
u = np.power(t, r) * np.power(1 - t, n - r - 1) * b
|
||||||
yield t, u @ points
|
yield t, u @ points
|
||||||
|
|
||||||
''' The binomial calculation for the bezier curve overflows with arrays of 1020 or more elements,
|
''' The binomial calculation for the bezier curve overflows with arrays of 1020 or more elements,
|
||||||
For large arrays, get a smaller slice of the full array.
|
For large arrays, get a smaller slice of the full array.
|
||||||
Do this by removing every nth element from the array '''
|
Do this by removing every nth element from the array '''
|
||||||
n = 5
|
n = 5
|
||||||
while len(p) > 1000:
|
|
||||||
p = np.delete(p, np.arange(0, len(p), n), axis=0)
|
|
||||||
while len(x) > 1000:
|
while len(x) > 1000:
|
||||||
x = np.delete(x, np.arange(0, len(x), n), axis=0)
|
x = np.delete(x, np.arange(0, len(x), n), axis=0)
|
||||||
y = np.delete(y, np.arange(0, len(y), n), axis=0)
|
y = np.delete(y, np.arange(0, len(y), n), axis=0)
|
||||||
@ -383,18 +398,15 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
|
|||||||
if not maxmin:
|
if not maxmin:
|
||||||
curve = np.array([c for _, c in bezier(np.array([x,y]).T, 250)])
|
curve = np.array([c for _, c in bezier(np.array([x,y]).T, 250)])
|
||||||
(x, y) = (curve[:,0], curve[:,1])
|
(x, y) = (curve[:,0], curve[:,1])
|
||||||
#else:
|
|
||||||
# TODO Apply bezier to each curve? Will this work for x?
|
|
||||||
#while len(q) > 1000:
|
|
||||||
# q = np.delete(q, np.arange(0, len(q), n), axis=0)
|
|
||||||
#curve1 = np.array([c for _, c in bezier(p[:], 250)])
|
|
||||||
#curve2 = np.array([c for _, c in bezier(q[:], 250)])
|
|
||||||
#(x, y) = (curve1[:,0], curve1[:,1])
|
|
||||||
#(x, z) = (curve2[:,0], curve2[:,1])
|
|
||||||
|
|
||||||
''' Add the mean or median glucose and A1c values '''
|
''' Add the mean or median glucose and A1c values '''
|
||||||
if transform == 'avgglucose' and isinstance(transforms[transform], (int, float)):
|
if transform == 'avgglucose' and isinstance(transforms[transform], (int, float)):
|
||||||
ax.annotate('Median glucose: %.1f%s' % (round(transforms['avgglucose'], 1), args.units), fontsize=9, \
|
if args.units == UNIT_MMOLL:
|
||||||
|
gmtext = 'Median glucose: %.1f%s' % (round(transforms['avgglucose'], 1), args.units)
|
||||||
|
else:
|
||||||
|
gmtext = 'Median glucose: %.0f%s' % (round(transforms['avgglucose'], 1), args.units)
|
||||||
|
|
||||||
|
ax.annotate(gmtext, fontsize=9, \
|
||||||
xy=(0.95, 0.85), xycoords='axes fraction', verticalalignment='top', horizontalalignment='right', \
|
xy=(0.95, 0.85), xycoords='axes fraction', verticalalignment='top', horizontalalignment='right', \
|
||||||
zorder=40, bbox=dict(facecolor=GREEN, edgecolor='#009e73', alpha=0.7, pad=8), \
|
zorder=40, bbox=dict(facecolor=GREEN, edgecolor='#009e73', alpha=0.7, pad=8), \
|
||||||
)
|
)
|
||||||
@ -404,8 +416,8 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
|
|||||||
zorder=40, bbox=dict(facecolor=BOXYELLOW, edgecolor='#e69f00', alpha=0.7, pad=8), \
|
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
|
# 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
|
# unicode chacters that lack proper glyph names are massed together and printed
|
||||||
# as the same character
|
# as the same character
|
||||||
if transform == 'label' and transforms[transform] is True:
|
if transform == 'label' and transforms[transform] is True:
|
||||||
for a, b, label in zip(x, y, z):
|
for a, b, label in zip(x, y, z):
|
||||||
@ -413,7 +425,7 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
|
|||||||
#print(label)
|
#print(label)
|
||||||
ax.annotate(
|
ax.annotate(
|
||||||
label,
|
label,
|
||||||
xy=(a, GRAPH_MAX-6),
|
xy=(a, args.graph_max-6),
|
||||||
rotation=45,
|
rotation=45,
|
||||||
zorder=25,
|
zorder=25,
|
||||||
)
|
)
|
||||||
@ -449,16 +461,16 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
|
|||||||
return ax
|
return ax
|
||||||
|
|
||||||
def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
|
def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
|
||||||
''' Parse a row to create the icons and modify the timestamp
|
''' Parse a row to create the icons and modify the timestamp
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: a dict containing the entries 'timestamp' and 'comment'
|
data: a dict containing the entries 'timestamp' and 'comment'
|
||||||
icons: bool indicating whether to display food/injection icons on the graph
|
icons: bool indicating whether to display food/injection icons on the graph
|
||||||
date_format: the format of the timestamp in data
|
date_format: the format of the timestamp in data
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
data: the modified dict
|
data: the modified dict
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError if an incorrectly-formatted date exists in data['timestamp']
|
ValueError if an incorrectly-formatted date exists in data['timestamp']
|
||||||
'''
|
'''
|
||||||
@ -479,8 +491,8 @@ def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
|
|||||||
if re.search('Long', ctype) is not None:
|
if re.search('Long', ctype) is not None:
|
||||||
cvalue += 'L'
|
cvalue += 'L'
|
||||||
|
|
||||||
# XXX At present, backend_pdf does not parse unicode correctly, and all recent
|
# 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
|
# unicode chacters that lack proper glyph names are massed together and printed
|
||||||
# as the same character
|
# as the same character
|
||||||
# XXX Alternatives include replacing the glyph with an image, or a Path
|
# 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('Food', '$\mathcal{\N{GREEN APPLE}}$', ctype, flags=re.IGNORECASE)
|
||||||
@ -511,14 +523,17 @@ def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
|
|||||||
''' Convert value from string to float '''
|
''' Convert value from string to float '''
|
||||||
data['value'] = float(data.get('value'))
|
data['value'] = float(data.get('value'))
|
||||||
|
|
||||||
|
# XXX convert everything to mg/dL for testing
|
||||||
|
#data['value'] = float(round(data.get('value') * 18.0, 0))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def list_days_and_weeks(data, trim_weeks=192):
|
def list_days_and_weeks(data, trim_weeks=192):
|
||||||
''' Create a dictionary of the days and weeks that occur in the CSV
|
''' Create a dictionary of the days and weeks that occur in the CSV
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: a dict containing a 'timestamp' entry
|
data: a dict containing a 'timestamp' entry
|
||||||
trim_weeks: the minimum number of entries a week should have in order to be considered for
|
trim_weeks: the minimum number of entries a week should have in order to be considered for
|
||||||
a weekly average graph. A reading taken every 15 minutes over two days would yield 192 readings.
|
a weekly average graph. A reading taken every 15 minutes over two days would yield 192 readings.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -592,7 +607,7 @@ def calculate_max_min(data):
|
|||||||
datas: a dict with elements 'timestamp' and 'value'
|
datas: a dict with elements 'timestamp' and 'value'
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
intervals: a dictionary of minimum and maximum values for a a time period
|
intervals: a dictionary of minimum and maximum values for a a time period
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError if an incorrectly-formatted date exists in data['timestamp']
|
ValueError if an incorrectly-formatted date exists in data['timestamp']
|
||||||
@ -602,7 +617,7 @@ def calculate_max_min(data):
|
|||||||
date = d.get('date')
|
date = d.get('date')
|
||||||
date = date.replace(minute=int(date.minute/INTERVAL)*INTERVAL, second=0, microsecond=0, tzinfo=None)
|
date = date.replace(minute=int(date.minute/INTERVAL)*INTERVAL, second=0, microsecond=0, tzinfo=None)
|
||||||
time = date.time()
|
time = date.time()
|
||||||
|
|
||||||
if not time in intervals:
|
if not time in intervals:
|
||||||
intervals[time] = {}
|
intervals[time] = {}
|
||||||
intervals[time]['min'] = d.get('value')
|
intervals[time]['min'] = d.get('value')
|
||||||
@ -618,11 +633,11 @@ def calculate_max_min(data):
|
|||||||
|
|
||||||
def fill_gaps(rows, interval, maxinterval=dt.timedelta(days=1)):
|
def fill_gaps(rows, interval, maxinterval=dt.timedelta(days=1)):
|
||||||
''' Fill in time gaps that may exist in a set of rows, in order to smooth drawn curves and fills
|
''' Fill in time gaps that may exist in a set of rows, in order to smooth drawn curves and fills
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
rows: a dict containing a 'date' entry (the result of parse_entry())
|
rows: a dict containing a 'date' entry (the result of parse_entry())
|
||||||
interval: a datetime.timedelta object that defines the maximum distance allowed between two entries
|
interval: a datetime.timedelta object that defines the maximum distance allowed between two entries
|
||||||
maxinterval: a datetime.timedelta object that defines the maximum amount of time, over which we ignore
|
maxinterval: a datetime.timedelta object that defines the maximum amount of time, over which we ignore
|
||||||
the difference between two consecutive entries
|
the difference between two consecutive entries
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -661,7 +676,7 @@ def fill_gaps(rows, interval, maxinterval=dt.timedelta(days=1)):
|
|||||||
item = {
|
item = {
|
||||||
'date': period,
|
'date': period,
|
||||||
'meal': '',
|
'meal': '',
|
||||||
'value': float('%.2f' % val),
|
'value': float('%.2f' % val),
|
||||||
'comment': '',
|
'comment': '',
|
||||||
'timestamp': period.strftime('%Y-%m-%dT%H:%M:%S'),
|
'timestamp': period.strftime('%Y-%m-%dT%H:%M:%S'),
|
||||||
'measure_method': 'Estimate',
|
'measure_method': 'Estimate',
|
||||||
@ -689,7 +704,7 @@ def verify_units(units = None, high = None, low = None):
|
|||||||
elif re.search('mg', units, flags=re.IGNORECASE) is not None:
|
elif re.search('mg', units, flags=re.IGNORECASE) is not None:
|
||||||
units = UNIT_MMOLL
|
units = UNIT_MMOLL
|
||||||
elif isinstance(high, (int, float)) or isinstance(low, (int, float)):
|
elif isinstance(high, (int, float)) or isinstance(low, (int, float)):
|
||||||
''' If units are not specified by the arguments or calling function, let's assume they are
|
''' If units are not specified by the arguments or calling function, let's assume they are
|
||||||
mg/dL if the high is more than 35 or the low more than 20 '''
|
mg/dL if the high is more than 35 or the low more than 20 '''
|
||||||
if (isinstance(high, (int, float)) and (high > 35) or
|
if (isinstance(high, (int, float)) and (high > 35) or
|
||||||
isinstance(low, (int, float)) and (low > 20)):
|
isinstance(low, (int, float)) and (low > 20)):
|
||||||
@ -726,16 +741,22 @@ def parse_arguments():
|
|||||||
default='mmol/L', choices=(UNIT_MGDL, UNIT_MMOLL),
|
default='mmol/L', choices=(UNIT_MGDL, UNIT_MMOLL),
|
||||||
help=('The measurement units used (mmol/L or mg/dL).'))
|
help=('The measurement units used (mmol/L or mg/dL).'))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--low', action='store', required=False, type=float, default=4,
|
'--low', action='store', required=False, type=float, default=DEFAULT_LOW,
|
||||||
help=('Minimum of target glucose range.'))
|
help=('Minimum of target glucose range.'))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--high', action='store', required=False, type=float, default=8,
|
'--high', action='store', required=False, type=float, default=DEFAULT_HIGH,
|
||||||
help=('Maximum of target glucose range.'))
|
help=('Maximum of target glucose range.'))
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
args.pagesize = verify_pagesize(args.pagesize)
|
args.pagesize = verify_pagesize(args.pagesize)
|
||||||
args.units = verify_units(args.units, args.high, args.low)
|
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
|
||||||
|
else:
|
||||||
|
args.graph_max = convert_glucose_unit(GRAPH_MAX, UNIT_MMOLL)
|
||||||
|
args.graph_min = convert_glucose_unit(GRAPH_MIN, UNIT_MMOLL)
|
||||||
|
|
||||||
''' Ensure we have a valid number of graphs_per_page '''
|
''' Ensure we have a valid number of graphs_per_page '''
|
||||||
if not isinstance(args.graphs_per_page, int) or args.graphs_per_page < 1:
|
if not isinstance(args.graphs_per_page, int) or args.graphs_per_page < 1:
|
||||||
@ -745,7 +766,7 @@ def parse_arguments():
|
|||||||
|
|
||||||
def from_csv(csv_file, newline=''):
|
def from_csv(csv_file, newline=''):
|
||||||
'''Returns the reading as a formatted comma-separated value string.'''
|
'''Returns the reading as a formatted comma-separated value string.'''
|
||||||
data = csv.reader(csv_file, delimiter=',', quotechar='"')
|
data = csv.reader(csv_file, delimiter=',', quotechar='"')
|
||||||
fields = [ 'timestamp', 'value', 'meal', 'measure_method', 'comment' ]
|
fields = [ 'timestamp', 'value', 'meal', 'measure_method', 'comment' ]
|
||||||
rows = []
|
rows = []
|
||||||
for row in data:
|
for row in data:
|
||||||
@ -753,6 +774,34 @@ def from_csv(csv_file, newline=''):
|
|||||||
rows.append(item)
|
rows.append(item)
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
def convert_glucose_unit(value, from_unit, to_unit=None):
|
||||||
|
"""Convert the given value of glucose level between units.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The value of glucose in the current unit
|
||||||
|
from_unit: The unit value is currently expressed in
|
||||||
|
to_unit: The unit to conver the value to: the other if empty.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted representation of the blood glucose level.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
exceptions.InvalidGlucoseUnit: If the parameters are incorrect.
|
||||||
|
"""
|
||||||
|
if from_unit not in VALID_UNITS:
|
||||||
|
raise exceptions.InvalidGlucoseUnit(from_unit)
|
||||||
|
|
||||||
|
if from_unit == to_unit:
|
||||||
|
return value
|
||||||
|
|
||||||
|
if to_unit is not None:
|
||||||
|
if to_unit not in VALID_UNITS:
|
||||||
|
raise exceptions.InvalidGlucoseUnit(to_unit)
|
||||||
|
|
||||||
|
if from_unit is UNIT_MGDL:
|
||||||
|
return round(value / 18.0, 2)
|
||||||
|
else:
|
||||||
|
return round(value * 18.0, 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
Loading…
Reference in New Issue
Block a user