2015-08-12 22:25:43 +00:00
#!/usr/bin/env python
2015-04-02 03:06:03 +00:00
import io
2014-12-09 03:12:21 +00:00
import kivy
from kivy . app import App
2014-12-10 22:22:52 +00:00
from kivy . clock import Clock
2014-12-11 03:38:40 +00:00
import kivy . core . image
2015-04-02 03:06:03 +00:00
import kivy . config
import kivy . core . image
2014-12-13 17:57:33 +00:00
from kivy . core . image import ImageData
from kivy . core . image . img_pil import ImageLoaderPIL
2014-12-11 03:38:40 +00:00
from kivy . core . image . img_pygame import ImageLoaderPygame
import kivy . graphics . texture
2014-12-13 17:57:33 +00:00
from kivy . graphics . texture import Texture
2014-12-10 22:22:52 +00:00
from kivy . logger import Logger
2015-03-21 18:07:17 +00:00
from kivy . properties import BooleanProperty , DictProperty , ListProperty , NumericProperty , ObjectProperty , StringProperty
2014-12-09 03:12:21 +00:00
from kivy . uix . boxlayout import BoxLayout
2014-12-13 17:57:33 +00:00
from kivy . uix . button import Button
from kivy . uix . dropdown import DropDown
2014-12-10 22:22:52 +00:00
from kivy . uix . floatlayout import FloatLayout
2014-12-13 17:57:33 +00:00
from kivy . uix . gridlayout import GridLayout
2014-12-11 03:38:40 +00:00
from kivy . uix . image import Image
2014-12-10 22:22:52 +00:00
from kivy . uix . label import Label
2014-12-13 21:44:05 +00:00
from kivy . uix . modalview import ModalView
from kivy . uix . popup import Popup
from kivy . uix . spinner import Spinner
2014-12-10 22:22:52 +00:00
import kivy . lang
2014-12-11 03:38:40 +00:00
import matplotlib
matplotlib . use ( ' Agg ' )
2015-04-02 03:06:03 +00:00
import matplotlib . backends . backend_agg
2014-12-11 03:38:40 +00:00
import matplotlib . image
2015-03-21 18:07:17 +00:00
import matplotlib . lines
import matplotlib . patches
2014-12-11 03:38:40 +00:00
import matplotlib . pyplot
2015-01-03 22:03:11 +00:00
import numpy
import os
import os . path
2014-12-13 17:57:33 +00:00
import pygame . image
2014-12-10 22:22:52 +00:00
import Queue
2015-03-22 22:54:30 +00:00
import cStringIO
2014-12-10 22:22:52 +00:00
import temp_log
2015-03-22 22:54:30 +00:00
import time
2014-12-10 22:22:52 +00:00
import threading
2014-12-09 03:12:21 +00:00
2014-12-10 22:22:52 +00:00
dataThread = None
dataThreadDataQueue = Queue . Queue ( )
dataThreadCommandQueue = Queue . Queue ( )
2014-12-13 17:57:33 +00:00
defaultSerialPort = ' /dev/ttyACM0 '
2014-12-10 22:22:52 +00:00
2015-04-02 03:06:03 +00:00
kivy . config . Config . set ( ' modules ' , ' monitor ' , ' ' )
2014-12-10 22:22:52 +00:00
class MainApp ( App ) :
2014-12-09 03:12:21 +00:00
def build ( self ) :
2014-12-10 22:22:52 +00:00
mainWindow = MainWindow ( )
2015-04-02 03:06:03 +00:00
Clock . schedule_interval ( mainWindow . update , 0.5 )
2014-12-10 22:22:52 +00:00
return mainWindow
def on_stop ( self ) :
global dataThreadCommandQueue
dataThreadCommandQueue . put_nowait ( ' stop ' )
def on_start ( self ) :
global dataThread
dataThread . start ( )
class MainWindow ( FloatLayout ) :
""" Main Window class """
2014-12-13 17:57:33 +00:00
dataSource = StringProperty ( defaultSerialPort )
dataSources = ListProperty ( temp_log . list_serial_ports ( ) )
2014-12-11 03:38:40 +00:00
lastTemperature = NumericProperty ( - 1000. )
lastTime = NumericProperty ( - 1. )
2014-12-13 17:57:33 +00:00
recordingState = StringProperty ( ' ' )
2014-12-13 21:44:05 +00:00
state = StringProperty ( ' running ' )
lastStatus = DictProperty ( { } )
2015-01-03 22:03:11 +00:00
image_output_file = StringProperty ( ' ' )
raw_data_output_file = StringProperty ( ' ' )
2015-01-04 17:58:58 +00:00
reference_profile_file = StringProperty ( ' ' )
2015-04-02 03:06:03 +00:00
lastData = ListProperty ( [ ] )
2014-12-10 22:22:52 +00:00
2014-12-13 17:57:33 +00:00
def __init__ ( self , * * kwargs ) :
super ( MainWindow , self ) . __init__ ( * * kwargs )
# self.ids is available here; use it's references to connect bindings
Logger . debug ( ' MainWindow IDs: ' + str ( self . ids ) )
2014-12-13 21:44:05 +00:00
self . modal_view = None
#self.ids['serial_chooser_dropdown'].bind(on_serial_port_change=self.serial_port_changed)
2014-12-13 17:57:33 +00:00
def update_last_temperature ( self ) :
2015-04-02 14:46:02 +00:00
start = time . time ( )
2014-12-10 22:22:52 +00:00
global dataThreadDataQueue
# This breaks if there are more updates in the queue than the frequency of update_last_temperature - it pulls old data out of the queue, and not the most recent.
2015-04-02 03:06:03 +00:00
done = False
data = [ ]
while not done :
try :
data . append ( dataThreadDataQueue . get_nowait ( ) )
except Queue . Empty :
done = True
if data :
2015-04-02 14:46:02 +00:00
#if len(data) > 1:
# Logger.debug('update_last_temperature: ' + str(len(data)) + ' items retrieved from queue')
2015-04-02 03:06:03 +00:00
last_point = data [ - 1 ]
if self . state == ' paused ' :
# Try to update the last temperature, but not to the plotwidget,
# when the data reception is halted.
self . lastStatus = { ' status ' : ' paused ' , ' message ' : ' Data receiption halted by user ' }
if ' data ' in last_point and ' temperature ' in last_point [ ' data ' ] :
self . lastTemperature = float ( last_point [ ' data ' ] [ ' temperature ' ] )
return
if ' data ' in last_point and ' temperature ' in last_point [ ' data ' ] :
self . lastStatus = { ' status ' : ' ok ' , ' message ' : ' ' }
self . lastTemperature = float ( last_point [ ' data ' ] [ ' temperature ' ] )
if ' time ' in last_point :
self . lastTime = float ( last_point [ ' time ' ] )
if ' exception ' in last_point :
self . lastStatus = { ' status ' : ' error ' , ' message ' : data [ ' exception ' ] }
self . lastData = data
2015-04-02 14:46:02 +00:00
#if dataThreadDataQueue.qsize():
# Logger.debug('update_last_temperature: ' + str(dataThreadDataQueue.qsize()) + ' items left in queue after ' + str(len(data)) + ' items were taken out')
Logger . debug ( ' update_last_temperature: Took ' + str ( time . time ( ) - start ) + ' seconds to retrieve ' + str ( len ( data ) ) + ' items. ' + str ( dataThreadDataQueue . qsize ( ) ) + ' items remain in queue ' )
2014-12-13 17:57:33 +00:00
def update ( self , dt ) :
self . update_data_sources ( )
self . update_last_temperature ( )
2015-04-02 14:46:02 +00:00
#Logger.debug('MainWindow: FPS ' + str(kivy.clock.Clock.get_fps()))
2014-12-13 21:44:05 +00:00
#Logger.debug('MainWindow: dataThread ' + str(dataThread.name) + ' status: ' + str(dataThread.is_alive()))
2014-12-13 17:57:33 +00:00
def update_data_sources ( self ) :
self . dataSources = temp_log . list_serial_ports ( )
def on_dataSources ( self , instance , value ) :
Logger . debug ( ' dataSources changed to: ' + str ( self . dataSources ) )
children = self . children [ : ]
def serial_port_changed ( self , * args ) :
Logger . debug ( ' MainWindow: Received on_serial_port_changed: ' + str ( args ) )
Logger . debug ( ' MainWindow: Current dataSource: ' + str ( self . dataSource ) )
if args :
new_port = args [ 0 ]
if new_port == self . dataSource :
Logger . debug ( ' MainWindow: Selected port matches current port, no change ' )
2014-12-13 21:44:05 +00:00
elif new_port in self . dataSources :
2014-12-13 17:57:33 +00:00
self . dataSource = new_port
2014-12-13 21:44:05 +00:00
else :
self . dataSource = ' '
2014-12-13 17:57:33 +00:00
def on_dataSource ( self , instance , value ) :
# Stop the current data thread, if any.
Logger . debug ( ' MainWindow: stopping dataThread ' )
global dataThread , dataThreadDataQueue , dataThreadCommandQueue
dataThreadCommandQueue . put ( ' stop ' )
# This could cause the UI to hang until the thread is joined, if ever.
dataThread . join ( )
# Start a new data thread using the selected data source
if value :
Logger . debug ( ' MainWindow: Starting new data thread on ' + str ( value ) )
# For some reason it seems necessary to create new Queue objects.
dataThreadCommandQueue = Queue . Queue ( )
dataThreadDataQueue = Queue . Queue ( )
dataThread = threading . Thread ( None , temp_log . threaded_reader , None , [ value , dataThreadDataQueue , dataThreadCommandQueue ] )
dataThread . start ( )
2014-12-13 21:44:05 +00:00
def set_state ( self , new_state ) :
self . state = new_state
def initiate_clear_dialog ( self ) :
if self . modal_view is None :
self . modal_view = YesNoModalView ( id = ' startstop_modal_dialog ' )
self . modal_view . process_callback = self . process_clear_dialog
self . add_widget ( self . modal_view )
2014-12-13 17:57:33 +00:00
2014-12-13 21:44:05 +00:00
def process_clear_dialog ( self , * args , * * kwargs ) :
Logger . debug ( ' StatusWidget: process_dialog args: ' + str ( args ) )
if args and args [ 0 ] :
self . ids . mainplot . clear_data ( )
2015-01-03 22:03:11 +00:00
self . image_output_file = ' '
self . raw_data_output_file = ' '
2014-12-13 21:44:05 +00:00
self . remove_widget ( self . modal_view )
self . modal_view = None
2015-01-03 22:03:11 +00:00
def choose_output_file ( self , * args , * * kwargs ) :
Logger . debug ( ' Choose Output File pressed ' )
output_file_dialog = OutputFileDialog ( cancel = self . dismiss_popup ,
set_output_file = self . set_output_file )
2015-01-04 17:58:58 +00:00
self . create_popup ( title = " Choose outputfile " , content = output_file_dialog )
2015-01-03 22:03:11 +00:00
def set_output_file ( self , path , file_name , * args , * * kwargs ) :
#Logger.debug('Setouput file: ', str((path, file_name, args, kwargs)))
# If we're oaky with writing path + file_name + (.csv|.png)
files = {
' image_file ' : os . path . join ( path , file_name + ' .png ' ) ,
' raw_data_file ' : os . path . join ( path , file_name + ' .csv ' ) ,
}
errors = { }
has_errors = False
for k , file_name in files . iteritems ( ) :
errors [ k ] = self . validate_output_file ( file_name )
if errors [ k ] :
has_errors = True
if not has_errors :
Logger . debug ( ' MainWindow: Set output files: ' + str ( files ) )
self . raw_data_output_file = files [ ' raw_data_file ' ]
self . image_output_file = files [ ' image_file ' ]
self . dismiss_popup ( )
else :
2015-01-04 17:58:58 +00:00
# @TODO UI Feedback in the popup dialog
2015-01-03 22:03:11 +00:00
Logger . debug ( ' MainWindow: Set output file errors: ' + str ( errors ) )
def validate_output_file ( self , file_name ) :
errors = [ ]
# File doesn't exist, parent dir is writable
exists = os . path . exists ( file_name )
# Note, the access check isn't for security, but usability here.
access = os . access ( os . path . dirname ( file_name ) , os . W_OK )
Logger . debug ( ' MainWindow: validate_ouput_file file: ' + str ( file_name ) )
Logger . debug ( ' MainWindow: validate_output_file stat: ' + str ( exists ) )
Logger . debug ( ' MainWindow: validate_output_file parent dir write access: ' + str ( access ) )
if exists :
errors . append ( ' File already exists ' )
if not access :
errors . append ( ' No write access to parent directory ' )
return errors
2015-01-04 17:58:58 +00:00
def create_popup ( self , * args , * * kwargs ) :
self . _popup = Popup ( * args , * * kwargs )
self . _popup . open ( )
2015-01-03 22:03:11 +00:00
def dismiss_popup ( self , * args , * * kwargs ) :
self . _popup . dismiss ( )
def clear_output_files ( self ) :
self . raw_data_output_file = ' '
self . image_output_file = ' '
def on_raw_data_output_file ( self , * args , * * kwargs ) :
if self . raw_data_output_file :
root = self . raw_data_output_file . rsplit ( ' . ' , 1 )
Logger . debug ( ' MainWindow: ' + str ( root ) )
2015-01-04 17:58:58 +00:00
self . ids . output_file_selected . text = root [ 0 ] + ' [.csv|.png] '
else :
self . ids . output_file_selected . text = ' No file Chosen '
def select_reference_profile ( self , * args , * * kwargs ) :
dialog = SelectReferenceProfileDialog ( output = self . set_reference_profile ,
cancel = self . dismiss_popup )
self . create_popup ( title = ' Reference Profile Selection ' ,
content = dialog )
def set_reference_profile ( self , * args , * * kwargs ) :
Logger . debug ( ' MainWindow: reference profile kwargs ' + str ( kwargs ) )
ref_file = ' '
if kwargs [ ' file ' ] :
ref_file = kwargs [ ' file ' ] [ 0 ]
if not os . path . isfile ( ref_file ) :
# Not a valid path/file
return
self . reference_profile_file = ref_file
self . dismiss_popup ( )
def on_reference_profile_file ( self , * kargs , * * kwargs ) :
if not self . reference_profile_file :
self . ids . reference_profile_selected . text = ' No profile selected '
2015-01-03 22:03:11 +00:00
else :
2015-01-04 17:58:58 +00:00
self . ids . reference_profile_selected . text = self . reference_profile_file
2015-01-03 22:03:11 +00:00
class OutputFileDialog ( FloatLayout ) :
def __init__ ( self , * args , * * kwargs ) :
super ( OutputFileDialog , self ) . __init__ ( * args , * * kwargs )
self . cancel = kwargs [ ' cancel ' ]
self . set_output = kwargs [ ' set_output_file ' ]
def cancel ( self , * args , * * kwargs ) :
self . cancel ( * args , * * kwargs )
def set_output ( self , * args , * * kwargs ) :
self . set_output ( * args , * * kwargs )
2015-01-04 17:58:58 +00:00
class SelectReferenceProfileDialog ( FloatLayout ) :
def __init__ ( self , * args , * * kwargs ) :
super ( SelectReferenceProfileDialog , self ) . __init__ ( * args , * * kwargs )
self . cancel = kwargs [ ' cancel ' ]
self . output = kwargs [ ' output ' ]
def cancel ( self , * args , * * kwargs ) :
self . cancel ( * args , * * kwargs )
def set_output ( self , * args , * * kwargs ) :
self . output ( * args , * * kwargs )
2014-12-13 21:44:05 +00:00
class SerialPortButton ( Spinner ) :
__events__ = ( ' on_serial_port_changed ' , )
noValueText = StringProperty ( ' No serial port selected ' )
2014-12-13 17:57:33 +00:00
def __init__ ( self , * * kwargs ) :
super ( SerialPortButton , self ) . __init__ ( * * kwargs )
self . text = ' /dev/ttyACM0 '
2014-12-13 21:44:05 +00:00
self . register_event_type ( ' on_serial_port_changed ' )
self . values = temp_log . list_serial_ports ( )
2014-12-13 17:57:33 +00:00
2014-12-13 21:44:05 +00:00
def on_text ( self , instance , value ) :
if value == self . noValueText :
value = ' '
Logger . debug ( ' SerialPortDropdown: ' + ' selected with args ' + str ( value ) )
self . dispatch ( ' on_serial_port_changed ' , value )
def on_values ( self , instance , value ) :
if self . text not in self . values :
self . text = self . noValueText
def on_serial_port_changed ( self , * args , * * kwargs ) :
pass
2014-12-13 17:57:33 +00:00
2014-12-10 22:22:52 +00:00
class MyLabel ( Label ) :
2014-12-09 03:12:21 +00:00
2014-12-13 17:57:33 +00:00
def __init__ ( self , * * kwargs ) :
super ( MyLabel , self ) . __init__ ( * * kwargs )
def on_temperature_change ( self , value ) :
if not value :
value = ' n/a '
2014-12-10 22:22:52 +00:00
self . text = ' Current temperature: ' + str ( value )
2014-12-09 03:12:21 +00:00
2014-12-13 21:44:05 +00:00
class StatusWidget ( BoxLayout ) :
def __init__ ( self , * * kwargs ) :
super ( StatusWidget , self ) . __init__ ( * * kwargs )
self . states = [ ' running ' , ' paused ' ]
self . change_state_labels = { ' running ' : ' Pause ' , ' paused ' : ' Resume ' }
self . modal_view = None
def change_state ( self ) :
current_state = self . states . index ( self . state )
new_state = ( current_state + 1 ) % len ( self . states )
self . state = self . states [ new_state ]
def on_state ( self , instance , value ) :
Logger . debug ( ' StatusWidget: new state ' + str ( self . state ) )
2014-12-11 03:38:40 +00:00
class PlotWidget ( Image ) :
2015-01-03 22:03:11 +00:00
image_output_file = StringProperty ( ' ' )
raw_data_output_file = StringProperty ( ' ' )
2015-01-04 17:58:58 +00:00
reference_profile_file = StringProperty ( ' ' )
2015-03-21 18:07:17 +00:00
# @TODO Include a way to change these properties.
show_betaglucan = BooleanProperty ( True )
show_protease = BooleanProperty ( True )
show_betaamylase = BooleanProperty ( True )
show_alphaamylase = BooleanProperty ( True )
2015-04-01 16:01:29 +00:00
file_write_interval = NumericProperty ( 15 )
image_update_interval = NumericProperty ( 5 )
2015-04-02 03:06:03 +00:00
buildingImage = BooleanProperty ( False )
imageDataQueue = ObjectProperty ( None , allownone = True )
2015-03-21 18:07:17 +00:00
# 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 ' )
2015-01-03 22:03:11 +00:00
2014-12-11 03:38:40 +00:00
def __init__ ( self , * * kwargs ) :
2014-12-13 17:57:33 +00:00
super ( PlotWidget , self ) . __init__ ( * * kwargs )
2015-04-02 03:06:03 +00:00
self . imageThread = None
2014-12-11 03:38:40 +00:00
self . data = numpy . array ( [ ] , dtype = float ) ;
self . lastTime = 0
self . lastTemperature = 0
2015-03-22 22:54:30 +00:00
self . lastSave = 0
2014-12-11 03:38:40 +00:00
self . figure = matplotlib . pyplot . figure ( )
self . plot_axes = self . figure . add_subplot ( 1 , 1 , 1 )
self . plot_axes . hold ( False )
self . plot_axes . set_ylabel ( ' Temperature (deg C) ' )
self . plot_axes . set_xlabel ( ' Time ' )
self . plot_axes . set_title ( ' Recorded Temperature ' )
2014-12-13 17:57:33 +00:00
self . image_data = None
self . _image_raw_data = None
2015-01-04 17:58:58 +00:00
self . reference_profile = None
2015-04-01 16:01:29 +00:00
Clock . schedule_interval ( self . do_update , self . image_update_interval )
2015-04-02 03:06:03 +00:00
Clock . schedule_once ( self . do_update )
2015-03-21 18:07:17 +00:00
2015-04-01 16:01:29 +00:00
def on_image_update_interval ( self , * args ) :
# Reschedule the update function.
Clock . unschedule ( self . do_update , True )
Clock . schedule_interval ( self . do_update , self . image_update_interval )
2014-12-13 21:44:05 +00:00
def clear_data ( self ) :
2015-03-22 22:54:30 +00:00
self . update_output_files ( True )
2014-12-13 21:44:05 +00:00
self . data = numpy . array ( [ ] , dtype = float ) ;
2015-01-04 17:58:58 +00:00
self . reference_profile_file = ' '
self . reference_profile = None
2014-12-13 21:44:05 +00:00
self . texture = None
2015-01-04 17:58:58 +00:00
self . image_output_file = ' '
self . raw_data_output_file = ' '
2015-03-21 18:07:17 +00:00
self . do_update ( )
2014-12-13 21:44:05 +00:00
2015-04-01 16:01:29 +00:00
def do_update ( self , * args ) :
2015-04-02 03:06:03 +00:00
if self . buildingImage :
# Check end condition, retrieve data, clean-up
if self . imageThread . isAlive ( ) :
return
self . imageThread . join ( 0.01 )
image_data = None
try :
image_data = self . imageDataQueue . get_nowait ( )
except Queue . Empty :
pass
if image_data :
image = image_data [ ' kivy_image ' ]
self . texture = image . texture
self . _image_raw_data = image_data [ ' raw_image_data ' ]
self . imageThread = None
self . imageDataQueue = None
# @TODO self.update_output_files() should may be a scheduled task
self . update_output_files ( )
self . buildingImage = False
return
self . buildingImage = True
self . imageDataQueue = Queue . Queue ( )
settings = {
' patches ' : {
' betaglucan ' : { ' show ' : self . show_betaglucan ,
' vertices ' : self . betaglucan_vertices } ,
' protease ' : { ' show ' : self . show_protease ,
' vertices ' : self . protease_vertices } ,
' betaamylase ' : { ' show ' : self . show_betaamylase ,
' vertices ' : self . betaamylase_vertices } ,
' alphaamylase ' : { ' show ' : self . show_alphaamylase ,
' vertices ' : self . alphaamylase_vertices } ,
}
}
self . imageThread = threading . Thread ( None , threaded_image_builder , None ,
[ self . data , self . reference_profile ,
self . imageDataQueue , settings ] )
self . imageThread . start ( )
2015-03-22 22:54:30 +00:00
def update_output_files ( self , force = False ) :
# Do this only once every 15 seconds to avoid hitting the disk frequently
2015-04-01 16:01:29 +00:00
if force or time . time ( ) - self . lastSave > self . file_write_interval :
2015-03-22 22:54:30 +00:00
self . update_raw_data_output_file ( )
self . update_image_output_file ( )
self . lastSave = time . time ( )
2015-01-03 22:03:11 +00:00
def update_raw_data_output_file ( self ) :
if self . raw_data_output_file and self . data . any ( ) :
numpy . savetxt ( self . raw_data_output_file , self . data , delimiter = ' , ' )
def update_image_output_file ( self , image_data = None ) :
if not image_data :
2015-03-22 22:54:30 +00:00
image_data = self . _image_raw_data
2015-01-03 22:03:11 +00:00
if self . image_output_file and image_data :
f = open ( self . image_output_file , ' w ' )
2015-03-22 22:54:30 +00:00
if f :
Logger . debug ( ' Updated image output file ' )
f . write ( image_data )
f . close ( )
else :
Logger . debug ( ' update_image_output_file: Unable to open file ' + self . image_output_file )
2014-12-11 03:38:40 +00:00
2015-04-02 03:06:03 +00:00
def update_data ( self , data = list ( ) , * args ) :
points = [ ]
for point in data :
if ' data ' in point and ' time ' in point and ' temperature ' in point [ ' data ' ] :
points . append ( numpy . array ( [ ( point [ ' time ' ] , point [ ' data ' ] [ ' temperature ' ] ) ] ,
dtype = float , ndmin = 2 ) )
extra_data = numpy . vstack ( tuple ( points ) )
2014-12-11 03:38:40 +00:00
if not self . data . any ( ) :
2015-04-02 03:06:03 +00:00
self . data = extra_data
2014-12-11 03:38:40 +00:00
return
#Logger.debug('self.data: ' + str(self.data))
#Logger.debug('newpoint: ' + str(newpoint))
2015-04-02 03:06:03 +00:00
self . data = numpy . vstack ( ( self . data , extra_data ) )
2014-12-11 03:38:40 +00:00
2015-01-04 17:58:58 +00:00
def on_reference_profile_file ( self , * args , * * kwargs ) :
Logger . debug ( ' PlotWidget: on_reference_profile_file ' + str ( self . reference_profile_file ) )
self . load_reference_profile ( )
def load_reference_profile ( self ) :
if not self . reference_profile_file :
return
self . reference_profile = numpy . loadtxt ( self . reference_profile_file ,
delimiter = ' , ' )
Logger . debug ( ' PlotWidget: reference_profile: ' + str ( self . reference_profile ) )
2015-03-21 18:07:17 +00:00
2014-12-13 21:44:05 +00:00
class YesNoModalView ( Popup ) :
def process ( self , result , * args , * * kwargs ) :
Logger . debug ( ' YesNoModalView: ' + str ( result ) + ' ' + str ( args ) )
Logger . debug ( ' YesNoModalView: callback ' + str ( self . process_callback ) )
if self . process_callback :
self . process_callback ( result , * args , * * kwargs )
self . dismiss ( )
class StatusBar ( BoxLayout ) :
def update_status ( self , status ) :
Logger . debug ( ' StatusBar: received status ' + str ( status ) )
Logger . debug ( ' StatusBar: ids available ' + str ( self . ids ) )
if ' message ' in status :
self . ids . status_message . text = status [ ' message ' ]
if ' status ' in status :
current_status = status [ ' status ' ]
if current_status == ' ok ' :
self . ids . status_text . text = ' Running '
self . ids . status_image . source = ' ./images/ok.png '
if not self . ids . status_message . text :
self . ids . status_message . text = " Everything ' s perfectly all right now. We ' re fine. We ' re all fine here now, thank you. How are you? "
elif current_status == ' error ' :
self . ids . status_text . text = ' ERROR '
self . ids . status_image . source = ' ./images/error.png '
elif current_status == ' paused ' :
self . ids . status_text . text = ' Paused '
self . ids . status_image . source = ' ./images/warning.png '
else :
self . ids . status_text . text = ' Unknown Status: ' + current_status
self . ids . status_image . source = ' ./images/warning.png '
2015-04-02 03:06:03 +00:00
def threaded_image_builder ( data , reference_profile , dataQueue , settings = dict ( ) ) :
figure , plot_axes = init_plot ( settings )
start = time . time ( )
image_data = create_graph ( figure , plot_axes , data , reference_profile , settings )
Logger . debug ( ' threaded_image_builder: Took ' + str ( time . time ( ) - start ) + ' seconds to build image data ' )
return_data = dict ( )
start = time . time ( )
image_data_raw = image_data . getvalue ( )
image_bytes = io . BytesIO ( image_data_raw )
return_data [ ' raw_image_data ' ] = image_data_raw
return_data [ ' kivy_image ' ] = kivy . core . image . Image ( image_bytes , ext = ' png ' )
Logger . debug ( ' threaded_image_builder: Took ' + str ( time . time ( ) - start ) + ' seconds to manipulate image data ' )
dataQueue . put_nowait ( return_data )
def init_plot ( settings = dict ( ) ) :
figure = matplotlib . pyplot . figure ( )
plot_axes = figure . add_subplot ( 1 , 1 , 1 )
plot_axes . hold ( False )
plot_axes . set_ylabel ( ' Temperature (deg C) ' )
plot_axes . set_xlabel ( ' Time ' )
plot_axes . set_title ( ' Recorded Temperature ' )
return figure , plot_axes
def create_graph ( figure , plot_axes , data , reference_profile , settings = dict ( ) ) :
# Add in polygons for hilightning particular temperature ranges of interest.
plot_args = [ ]
t = None
time_max = 60
temp_max = 80
handles = [ ]
if 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 reference_profile is not None and reference_profile . any ( ) :
#reference_data = numpy.copy(self.reference_profile)
reference_data = reference_profile . 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 ] )
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 = plot_axes . plot ( * plot_args )
# Patches must be changed after the axes are plotted.
graph_update_patches ( figure , plot_axes , time_max , settings )
# Disabled the legend for the moment.
# @TODO Make a small subplot next to the main plot into which legends may be placed.
#self.plot_axes.legend(lines, handles, 'lower right', title = 'Temperature Profiles')
# 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)
plot_axes . set_ylim ( 0 , temp_max )
image_data = cStringIO . StringIO ( )
# Save a 640x480 image, see http://stackoverflow.com/questions/13714454/specifying-and-saving-a-figure-with-exact-size-in-pixels
#canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(figure)
#canvas.draw()
#renderer = canvas.get_renderer()
#raw_data = renderer.tostring_argb()
#size = canvas.get_width_height()
figure . savefig ( image_data , format = ' png ' , dpi = 96 , figsize = ( 640 / 96 , 480 / 96 ) )
#image_data.seek(0)
return image_data
#return raw_data, size
def graph_update_patches ( figure , plot_axes , time_max , settings ) :
if ' patches ' in settings :
settings = settings [ ' patches ' ]
items = list ( )
for patch_id , patch_settings in settings . iteritems ( ) :
items . append ( ( patch_settings [ ' show ' ] , patch_settings [ ' vertices ' ] ) )
legend_handles = [ ]
legend_strings = [ ]
for show , config in items :
color = ' 0.5 ' # default color.
if show :
origin , width , height , color , name = config
if time_max is not None :
width = time_max
p = plot_axes . add_patch (
matplotlib . patches . Rectangle ( origin , width , height ,
fc = color , visible = True ,
fill = True )
)
legend_handles . append ( p )
legend_strings . append ( name )
# Disabled for the moment
figure . legend ( legend_handles , legend_strings , ' lower right ' ,
title = ' Enzyme Activity Zones ' )
return legend_handles
2014-12-09 03:12:21 +00:00
if __name__ == ' __main__ ' :
2014-12-10 22:22:52 +00:00
dataThread = threading . Thread ( None , temp_log . threaded_reader , None , [ ' /dev/ttyACM0 ' , dataThreadDataQueue , dataThreadCommandQueue ] )
MainApp ( ) . run ( )