Implemented output file choosing to save png and csv (raw data) on every update
This commit is contained in:
parent
41d1e07749
commit
5ff70ebd24
39
main.kv
39
main.kv
|
@ -35,6 +35,34 @@
|
|||
id: status_message
|
||||
size_hint_x: 0.80
|
||||
|
||||
<OutputFileDialog>:
|
||||
textinput: text_input
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
Label:
|
||||
text: "Select a folder & file name for the output. A csv & png file will be saved."
|
||||
size_hint_y: 0.1
|
||||
FileChooserListView:
|
||||
id: filechooser
|
||||
on_selection: text_input.text = self.selection and self.selection[0] or ''
|
||||
BoxLayout:
|
||||
size_hint_y: 0.1
|
||||
orientation: 'horizontal'
|
||||
Label:
|
||||
text: 'File name:'
|
||||
size_hint_x: 0.2
|
||||
TextInput:
|
||||
id: text_input
|
||||
BoxLayout:
|
||||
size_hint_y: 0.1
|
||||
orientation: 'horizontal'
|
||||
Button:
|
||||
text: 'Cancel'
|
||||
on_press: root.cancel()
|
||||
Button:
|
||||
text: 'Set Output File'
|
||||
on_press: root.set_output(filechooser.path, text_input.text)
|
||||
|
||||
<MainWindow>:
|
||||
label_wid: "Temperature Monitor"
|
||||
current_temperature: current_temperature
|
||||
|
@ -44,6 +72,10 @@
|
|||
on_lastTemperature: mainplot.on_lastTemperature(self.lastTemperature)
|
||||
on_lastTime: mainplot.on_lastTime(self.lastTime)
|
||||
on_lastStatus: status_bar.update_status(self.lastStatus)
|
||||
raw_data_output_file: ''
|
||||
image_output_file: ''
|
||||
on_raw_data_output_file: mainplot.raw_data_output_file = self.raw_data_output_file
|
||||
on_image_output_file: mainplot.image_output_file = self.image_output_file
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
BoxLayout:
|
||||
|
@ -53,6 +85,7 @@
|
|||
orientation: 'horizontal'
|
||||
MyLabel:
|
||||
id: current_temperature
|
||||
text: "Current Temperature: n/a"
|
||||
StatusWidget:
|
||||
id: startstop_widget
|
||||
state: 'running'
|
||||
|
@ -66,13 +99,17 @@
|
|||
id: clear_button
|
||||
text: 'Clear'
|
||||
on_press: root.initiate_clear_dialog()
|
||||
Label:
|
||||
Button:
|
||||
id: output_button
|
||||
text: 'No File Chosen'
|
||||
on_press: root.choose_output_file()
|
||||
SerialPortButton:
|
||||
id: serial_chooser_button
|
||||
on_serial_port_changed: root.serial_port_changed(self.text)
|
||||
PlotWidget:
|
||||
id: mainplot
|
||||
on_image_output_file: self.update_image_output_file()
|
||||
on_raw_data_output_file: self.update_raw_data_output_file()
|
||||
size_hint_y: 0.8
|
||||
StatusBar:
|
||||
size_hint_y: 0.1
|
||||
|
|
147
main.py
147
main.py
|
@ -20,11 +20,13 @@ from kivy.uix.modalview import ModalView
|
|||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.spinner import Spinner
|
||||
import kivy.lang
|
||||
import numpy
|
||||
import matplotlib
|
||||
matplotlib.use('Agg')
|
||||
import matplotlib.image
|
||||
import matplotlib.pyplot
|
||||
import numpy
|
||||
import os
|
||||
import os.path
|
||||
import pygame.image
|
||||
import Queue
|
||||
import StringIO
|
||||
|
@ -63,6 +65,8 @@ class MainWindow(FloatLayout):
|
|||
recordingState = StringProperty('')
|
||||
state = StringProperty('running')
|
||||
lastStatus = DictProperty({})
|
||||
image_output_file = StringProperty('')
|
||||
raw_data_output_file = StringProperty('')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(MainWindow, self).__init__(**kwargs)
|
||||
|
@ -162,10 +166,93 @@ class MainWindow(FloatLayout):
|
|||
Logger.debug('StatusWidget: process_dialog args: ' + str(args))
|
||||
if args and args[0]:
|
||||
self.ids.mainplot.clear_data()
|
||||
self.image_output_file = ''
|
||||
self.raw_data_output_file = ''
|
||||
self.remove_widget(self.modal_view)
|
||||
self.modal_view = None
|
||||
|
||||
|
||||
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)
|
||||
self._popup = Popup(title="Choose outputfile", content = output_file_dialog)
|
||||
self._popup.open()
|
||||
|
||||
|
||||
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:
|
||||
# UI Feedback in the popup dialog
|
||||
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
|
||||
|
||||
|
||||
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))
|
||||
self.ids.output_button.text = root[0] + '[.csv|.png]'
|
||||
else:
|
||||
self.ids.output_button.text = 'No File Chosen'
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class SerialPortButton(Spinner):
|
||||
|
||||
__events__ = ('on_serial_port_changed',)
|
||||
|
@ -229,6 +316,9 @@ class StatusWidget(BoxLayout):
|
|||
|
||||
class PlotWidget(Image):
|
||||
|
||||
image_output_file = StringProperty('')
|
||||
raw_data_output_file = StringProperty('')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(PlotWidget, self).__init__(**kwargs)
|
||||
self.data = numpy.array([], dtype=float);
|
||||
|
@ -250,22 +340,7 @@ class PlotWidget(Image):
|
|||
|
||||
|
||||
def do_update(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)
|
||||
#Logger.debug('time: ' + str(t[0]))
|
||||
#Logger.debug('temperature: ' + str(temperature[0]))
|
||||
self.plot_axes.plot(t[0], temperature[0])
|
||||
image_data = StringIO.StringIO()
|
||||
self.figure.savefig(image_data, format = 'png')
|
||||
image_data.seek(0)
|
||||
self._image_raw_data = image_data
|
||||
image_data = self.to_image_data()
|
||||
# We can't use ImageLoaders since they assume it's a file on disk.
|
||||
# This replicates code from ImageLoaderPygame.load() and ImageLoaderBase.populate()
|
||||
try:
|
||||
|
@ -282,6 +357,44 @@ class PlotWidget(Image):
|
|||
self.image_data = ImageData(im.get_width(), im.get_height(), fmt, data)
|
||||
self.texture = Texture.create_from_data(self.image_data)
|
||||
self.texture.flip_vertical()
|
||||
# Update output files, if any
|
||||
# @TODO Add in an update interval so the disk isn't hammered on every single update
|
||||
self.update_image_output_file()
|
||||
self.update_raw_data_output_file()
|
||||
|
||||
|
||||
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)
|
||||
#Logger.debug('time: ' + str(t[0]))
|
||||
#Logger.debug('temperature: ' + str(temperature[0]))
|
||||
self.plot_axes.plot(t[0], temperature[0])
|
||||
image_data = StringIO.StringIO()
|
||||
self.figure.savefig(image_data, format = 'png')
|
||||
image_data.seek(0)
|
||||
self._image_raw_data = image_data
|
||||
return self._image_raw_data
|
||||
|
||||
|
||||
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:
|
||||
image_data = self.to_image_data()
|
||||
if self.image_output_file and image_data:
|
||||
f = open(self.image_output_file, 'w')
|
||||
f.write(image_data.read())
|
||||
f.close()
|
||||
|
||||
|
||||
def on_lastTemperature(self, value):
|
||||
|
|
Loading…
Reference in New Issue