mirror of https://github.com/m-labs/artiq.git
gui: beginning of realtime plotting
This commit is contained in:
parent
33283feacb
commit
95ee6a4951
|
@ -0,0 +1,160 @@
|
||||||
|
import asyncio
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from gi.repository import Gtk
|
||||||
|
import cairoplot
|
||||||
|
|
||||||
|
from artiq.management.sync_struct import Subscriber
|
||||||
|
from artiq.gui.tools import Window
|
||||||
|
|
||||||
|
|
||||||
|
class _PlotWindow(Window):
|
||||||
|
def __init__(self, set_names):
|
||||||
|
self.set_names = set_names
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
Window.__init__(self, title="/".join(set_names))
|
||||||
|
self.set_default_size(700, 500)
|
||||||
|
|
||||||
|
self.darea = Gtk.DrawingArea()
|
||||||
|
self.darea.set_size_request(100, 100)
|
||||||
|
self.darea.connect("draw", self.on_draw)
|
||||||
|
self.add(self.darea)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
class XYWindow(_PlotWindow):
|
||||||
|
def on_draw(self, widget, ctx):
|
||||||
|
if self.data is not None:
|
||||||
|
cairoplot.scatter_plot(
|
||||||
|
ctx,
|
||||||
|
data=self.filter_data(),
|
||||||
|
width=widget.get_allocated_width(),
|
||||||
|
height=widget.get_allocated_height(),
|
||||||
|
border=20, axis=True, grid=True,
|
||||||
|
dots=1, discrete=True,
|
||||||
|
series_colors=[(0.0, 0.0, 0.0)],
|
||||||
|
background="white"
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_data(self):
|
||||||
|
return [
|
||||||
|
self.data[self.set_names[0]],
|
||||||
|
self.data[self.set_names[1]],
|
||||||
|
]
|
||||||
|
|
||||||
|
def set_data(self, data):
|
||||||
|
self.data = data
|
||||||
|
# The two axes are not updated simultaneously.
|
||||||
|
# Redraw only after receiving a new point for each.
|
||||||
|
x, y = self.filter_data()
|
||||||
|
if len(x) == len(y):
|
||||||
|
self.darea.queue_draw()
|
||||||
|
|
||||||
|
|
||||||
|
def _create_view(set_names, view_description):
|
||||||
|
r = XYWindow(set_names)
|
||||||
|
r.show_all()
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class _Group:
|
||||||
|
def __init__(self, init):
|
||||||
|
# data key -> list of views using it
|
||||||
|
self.views = defaultdict(list)
|
||||||
|
# original data
|
||||||
|
self.data = dict()
|
||||||
|
for k, v in init.items():
|
||||||
|
self[k] = v
|
||||||
|
|
||||||
|
def all_views(self):
|
||||||
|
r = set()
|
||||||
|
for view_list in self.views.values():
|
||||||
|
for view in view_list:
|
||||||
|
r.add(view)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
for view in self.all_views():
|
||||||
|
view.delete()
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key == "data":
|
||||||
|
return self.data
|
||||||
|
else:
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key == "description":
|
||||||
|
self.delete()
|
||||||
|
self.views = defaultdict(list)
|
||||||
|
for set_names, view_description in value.items():
|
||||||
|
if not isinstance(set_names, tuple):
|
||||||
|
set_names = (set_names, )
|
||||||
|
view = _create_view(set_names, view_description)
|
||||||
|
view.set_data(self.data)
|
||||||
|
for set_name in set_names:
|
||||||
|
self.views[set_name].append(view)
|
||||||
|
elif key == "data":
|
||||||
|
self.data = value
|
||||||
|
for view in self.all_views():
|
||||||
|
view.set_data(self.data)
|
||||||
|
else:
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
def on_data_modified(self, key):
|
||||||
|
for view in self.views[key]:
|
||||||
|
view.set_data(self.data)
|
||||||
|
|
||||||
|
|
||||||
|
class _Groups:
|
||||||
|
def __init__(self, init):
|
||||||
|
self.groups = dict()
|
||||||
|
for k, v in init.items():
|
||||||
|
self[k] = v
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
for s in self.groups.values():
|
||||||
|
s.delete()
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.groups[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key in self.groups:
|
||||||
|
self.groups[key].delete()
|
||||||
|
self.groups[key] = _Group(value)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
self.groups[key].delete()
|
||||||
|
del self.groups[key]
|
||||||
|
|
||||||
|
|
||||||
|
class RTResults:
|
||||||
|
def __init__(self):
|
||||||
|
self.current_groups = None
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def sub_connect(self, host, port):
|
||||||
|
self.sets_subscriber = Subscriber("rt_results",
|
||||||
|
self.init_groups, self.on_mod)
|
||||||
|
yield from self.sets_subscriber.connect(host, port)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def sub_close(self):
|
||||||
|
yield from self.sets_subscriber.close()
|
||||||
|
|
||||||
|
def init_groups(self, init):
|
||||||
|
if self.current_groups is not None:
|
||||||
|
self.current_groups.delete()
|
||||||
|
self.current_groups = _Groups(init)
|
||||||
|
return self.current_groups
|
||||||
|
|
||||||
|
def on_mod(self, mod):
|
||||||
|
if mod["action"] != "init" and len(mod["path"]) >= 3:
|
||||||
|
path = mod["path"]
|
||||||
|
group = self.current_groups[path[0]]
|
||||||
|
if path[1] == "data":
|
||||||
|
group.on_data_modified(path[2])
|
|
@ -3,8 +3,8 @@ from artiq.management.sync_struct import Notifier, process_mod
|
||||||
|
|
||||||
class RTResults:
|
class RTResults:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sets = Notifier(dict())
|
self.groups = Notifier(dict())
|
||||||
self.current_set = "default"
|
self.current_group = "default"
|
||||||
|
|
||||||
def init(self, description):
|
def init(self, description):
|
||||||
data = dict()
|
data = dict()
|
||||||
|
@ -14,11 +14,11 @@ class RTResults:
|
||||||
data[e] = []
|
data[e] = []
|
||||||
else:
|
else:
|
||||||
data[rtr] = []
|
data[rtr] = []
|
||||||
self.sets[self.current_set] = {
|
self.groups[self.current_group] = {
|
||||||
"description": description,
|
"description": description,
|
||||||
"data": data
|
"data": data
|
||||||
}
|
}
|
||||||
|
|
||||||
def update(self, mod):
|
def update(self, mod):
|
||||||
target = self.sets[self.current_set]["data"]
|
target = self.groups[self.current_group]["data"]
|
||||||
process_mod(target, mod)
|
process_mod(target, mod)
|
||||||
|
|
|
@ -10,6 +10,7 @@ from gi.repository import Gtk
|
||||||
from artiq.management.pc_rpc import AsyncioClient
|
from artiq.management.pc_rpc import AsyncioClient
|
||||||
from artiq.gui.scheduler import SchedulerWindow
|
from artiq.gui.scheduler import SchedulerWindow
|
||||||
from artiq.gui.parameters import ParametersWindow
|
from artiq.gui.parameters import ParametersWindow
|
||||||
|
from artiq.gui.rt_results import RTResults
|
||||||
|
|
||||||
|
|
||||||
def _get_args():
|
def _get_args():
|
||||||
|
@ -55,6 +56,13 @@ def main():
|
||||||
atexit.register(
|
atexit.register(
|
||||||
lambda: loop.run_until_complete(parameters_win.sub_close()))
|
lambda: loop.run_until_complete(parameters_win.sub_close()))
|
||||||
|
|
||||||
|
rtr = RTResults()
|
||||||
|
loop.run_until_complete(rtr.sub_connect(
|
||||||
|
args.server, args.port_notify))
|
||||||
|
atexit.register(
|
||||||
|
lambda: loop.run_until_complete(rtr.sub_close()))
|
||||||
|
|
||||||
|
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -62,7 +62,7 @@ def main():
|
||||||
"devices": ddb.data,
|
"devices": ddb.data,
|
||||||
"parameters": pdb.data,
|
"parameters": pdb.data,
|
||||||
"parameters_simplehist": simplephist.history,
|
"parameters_simplehist": simplephist.history,
|
||||||
"rt_results": rtr.sets
|
"rt_results": rtr.groups
|
||||||
})
|
})
|
||||||
loop.run_until_complete(server_notify.start(
|
loop.run_until_complete(server_notify.start(
|
||||||
args.bind, args.port_notify))
|
args.bind, args.port_notify))
|
||||||
|
|
Loading…
Reference in New Issue