Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 2867 additions and 713 deletions
import time
import struct
from .experiment_template import PlotBox, Experiment
class CVExp(Experiment):
id = 'cve'
"""Cyclic Voltammetry experiment"""
def setup(self):
self.plotlims['current_voltage']['xlims'] = tuple(
sorted((int(self.parameters['v1']), int(self.parameters['v2'])))
)
super(CVExp, self).setup()
self.datatype = "CVData"
self.xlabel = "Voltage (mV)"
self.ylabel = "Current (A)"
self.datalength = 2 * self.parameters['scans'] # x and y for each scan
self.databytes = 6 # uint16 + int32
self.commands += "E"
self.commands[2] += "C"
self.commands[2] += str(self.parameters['clean_s'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['dep_s'])
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['clean_mV'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['dep_mV'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['v1'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['v2'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['start'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(self.parameters['scans'])
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['slope'])/
self.re_voltage_scale*
(65536./3000)
))
self.commands[2] += " "
def get_progress(self):
return (len(self.data['current_voltage'])-1) / float(self.parameters['scans'])
#!/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
from ..dstat import state
import logging
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)
logger = logging.getLogger(__name__)
class BaseLoop(GObject.GObject):
__gsignals__ = {
b'experiment_done': (GObject.SIGNAL_RUN_FIRST, None, tuple()),
b'progress_update': (GObject.SIGNAL_RUN_FIRST, None, (float,))
}
def __init__(self, experiment, callbacks=None):
GObject.GObject.__init__(self)
self.line = None
self.lastdataline = 0
self.current_exp = experiment
self.experiment_proc = None
for signal, cb in callbacks.items():
try:
self.connect(signal, cb)
except TypeError:
logger.warning("Invalid signal %s", signal)
def run(self):
self.experiment_proc = [
GObject.idle_add(self.experiment_running_data),
GObject.idle_add(self.experiment_running_proc),
GObject.timeout_add(100, self.update_progress)
]
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 = state.ser.get_data()
while incoming is not None:
try:
self.line = incoming[0]
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)
except TypeError:
pass
incoming = 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 = state.ser.get_ctrl()
try:
if ctrl_buffer is not None:
self.current_exp.ctrl_loop(ctrl_buffer)
except AttributeError:
pass
proc_buffer = 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_done(self):
logger.info("Experiment done")
for proc in self.experiment_proc:
GObject.source_remove(proc)
self.current_exp.scan_process(self.lastdataline)
self.current_exp.experiment_done()
self.emit("experiment_done")
def update_progress(self):
try:
progress = self.current_exp.get_progress()
except AttributeError:
progress = -1
self.emit("progress_update", progress)
return True
class PlotLoop(BaseLoop):
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.
"""
if self.line is None:
return True
for plot in self.current_exp.plots:
if (plot.scan_refresh and self.line > self.lastdataline):
while self.line > self.lastline:
# make sure all of last line is added
plot.updateline(self.current_exp, self.lastdataline)
self.lastdataline += 1
plot.updateline(self.current_exp, self.line)
plot.redraw()
else:
while self.line > self.lastdataline:
# make sure all of last line is added
plot.updateline(self.current_exp, self.lastdataline)
self.lastdataline += 1
plot.updateline(self.current_exp, self.line)
if plot.continuous_refresh is True or force_refresh is True:
plot.redraw()
return True
def run(self):
super(PlotLoop, self).run()
self.experiment_proc.append(
GObject.timeout_add(200, self.experiment_running_plot)
)
def experiment_done(self):
logger.info("Experiment done")
for proc in self.experiment_proc:
GObject.source_remove(proc)
self.current_exp.scan_process(self.lastdataline)
self.current_exp.experiment_done()
self.experiment_running_plot(force_refresh=True)
self.emit("experiment_done")
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# DStat Interface - An interface for the open hardware DStat potentiostat
# Copyright (C) 2017 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/>.
import logging
import struct
import time
from collections import OrderedDict
from copy import deepcopy
from datetime import datetime
from math import ceil
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)
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
from matplotlib.backends.backend_gtk3agg \
import FigureCanvasGTK3Agg as FigureCanvas
from pandas import DataFrame
import seaborn as sns
sns.set(context='paper', style='darkgrid')
import serial
from ..dstat import state, comm
from ..dstat.comm import TransmitError
from . import experiment_loops
logger = logging.getLogger(__name__)
dstat_logger = logging.getLogger("{}.DSTAT".format(comm.__name__))
exp_logger = logging.getLogger("{}.Experiment".format(__name__))
class Experiment(GObject.Object):
"""Store and acquire a potentiostat experiment. Meant to be subclassed
to by different experiment types and not used instanced directly. Subclass
must instantiate self.plotbox as the PlotBox class to use and define id as
a class attribute.
"""
id = None
Loops = experiment_loops.PlotLoop
__gsignals__ = {
b'exp_ready': (GObject.SIGNAL_RUN_FIRST, None, ()),
b'exp_done': (GObject.SIGNAL_RUN_FIRST, None, ()),
}
def __init__(self, parameters):
"""Adds commands for gain and ADC."""
super(Experiment, self).__init__()
self.current_command = None
self.parameters = parameters
self.databytes = 8
self.datapoint = 0
self.scan = 0
self.time = 0
self.plots = []
self.re_voltage_scale = state.board_instance.re_voltage_scale
self.gain = state.board_instance.gain[int(self.parameters['gain'])]
try:
self.gain_trim = int(
state.settings[
state.board_instance.gain_trim[int(self.parameters['gain'])]
][1]
)
except AttributeError:
logger.debug("No gain trim table.")
self.commands = ["EA", "EG"]
if self.parameters['buffer_true']:
self.commands[0] += "2"
else:
self.commands[0] += "0"
self.commands[0] += " {p[adc_rate]} {p[adc_pga]} ".format(
p=self.parameters)
self.commands[1] += "{p[gain]} {p[short_true]:d} ".format(
p=self.parameters)
self.plotlims = {'current_voltage' : {'xlims' : (0, 1)}
}
self.setup()
self.time = [datetime.utcnow()]
def setup_loops(self, callbacks):
self.loops = self.__class__.Loops(self, callbacks)
self.loops.run()
def setup(self):
self.data = OrderedDict(current_voltage=[([], [])])
self.columns = ['Voltage (mV)', 'Current (A)']
# list of scans, tuple of dimensions, list of data
self.line_data = ([], [])
plot = PlotBox(['current_voltage'])
plot.setlims('current_voltage', **self.plotlims['current_voltage'])
self.plots.append(plot)
def write_command(self, cmd, params=None, retry=5):
"""Write command to serial with optional number of retries."""
def get_reply(retries=3):
while True:
reply = self.serial.readline().rstrip()
if reply.startswith('#'):
dstat_logger.info(reply)
elif reply == "":
retries -= 1
if retries <= 0:
raise TransmitError
else:
return reply
n = len(cmd)
if params is not None:
n_params = len(params)
for _ in range(retry):
tries = 5
while True:
time.sleep(0.2)
self.serial.reset_input_buffer()
self.serial.write('!{}\n'.format(n))
time.sleep(.1)
try:
reply = get_reply()
except TransmitError:
if tries <= 0:
continue
tries -= 1
pass
else:
break
if reply != "@ACK {}".format(n):
logger.warning("Expected ACK got: {}".format(reply))
continue
tries = 5
while True:
self.serial.write('{}\n'.format(cmd))
try:
reply = get_reply()
except TransmitError:
if tries <= 0:
continue
tries -= 1
pass
else:
break
if reply != "@RCV {}".format(n):
logger.warning("Expected RCV got: {}".format(reply))
continue
if params is None:
return True
tries = 5
while True:
try:
reply = get_reply()
except TransmitError:
if tries <= 0:
break
tries -= 1
pass
else:
break
if reply != "@RQP {}".format(n_params):
logger.warning("Expected RQP got: {}".format(reply))
continue
tries = 5
for i in params:
while True:
self.serial.write(i + " ")
try:
reply = get_reply()
if reply == "@RCVC {}".format(i):
break
except TransmitError:
if tries <= 0:
continue
tries -= 1
pass
else:
break
return True
return False
def run(self, ser, ctrl_pipe, data_pipe):
"""Execute experiment. Connects and sends handshake signal to DStat
then sends self.commands.
"""
self.serial = ser
self.ctrl_pipe = ctrl_pipe
self.data_pipe = data_pipe
exp_logger.info("Experiment running")
try:
for i in self.commands:
self.current_command = i
status = "DONE"
if isinstance(i, (str, unicode)):
logger.info("Command: %s", i)
if not self.write_command(i):
status = "ABORT"
break
else:
cmd = i[0]
data = i[1]
logger.info("Command: {}".format(cmd))
if not self.write_command(cmd, params=data):
status = "ABORT"
break
if not self.serial_handler():
status = "ABORT"
break
time.sleep(0.5)
except serial.SerialException:
status = "SERIAL_ERROR"
finally:
while self.ctrl_pipe.poll():
self.ctrl_pipe.recv()
return status
def serial_handler(self):
"""Handles incoming serial transmissions from DStat. Returns False
if stop button pressed and sends abort signal to instrument. Sends
data to self.data_pipe as result of self.data_handler).
"""
scan = 0
def check_ctrl():
if self.ctrl_pipe.poll():
input = self.ctrl_pipe.recv()
logger.info("serial_handler: %s", input)
if input == "DISCONNECT":
self.serial.write('a')
self.serial.reset_input_buffer()
logger.info("serial_handler: ABORT pressed!")
time.sleep(.3)
return False
elif input == 'a':
self.serial.write('a')
else:
self.serial.write(input)
try:
while True:
check_ctrl()
for line in self.serial:
check_ctrl()
if line.startswith('B'):
data = self.data_handler(
(scan, self.serial.read(size=self.databytes)))
data = self.data_postprocessing(data)
if data is not None:
self.data_pipe.send(data)
try:
self.datapoint += 1
except AttributeError: #Datapoint counting is optional
pass
elif line.lstrip().startswith('S'):
scan += 1
elif line.lstrip().startswith("#"):
dstat_logger.info(line.lstrip().rstrip())
elif line.lstrip().startswith("@DONE"):
dstat_logger.debug(line.lstrip().rstrip())
time.sleep(.3)
return True
except serial.SerialException:
return False
def data_handler(self, data_input):
"""Takes data_input as tuple -- (scan, data).
Returns:
(scan number, (voltage, current)) -- voltage in mV, current in A
"""
scan, data = data_input
voltage, current = struct.unpack('<Hl', data) #uint16 + int32
return (scan, (
(voltage-32768)*(3000./65536)*self.re_voltage_scale,
(current+self.gain_trim)*(1.5/8388607)/self.gain
)
)
def store_data(self, incoming, newline):
"""Stores data in data attribute. Should not be called from subprocess.
Can be overriden for custom experiments."""
line, data = incoming
if newline is True:
self.data['current_voltage'].append(deepcopy(self.line_data))
for i, item in enumerate(self.data['current_voltage'][line]):
item.append(data[i])
def data_postprocessing(self, data):
"""Discards first data point (usually gitched) by default, can be overridden
in subclass.
"""
try:
if self.datapoint <= 1:
return None
except AttributeError: # Datapoint counting is optional
pass
return data
def scan_process(self, line):
pass
def experiment_done(self):
"""Runs when experiment is finished (all data acquired)"""
self.data_to_pandas()
self.time += [datetime.utcnow()]
def export(self):
"""Return a dict containing data for saving."""
output = {
"datatype" : self.datatype,
"xlabel" : self.xlabel,
"ylabel" : self.ylabel,
"xmin" : self.xmin,
"xmax" : self.xmax,
"parameters" : self.parameters,
"data" : self.data,
"commands" : self.commands
}
return output
def data_to_pandas(self):
"""Convert data to pandas DataFrame and set as member of .df
attribute."""
self.df = OrderedDict()
for name, data in self.data.items():
try:
df = DataFrame(
columns=['Scan'] + list(self.plot_format[name]['labels']))
for n, line in enumerate(data): # Each scan
df = df.append(
DataFrame(
OrderedDict(zip(
['Scan'] + list(self.plot_format[name]['labels']),
[n] + list(line))
)
), ignore_index = True
)
except (AttributeError, KeyError):
try:
df = DataFrame(
columns=['Scan'] + list(self.columns))
for n, line in enumerate(data): # Each scan
df = df.append(
DataFrame(
OrderedDict(zip(
['Scan'] + list(self.columns),
[n] + list(line))
)
), ignore_index = True
)
except AttributeError as e: # Fallback if no self.columns
df = DataFrame(
columns=['Scan'] + ["{}{}".format(name, n)
for n in range(len(data))]
)
for n, line in enumerate(data):
df = df.append(
DataFrame(
OrderedDict(zip(
['Scan'] + ["{}{}".format(name, n)
for n in range(len(data))],
[n] + list(line))
)
), ignore_index = True
)
self.df[name] = df
def get_info_text(self):
"""Return string of text to disply on Info tab."""
buf = "#Time: S{} E{}\n".format(self.time[0], self.time[1])
buf += "#Commands:\n"
for line in self.commands:
buf += '#{}\n'.format(line)
return buf
def get_save_strings(self):
"""Return dict of strings with experiment parameters and data."""
buf = {}
buf['params'] = self.get_info_text()
buf.update(
{exp : df.to_csv(sep='\t', encoding='utf-8')
for exp, df in self.df.items()}
)
return buf
class PlotBox(object):
"""Contains data plot and associated methods."""
def __init__(self, plots=None):
"""Initializes plots. self.box should be reparented."""
self.name = "Main"
self.continuous_refresh = True
self.scan_refresh = False
if plots is not None:
self.plotnames = plots
else:
self.plotnames = []
self.subplots = {}
self.figure = Figure()
# self.figure.subplots_adjust(left=0.07, bottom=0.07,
# right=0.96, top=0.96)
self.setup()
self.format_plots() # Should be overriden by subclass
self.figure.set_tight_layout(True)
self.canvas = FigureCanvas(self.figure)
self.canvas.set_vexpand(True)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.box.pack_start(self.canvas, expand=True, fill=True, padding=0)
def setup(self):
self.plot_format = {
'current_voltage': {'xlabel': "Voltage (mV)",
'ylabel': "Current (A)"
}
}
def format_plots(self):
"""
Creates and formats subplots needed. Should be overriden by subclass
"""
# Calculate size of grid needed
if len(self.plotnames) > 1:
gs = gridspec.GridSpec(int(ceil(len(self.plotnames)/2.)),2)
else:
gs = gridspec.GridSpec(1,1)
for n, i in enumerate(self.plotnames):
self.subplots[i] = self.figure.add_subplot(gs[n])
for key, subplot in self.subplots.items():
subplot.ticklabel_format(style='sci', scilimits=(0, 3),
useOffset=False, axis='y')
subplot.plot([], [])
subplot.set_xlabel(self.plot_format[key]['xlabel'])
subplot.set_ylabel(self.plot_format[key]['ylabel'])
def clearall(self):
"""Remove all lines on plot. """
for name, plot in self.subplots.items():
for line in reversed(plot.lines):
line.remove()
self.addline()
def clearline(self, subplot, line_number):
"""Remove a single line.
Arguments:
subplot -- key in self.subplots
line_number -- line number in subplot
"""
self.subplots[subplot].lines[line_number].remove()
def addline(self):
"""Add a new line to plot. (initialized with dummy data)))"""
for subplot in self.subplots.values():
subplot.plot([], [])
def updateline(self, Experiment, line_number):
"""Update a line specified with new data.
Arguments:
Experiment -- Experiment instance
line_number -- line number to update
"""
for subplot in Experiment.data:
while True:
try:
self.subplots[subplot].lines[line_number].set_xdata(
Experiment.data[subplot][line_number][0])
self.subplots[subplot].lines[line_number].set_ydata(
Experiment.data[subplot][line_number][1])
except IndexError:
self.addline()
except KeyError:
pass
else:
break
# logger.warning("Tried to set line %s that doesn't exist.", line_number)
def setlims(self, plot, xlims=None, ylims=None):
"""Sets x and y limits.
"""
if xlims is not None:
self.subplots[plot].set_xlim(xlims)
if ylims is not None:
self.subplots[plot].set_ylim(ylims)
self.figure.canvas.draw()
def redraw(self):
"""Autoscale and refresh the plot."""
for name, plot in self.subplots.items():
plot.relim()
plot.autoscale(True, axis = 'y')
self.figure.canvas.draw()
return True
\ No newline at end of file
import time
import struct
from .experiment_template import Experiment
from ..dstat import state
class OCPExp(Experiment):
"""Open circuit potential measumement in statusbar."""
id = 'ocp'
def __init__(self):
self.re_voltage_scale = state.board_instance.re_voltage_scale
self.databytes = 8
self.commands = ["EA", "EP0 0 "]
self.commands[0] += "2 " # input buffer
self.commands[0] += "3 " # 2.5 Hz sample rate
self.commands[0] += "1 " # 2x PGA
def data_handler(self, data_input):
"""Overrides Experiment method to only send ADC values."""
scan, data = data_input
# 2*uint16 + int32
seconds, milliseconds, voltage = struct.unpack('<HHl', data)
return voltage/5.592405e6*self.re_voltage_scale
class PMTIdle(Experiment):
"""PMT idle mode."""
id = "pmt_idle"
def __init__(self):
self.databytes = 8
self.commands = ["EA", "EM"]
self.commands[0] += "2 " # input buffer
self.commands[0] += "3 " # 2.5 Hz sample rate
self.commands[0] += "1 " # 2x PGA
import time
import struct
from .experiment_template import PlotBox, Experiment
class LSVExp(Experiment):
"""Linear Scan Voltammetry experiment"""
id = 'lsv'
def setup(self):
self.plotlims['current_voltage']['xlims'] = tuple(
sorted(
(int(self.parameters['start']),
int(self.parameters['stop']))
)
)
super(LSVExp, self).setup()
self.datatype = "linearData"
self.datalength = 2
self.databytes = 6 # uint16 + int32
self.stop_mv = int(self.parameters['stop'])
self.max_mv = abs(int(self.parameters['start'])-int(self.parameters['stop']))
self.commands += "E"
self.commands[2] += "L"
self.commands[2] += str(self.parameters['clean_s'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['dep_s'])
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['clean_mV'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['dep_mV'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['start'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['stop'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['slope'])/
self.re_voltage_scale*
(65536./3000)
))
self.commands[2] += " "
def get_progress(self):
try:
return 1 - (abs(self.stop_mv - self.data['current_voltage'][-1][0][-1])/self.max_mv)
except IndexError:
return 0
\ No newline at end of file
import time
import struct
from .experiment_template import PlotBox, Experiment
class PotBox(PlotBox):
def setup(self):
self.plot_format = {
'voltage_time': {'xlabel': "Time (s)",
'ylabel': "Voltage (V)"
}
}
def format_plots(self):
"""
Creates and formats subplots needed. Overrides superclass.
"""
self.subplots = {'voltage_time': self.figure.add_subplot(111)}
for key, subplot in self.subplots.items():
subplot.ticklabel_format(style='sci', scilimits=(0, 3),
useOffset=False, axis='y')
subplot.plot([],[])
subplot.set_xlabel(self.plot_format[key]['xlabel'])
subplot.set_ylabel(self.plot_format[key]['ylabel'])
class PotExp(Experiment):
id = 'pot'
"""Potentiometry experiment"""
def setup(self):
self.plots.append(PotBox(['voltage_time']))
self.datatype = "linearData"
self.datalength = 2
self.databytes = 8
self.data = {'voltage_time' : [([],[])]}
self.columns = ['Time (s)', 'Voltage (V)']
self.plotlims = {
'voltage_time': {
'xlims': (0, int(self.parameters['time']))
}
}
self.plots[-1].setlims('voltage_time', **self.plotlims['voltage_time'])
self.total_time = int(self.parameters['time'])
self.commands += "E"
self.commands[2] += "P"
self.commands[2] += str(self.parameters['time'])
self.commands[2] += " 1 " # potentiometry mode
def data_handler(self, data_input):
"""Overrides Experiment method to not convert x axis to mV."""
scan, data = data_input
# 2*uint16 + int32
seconds, milliseconds, voltage = struct.unpack('<HHl', data)
return (scan, (
seconds+milliseconds/1000.,
voltage*self.re_voltage_scale*(1.5/8388607.)
)
)
def store_data(self, incoming, newline):
"""Stores data in data attribute. Should not be called from subprocess.
Can be overriden for custom experiments."""
line, data = incoming
if newline is True:
self.data['voltage_time'].append(deepcopy(self.line_data))
for i, item in enumerate(self.data['voltage_time'][line]):
item.append(data[i])
def get_progress(self):
try:
return self.data['voltage_time'][-1][0][-1]/self.total_time
except IndexError:
return 0
\ No newline at end of file
import time
import struct
from copy import deepcopy
from .experiment_template import PlotBox, Experiment
class SWVBox(PlotBox):
def setup(self):
self.plot_format = {
'swv': {'xlabel': "Voltage (mV)",
'ylabel': "Current (A)"
}
}
def format_plots(self):
"""
Creates and formats subplots needed. Overrides superclass.
"""
self.subplots = {'swv': self.figure.add_subplot(111)}
for key, subplot in self.subplots.items():
subplot.ticklabel_format(style='sci', scilimits=(0, 3),
useOffset=False, axis='y')
subplot.plot([],[])
subplot.set_xlabel(self.plot_format[key]['xlabel'])
subplot.set_ylabel(self.plot_format[key]['ylabel'])
class SWVExp(Experiment):
"""Square Wave Voltammetry experiment"""
id = 'swv'
def setup(self):
self.datatype = "SWVData"
self.xlabel = "Voltage (mV)"
self.ylabel = "Current (A)"
self.data = {
'swv' : [([], [], [], [])]
} # voltage, current, forwards, reverse
self.line_data = ([], [], [], [])
self.datalength = 2 * self.parameters['scans']
self.databytes = 10
self.columns = ['Voltage (mV)', 'Net Current (A)',
'Forward Current (A)', 'Reverse Current (A)']
self.plotlims = {
'swv': {
'xlims': tuple(sorted(
(int(self.parameters['start']),
int(self.parameters['stop']))
)
)
}
}
plot = SWVBox()
plot.setlims('swv', **self.plotlims['swv'])
self.plots.append(plot)
self.stop_mv = int(self.parameters['stop'])
self.max_mv = abs(int(self.parameters['start']) - int(self.parameters['stop']))
self.scan_points = self.max_mv * 2 / float(self.parameters['step'])
self.commands += "E"
self.commands[2] += "S"
self.commands[2] += str(self.parameters['clean_s'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['dep_s'])
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['clean_mV'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['dep_mV'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['start'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['stop'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['step'])/
self.re_voltage_scale*
(65536./3000)
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['pulse'])/
self.re_voltage_scale*
(65536./3000)
))
self.commands[2] += " "
self.commands[2] += str(self.parameters['freq'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['scans'])
self.commands[2] += " "
def data_handler(self, input_data):
"""Overrides Experiment method to calculate difference current"""
scan, data = input_data
# uint16 + int32
voltage, forward, reverse = struct.unpack('<Hll', data)
f_trim = forward+self.gain_trim
r_trim = reverse+self.gain_trim
return (scan, (
(voltage-32768)*3000./65536*self.re_voltage_scale,
(f_trim-r_trim)*(1.5/self.gain/8388607),
f_trim*(1.5/self.gain/8388607),
r_trim*(1.5/self.gain/8388607)
)
)
def store_data(self, incoming, newline):
"""Stores data in data attribute. Should not be called from subprocess.
Can be overriden for custom experiments."""
line, data = incoming
if newline is True:
self.data['swv'].append(deepcopy(self.line_data))
for i, item in enumerate(self.data['swv'][line]):
item.append(data[i])
def get_progress(self):
try:
if int(self.parameters['scans']) != 0:
scans_prog = (len(self.data['swv'])-1) / float(self.parameters['scans'])
scan_prog = (len(self.data['swv'][-1][0])-1) / self.scan_points / float(self.parameters['scans'])
prog = scans_prog + scan_prog
if prog > 1:
prog = 1
return prog
else:
return 1 - (abs(self.stop_mv - self.data['swv'][-1][0][-1])/self.max_mv)
except IndexError:
return 0
class DPVExp(SWVExp):
"""Diffential Pulse Voltammetry experiment."""
id = 'dpv'
def setup(self):
self.datatype = "SWVData"
self.xlabel = "Voltage (mV)"
self.ylabel = "Current (A)"
self.data = {
'swv' : [([], [], [], [])]
} # voltage, current, forwards, reverse
self.line_data = ([], [], [], [])
self.datalength = 2
self.databytes = 10
self.columns = ['Voltage (mV)', 'Net Current (A)',
'Forward Current (A)', 'Reverse Current (A)']
self.plotlims = {
'swv': {
'xlims': tuple(sorted(
(int(self.parameters['start']),
int(self.parameters['stop']))
)
)
}
}
plot = SWVBox()
plot.setlims('swv', **self.plotlims['swv'])
self.plots.append(plot)
self.stop_mv = int(self.parameters['stop'])
self.max_mv = abs(int(self.parameters['start']) - int(self.parameters['stop']))
self.commands += "E"
self.commands[2] += "D"
self.commands[2] += str(self.parameters['clean_s'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['dep_s'])
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['clean_mV'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['dep_mV'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['start'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['stop'])/
self.re_voltage_scale*
(65536./3000)+32768
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['step'])/
self.re_voltage_scale*
(65536./3000)
))
self.commands[2] += " "
self.commands[2] += str(int(
int(self.parameters['pulse'])/
self.re_voltage_scale*
(65536./3000)
))
self.commands[2] += " "
self.commands[2] += str(self.parameters['period'])
self.commands[2] += " "
self.commands[2] += str(self.parameters['width'])
self.commands[2] += " "
def get_progress(self):
try:
return 1 - (abs(self.stop_mv - self.data['swv'][-1][0][-1])/self.max_mv)
except IndexError:
return 0
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy project-wide -->
<requires lib="gtk+" version="3.10"/>
<object class="GtkListStore" id="gain_liststore">
<columns>
<!-- column-name index -->
......@@ -196,13 +196,15 @@
<object class="GtkWindow" id="window1">
<property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="vbox1">
<object class="GtkBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkVBox" id="vbox2">
<object class="GtkBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="adc_frame">
<property name="visible">True</property>
......@@ -217,12 +219,11 @@
<property name="left_padding">5</property>
<property name="right_padding">5</property>
<child>
<object class="GtkTable" id="table1">
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<property name="row_spacing">2</property>
<property name="row_homogeneous">True</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
......@@ -230,6 +231,10 @@
<property name="tooltip_text" translatable="yes">Gain of the ADC's programmable gain amplifier. Default is 2 - gives full scale input.</property>
<property name="label" translatable="yes">PGA Setting</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
......@@ -238,8 +243,8 @@
<property name="label" translatable="yes">Sample Rate</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
......@@ -252,10 +257,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_EXPAND</property>
</packing>
</child>
<child>
......@@ -266,8 +268,8 @@
<property name="label" translatable="yes">Input Buffer</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
......@@ -278,7 +280,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
......@@ -289,9 +291,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
......@@ -302,8 +302,8 @@
<property name="label" translatable="yes">2 Electrode Mode</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
......@@ -315,10 +315,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">GTK_EXPAND</property>
</packing>
</child>
</object>
......@@ -336,8 +333,7 @@
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">2</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
......@@ -355,9 +351,10 @@
<property name="left_padding">5</property>
<property name="right_padding">5</property>
<child>
<object class="GtkHBox" id="hbox1">
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
......@@ -366,9 +363,8 @@
<property name="label" translatable="yes">Gain</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
......@@ -378,9 +374,8 @@
<property name="model">gain_liststore</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
......@@ -405,8 +400,9 @@
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">2</property>
<property name="position">0</property>
</packing>
</child>
......
......@@ -17,37 +17,28 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os.path
from pkg_resources import parse_version
import gtk
try:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
except ImportError:
print "ERR: GTK not available"
sys.exit(1)
from errors import InputError, VarError, ErrorLogger
_logger = ErrorLogger(sender="dstat_adc_pot")
from ..errors import InputError, VarError
from ..dstat import state
v1_1_gain = [(0, "100 Ω (15 mA FS)", 0),
(1, "300 Ω (5 mA FS)", 1),
(2, "3 kΩ (500 µA FS)", 2),
(3, "30 kΩ (50 µA FS)", 3),
(4, "300 kΩ (5 µA FS)", 4),
(5, "3 MΩ (500 nA FS)", 5),
(6, "30 MΩ (50 nA FS)", 6),
(7, "500 MΩ (3 nA FS)", 7)]
v1_2_gain = [(0, "Bypass", 0),
(1, "100 Ω (15 mA FS)", 1),
(2, "3 kΩ (500 µA FS)", 2),
(3, "30 kΩ (50 µA FS)", 3),
(4, "300 kΩ (5 µA FS)", 4),
(5, "3 MΩ (500 nA FS)", 5),
(6, "30 MΩ (50 nA FS)", 6),
(7, "100 MΩ (15 nA FS)", 7)]
mod_dir = os.path.dirname(os.path.abspath(__file__))
class adc_pot(object):
def __init__(self):
self.builder = gtk.Builder()
self.builder.add_from_file('interface/adc_pot.glade')
self.builder = Gtk.Builder()
self.builder.add_from_file(os.path.join(mod_dir,'adc_pot.glade'))
self.builder.connect_signals(self)
self.cell = gtk.CellRendererText()
self.cell = Gtk.CellRendererText()
ui_keys = ['buffer_true',
'short_true',
......@@ -76,7 +67,7 @@ class adc_pot(object):
self.gain_liststore = self.builder.get_object('gain_liststore')
self.ui['gain_index'].pack_start(self.cell, True)
self.ui['gain_index'].add_attribute(self.cell, 'text', 1)
self.ui['gain_index'].set_active(2)
# self.ui['gain_index'].set_active(2)
self._params = {}
......@@ -121,10 +112,8 @@ class adc_pot(object):
self._params = dict.fromkeys(self.ui.keys())
for i in self._params:
try:
if i in params:
self._params[i] = params[i]
except KeyError as e:
_logger.error("Invalid parameter key: %s" % e, "WAR")
self._set_params()
def _set_params(self):
......@@ -132,15 +121,19 @@ class adc_pot(object):
for i in self.ui:
self.ui[i].set_active(self._params[i])
def set_version(self, version):
def set_version(self, boost=None):
""" Sets menus for DStat version. """
try:
if self.version == state.board_instance:
return
except AttributeError:
pass
self.version = state.board_instance
self.gain_liststore.clear()
if version[0] == 1:
if version[1] == 1:
for i in v1_1_gain:
self.gain_liststore.append(i)
elif version[1] >= 2:
for i in v1_2_gain:
self.gain_liststore.append(i)
for n, i in enumerate(self.version.gain_labels):
self.gain_liststore.append((n, i, str(n)))
\ No newline at end of file
self.ui['gain_index'].set_active(self.version.gain_default_index)
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkAdjustment" id="stats_start_adj">
<property name="lower">-3000</property>
<property name="upper">9999</property>
<property name="step_increment">0.5</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="stats_stop_adj">
<property name="lower">-3000</property>
<property name="upper">9999</property>
<property name="step_increment">0.5</property>
<property name="page_increment">10</property>
</object>
<requires lib="gtk+" version="3.10"/>
<object class="GtkDialog" id="analysis_dialog">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Analysis Options…</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox1">
<object class="GtkBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area1">
<object class="GtkButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
......@@ -54,27 +43,18 @@
</packing>
</child>
<child>
<object class="GtkTable" id="table1">
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkSpinButton" id="stats_start_spin">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
<property name="text" translatable="yes">0.00</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>
<property name="adjustment">stats_start_adj</property>
<property name="climb_rate">0.050000000000000003</property>
<property name="digits">2</property>
......@@ -82,9 +62,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
......@@ -93,10 +71,9 @@
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
<property name="text" translatable="yes">0.00</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>
<property name="adjustment">stats_stop_adj</property>
<property name="climb_rate">0.050000000000000003</property>
<property name="digits">2</property>
......@@ -105,9 +82,7 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
......@@ -116,11 +91,14 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="image_position">bottom</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
......@@ -130,11 +108,12 @@
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="hexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
......@@ -144,16 +123,17 @@
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="hexpand">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
......@@ -164,4 +144,16 @@
<action-widget response="0">ok_button</action-widget>
</action-widgets>
</object>
<object class="GtkAdjustment" id="stats_start_adj">
<property name="lower">-3000</property>
<property name="upper">9999</property>
<property name="step_increment">0.5</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="stats_stop_adj">
<property name="lower">-3000</property>
<property name="upper">9999</property>
<property name="step_increment">0.5</property>
<property name="page_increment">10</property>
</object>
</interface>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy project-wide -->
<requires lib="gtk+" version="3.10"/>
<object class="GtkListStore" id="ca_list">
<columns>
<!-- column-name millivolts -->
......@@ -18,29 +18,34 @@
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkViewport" id="viewport1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkVBox" id="vbox1">
<object class="GtkBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTable" id="table1">
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="row_homogeneous">True</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Potential (mV)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="potential_entry">
......@@ -51,18 +56,13 @@
<property name="text" translatable="yes">0</property>
<property name="xalign">1</property>
<property name="truncate_multiline">True</property>
<property name="invisible_char_set">True</property>
<property name="caps_lock_warning">False</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>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options"/>
<property name="y_options">GTK_SHRINK</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
......@@ -72,8 +72,8 @@
<property name="label" translatable="yes">Time (s)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
......@@ -85,20 +85,13 @@
<property name="text" translatable="yes">0</property>
<property name="xalign">1</property>
<property name="truncate_multiline">True</property>
<property name="invisible_char_set">True</property>
<property name="caps_lock_warning">False</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>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options"/>
<property name="y_options">GTK_SHRINK</property>
</packing>
</child>
<child>
......@@ -111,8 +104,8 @@
<signal name="clicked" handler="on_add_button_clicked" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
......@@ -126,29 +119,26 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox2">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTreeView" id="treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">ca_list</property>
<property name="headers_clickable">False</property>
<property name="reorderable">True</property>
<property name="rules_hint">True</property>
<property name="enable_search">False</property>
......@@ -157,6 +147,9 @@
<property name="show_expanders">False</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
<packing>
<property name="expand">True</property>
......@@ -169,7 +162,6 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<property name="has_resize_grip">False</property>
</object>
<packing>
<property name="expand">False</property>
......
from __future__ import division, absolute_import, print_function, unicode_literals
import logging
logger = logging.getLogger(__name__)
from collections import OrderedDict
try:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
except ImportError:
print("ERR: GTK not available")
sys.exit(1)
class DataPage(object):
def __init__(self, notebook, name="Data"):
"""Make new notebook page and adds to notebook."""
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.combobox = Gtk.ComboBoxText()
self.scroll = Gtk.ScrolledWindow(vexpand=True)
self.textview = Gtk.TextView(cursor_visible=False, monospace=True,
editable=False)
self.scroll.add(self.textview)
self.box.add(self.combobox)
self.box.add(self.scroll)
self.name = name
self.buffers = {}
self.combobox.connect('changed', self.combobox_changed)
notebook.append_page(self.box, Gtk.Label(label=name))
def add_exp(self, exp):
"""Add all data from exp to page."""
for name, df in exp.df.items():
self.combobox.append(id=name, text=name)
self.buffers[name] = Gtk.TextBuffer()
self.buffers[name].set_text(df.to_string())
self.box.show_all()
self.combobox.set_active(0)
def clear_exps(self):
self.combobox.remove_all()
self.buffers = {}
def combobox_changed(self, object):
"""Switch displayed data buffer."""
try:
self.textview.set_buffer(
self.buffers[self.combobox.get_active_id()]
)
except KeyError:
pass
class InfoPage(object):
def __init__(self, notebook, name="Info"):
"""Make new notebook page and adds to notebook."""
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.buffer = Gtk.TextBuffer()
self.scroll = Gtk.ScrolledWindow(vexpand=True)
self.textview = Gtk.TextView(cursor_visible=False, monospace=True,
editable=False, buffer=self.buffer)
self.scroll.add(self.textview)
self.box.add(self.scroll)
self.name = name
notebook.append_page(self.box, Gtk.Label(label=name))
self.box.show_all()
def clear(self):
"""Clear buffer"""
self.buffer.set_text('')
def set_text(self, text):
self.buffer.set_text(text)
def add_line(self, line):
self.buffer.insert_at_cursor('{}\n'.format(line))
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.