From e6e3104410b76ab0250c2fbc31012e85ce9e8889 Mon Sep 17 00:00:00 2001 From: x01da Date: Mon, 10 Aug 2026 07:46:44 +0200 Subject: [PATCH] wip widget --- .../scan_control_advanced.py | 140 ++++++++++++++---- 1 file changed, 109 insertions(+), 31 deletions(-) diff --git a/debye_bec/bec_widgets/widgets/scan_control_advanced/scan_control_advanced.py b/debye_bec/bec_widgets/widgets/scan_control_advanced/scan_control_advanced.py index 44a4020..ddf3659 100644 --- a/debye_bec/bec_widgets/widgets/scan_control_advanced/scan_control_advanced.py +++ b/debye_bec/bec_widgets/widgets/scan_control_advanced/scan_control_advanced.py @@ -11,7 +11,7 @@ from bec_widgets.utils.bec_dispatcher import BECDispatcher 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 +from qtpy.QtCore import Qt, Signal # pylint: disable=E0611 from qtpy.QtWidgets import ( @@ -41,7 +41,7 @@ SHOW_MOTION_PROFILE = [ "xas_advanced_scan_with_xrd", ] -# TODO: Changing edge trigger change self.edge_energy. Theme change edge marker. Name of edge marker label should be element and edge +# TODO: Theme change edge marker. Show edge energy next to edge selector. Bugfix edge selector class ScanControlAdvanced(ScanControl): @@ -51,7 +51,9 @@ class ScanControlAdvanced(ScanControl): motion_profile_layout = QVBoxLayout(self._motion_profile_group) self.motion_profile_widget = MotionProfileWidget(self) motion_profile_layout.addWidget(self.motion_profile_widget) - super().__init__(*args, **kwargs) + super().__init__( + allowed_scans=SHOW_MOTION_PROFILE, default_scan="xas_simple_scan", *args, **kwargs + ) self.show_motion_profile(self._selected_scan) self.d_spacing = 0 @@ -165,7 +167,7 @@ ELEMENTS = { # period 6 "Cs": (6, 1), "Ba": (6, 2), - "La": (6, 3), + "La": (8, 3), "Ce": (8, 4), "Pr": (8, 5), "Nd": (8, 6), @@ -199,6 +201,8 @@ class PeriodicTableDialog(QDialog): self.selected = None layout = QGridLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(1) for element, (row, col) in ELEMENTS.items(): button = QPushButton(element) @@ -212,6 +216,9 @@ class PeriodicTableDialog(QDialog): class ElementSelector(QWidget): + + dialogClosed = Signal() + def __init__(self): super().__init__() self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) @@ -225,21 +232,39 @@ class ElementSelector(QWidget): self.button.setStyleSheet( f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}" ) - self.selection = QLabel("") + # self.selection = QLabel("") layout_selection.addWidget(self.button) - layout_selection.addWidget(self.selection) + # layout_selection.addWidget(self.selection) layout.addWidget(self.label) layout.addLayout(layout_selection) + self.current_element = "" self.button.clicked.connect(self.open_table) def open_table(self): dialog = PeriodicTableDialog(self) if dialog.exec_(): - self.selection.setText(dialog.selected) + self.current_element = dialog.selected + # self.selection.setText(dialog.selected) + self.dialogClosed.emit() - def currentText(self): - return self.selection.text() + 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;}}" + ) class MotionProfileWidget(QWidget): @@ -252,9 +277,10 @@ class MotionProfileWidget(QWidget): self.element = ElementSelector() self.edge = ComboBox("Edge", "Edge", ["K", "L1", "L2", "L3"]) + self.edge_text = TextIndicator("", "Selected Edge", "No edge selected") plot_container = QWidget() - plot_layout = QVBoxLayout(plot_container) + plot_layout = QHBoxLayout(plot_container) plot_layout.setContentsMargins(0, 0, 0, 0) self.plot_pos = self._make_plot(left_label="Energy
[eV]", bottom_label="Time [s]") @@ -262,13 +288,13 @@ class MotionProfileWidget(QWidget): left_label="Scan Velocity
[eV/s]", bottom_label="Energy [eV]" ) - self.edge_energy = 8000 + self.edge_energy = None plot_layout.addWidget(self.plot_pos) plot_layout.addWidget(self.plot_vel) plot_container.setMinimumHeight( - self.plot_pos.minimumHeight() + self.plot_vel.minimumHeight() + int(np.max([self.plot_pos.minimumHeight(), self.plot_vel.minimumHeight()])) ) scroll = QScrollArea() scroll.setWidgetResizable(True) @@ -279,6 +305,7 @@ class MotionProfileWidget(QWidget): 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.addStretch(1) outer_layout.addLayout(self.edge_selection) @@ -288,6 +315,9 @@ class MotionProfileWidget(QWidget): self.scan_parameters = None self.d_spacing = None + self.element.dialogClosed.connect(self._update_edge_selection) + self.edge.activated_connect(self._update_edge_selection) + @staticmethod def _make_plot(left_label: str, bottom_label: str) -> pg.PlotWidget: plot = pg.PlotWidget() @@ -298,28 +328,54 @@ class MotionProfileWidget(QWidget): plot.hideButtons() plot.getAxis("left").enableAutoSIPrefix(False) plot.getAxis("bottom").enableAutoSIPrefix(False) - plot.setMinimumHeight(220) + plot.setMinimumHeight(180) plot.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.MinimumExpanding) return plot - def _add_edge_marker(self, plot, x_arr, y_arr, energy_arr): + def _add_edge_marker(self, plot, x_arr, y_arr, energy_arr, edge_marker): if self.edge_energy is None: return idx = int(np.argmin(np.abs(np.asarray(energy_arr) - self.edge_energy))) + if idx == 0 or idx == len(energy_arr) - 1: + return x_marker = x_arr[idx] y_marker = y_arr[idx] scatter = pg.ScatterPlotItem( - [x_marker], [y_marker], symbol="star", size=14, brush=pg.mkBrush("r"), pen=pg.mkPen("r") + [x_marker], + [y_marker], + symbol=edge_marker, + size=20, + brush=pg.mkBrush("r"), + pen=pg.mkPen("r"), ) plot.addItem(scatter, ignoreBounds=True) - label = pg.TextItem("edge", anchor=(0, 1), color="r") + text = f"{self.element.currentElement()}, {self.edge.currentText()}-edge, {self.edge_energy:0.1f} eV" + label = pg.TextItem(text, anchor=(0.5, 1.5), color="r") label.setPos(x_marker, y_marker) plot.addItem(label, ignoreBounds=True) - def update_plot(self, scan_name=None, scan_parameters=None, d_spacing=None): + @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, *_, **__): if scan_name is not None: self.scan_name = scan_name if scan_parameters is not None: @@ -351,10 +407,10 @@ class MotionProfileWidget(QWidget): vel_e = self.velocity_to_energy_per_s(pos, vel) self.plot_pos.plot(x_time, energy) - self._add_edge_marker(self.plot_pos, x_time, energy, energy) + self._add_edge_marker(self.plot_pos, x_time, energy, energy, "_") self.plot_vel.plot(energy, -vel_e) - self._add_edge_marker(self.plot_vel, energy, -vel_e, energy) + self._add_edge_marker(self.plot_vel, energy, -vel_e, energy, "|") if "xas_advanced_scan" in self.scan_name: if ( @@ -379,10 +435,10 @@ class MotionProfileWidget(QWidget): time_flipped = -np.flip(x_time) + x_time[-1] energy_flipped = np.flip(energy) self.plot_pos.plot(time_flipped, energy_flipped) - self._add_edge_marker(self.plot_pos, time_flipped, energy_flipped, energy_flipped) + self._add_edge_marker(self.plot_pos, time_flipped, energy_flipped, energy_flipped, "_") self.plot_vel.plot(energy, -vel_e) - self._add_edge_marker(self.plot_vel, energy, -vel_e, energy) + self._add_edge_marker(self.plot_vel, energy, -vel_e, energy, "|") self.apply_theme() @@ -393,7 +449,6 @@ class MotionProfileWidget(QWidget): Args: theme (Optional[str]): Theme, either "dark", "light", or None. Defaults to None. """ - logger.info(f"theme update called with theme: {theme}") if theme is None: app = QApplication.instance() theme = app.theme.theme # type: ignore @@ -411,7 +466,9 @@ class MotionProfileWidget(QWidget): curves = plot.listDataItems() colors = Colors.golden_angle_color(colormap="plasma", num=len(curves), format="HEX") for curve, color in zip(curves, colors): - curve.setPen(pg.mkPen(color=color, width=2)) + if not isinstance(curve, pg.ScatterPlotItem): + curve.setPen(pg.mkPen(color=color, width=2)) + self.element.apply_theme(theme) def energy_to_angle(self, energy: int | float): if self.d_spacing is None: @@ -486,19 +543,40 @@ class ComboBox(QWidget): def activated_connect(self, func): """Connect a function to the Enter/Return key press.""" - self.value.activated.connect( - partial( - func, - identifier=self.identifier, - value_obj=self.value, - value=lambda: self.value.currentText(), - ) - ) + self.value.activated.connect(func) def setDisabled(self, disable): self.value.setDisabled(disable) +class TextIndicator(QWidget): + def __init__(self, identifier="", label="", text=""): + super().__init__() + + layout = QVBoxLayout(self) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) + + self.identifier = identifier + + self.label = QLabel(label) + self.label.setFixedWidth(LABEL_WIDTH) + self.label.setWordWrap(True) + self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + layout.addWidget(self.label) + + self.value = QLabel(text) + self.value.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + + layout.addWidget(self.value) + + def setText(self, text): + self.value.setText(text) + + def text(self) -> str: + return self.value.text() + + if __name__ == "__main__": app = QApplication(sys.argv) apply_theme("light")