wip widget

This commit is contained in:
x01da
2026-08-10 21:27:07 +02:00
parent ab84d7fb7a
commit 5d21216f63
3 changed files with 452 additions and 28 deletions
@@ -0,0 +1,339 @@
import sys
from typing import Literal, Optional
import xraydb
from bec_lib import bec_logger
from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.utils.colors import apply_theme, get_accent_colors
from bec_widgets.utils.error_popups import SafeSlot
from qtpy.QtCore import Signal
# pylint: disable=E0611
from qtpy.QtWidgets import (
QApplication,
QComboBox,
QDialog,
QGridLayout,
QHBoxLayout,
QLabel,
QPushButton,
QSizePolicy,
QVBoxLayout,
QWidget,
)
logger = bec_logger.logger
ELEMENTS = {
# period 1
"H": (1, 1),
"He": (1, 18),
# period 2
"Li": (2, 1),
"Be": (2, 2),
"B": (2, 13),
"C": (2, 14),
"N": (2, 15),
"O": (2, 16),
"F": (2, 17),
"Ne": (2, 18),
# period 3
"Na": (3, 1),
"Mg": (3, 2),
"Al": (3, 13),
"Si": (3, 14),
"P": (3, 15),
"S": (3, 16),
"Cl": (3, 17),
"Ar": (3, 18),
# period 4
"K": (4, 1),
"Ca": (4, 2),
"Sc": (4, 3),
"Ti": (4, 4),
"V": (4, 5),
"Cr": (4, 6),
"Mn": (4, 7),
"Fe": (4, 8),
"Co": (4, 9),
"Ni": (4, 10),
"Cu": (4, 11),
"Zn": (4, 12),
"Ga": (4, 13),
"Ge": (4, 14),
"As": (4, 15),
"Se": (4, 16),
"Br": (4, 17),
"Kr": (4, 18),
# period 5
"Rb": (5, 1),
"Sr": (5, 2),
"Y": (5, 3),
"Zr": (5, 4),
"Nb": (5, 5),
"Mo": (5, 6),
"Tc": (5, 7),
"Ru": (5, 8),
"Rh": (5, 9),
"Pd": (5, 10),
"Ag": (5, 11),
"Cd": (5, 12),
"In": (5, 13),
"Sn": (5, 14),
"Sb": (5, 15),
"Te": (5, 16),
"I": (5, 17),
"Xe": (5, 18),
# period 6
"Cs": (6, 1),
"Ba": (6, 2),
"La": (6, 3),
# lanthanides (row 8)
"Ce": (8, 4),
"Pr": (8, 5),
"Nd": (8, 6),
"Pm": (8, 7),
"Sm": (8, 8),
"Eu": (8, 9),
"Gd": (8, 10),
"Tb": (8, 11),
"Dy": (8, 12),
"Ho": (8, 13),
"Er": (8, 14),
"Tm": (8, 15),
"Yb": (8, 16),
"Lu": (8, 17),
# period 6 continued
"Hf": (6, 4),
"Ta": (6, 5),
"W": (6, 6),
"Re": (6, 7),
"Os": (6, 8),
"Ir": (6, 9),
"Pt": (6, 10),
"Au": (6, 11),
"Hg": (6, 12),
"Tl": (6, 13),
"Pb": (6, 14),
"Bi": (6, 15),
"Po": (6, 16),
"At": (6, 17),
"Rn": (6, 18),
# period 7
"Fr": (7, 1),
"Ra": (7, 2),
"Ac": (7, 3),
# actinides (row 9)
"Th": (9, 4),
"Pa": (9, 5),
"U": (9, 6),
"Np": (9, 7),
"Pu": (9, 8),
"Am": (9, 9),
"Cm": (9, 10),
"Bk": (9, 11),
"Cf": (9, 12),
}
class EdgeSelector(QDialog):
"""BEC widget to display a selection dialog where the user can select and edge and an element"""
def __init__(self, parent=None, llim: int | float = 0, hlim: int | float = 1e6):
super().__init__(parent)
self.setWindowTitle("Absorption Edge Selector")
self.buttons = {}
self.active_edge = None
self.active_element = None
self.active_energy = None
self.llim = llim
self.hlim = hlim
layout = QVBoxLayout(self)
text = QLabel("Select edge and element.")
edge_layout = QHBoxLayout(self)
edge_label = QLabel("Edge")
self.edge = QComboBox()
self.edge.addItems(["K", "L1", "L2", "L3"])
edge_layout.addWidget(edge_label)
edge_layout.addWidget(self.edge)
edge_layout.addStretch()
grid_layout = QGridLayout(self)
grid_layout.setContentsMargins(0, 0, 0, 0)
grid_layout.setSpacing(1)
for element, (row, col) in ELEMENTS.items():
button = QPushButton(element)
button.setFixedSize(42, 32)
button.setCheckable(True)
button.setAutoDefault(False)
button.setDefault(False)
button.clicked.connect(lambda checked=False, e=element: self._element_changed(e))
grid_layout.addWidget(button, row - 1, col - 1)
self.buttons[element] = button
res_layout = QHBoxLayout(self)
self.res = QLabel("")
res_layout.addStretch()
res_layout.addWidget(self.res)
res_layout.addStretch()
button_layout = QHBoxLayout()
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(self.reject)
self.cancel_button.setStyleSheet(
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
)
self.select_button = QPushButton("Select")
self.select_button.setEnabled(False)
self.select_button.clicked.connect(self.accept)
self.select_button.setStyleSheet(
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
)
button_layout.addStretch()
button_layout.addWidget(self.cancel_button)
button_layout.addWidget(self.select_button)
button_layout.addStretch()
layout.addWidget(text)
layout.addLayout(edge_layout)
layout.addLayout(grid_layout)
layout.addLayout(res_layout)
layout.addLayout(button_layout)
self.edge.activated.connect(self._edge_changed)
self._edge_changed()
@SafeSlot()
def _edge_changed(self, *_):
self.active_edge = self.edge.currentText()
for element, button in self.buttons.items():
# logger.info(f"{element}")
if "L" in self.active_edge and element in ["H", "He"]:
button.setDisabled(True)
button.setChecked(False)
elif self.active_edge in ["L2", "L3"] and element == "Li":
button.setDisabled(True)
button.setChecked(False)
else:
energy = xraydb.xray_edge(element, self.active_edge, True)
if self.active_element == element:
self.active_energy = energy
if energy is not None:
if energy < self.llim or energy > self.hlim:
button.setChecked(False)
button.setDisabled(True)
else:
button.setEnabled(True)
else:
button.setChecked(False)
button.setDisabled(True)
self._set_select_text()
@SafeSlot()
def _element_changed(self, element: str | None = None):
if element is None:
if self.active_element is None:
return
element = self.active_element
# Uncheck all other buttons so only the picked one stays highlighted
for e, btn in self.buttons.items():
if e != element:
btn.setChecked(False)
self.buttons[element].setChecked(True)
self.active_element = element
self.active_energy = xraydb.xray_edge(element, self.active_edge, True)
self.select_button.setEnabled(True)
self._set_select_text()
def _set_select_text(self):
if self.active_energy is not None:
if self.buttons[self.active_element].isChecked():
self._enable_button(self.select_button, True)
self.select_button.setText(
f"Selected {self.active_element}, {self.active_edge}-edge, {self.active_energy:0.1f} eV"
)
else:
self._enable_button(self.select_button, False)
self.select_button.setText("Select")
else:
self._enable_button(self.select_button, False)
@staticmethod
def _enable_button(button: QPushButton, enable: bool):
if enable:
button.setEnabled(True)
button.setStyleSheet(
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
)
else:
button.setDisabled(True)
button.setStyleSheet(
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
)
ROW_MARGINS = (4, 0, 4, 0)
ROW_SPACING = 6
class ElementSelector(QWidget):
dialogClosed = Signal()
def __init__(self):
super().__init__()
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
layout = QVBoxLayout(self)
layout.setContentsMargins(*ROW_MARGINS)
layout.setSpacing(ROW_SPACING)
self.label = QLabel("Element")
layout_selection = QHBoxLayout(self)
self.button = QPushButton("Select")
self.button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.button.setStyleSheet(
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
)
layout_selection.addWidget(self.button)
layout.addWidget(self.label)
layout.addLayout(layout_selection)
self.current_element = ""
self.button.clicked.connect(self.open_table)
def open_table(self):
dialog = EdgeSelector(self, llim=4500, hlim=60000)
if dialog.exec_():
# self.current_element = dialog.selected
self.dialogClosed.emit()
def currentElement(self):
return self.current_element
def apply_theme(self, theme: Optional[Literal["dark", "light"]] = None):
"""
Apply the theme
Args:
theme (Optional[str]): Theme, either "dark", "light", or None. Defaults to None.
"""
if theme is None:
app = QApplication.instance()
theme = app.theme.theme # type: ignore
self.button.setStyleSheet(
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
)
if __name__ == "__main__":
app = QApplication(sys.argv)
apply_theme("light")
dispatcher = BECDispatcher(gui_id="scan_control_advanced")
win = ElementSelector()
win.show()
sys.exit(app.exec_())
@@ -23,6 +23,40 @@ ROW_MARGINS = (4, 0, 4, 0)
ROW_SPACING = 6
class Button(QWidget):
def __init__(self, label=None, label_button: str = "", enabled=False):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(*ROW_MARGINS)
layout.setSpacing(ROW_SPACING)
if label is not None:
self.label = QLabel(label)
self.label.setFixedWidth(LABEL_WIDTH)
layout.addWidget(self.label)
self.button = QPushButton(label_button)
self.enable_button(enabled)
layout.addWidget(self.button)
def clicked_connect(self, func):
"""Connect a function to the button press."""
self.button.clicked.connect(func)
def enable_button(self, enable: bool = False):
if enable:
self.button.setStyleSheet(
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
)
self.button.setEnabled(True)
else: # disabled
self.button.setStyleSheet(
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
)
self.button.setDisabled(True)
def setText(self, text):
self.button.setText(text)
class ComboBox(QWidget):
def __init__(self, identifier="", label="", enums=None):
super().__init__()
@@ -11,7 +11,7 @@ import xraydb
from bec_lib import bec_logger
from bec_lib.endpoints import MessageEndpoints
from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.utils.colors import Colors, apply_theme
from bec_widgets.utils.colors import Colors, apply_theme, get_accent_colors
from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
from qtpy.QtCore import Qt
@@ -19,9 +19,12 @@ from qtpy.QtCore import Qt
# pylint: disable=E0611
from qtpy.QtWidgets import (
QApplication,
QCheckBox,
QFrame,
QGroupBox,
QHBoxLayout,
QLabel,
QPushButton,
QScrollArea,
QSizePolicy,
QVBoxLayout,
@@ -30,7 +33,8 @@ from qtpy.QtWidgets import (
# pylint: disable=E0402
from ....devices.mo1_bragg.mo1_bragg_utils import compute_spline
from .qt_widgets import ComboBox, ElementSelector, TextIndicator
from ..edge_selector import EdgeSelector
from .qt_widgets import Button, ComboBox, ElementSelector, TextIndicator
logger = bec_logger.logger
@@ -141,9 +145,26 @@ class MotionProfileWidget(QWidget):
outer_layout = QVBoxLayout(self)
outer_layout.setContentsMargins(0, 0, 0, 0)
self.element = ElementSelector()
self.edge = ComboBox("Edge", "Edge", ["K", "L1", "L2", "L3"])
self.edge_text = TextIndicator("", "Selected Edge", "No edge selected")
# self.element = ElementSelector()
# self.edge = ComboBox("Edge", "Edge", ["K", "L1", "L2", "L3"])
# self.edge_text = TextIndicator("", "Selected Edge", "No edge selected")
edge_selector_layout = QHBoxLayout(self)
edge_selector_label = QLabel("Absorption edge:")
self.edge_selector_button = QPushButton("Choose")
self.edge_label = QLabel("No edge selected")
edge_selector_layout.addWidget(edge_selector_label)
edge_selector_layout.addWidget(self.edge_label)
edge_selector_layout.addWidget(self.edge_selector_button)
edge_selector_layout.addStretch()
scan_rng_layout = QHBoxLayout(self)
self.scan_rng_ckbox = QCheckBox(self)
self.scan_rng_ckbox.setCheckState(Qt.CheckState.Checked)
scan_rng_label = QLabel("Auto adjust scan range to absorption edge")
scan_rng_layout.addWidget(self.scan_rng_ckbox)
scan_rng_layout.addWidget(scan_rng_label)
scan_rng_layout.addStretch()
plot_container = QWidget()
plot_layout = QHBoxLayout(plot_container)
@@ -169,20 +190,25 @@ class MotionProfileWidget(QWidget):
scroll.setWidget(plot_container)
self.edge_selection = QHBoxLayout()
self.edge_selection.addWidget(self.element)
self.edge_selection.addWidget(self.edge)
self.edge_selection.addWidget(self.edge_text)
# self.edge_selection.addWidget(self.element)
# self.edge_selection.addWidget(self.edge)
# self.edge_selection.addWidget(self.edge_text)
self.edge_selection.addLayout(edge_selector_layout)
self.edge_selection.addStretch(1)
outer_layout.addLayout(self.edge_selection)
outer_layout.addLayout(scan_rng_layout)
outer_layout.addWidget(scroll, stretch=1)
self.scan_name = None
self.scan_parameters = None
self.d_spacing = None
self.element.dialogClosed.connect(self._update_edge_selection)
self.edge.activated_connect(self._update_edge_selection)
self.edge_selector_button.clicked.connect(self._update_edge)
self.scan_rng_ckbox.stateChanged.connect(self._update_auto_scan_rng)
# self.element.dialogClosed.connect(self._update_edge_selection)
# self.edge.activated_connect(self._update_edge_selection)
self.apply_theme()
def _make_plot(self, left_label: str, bottom_label: str) -> pg.PlotWidget:
plot = pg.PlotWidget()
@@ -217,27 +243,48 @@ class MotionProfileWidget(QWidget):
)
plot.addItem(scatter, ignoreBounds=True)
text = f"{self.element.currentElement()}, {self.edge.currentText()}-edge, {self.edge_energy:0.1f} eV"
label = pg.TextItem(text, anchor=(0, 1.5), color="r")
label = pg.TextItem(self.edge_label.text(), anchor=(0, 1.5), color="r")
label.setPos(x_marker, y_marker)
plot.addItem(label, ignoreBounds=True)
@SafeSlot()
def _update_edge_selection(self, *_, **__):
if self.element.currentElement() == "":
return
if self.edge.currentText() == "":
return
self.edge_energy = xraydb.xray_edge(
self.element.currentElement(), self.edge.currentText(), True
)
if self.edge_energy is not None:
self.edge_text.setText(
f"{self.element.currentElement()}, {self.edge.currentText()}-edge, {self.edge_energy:0.1f} eV"
def _update_edge(self, *_):
dlg = EdgeSelector(self, llim=4500, hlim=60000)
if dlg.exec_():
self.edge_energy = dlg.active_energy
self.edge_label.setText(
f"{dlg.active_element}, {dlg.active_edge}-edge, {dlg.active_energy:0.1f} eV"
)
else:
self.edge_text.setText("No valid edge selected!")
self.update_plot()
self._update_auto_scan_rng()
self.update_plot()
@SafeSlot()
def _update_auto_scan_rng(self, *_):
if self.scan_rng_ckbox.isChecked() and self.edge_energy is not None:
self._parent_widget._restore_kwargs(
{
"start": self.edge_energy - 200,
"stop": self.edge_energy + 1000,
"e_kink": self.edge_energy + 1000,
}
)
# @SafeSlot()
# def _update_edge_selection(self, *_, **__):
# if self.element.currentElement() == "":
# return
# if self.edge.currentText() == "":
# return
# self.edge_energy = xraydb.xray_edge(
# self.element.currentElement(), self.edge.currentText(), True
# )
# if self.edge_energy is not None:
# self.edge_text.setText(
# f"{self.element.currentElement()}, {self.edge.currentText()}-edge, {self.edge_energy:0.1f} eV"
# )
# else:
# self.edge_text.setText("No valid edge selected!")
# self.update_plot()
@SafeSlot()
def update_plot(self, *_, scan_name=None, scan_parameters=None, d_spacing=None, **__):
@@ -331,6 +378,10 @@ class MotionProfileWidget(QWidget):
app = QApplication.instance()
theme = app.theme.theme # type: ignore
self.edge_selector_button.setStyleSheet(
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
)
bg_color = pg.getConfigOption("background")
fg_color = pg.getConfigOption("foreground")
@@ -346,7 +397,7 @@ class MotionProfileWidget(QWidget):
for curve, color in zip(curves, colors):
if not isinstance(curve, pg.ScatterPlotItem):
curve.setPen(pg.mkPen(color=color, width=2))
self.element.apply_theme(theme)
# self.element.apply_theme(theme)
def _energy_to_angle(self, energy: int | float):
if self.d_spacing is None: