mirror of https://github.com/m-labs/artiq.git
dashboard: support reloading arguments from HDF5
This commit is contained in:
parent
c50555e11c
commit
597d7c389e
|
@ -38,6 +38,8 @@ unreleased [2.x]
|
||||||
current directory.
|
current directory.
|
||||||
* The ``parent`` keyword argument of ``HasEnvironment`` (and ``EnvExperiment``)
|
* The ``parent`` keyword argument of ``HasEnvironment`` (and ``EnvExperiment``)
|
||||||
has been replaced. Pass the parent as first argument instead.
|
has been replaced. Pass the parent as first argument instead.
|
||||||
|
* In the dashboard's experiment windows, partial or full argument recomputation
|
||||||
|
takes into account the repository revision field.
|
||||||
|
|
||||||
|
|
||||||
1.0rc4 (unreleased)
|
1.0rc4 (unreleased)
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
import h5py
|
||||||
|
|
||||||
from artiq.gui.tools import LayoutWidget, log_level_to_name
|
from artiq.gui.tools import LayoutWidget, log_level_to_name
|
||||||
from artiq.gui.entries import argty_to_entry
|
from artiq.gui.entries import argty_to_entry
|
||||||
|
from artiq.protocols import pyon
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -83,12 +86,21 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||||
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
|
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
|
||||||
recompute_arguments.setIcon(QtWidgets.QApplication.style().standardIcon(
|
recompute_arguments.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
QtWidgets.QStyle.SP_BrowserReload))
|
QtWidgets.QStyle.SP_BrowserReload))
|
||||||
recompute_arguments.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
|
|
||||||
QtWidgets.QSizePolicy.Maximum)
|
|
||||||
recompute_arguments.clicked.connect(dock._recompute_arguments_clicked)
|
recompute_arguments.clicked.connect(dock._recompute_arguments_clicked)
|
||||||
fix_layout = LayoutWidget()
|
|
||||||
fix_layout.addWidget(recompute_arguments)
|
load_hdf5 = QtWidgets.QPushButton("Load HDF5")
|
||||||
self.setItemWidget(widget_item, 1, fix_layout)
|
load_hdf5.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_DialogOpenButton))
|
||||||
|
load_hdf5.clicked.connect(dock._load_hdf5_clicked)
|
||||||
|
|
||||||
|
buttons = LayoutWidget()
|
||||||
|
buttons.addWidget(recompute_arguments, 1, 1)
|
||||||
|
buttons.addWidget(load_hdf5, 1, 2)
|
||||||
|
buttons.layout.setColumnStretch(0, 1)
|
||||||
|
buttons.layout.setColumnStretch(1, 0)
|
||||||
|
buttons.layout.setColumnStretch(2, 0)
|
||||||
|
buttons.layout.setColumnStretch(3, 1)
|
||||||
|
self.setItemWidget(widget_item, 1, buttons)
|
||||||
|
|
||||||
def _get_group(self, name):
|
def _get_group(self, name):
|
||||||
if name in self._groups:
|
if name in self._groups:
|
||||||
|
@ -143,6 +155,9 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||||
|
|
||||||
|
|
||||||
class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
sigClosed = QtCore.pyqtSignal()
|
sigClosed = QtCore.pyqtSignal()
|
||||||
|
|
||||||
|
@ -222,7 +237,6 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
flush.stateChanged.connect(update_flush)
|
flush.stateChanged.connect(update_flush)
|
||||||
|
|
||||||
log_level = QtWidgets.QComboBox()
|
log_level = QtWidgets.QComboBox()
|
||||||
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
||||||
log_level.addItems(log_levels)
|
log_level.addItems(log_levels)
|
||||||
log_level.setCurrentIndex(1)
|
log_level.setCurrentIndex(1)
|
||||||
log_level.setToolTip("Minimum level for log entry production")
|
log_level.setToolTip("Minimum level for log entry production")
|
||||||
|
@ -236,6 +250,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
def update_log_level(index):
|
def update_log_level(index):
|
||||||
options["log_level"] = getattr(logging, log_level.currentText())
|
options["log_level"] = getattr(logging, log_level.currentText())
|
||||||
log_level.currentIndexChanged.connect(update_log_level)
|
log_level.currentIndexChanged.connect(update_log_level)
|
||||||
|
self.log_level = log_level
|
||||||
|
|
||||||
if "repo_rev" in options:
|
if "repo_rev" in options:
|
||||||
repo_rev = QtWidgets.QLineEdit()
|
repo_rev = QtWidgets.QLineEdit()
|
||||||
|
@ -253,7 +268,8 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
options["repo_rev"] = text
|
options["repo_rev"] = text
|
||||||
else:
|
else:
|
||||||
options["repo_rev"] = None
|
options["repo_rev"] = None
|
||||||
repo_rev.textEdited.connect(update_repo_rev)
|
repo_rev.textChanged.connect(update_repo_rev)
|
||||||
|
self.repo_rev = repo_rev
|
||||||
|
|
||||||
submit = QtWidgets.QPushButton("Submit")
|
submit = QtWidgets.QPushButton("Submit")
|
||||||
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
@ -275,6 +291,8 @@ 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)
|
||||||
|
|
||||||
|
self.hdf5_load_directory = os.path.expanduser("~")
|
||||||
|
|
||||||
def submit_clicked(self):
|
def submit_clicked(self):
|
||||||
try:
|
try:
|
||||||
self.manager.submit(self.expurl)
|
self.manager.submit(self.expurl)
|
||||||
|
@ -296,18 +314,59 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
def _recompute_arguments_clicked(self):
|
def _recompute_arguments_clicked(self):
|
||||||
asyncio.ensure_future(self._recompute_arguments_task())
|
asyncio.ensure_future(self._recompute_arguments_task())
|
||||||
|
|
||||||
async def _recompute_arguments_task(self):
|
async def _recompute_arguments_task(self, overrides=dict()):
|
||||||
try:
|
try:
|
||||||
arginfo = await self.manager.compute_arginfo(self.expurl)
|
arginfo = await self.manager.compute_arginfo(self.expurl)
|
||||||
except:
|
except:
|
||||||
logger.error("Could not recompute arguments of '%s'",
|
logger.error("Could not recompute arguments of '%s'",
|
||||||
self.expurl, exc_info=True)
|
self.expurl, exc_info=True)
|
||||||
|
return
|
||||||
|
for k, v in overrides.items():
|
||||||
|
arginfo[k][0]["default"] = v
|
||||||
self.manager.initialize_submission_arguments(self.expurl, arginfo)
|
self.manager.initialize_submission_arguments(self.expurl, arginfo)
|
||||||
|
|
||||||
self.argeditor.deleteLater()
|
self.argeditor.deleteLater()
|
||||||
self.argeditor = _ArgumentEditor(self.manager, self, self.expurl)
|
self.argeditor = _ArgumentEditor(self.manager, self, self.expurl)
|
||||||
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
||||||
|
|
||||||
|
def _load_hdf5_clicked(self):
|
||||||
|
dialog = QtWidgets.QFileDialog(self.manager.main_window,
|
||||||
|
"Load HDF5", self.hdf5_load_directory,
|
||||||
|
"HDF5 files (*.h5 *.hdf5);;All files (*.*)")
|
||||||
|
dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
|
||||||
|
def on_accept():
|
||||||
|
filename = dialog.selectedFiles()[0]
|
||||||
|
self.hdf5_load_directory = os.path.dirname(filename)
|
||||||
|
asyncio.ensure_future(self._load_hdf5_task(filename))
|
||||||
|
dialog.accepted.connect(on_accept)
|
||||||
|
dialog.open()
|
||||||
|
|
||||||
|
async def _load_hdf5_task(self, filename):
|
||||||
|
try:
|
||||||
|
with h5py.File(filename, "r") as f:
|
||||||
|
expid = f["expid"][()]
|
||||||
|
expid = pyon.decode(expid)
|
||||||
|
arguments = expid["arguments"]
|
||||||
|
except:
|
||||||
|
logger.error("Could not retrieve expid from HDF5 file",
|
||||||
|
exc_info=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.log_level.setCurrentIndex(log_levels.index(
|
||||||
|
log_level_to_name(expid["log_level"])))
|
||||||
|
if ("repo_rev" in expid
|
||||||
|
and expid["repo_rev"] != "N/A"
|
||||||
|
and hasattr(self, "repo_rev")):
|
||||||
|
self.repo_rev.setText(expid["repo_rev"])
|
||||||
|
except:
|
||||||
|
logger.error("Could not set submission options from HDF5 expid",
|
||||||
|
exc_info=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._recompute_arguments_task(arguments)
|
||||||
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self.sigClosed.emit()
|
self.sigClosed.emit()
|
||||||
QtWidgets.QMdiSubWindow.closeEvent(self, event)
|
QtWidgets.QMdiSubWindow.closeEvent(self, event)
|
||||||
|
@ -315,12 +374,14 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
return {
|
return {
|
||||||
"args": self.argeditor.save_state(),
|
"args": self.argeditor.save_state(),
|
||||||
"geometry": bytes(self.saveGeometry())
|
"geometry": bytes(self.saveGeometry()),
|
||||||
|
"hdf5_load_directory": self.hdf5_load_directory
|
||||||
}
|
}
|
||||||
|
|
||||||
def restore_state(self, state):
|
def restore_state(self, state):
|
||||||
self.argeditor.restore_state(state["args"])
|
self.argeditor.restore_state(state["args"])
|
||||||
self.restoreGeometry(QtCore.QByteArray(state["geometry"]))
|
self.restoreGeometry(QtCore.QByteArray(state["geometry"]))
|
||||||
|
self.hdf5_load_directory = state["hdf5_load_directory"]
|
||||||
|
|
||||||
|
|
||||||
class ExperimentManager:
|
class ExperimentManager:
|
||||||
|
@ -490,8 +551,10 @@ class ExperimentManager:
|
||||||
|
|
||||||
async def compute_arginfo(self, expurl):
|
async def compute_arginfo(self, expurl):
|
||||||
file, class_name, use_repository = self.resolve_expurl(expurl)
|
file, class_name, use_repository = self.resolve_expurl(expurl)
|
||||||
description = await self.experiment_db_ctl.examine(file,
|
if use_repository:
|
||||||
use_repository)
|
revision = self.get_submission_options(expurl)["repo_rev"]
|
||||||
|
description = await self.experiment_db_ctl.examine(
|
||||||
|
file, use_repository, revision)
|
||||||
return description[class_name]["arginfo"]
|
return description[class_name]["arginfo"]
|
||||||
|
|
||||||
async def open_file(self, file):
|
async def open_file(self, file):
|
||||||
|
|
|
@ -120,8 +120,9 @@ class ExperimentDB:
|
||||||
asyncio.ensure_future(
|
asyncio.ensure_future(
|
||||||
exc_to_warning(self.scan_repository(new_cur_rev)))
|
exc_to_warning(self.scan_repository(new_cur_rev)))
|
||||||
|
|
||||||
async def examine(self, filename, use_repository=True):
|
async def examine(self, filename, use_repository=True, revision=None):
|
||||||
if use_repository:
|
if use_repository:
|
||||||
|
if revision is None:
|
||||||
revision = self.cur_rev
|
revision = self.cur_rev
|
||||||
wd, _ = self.repo_backend.request_rev(revision)
|
wd, _ = self.repo_backend.request_rev(revision)
|
||||||
filename = os.path.join(wd, filename)
|
filename = os.path.join(wd, filename)
|
||||||
|
|
Loading…
Reference in New Issue