From 95ee6a495124e7af0748ba7252d2f7befb77a978 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Wed, 14 Jan 2015 22:22:33 +0800 Subject: [PATCH] gui: beginning of realtime plotting --- artiq/gui/rt_results.py | 160 +++++++++++++++++++++++++++++++++++++ artiq/master/rt_results.py | 8 +- frontend/artiq_gui.py | 8 ++ frontend/artiq_master.py | 2 +- 4 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 artiq/gui/rt_results.py diff --git a/artiq/gui/rt_results.py b/artiq/gui/rt_results.py new file mode 100644 index 000000000..9761ceb45 --- /dev/null +++ b/artiq/gui/rt_results.py @@ -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]) diff --git a/artiq/master/rt_results.py b/artiq/master/rt_results.py index b554a3c94..7d07851a1 100644 --- a/artiq/master/rt_results.py +++ b/artiq/master/rt_results.py @@ -3,8 +3,8 @@ from artiq.management.sync_struct import Notifier, process_mod class RTResults: def __init__(self): - self.sets = Notifier(dict()) - self.current_set = "default" + self.groups = Notifier(dict()) + self.current_group = "default" def init(self, description): data = dict() @@ -14,11 +14,11 @@ class RTResults: data[e] = [] else: data[rtr] = [] - self.sets[self.current_set] = { + self.groups[self.current_group] = { "description": description, "data": data } def update(self, mod): - target = self.sets[self.current_set]["data"] + target = self.groups[self.current_group]["data"] process_mod(target, mod) diff --git a/frontend/artiq_gui.py b/frontend/artiq_gui.py index 6d6c04a43..0ad585d9a 100755 --- a/frontend/artiq_gui.py +++ b/frontend/artiq_gui.py @@ -10,6 +10,7 @@ from gi.repository import Gtk from artiq.management.pc_rpc import AsyncioClient from artiq.gui.scheduler import SchedulerWindow from artiq.gui.parameters import ParametersWindow +from artiq.gui.rt_results import RTResults def _get_args(): @@ -55,6 +56,13 @@ def main(): atexit.register( 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() if __name__ == "__main__": diff --git a/frontend/artiq_master.py b/frontend/artiq_master.py index a5462b55a..60d9c1a99 100755 --- a/frontend/artiq_master.py +++ b/frontend/artiq_master.py @@ -62,7 +62,7 @@ def main(): "devices": ddb.data, "parameters": pdb.data, "parameters_simplehist": simplephist.history, - "rt_results": rtr.sets + "rt_results": rtr.groups }) loop.run_until_complete(server_notify.start( args.bind, args.port_notify))