Skip to content
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, absolute_import, print_function, unicode_literals
import io
import os
import logging
logger = logging.getLogger(__name__)
try:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
except ImportError:
print("ERR: GTK not available")
sys.exit(1)
import numpy as np
from ..errors import InputError, VarError
from ..params import save_params, load_params
def manSave(current_exp):
fcd = Gtk.FileChooserDialog("Save…", None, Gtk.FileChooserAction.SAVE,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
filters = [Gtk.FileFilter()]
filters[0].set_name("Tab-separated Text (.txt)")
filters[0].add_pattern("*.txt")
fcd.set_do_overwrite_confirmation(True)
for i in filters:
fcd.add_filter(i)
response = fcd.run()
if response == Gtk.ResponseType.OK:
path = fcd.get_filename().decode("utf-8")
logger.info("Selected filepath: %s", path)
filter_selection = fcd.get_filter().get_name().decode("utf-8")
if filter_selection.endswith("(.txt)"):
save_text(current_exp, path)
fcd.destroy()
elif response == Gtk.ResponseType.CANCEL:
fcd.destroy()
def plot_save_dialog(plots):
fcd = Gtk.FileChooserDialog("Save Plot…", None,
Gtk.FileChooserAction.SAVE,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
filters = [Gtk.FileFilter()]
filters[0].set_name("Portable Document Format (.pdf)")
filters[0].add_pattern("*.pdf")
filters.append(Gtk.FileFilter())
filters[1].set_name("Portable Network Graphics (.png)")
filters[1].add_pattern("*.png")
fcd.set_do_overwrite_confirmation(True)
for i in filters:
fcd.add_filter(i)
response = fcd.run()
if response == Gtk.ResponseType.OK:
path = fcd.get_filename().decode("utf-8")
logger.info("Selected filepath: %r", path)
filter_selection = fcd.get_filter().get_name().decode("utf-8")
if filter_selection.endswith("(.pdf)"):
if not path.endswith(".pdf"):
path += ".pdf"
elif filter_selection.endswith("(.png)"):
if not path.endswith(".png"):
path += ".png"
save_plot(plots, path)
fcd.destroy()
elif response == Gtk.ResponseType.CANCEL:
fcd.destroy()
def man_param_save(window):
fcd = Gtk.FileChooserDialog("Save Parameters…",
None,
Gtk.FileChooserAction.SAVE,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
filters = [Gtk.FileFilter()]
filters[0].set_name("Parameter File (.yml)")
filters[0].add_pattern("*.yml")
fcd.set_do_overwrite_confirmation(True)
for i in filters:
fcd.add_filter(i)
response = fcd.run()
if response == Gtk.ResponseType.OK:
path = fcd.get_filename().decode("utf-8")
logger.info("Selected filepath: %s", path)
if not path.endswith(".yml"):
path += '.yml'
save_params(window, path)
fcd.destroy()
elif response == Gtk.ResponseType.CANCEL:
fcd.destroy()
def man_param_load(window):
fcd = Gtk.FileChooserDialog("Load Parameters…",
None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
filters = [Gtk.FileFilter()]
filters[0].set_name("Parameter File (.yml)")
filters[0].add_pattern("*.yml")
for i in filters:
fcd.add_filter(i)
response = fcd.run()
if response == Gtk.ResponseType.OK:
path = fcd.get_filename().decode("utf-8")
logger.info("Selected filepath: %s", path)
load_params(window, path)
fcd.destroy()
elif response == Gtk.ResponseType.CANCEL:
fcd.destroy()
def autoSave(exp, path, name):
if name == "":
name = "file"
path += '/'
path += name
save_text(exp, path)
def autoPlot(exp, path, name):
if name == "":
name = "file"
path += '/'
path += name
if not (path.endswith(".pdf") or path.endswith(".png")):
path += ".pdf"
save_plot(exp, path)
def save_text(exp, path):
savestrings = exp.get_save_strings()
path = path.rstrip('.txt')
num = ''
j = 0
for key, text in savestrings.items(): # Test for existing files of any kind
while os.path.exists("{}{}-{}.txt".format(path, num, key)):
j += 1
num = j
save_path = "{}{}".format(path, num)
for key, text in savestrings.items():
with open('{}-{}.txt'.format(save_path, key), 'w') as f:
f.write(text)
def save_plot(exp, path):
"""Saves everything in exp.plots to path. Appends a number for duplicates.
If no file extension or unknown, uses pdf.
"""
name, _sep, ext = path.rpartition('.')
if _sep == '':
name = ext
ext = 'pdf'
num = ''
j = 0
for i in exp.plots: # Test for any existing files
plot_type = '_'.join(i.name.lower().split())
while os.path.exists("{}{}-{}.{}".format(name, num, plot_type, ext)):
j += 1
num = j
for i in exp.plots: # save data
plot_type = '_'.join(i.name.lower().split())
i.figure.savefig("{}{}-{}.{}".format(name, num, plot_type, ext))
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface> <interface>
<requires lib="gtk+" version="2.24"/> <requires lib="gtk+" version="3.10"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkWindow" id="window1"> <object class="GtkWindow" id="window1">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child> <child>
<object class="GtkScrolledWindow" id="scrolledwindow1"> <object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property> <property name="hexpand">True</property>
<property name="vscrollbar_policy">automatic</property> <property name="vexpand">True</property>
<child> <child>
<object class="GtkViewport" id="viewport1"> <object class="GtkViewport" id="viewport1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="shadow_type">none</property> <property name="shadow_type">none</property>
<child> <child>
<object class="GtkVBox" id="vbox1"> <object class="GtkBox" id="vbox1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child> <child>
<object class="GtkFrame" id="frame1"> <object class="GtkFrame" id="frame1">
<property name="visible">True</property> <property name="visible">True</property>
...@@ -33,11 +34,11 @@ ...@@ -33,11 +34,11 @@
<property name="left_padding">5</property> <property name="left_padding">5</property>
<property name="right_padding">5</property> <property name="right_padding">5</property>
<child> <child>
<object class="GtkTable" id="table1"> <object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="n_rows">3</property> <property name="row_homogeneous">True</property>
<property name="n_columns">3</property> <property name="column_homogeneous">True</property>
<child> <child>
<object class="GtkLabel" id="label3"> <object class="GtkLabel" id="label3">
<property name="visible">True</property> <property name="visible">True</property>
...@@ -46,8 +47,7 @@ ...@@ -46,8 +47,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">0</property>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
...@@ -58,8 +58,7 @@ ...@@ -58,8 +58,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">2</property> <property name="left_attach">2</property>
<property name="right_attach">3</property> <property name="top_attach">0</property>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
...@@ -68,7 +67,8 @@ ...@@ -68,7 +67,8 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
</object> </object>
<packing> <packing>
<property name="y_options">GTK_FILL</property> <property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing> </packing>
</child> </child>
<child> <child>
...@@ -78,9 +78,8 @@ ...@@ -78,9 +78,8 @@
<property name="label" translatable="yes">Cleaning</property> <property name="label" translatable="yes">Cleaning</property>
</object> </object>
<packing> <packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property> <property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
...@@ -90,9 +89,8 @@ ...@@ -90,9 +89,8 @@
<property name="label" translatable="yes">Deposition</property> <property name="label" translatable="yes">Deposition</property>
</object> </object>
<packing> <packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property> <property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
...@@ -103,19 +101,12 @@ ...@@ -103,19 +101,12 @@
<property name="width_chars">8</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property> <property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property> <property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property> <property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
...@@ -126,19 +117,12 @@ ...@@ -126,19 +117,12 @@
<property name="width_chars">8</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property> <property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property> <property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property> <property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
...@@ -149,19 +133,12 @@ ...@@ -149,19 +133,12 @@
<property name="width_chars">8</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property> <property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property> <property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">2</property> <property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">2</property> <property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
...@@ -172,21 +149,17 @@ ...@@ -172,21 +149,17 @@
<property name="width_chars">8</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property> <property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property> <property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">2</property> <property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property> <property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child>
<placeholder/>
</child>
</object> </object>
</child> </child>
</object> </object>
...@@ -221,251 +194,198 @@ ...@@ -221,251 +194,198 @@
<property name="left_padding">5</property> <property name="left_padding">5</property>
<property name="right_padding">5</property> <property name="right_padding">5</property>
<child> <child>
<object class="GtkTable" id="table2"> <object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="n_rows">7</property> <property name="row_homogeneous">True</property>
<property name="n_columns">2</property> <property name="column_homogeneous">True</property>
<property name="column_spacing">10</property>
<property name="homogeneous">True</property>
<child> <child>
<object class="GtkLabel" id="label8"> <object class="GtkEntry" id="scans_entry">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">True</property>
<property name="label" translatable="yes">Start (mV)</property> <property name="invisible_char"></property>
<property name="width_chars">8</property>
<property name="text" translatable="yes">0</property>
<property name="xalign">1</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
</object> </object>
<packing> <packing>
<property name="y_options">GTK_FILL</property> <property name="left_attach">1</property>
<property name="top_attach">6</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label9"> <object class="GtkCheckButton" id="cyclic_checkbutton">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">True</property>
<property name="label" translatable="yes">Stop (mV)</property> <property name="receives_default">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="use_stock">True</property>
<property name="draw_indicator">True</property>
</object> </object>
<packing> <packing>
<property name="top_attach">1</property> <property name="left_attach">1</property>
<property name="bottom_attach">2</property> <property name="top_attach">5</property>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label10"> <object class="GtkLabel" id="label15">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Step Size (mV)</property> <property name="label" translatable="yes">Scans</property>
</object> </object>
<packing> <packing>
<property name="top_attach">2</property> <property name="left_attach">0</property>
<property name="bottom_attach">3</property> <property name="top_attach">6</property>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="start_entry"> <object class="GtkLabel" id="label14">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="invisible_char"></property> <property name="tooltip_text" translatable="yes">Scan both forwards and backwards.</property>
<property name="width_chars">8</property> <property name="label" translatable="yes">Cyclic Mode</property>
<property name="text" translatable="yes">0</property>
<property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">0</property>
<property name="right_attach">2</property> <property name="top_attach">5</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
<property name="y_padding">3</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="stop_entry"> <object class="GtkEntry" id="freq_entry">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="invisible_char"></property> <property name="invisible_char"></property>
<property name="width_chars">8</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property> <property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property> <property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">4</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
<property name="y_padding">3</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="step_entry"> <object class="GtkEntry" id="pulse_entry">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="invisible_char"></property> <property name="invisible_char"></property>
<property name="width_chars">8</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property> <property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property> <property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">3</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
<property name="y_padding">3</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label11"> <object class="GtkLabel" id="label12">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Pulse Height (mV)</property> <property name="label" translatable="yes">Frequency (Hz)</property>
</object> </object>
<packing> <packing>
<property name="top_attach">3</property> <property name="left_attach">0</property>
<property name="bottom_attach">4</property> <property name="top_attach">4</property>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label12"> <object class="GtkLabel" id="label11">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Frequency (Hz)</property> <property name="label" translatable="yes">Pulse Height (mV)</property>
</object> </object>
<packing> <packing>
<property name="top_attach">4</property> <property name="left_attach">0</property>
<property name="bottom_attach">5</property> <property name="top_attach">3</property>
<property name="y_options">GTK_FILL</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="pulse_entry"> <object class="GtkEntry" id="step_entry">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="invisible_char"></property> <property name="invisible_char"></property>
<property name="width_chars">8</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property> <property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property> <property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
<property name="y_padding">3</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="freq_entry"> <object class="GtkEntry" id="stop_entry">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="invisible_char"></property> <property name="invisible_char"></property>
<property name="width_chars">8</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property> <property name="text" translatable="yes">0</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property> <property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property> <property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">1</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
<property name="y_padding">3</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label14"> <object class="GtkEntry" id="start_entry">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Scan both forwards and backwards.</property> <property name="invisible_char"></property>
<property name="label" translatable="yes">Cyclic Mode</property> <property name="width_chars">8</property>
<property name="text" translatable="yes">0</property>
<property name="xalign">1</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
</object> </object>
<packing> <packing>
<property name="top_attach">5</property> <property name="left_attach">1</property>
<property name="bottom_attach">6</property> <property name="top_attach">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label15"> <object class="GtkLabel" id="label10">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Scans</property> <property name="label" translatable="yes">Step Size (mV)</property>
</object> </object>
<packing> <packing>
<property name="top_attach">6</property> <property name="left_attach">0</property>
<property name="bottom_attach">7</property> <property name="top_attach">2</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkCheckButton" id="cyclic_checkbutton"> <object class="GtkLabel" id="label9">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="receives_default">False</property> <property name="label" translatable="yes">Stop (mV)</property>
<property name="use_stock">True</property>
<property name="draw_indicator">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">0</property>
<property name="right_attach">2</property> <property name="top_attach">1</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="x_options"/>
<property name="y_options"/>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="scans_entry"> <object class="GtkLabel" id="label8">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="invisible_char"></property> <property name="label" translatable="yes">Start (mV)</property>
<property name="width_chars">8</property>
<property name="text" translatable="yes">0</property>
<property name="xalign">1</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">0</property>
<property name="right_attach">2</property> <property name="top_attach">0</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="x_options"/>
<property name="y_options">GTK_FILL</property>
<property name="y_padding">3</property>
</packing> </packing>
</child> </child>
</object> </object>
...@@ -492,9 +412,10 @@ ...@@ -492,9 +412,10 @@
<object class="GtkLabel" id="label13"> <object class="GtkLabel" id="label13">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Note: The ADC converts continuously during SWV measurements and has priority over DAC changes. ADC sample rate should be significantly larger than SWV frequency for accurate timing.</property> <property name="label" translatable="yes">Note: ADC samples 1/(ADC Frequency) before end of pulse. ADC frequency should be significantly larger than SWV frequency to reduce capacitive current.</property>
<property name="justify">center</property>
<property name="wrap">True</property> <property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="width_chars">43</property>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import zmq import zmq
import zmq.error
#signals #signals
CONREQ = "0" CONREQ = "0"
...@@ -57,9 +56,11 @@ class microdropConnection(object): ...@@ -57,9 +56,11 @@ class microdropConnection(object):
[1] -- The recieved message or "" if no message received. [1] -- The recieved message or "" if no message received.
""" """
if self.state == SEND: if self.state == SEND:
print "WAR: Microdrop Connection state invalid, resetting..." print "WAR: [uDrop-listen] Connection state invalid, resetting..."
self.reset() # self.reset()
self.__init__(self.port) # self.__init__(self.port)
return (False, "")
try: try:
message = self.soc.recv(flags=zmq.NOBLOCK, copy=True) message = self.soc.recv(flags=zmq.NOBLOCK, copy=True)
self.state = SEND self.state = SEND
...@@ -74,7 +75,7 @@ class microdropConnection(object): ...@@ -74,7 +75,7 @@ class microdropConnection(object):
data -- a str to be sent data -- a str to be sent
""" """
if self.state == RECV: if self.state == RECV:
print "WAR: Microdrop Connection state invalid, resetting..." print "WAR: [uDrop-reply] Connection state invalid, resetting..."
self.reset() self.reset()
self.__init__(self.port) self.__init__(self.port)
return False return False
......
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# DStat Interface - An interface for the open hardware DStat potentiostat # DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden - # Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca> # Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
...@@ -16,10 +17,66 @@ ...@@ -16,10 +17,66 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from distutils.core import setup import yaml
from Cython.Build import cythonize
setup( from errors import InputError
ext_modules = cythonize("*.pyx")
) logger = logging.getLogger(__name__)
\ No newline at end of file
def get_params(window):
"""Fetches and returns dict of all parameters for saving."""
selection = window.exp_window.expcombobox.get_active_id()
parameters = {'experiment_index' : selection}
try:
parameters['version'] = window.version
except AttributeError: # Will be thrown if not connected to DStat
pass
try:
parameters.update(window.adc_pot.params)
except InputError:
logger.info("No gain selected.")
parameters.update(window.exp_window.get_params(selection))
parameters.update(window.analysis_opt_window.params)
return parameters
def save_params(window, path):
"""Fetches current params and saves to path."""
logger.info("Save to: %s", path)
params = get_params(window)
with open(path, 'w') as f:
yaml.dump(params, f)
def load_params(window, path):
"""Loads params from a path into UI elements."""
try:
get_params(window)
except InputError: # Will be thrown because no experiment will be selected
pass
except KeyError: # Will be thrown because no experiment will be selected
pass
with open(path, 'r') as f:
params = yaml.load(f)
set_params(window, params)
def set_params(window, params):
window.adc_pot.params = params
if 'experiment_index' in params:
window.exp_window.expcombobox.set_active_id(params['experiment_index'])
window.exp_window.set_params(params['experiment_index'], params)
window.analysis_opt_window.params = params
window.params_loaded = True
# -*- coding: utf-8 -*-
import logging
from params import get_params, set_params, load_params, save_params
from interface.save import save_text, save_plot
from zmq_plugin.plugin import Plugin as ZmqPlugin
from zmq_plugin.schema import decode_content_data
import gtk
import zmq
logger = logging.getLogger(__name__)
def get_hub_uri(default='tcp://localhost:31000', parent=None):
message = 'Please enter 0MQ hub URI:'
d = gtk.MessageDialog(parent=parent, flags=gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT,
type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK_CANCEL,
message_format=message)
entry = gtk.Entry()
entry.set_text(default)
d.vbox.pack_end(entry)
d.vbox.show_all()
entry.connect('activate', lambda _: d.response(gtk.RESPONSE_OK))
d.set_default_response(gtk.RESPONSE_OK)
r = d.run()
text = entry.get_text().decode('utf8')
d.destroy()
if r == gtk.RESPONSE_OK:
return text
else:
return None
class DstatPlugin(ZmqPlugin):
'''
Public 0MQ plugin API.
'''
def __init__(self, parent, *args, **kwargs):
self.parent = parent
super(DstatPlugin, self).__init__(*args, **kwargs)
def check_sockets(self):
'''
Check for messages on command and subscription sockets and process
any messages accordingly.
'''
try:
msg_frames = self.command_socket.recv_multipart(zmq.NOBLOCK)
except zmq.Again:
pass
else:
self.on_command_recv(msg_frames)
try:
msg_frames = self.subscribe_socket.recv_multipart(zmq.NOBLOCK)
source, target, msg_type, msg_json = msg_frames
self.most_recent = msg_json
except zmq.Again:
pass
except:
logger.error('Error processing message from subscription '
'socket.', exc_info=True)
return True
def on_execute__load_params(self, request):
'''
Args
----
params_path (str) : Path to file for parameters yaml file.
'''
data = decode_content_data(request)
load_params(self.parent, data['params_path'])
def on_execute__save_params(self, request):
'''
Args
----
params_path (str) : Path to file for parameters yaml file.
'''
data = decode_content_data(request)
save_params(self.parent, data['params_path'])
def on_execute__set_params(self, request):
'''
Args
----
(dict) : Parameters dictionary in format returned by `get_params`.
'''
data = decode_content_data(request)
set_params(self.parent, data['params'])
def on_execute__get_params(self, request):
return get_params(self.parent)
def on_execute__run_active_experiment(self, request):
data = decode_content_data(request)
self.parent.statusbar.push(self.parent.message_context_id, "µDrop "
"acquisition requested.")
return self.parent.run_active_experiment(
param_override=data.get('params'),
metadata=data.get('metadata')
)
def on_execute__set_metadata(self, request=None):
'''
Args
----
(dict) : Dictionary of metadata to be used in subsequent
experiments. Should include `device_id`, `patient_id`, and
`experiment_id`. Leave blank to reset all metadata fields or set
individual keys to `None` to reset individual values.
'''
data = decode_content_data(request)
self.parent.metadata = request
def on_execute__save_text(self, request):
'''
Args
----
save_data_path (str) : Path to file to save text data.
'''
data = decode_content_data(request)
save_text(self.parent.current_exp, data['save_data_path'])
def on_execute__save_plot(self, request):
'''
Args
----
save_plot_path (str) : Path to file to save plot.
'''
data = decode_content_data(request)
save_plot(self.parent.current_exp, data['save_plot_path'])
def on_execute__acquisition_complete(self, request):
'''
Args
----
Returns
-------
(datetime.datetime or None) : The completion time of the experiment
corresponding to the specified UUID.
'''
data = decode_content_data(request)
self.parent.statusbar.push(self.parent.message_context_id, "µDrop "
"notified of completed acquisition.")
if data['experiment_id'] in self.parent.completed_experiment_ids:
return self.parent.completed_experiment_ids[data['experiment_id']]
elif data['experiment_id'] == self.parent.active_experiment_id:
return None
else:
raise KeyError('Unknown experiment ID: %s' % data['experiment_id'])
# -*- coding: utf-8 -*-
"""Calculates the current version number.
If possible, uses output of “git describe” modified to conform to the
visioning scheme that setuptools uses (see PEP 386). Releases must be
labelled with annotated tags (signed tags are annotated) of the following
format:
v<num>(.<num>)+ [ {a|b|c|rc} <num> (.<num>)* ]
If “git describe” returns an error (likely because we're in an unpacked copy
of a release tarball, rather than a git working copy), or returns a tag that
does not match the above format, version is read from RELEASE-VERSION file.
To use this script, simply import it your setup.py file, and use the results
of getVersion() as your package version:
import version
setup(
version=version.getVersion(),
.
.
.
)
This will automatically update the RELEASE-VERSION file. The RELEASE-VERSION
file should *not* be checked into git but it *should* be included in sdist
tarballs (as should version.py file). To do this, run:
echo include RELEASE-VERSION version.py >>MANIFEST.in
echo RELEASE-VERSION >>.gitignore
With that setup, a new release can be labelled by simply invoking:
git tag -s v1.0
"""
__author__ = ('Douglas Creager <dcreager@dcreager.net>',
'Michal Nazarewicz <mina86@mina86.com>')
__license__ = 'This file is placed into the public domain.'
__maintainer__ = 'Michal Nazarewicz'
__email__ = 'mina86@mina86.com'
__all__ = ('getVersion')
import re
import subprocess
import sys
import os.path
import inspect
RELEASE_VERSION_FILE = '{}/RELEASE-VERSION'.format(
os.path.dirname(os.path.abspath(inspect.stack()[0][1])))
# http://www.python.org/dev/peps/pep-0386/
_PEP386_SHORT_VERSION_RE = r'\d+(?:\.\d+)+(?:(?:[abc]|rc)\d+(?:\.\d+)*)?'
_PEP386_VERSION_RE = r'^%s(?:\.post\d+)?(?:\.dev\d+)?$' % (
_PEP386_SHORT_VERSION_RE)
_GIT_DESCRIPTION_RE = r'^v(?P<ver>%s)-(?P<commits>\d+)-g(?P<sha>[\da-f]+)$' % (
_PEP386_SHORT_VERSION_RE)
def readGitVersion():
try:
proc = subprocess.Popen(('git', 'describe', '--long',
'--match', 'v[0-9]*.*'),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
data, _ = proc.communicate()
if proc.returncode:
return None
ver = data.splitlines()[0].strip()
proc = subprocess.Popen(('git', 'rev-parse', '--abbrev-ref', 'HEAD'),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
branch, _ = proc.communicate()
if proc.returncode:
return None
except:
return None
if not ver:
return None
m = re.search(_GIT_DESCRIPTION_RE, ver)
if not m:
sys.stderr.write('version: git description (%s) is invalid, '
'ignoring\n' % ver)
return None
commits = int(m.group('commits'))
if not commits:
version = m.group('ver')
else:
version = '%s.post%d' % (
m.group('ver'), commits)
if branch.strip() != 'master':
version += '.dev%d' % int(m.group('sha'), 16)
return version
def readReleaseVersion():
try:
fd = open(RELEASE_VERSION_FILE)
try:
ver = fd.readline().strip()
finally:
fd.close()
if not re.search(_PEP386_VERSION_RE, ver):
sys.stderr.write('version: release version (%s) is invalid, '
'will use it anyway\n' % ver)
return ver
except:
return None
def writeReleaseVersion(version):
fd = open(RELEASE_VERSION_FILE, 'w')
fd.write('%s\n' % version)
fd.close()
def getVersion():
release_version = readReleaseVersion()
version = readGitVersion() or release_version
if not version:
raise ValueError('Cannot find the version number')
if version != release_version:
writeReleaseVersion(version)
return version
if __name__ == '__main__':
print getVersion()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2014 Michael D. M. Dryden -
# Wheeler Microfluidics Laboratory <http://microfluidics.utoronto.ca>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" GUI Interface for Wheeler Lab DStat """
from __future__ import division, absolute_import, print_function, unicode_literals
import sys
import os
import platform
import multiprocessing
import uuid
from collections import OrderedDict
from datetime import datetime
import logging
from pkg_resources import parse_version
from serial import SerialException
import zmq
from dstat_interface.core.utils.version import getVersion
from dstat_interface.core.experiments import idle, pot
from dstat_interface.core import params, analysis, dstat
from dstat_interface.core.dstat import boards
from dstat_interface.core.interface import (exp_window, adc_pot, plot_ui, data_view,
save, hw_info)
from dstat_interface.core.errors import InputError
from dstat_interface.core.plugin import DstatPlugin, get_hub_uri
try:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
except ImportError:
print("ERR: GTK not available")
sys.exit(1)
mod_dir = os.path.dirname(os.path.abspath(__file__))
conf_path = os.path.join(os.path.expanduser("~"), '.dstat-interface')
# if __name__ == "__parents_main__": # Only runs for forking emulation on win
# sys.path.append(mod_dir)
# Setup Logging
logger = logging.getLogger(__name__)
core_logger = logging.getLogger("dstat_interface.core")
loggers = [logger, core_logger]
log_handler = logging.StreamHandler()
log_formatter = logging.Formatter(
fmt='%(asctime)s %(levelname)s: [%(name)s] %(message)s',
datefmt='%H:%M:%S'
)
log_handler.setFormatter(log_formatter)
for log in loggers:
log.setLevel(level=logging.INFO)
log.addHandler(log_handler)
class Main(GObject.Object):
"""Main program """
__gsignals__ = {
b'exp_start': (GObject.SIGNAL_RUN_FIRST, None, ()),
b'exp_stop': (GObject.SIGNAL_RUN_FIRST, None, ())
}
def __init__(self):
super(Main, self).__init__()
self.builder = Gtk.Builder()
self.builder.add_from_file(
os.path.join(mod_dir, 'core/interface/dstatinterface.glade'))
self.builder.connect_signals(self)
self.cell = Gtk.CellRendererText()
# Create instance of interface components
self.statusbar = self.builder.get_object('statusbar')
self.ocp_disp = self.builder.get_object('ocp_disp')
self.window = self.builder.get_object('window1')
self.aboutdialog = self.builder.get_object('aboutdialog1')
self.stopbutton = self.builder.get_object('pot_stop')
self.startbutton = self.builder.get_object('pot_start')
self.startbutton.set_sensitive(False)
self.exp_progressbar = self.builder.get_object('exp_progressbar')
self.adc_pot = adc_pot.adc_pot()
self.error_context_id = self.statusbar.get_context_id("error")
self.message_context_id = self.statusbar.get_context_id("message")
self.exp_window = exp_window.Experiments(self.builder)
self.exp_window.connect('run_utility', self.stop_ocp)
self.exp_window.connect('done_utility', self.start_ocp)
self.analysis_opt_window = analysis.AnalysisOptions(self.builder)
# Setup Autosave
self.autosave_checkbox = self.builder.get_object('autosave_checkbutton')
self.autosavedir_button = self.builder.get_object('autosavedir_button')
self.autosavename = self.builder.get_object('autosavename')
# Setup Plots
self.plot_notebook = self.builder.get_object('plot_notebook')
self.main_notebook = self.builder.get_object('main_notebook')
self.data_view = data_view.DataPage(self.main_notebook)
self.info_page = data_view.InfoPage(self.main_notebook)
# fill adc_pot_box
self.adc_pot_box = self.builder.get_object('gain_adc_box')
self.adc_pot_container = self.adc_pot.builder.get_object('vbox1')
self.adc_pot_container.reparent(self.adc_pot_box)
# fill serial
self.serial_connect = self.builder.get_object('serial_connect')
self.serial_pmt_connect = self.builder.get_object('pmt_mode')
self.serial_disconnect = self.builder.get_object('serial_disconnect')
self.serial_disconnect.set_sensitive(False)
self.serial_combobox = self.builder.get_object('serial_combobox')
self.serial_combobox.pack_start(self.cell, True)
self.serial_combobox.add_attribute(self.cell, 'text', 0)
self.serial_liststore = self.builder.get_object('serial_liststore')
self.serial_devices = dstat.comm.SerialDevices()
for i in self.serial_devices.ports:
self.serial_liststore.append([i])
self.serial_combobox.set_active(0)
self.spinner = self.builder.get_object('spinner')
self.mainwindow = self.builder.get_object('window1')
self.menu_dstat_info = self.builder.get_object('menu_dstat_info')
self.menu_dstat_info.set_sensitive(False)
self.dstat_info_window = hw_info.InfoDialog(
parent=self.mainwindow,
connect=self.menu_dstat_info
)
self.menu_dstat_fw = self.builder.get_object('menu_dstat_fw')
self.menu_dstat_fw.set_sensitive(False)
self.dstat_fw_window = dstat.dfu.FWDialog(
parent=self.mainwindow,
connect=self.menu_dstat_fw,
stop_callback=self.stop_ocp,
disconnect_callback=self.on_serial_disconnect_clicked
)
self.menu_dstat_reset = self.builder.get_object('menu_dstat_reset')
self.menu_dstat_reset.set_sensitive(False)
self.menu_dstat_reset_window = hw_info.ResetDialog(
parent=self.mainwindow,
connect=self.menu_dstat_reset,
stop_callback=self.stop_ocp,
disconnect_callback=self.on_serial_disconnect_clicked
)
# Set Version Strings
try:
ver = getVersion()
except ValueError:
ver = "1.x"
logger.warning("Could not fetch version number")
self.mainwindow.set_title(" ".join(("DStat Interface", ver)))
self.aboutdialog.set_version(ver)
self.mainwindow.show_all()
self.exp_window.hide_exps()
self.expnumber = 0
self.connected = False
self.pmt_mode = False
self.menu_dropbot_connect = self.builder.get_object(
'menu_dropbot_connect')
self.menu_dropbot_disconnect = self.builder.get_object(
'menu_dropbot_disconnect')
self.dropbot_enabled = False
self.dropbot_triggered = False
self.metadata = None # Should only be added to by plugin interface
self.params_loaded = False
# Disable 0MQ plugin API by default.
self.plugin = None
self.plugin_timeout_id = None
# UUID for active experiment.
self.active_experiment_id = None
# UUIDs for completed experiments.
self.completed_experiment_ids = OrderedDict()
def on_window1_destroy(self, object, data=None):
""" Quit when main window closed."""
self.quit()
def on_gtk_quit_activate(self, menuitem, data=None):
"""Quit when Quit selected from menu."""
self.quit()
def quit(self):
"""Disconnect and save parameters on quit."""
try:
params.save_params(self, os.path.join(conf_path, 'last_params.yml'))
self.on_serial_disconnect_clicked()
except KeyError:
pass
mainloop.quit()
def on_gtk_about_activate(self, menuitem, data=None):
"""Display the about window."""
self.aboutdialog.run() # waits for user to click close
self.aboutdialog.hide()
def on_menu_analysis_options_activate(self, menuitem, data=None):
self.analysis_opt_window.show()
def on_serial_refresh_clicked(self, data=None):
"""Refresh list of serial devices."""
try:
self.serial_devices.refresh()
self.serial_liststore.clear()
except ValueError:
logger.warning("No DStats found")
for i in self.serial_devices.ports:
self.serial_liststore.append([i])
def on_serial_connect_clicked(self, widget):
"""Connect and retrieve DStat version."""
selection = self.serial_combobox.get_active_iter()
if selection is None:
return
if widget is self.serial_pmt_connect:
self.pmt_mode = True
self.adc_pot.ui['short_true'].set_active(True)
self.adc_pot.ui['short_true'].set_sensitive(False)
try:
self.serial_connect.set_sensitive(False)
self.serial_pmt_connect.set_sensitive(False)
dstat.comm.version_check(
self.serial_liststore.get_value(selection, 0)
)
dstat.state.board_instance = boards.find_board(
dstat.state.dstat_version)()
self.statusbar.remove_all(self.error_context_id)
self.adc_pot.set_version()
self.statusbar.push(
self.message_context_id,
"DStat version: {}".format(
dstat.state.dstat_version.base_version)
)
dstat.comm.read_settings()
try:
if dstat.state.settings['dac_units_true'][1] != b'1':
dstat.state.settings['dac_units_true'][1] = b'1'
dstat.comm.write_settings()
except KeyError:
logger.warning("Connected DStat does not support sending DAC units.")
dialog = Gtk.MessageDialog(
self.window, 0, Gtk.MessageType.WARNING,
Gtk.ButtonsType.OK, "Connected DStat does not support sending DAC units." +
"Update firmware or set potentials will be incorrect!")
dialog.run()
dialog.destroy()
self.start_ocp()
self.connected = True
self.serial_disconnect.set_sensitive(True)
except:
self.serial_connect.set_sensitive(True)
self.serial_pmt_connect.set_sensitive(True)
self.adc_pot.ui['short_true'].set_sensitive(True)
raise
if self.params_loaded == False:
try:
try:
os.makedirs(conf_path)
except OSError:
if not os.path.isdir(conf_path):
raise
params.load_params(self,
os.path.join(conf_path, 'last_params.yml')
)
except IOError:
logger.info("No previous parameters found.")
def on_serial_disconnect_clicked(self, data=None):
"""Disconnect from DStat."""
if self.connected == False:
return
try:
if self.ocp_is_running:
self.stop_ocp()
else:
self.on_pot_stop_clicked()
dstat.state.ser.disconnect()
except AttributeError as err:
logger.warning("AttributeError: %s", err)
pass
if self.pmt_mode is True:
self.adc_pot.ui['short_true'].set_sensitive(True)
self.pmt_mode = False
self.connected = False
self.serial_connect.set_sensitive(True)
self.serial_pmt_connect.set_sensitive(True)
self.serial_disconnect.set_sensitive(False)
self.startbutton.set_sensitive(False)
self.stopbutton.set_sensitive(False)
self.menu_dstat_info.set_sensitive(False)
self.menu_dstat_fw.set_sensitive(False)
self.menu_dstat_reset.set_sensitive(False)
self.adc_pot.ui['short_true'].set_sensitive(True)
dstat.state.reset()
def start_ocp(self, data=None):
"""Start OCP measurements."""
if dstat.state.dstat_version >= parse_version('1.2'):
# Flush data pipe
dstat.state.ser.flush_data()
if self.pmt_mode is True:
logger.info("Start PMT idle mode")
dstat.state.ser.start_exp(idle.PMTIdle())
self.ocp_is_running = True
self.ocp_proc = (GObject.timeout_add(250, self.ocp_running_proc)
,
)
else:
logger.info("Start OCP")
dstat.state.ser.start_exp(idle.OCPExp())
self.ocp_proc = (GObject.timeout_add(300, self.ocp_running_data),
GObject.timeout_add(250, self.ocp_running_proc)
)
self.ocp_is_running = False
GObject.timeout_add(100, self.ocp_assert) # Check if getting data
else:
logger.info("OCP measurements not supported on v1.1 boards.")
return
def stop_ocp(self, data=None):
"""Stop OCP measurements."""
if dstat.state.dstat_version >= parse_version('1.2'):
if self.pmt_mode == True:
logger.info("Stop PMT idle mode")
else:
logger.info("Stop OCP")
dstat.state.ser.send_ctrl('a')
for i in self.ocp_proc:
GObject.source_remove(i)
while self.ocp_running_proc():
pass
self.ocp_disp.set_text("")
self.ocp_is_running = False
self.startbutton.set_sensitive(True)
self.menu_dstat_info.set_sensitive(True)
self.menu_dstat_fw.set_sensitive(True)
self.menu_dstat_reset.set_sensitive(True)
else:
logger.error("OCP measurements not supported on v1.1 boards.")
return
def ocp_assert(self):
if self.ocp_is_running:
self.startbutton.set_sensitive(True)
self.menu_dstat_info.set_sensitive(True)
self.menu_dstat_fw.set_sensitive(True)
self.menu_dstat_reset.set_sensitive(True)
return False
else:
return True
def ocp_running_data(self):
"""Receive OCP value from experiment process and update ocp_disp field
Returns:
True -- when experiment is continuing to keep function in GTK's queue.
False -- when experiment process signals EOFError or IOError to remove
function from GTK's queue.
"""
try:
incoming = dstat.state.ser.get_data()
while incoming is not None:
if isinstance(incoming, basestring): # test if incoming is str
self.on_serial_disconnect_clicked()
return False
try:
data = "".join(["OCP: ",
"{0:.3f}".format(incoming),
" V"])
self.ocp_disp.set_text(data)
self.ocp_is_running = True
except ValueError:
pass
incoming = dstat.state.ser.get_data()
return True
except EOFError:
return False
except IOError:
return False
def ocp_running_proc(self):
"""Handles signals on proc_pipe_p for OCP.
Returns:
True -- when experiment is continuing to keep function in GTK's queue.
False -- when experiment process signals EOFError or IOError to remove
function from GTK's queue.
"""
try:
proc_buffer = dstat.state.ser.get_proc()
while proc_buffer is not None:
logger.debug("ocp_running_proc: %s", proc_buffer)
if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]:
if proc_buffer == "SERIAL_ERROR":
self.on_serial_disconnect_clicked()
dstat.state.ser.flush_data()
return False
proc_buffer = dstat.state.ser.get_proc()
return True
except EOFError:
return False
except IOError:
return False
def on_pot_start_clicked(self, data=None):
try:
self.run_active_experiment()
except (ValueError, KeyError, InputError, SerialException,
AssertionError):
# Ignore expected exceptions when triggering experiment from UI.
pass
def run_active_experiment(self, param_override=None, metadata=None):
"""Run currently visible experiment."""
# Assign current experiment a unique identifier.
experiment_id = uuid.uuid4()
self.active_experiment_id = experiment_id
logger.info("Current measurement id: %s", experiment_id.hex)
self.metadata = metadata
if self.metadata is not None:
logger.info("Loading external metadata")
def exceptions():
""" Cleans up after errors """
self.spinner.stop()
self.startbutton.set_sensitive(True)
self.stopbutton.set_sensitive(False)
self.start_ocp()
self.stop_ocp()
self.statusbar.remove_all(self.error_context_id)
dstat.state.ser.flush_data()
parameters = {}
parameters['metadata'] = self.metadata
# Make sure these are defined
parameters['sync_true'] = False
parameters['shutter_true'] = False
try:
if param_override is not None:
params.set_params(self, param_override)
parameters.update(params.get_params(self))
self.line = 0
self.lastline = 0
self.lastdataline = 0
self.spinner.start()
self.startbutton.set_sensitive(False)
self.stopbutton.set_sensitive(True)
self.statusbar.remove_all(self.error_context_id)
try:
del self.current_exp
except AttributeError:
pass
callbacks = {'experiment_done' : self.experiment_done,
'progress_update' : self.progress_update}
self.current_exp = self.exp_window.setup_exp(parameters)
plot_ui.replace_notebook_exp(
self.plot_notebook, self.current_exp, self.window
)
self.data_view.clear_exps()
self.info_page.clear()
dstat.state.ser.start_exp(self.current_exp)
# Flush data pipe
dstat.state.ser.flush_data()
self.current_exp.setup_loops(callbacks)
return experiment_id
except ValueError as i:
logger.info("ValueError: %s",i)
self.statusbar.push(self.error_context_id,
"Experiment parameters must be integers.")
exceptions()
raise
except KeyError as i:
import traceback
traceback.print_exc()
logger.info("KeyError: %s", i)
self.statusbar.push(self.error_context_id,
"Experiment parameters must be integers.")
exceptions()
raise
except InputError as err:
logger.info("InputError: %s", err)
self.statusbar.push(self.error_context_id, err.msg)
exceptions()
raise
except SerialException as err:
logger.info("SerialException: %s", err)
self.statusbar.push(self.error_context_id,
"Could not establish serial connection.")
exceptions()
raise
except AssertionError as err:
logger.info("AssertionError: %s", err)
self.statusbar.push(self.error_context_id, str(err))
exceptions()
raise
def progress_update(self, widget, progress):
if 0 <= progress <= 1:
self.exp_progressbar.set_fraction(progress)
else:
self.exp_progressbar.pulse()
def experiment_running_data(self):
"""Receive data from experiment process and add to
current_exp.data['data].
Run in GTK main loop.
Returns:
True -- when experiment is continuing to keep function in GTK's queue.
False -- when experiment process signals EOFError or IOError to remove
function from GTK's queue.
"""
try:
incoming = dstat.state.ser.get_data()
while incoming is not None:
try:
self.line, data = incoming
if self.line > self.lastdataline:
newline = True
try:
logger.info("running scan_process()")
self.current_exp.scan_process(self.lastdataline)
except AttributeError:
pass
self.lastdataline = self.line
else:
newline = False
self.current_exp.store_data(incoming, newline)
if newline:
self.experiment_running_plot()
except TypeError:
pass
incoming = dstat.state.ser.get_data()
return True
except EOFError as err:
logger.error(err)
self.experiment_done()
return False
except IOError as err:
logger.error(err)
self.experiment_done()
return False
def experiment_running_proc(self):
"""Receive proc signals from experiment process.
Run in GTK main loop.
Returns:
True -- when experiment is continuing to keep function in GTK's queue.
False -- when experiment process signals EOFError or IOError to remove
function from GTK's queue.
"""
try:
ctrl_buffer = dstat.state.ser.get_ctrl()
try:
if ctrl_buffer is not None:
self.current_exp.ctrl_loop(ctrl_buffer)
except AttributeError:
pass
proc_buffer = dstat.state.ser.get_proc()
if proc_buffer is not None:
if proc_buffer in ["DONE", "SERIAL_ERROR", "ABORT"]:
self.experiment_done()
if proc_buffer == "SERIAL_ERROR":
self.on_serial_disconnect_clicked()
else:
logger.warning("Unrecognized experiment return code: %s",
proc_buffer)
return False
return True
except EOFError as err:
logger.warning("EOFError: %s", err)
self.experiment_done()
return False
except IOError as err:
logger.warning("IOError: %s", err)
self.experiment_done()
return False
def experiment_running_plot(self, force_refresh=False):
"""Plot all data in current_exp.data.
Run in GTK main loop. Always returns True so must be manually
removed from GTK's queue.
"""
for plot in self.current_exp.plots:
if (plot.scan_refresh and self.line > self.lastline):
while self.line > self.lastline:
# make sure all of last line is added
plot.updateline(self.current_exp, self.lastline)
self.lastline += 1
plot.updateline(self.current_exp, self.line)
plot.redraw()
else:
while self.line > self.lastline:
# make sure all of last line is added
plot.updateline(self.current_exp, self.lastline)
self.lastline += 1
plot.updateline(self.current_exp, self.line)
if plot.continuous_refresh is True or force_refresh is True:
plot.redraw()
return True
def experiment_done(self, widget=None):
"""Clean up after data acquisition is complete. Update plot and
copy data to raw data tab. Saves data if autosave enabled.
"""
try:
# Run Analysis
analysis.do_analysis(self.current_exp)
# Write DStat commands
self.info_page.set_text(self.current_exp.get_info_text())
try:
self.statusbar.push(
self.message_context_id,
"Integral: %s A" % self.current_exp.analysis['FT Integral'][0][1]
)
except KeyError:
pass
# Data Output
analysis_buffer = []
if self.current_exp.analysis != {}:
analysis_buffer.append("# ANALYSIS")
for key, value in self.current_exp.analysis.iteritems():
analysis_buffer.append("# %s:" % key)
for scan in value:
number, result = scan
analysis_buffer.append(
"# Scan %s -- %s" % (number, result)
)
for i in analysis_buffer:
self.info_page.add_line(i)
self.data_view.add_exp(self.current_exp)
# Autosaving
if self.autosave_checkbox.get_active():
save.autoSave(self.current_exp,
self.autosavedir_button.get_filename().decode('utf-8'),
self.autosavename.get_text()
)
save.autoPlot(self.current_exp,
self.autosavedir_button.get_filename().decode('utf-8'),
self.autosavename.get_text()
)
# uDrop
# UI stuff
finally:
self.metadata = None # Reset metadata
self.spinner.stop()
self.exp_progressbar.set_fraction(0)
self.stopbutton.set_sensitive(False)
self.start_ocp()
self.completed_experiment_ids[self.active_experiment_id] =\
datetime.utcnow()
# Temporary fix for weird drawing bug on windows Gtk+3
if platform.system() == 'Windows':
position = self.window.get_position()
self.window.hide()
self.window.show()
self.window.move(*position)
def on_pot_stop_clicked(self, data=None):
"""Stop current experiment. Signals experiment process to stop."""
try:
dstat.state.ser.stop_exp()
except AttributeError:
pass
except:
logger.warning(sys.exc_info())
def on_file_save_exp_activate(self, menuitem, data=None):
"""Activate dialogue to save current experiment data. """
try:
save.manSave(self.current_exp)
except AttributeError:
logger.warning("Tried to save with no experiment run")
def on_file_save_plot_activate(self, menuitem, data=None):
"""Activate dialogue to save current plot."""
try:
save.plot_save_dialog(self.current_exp)
except AttributeError:
logger.warning("Tried to save with no experiment run")
def on_file_save_params_activate(self, menuitem, data=None):
"""Activate dialogue to save current experiment parameters. """
save.man_param_save(self)
def on_file_load_params_activate(self, menuitem, data=None):
"""Activate dialogue to load experiment parameters from file. """
save.man_param_load(self)
def on_menu_dropbot_connect_activate(self, menuitem, data=None):
"""Listen for remote control connection from µDrop."""
# Prompt user for 0MQ plugin hub URI.
zmq_plugin_hub_uri = get_hub_uri(parent=self.window)
self.dropbot_enabled = True
self.menu_dropbot_connect.set_sensitive(False)
self.menu_dropbot_disconnect.set_sensitive(True)
self.statusbar.push(self.message_context_id,
"Waiting for µDrop to connect…")
self.enable_plugin(zmq_plugin_hub_uri)
def on_menu_dropbot_disconnect_activate(self, menuitem=None, data=None):
"""Disconnect µDrop connection and stop listening."""
self.cleanup_plugin()
self.dropbot_enabled = False
self.menu_dropbot_connect.set_sensitive(True)
self.menu_dropbot_disconnect.set_sensitive(False)
self.statusbar.push(self.message_context_id, "µDrop disconnected.")
def enable_plugin(self, hub_uri):
'''
Connect to 0MQ plugin hub to expose public D-Stat API.
Args
----
hub_uri (str) : URI for 0MQ plugin hub.
'''
self.cleanup_plugin()
# Initialize 0MQ hub plugin and subscribe to hub messages.
self.plugin = DstatPlugin(self, 'dstat-interface', hub_uri,
subscribe_options={zmq.SUBSCRIBE: ''})
# Initialize sockets.
self.plugin.reset()
# Periodically process outstanding message received on plugin sockets.
self.plugin_timeout_id = Gtk.timeout_add(500,
self.plugin.check_sockets)
def cleanup_plugin(self):
if self.plugin_timeout_id is not None:
GObject.source_remove(self.plugin_timeout_id)
if self.plugin is not None:
self.plugin = None
if __name__ == "__main__":
multiprocessing.freeze_support()
GObject.threads_init()
MAIN = Main()
mainloop = GObject.MainLoop()
try:
mainloop.run()
except KeyboardInterrupt:
logger.info('Ctrl+C hit, quitting')
MAIN.quit()
import sys
from paver.easy import task, needs, path, sh, cmdopts, options
from paver.setuputils import setup, install_distutils_tasks, find_packages
from distutils.extension import Extension
from distutils.dep_util import newer
sys.path.insert(0, path('.').abspath())
import version
setup(name='dstat_interface',
version=version.getVersion(),
description='Interface software for DStat potentiostat.',
keywords='',
author='Michael D. M Dryden',
author_email='mdryden@chem.utoronto.ca',
url='http://microfluidics.utoronto.ca/dstat',
license='GPLv3',
packages=find_packages(),
install_requires=['matplotlib', 'numpy', 'pyserial', 'pyzmq',
'pyyaml','seaborn', 'setuptools',
'zmq-plugin>=0.2.post2'],
# Install data listed in `MANIFEST.in`
include_package_data=True)
@task
@needs('generate_setup', 'minilib', 'setuptools.command.sdist')
def sdist():
"""Overrides sdist to make sure that our setup.py is generated."""
pass
# -*- coding: utf-8 -*-
"""Calculates the current version number.
If possible, uses output of “git describe” modified to conform to the
visioning scheme that setuptools uses (see PEP 386). Releases must be
labelled with annotated tags (signed tags are annotated) of the following
format:
v<num>(.<num>)+ [ {a|b|c|rc} <num> (.<num>)* ]
If “git describe” returns an error (likely because we're in an unpacked copy
of a release tarball, rather than a git working copy), or returns a tag that
does not match the above format, version is read from RELEASE-VERSION file.
To use this script, simply import it your setup.py file, and use the results
of getVersion() as your package version:
import version
setup(
version=version.getVersion(),
.
.
.
)
This will automatically update the RELEASE-VERSION file. The RELEASE-VERSION
file should *not* be checked into git but it *should* be included in sdist
tarballs (as should version.py file). To do this, run:
echo include RELEASE-VERSION version.py >>MANIFEST.in
echo RELEASE-VERSION >>.gitignore
With that setup, a new release can be labelled by simply invoking:
git tag -s v1.0
"""
__author__ = ('Douglas Creager <dcreager@dcreager.net>',
'Michal Nazarewicz <mina86@mina86.com>')
__license__ = 'This file is placed into the public domain.'
__maintainer__ = 'Michal Nazarewicz'
__email__ = 'mina86@mina86.com'
__all__ = ('getVersion')
import re
import subprocess
import sys
import os.path
import inspect
RELEASE_VERSION_FILE = '{}/RELEASE-VERSION'.format(
os.path.dirname(os.path.abspath(inspect.stack()[0][1])))
# http://www.python.org/dev/peps/pep-0386/
_PEP386_SHORT_VERSION_RE = r'\d+(?:\.\d+)+(?:(?:[abc]|rc)\d+(?:\.\d+)*)?'
_PEP386_VERSION_RE = r'^%s(?:\.post\d+)?(?:\.dev\d+)?$' % (
_PEP386_SHORT_VERSION_RE)
_GIT_DESCRIPTION_RE = r'^v(?P<ver>%s)-(?P<commits>\d+)-g(?P<sha>[\da-f]+)$' % (
_PEP386_SHORT_VERSION_RE)
def readGitVersion():
try:
proc = subprocess.Popen(('git', 'describe', '--long',
'--match', 'v[0-9]*.*'),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
data, _ = proc.communicate()
if proc.returncode:
return None
ver = data.splitlines()[0].strip()
proc = subprocess.Popen(('git', 'rev-parse', '--abbrev-ref', 'HEAD'),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
branch, _ = proc.communicate()
if proc.returncode:
return None
except:
return None
if not ver:
return None
m = re.search(_GIT_DESCRIPTION_RE, ver)
if not m:
sys.stderr.write('version: git description (%s) is invalid, '
'ignoring\n' % ver)
return None
commits = int(m.group('commits'))
if not commits:
version = m.group('ver')
else:
version = '%s.post%d' % (
m.group('ver'), commits)
if branch.strip() != 'master':
version += '.dev%d' % int(m.group('sha'), 16)
return version
def readReleaseVersion():
try:
fd = open(RELEASE_VERSION_FILE)
try:
ver = fd.readline().strip()
finally:
fd.close()
if not re.search(_PEP386_VERSION_RE, ver):
sys.stderr.write('version: release version (%s) is invalid, '
'will use it anyway\n' % ver)
return ver
except:
return None
def writeReleaseVersion(version):
fd = open(RELEASE_VERSION_FILE, 'w')
fd.write('%s\n' % version)
fd.close()
def getVersion():
release_version = readReleaseVersion()
version = readGitVersion() or release_version
if not version:
raise ValueError('Cannot find the version number')
if version != release_version:
writeReleaseVersion(version)
return version
if __name__ == '__main__':
print getVersion()