Fix bugs that occur when switching to mg/dL units.
This commit is contained in:
		@@ -9,7 +9,7 @@ __license__ = 'MIT'
 | 
			
		||||
# TODO: comments -- unicode
 | 
			
		||||
# TODO: prettify
 | 
			
		||||
# 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 csv
 | 
			
		||||
@@ -39,17 +39,20 @@ VALID_UNITS = [UNIT_MGDL, UNIT_MMOLL]
 | 
			
		||||
# When averaging, set the period to this number of minutes
 | 
			
		||||
INTERVAL = 15
 | 
			
		||||
# 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'
 | 
			
		||||
# Set colour for above-target maxmins
 | 
			
		||||
# Colour for above-target maxmins
 | 
			
		||||
YELLOW = '#f1b80e'
 | 
			
		||||
# Set colour for graph lines
 | 
			
		||||
# Colour for graph lines
 | 
			
		||||
BLUE = '#02538f'
 | 
			
		||||
# Set colour for median glucose box
 | 
			
		||||
# Colour for median glucose box
 | 
			
		||||
GREEN = '#009e73'
 | 
			
		||||
# Set colour for median A1c box
 | 
			
		||||
# Colour for median A1c box
 | 
			
		||||
BOXYELLOW = '#e69f00'
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
@@ -68,6 +71,18 @@ 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 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 '''
 | 
			
		||||
  ''' We're using 8 minute gaps in order to have more accurate fills '''
 | 
			
		||||
  rows = fill_gaps(rows, interval=dt.timedelta(minutes=10))
 | 
			
		||||
@@ -140,7 +155,7 @@ def main():
 | 
			
		||||
    generate_plot(data,
 | 
			
		||||
        ax=ax,
 | 
			
		||||
        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,
 | 
			
		||||
        color=BLUE,
 | 
			
		||||
    )
 | 
			
		||||
@@ -211,7 +226,7 @@ def main():
 | 
			
		||||
        generate_plot(data,
 | 
			
		||||
            ax=ax,
 | 
			
		||||
            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,
 | 
			
		||||
            color=BLUE,
 | 
			
		||||
        )
 | 
			
		||||
@@ -308,13 +323,17 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
 | 
			
		||||
  x_min       =  mdates.date2num(firstminute)
 | 
			
		||||
  x_max       =  mdates.date2num(lastminute)
 | 
			
		||||
  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 '''
 | 
			
		||||
  xtimes = []
 | 
			
		||||
  time = firstminute
 | 
			
		||||
  while time < lastminute:
 | 
			
		||||
      xtimes.append(time)
 | 
			
		||||
      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 '''
 | 
			
		||||
  ax.set_xlabel('Time', fontsize=9)
 | 
			
		||||
@@ -327,9 +346,9 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
 | 
			
		||||
    tick.label1.set_horizontalalignment('left')
 | 
			
		||||
 | 
			
		||||
  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.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_ticks_position('none')
 | 
			
		||||
 | 
			
		||||
@@ -350,15 +369,13 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
 | 
			
		||||
 | 
			
		||||
    elif transform == 'spline' and transforms[transform] is True:
 | 
			
		||||
      ''' 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:
 | 
			
		||||
        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)
 | 
			
		||||
      #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:
 | 
			
		||||
      ''' Create bezier function for transforming (s is transforming factor) '''
 | 
			
		||||
@@ -374,8 +391,6 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
 | 
			
		||||
          For large arrays, get a smaller slice of the full array.
 | 
			
		||||
          Do this by removing every nth element from the array '''
 | 
			
		||||
      n = 5
 | 
			
		||||
      while len(p) > 1000:
 | 
			
		||||
        p = np.delete(p, np.arange(0, len(p), n), axis=0)
 | 
			
		||||
      while len(x) > 1000:
 | 
			
		||||
        x = np.delete(x, np.arange(0, len(x), 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:
 | 
			
		||||
        curve = np.array([c for _, c in bezier(np.array([x,y]).T, 250)])
 | 
			
		||||
        (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 '''
 | 
			
		||||
    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', \
 | 
			
		||||
          zorder=40, bbox=dict(facecolor=GREEN, edgecolor='#009e73', alpha=0.7, pad=8), \
 | 
			
		||||
      )
 | 
			
		||||
@@ -413,7 +425,7 @@ def generate_plot(data, ax=None, transforms={}, args=[], **plot_args):
 | 
			
		||||
          #print(label)
 | 
			
		||||
          ax.annotate(
 | 
			
		||||
            label,
 | 
			
		||||
            xy=(a, GRAPH_MAX-6), 
 | 
			
		||||
            xy=(a, args.graph_max-6),
 | 
			
		||||
            rotation=45,
 | 
			
		||||
            zorder=25,
 | 
			
		||||
            )
 | 
			
		||||
@@ -511,6 +523,9 @@ def parse_entry(data, icons, fmt='%Y-%m-%d %H:%M:%S'):
 | 
			
		||||
  ''' Convert value from string to float '''
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
def list_days_and_weeks(data, trim_weeks=192):
 | 
			
		||||
@@ -726,16 +741,22 @@ def parse_arguments():
 | 
			
		||||
    default='mmol/L', choices=(UNIT_MGDL, UNIT_MMOLL),
 | 
			
		||||
    help=('The measurement units used (mmol/L or mg/dL).'))
 | 
			
		||||
  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.'))
 | 
			
		||||
  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.'))
 | 
			
		||||
 | 
			
		||||
  args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
  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 '''
 | 
			
		||||
  if not isinstance(args.graphs_per_page, int) or args.graphs_per_page < 1:
 | 
			
		||||
@@ -753,6 +774,34 @@ def from_csv(csv_file, newline=''):
 | 
			
		||||
    rows.append(item)
 | 
			
		||||
  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__":
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user