forked from M-Labs/artiq
gui/experiments: support scan widgets
This commit is contained in:
parent
26630ea4b6
commit
23a84500a3
|
@ -16,27 +16,35 @@ logger = logging.getLogger(__name__)
|
||||||
class _StringEntry(QtGui.QLineEdit):
|
class _StringEntry(QtGui.QLineEdit):
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
QtGui.QLineEdit.__init__(self)
|
QtGui.QLineEdit.__init__(self)
|
||||||
self.setText(argument["value"])
|
self.setText(argument["state"])
|
||||||
def update():
|
def update():
|
||||||
argument["value"] = self.text()
|
argument["state"] = self.text()
|
||||||
self.editingFinished.connect(update)
|
self.editingFinished.connect(update)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default(argdesc):
|
def state_to_value(state):
|
||||||
return ""
|
return state
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_state(procdesc):
|
||||||
|
return procdesc.get("default", "")
|
||||||
|
|
||||||
|
|
||||||
class _BooleanEntry(QtGui.QCheckBox):
|
class _BooleanEntry(QtGui.QCheckBox):
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
QtGui.QCheckBox.__init__(self)
|
QtGui.QCheckBox.__init__(self)
|
||||||
self.setChecked(argument["value"])
|
self.setChecked(argument["state"])
|
||||||
def update(checked):
|
def update(checked):
|
||||||
argument["value"] = checked
|
argument["state"] = checked
|
||||||
self.stateChanged.connect(update)
|
self.stateChanged.connect(update)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default(argdesc):
|
def state_to_value(state):
|
||||||
return False
|
return state
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_state(procdesc):
|
||||||
|
return procdesc.get("default", False)
|
||||||
|
|
||||||
|
|
||||||
class _EnumerationEntry(QtGui.QComboBox):
|
class _EnumerationEntry(QtGui.QComboBox):
|
||||||
|
@ -44,43 +52,57 @@ class _EnumerationEntry(QtGui.QComboBox):
|
||||||
QtGui.QComboBox.__init__(self)
|
QtGui.QComboBox.__init__(self)
|
||||||
choices = argument["desc"]["choices"]
|
choices = argument["desc"]["choices"]
|
||||||
self.addItems(choices)
|
self.addItems(choices)
|
||||||
idx = choices.index(argument["value"])
|
idx = choices.index(argument["state"])
|
||||||
self.setCurrentIndex(idx)
|
self.setCurrentIndex(idx)
|
||||||
def update(index):
|
def update(index):
|
||||||
argument["value"] = choices[index]
|
argument["state"] = choices[index]
|
||||||
self.currentIndexChanged.connect(update)
|
self.currentIndexChanged.connect(update)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default(argdesc):
|
def state_to_value(state):
|
||||||
return argdesc["choices"][0]
|
return state
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_state(procdesc):
|
||||||
|
if "default" in procdesc:
|
||||||
|
return procdesc["default"]
|
||||||
|
else:
|
||||||
|
return procdesc["choices"][0]
|
||||||
|
|
||||||
|
|
||||||
class _NumberEntry(QtGui.QDoubleSpinBox):
|
class _NumberEntry(QtGui.QDoubleSpinBox):
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
QtGui.QDoubleSpinBox.__init__(self)
|
QtGui.QDoubleSpinBox.__init__(self)
|
||||||
argdesc = argument["desc"]
|
procdesc = argument["desc"]
|
||||||
scale = argdesc["scale"]
|
scale = procdesc["scale"]
|
||||||
self.setDecimals(argdesc["ndecimals"])
|
self.setDecimals(procdesc["ndecimals"])
|
||||||
self.setSingleStep(argdesc["step"]/scale)
|
self.setSingleStep(procdesc["step"]/scale)
|
||||||
if argdesc["min"] is not None:
|
if procdesc["min"] is not None:
|
||||||
self.setMinimum(argdesc["min"]/scale)
|
self.setMinimum(procdesc["min"]/scale)
|
||||||
else:
|
else:
|
||||||
self.setMinimum(float("-inf"))
|
self.setMinimum(float("-inf"))
|
||||||
if argdesc["max"] is not None:
|
if procdesc["max"] is not None:
|
||||||
self.setMaximum(argdesc["max"]/scale)
|
self.setMaximum(procdesc["max"]/scale)
|
||||||
else:
|
else:
|
||||||
self.setMaximum(float("inf"))
|
self.setMaximum(float("inf"))
|
||||||
if argdesc["unit"]:
|
if procdesc["unit"]:
|
||||||
self.setSuffix(" " + argdesc["unit"])
|
self.setSuffix(" " + procdesc["unit"])
|
||||||
|
|
||||||
self.setValue(argument["value"]/scale)
|
self.setValue(argument["state"]/scale)
|
||||||
def update(value):
|
def update(value):
|
||||||
argument["value"] = value*scale
|
argument["state"] = value*scale
|
||||||
self.valueChanged.connect(update)
|
self.valueChanged.connect(update)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default(argdesc):
|
def state_to_value(state):
|
||||||
return 0.0
|
return state
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_state(procdesc):
|
||||||
|
if "default" in procdesc:
|
||||||
|
return procdesc["default"]
|
||||||
|
else:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
_argty_to_entry = {
|
_argty_to_entry = {
|
||||||
|
@ -254,16 +276,11 @@ class ExperimentManager:
|
||||||
arguments = OrderedDict()
|
arguments = OrderedDict()
|
||||||
arginfo = self.explist[expname]["arguments"]
|
arginfo = self.explist[expname]["arguments"]
|
||||||
for name, (procdesc, group) in arginfo:
|
for name, (procdesc, group) in arginfo:
|
||||||
argdesc = dict(procdesc)
|
state = _argty_to_entry[procdesc["ty"]].default_state(procdesc)
|
||||||
if "default" in argdesc:
|
|
||||||
value = argdesc["default"]
|
|
||||||
del argdesc["default"]
|
|
||||||
else:
|
|
||||||
value = _argty_to_entry[argdesc["ty"]].default(argdesc)
|
|
||||||
arguments[name] = {
|
arguments[name] = {
|
||||||
"desc": argdesc,
|
"desc": procdesc,
|
||||||
"group": group,
|
"group": group,
|
||||||
"value": value # mutated by entries
|
"state": state # mutated by entries
|
||||||
}
|
}
|
||||||
self.submission_arguments[expname] = arguments
|
self.submission_arguments[expname] = arguments
|
||||||
return arguments
|
return arguments
|
||||||
|
@ -289,7 +306,11 @@ class ExperimentManager:
|
||||||
scheduling = self.get_submission_scheduling(expname)
|
scheduling = self.get_submission_scheduling(expname)
|
||||||
options = self.get_submission_options(expname)
|
options = self.get_submission_options(expname)
|
||||||
arguments = self.get_submission_arguments(expname)
|
arguments = self.get_submission_arguments(expname)
|
||||||
argument_values = {k: v["value"] for k, v in arguments.items()}
|
|
||||||
|
argument_values = dict()
|
||||||
|
for name, argument in arguments.items():
|
||||||
|
entry_cls = _argty_to_entry[argument["desc"]["ty"]]
|
||||||
|
argument_values[name] = entry_cls.state_to_value(argument["state"])
|
||||||
|
|
||||||
expid = {
|
expid = {
|
||||||
"log_level": options["log_level"],
|
"log_level": options["log_level"],
|
||||||
|
|
|
@ -1,26 +1,60 @@
|
||||||
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from quamash import QtGui
|
from quamash import QtGui
|
||||||
from pyqtgraph import LayoutWidget
|
from pyqtgraph import LayoutWidget
|
||||||
|
|
||||||
|
|
||||||
class _Range(LayoutWidget):
|
logger = logging.getLogger(__name__)
|
||||||
def __init__(self, global_min, global_max, global_step, unit, scale, ndecimals):
|
|
||||||
|
|
||||||
|
class _NoScan(LayoutWidget):
|
||||||
|
def __init__(self, procdesc, state):
|
||||||
LayoutWidget.__init__(self)
|
LayoutWidget.__init__(self)
|
||||||
|
|
||||||
self.scale = scale
|
scale = procdesc["scale"]
|
||||||
|
self.value = QtGui.QDoubleSpinBox()
|
||||||
|
self.value.setDecimals(procdesc["ndecimals"])
|
||||||
|
if procdesc["global_min"] is not None:
|
||||||
|
self.value.setMinimum(procdesc["global_min"]/scale)
|
||||||
|
else:
|
||||||
|
self.value.setMinimum(float("-inf"))
|
||||||
|
if procdesc["global_max"] is not None:
|
||||||
|
self.value.setMaximum(procdesc["global_max"]/scale)
|
||||||
|
else:
|
||||||
|
self.value.setMaximum(float("inf"))
|
||||||
|
self.value.setSingleStep(procdesc["global_step"]/scale)
|
||||||
|
if procdesc["unit"]:
|
||||||
|
self.value.setSuffix(" " + procdesc["unit"])
|
||||||
|
self.addWidget(QtGui.QLabel("Value:"), 0, 0)
|
||||||
|
self.addWidget(self.value, 0, 1)
|
||||||
|
|
||||||
|
self.value.setValue(state["value"]/scale)
|
||||||
|
def update(value):
|
||||||
|
state["value"] = value*scale
|
||||||
|
self.value.valueChanged.connect(update)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: prevent max < min
|
||||||
|
class _Range(LayoutWidget):
|
||||||
|
def __init__(self, procdesc, state):
|
||||||
|
LayoutWidget.__init__(self)
|
||||||
|
|
||||||
|
scale = procdesc["scale"]
|
||||||
def apply_properties(spinbox):
|
def apply_properties(spinbox):
|
||||||
spinbox.setDecimals(ndecimals)
|
spinbox.setDecimals(procdesc["ndecimals"])
|
||||||
if global_min is not None:
|
if procdesc["global_min"] is not None:
|
||||||
spinbox.setMinimum(global_min/self.scale)
|
spinbox.setMinimum(procdesc["global_min"]/scale)
|
||||||
else:
|
else:
|
||||||
spinbox.setMinimum(float("-inf"))
|
spinbox.setMinimum(float("-inf"))
|
||||||
if global_max is not None:
|
if procdesc["global_max"] is not None:
|
||||||
spinbox.setMaximum(global_max/self.scale)
|
spinbox.setMaximum(procdesc["global_max"]/scale)
|
||||||
else:
|
else:
|
||||||
spinbox.setMaximum(float("inf"))
|
spinbox.setMaximum(float("inf"))
|
||||||
if global_step is not None:
|
if procdesc["global_step"] is not None:
|
||||||
spinbox.setSingleStep(global_step/self.scale)
|
spinbox.setSingleStep(procdesc["global_step"]/scale)
|
||||||
if unit:
|
if procdesc["unit"]:
|
||||||
spinbox.setSuffix(" " + unit)
|
spinbox.setSuffix(" " + procdesc["unit"])
|
||||||
|
|
||||||
self.addWidget(QtGui.QLabel("Min:"), 0, 0)
|
self.addWidget(QtGui.QLabel("Min:"), 0, 0)
|
||||||
self.min = QtGui.QDoubleSpinBox()
|
self.min = QtGui.QDoubleSpinBox()
|
||||||
|
@ -38,121 +72,104 @@ class _Range(LayoutWidget):
|
||||||
self.npoints.setValue(10)
|
self.npoints.setValue(10)
|
||||||
self.addWidget(self.npoints, 2, 1)
|
self.addWidget(self.npoints, 2, 1)
|
||||||
|
|
||||||
def set_values(self, min, max, npoints):
|
self.min.setValue(state["min"]/scale)
|
||||||
self.min.setValue(min/self.scale)
|
self.max.setValue(state["max"]/scale)
|
||||||
self.max.setValue(max/self.scale)
|
self.npoints.setValue(state["npoints"])
|
||||||
self.npoints.setValue(npoints)
|
def update_min(value):
|
||||||
|
state["min"] = value*scale
|
||||||
|
def update_max(value):
|
||||||
|
state["min"] = value*scale
|
||||||
|
def update_npoints(value):
|
||||||
|
state["npoints"] = value
|
||||||
|
self.min.valueChanged.connect(update_min)
|
||||||
|
self.max.valueChanged.connect(update_max)
|
||||||
|
self.npoints.valueChanged.connect(update_npoints)
|
||||||
|
|
||||||
def get_values(self):
|
|
||||||
min = self.min.value()
|
# TODO: use QRegExpValidator to prevent invalid input
|
||||||
max = self.max.value()
|
class _Explicit(LayoutWidget):
|
||||||
if min > max:
|
def __init__(self, state):
|
||||||
raise ValueError("Minimum scan boundary must be less than maximum")
|
LayoutWidget.__init__(self)
|
||||||
return {
|
|
||||||
"min": min*self.scale,
|
self.value = QtGui.QLineEdit()
|
||||||
"max": max*self.scale,
|
self.addWidget(QtGui.QLabel("Sequence:"), 0, 0)
|
||||||
"npoints": self.npoints.value()
|
self.addWidget(self.value, 0, 1)
|
||||||
}
|
|
||||||
|
self.value.setText(" ".join([str(x) for x in state["sequence"]]))
|
||||||
|
def update():
|
||||||
|
state["sequence"] = [float(x) for x in self.value.text().split()]
|
||||||
|
self.value.editingFinished.connect(update)
|
||||||
|
|
||||||
|
|
||||||
class ScanController(LayoutWidget):
|
class ScanController(LayoutWidget):
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
LayoutWidget.__init__(self)
|
LayoutWidget.__init__(self)
|
||||||
|
self.argument = argument
|
||||||
|
|
||||||
self.stack = QtGui.QStackedWidget()
|
self.stack = QtGui.QStackedWidget()
|
||||||
self.addWidget(self.stack, 1, 0, colspan=4)
|
self.addWidget(self.stack, 1, 0, colspan=4)
|
||||||
|
|
||||||
argdesc = argument["desc"]
|
procdesc = argument["desc"]
|
||||||
self.scale = argdesc["scale"]
|
state = argument["state"]
|
||||||
|
self.widgets = OrderedDict()
|
||||||
|
self.widgets["NoScan"] = _NoScan(procdesc, state["NoScan"])
|
||||||
|
self.widgets["LinearScan"] = _Range(procdesc, state["LinearScan"])
|
||||||
|
self.widgets["RandomScan"] = _Range(procdesc, state["RandomScan"])
|
||||||
|
self.widgets["ExplicitScan"] = _Explicit(state["ExplicitScan"])
|
||||||
|
for widget in self.widgets.values():
|
||||||
|
self.stack.addWidget(widget)
|
||||||
|
|
||||||
gmin, gmax = argdesc["global_min"], argdesc["global_max"]
|
self.radiobuttons = OrderedDict()
|
||||||
gstep = argdesc["global_step"]
|
self.radiobuttons["NoScan"] = QtGui.QRadioButton("No scan")
|
||||||
unit = argdesc["unit"]
|
self.radiobuttons["LinearScan"] = QtGui.QRadioButton("Linear")
|
||||||
ndecimals = argdesc["ndecimals"]
|
self.radiobuttons["RandomScan"] = QtGui.QRadioButton("Random")
|
||||||
|
self.radiobuttons["ExplicitScan"] = QtGui.QRadioButton("Explicit")
|
||||||
self.v_noscan = QtGui.QDoubleSpinBox()
|
scan_type = QtGui.QButtonGroup()
|
||||||
self.v_noscan.setDecimals(ndecimals)
|
for n, b in enumerate(self.radiobuttons.values()):
|
||||||
if gmin is not None:
|
|
||||||
self.v_noscan.setMinimum(gmin/self.scale)
|
|
||||||
else:
|
|
||||||
self.v_noscan.setMinimum(float("-inf"))
|
|
||||||
if gmax is not None:
|
|
||||||
self.v_noscan.setMaximum(gmax/self.scale)
|
|
||||||
else:
|
|
||||||
self.v_noscan.setMaximum(float("inf"))
|
|
||||||
self.v_noscan.setSingleStep(gstep/self.scale)
|
|
||||||
if unit:
|
|
||||||
self.v_noscan.setSuffix(" " + unit)
|
|
||||||
self.v_noscan_gr = LayoutWidget()
|
|
||||||
self.v_noscan_gr.addWidget(QtGui.QLabel("Value:"), 0, 0)
|
|
||||||
self.v_noscan_gr.addWidget(self.v_noscan, 0, 1)
|
|
||||||
self.stack.addWidget(self.v_noscan_gr)
|
|
||||||
|
|
||||||
self.v_linear = _Range(gmin, gmax, gstep, unit, self.scale, ndecimals)
|
|
||||||
self.stack.addWidget(self.v_linear)
|
|
||||||
|
|
||||||
self.v_random = _Range(gmin, gmax, gstep, unit, self.scale, ndecimals)
|
|
||||||
self.stack.addWidget(self.v_random)
|
|
||||||
|
|
||||||
self.v_explicit = QtGui.QLineEdit()
|
|
||||||
self.v_explicit_gr = LayoutWidget()
|
|
||||||
self.v_explicit_gr.addWidget(QtGui.QLabel("Sequence:"), 0, 0)
|
|
||||||
self.v_explicit_gr.addWidget(self.v_explicit, 0, 1)
|
|
||||||
self.stack.addWidget(self.v_explicit_gr)
|
|
||||||
|
|
||||||
self.noscan = QtGui.QRadioButton("No scan")
|
|
||||||
self.linear = QtGui.QRadioButton("Linear")
|
|
||||||
self.random = QtGui.QRadioButton("Random")
|
|
||||||
self.explicit = QtGui.QRadioButton("Explicit")
|
|
||||||
radiobuttons = QtGui.QButtonGroup()
|
|
||||||
for n, b in enumerate([self.noscan, self.linear,
|
|
||||||
self.random, self.explicit]):
|
|
||||||
self.addWidget(b, 0, n)
|
self.addWidget(b, 0, n)
|
||||||
radiobuttons.addButton(b)
|
scan_type.addButton(b)
|
||||||
b.toggled.connect(self.select_page)
|
b.toggled.connect(self._scan_type_toggled)
|
||||||
|
|
||||||
|
selected = argument["state"]["selected"]
|
||||||
|
self.radiobuttons[selected].setChecked(True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default(argdesc):
|
def state_to_value(state):
|
||||||
return {"ty": "NoScan", "value": 0.0}
|
selected = state["selected"]
|
||||||
|
r = dict(state[selected])
|
||||||
|
r["ty"] = selected
|
||||||
|
return r
|
||||||
|
|
||||||
def select_page(self):
|
@staticmethod
|
||||||
if self.noscan.isChecked():
|
def default_state(procdesc):
|
||||||
self.stack.setCurrentWidget(self.v_noscan_gr)
|
scale = procdesc["scale"]
|
||||||
elif self.linear.isChecked():
|
state = {
|
||||||
self.stack.setCurrentWidget(self.v_linear)
|
"selected": "NoScan",
|
||||||
elif self.random.isChecked():
|
"NoScan": {"value": 0.0},
|
||||||
self.stack.setCurrentWidget(self.v_random)
|
"LinearScan": {"min": 0.0, "max": 100.0*scale, "npoints": 10},
|
||||||
elif self.explicit.isChecked():
|
"RandomScan": {"min": 0.0, "max": 100.0*scale, "npoints": 10},
|
||||||
self.stack.setCurrentWidget(self.v_explicit_gr)
|
"ExplicitScan": {"sequence": []}
|
||||||
|
}
|
||||||
|
if "default" in procdesc:
|
||||||
|
default = procdesc["default"]
|
||||||
|
ty = default["ty"]
|
||||||
|
state["selected"] = ty
|
||||||
|
if ty == "NoScan":
|
||||||
|
state["NoScan"]["value"] = default["value"]
|
||||||
|
elif ty == "LinearScan" or ty == "RandomScan":
|
||||||
|
for d in state["LinearScan"], state["RandomScan"]:
|
||||||
|
d["min"] = default["min"]
|
||||||
|
d["max"] = default["max"]
|
||||||
|
d["npoints"] = default["npoints"]
|
||||||
|
elif ty == "ExplicitScan":
|
||||||
|
state["ExplicitScan"]["sequence"] = default["sequence"]
|
||||||
|
else:
|
||||||
|
logger.warning("unknown default type: %s", ty)
|
||||||
|
return state
|
||||||
|
|
||||||
def get_argument_value(self):
|
def _scan_type_toggled(self):
|
||||||
if self.noscan.isChecked():
|
for ty, button in self.radiobuttons.items():
|
||||||
return {"ty": "NoScan", "value": self.v_noscan.value()*self.scale}
|
if button.isChecked():
|
||||||
elif self.linear.isChecked():
|
self.stack.setCurrentWidget(self.widgets[ty])
|
||||||
d = {"ty": "LinearScan"}
|
self.argument["state"]["selected"] = ty
|
||||||
d.update(self.v_linear.get_values())
|
break
|
||||||
return d
|
|
||||||
elif self.random.isChecked():
|
|
||||||
d = {"ty": "RandomScan"}
|
|
||||||
d.update(self.v_random.get_values())
|
|
||||||
return d
|
|
||||||
elif self.explicit.isChecked():
|
|
||||||
sequence = [float(x) for x in self.v_explicit.text().split()]
|
|
||||||
return {"ty": "ExplicitScan", "sequence": sequence}
|
|
||||||
|
|
||||||
def set_argument_value(self, d):
|
|
||||||
if d["ty"] == "NoScan":
|
|
||||||
self.noscan.setChecked(True)
|
|
||||||
self.v_noscan.setValue(d["value"]/self.scale)
|
|
||||||
elif d["ty"] == "LinearScan":
|
|
||||||
self.linear.setChecked(True)
|
|
||||||
self.v_linear.set_values(d["min"], d["max"], d["npoints"])
|
|
||||||
elif d["ty"] == "RandomScan":
|
|
||||||
self.random.setChecked(True)
|
|
||||||
self.v_random.set_values(d["min"], d["max"], d["npoints"])
|
|
||||||
elif d["ty"] == "ExplicitScan":
|
|
||||||
self.explicit.setChecked(True)
|
|
||||||
self.v_explicit.insert(" ".join(
|
|
||||||
[str(x) for x in d["sequence"]]))
|
|
||||||
else:
|
|
||||||
raise ValueError("Unknown scan type '{}'".format(d["ty"]))
|
|
||||||
|
|
Loading…
Reference in New Issue