forked from M-Labs/artiq
browser: support opening experiments
This commit is contained in:
parent
5332c198c2
commit
4016e5adaa
|
@ -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():
|
||||||
|
try:
|
||||||
entry = argty_to_entry[argument["desc"]["ty"]](argument)
|
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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue