From f328a6dae615e156e984ccfcc903f01fb22b666b Mon Sep 17 00:00:00 2001 From: Kienan Stewart Date: Sat, 21 Mar 2015 14:07:17 -0400 Subject: [PATCH] Filechooser UI should now have smaller buttons; Added enzyme activity zones & legend; added legend for data lines; default view renders a graph; graph now updates properly after clearing data and after selecting a reference profile --- main.kv | 4 +-- main.py | 102 +++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 84 insertions(+), 22 deletions(-) diff --git a/main.kv b/main.kv index b470d1d..ca30e83 100644 --- a/main.kv +++ b/main.kv @@ -12,6 +12,7 @@ text: 'Continuing will clear the graph and turn off logging to file.' BoxLayout: orientation: 'horizontal' + size_hint_y: 0.1 Button: id: cancel_button text: 'Cancel' @@ -70,11 +71,10 @@ text: 'Select a reference temperature profile (csv)' size_hint_y: 0.1 FileChooserListView: - size_hint_y: 0.8 id: filechooser filters: ['*.csv'] BoxLayout: - size_hiny_y: 0.1 + size_hint_y: 0.1 orientation: 'horizontal' Button: text: 'Cancel' diff --git a/main.py b/main.py index 9d59451..af1abbe 100644 --- a/main.py +++ b/main.py @@ -8,7 +8,7 @@ from kivy.core.image.img_pygame import ImageLoaderPygame import kivy.graphics.texture from kivy.graphics.texture import Texture from kivy.logger import Logger -from kivy.properties import DictProperty, ListProperty, NumericProperty, ObjectProperty, StringProperty +from kivy.properties import BooleanProperty, DictProperty, ListProperty, NumericProperty, ObjectProperty, StringProperty from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.uix.dropdown import DropDown @@ -23,6 +23,8 @@ import kivy.lang import matplotlib matplotlib.use('Agg') import matplotlib.image +import matplotlib.lines +import matplotlib.patches import matplotlib.pyplot import numpy import os @@ -366,6 +368,21 @@ class PlotWidget(Image): image_output_file = StringProperty('') raw_data_output_file = StringProperty('') reference_profile_file = StringProperty('') + # @TODO Include a way to change these properties. + show_betaglucan = BooleanProperty(True) + show_protease = BooleanProperty(True) + show_betaamylase = BooleanProperty(True) + show_alphaamylase = BooleanProperty(True) + + # Common data that we should only instantiate once + # Source : http://www.howtobrew.com/section3/chapter14-1.html + # accessed on march 21, 2015 + # x_vertices = ((x, y), width, height, color, legend_string) + betaglucan_vertices = ((0, 35), 12000, 10, '#57ff14', 'Betaglucanase') + protease_vertices = ((0,45), 12000, 10, '#f3ff14', 'Protease / Peptidase') + betaamylase_vertices = ((0,55), 12000, 10.6, '#ffc014', 'Beta Amylase') + alphaamylase_vertices = ((0, 67.7), 12000, 4.5, '#ff6614', 'Alpha Amylase') + def __init__(self, **kwargs): super(PlotWidget, self).__init__(**kwargs) @@ -381,6 +398,34 @@ class PlotWidget(Image): self.image_data = None self._image_raw_data = None self.reference_profile = None + self.do_update() + + + def update_patches(self, tmax = None): + items = [ + (self.show_betaglucan, self.betaglucan_vertices), + (self.show_protease, self.protease_vertices), + (self.show_betaamylase, self.betaamylase_vertices), + (self.show_alphaamylase, self.alphaamylase_vertices) + ] + legend_handles = [] + legend_strings = [] + for show, config in items: + color = '0.5' # default color. + if show: + origin, width, height, color, name = config + if tmax is not None: + width = tmax + p = self.plot_axes.add_patch( + matplotlib.patches.Rectangle(origin, width, height, + fc = color, visible = True, + fill = True) + #matplotlib.patches.Polygon(vertices, facecolor = color) + ) + legend_handles.append(p) + legend_strings.append(name) + self.figure.legend(legend_handles, legend_strings, 'upper left', + title = 'Enzyme Activity Zones') def clear_data(self): @@ -390,6 +435,7 @@ class PlotWidget(Image): self.texture = None self.image_output_file = '' self.raw_data_output_file = '' + self.do_update() def do_update(self): @@ -417,30 +463,44 @@ class PlotWidget(Image): def to_image_data(self): - if not self.data.any(): - return - #Logger.debug('self.data: ' + str(self.data)) - #Logger.debug('self.data shape: ' + str(self.data.shape)) - data = numpy.copy(self.data) - data = data.transpose() - #Logger.debug('transpoed data: ' + str(data)) - #Logger.debug('transposed data shape: ' + str(data.shape)) - t, temperature = numpy.split(data, 2, axis = 0) - reference_data = None + # Add in polygons for hilightning particular temperature ranges of interest. + plot_args = [] + t = None + time_max = 60 + temp_max = 80 + handles = [] + if self.data.any(): + data = numpy.copy(self.data) + data = data.transpose() + t, temperature = numpy.split(data, 2, axis = 0) + if t[0][-1] > time_max: + time_max = t[0][-1] + if numpy.nanmax(temperature[0]) > temp_max: + temp_max = numpy.nanmax(temperature[0]) + plot_args.extend((t[0], temperature[0], 'b')) + handles.append('Recorded Temperature Profile') if self.reference_profile is not None and self.reference_profile.any(): reference_data = numpy.copy(self.reference_profile) reference_data = reference_data.transpose() t2, reference_temperature = numpy.split(reference_data, 2, axis = 0) # Add t[0] to all reference points to make it fit on the current graph - t2 = numpy.add(t2, t[0][0]) - #Logger.debug('PlotWidget: reference profile t: ' + str(t2)) - #Logger.debug('PlotWidget: reference profile temp: ' + str(reference_temperature)) - #Logger.debug('PlotWidget: time: ' + str(t[0])) - #Logger.debug('PlotWidget: temperature: ' + str(temperature[0])) - if reference_data is not None and reference_data.any(): - self.plot_axes.plot(t[0], temperature[0], 'b', t2[0], reference_temperature[0], 'r') - else: - self.plot_axes.plot(t[0], temperature[0], 'b') + if t: + t2 = numpy.add(t2, t[0][0]) + if t2[0][-1] > time_max: + time_max = t2[0][-1] + if numpy.nanmax(reference_temperature[0]) > temp_max: + temp_max = numpy.nanmax(reference_temperature[0]) + plot_args.extend((t2[0], reference_temperature[0], 'r')) + handles.append('Reference Temperature Profile') + lines = self.plot_axes.plot(*plot_args) + # Patches must be changed after the axes are plotted. + self.update_patches(time_max) + self.plot_axes.legend(lines, handles, 'upper right', title = 'Temperature Profiles') + if not lines: + # Set a default in case no data is plotted. This may allow patches to show, + # and it will look a little nicer before recording starts. + self.plot_axes.set_xlim(0, time_max) + self.plot_axes.set_ylim(0, temp_max) image_data = StringIO.StringIO() self.figure.savefig(image_data, format = 'png') image_data.seek(0) @@ -487,6 +547,7 @@ class PlotWidget(Image): def on_reference_profile_file(self, *args, **kwargs): Logger.debug('PlotWidget: on_reference_profile_file ' + str(self.reference_profile_file)) self.load_reference_profile() + self.do_update() def load_reference_profile(self): @@ -496,6 +557,7 @@ class PlotWidget(Image): delimiter=',') Logger.debug('PlotWidget: reference_profile: ' + str(self.reference_profile)) + class YesNoModalView(Popup): def process(self, result, *args, **kwargs):