browser: support opening experiments

This commit is contained in:
Robert Jördens 2016-05-07 23:22:39 +02:00
parent 5332c198c2
commit 4016e5adaa
2 changed files with 120 additions and 66 deletions

View File

@ -2,15 +2,16 @@ import asyncio
import logging import logging
import os import os
from functools import partial from functools import partial
from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
import h5py import h5py
from artiq import __artiq_dir__ as artiq_dir 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.gui.entries import argty_to_entry
from artiq.protocols import pyon from artiq.protocols import pyon
from artiq.master.worker import Worker
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -25,13 +26,13 @@ class _WheelFilter(QtCore.QObject):
class _ArgumentEditor(QtWidgets.QTreeWidget): class _ArgumentEditor(QtWidgets.QTreeWidget):
def __init__(self, expurl): def __init__(self, dock):
QtWidgets.QTreeWidget.__init__(self) QtWidgets.QTreeWidget.__init__(self)
self.setColumnCount(3) self.setColumnCount(3)
self.header().setStretchLastSection(False) self.header().setStretchLastSection(False)
if hasattr(self.header(), "setSectionResizeMode"): try:
set_resize_mode = self.header().setSectionResizeMode set_resize_mode = self.header().setSectionResizeMode
else: except AttributeError:
set_resize_mode = self.header().setResizeMode set_resize_mode = self.header().setResizeMode
set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents) set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents)
set_resize_mode(1, QtWidgets.QHeaderView.Stretch) set_resize_mode(1, QtWidgets.QHeaderView.Stretch)
@ -43,18 +44,18 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
self.viewport().installEventFilter(_WheelFilter(self.viewport())) self.viewport().installEventFilter(_WheelFilter(self.viewport()))
self.expurl = expurl
self._groups = dict() self._groups = dict()
self._arg_to_entry_widgetitem = dict() self._arg_to_entry_widgetitem = dict()
self._dock = dock
arguments = self.get_submission_arguments() # TODO if not self._dock.arguments:
if not arguments:
self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"])) self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"]))
for name, argument in arguments.items(): for name, argument in self._dock.arguments.items():
entry = argty_to_entry[argument["desc"]["ty"]](argument) try:
entry = argty_to_entry[argument["desc"]["ty"]](argument)
except:
print(name, argument)
widget_item = QtWidgets.QTreeWidgetItem([name]) widget_item = QtWidgets.QTreeWidgetItem([name])
self._arg_to_entry_widgetitem[name] = entry, widget_item self._arg_to_entry_widgetitem[name] = entry, widget_item
@ -85,10 +86,8 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
buttons = LayoutWidget() buttons = LayoutWidget()
buttons.addWidget(recompute_arguments, 1, 1) buttons.addWidget(recompute_arguments, 1, 1)
buttons.layout.setColumnStretch(0, 1) for i, s in enumerate((1, 0, 0, 1)):
buttons.layout.setColumnStretch(1, 0) buttons.layout.setColumnStretch(i, s)
buttons.layout.setColumnStretch(2, 0)
buttons.layout.setColumnStretch(3, 1)
self.setItemWidget(widget_item, 1, buttons) self.setItemWidget(widget_item, 1, buttons)
def _get_group(self, name): def _get_group(self, name):
@ -105,23 +104,20 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
self._groups[name] = group self._groups[name] = group
return group return group
def get_submission_arguments(self):
return {} # TODO
def _recompute_arguments_clicked(self): def _recompute_arguments_clicked(self):
pass # TODO asyncio.ensure_future(self._dock.recompute_arguments())
def _recompute_argument_clicked(self, name): def _recompute_argument_clicked(self, name):
asyncio.ensure_future(self._recompute_argument(name)) asyncio.ensure_future(self._recompute_argument(name))
async def _recompute_argument(self, name): async def _recompute_argument(self, name):
try: try:
arginfo = await self.compute_arginfo() arginfo = await self._dock.compute_arginfo()
except: except:
logger.error("Could not recompute argument '%s' of '%s'", logger.error("Could not recompute argument '%s' of '%s'",
name, self.expurl, exc_info=True) name, self._dock.expurl, exc_info=True)
return return
argument = self.get_submission_arguments()[name] argument = self._dock.arguments[name]
procdesc = arginfo[name][0] procdesc = arginfo[name][0]
state = argty_to_entry[procdesc["ty"]].default_state(procdesc) 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._arg_to_entry_widgetitem[name] = entry, widget_item
self.setItemWidget(widget_item, 1, entry) self.setItemWidget(widget_item, 1, entry)
async def compute_arginfo(self):
return {} # TODO
def save_state(self): def save_state(self):
expanded = [] expanded = []
for k, v in self._groups.items(): for k, v in self._groups.items():
@ -159,7 +152,7 @@ log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
class _ExperimentDock(QtWidgets.QMdiSubWindow): class _ExperimentDock(QtWidgets.QMdiSubWindow):
sigClosed = QtCore.pyqtSignal() sigClosed = QtCore.pyqtSignal()
def __init__(self, expurl): def __init__(self, area, expurl, arguments, worker_handlers):
QtWidgets.QMdiSubWindow.__init__(self) QtWidgets.QMdiSubWindow.__init__(self)
self.setWindowTitle(expurl) self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon( self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
@ -172,7 +165,10 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.layout.setSpacing(5) self.layout.setSpacing(5)
self.layout.setContentsMargins(5, 5, 5, 5) self.layout.setContentsMargins(5, 5, 5, 5)
self._area = area
self.expurl = expurl self.expurl = expurl
self.worker_handlers = worker_handlers
self.arguments = arguments
self.argeditor = _ArgumentEditor(self) self.argeditor = _ArgumentEditor(self)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5) self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
@ -218,23 +214,20 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.layout.addWidget(reqterm, 3, 4) self.layout.addWidget(reqterm, 3, 4)
reqterm.clicked.connect(self.reqterm_clicked) reqterm.clicked.connect(self.reqterm_clicked)
def submit_clicked(self): async def _recompute_arguments(self, overrides={}):
try: try:
pass # TODO arginfo = await self._area.compute_arginfo(self.expurl)
except: except:
# May happen when experiment has been removed logger.error("Could not recompute arguments of '%s'",
# from repository/explist
logger.error("Failed to submit '%s'",
self.expurl, exc_info=True) 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): self.argeditor.deleteLater()
try: self.argeditor = _ArgumentEditor(self)
pass # TODO self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
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)
async def _load_hdf5_task(self, filename): async def _load_hdf5_task(self, filename):
try: try:
@ -255,7 +248,25 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
exc_info=True) exc_info=True)
return 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): def closeEvent(self, event):
self.sigClosed.emit() self.sigClosed.emit()
@ -265,19 +276,17 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
return { return {
"argeditor": self.argeditor.save_state(), "argeditor": self.argeditor.save_state(),
"geometry": bytes(self.saveGeometry()), "geometry": bytes(self.saveGeometry()),
"expurl": self.expurl,
"options": self.options, "options": self.options,
} }
def restore_state(self, state): def restore_state(self, state):
self.argeditor.restore_state(state["argeditor"]) self.argeditor.restore_state(state["argeditor"])
self.restoreGeometry(QtCore.QByteArray(state["geometry"])) self.restoreGeometry(QtCore.QByteArray(state["geometry"]))
self.expurl = state["expurl"]
self.options = state["options"] self.options = state["options"]
class ExperimentsArea(QtWidgets.QMdiArea): class ExperimentsArea(QtWidgets.QMdiArea):
def __init__(self, root): def __init__(self, root, datasets_sub):
QtWidgets.QMdiArea.__init__(self) QtWidgets.QMdiArea.__init__(self)
self.pixmap = QtGui.QPixmap(os.path.join( self.pixmap = QtGui.QPixmap(os.path.join(
artiq_dir, "gui", "logo20.svg")) artiq_dir, "gui", "logo20.svg"))
@ -288,11 +297,17 @@ class ExperimentsArea(QtWidgets.QMdiArea):
action = QtWidgets.QAction("&Open experiment", self) action = QtWidgets.QAction("&Open experiment", self)
action.setShortcut(QtGui.QKeySequence("CTRL+o")) action.setShortcut(QtGui.QKeySequence("CTRL+o"))
action.setShortcutContext(QtCore.Qt.WidgetShortcut) action.setShortcutContext(QtCore.Qt.WidgetShortcut)
action.triggered.connect(self.open_experiment) action.triggered.connect(self._select_experiment)
self.addAction(action) self.addAction(action)
self.open_experiments = [] 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): def paintEvent(self, event):
QtWidgets.QMdiArea.paintEvent(self, event) QtWidgets.QMdiArea.paintEvent(self, event)
painter = QtGui.QPainter(self.viewport()) painter = QtGui.QPainter(self.viewport())
@ -302,38 +317,77 @@ class ExperimentsArea(QtWidgets.QMdiArea):
painter.drawPixmap(x, y, self.pixmap) painter.drawPixmap(x, y, self.pixmap)
def save_state(self): def save_state(self):
return {"experiments": [experiment.save_state() return {"experiments": [{
for experiment in self.open_experiments]} "expurl": dock.expurl,
"arguments": dock.arguments,
"dock": dock.save_state(),
} for dock in self.open_experiments]}
def restore_state(self, state): def restore_state(self, state):
if self.open_experiments: if self.open_experiments:
raise NotImplementedError raise NotImplementedError
for ex_state in state["experiments"]: for ex_state in state["experiments"]:
ex = self.load_experiment(ex_state["expurl"]) dock = self.open_experiment(ex_state["expurl"],
ex.restore_state(ex_state) ex_state["arguments"])
dock.restore_state(ex_state["dock"])
def open_experiment(self): def _select_experiment(self):
file, filter = QtWidgets.QFileDialog.getOpenFileName( asyncio.ensure_future(self._select_experiment_task())
self, "Open experiment", self.current_dir, "Experiments (*.py)")
if not file:
return
logger.info("opening experiment %s", file)
self.load_experiment(file)
def load_experiment(self, expurl): async def _select_experiment_task(self):
try: 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: except:
logger.warning("Failed to create experiment dock for %s, " logger.warning("Failed to create experiment dock for %s, "
"attempting to reset arguments", expurl, "retrying with arguments reset", expurl,
exc_info=True) exc_info=True)
del self.submission_arguments[expurl] dock = _ExperimentDock(self, expurl, {}, self.worker_handlers)
dock = _ExperimentDock(expurl) asyncio.ensure_future(dock._recompute_arguments())
self.open_experiments.append(dock)
self.addSubWindow(dock) self.addSubWindow(dock)
dock.show() 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 return dock
def on_dock_closed(self, expurl): def on_dock_closed(self, dock):
del self.open_experiments[expurl] self.open_experiments.remove(dock)

View File

@ -85,7 +85,7 @@ def main():
status_bar = QtWidgets.QStatusBar() status_bar = QtWidgets.QStatusBar()
main_window.setStatusBar(status_bar) 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.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
main_window.setCentralWidget(mdi_area) main_window.setCentralWidget(mdi_area)