From 4016e5adaa3479f78af716efb580a28ae69fa6c0 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Sat, 7 May 2016 23:22:39 +0200 Subject: [PATCH] browser: support opening experiments --- artiq/browser/experiments.py | 184 +++++++++++++++++++++----------- artiq/frontend/artiq_browser.py | 2 +- 2 files changed, 120 insertions(+), 66 deletions(-) diff --git a/artiq/browser/experiments.py b/artiq/browser/experiments.py index 528d40276..731d289c6 100644 --- a/artiq/browser/experiments.py +++ b/artiq/browser/experiments.py @@ -2,15 +2,16 @@ import asyncio import logging import os from functools import partial +from collections import OrderedDict from PyQt5 import QtCore, QtGui, QtWidgets import h5py from artiq import __artiq_dir__ as artiq_dir -from artiq.gui.tools import LayoutWidget, log_level_to_name +from artiq.gui.tools import LayoutWidget, log_level_to_name, getOpenFileName from artiq.gui.entries import argty_to_entry from artiq.protocols import pyon - +from artiq.master.worker import Worker logger = logging.getLogger(__name__) @@ -25,13 +26,13 @@ class _WheelFilter(QtCore.QObject): class _ArgumentEditor(QtWidgets.QTreeWidget): - def __init__(self, expurl): + def __init__(self, dock): QtWidgets.QTreeWidget.__init__(self) self.setColumnCount(3) self.header().setStretchLastSection(False) - if hasattr(self.header(), "setSectionResizeMode"): + try: set_resize_mode = self.header().setSectionResizeMode - else: + except AttributeError: set_resize_mode = self.header().setResizeMode set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents) set_resize_mode(1, QtWidgets.QHeaderView.Stretch) @@ -43,18 +44,18 @@ class _ArgumentEditor(QtWidgets.QTreeWidget): self.viewport().installEventFilter(_WheelFilter(self.viewport())) - self.expurl = expurl - self._groups = dict() self._arg_to_entry_widgetitem = dict() + self._dock = dock - arguments = self.get_submission_arguments() # TODO - - if not arguments: + if not self._dock.arguments: self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"])) - for name, argument in arguments.items(): - entry = argty_to_entry[argument["desc"]["ty"]](argument) + for name, argument in self._dock.arguments.items(): + try: + entry = argty_to_entry[argument["desc"]["ty"]](argument) + except: + print(name, argument) widget_item = QtWidgets.QTreeWidgetItem([name]) self._arg_to_entry_widgetitem[name] = entry, widget_item @@ -85,10 +86,8 @@ class _ArgumentEditor(QtWidgets.QTreeWidget): buttons = LayoutWidget() buttons.addWidget(recompute_arguments, 1, 1) - buttons.layout.setColumnStretch(0, 1) - buttons.layout.setColumnStretch(1, 0) - buttons.layout.setColumnStretch(2, 0) - buttons.layout.setColumnStretch(3, 1) + for i, s in enumerate((1, 0, 0, 1)): + buttons.layout.setColumnStretch(i, s) self.setItemWidget(widget_item, 1, buttons) def _get_group(self, name): @@ -105,23 +104,20 @@ class _ArgumentEditor(QtWidgets.QTreeWidget): self._groups[name] = group return group - def get_submission_arguments(self): - return {} # TODO - def _recompute_arguments_clicked(self): - pass # TODO + asyncio.ensure_future(self._dock.recompute_arguments()) def _recompute_argument_clicked(self, name): asyncio.ensure_future(self._recompute_argument(name)) async def _recompute_argument(self, name): try: - arginfo = await self.compute_arginfo() + arginfo = await self._dock.compute_arginfo() except: logger.error("Could not recompute argument '%s' of '%s'", - name, self.expurl, exc_info=True) + name, self._dock.expurl, exc_info=True) return - argument = self.get_submission_arguments()[name] + argument = self._dock.arguments[name] procdesc = arginfo[name][0] state = argty_to_entry[procdesc["ty"]].default_state(procdesc) @@ -135,9 +131,6 @@ class _ArgumentEditor(QtWidgets.QTreeWidget): self._arg_to_entry_widgetitem[name] = entry, widget_item self.setItemWidget(widget_item, 1, entry) - async def compute_arginfo(self): - return {} # TODO - def save_state(self): expanded = [] for k, v in self._groups.items(): @@ -159,7 +152,7 @@ log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] class _ExperimentDock(QtWidgets.QMdiSubWindow): sigClosed = QtCore.pyqtSignal() - def __init__(self, expurl): + def __init__(self, area, expurl, arguments, worker_handlers): QtWidgets.QMdiSubWindow.__init__(self) self.setWindowTitle(expurl) self.setWindowIcon(QtWidgets.QApplication.style().standardIcon( @@ -172,7 +165,10 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow): self.layout.setSpacing(5) self.layout.setContentsMargins(5, 5, 5, 5) + self._area = area self.expurl = expurl + self.worker_handlers = worker_handlers + self.arguments = arguments self.argeditor = _ArgumentEditor(self) self.layout.addWidget(self.argeditor, 0, 0, 1, 5) @@ -218,23 +214,20 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow): self.layout.addWidget(reqterm, 3, 4) reqterm.clicked.connect(self.reqterm_clicked) - def submit_clicked(self): + async def _recompute_arguments(self, overrides={}): try: - pass # TODO + arginfo = await self._area.compute_arginfo(self.expurl) except: - # May happen when experiment has been removed - # from repository/explist - logger.error("Failed to submit '%s'", + logger.error("Could not recompute arguments of '%s'", self.expurl, exc_info=True) + return + for k, v in overrides.items(): + arginfo[k][0]["default"] = v + self.arguments = self._area.initialize_submission_arguments(arginfo) - def reqterm_clicked(self): - try: - pass # TODO - except: - # May happen when experiment has been removed - # from repository/explist - logger.error("Failed to request termination of instances of '%s'", - self.expurl, exc_info=True) + self.argeditor.deleteLater() + self.argeditor = _ArgumentEditor(self) + self.layout.addWidget(self.argeditor, 0, 0, 1, 5) async def _load_hdf5_task(self, filename): try: @@ -255,7 +248,25 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow): exc_info=True) return - await self._recompute_arguments_task(arguments) + await self._recompute_arguments(arguments) + + def submit_clicked(self): + try: + pass # TODO + except: + # May happen when experiment has been removed + # from repository/explist + logger.error("Failed to submit '%s'", + self.expurl, exc_info=True) + + def reqterm_clicked(self): + try: + pass # TODO + except: + # May happen when experiment has been removed + # from repository/explist + logger.error("Failed to request termination of instances of '%s'", + self.expurl, exc_info=True) def closeEvent(self, event): self.sigClosed.emit() @@ -265,19 +276,17 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow): return { "argeditor": self.argeditor.save_state(), "geometry": bytes(self.saveGeometry()), - "expurl": self.expurl, "options": self.options, } def restore_state(self, state): self.argeditor.restore_state(state["argeditor"]) self.restoreGeometry(QtCore.QByteArray(state["geometry"])) - self.expurl = state["expurl"] self.options = state["options"] class ExperimentsArea(QtWidgets.QMdiArea): - def __init__(self, root): + def __init__(self, root, datasets_sub): QtWidgets.QMdiArea.__init__(self) self.pixmap = QtGui.QPixmap(os.path.join( artiq_dir, "gui", "logo20.svg")) @@ -288,11 +297,17 @@ class ExperimentsArea(QtWidgets.QMdiArea): action = QtWidgets.QAction("&Open experiment", self) action.setShortcut(QtGui.QKeySequence("CTRL+o")) action.setShortcutContext(QtCore.Qt.WidgetShortcut) - action.triggered.connect(self.open_experiment) + action.triggered.connect(self._select_experiment) self.addAction(action) self.open_experiments = [] + self.worker_handlers = { + # "get_dataset": dataset_db.get, + # "update_dataset": dataset_db.update, + "get_dataset": lambda k: 0, # TODO + } + def paintEvent(self, event): QtWidgets.QMdiArea.paintEvent(self, event) painter = QtGui.QPainter(self.viewport()) @@ -302,38 +317,77 @@ class ExperimentsArea(QtWidgets.QMdiArea): painter.drawPixmap(x, y, self.pixmap) def save_state(self): - return {"experiments": [experiment.save_state() - for experiment in self.open_experiments]} + return {"experiments": [{ + "expurl": dock.expurl, + "arguments": dock.arguments, + "dock": dock.save_state(), + } for dock in self.open_experiments]} def restore_state(self, state): if self.open_experiments: raise NotImplementedError for ex_state in state["experiments"]: - ex = self.load_experiment(ex_state["expurl"]) - ex.restore_state(ex_state) + dock = self.open_experiment(ex_state["expurl"], + ex_state["arguments"]) + dock.restore_state(ex_state["dock"]) - def open_experiment(self): - file, filter = QtWidgets.QFileDialog.getOpenFileName( - self, "Open experiment", self.current_dir, "Experiments (*.py)") - if not file: - return - logger.info("opening experiment %s", file) - self.load_experiment(file) + def _select_experiment(self): + asyncio.ensure_future(self._select_experiment_task()) - def load_experiment(self, expurl): + async def _select_experiment_task(self): try: - dock = _ExperimentDock(expurl) + file = await getOpenFileName( + self, "Open experiment", self.current_dir, + "Experiments (*.py);;All files (*.*)") + except asyncio.CancelledError: + return + self.current_dir = os.path.dirname(file) + logger.info("opening experiment %s", file) + description = await self.examine(file) + for class_name, class_desc in description.items(): + expurl = "{}@{}".format(class_name, file) + arguments = self.initialize_submission_arguments( + class_desc["arginfo"]) + self.open_experiment(expurl, arguments) + + def initialize_submission_arguments(self, arginfo): + arguments = OrderedDict() + for name, (procdesc, group) in arginfo.items(): + state = argty_to_entry[procdesc["ty"]].default_state(procdesc) + arguments[name] = { + "desc": procdesc, + "group": group, + "state": state # mutated by entries + } + return arguments + + async def examine(self, file): + worker = Worker(self.worker_handlers) + try: + return await worker.examine("examine", file) + finally: + await worker.close() + + async def compute_arginfo(self, expurl): + class_name, file = expurl.split("@", maxsplit=1) + desc = await self.examine(file) + return desc[class_name]["arginfo"] + + def open_experiment(self, expurl, arguments): + try: + dock = _ExperimentDock(self, expurl, arguments, + self.worker_handlers) except: logger.warning("Failed to create experiment dock for %s, " - "attempting to reset arguments", expurl, + "retrying with arguments reset", expurl, exc_info=True) - del self.submission_arguments[expurl] - dock = _ExperimentDock(expurl) - self.open_experiments.append(dock) + dock = _ExperimentDock(self, expurl, {}, self.worker_handlers) + asyncio.ensure_future(dock._recompute_arguments()) self.addSubWindow(dock) dock.show() - dock.sigClosed.connect(partial(self.on_dock_closed, expurl)) + dock.sigClosed.connect(partial(self.on_dock_closed, dock)) + self.open_experiments.append(dock) return dock - def on_dock_closed(self, expurl): - del self.open_experiments[expurl] + def on_dock_closed(self, dock): + self.open_experiments.remove(dock) diff --git a/artiq/frontend/artiq_browser.py b/artiq/frontend/artiq_browser.py index 57f4adc84..9bfc0a92b 100755 --- a/artiq/frontend/artiq_browser.py +++ b/artiq/frontend/artiq_browser.py @@ -85,7 +85,7 @@ def main(): status_bar = QtWidgets.QStatusBar() main_window.setStatusBar(status_bar) - mdi_area = experiments.ExperimentsArea(args.browse_root) + mdi_area = experiments.ExperimentsArea(args.browse_root, datasets_sub) mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) main_window.setCentralWidget(mdi_area)