mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 11:11:49 +02:00
refactor: various minor improvements for the alignment gui
This commit is contained in:
0
bec_widgets/applications/__init__.py
Normal file
0
bec_widgets/applications/__init__.py
Normal file
0
bec_widgets/applications/alignment/__init__.py
Normal file
0
bec_widgets/applications/alignment/__init__.py
Normal file
@ -1,6 +1,5 @@
|
||||
""" This module contains the GUI for the 1D alignment application.
|
||||
#TODO at this stage it is a preliminary version of the GUI, which will be added to the main branch although it is not yet fully functional.
|
||||
It is a work in progress and will be updated in the future.
|
||||
It is a preliminary version of the GUI, which will be added to the main branch and steadily updated to be improved.
|
||||
"""
|
||||
|
||||
import os
|
||||
@ -8,24 +7,32 @@ from typing import Optional
|
||||
|
||||
from bec_lib.device import Positioner, Signal
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtCore import Signal as pyqtSignal
|
||||
from qtpy.QtWidgets import QCheckBox, QDoubleSpinBox, QSpinBox, QVBoxLayout, QWidget
|
||||
from qtpy.QtGui import QIcon
|
||||
from qtpy.QtWidgets import QCheckBox, QDoubleSpinBox, QPushButton, QSpinBox, QVBoxLayout, QWidget
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
||||
from bec_widgets.qt_utils.toolbar import WidgetAction
|
||||
from bec_widgets.utils import UILoader
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_widgets.widgets.bec_progressbar.bec_progressbar import BECProgressBar
|
||||
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
|
||||
from bec_widgets.widgets.lmfit_dialog.lmfit_dialog import LMFitDialog
|
||||
from bec_widgets.widgets.positioner_box.positioner_control_line import PositionerControlLine
|
||||
from bec_widgets.widgets.positioner_box.positioner_box import PositionerBox
|
||||
from bec_widgets.widgets.stop_button.stop_button import StopButton
|
||||
from bec_widgets.widgets.toggle.toggle import ToggleSwitch
|
||||
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
|
||||
|
||||
MODULE_PATH = os.path.dirname(bec_widgets.__file__)
|
||||
|
||||
|
||||
class Alignment1D(BECWidget, QWidget):
|
||||
"""GUI for 1D alignment"""
|
||||
"""Alignment GUI to perform 1D scans"""
|
||||
|
||||
# Emit a signal when a motion is ongoing
|
||||
motion_is_active = pyqtSignal(bool)
|
||||
|
||||
def __init__(
|
||||
@ -42,6 +49,7 @@ class Alignment1D(BECWidget, QWidget):
|
||||
super().__init__(client=client, gui_id=gui_id)
|
||||
QWidget.__init__(self, parent)
|
||||
self.get_bec_shortcuts()
|
||||
self._accent_colors = get_accent_colors()
|
||||
self.ui_file = "alignment_1d.ui"
|
||||
self.ui = None
|
||||
self.progress_bar = None
|
||||
@ -50,57 +58,61 @@ class Alignment1D(BECWidget, QWidget):
|
||||
|
||||
def init_ui(self):
|
||||
"""Initialise the UI from QT Designer file"""
|
||||
|
||||
current_path = os.path.dirname(__file__)
|
||||
self.ui = UILoader(self).loader(os.path.join(current_path, self.ui_file))
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(self.ui)
|
||||
self.setLayout(layout)
|
||||
self.waveform = self.ui.findChild(BECWaveformWidget, "bec_waveform_widget")
|
||||
# Customize the plotting widget
|
||||
self.waveform = self.ui.findChild(BECWaveformWidget, "bec_waveform_widget")
|
||||
self._customise_bec_waveform_widget()
|
||||
# Setup filters for comboboxes
|
||||
# Setup comboboxes for motor and signal selection
|
||||
# FIXME after changing the filtering in the combobox
|
||||
self._setup_motor_combobox()
|
||||
self._setup_signal_combobox()
|
||||
# Setup arrow item
|
||||
self._setup_arrow_item()
|
||||
# Setup Scan Control
|
||||
# Setup motor indicator
|
||||
self._setup_motor_indicator()
|
||||
# Connect spinboxes to scan Control
|
||||
self._setup_scan_control()
|
||||
# Setup progress bar
|
||||
self._setup_progress_bar()
|
||||
# Add actions buttons
|
||||
self._add_action_buttons()
|
||||
self._customise_buttons()
|
||||
# Customize the positioner box
|
||||
self._customize_positioner_box()
|
||||
# Hook scaninfo updates
|
||||
self.bec_dispatcher.connect_slot(self._scan_status_callback, MessageEndpoints.scan_status())
|
||||
self.bec_dispatcher.connect_slot(self.scan_status_callback, MessageEndpoints.scan_status())
|
||||
|
||||
##############################
|
||||
############ SLOTS ###########
|
||||
##############################
|
||||
|
||||
@Slot(dict, dict)
|
||||
def _scan_status_callback(self, content: dict, metadata: dict) -> None:
|
||||
def scan_status_callback(self, content: dict, _) -> None:
|
||||
"""This slot allows to enable/disable the UI critical components when a scan is running"""
|
||||
if content["status"] in ["running", "open"]:
|
||||
self.motion_is_active.emit(True)
|
||||
self._enable_ui(False)
|
||||
self.enable_ui(False)
|
||||
elif content["status"] in ["aborted", "halted", "closed"]:
|
||||
self.motion_is_active.emit(False)
|
||||
self._enable_ui(True)
|
||||
self.enable_ui(True)
|
||||
|
||||
@Slot(float)
|
||||
def move_to_center(self, pos: float) -> None:
|
||||
@Slot(tuple)
|
||||
def move_to_center(self, move_request: tuple) -> None:
|
||||
"""Move the selected motor to the center"""
|
||||
motor = self.ui.device_combobox.currentText()
|
||||
self.dev[motor].move(float(pos), relative=False)
|
||||
if move_request[0] in ["center", "center1", "center2"]:
|
||||
pos = move_request[1]
|
||||
self.dev.get(motor).move(float(pos), relative=False)
|
||||
|
||||
@Slot()
|
||||
def _reset_progress_bar(self) -> None:
|
||||
def reset_progress_bar(self) -> None:
|
||||
"""Reset the progress bar"""
|
||||
self.progress_bar.set_value(0)
|
||||
self.progress_bar.set_minimum(0)
|
||||
|
||||
@Slot(dict, dict)
|
||||
def _update_progress_bar(self, content: dict, metadata: dict) -> None:
|
||||
def update_progress_bar(self, content: dict, _) -> None:
|
||||
"""Hook to update the progress bar
|
||||
|
||||
Args:
|
||||
@ -113,58 +125,75 @@ class Alignment1D(BECWidget, QWidget):
|
||||
self.progress_bar.set_maximum(content["max_value"])
|
||||
self.progress_bar.set_value(content["value"])
|
||||
|
||||
@Slot()
|
||||
def clear_queue(self) -> None:
|
||||
"""Clear the scan queue"""
|
||||
self.queue.request_queue_reset()
|
||||
|
||||
##############################
|
||||
######## END OF SLOTS ########
|
||||
##############################
|
||||
|
||||
def _enable_ui(self, enable: bool) -> None:
|
||||
def enable_ui(self, enable: bool) -> None:
|
||||
"""Enable or disable the UI components"""
|
||||
# Device selection comboboxes
|
||||
# Enable/disable motor and signal selection
|
||||
self.ui.device_combobox.setEnabled(enable)
|
||||
self.ui.device_combobox_2.setEnabled(enable)
|
||||
# Enable/disable DAP selection
|
||||
self.ui.dap_combo_box.setEnabled(enable)
|
||||
# Scan button
|
||||
# Enable/disable Scan Button
|
||||
self.ui.scan_button.setEnabled(enable)
|
||||
# Positioner control line
|
||||
# pylint: disable=protected-access
|
||||
self.ui.positioner_control_line._toogle_enable_buttons(enable)
|
||||
# Send report to scilog
|
||||
self.ui.button_send_summary_scilog.setEnabled(enable)
|
||||
self.ui.positioner_box._toogle_enable_buttons(enable)
|
||||
# Disable move to buttons in LMFitDialog
|
||||
self.ui.findChild(LMFitDialog).set_enable_move_to_buttons(enable)
|
||||
self.ui.findChild(LMFitDialog).set_actions_enabled(enable)
|
||||
|
||||
def _add_action_buttons(self) -> None:
|
||||
"""Add action buttons for the Action Control"""
|
||||
def _customise_buttons(self) -> None:
|
||||
"""Add action buttons for the Action Control.
|
||||
In addition, we are adding a callback to also clear the queue to the stop button
|
||||
to ensure that upon clicking the button, no scans from another client may be queued
|
||||
which would be confusing without the queue widget.
|
||||
"""
|
||||
fit_dialog = self.ui.findChild(LMFitDialog)
|
||||
fit_dialog.update_activated_button_list(["center", "center1", "center2"])
|
||||
fit_dialog.move_to_position.connect(self.move_to_center)
|
||||
fit_dialog.active_action_list = ["center", "center1", "center2"]
|
||||
fit_dialog.move_action.connect(self.move_to_center)
|
||||
scan_button = self.ui.findChild(QPushButton, "scan_button")
|
||||
scan_button.setStyleSheet(
|
||||
f"""
|
||||
QPushButton:enabled {{ background-color: {self._accent_colors.success.name()};color: white; }}
|
||||
QPushButton:disabled {{ background-color: grey;color: white; }}
|
||||
"""
|
||||
)
|
||||
stop_button = self.ui.findChild(StopButton)
|
||||
stop_button.button.setText("Stop and Clear Queue")
|
||||
stop_button.button.clicked.connect(self.clear_queue)
|
||||
|
||||
def _customise_bec_waveform_widget(self) -> None:
|
||||
"""Customise the BEC Waveform Widget, i.e. hide toolbar buttons except roi selection"""
|
||||
for button in self.waveform.toolbar.widgets.values():
|
||||
if getattr(button, "action", None) is not None:
|
||||
button.action.setVisible(False)
|
||||
self.waveform.toolbar.widgets["roi_select"].action.setVisible(True)
|
||||
"""Customise the BEC Waveform Widget, i.e. clear the toolbar, add the DAP ROI selection to the toolbar.
|
||||
We also move the scan_control widget which is fully hidden and solely used for setting up the scan parameters to the toolbar.
|
||||
"""
|
||||
self.waveform.toolbar.clear()
|
||||
toggle_switch = self.ui.findChild(ToggleSwitch, "toggle_switch")
|
||||
scan_control = self.ui.scan_control
|
||||
self.waveform.toolbar.populate_toolbar(
|
||||
{
|
||||
"label": WidgetAction(label="Enable DAP ROI", widget=toggle_switch),
|
||||
"label": WidgetAction(label="ENABLE DAP ROI", widget=toggle_switch),
|
||||
"scan_control": WidgetAction(widget=scan_control),
|
||||
},
|
||||
self.waveform,
|
||||
)
|
||||
|
||||
def _setup_arrow_item(self) -> None:
|
||||
def _setup_motor_indicator(self) -> None:
|
||||
"""Setup the arrow item"""
|
||||
self.waveform.waveform.motor_pos_tick.add_to_plot()
|
||||
positioner_line = self.ui.findChild(PositionerControlLine)
|
||||
positioner_line.position_update.connect(self.waveform.waveform.motor_pos_tick.set_position)
|
||||
self.waveform.waveform.tick_item.add_to_plot()
|
||||
positioner_box = self.ui.findChild(PositionerBox)
|
||||
positioner_box.position_update.connect(self.waveform.waveform.tick_item.set_position)
|
||||
try:
|
||||
pos = float(positioner_line.ui.readback.text())
|
||||
pos = float(positioner_box.ui.readback.text())
|
||||
except ValueError:
|
||||
pos = 0
|
||||
self.waveform.waveform.motor_pos_tick.set_position(pos)
|
||||
self.waveform.waveform.tick_item.set_position(pos)
|
||||
|
||||
def _setup_motor_combobox(self) -> None:
|
||||
"""Setup motor selection"""
|
||||
@ -182,9 +211,11 @@ class Alignment1D(BECWidget, QWidget):
|
||||
|
||||
def _setup_scan_control(self) -> None:
|
||||
"""Setup scan control, connect spin and check boxes to the scan_control widget"""
|
||||
# Connect motor
|
||||
device_line_edit = self.ui.scan_control.arg_box.findChild(DeviceLineEdit)
|
||||
self.ui.device_combobox.currentTextChanged.connect(device_line_edit.setText)
|
||||
device_line_edit.setText(self.ui.device_combobox.currentText())
|
||||
# Connect start, stop, step, exp_time and relative check box
|
||||
spin_boxes = self.ui.scan_control.arg_box.findChildren(QDoubleSpinBox)
|
||||
start = self.ui.findChild(QDoubleSpinBox, "linescan_start")
|
||||
start.valueChanged.connect(spin_boxes[0].setValue)
|
||||
@ -208,18 +239,27 @@ class Alignment1D(BECWidget, QWidget):
|
||||
# FIXME once the BECScanProgressBar is implemented
|
||||
self.progress_bar = self.ui.findChild(BECProgressBar, "bec_progress_bar")
|
||||
self.progress_bar.set_value(0)
|
||||
self.ui.bec_waveform_widget.new_scan.connect(self._reset_progress_bar)
|
||||
self.bec_dispatcher.connect_slot(
|
||||
self._update_progress_bar, MessageEndpoints.scan_progress()
|
||||
)
|
||||
self.ui.bec_waveform_widget.new_scan.connect(self.reset_progress_bar)
|
||||
self.bec_dispatcher.connect_slot(self.update_progress_bar, MessageEndpoints.scan_progress())
|
||||
|
||||
def _customize_positioner_box(self) -> None:
|
||||
"""Customize the positioner Box, i.e. remove the stop button"""
|
||||
box = self.ui.findChild(PositionerBox)
|
||||
box.ui.stop.setVisible(False)
|
||||
box.ui.position_indicator.setFixedHeight(20)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
from qtpy.QtWidgets import QApplication # pylint: disable=ungrouped-imports
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
icon = QIcon()
|
||||
icon.addFile(
|
||||
os.path.join(MODULE_PATH, "assets", "app_icons", "alignment_1d.png"), size=QSize(48, 48)
|
||||
)
|
||||
app.setWindowIcon(icon)
|
||||
window = Alignment1D()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
|
File diff suppressed because it is too large
Load Diff
BIN
bec_widgets/assets/app_icons/alignment_1d.png
Normal file
BIN
bec_widgets/assets/app_icons/alignment_1d.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
@ -9,7 +9,7 @@ from typing import Literal
|
||||
from bec_qthemes._icon.material_icons import material_icon
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtGui import QAction, QColor, QIcon
|
||||
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QToolBar, QToolButton, QWidget
|
||||
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QSizePolicy, QToolBar, QToolButton, QWidget
|
||||
|
||||
import bec_widgets
|
||||
|
||||
@ -165,7 +165,9 @@ class WidgetAction(ToolBarAction):
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
if self.label is not None:
|
||||
label = QLabel(f"{self.label}")
|
||||
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||
layout.addWidget(label)
|
||||
self.widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||
layout.addWidget(self.widget)
|
||||
toolbar.addWidget(widget)
|
||||
|
||||
|
@ -30,7 +30,7 @@ class LinearRegionWrapper(QObject):
|
||||
self.proxy = None
|
||||
self.change_roi_color((color, hover_color))
|
||||
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
||||
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
||||
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
|
||||
|
||||
# Slot for changing the color of the region selector (edge and fill)
|
||||
@Slot(tuple)
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from qtpy.QtCore import QObject, Qt, Signal, Slot
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import QObject, QPointF, Signal, Slot
|
||||
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class BECIndicatorItem(QObject):
|
||||
|
||||
@ -13,10 +16,20 @@ class BECIndicatorItem(QObject):
|
||||
super().__init__(parent=parent)
|
||||
self.accent_colors = get_accent_colors()
|
||||
self.plot_item = plot_item
|
||||
self._item_on_plot = False
|
||||
self._pos = None
|
||||
self.is_log_x = False
|
||||
self.is_log_y = False
|
||||
|
||||
@property
|
||||
def item_on_plot(self) -> bool:
|
||||
"""Returns if the item is on the plot"""
|
||||
return self._item_on_plot
|
||||
|
||||
@item_on_plot.setter
|
||||
def item_on_plot(self, value: bool) -> None:
|
||||
self._item_on_plot = value
|
||||
|
||||
def add_to_plot(self) -> None:
|
||||
"""Add the item to the plot"""
|
||||
raise NotImplementedError("Method add_to_plot not implemented")
|
||||
@ -25,8 +38,11 @@ class BECIndicatorItem(QObject):
|
||||
"""Remove the item from the plot"""
|
||||
raise NotImplementedError("Method remove_from_plot not implemented")
|
||||
|
||||
def set_position(self, pos: tuple[float, float] | float) -> None:
|
||||
"""Set the position of the item"""
|
||||
def set_position(self, pos) -> None:
|
||||
"""This method should implement the logic to set the position of the
|
||||
item on the plot. Depending on the child class, the position can be
|
||||
a tuple (x,y) or a single value, i.e. x position where y position is fixed.
|
||||
"""
|
||||
raise NotImplementedError("Method set_position not implemented")
|
||||
|
||||
def check_log(self):
|
||||
@ -35,11 +51,6 @@ class BECIndicatorItem(QObject):
|
||||
self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked()
|
||||
self.set_position(self._pos)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""Cleanup the item"""
|
||||
self.remove_from_plot()
|
||||
self.deleteLater()
|
||||
|
||||
|
||||
class BECTickItem(BECIndicatorItem):
|
||||
"""Class to create a tick item which can be added to a pyqtgraph plot.
|
||||
@ -51,15 +62,17 @@ class BECTickItem(BECIndicatorItem):
|
||||
|
||||
def __init__(self, plot_item: pg.PlotItem = None, parent=None):
|
||||
super().__init__(plot_item=plot_item, parent=parent)
|
||||
self.tick_item = pg.TickSliderItem(allowAdd=False, allowRemove=False)
|
||||
self.tick_item = pg.TickSliderItem(
|
||||
parent=parent, allowAdd=False, allowRemove=False, orientation="bottom"
|
||||
)
|
||||
self.tick_item.skip_auto_range = True
|
||||
self.tick = None
|
||||
# Set _pos to float
|
||||
self._pos = 0
|
||||
self._pos = 0.0
|
||||
self._range = [0, 1]
|
||||
|
||||
@Slot(float)
|
||||
def set_position(self, pos: float) -> None:
|
||||
"""Set the position of the tick item
|
||||
"""Set the x position of the tick item
|
||||
|
||||
Args:
|
||||
pos (float): The position of the tick item.
|
||||
@ -74,36 +87,71 @@ class BECTickItem(BECIndicatorItem):
|
||||
self.position_changed.emit(pos)
|
||||
self.position_changed_str.emit(str(pos))
|
||||
|
||||
def update_range(self, vb, viewRange) -> None:
|
||||
@Slot()
|
||||
def update_range(self, _, view_range: tuple[float, float]) -> None:
|
||||
"""Update the range of the tick item
|
||||
|
||||
Args:
|
||||
vb (pg.ViewBox): The view box.
|
||||
viewRange (tuple): The view range.
|
||||
"""
|
||||
origin = self.tick_item.tickSize / 2.0
|
||||
length = self.tick_item.length
|
||||
if self._pos < view_range[0] or self._pos > view_range[1]:
|
||||
self.tick_item.setVisible(False)
|
||||
else:
|
||||
self.tick_item.setVisible(True)
|
||||
|
||||
lengthIncludingPadding = length + self.tick_item.tickSize + 2
|
||||
if self.tick_item.isVisible():
|
||||
origin = self.tick_item.tickSize / 2.0
|
||||
length = self.tick_item.length
|
||||
|
||||
self._range = viewRange
|
||||
tickValueIncludingPadding = (self._pos - viewRange[0]) / (viewRange[1] - viewRange[0])
|
||||
tickValue = (tickValueIncludingPadding * lengthIncludingPadding - origin) / length
|
||||
self.tick_item.setTickValue(self.tick, tickValue)
|
||||
length_with_padding = length + self.tick_item.tickSize + 2
|
||||
|
||||
self._range = view_range
|
||||
tick_with_padding = (self._pos - view_range[0]) / (view_range[1] - view_range[0])
|
||||
tick_value = (tick_with_padding * length_with_padding - origin) / length
|
||||
self.tick_item.setTickValue(self.tick, tick_value)
|
||||
|
||||
def add_to_plot(self):
|
||||
"""Add the tick item to the view box or plot item."""
|
||||
if self.plot_item is not None:
|
||||
self.plot_item.layout.addItem(self.tick_item, 4, 1)
|
||||
self.tick = self.tick_item.addTick(0, movable=False, color=self.accent_colors.highlight)
|
||||
self.plot_item.vb.sigXRangeChanged.connect(self.update_range)
|
||||
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
||||
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
|
||||
if self.plot_item is None:
|
||||
return
|
||||
|
||||
self.plot_item.layout.addItem(self.tick_item, 2, 1)
|
||||
self.tick_item.setOrientation("top")
|
||||
self.tick = self.tick_item.addTick(0, movable=False, color=self.accent_colors.highlight)
|
||||
self.update_tick_pos_y()
|
||||
self.plot_item.vb.sigXRangeChanged.connect(self.update_range)
|
||||
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
||||
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
|
||||
self.plot_item.vb.geometryChanged.connect(self.update_tick_pos_y)
|
||||
self.item_on_plot = True
|
||||
|
||||
@Slot()
|
||||
def update_tick_pos_y(self):
|
||||
"""Update tick position, while respecting the tick_item coordinates"""
|
||||
pos = self.tick.pos()
|
||||
pos = self.tick_item.mapToParent(pos)
|
||||
new_pos = self.plot_item.vb.geometry().bottom()
|
||||
new_pos = self.tick_item.mapFromParent(QPointF(pos.x(), new_pos))
|
||||
self.tick.setPos(new_pos)
|
||||
|
||||
def remove_from_plot(self):
|
||||
"""Remove the tick item from the view box or plot item."""
|
||||
if self.plot_item is not None:
|
||||
self.plot_item.layout.removeItem(self.tick_item)
|
||||
if self.plot_item is not None and self.item_on_plot is True:
|
||||
self.plot_item.vb.sigXRangeChanged.disconnect(self.update_range)
|
||||
self.plot_item.ctrl.logXCheck.checkStateChanged.disconnect(self.check_log)
|
||||
self.plot_item.ctrl.logYCheck.checkStateChanged.disconnect(self.check_log)
|
||||
if self.plot_item.layout is not None:
|
||||
self.plot_item.layout.removeItem(self.tick_item)
|
||||
self.item_on_plot = False
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""Cleanup the item"""
|
||||
self.remove_from_plot()
|
||||
if self.tick_item is not None:
|
||||
self.tick_item.close()
|
||||
self.tick_item.deleteLater()
|
||||
self.tick_item = None
|
||||
|
||||
|
||||
class BECArrowItem(BECIndicatorItem):
|
||||
@ -126,8 +174,10 @@ class BECArrowItem(BECIndicatorItem):
|
||||
|
||||
def __init__(self, plot_item: pg.PlotItem = None, parent=None):
|
||||
super().__init__(plot_item=plot_item, parent=parent)
|
||||
self.arrow_item = pg.ArrowItem()
|
||||
self.arrow_item = pg.ArrowItem(parent=parent)
|
||||
self.arrow_item.skip_auto_range = True
|
||||
self._pos = (0, 0)
|
||||
self.arrow_item.setVisible(False)
|
||||
|
||||
@Slot(dict)
|
||||
def set_style(self, style: dict) -> None:
|
||||
@ -166,24 +216,38 @@ class BECArrowItem(BECIndicatorItem):
|
||||
|
||||
def add_to_plot(self):
|
||||
"""Add the arrow item to the view box or plot item."""
|
||||
if not self.arrow_item:
|
||||
logger.warning(f"Arrow item was already destroyed, cannot be created")
|
||||
return
|
||||
|
||||
self.arrow_item.setStyle(
|
||||
angle=-90,
|
||||
pen=pg.mkPen(self.accent_colors.emergency, width=1),
|
||||
brush=pg.mkBrush(self.accent_colors.highlight),
|
||||
headLen=20,
|
||||
)
|
||||
self.arrow_item.setVisible(True)
|
||||
if self.plot_item is not None:
|
||||
self.plot_item.addItem(self.arrow_item)
|
||||
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
||||
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
|
||||
self.item_on_plot = True
|
||||
|
||||
def remove_from_plot(self):
|
||||
"""Remove the arrow item from the view box or plot item."""
|
||||
if self.plot_item is not None:
|
||||
if self.plot_item is not None and self.item_on_plot is True:
|
||||
self.plot_item.ctrl.logXCheck.checkStateChanged.disconnect(self.check_log)
|
||||
self.plot_item.ctrl.logYCheck.checkStateChanged.disconnect(self.check_log)
|
||||
self.plot_item.removeItem(self.arrow_item)
|
||||
self.item_on_plot = False
|
||||
|
||||
def check_log(self):
|
||||
"""Checks if the x or y axis is in log scale and updates the internal state accordingly."""
|
||||
self.is_log_x = self.plot_item.ctrl.logXCheck.isChecked()
|
||||
self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked()
|
||||
self.set_position(self._pos)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""Cleanup the item"""
|
||||
self.remove_from_plot()
|
||||
self.arrow_item = None
|
||||
|
@ -106,7 +106,7 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
|
||||
|
||||
self.add_legend()
|
||||
self.crosshair = None
|
||||
self.motor_pos_tick = BECTickItem(parent=self, plot_item=self.plot_item)
|
||||
self.tick_item = BECTickItem(parent=self, plot_item=self.plot_item)
|
||||
self.arrow_item = BECArrowItem(parent=self, plot_item=self.plot_item)
|
||||
self._connect_to_theme_change()
|
||||
|
||||
@ -431,7 +431,7 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
|
||||
def cleanup_pyqtgraph(self):
|
||||
"""Cleanup pyqtgraph items."""
|
||||
self.unhook_crosshair()
|
||||
self.motor_pos_tick.cleanup()
|
||||
self.tick_item.cleanup()
|
||||
self.arrow_item.cleanup()
|
||||
item = self.plot_item
|
||||
item.vb.menu.close()
|
||||
|
@ -1,6 +1,6 @@
|
||||
# pylint: disable=too_many_lines
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from typing import Any, Literal, Optional
|
||||
|
||||
@ -13,7 +13,7 @@ from bec_lib.logger import bec_logger
|
||||
from pydantic import Field, ValidationError, field_validator
|
||||
from pyqtgraph.exporters import MatplotlibExporter
|
||||
from qtpy.QtCore import Signal as pyqtSignal
|
||||
from qtpy.QtWidgets import QApplication, QWidget
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
||||
from bec_widgets.utils import Colors, EntryValidator
|
||||
@ -42,11 +42,12 @@ class BECSignalProxy(pg.SignalProxy):
|
||||
self.args = args
|
||||
if self.blocking:
|
||||
return
|
||||
# self.blocking = True
|
||||
super().signalReceived(*args)
|
||||
|
||||
def flush(self):
|
||||
"""If there is a signal queued send it out"""
|
||||
super().flush()
|
||||
# def flush(self):
|
||||
# """If there is a signal queued send it out"""
|
||||
# super().flush()
|
||||
|
||||
@Slot()
|
||||
def unblock_proxy(self):
|
||||
@ -190,7 +191,7 @@ class BECWaveform(BECPlotBase):
|
||||
self.roi_select.linear_region_selector.setRegion(region)
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting region {tuple}; Exception raised: {e}")
|
||||
raise ValueError(f"Error setting region {tuple}; Exception raised: {e}")
|
||||
raise ValueError(f"Error setting region {tuple}; Exception raised: {e}") from e
|
||||
|
||||
def _hook_roi(self):
|
||||
"""Hook the linear region selector to the plot."""
|
||||
@ -244,7 +245,7 @@ class BECWaveform(BECPlotBase):
|
||||
# Reset curves
|
||||
self._curves_data = defaultdict(dict)
|
||||
self._curves = self.plot_item.curves
|
||||
for curve_id, curve_config in self.config.curves.items():
|
||||
for curve_config in self.config.curves.values():
|
||||
self.add_curve_by_config(curve_config)
|
||||
if replot_last_scan:
|
||||
self.scan_history(scan_index=-1)
|
||||
@ -367,7 +368,7 @@ class BECWaveform(BECPlotBase):
|
||||
Returns:
|
||||
CurveConfig|dict: Configuration of the curve.
|
||||
"""
|
||||
for source, curves in self._curves_data.items():
|
||||
for curves in self._curves_data.values():
|
||||
if curve_id in curves:
|
||||
if dict_output:
|
||||
return curves[curve_id].config.model_dump()
|
||||
@ -387,7 +388,7 @@ class BECWaveform(BECPlotBase):
|
||||
if isinstance(identifier, int):
|
||||
return self.plot_item.curves[identifier]
|
||||
elif isinstance(identifier, str):
|
||||
for source_type, curves in self._curves_data.items():
|
||||
for curves in self._curves_data.values():
|
||||
if identifier in curves:
|
||||
return curves[identifier]
|
||||
raise ValueError(f"Curve with ID '{identifier}' not found.")
|
||||
@ -538,6 +539,7 @@ class BECWaveform(BECPlotBase):
|
||||
|
||||
@Slot()
|
||||
def auto_range(self):
|
||||
"""Manually set auto range of the plotitem"""
|
||||
self.plot_item.autoRange()
|
||||
|
||||
def set_auto_range(self, enabled: bool, axis: str = "xy"):
|
||||
@ -576,7 +578,6 @@ class BECWaveform(BECPlotBase):
|
||||
Returns:
|
||||
BECCurve: The curve object.
|
||||
"""
|
||||
curve_source = curve_source
|
||||
curve_id = label or f"Curve {len(self.plot_item.curves) + 1}"
|
||||
|
||||
curve_exits = self._check_curve_id(curve_id, self._curves_data)
|
||||
@ -750,7 +751,7 @@ class BECWaveform(BECPlotBase):
|
||||
|
||||
if self.x_axis_mode["readout_priority"] == "async":
|
||||
raise ValueError(
|
||||
f"Async signals cannot be fitted at the moment. Please switch to 'monitored' or 'baseline' signals."
|
||||
"Async signals cannot be fitted at the moment. Please switch to 'monitored' or 'baseline' signals."
|
||||
)
|
||||
|
||||
if validate_bec is True:
|
||||
@ -1024,7 +1025,7 @@ class BECWaveform(BECPlotBase):
|
||||
Args:
|
||||
curve_id(str): ID of the curve to be removed.
|
||||
"""
|
||||
for source, curves in self._curves_data.items():
|
||||
for curves in self._curves_data.values():
|
||||
if curve_id in curves:
|
||||
curve = curves.pop(curve_id)
|
||||
self.plot_item.removeItem(curve)
|
||||
@ -1047,7 +1048,7 @@ class BECWaveform(BECPlotBase):
|
||||
self.plot_item.removeItem(curve)
|
||||
del self.config.curves[curve_id]
|
||||
# Remove from self.curve_data
|
||||
for source, curves in self._curves_data.items():
|
||||
for curves in self._curves_data.values():
|
||||
if curve_id in curves:
|
||||
del curves[curve_id]
|
||||
break
|
||||
@ -1077,7 +1078,7 @@ class BECWaveform(BECPlotBase):
|
||||
if self._curves_data["DAP"]:
|
||||
self.setup_dap(self.old_scan_id, self.scan_id)
|
||||
if self._curves_data["async"]:
|
||||
for curve_id, curve in self._curves_data["async"].items():
|
||||
for curve in self._curves_data["async"].values():
|
||||
self.setup_async(
|
||||
name=curve.config.signals.y.name, entry=curve.config.signals.y.entry
|
||||
)
|
||||
@ -1212,7 +1213,8 @@ class BECWaveform(BECPlotBase):
|
||||
"""Callback for DAP response message."""
|
||||
if self.proxy_update_dap is not None:
|
||||
self.proxy_update_dap.unblock_proxy()
|
||||
self.msg = msg
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
scan_id, x_name, x_entry, y_name, y_entry = msg["dap_request"].content["config"]["args"]
|
||||
model = msg["dap_request"].content["config"]["class_kwargs"]["model"]
|
||||
|
||||
@ -1241,7 +1243,7 @@ class BECWaveform(BECPlotBase):
|
||||
metadata(dict): Metadata of the message.
|
||||
"""
|
||||
instruction = metadata.get("async_update")
|
||||
for curve_id, curve in self._curves_data["async"].items():
|
||||
for curve in self._curves_data["async"].values():
|
||||
y_name = curve.config.signals.y.name
|
||||
y_entry = curve.config.signals.y.entry
|
||||
x_name = self._x_axis_mode["name"]
|
||||
@ -1365,7 +1367,7 @@ class BECWaveform(BECPlotBase):
|
||||
if self._x_axis_mode["name"] is None or self._x_axis_mode["name"] == "best_effort":
|
||||
if len(self._curves_data["async"]) > 0:
|
||||
x_data = None
|
||||
self._x_axis_mode["label_suffix"] = f" [auto: index]"
|
||||
self._x_axis_mode["label_suffix"] = " [auto: index]"
|
||||
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
||||
self.plot_item.setLabel(
|
||||
"bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
|
||||
@ -1511,7 +1513,7 @@ class BECWaveform(BECPlotBase):
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.update_dap, MessageEndpoints.dap_response(self.scan_id)
|
||||
)
|
||||
for curve_id, curve in self._curves_data["async"].items():
|
||||
for curve_id in self._curves_data["async"]:
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_async_readback,
|
||||
MessageEndpoints.device_async_readback(self.scan_id, curve_id),
|
||||
|
@ -1,12 +1,12 @@
|
||||
import os
|
||||
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import Property, Signal, Slot
|
||||
from qtpy.QtWidgets import QPushButton, QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils import UILoader
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@ -17,8 +17,8 @@ class LMFitDialog(BECWidget, QWidget):
|
||||
ICON_NAME = "monitoring"
|
||||
# Signal to emit the currently selected fit curve_id
|
||||
selected_fit = Signal(str)
|
||||
# Signal to emit a position to move to.
|
||||
move_to_position = Signal(float)
|
||||
# Signal to emit a move action in form of a tuple (param_name, value)
|
||||
move_action = Signal(tuple)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -53,56 +53,44 @@ class LMFitDialog(BECWidget, QWidget):
|
||||
self._fit_curve_id = None
|
||||
self._deci_precision = 3
|
||||
self._always_show_latest = False
|
||||
self._activated_buttons_for_move_action = []
|
||||
self.ui.curve_list.currentItemChanged.connect(self.display_fit_details)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self.layout)
|
||||
self._enable_move_to_buttons = False
|
||||
self._active_actions = []
|
||||
self._enable_actions = True
|
||||
self._move_buttons = []
|
||||
self._accent_colors = get_accent_colors()
|
||||
self.action_buttons = {}
|
||||
|
||||
@Property(bool)
|
||||
def enable_move_to_buttons(self):
|
||||
@property
|
||||
def enable_actions(self) -> bool:
|
||||
"""Property to enable the move to buttons."""
|
||||
return self._enable_move_to_buttons
|
||||
return self._enable_actions
|
||||
|
||||
@enable_move_to_buttons.setter
|
||||
def enable_move_to_buttons(self, enable: bool):
|
||||
self._enable_move_to_buttons = enable
|
||||
for button in self._move_buttons:
|
||||
if button.text().split(" ")[-1] in self.activated_buttons_for_move_action:
|
||||
button.setEnabled(enable)
|
||||
@enable_actions.setter
|
||||
def enable_actions(self, enable: bool):
|
||||
self._enable_actions = enable
|
||||
for button in self.action_buttons.values():
|
||||
button.setEnabled(enable)
|
||||
|
||||
@Property(list)
|
||||
def active_action_list(self) -> list[str]:
|
||||
"""Property to list the names of the fit parameters for which actions should be enabled."""
|
||||
return self._active_actions
|
||||
|
||||
@active_action_list.setter
|
||||
def active_action_list(self, actions: list[str]):
|
||||
self._active_actions = actions
|
||||
|
||||
# This slot needed?
|
||||
@Slot(bool)
|
||||
def set_enable_move_to_buttons(self, enable: bool):
|
||||
def set_actions_enabled(self, enable: bool) -> bool:
|
||||
"""Slot to enable the move to buttons.
|
||||
|
||||
Args:
|
||||
enable (bool): Whether to enable the move to buttons.
|
||||
enable (bool): Whether to enable the action buttons.
|
||||
"""
|
||||
self.enable_move_to_buttons = enable
|
||||
|
||||
@Property(list)
|
||||
def activated_buttons_for_move_action(self) -> list:
|
||||
"""Property for the buttons that should be activated for the move action in the parameter list."""
|
||||
return self._activated_buttons_for_move_action
|
||||
|
||||
@activated_buttons_for_move_action.setter
|
||||
def activated_buttons_for_move_action(self, buttons: list):
|
||||
"""Setter for the buttons that should be activated for the move action.
|
||||
|
||||
Args:
|
||||
buttons (list): The buttons that should be activated for the move action.
|
||||
"""
|
||||
self._activated_buttons_for_move_action = buttons
|
||||
|
||||
@Slot(list)
|
||||
def update_activated_button_list(self, names: list) -> None:
|
||||
"""Update the list of activated buttons for the move action.
|
||||
|
||||
Args:
|
||||
names (list): List of button names to be activated.
|
||||
"""
|
||||
self.activated_buttons_for_move_action = names
|
||||
self.enable_actions = enable
|
||||
|
||||
@Property(bool)
|
||||
def always_show_latest(self):
|
||||
@ -128,7 +116,7 @@ class LMFitDialog(BECWidget, QWidget):
|
||||
self.ui.group_curve_selection.setVisible(not show)
|
||||
|
||||
@Property(bool)
|
||||
def hide_summary(self):
|
||||
def hide_summary(self) -> bool:
|
||||
"""Property for showing the summary."""
|
||||
return not self.ui.group_summary.isVisible()
|
||||
|
||||
@ -142,7 +130,7 @@ class LMFitDialog(BECWidget, QWidget):
|
||||
self.ui.group_summary.setVisible(not show)
|
||||
|
||||
@Property(bool)
|
||||
def hide_parameters(self):
|
||||
def hide_parameters(self) -> bool:
|
||||
"""Property for showing the parameters."""
|
||||
return not self.ui.group_parameters.isVisible()
|
||||
|
||||
@ -156,7 +144,7 @@ class LMFitDialog(BECWidget, QWidget):
|
||||
self.ui.group_parameters.setVisible(not show)
|
||||
|
||||
@property
|
||||
def fit_curve_id(self):
|
||||
def fit_curve_id(self) -> str:
|
||||
"""Property for the currently displayed fit curve_id."""
|
||||
return self._fit_curve_id
|
||||
|
||||
@ -266,28 +254,48 @@ class LMFitDialog(BECWidget, QWidget):
|
||||
param_std = f"{param_std:.{self._deci_precision}f}"
|
||||
else:
|
||||
param_std = "None"
|
||||
# Create a push button to move the motor to a specific position
|
||||
# Per default, this feature is deactivated
|
||||
widget = QWidget()
|
||||
layout = QVBoxLayout(widget)
|
||||
push_button = QPushButton(f"Move to {param_name}")
|
||||
if param_name in self.activated_buttons_for_move_action:
|
||||
push_button.setEnabled(True)
|
||||
push_button.clicked.connect(
|
||||
lambda _, value=param[1]: self.move_to_position.emit(float(value))
|
||||
)
|
||||
else:
|
||||
push_button.setEnabled(False)
|
||||
self._move_buttons.append(push_button)
|
||||
layout.addWidget(push_button)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
tree_item = QTreeWidgetItem(self.ui.param_tree, [param_name, param_value, param_std])
|
||||
self.ui.param_tree.setItemWidget(tree_item, 3, widget)
|
||||
if param_name in self.active_action_list: # pylint: disable=unsupported-membership-test
|
||||
# Create a push button to move the motor to a specific position
|
||||
widget = QWidget()
|
||||
button = QPushButton(f"Move to {param_name}")
|
||||
button.clicked.connect(self._create_move_action(param_name, param[1]))
|
||||
if self.enable_actions is True:
|
||||
button.setEnabled(True)
|
||||
else:
|
||||
button.setEnabled(False)
|
||||
button.setStyleSheet(
|
||||
f"""
|
||||
QPushButton:enabled {{ background-color: {self._accent_colors.success.name()};color: white; }}
|
||||
QPushButton:disabled {{ background-color: grey;color: white; }}
|
||||
"""
|
||||
)
|
||||
self.action_buttons[param_name] = button
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.action_buttons[param_name])
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
widget.setLayout(layout)
|
||||
self.ui.param_tree.setItemWidget(tree_item, 3, widget)
|
||||
|
||||
def _create_move_action(self, param_name: str, param_value: float) -> callable:
|
||||
"""Create a move action for the given parameter name and value.
|
||||
|
||||
Args:
|
||||
param_name (str): The name of the parameter.
|
||||
param_value (float): The value of the parameter.
|
||||
Returns:
|
||||
callable: The move action with the given parameter name and value.
|
||||
"""
|
||||
|
||||
def move_action():
|
||||
self.move_action.emit((param_name, param_value))
|
||||
|
||||
return move_action
|
||||
|
||||
def populate_curve_list(self):
|
||||
"""Populate the curve list with the available fit curves."""
|
||||
for curve_name in self.summary_data.keys():
|
||||
for curve_name in self.summary_data:
|
||||
self.ui.curve_list.addItem(curve_name)
|
||||
|
||||
def refresh_curve_list(self):
|
||||
@ -310,10 +318,10 @@ class LMFitDialog(BECWidget, QWidget):
|
||||
self.update_summary_tree(data, {"curve_id": curve_name})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
from qtpy.QtWidgets import QApplication # pylint: disable=ungrouped-imports
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
dialog = LMFitDialog()
|
||||
|
@ -321,7 +321,7 @@ class PositionerBox(BECWidget, QWidget):
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
from qtpy.QtWidgets import QApplication # pylint: disable=ungrouped-imports
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
set_theme("dark")
|
||||
|
@ -1,6 +1,6 @@
|
||||
from bec_qthemes import material_icon
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtWidgets import QHBoxLayout, QPushButton, QToolButton, QWidget
|
||||
from qtpy.QtWidgets import QHBoxLayout, QPushButton, QSizePolicy, QToolButton, QWidget
|
||||
|
||||
from bec_widgets.qt_utils.error_popups import SafeSlot
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
@ -28,9 +28,10 @@ class StopButton(BECWidget, QWidget):
|
||||
self.button.setToolTip("Stop the scan queue")
|
||||
else:
|
||||
self.button = QPushButton()
|
||||
self.button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
self.button.setText("Stop")
|
||||
self.button.setStyleSheet(
|
||||
"background-color: #cc181e; color: white; font-weight: bold; font-size: 12px;"
|
||||
f"background-color: #cc181e; color: white; font-weight: bold; font-size: 12px;"
|
||||
)
|
||||
self.button.clicked.connect(self.stop_scan)
|
||||
|
||||
@ -47,3 +48,14 @@ class StopButton(BECWidget, QWidget):
|
||||
scan_id(str|None): The scan id to stop. If None, the current scan will be stopped.
|
||||
"""
|
||||
self.queue.request_scan_halt()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
w = StopButton()
|
||||
w.show()
|
||||
sys.exit(app.exec_())
|
||||
|
@ -168,6 +168,7 @@ def test_remove_dap_data(lmfit_dialog):
|
||||
|
||||
def test_update_summary_tree(lmfit_dialog, lmfit_message):
|
||||
"""Test display_fit_details method"""
|
||||
lmfit_dialog.active_action_list = ["center", "amplitude"]
|
||||
lmfit_dialog.update_summary_tree(data=lmfit_message, metadata={"curve_id": "test_curve_id"})
|
||||
# Check if the data is updated
|
||||
assert lmfit_dialog.summary_data == {"test_curve_id": lmfit_message}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import pyqtgraph as pg
|
||||
import pytest
|
||||
from qtpy.QtCore import QPointF
|
||||
|
||||
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
|
||||
|
||||
@ -21,7 +22,7 @@ def plot_widget_with_tick_item(qtbot, mocked_client):
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
|
||||
yield widget.waveform.motor_pos_tick, widget.waveform.plot_item
|
||||
yield widget.waveform.tick_item, widget.waveform.plot_item
|
||||
|
||||
|
||||
def test_arrow_item_add_to_plot(plot_widget_with_arrow_item):
|
||||
@ -33,15 +34,6 @@ def test_arrow_item_add_to_plot(plot_widget_with_arrow_item):
|
||||
assert arrow_item.plot_item.items == [arrow_item.arrow_item]
|
||||
|
||||
|
||||
def test_arrow_item_remove_to_plot(plot_widget_with_arrow_item):
|
||||
"""Test the remove_from_plot method"""
|
||||
arrow_item, plot_item = plot_widget_with_arrow_item
|
||||
arrow_item.add_to_plot()
|
||||
assert arrow_item.plot_item.items == [arrow_item.arrow_item]
|
||||
arrow_item.remove_from_plot()
|
||||
assert arrow_item.plot_item.items == []
|
||||
|
||||
|
||||
def test_arrow_item_set_position(plot_widget_with_arrow_item):
|
||||
"""Test the set_position method"""
|
||||
arrow_item, plot_item = plot_widget_with_arrow_item
|
||||
@ -53,28 +45,37 @@ def test_arrow_item_set_position(plot_widget_with_arrow_item):
|
||||
arrow_item.add_to_plot()
|
||||
arrow_item.position_changed.connect(signal_callback)
|
||||
arrow_item.set_position(pos=(1, 1))
|
||||
assert arrow_item.arrow_item.pos().toTuple() == (1, 1)
|
||||
point = QPointF(1.0, 1.0)
|
||||
assert arrow_item.arrow_item.pos() == point
|
||||
arrow_item.set_position(pos=(2, 2))
|
||||
assert arrow_item.arrow_item.pos().toTuple() == (2, 2)
|
||||
point = QPointF(2.0, 2.0)
|
||||
assert arrow_item.arrow_item.pos() == point
|
||||
assert container == [(1, 1), (2, 2)]
|
||||
|
||||
|
||||
def test_arrow_item_cleanup(plot_widget_with_arrow_item):
|
||||
"""Test cleanup procedure"""
|
||||
arrow_item, plot_item = plot_widget_with_arrow_item
|
||||
arrow_item.add_to_plot()
|
||||
assert arrow_item.item_on_plot is True
|
||||
arrow_item.cleanup()
|
||||
assert arrow_item.plot_item.items == []
|
||||
assert arrow_item.item_on_plot is False
|
||||
assert arrow_item.arrow_item is None
|
||||
|
||||
|
||||
def test_tick_item_add_to_plot(plot_widget_with_tick_item):
|
||||
"""Test the add_to_plot method"""
|
||||
tick_item, plot_item = plot_widget_with_tick_item
|
||||
assert tick_item.plot_item is not None
|
||||
assert tick_item.plot_item.items == []
|
||||
tick_item.add_to_plot()
|
||||
assert tick_item.plot_item.layout.itemAt(4, 1) == tick_item.tick_item
|
||||
|
||||
|
||||
def test_tick_item_remove_to_plot(plot_widget_with_tick_item):
|
||||
"""Test the remove_from_plot method"""
|
||||
tick_item, plot_item = plot_widget_with_tick_item
|
||||
tick_item.add_to_plot()
|
||||
assert tick_item.plot_item.layout.itemAt(4, 1) == tick_item.tick_item
|
||||
tick_item.remove_from_plot()
|
||||
assert tick_item.plot_item.layout.itemAt(4, 1) is None
|
||||
assert tick_item.plot_item.layout.itemAt(2, 1) == tick_item.tick_item
|
||||
assert tick_item.item_on_plot is True
|
||||
new_pos = plot_item.vb.geometry().bottom()
|
||||
pos = tick_item.tick.pos()
|
||||
new_pos = tick_item.tick_item.mapFromParent(QPointF(pos.x(), new_pos))
|
||||
assert new_pos.y() == pos.y()
|
||||
|
||||
|
||||
def test_tick_item_set_position(plot_widget_with_tick_item):
|
||||
@ -93,3 +94,15 @@ def test_tick_item_set_position(plot_widget_with_tick_item):
|
||||
tick_item.set_position(pos=2)
|
||||
assert tick_item._pos == 2
|
||||
assert container == [1.0, 2.0]
|
||||
|
||||
|
||||
def test_tick_item_cleanup(plot_widget_with_tick_item):
|
||||
"""Test cleanup procedure"""
|
||||
tick_item, plot_item = plot_widget_with_tick_item
|
||||
tick_item.add_to_plot()
|
||||
assert tick_item.item_on_plot is True
|
||||
tick_item.cleanup()
|
||||
ticks = getattr(tick_item.plot_item.layout.itemAt(3, 1), "ticks", None)
|
||||
assert ticks == None
|
||||
assert tick_item.item_on_plot is False
|
||||
assert tick_item.tick_item is None
|
||||
|
Reference in New Issue
Block a user