From 22e804a7bce6c09fc4f542358f9266d893d51860 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Fri, 28 Feb 2025 00:27:14 +0100 Subject: [PATCH] refactor(waveform_widget): removed and replaced by Waveform --- .../alignment/alignment_1d/alignment_1d.py | 1 + bec_widgets/cli/client.py | 335 +------- .../jupyter_console/jupyter_console_window.py | 17 +- bec_widgets/qt_utils/side_panel.py | 4 +- .../widgets/plots/waveform/__init__.py | 0 .../waveform/bec_waveform_widget.pyproject | 1 - .../waveform/bec_waveform_widget_plugin.py | 58 -- .../waveform/register_bec_waveform_widget.py | 17 - .../waveform/waveform_popups/__init__.py | 0 .../waveform_popups/curve_dialog/__init__.py | 0 .../curve_dialog/curve_dialog.py | 336 -------- .../curve_dialog/curve_dialog.ui | 372 --------- .../dap_summary_dialog/__init__.py | 0 .../dap_summary_dialog/dap_summary_dialog.py | 25 - .../widgets/plots/waveform/waveform_widget.py | 751 ------------------ tests/unit_tests/test_bec_dock.py | 2 +- tests/unit_tests/test_crosshair.py | 20 +- tests/unit_tests/test_waveform_widget.py | 573 ------------- 18 files changed, 46 insertions(+), 2466 deletions(-) delete mode 100644 bec_widgets/widgets/plots/waveform/__init__.py delete mode 100644 bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject delete mode 100644 bec_widgets/widgets/plots/waveform/bec_waveform_widget_plugin.py delete mode 100644 bec_widgets/widgets/plots/waveform/register_bec_waveform_widget.py delete mode 100644 bec_widgets/widgets/plots/waveform/waveform_popups/__init__.py delete mode 100644 bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/__init__.py delete mode 100644 bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py delete mode 100644 bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui delete mode 100644 bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/__init__.py delete mode 100644 bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py delete mode 100644 bec_widgets/widgets/plots/waveform/waveform_widget.py delete mode 100644 tests/unit_tests/test_waveform_widget.py diff --git a/bec_widgets/applications/alignment/alignment_1d/alignment_1d.py b/bec_widgets/applications/alignment/alignment_1d/alignment_1d.py index 6050a612..8152c380 100644 --- a/bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +++ b/bec_widgets/applications/alignment/alignment_1d/alignment_1d.py @@ -29,6 +29,7 @@ MODULE_PATH = os.path.dirname(bec_widgets.__file__) logger = bec_logger.logger +# FIXME BECWaveFormWidget is gone, this app will not work until adapted to new Waveform class Alignment1D: """Alignment GUI to perform 1D scans""" diff --git a/bec_widgets/cli/client.py b/bec_widgets/cli/client.py index 87233c2c..5d4c90b0 100644 --- a/bec_widgets/cli/client.py +++ b/bec_widgets/cli/client.py @@ -24,7 +24,6 @@ class Widgets(str, enum.Enum): BECProgressBar = "BECProgressBar" BECQueue = "BECQueue" BECStatusBox = "BECStatusBox" - BECWaveformWidget = "BECWaveformWidget" DapComboBox = "DapComboBox" DarkModeButton = "DarkModeButton" DeviceBrowser = "DeviceBrowser" @@ -2690,313 +2689,6 @@ class BECWaveform(RPCBase): """ -class BECWaveformWidget(RPCBase): - @property - @rpc_call - def curves(self) -> "list[BECCurve]": - """ - Get the curves of the plot widget as a list - Returns: - list: List of curves. - """ - - @rpc_call - def plot( - self, - arg1: "list | np.ndarray | str | None" = None, - x: "list | np.ndarray | None" = None, - y: "list | np.ndarray | None" = None, - x_name: "str | None" = None, - y_name: "str | None" = None, - z_name: "str | None" = None, - x_entry: "str | None" = None, - y_entry: "str | None" = None, - z_entry: "str | None" = None, - color: "str | None" = None, - color_map_z: "str | None" = "magma", - label: "str | None" = None, - validate: "bool" = True, - dap: "str | None" = None, - **kwargs, - ) -> "BECCurve": - """ - Plot a curve to the plot widget. - Args: - arg1(list | np.ndarray | str | None): First argument which can be x data(list | np.ndarray), y data(list | np.ndarray), or y_name(str). - x(list | np.ndarray): Custom x data to plot. - y(list | np.ndarray): Custom y data to plot. - x_name(str): The name of the device for the x-axis. - y_name(str): The name of the device for the y-axis. - z_name(str): The name of the device for the z-axis. - x_entry(str): The name of the entry for the x-axis. - y_entry(str): The name of the entry for the y-axis. - z_entry(str): The name of the entry for the z-axis. - color(str): The color of the curve. - color_map_z(str): The color map to use for the z-axis. - label(str): The label of the curve. - validate(bool): If True, validate the device names and entries. - dap(str): The dap model to use for the curve. If not specified, none will be added. - - Returns: - BECCurve: The curve object. - """ - - @rpc_call - def add_dap( - self, - x_name: "str", - y_name: "str", - dap: "str", - x_entry: "str | None" = None, - y_entry: "str | None" = None, - color: "str | None" = None, - validate_bec: "bool" = True, - **kwargs, - ) -> "BECCurve": - """ - Add LMFIT dap model curve to the plot widget. - - Args: - x_name(str): Name of the x signal. - x_entry(str): Entry of the x signal. - y_name(str): Name of the y signal. - y_entry(str): Entry of the y signal. - color(str, optional): Color of the curve. Defaults to None. - dap(str): The dap model to use for the curve. - validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True. - **kwargs: Additional keyword arguments for the curve configuration. - - Returns: - BECCurve: The curve object. - """ - - @rpc_call - def get_dap_params(self) -> "dict": - """ - Get the DAP parameters of all DAP curves. - - Returns: - dict: DAP parameters of all DAP curves. - """ - - @rpc_call - def remove_curve(self, *identifiers): - """ - Remove a curve from the plot widget. - - Args: - *identifiers: Identifier of the curve to be removed. Can be either an integer (index) or a string (curve_id). - """ - - @rpc_call - def scan_history(self, scan_index: "int" = None, scan_id: "str" = None): - """ - Update the scan curves with the data from the scan storage. - Provide only one of scan_id or scan_index. - - Args: - scan_id(str, optional): ScanID of the scan to be updated. Defaults to None. - scan_index(int, optional): Index of the scan to be updated. Defaults to None. - """ - - @rpc_call - def get_all_data(self, output: "Literal['dict', 'pandas']" = "dict") -> "dict | pd.DataFrame": - """ - Extract all curve data into a dictionary or a pandas DataFrame. - - Args: - output (Literal["dict", "pandas"]): Format of the output data. - - Returns: - dict | pd.DataFrame: Data of all curves in the specified format. - """ - - @rpc_call - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - - @rpc_call - def set_x(self, x_name: "str", x_entry: "str | None" = None): - """ - Change the x axis of the plot widget. - - Args: - x_name(str): Name of the x signal. - - "best_effort": Use the best effort signal. - - "timestamp": Use the timestamp signal. - - "index": Use the index signal. - - Custom signal name of device from BEC. - x_entry(str): Entry of the x signal. - """ - - @rpc_call - def set_title(self, title: "str"): - """ - Set the title of the plot widget. - - Args: - title(str): Title of the plot. - """ - - @rpc_call - def set_x_label(self, x_label: "str"): - """ - Set the x-axis label of the plot widget. - - Args: - x_label(str): Label of the x-axis. - """ - - @rpc_call - def set_y_label(self, y_label: "str"): - """ - Set the y-axis label of the plot widget. - - Args: - y_label(str): Label of the y-axis. - """ - - @rpc_call - def set_x_scale(self, x_scale: "Literal['linear', 'log']"): - """ - Set the scale of the x-axis of the plot widget. - - Args: - x_scale(Literal["linear", "log"]): Scale of the x-axis. - """ - - @rpc_call - def set_y_scale(self, y_scale: "Literal['linear', 'log']"): - """ - Set the scale of the y-axis of the plot widget. - - Args: - y_scale(Literal["linear", "log"]): Scale of the y-axis. - """ - - @rpc_call - def set_x_lim(self, x_lim: "tuple"): - """ - Set the limits of the x-axis of the plot widget. - - Args: - x_lim(tuple): Limits of the x-axis. - """ - - @rpc_call - def set_y_lim(self, y_lim: "tuple"): - """ - Set the limits of the y-axis of the plot widget. - - Args: - y_lim(tuple): Limits of the y-axis. - """ - - @rpc_call - def set_legend_label_size(self, legend_label_size: "int"): - """ - Set the size of the legend labels of the plot widget. - - Args: - legend_label_size(int): Size of the legend labels. - """ - - @rpc_call - def set_auto_range(self, enabled: "bool", axis: "str" = "xy"): - """ - Set the auto range of the plot widget. - - Args: - enabled(bool): If True, enable the auto range. - axis(str, optional): The axis to enable the auto range. - - "xy": Enable auto range for both x and y axis. - - "x": Enable auto range for x axis. - - "y": Enable auto range for y axis. - """ - - @rpc_call - def set_grid(self, x_grid: "bool", y_grid: "bool"): - """ - Set the grid visibility of the plot widget. - - Args: - x_grid(bool): Visibility of the x-axis grid. - y_grid(bool): Visibility of the y-axis grid. - """ - - @rpc_call - def enable_fps_monitor(self, enabled: "bool"): - """ - Enable the FPS monitor of the plot widget. - - Args: - enabled(bool): If True, enable the FPS monitor. - """ - - @rpc_call - def enable_scatter(self, enabled: "bool"): - """ - Enable the scatter plot of the plot widget. - - Args: - enabled(bool): If True, enable the scatter plot. - """ - - @rpc_call - def lock_aspect_ratio(self, lock: "bool"): - """ - Lock the aspect ratio of the plot widget. - - Args: - lock(bool): Lock the aspect ratio. - """ - - @rpc_call - def export(self): - """ - Show the export dialog for the plot widget. - """ - - @rpc_call - def export_to_matplotlib(self): - """ - Export the plot widget to Matplotlib. - """ - - @rpc_call - def toggle_roi(self, checked: "bool"): - """ - Toggle the linear region selector. - - Args: - checked(bool): If True, enable the linear region selector. - """ - - @rpc_call - def select_roi(self, region: "tuple"): - """ - Set the region of interest of the plot widget. - - Args: - region(tuple): Region of interest. - """ - - class Curve(RPCBase): @rpc_call def remove(self): @@ -3833,6 +3525,31 @@ class ScanControl(RPCBase): """ +class ScanMetadata(RPCBase): + @property + @rpc_call + def _config_dict(self) -> "dict": + """ + Get the configuration of the widget. + + Returns: + dict: The configuration of the widget. + """ + + @rpc_call + def _get_all_rpc(self) -> "dict": + """ + Get all registered RPC objects. + """ + + @property + @rpc_call + def _rpc_id(self) -> "str": + """ + Get the RPC ID of the widget. + """ + + class SignalComboBox(RPCBase): @property @rpc_call @@ -4309,7 +4026,7 @@ class Waveform(RPCBase): Remove a curve from the plot widget. Args: - curve(int|str): The curve to remove. Can be the order of the curve or the name of the curve. + curve(int|str): The curve to remove. It Can be the order of the curve or the name of the curve. """ @rpc_call diff --git a/bec_widgets/examples/jupyter_console/jupyter_console_window.py b/bec_widgets/examples/jupyter_console/jupyter_console_window.py index 9d4421c5..f10f1d43 100644 --- a/bec_widgets/examples/jupyter_console/jupyter_console_window.py +++ b/bec_widgets/examples/jupyter_console/jupyter_console_window.py @@ -52,8 +52,6 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover: "w10": self.w10, "d0": self.d0, "d1": self.d1, - "d2": self.d2, - "wave": self.wf, "im": self.im, "mm": self.mm, "mw": self.mw, @@ -66,7 +64,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover: "btn6": self.btn6, "pb": self.pb, "pi": self.pi, - "wfng": self.wfng, + "wf": self.wf, } ) @@ -121,8 +119,8 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover: fifth_tab = QWidget() fifth_tab_layout = QVBoxLayout(fifth_tab) - self.wfng = Waveform() - fifth_tab_layout.addWidget(self.wfng) + self.wf = Waveform() + fifth_tab_layout.addWidget(self.wf) tab_widget.addTab(fifth_tab, "Waveform Next Gen") tab_widget.setCurrentIndex(4) # add stuff to the new Waveform widget @@ -140,8 +138,8 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover: # self.wfng._add_curve_custom(x=np.arange(10), y=np.random.rand(10), label="curve1") # self.wfng._add_curve_custom(x=np.arange(10), y=np.random.rand(10), label="curve2") # self.wfng._add_curve_custom(x=np.arange(10), y=np.random.rand(10), label="curve3") - self.wfng.plot(y_name="bpm4i", y_entry="bpm4i", dap="GaussianModel") - self.wfng.plot(y_name="bpm3a", y_entry="bpm3a", dap="GaussianModel") + self.wf.plot(y_name="bpm4i", y_entry="bpm4i", dap="GaussianModel") + self.wf.plot(y_name="bpm3a", y_entry="bpm3a", dap="GaussianModel") def _init_figure(self): self.w1 = self.figure.plot(x_name="samx", y_name="bpm4i", row=0, col=0) @@ -208,11 +206,6 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover: self.im = self.d1.add_widget("BECImageWidget") self.im.image("waveform", "1d") - self.d2 = self.dock.add_dock(name="dock_2", position="bottom") - self.wf = self.d2.add_widget("BECWaveformWidget", row=0, col=0) - self.wf.plot("bpm4i") - self.wf.plot("bpm3a") - self.mw = None # self.wf.multi_waveform(monitor="waveform") # , config=config) self.dock.save_state() diff --git a/bec_widgets/qt_utils/side_panel.py b/bec_widgets/qt_utils/side_panel.py index 5f855569..fc07ea7d 100644 --- a/bec_widgets/qt_utils/side_panel.py +++ b/bec_widgets/qt_utils/side_panel.py @@ -317,9 +317,9 @@ class ExampleApp(QMainWindow): # pragma: no cover self.side_panel = SidePanel(self, orientation="left", panel_max_width=250) self.layout.addWidget(self.side_panel) - from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget + from bec_widgets.widgets.plots_next_gen.waveform.waveform import Waveform - self.plot = BECWaveformWidget() + self.plot = Waveform() self.layout.addWidget(self.plot) self.add_side_menus() diff --git a/bec_widgets/widgets/plots/waveform/__init__.py b/bec_widgets/widgets/plots/waveform/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject b/bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject deleted file mode 100644 index b03b94e2..00000000 --- a/bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +++ /dev/null @@ -1 +0,0 @@ -{'files': ['waveform_widget.py']} \ No newline at end of file diff --git a/bec_widgets/widgets/plots/waveform/bec_waveform_widget_plugin.py b/bec_widgets/widgets/plots/waveform/bec_waveform_widget_plugin.py deleted file mode 100644 index bd110b30..00000000 --- a/bec_widgets/widgets/plots/waveform/bec_waveform_widget_plugin.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -import os - -from qtpy.QtDesigner import QDesignerCustomWidgetInterface - -import bec_widgets -from bec_widgets.utils.bec_designer import designer_material_icon -from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget - -DOM_XML = """ - - - - -""" - -MODULE_PATH = os.path.dirname(bec_widgets.__file__) - - -class BECWaveformWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cover - def __init__(self): - super().__init__() - self._form_editor = None - - def createWidget(self, parent): - t = BECWaveformWidget(parent) - return t - - def domXml(self): - return DOM_XML - - def group(self): - return "BEC Plots" - - def icon(self): - return designer_material_icon(BECWaveformWidget.ICON_NAME) - - def includeFile(self): - return "bec_waveform_widget" - - def initialize(self, form_editor): - self._form_editor = form_editor - - def isContainer(self): - return False - - def isInitialized(self): - return self._form_editor is not None - - def name(self): - return "BECWaveformWidget" - - def toolTip(self): - return "BECWaveformWidget" - - def whatsThis(self): - return self.toolTip() diff --git a/bec_widgets/widgets/plots/waveform/register_bec_waveform_widget.py b/bec_widgets/widgets/plots/waveform/register_bec_waveform_widget.py deleted file mode 100644 index 54b299b9..00000000 --- a/bec_widgets/widgets/plots/waveform/register_bec_waveform_widget.py +++ /dev/null @@ -1,17 +0,0 @@ -def main(): # pragma: no cover - from qtpy import PYSIDE6 - - if not PYSIDE6: - print("PYSIDE6 is not available in the environment. Cannot patch designer.") - return - from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection - - from bec_widgets.widgets.plots.waveform.bec_waveform_widget_plugin import ( - BECWaveformWidgetPlugin, - ) - - QPyDesignerCustomWidgetCollection.addCustomWidget(BECWaveformWidgetPlugin()) - - -if __name__ == "__main__": # pragma: no cover - main() diff --git a/bec_widgets/widgets/plots/waveform/waveform_popups/__init__.py b/bec_widgets/widgets/plots/waveform/waveform_popups/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/__init__.py b/bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py b/bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py deleted file mode 100644 index bc880271..00000000 --- a/bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +++ /dev/null @@ -1,336 +0,0 @@ -from __future__ import annotations - -import os -from typing import Literal - -from bec_qthemes import material_icon -from pydantic import BaseModel -from qtpy.QtCore import QObject, Slot -from qtpy.QtWidgets import QComboBox, QLineEdit, QPushButton, QSpinBox, QTableWidget, QVBoxLayout - -import bec_widgets -from bec_widgets.qt_utils.error_popups import WarningPopupUtility -from bec_widgets.qt_utils.settings_dialog import SettingWidget -from bec_widgets.utils import Colors, UILoader -from bec_widgets.widgets.control.device_input.device_line_edit.device_line_edit import ( - DeviceLineEdit, -) -from bec_widgets.widgets.dap.dap_combo_box.dap_combo_box import DapComboBox -from bec_widgets.widgets.utility.visual.color_button.color_button import ColorButton - -MODULE_PATH = os.path.dirname(bec_widgets.__file__) - - -class CurveSettings(SettingWidget): - def __init__(self, parent=None, *args, **kwargs): - super().__init__(parent, *args, **kwargs) - current_path = os.path.dirname(__file__) - - self.ui = UILoader(self).loader(os.path.join(current_path, "curve_dialog.ui")) - self._setup_icons() - - self.warning_util = WarningPopupUtility(self) - - self.layout = QVBoxLayout(self) - self.layout.addWidget(self.ui) - - self.ui.add_curve.clicked.connect(self.add_curve) - self.ui.add_dap.clicked.connect(self.add_dap) - self.ui.x_mode.currentIndexChanged.connect(self.set_x_mode) - self.ui.normalize_colors_scan.clicked.connect(lambda: self.change_colormap("scan")) - self.ui.normalize_colors_dap.clicked.connect(lambda: self.change_colormap("dap")) - - def _setup_icons(self): - add_icon = material_icon(icon_name="add", size=(20, 20), convert_to_pixmap=False) - self.ui.add_dap.setIcon(add_icon) - self.ui.add_dap.setToolTip("Add DAP Curve") - self.ui.add_curve.setIcon(add_icon) - self.ui.add_curve.setToolTip("Add Scan Curve") - - @Slot(dict) - def display_current_settings(self, config: dict | BaseModel): - - # What elements should be enabled - x_name = self.target_widget.waveform._x_axis_mode["name"] - x_entry = self.target_widget.waveform._x_axis_mode["entry"] - self._enable_ui_elements(x_name, x_entry) - cm = self.target_widget.config.color_palette - self.ui.color_map_selector_scan.colormap = cm - - # Scan Curve Table - for source in ["scan_segment", "async"]: - for label, curve in config[source].items(): - row_count = self.ui.scan_table.rowCount() - self.ui.scan_table.insertRow(row_count) - DialogRow( - parent=self, - table_widget=self.ui.scan_table, - client=self.target_widget.client, - row=row_count, - config=curve.config, - ).add_scan_row() - - # Add DAP Curves - for label, curve in config["DAP"].items(): - row_count = self.ui.dap_table.rowCount() - self.ui.dap_table.insertRow(row_count) - DialogRow( - parent=self, - table_widget=self.ui.dap_table, - client=self.target_widget.client, - row=row_count, - config=curve.config, - ).add_dap_row() - - def _enable_ui_elements(self, name, entry): - if name is None: - name = "best_effort" - if name in ["index", "timestamp", "best_effort"]: - self.ui.x_mode.setCurrentText(name) - self.set_x_mode() - else: - self.ui.x_mode.setCurrentText("device") - self.set_x_mode() - self.ui.x_name.setText(name) - self.ui.x_entry.setText(entry) - - @Slot() - def set_x_mode(self): - x_mode = self.ui.x_mode.currentText() - if x_mode in ["index", "timestamp", "best_effort"]: - self.ui.x_name.setEnabled(False) - self.ui.x_entry.setEnabled(False) - self.ui.dap_table.setEnabled(False) - self.ui.add_dap.setEnabled(False) - if self.ui.dap_table.rowCount() > 0: - self.warning_util.show_warning( - title="DAP Warning", - message="DAP is not supported without specific x-axis device. All current DAP curves will be removed.", - detailed_text=f"Affected curves: {[self.ui.dap_table.cellWidget(row, 0).text() for row in range(self.ui.dap_table.rowCount())]}", - ) - else: - self.ui.x_name.setEnabled(True) - self.ui.x_entry.setEnabled(True) - self.ui.dap_table.setEnabled(True) - self.ui.add_dap.setEnabled(True) - - @Slot() - def change_colormap(self, target: Literal["scan", "dap"]): - if target == "scan": - cm = self.ui.color_map_selector_scan.colormap - table = self.ui.scan_table - if target == "dap": - cm = self.ui.color_map_selector_dap.colormap - table = self.ui.dap_table - rows = table.rowCount() - colors = Colors.golden_angle_color(colormap=cm, num=max(10, rows + 1), format="HEX") - color_button_col = 2 if target == "scan" else 3 - for row in range(rows): - table.cellWidget(row, color_button_col).set_color(colors[row]) - - @Slot() - def accept_changes(self): - self.accept_curve_changes() - - def accept_curve_changes(self): - sources = ["scan_segment", "async", "DAP"] - old_curves = [] - - for source in sources: - old_curves += list(self.target_widget.waveform._curves_data[source].values()) - for curve in old_curves: - curve.remove() - self.get_curve_params() - - def get_curve_params(self): - x_mode = self.ui.x_mode.currentText() - - if x_mode in ["index", "timestamp", "best_effort"]: - x_name = x_mode - x_entry = x_mode - else: - x_name = self.ui.x_name.text() - x_entry = self.ui.x_entry.text() - - self.target_widget.set_x(x_name=x_name, x_entry=x_entry) - - for row in range(self.ui.scan_table.rowCount()): - y_name = self.ui.scan_table.cellWidget(row, 0).text() - y_entry = self.ui.scan_table.cellWidget(row, 1).text() - color = self.ui.scan_table.cellWidget(row, 2).get_color() - style = self.ui.scan_table.cellWidget(row, 3).currentText() - width = self.ui.scan_table.cellWidget(row, 4).value() - symbol_size = self.ui.scan_table.cellWidget(row, 5).value() - self.target_widget.plot( - y_name=y_name, - y_entry=y_entry, - color=color, - pen_style=style, - pen_width=width, - symbol_size=symbol_size, - ) - - if x_mode not in ["index", "timestamp", "best_effort"]: - - for row in range(self.ui.dap_table.rowCount()): - y_name = self.ui.dap_table.cellWidget(row, 0).text() - y_entry = self.ui.dap_table.cellWidget(row, 1).text() - dap = self.ui.dap_table.cellWidget(row, 2).currentText() - color = self.ui.dap_table.cellWidget(row, 3).get_color() - style = self.ui.dap_table.cellWidget(row, 4).currentText() - width = self.ui.dap_table.cellWidget(row, 5).value() - symbol_size = self.ui.dap_table.cellWidget(row, 6).value() - - self.target_widget.add_dap( - x_name=x_name, - x_entry=x_entry, - y_name=y_name, - y_entry=y_entry, - dap=dap, - color=color, - pen_style=style, - pen_width=width, - symbol_size=symbol_size, - ) - self.target_widget.scan_history(-1) - - def add_curve(self): - row_count = self.ui.scan_table.rowCount() - self.ui.scan_table.insertRow(row_count) - DialogRow( - parent=self, - table_widget=self.ui.scan_table, - client=self.target_widget.client, - row=row_count, - config=None, - ).add_scan_row() - - def add_dap(self): - row_count = self.ui.dap_table.rowCount() - self.ui.dap_table.insertRow(row_count) - DialogRow( - parent=self, - table_widget=self.ui.dap_table, - client=self.target_widget.client, - row=row_count, - config=None, - ).add_dap_row() - - -class DialogRow(QObject): - def __init__( - self, - parent=None, - table_widget: QTableWidget = None, - row: int = None, - config: dict = None, - client=None, - ): - - super().__init__(parent=parent) - self.client = client - - self.table_widget = table_widget - self.row = row - self.config = config - self.init_default_widgets() - - def init_default_widgets(self): - - # Remove Button - self.remove_button = RemoveButton() - - # Name and Entry - self.device_line_edit = DeviceLineEdit() - self.entry_line_edit = QLineEdit() - - self.dap_combo = DapComboBox() - self.dap_combo.populate_fit_model_combobox() - self.dap_combo.select_fit_model("GaussianModel") - - # Styling - self.color_button = ColorButton() - self.style_combo = StyleComboBox() - self.width = QSpinBox() - self.width.setMinimum(1) - self.width.setMaximum(20) - self.width.setValue(4) - - self.symbol_size = QSpinBox() - self.symbol_size.setMinimum(1) - self.symbol_size.setMaximum(20) - self.symbol_size.setValue(7) - - self.remove_button.clicked.connect( - lambda: self.remove_row() - ) # From some reason do not work without lambda - - def add_scan_row(self): - if self.config is not None: - self.device_line_edit.setText(self.config.signals.y.name) - self.entry_line_edit.setText(self.config.signals.y.entry) - self.color_button.set_color(self.config.color) - self.style_combo.setCurrentText(self.config.pen_style) - self.width.setValue(self.config.pen_width) - self.symbol_size.setValue(self.config.symbol_size) - else: - default_colors = Colors.golden_angle_color( - colormap="magma", num=max(10, self.row + 1), format="HEX" - ) - default_color = default_colors[self.row] - self.color_button.set_color(default_color) - - self.table_widget.setCellWidget(self.row, 0, self.device_line_edit) - self.table_widget.setCellWidget(self.row, 1, self.entry_line_edit) - self.table_widget.setCellWidget(self.row, 2, self.color_button) - self.table_widget.setCellWidget(self.row, 3, self.style_combo) - self.table_widget.setCellWidget(self.row, 4, self.width) - self.table_widget.setCellWidget(self.row, 5, self.symbol_size) - self.table_widget.setCellWidget(self.row, 6, self.remove_button) - - def add_dap_row(self): - if self.config is not None: - self.device_line_edit.setText(self.config.signals.y.name) - self.entry_line_edit.setText(self.config.signals.y.entry) - self.dap_combo.fit_model_combobox.setCurrentText(self.config.signals.dap) - self.color_button.set_color(self.config.color) - self.style_combo.setCurrentText(self.config.pen_style) - self.width.setValue(self.config.pen_width) - self.symbol_size.setValue(self.config.symbol_size) - else: - default_colors = Colors.golden_angle_color( - colormap="magma", num=max(10, self.row + 1), format="HEX" - ) - default_color = default_colors[self.row] - self.color_button.set_color(default_color) - - self.table_widget.setCellWidget(self.row, 0, self.device_line_edit) - self.table_widget.setCellWidget(self.row, 1, self.entry_line_edit) - self.table_widget.setCellWidget(self.row, 2, self.dap_combo.fit_model_combobox) - self.table_widget.setCellWidget(self.row, 3, self.color_button) - self.table_widget.setCellWidget(self.row, 4, self.style_combo) - self.table_widget.setCellWidget(self.row, 5, self.width) - self.table_widget.setCellWidget(self.row, 6, self.symbol_size) - self.table_widget.setCellWidget(self.row, 7, self.remove_button) - - @Slot() - def remove_row(self): - row = self.table_widget.indexAt(self.remove_button.pos()).row() - self.cleanup() - self.table_widget.removeRow(row) - - def cleanup(self): - self.device_line_edit.cleanup() - - -class StyleComboBox(QComboBox): - def __init__(self, parent=None): - super().__init__(parent) - self.addItems(["solid", "dash", "dot", "dashdot"]) - - -class RemoveButton(QPushButton): - def __init__(self, parent=None): - super().__init__(parent) - icon = material_icon("disabled_by_default", size=(20, 20), convert_to_pixmap=False) - self.setIcon(icon) diff --git a/bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui b/bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui deleted file mode 100644 index 1f087e90..00000000 --- a/bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +++ /dev/null @@ -1,372 +0,0 @@ - - - Form - - - - 0 - 0 - 720 - 806 - - - - Form - - - - 2 - - - 2 - - - 2 - - - 2 - - - - - X Axis - - - - - - X Axis Mode - - - - - - - - 150 - 26 - - - - - best_effort - - - - - device - - - - - index - - - - - timestamp - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - Name - - - - - - - - - - Entry - - - - - - - - - - - - - Y Axis - - - - - - 0 - - - - Scan - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - Normalize Colors - - - - - - - 0 - - - false - - - true - - - true - - - false - - - - Name - - - - - Entry - - - - - Color - - - - - Style - - - - - Width - - - - - Symbol Size - - - - - Delete - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - DAP - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - true - - - true - - - - Name - - - - - Entry - - - - - Model - - - - - Color - - - - - Style - - - - - Width - - - - - Symbol Size - - - - - Delete - - - - - - - - Normalize Colors - - - - - - - Qt::Orientation::Horizontal - - - - 585 - 20 - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - - DeviceLineEdit - QLineEdit -
device_line_edit
-
- - BECColorMapWidget - QWidget -
bec_color_map_widget
-
-
- - -
diff --git a/bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/__init__.py b/bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py b/bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py deleted file mode 100644 index 4a1adba5..00000000 --- a/bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +++ /dev/null @@ -1,25 +0,0 @@ -from qtpy.QtWidgets import QDialog, QVBoxLayout - -from bec_widgets.widgets.dap.lmfit_dialog.lmfit_dialog import LMFitDialog - - -class FitSummaryWidget(QDialog): - - def __init__(self, parent=None, target_widget=None): - super().__init__(parent=parent) - - self.setModal(True) - self.target_widget = target_widget - self.dap_dialog = LMFitDialog(parent=self, ui_file="lmfit_dialog_compact.ui") - self.layout = QVBoxLayout(self) - self.layout.addWidget(self.dap_dialog) - self.target_widget.dap_summary_update.connect(self.dap_dialog.update_summary_tree) - self.setLayout(self.layout) - self._get_dap_from_target_widget() - - def _get_dap_from_target_widget(self) -> None: - """Get the DAP data from the target widget and update the DAP dialog manually on creation.""" - dap_summary = self.target_widget.get_dap_summary() - for curve_id, data in dap_summary.items(): - md = {"curve_id": curve_id} - self.dap_dialog.update_summary_tree(data=data, metadata=md) diff --git a/bec_widgets/widgets/plots/waveform/waveform_widget.py b/bec_widgets/widgets/plots/waveform/waveform_widget.py deleted file mode 100644 index a5be8759..00000000 --- a/bec_widgets/widgets/plots/waveform/waveform_widget.py +++ /dev/null @@ -1,751 +0,0 @@ -from __future__ import annotations - -import sys -from typing import Literal - -import numpy as np -import pyqtgraph as pg -from bec_lib.logger import bec_logger -from qtpy.QtCore import Property, Signal, Slot -from qtpy.QtWidgets import QVBoxLayout, QWidget - -from bec_widgets.qt_utils.error_popups import SafeSlot, WarningPopupUtility -from bec_widgets.qt_utils.settings_dialog import SettingsDialog -from bec_widgets.qt_utils.toolbar import MaterialIconAction, ModularToolBar, SeparatorAction -from bec_widgets.utils.bec_widget import BECWidget -from bec_widgets.widgets.containers.figure import BECFigure -from bec_widgets.widgets.containers.figure.plots.axis_settings import AxisSettings -from bec_widgets.widgets.containers.figure.plots.waveform.waveform import Waveform1DConfig -from bec_widgets.widgets.containers.figure.plots.waveform.waveform_curve import BECCurve -from bec_widgets.widgets.plots.waveform.waveform_popups.curve_dialog.curve_dialog import ( - CurveSettings, -) -from bec_widgets.widgets.plots.waveform.waveform_popups.dap_summary_dialog.dap_summary_dialog import ( - FitSummaryWidget, -) - -try: - import pandas as pd -except ImportError: - pd = None - -logger = bec_logger.logger - - -class BECWaveformWidget(BECWidget, QWidget): - PLUGIN = True - ICON_NAME = "show_chart" - USER_ACCESS = [ - "curves", - "plot", - "add_dap", - "get_dap_params", - "remove_curve", - "scan_history", - "get_all_data", - "set", - "set_x", - "set_title", - "set_x_label", - "set_y_label", - "set_x_scale", - "set_y_scale", - "set_x_lim", - "set_y_lim", - "set_legend_label_size", - "set_auto_range", - "set_grid", - "enable_fps_monitor", - "enable_scatter", - "lock_aspect_ratio", - "export", - "export_to_matplotlib", - "toggle_roi", - "select_roi", - ] - scan_signal_update = Signal() - async_signal_update = Signal() - dap_summary_update = Signal(dict, dict) - dap_params_update = Signal(dict, dict) - autorange_signal = Signal() - new_scan = Signal() - crosshair_position_changed = Signal(tuple) - crosshair_position_changed_string = Signal(str) - crosshair_position_clicked = Signal(tuple) - crosshair_position_clicked_string = Signal(str) - crosshair_coordinates_changed = Signal(tuple) - crosshair_coordinates_changed_string = Signal(str) - crosshair_coordinates_clicked = Signal(tuple) - crosshair_coordinates_clicked_string = Signal(str) - roi_changed = Signal(tuple) - roi_active = Signal(bool) - - def __init__( - self, - parent: QWidget | None = None, - config: Waveform1DConfig | dict = None, - client=None, - gui_id: str | None = None, - **kwargs, - ) -> None: - if config is None: - config = Waveform1DConfig(widget_class=self.__class__.__name__) - else: - if isinstance(config, dict): - config = Waveform1DConfig(**config) - super().__init__(client=client, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent) - - self.layout = QVBoxLayout(self) - self.layout.setSpacing(0) - self.layout.setContentsMargins(0, 0, 0, 0) - - self.fig = BECFigure() - self.toolbar = ModularToolBar( - actions={ - "save": MaterialIconAction(icon_name="save", tooltip="Open Export Dialog"), - "matplotlib": MaterialIconAction( - icon_name="photo_library", tooltip="Open Matplotlib Plot" - ), - "separator_1": SeparatorAction(), - "drag_mode": MaterialIconAction( - icon_name="drag_pan", tooltip="Drag Mouse Mode", checkable=True - ), - "rectangle_mode": MaterialIconAction( - icon_name="frame_inspect", tooltip="Rectangle Zoom Mode", checkable=True - ), - "auto_range": MaterialIconAction( - icon_name="open_in_full", tooltip="Autorange Plot" - ), - "separator_2": SeparatorAction(), - "curves": MaterialIconAction( - icon_name="timeline", tooltip="Open Curves Configuration" - ), - "fit_params": MaterialIconAction( - icon_name="monitoring", tooltip="Open Fitting Parameters" - ), - "separator_3": SeparatorAction(), - "crosshair": MaterialIconAction( - icon_name="point_scan", tooltip="Show Crosshair", checkable=True - ), - "roi_select": MaterialIconAction( - icon_name="align_justify_space_between", - tooltip="Add ROI region for DAP", - checkable=True, - ), - "separator_4": SeparatorAction(), - "fps_monitor": MaterialIconAction( - icon_name="speed", tooltip="Show FPS Monitor", checkable=True - ), - "axis_settings": MaterialIconAction( - icon_name="settings", tooltip="Open Configuration Dialog" - ), - }, - target_widget=self, - ) - - self.layout.addWidget(self.toolbar) - self.layout.addWidget(self.fig) - - self.warning_util = WarningPopupUtility(self) - - self.waveform = self.fig.plot() - self.waveform.apply_config(config) - - self.config = config - self._clear_curves_on_plot_update = False - - self.hook_waveform_signals() - self._hook_actions() - - def hook_waveform_signals(self): - self.waveform.scan_signal_update.connect(self.scan_signal_update) - self.waveform.async_signal_update.connect(self.async_signal_update) - self.waveform.dap_params_update.connect(self.dap_params_update) - self.waveform.dap_summary_update.connect(self.dap_summary_update) - self.waveform.autorange_signal.connect(self.autorange_signal) - self.waveform.new_scan.connect(self.new_scan) - self.waveform.crosshair_coordinates_changed.connect(self.crosshair_coordinates_changed) - self.waveform.crosshair_coordinates_clicked.connect(self.crosshair_coordinates_clicked) - self.waveform.crosshair_coordinates_changed.connect( - self._emit_crosshair_coordinates_changed_string - ) - self.waveform.crosshair_coordinates_clicked.connect( - self._emit_crosshair_coordinates_clicked_string - ) - self.waveform.crosshair_position_changed.connect(self.crosshair_position_changed) - self.waveform.crosshair_position_clicked.connect(self.crosshair_position_clicked) - self.waveform.crosshair_position_changed.connect( - self._emit_crosshair_position_changed_string - ) - self.waveform.crosshair_position_clicked.connect( - self._emit_crosshair_position_clicked_string - ) - self.waveform.roi_changed.connect(self.roi_changed) - self.waveform.roi_active.connect(self.roi_active) - - def _hook_actions(self): - self.toolbar.widgets["save"].action.triggered.connect(self.export) - self.toolbar.widgets["matplotlib"].action.triggered.connect(self.export_to_matplotlib) - self.toolbar.widgets["drag_mode"].action.triggered.connect(self.enable_mouse_pan_mode) - self.toolbar.widgets["rectangle_mode"].action.triggered.connect( - self.enable_mouse_rectangle_mode - ) - self.toolbar.widgets["auto_range"].action.triggered.connect(self._auto_range_from_toolbar) - self.toolbar.widgets["curves"].action.triggered.connect(self.show_curve_settings) - self.toolbar.widgets["fit_params"].action.triggered.connect(self.show_fit_summary_dialog) - self.toolbar.widgets["axis_settings"].action.triggered.connect(self.show_axis_settings) - self.toolbar.widgets["crosshair"].action.triggered.connect(self.waveform.toggle_crosshair) - self.toolbar.widgets["roi_select"].action.toggled.connect(self.waveform.toggle_roi) - self.toolbar.widgets["fps_monitor"].action.toggled.connect(self.enable_fps_monitor) - # self.toolbar.widgets["import"].action.triggered.connect( - # lambda: self.load_config(path=None, gui=True) - # ) - # self.toolbar.widgets["export"].action.triggered.connect( - # lambda: self.save_config(path=None, gui=True) - # ) - - @Slot(bool) - def toogle_roi_select(self, checked: bool): - """Toggle the linear region selector. - - Args: - checked(bool): If True, enable the linear region selector. - """ - self.toolbar.widgets["roi_select"].action.setChecked(checked) - - @Property(bool) - def clear_curves_on_plot_update(self) -> bool: - """If True, clear curves on plot update.""" - return self._clear_curves_on_plot_update - - @clear_curves_on_plot_update.setter - def clear_curves_on_plot_update(self, value: bool): - """Set the clear curves on plot update property. - - Args: - value(bool): If True, clear curves on plot update. - """ - self._clear_curves_on_plot_update = value - - @SafeSlot(tuple) - def _emit_crosshair_coordinates_changed_string(self, coordinates): - self.crosshair_coordinates_changed_string.emit(str(coordinates)) - - @SafeSlot(tuple) - def _emit_crosshair_coordinates_clicked_string(self, coordinates): - self.crosshair_coordinates_clicked_string.emit(str(coordinates)) - - @SafeSlot(tuple) - def _emit_crosshair_position_changed_string(self, position): - self.crosshair_position_changed_string.emit(str(position)) - - @SafeSlot(tuple) - def _emit_crosshair_position_clicked_string(self, position): - self.crosshair_position_clicked_string.emit(str(position)) - - ################################### - # Dialog Windows - ################################### - def show_axis_settings(self): - dialog = SettingsDialog( - self, - settings_widget=AxisSettings(), - window_title="Axis Settings", - config=self._config_dict["axis"], - ) - dialog.exec() - - def show_curve_settings(self): - dialog = SettingsDialog( - self, - settings_widget=CurveSettings(), - window_title="Curve Settings", - config=self.waveform._curves_data, - ) - dialog.resize(800, 600) - dialog.exec() - - def show_fit_summary_dialog(self): - dialog = FitSummaryWidget(target_widget=self) - dialog.resize(800, 600) - dialog.exec() - - ################################### - # User Access Methods from Waveform - ################################### - @property - def curves(self) -> list[BECCurve]: - """ - Get the curves of the plot widget as a list - Returns: - list: List of curves. - """ - return self.waveform._curves - - @curves.setter - def curves(self, value: list[BECCurve]): - self.waveform._curves = value - - def get_curve(self, identifier) -> BECCurve: - """ - Get the curve by its index or ID. - - Args: - identifier(int|str): Identifier of the curve. Can be either an integer (index) or a string (curve_id). - - Returns: - BECCurve: The curve object. - """ - return self.waveform.get_curve(identifier) - - def set_colormap(self, colormap: str): - """ - Set the colormap of the plot widget. - - Args: - colormap(str, optional): Scale the colors of curves to colormap. If None, use the default color palette. - """ - self.waveform.set_colormap(colormap) - - @Slot(str, str) # Slot for x_name, x_entry - @SafeSlot(str, popup_error=True) # Slot for x_name and - def set_x(self, x_name: str, x_entry: str | None = None): - """ - Change the x axis of the plot widget. - - Args: - x_name(str): Name of the x signal. - - "best_effort": Use the best effort signal. - - "timestamp": Use the timestamp signal. - - "index": Use the index signal. - - Custom signal name of device from BEC. - x_entry(str): Entry of the x signal. - """ - self.waveform.set_x(x_name, x_entry) - - @Slot(str) # Slot for y_name - @SafeSlot(popup_error=True) - def plot( - self, - arg1: list | np.ndarray | str | None = None, - x: list | np.ndarray | None = None, - y: list | np.ndarray | None = None, - x_name: str | None = None, - y_name: str | None = None, - z_name: str | None = None, - x_entry: str | None = None, - y_entry: str | None = None, - z_entry: str | None = None, - color: str | None = None, - color_map_z: str | None = "magma", - label: str | None = None, - validate: bool = True, - dap: str | None = None, # TODO add dap custom curve wrapper - **kwargs, - ) -> BECCurve: - """ - Plot a curve to the plot widget. - Args: - arg1(list | np.ndarray | str | None): First argument which can be x data(list | np.ndarray), y data(list | np.ndarray), or y_name(str). - x(list | np.ndarray): Custom x data to plot. - y(list | np.ndarray): Custom y data to plot. - x_name(str): The name of the device for the x-axis. - y_name(str): The name of the device for the y-axis. - z_name(str): The name of the device for the z-axis. - x_entry(str): The name of the entry for the x-axis. - y_entry(str): The name of the entry for the y-axis. - z_entry(str): The name of the entry for the z-axis. - color(str): The color of the curve. - color_map_z(str): The color map to use for the z-axis. - label(str): The label of the curve. - validate(bool): If True, validate the device names and entries. - dap(str): The dap model to use for the curve. If not specified, none will be added. - - Returns: - BECCurve: The curve object. - """ - if self.clear_curves_on_plot_update is True: - self.waveform.clear_source(source="scan_segment") - return self.waveform.plot( - arg1=arg1, - x=x, - y=y, - x_name=x_name, - y_name=y_name, - z_name=z_name, - x_entry=x_entry, - y_entry=y_entry, - z_entry=z_entry, - color=color, - color_map_z=color_map_z, - label=label, - validate=validate, - dap=dap, - **kwargs, - ) - - @Slot( - str, str, str, str, str, str, bool - ) # Slot for x_name, y_name, x_entry, y_entry, color, validate_bec - @SafeSlot(str, str, str, popup_error=True) - def add_dap( - self, - x_name: str, - y_name: str, - dap: str, - x_entry: str | None = None, - y_entry: str | None = None, - color: str | None = None, - validate_bec: bool = True, - **kwargs, - ) -> BECCurve: - """ - Add LMFIT dap model curve to the plot widget. - - Args: - x_name(str): Name of the x signal. - x_entry(str): Entry of the x signal. - y_name(str): Name of the y signal. - y_entry(str): Entry of the y signal. - color(str, optional): Color of the curve. Defaults to None. - dap(str): The dap model to use for the curve. - validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True. - **kwargs: Additional keyword arguments for the curve configuration. - - Returns: - BECCurve: The curve object. - """ - if self.clear_curves_on_plot_update is True: - self.waveform.clear_source(source="DAP") - return self.waveform.add_dap( - x_name=x_name, - y_name=y_name, - x_entry=x_entry, - y_entry=y_entry, - color=color, - dap=dap, - validate_bec=validate_bec, - **kwargs, - ) - - def get_dap_params(self) -> dict: - """ - Get the DAP parameters of all DAP curves. - - Returns: - dict: DAP parameters of all DAP curves. - """ - - return self.waveform.get_dap_params() - - def get_dap_summary(self) -> dict: - """ - Get the DAP summary of all DAP curves. - - Returns: - dict: DAP summary of all DAP curves. - """ - return self.waveform.get_dap_summary() - - def remove_curve(self, *identifiers): - """ - Remove a curve from the plot widget. - - Args: - *identifiers: Identifier of the curve to be removed. Can be either an integer (index) or a string (curve_id). - """ - self.waveform.remove_curve(*identifiers) - - def scan_history(self, scan_index: int = None, scan_id: str = None): - """ - Update the scan curves with the data from the scan storage. - Provide only one of scan_id or scan_index. - - Args: - scan_id(str, optional): ScanID of the scan to be updated. Defaults to None. - scan_index(int, optional): Index of the scan to be updated. Defaults to None. - """ - self.waveform.scan_history(scan_index, scan_id) - - def get_all_data(self, output: Literal["dict", "pandas"] = "dict") -> dict | pd.DataFrame: - """ - Extract all curve data into a dictionary or a pandas DataFrame. - - Args: - output (Literal["dict", "pandas"]): Format of the output data. - - Returns: - dict | pd.DataFrame: Data of all curves in the specified format. - """ - try: - import pandas as pd - except ImportError: - pd = None - if output == "pandas": - logger.warning( - "Pandas is not installed. " - "Please install pandas using 'pip install pandas'." - "Output will be dictionary instead." - ) - output = "dict" - return self.waveform.get_all_data(output) - - ################################### - # User Access Methods from Plotbase - ################################### - - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - self.waveform.set(**kwargs) - - def set_title(self, title: str): - """ - Set the title of the plot widget. - - Args: - title(str): Title of the plot. - """ - self.waveform.set_title(title) - - def set_x_label(self, x_label: str): - """ - Set the x-axis label of the plot widget. - - Args: - x_label(str): Label of the x-axis. - """ - self.waveform.set_x_label(x_label) - - def set_y_label(self, y_label: str): - """ - Set the y-axis label of the plot widget. - - Args: - y_label(str): Label of the y-axis. - """ - self.waveform.set_y_label(y_label) - - def set_x_scale(self, x_scale: Literal["linear", "log"]): - """ - Set the scale of the x-axis of the plot widget. - - Args: - x_scale(Literal["linear", "log"]): Scale of the x-axis. - """ - self.waveform.set_x_scale(x_scale) - - def set_y_scale(self, y_scale: Literal["linear", "log"]): - """ - Set the scale of the y-axis of the plot widget. - - Args: - y_scale(Literal["linear", "log"]): Scale of the y-axis. - """ - self.waveform.set_y_scale(y_scale) - - def set_x_lim(self, x_lim: tuple): - """ - Set the limits of the x-axis of the plot widget. - - Args: - x_lim(tuple): Limits of the x-axis. - """ - self.waveform.set_x_lim(x_lim) - - def set_y_lim(self, y_lim: tuple): - """ - Set the limits of the y-axis of the plot widget. - - Args: - y_lim(tuple): Limits of the y-axis. - """ - self.waveform.set_y_lim(y_lim) - - def set_legend_label_size(self, legend_label_size: int): - """ - Set the size of the legend labels of the plot widget. - - Args: - legend_label_size(int): Size of the legend labels. - """ - self.waveform.set_legend_label_size(legend_label_size) - - def set_auto_range(self, enabled: bool, axis: str = "xy"): - """ - Set the auto range of the plot widget. - - Args: - enabled(bool): If True, enable the auto range. - axis(str, optional): The axis to enable the auto range. - - "xy": Enable auto range for both x and y axis. - - "x": Enable auto range for x axis. - - "y": Enable auto range for y axis. - """ - self.waveform.set_auto_range(enabled, axis) - - def toggle_roi(self, checked: bool): - """Toggle the linear region selector. - - Args: - checked(bool): If True, enable the linear region selector. - """ - self.waveform.toggle_roi(checked) - if self.toolbar.widgets["roi_select"].action.isChecked() != checked: - self.toolbar.widgets["roi_select"].action.setChecked(checked) - - def select_roi(self, region: tuple): - """ - Set the region of interest of the plot widget. - - Args: - region(tuple): Region of interest. - """ - self.waveform.select_roi(region) - - def enable_fps_monitor(self, enabled: bool): - """ - Enable the FPS monitor of the plot widget. - - Args: - enabled(bool): If True, enable the FPS monitor. - """ - self.waveform.enable_fps_monitor(enabled) - if self.toolbar.widgets["fps_monitor"].action.isChecked() != enabled: - self.toolbar.widgets["fps_monitor"].action.setChecked(enabled) - - @SafeSlot() - def _auto_range_from_toolbar(self): - """ - Set the auto range of the plot widget from the toolbar. - """ - self.waveform.set_auto_range(True, "xy") - - def set_grid(self, x_grid: bool, y_grid: bool): - """ - Set the grid visibility of the plot widget. - - Args: - x_grid(bool): Visibility of the x-axis grid. - y_grid(bool): Visibility of the y-axis grid. - """ - self.waveform.set_grid(x_grid, y_grid) - - def set_outer_axes(self, show: bool): - """ - Set the outer axes visibility of the plot widget. - - Args: - show(bool): Visibility of the outer axes. - """ - self.waveform.set_outer_axes(show) - - def enable_scatter(self, enabled: bool): - """ - Enable the scatter plot of the plot widget. - - Args: - enabled(bool): If True, enable the scatter plot. - """ - self.waveform.enable_scatter(enabled) - - def lock_aspect_ratio(self, lock: bool): - """ - Lock the aspect ratio of the plot widget. - - Args: - lock(bool): Lock the aspect ratio. - """ - self.waveform.lock_aspect_ratio(lock) - - @SafeSlot() - def enable_mouse_rectangle_mode(self): - self.toolbar.widgets["rectangle_mode"].action.setChecked(True) - self.toolbar.widgets["drag_mode"].action.setChecked(False) - self.waveform.plot_item.getViewBox().setMouseMode(pg.ViewBox.RectMode) - - @SafeSlot() - def enable_mouse_pan_mode(self): - self.toolbar.widgets["drag_mode"].action.setChecked(True) - self.toolbar.widgets["rectangle_mode"].action.setChecked(False) - self.waveform.plot_item.getViewBox().setMouseMode(pg.ViewBox.PanMode) - - def export(self): - """ - Show the export dialog for the plot widget. - """ - self.waveform.export() - - def export_to_matplotlib(self): - """ - Export the plot widget to Matplotlib. - """ - try: - import matplotlib as mpl - except ImportError: - self.warning_util.show_warning( - title="Matplotlib not installed", - message="Matplotlib is required for this feature.", - detailed_text="Please install matplotlib in your Python environment by using 'pip install matplotlib'.", - ) - return - self.waveform.export_to_matplotlib() - - ####################################### - # User Access Methods from BECConnector - ###################################### - def load_config(self, path: str | None = None, gui: bool = False): - """ - Load the configuration of the widget from YAML. - - Args: - path(str): Path to the configuration file for non-GUI dialog mode. - gui(bool): If True, use the GUI dialog to load the configuration file. - """ - self.fig.load_config(path=path, gui=gui) - - def save_config(self, path: str | None = None, gui: bool = False): - """ - Save the configuration of the widget to YAML. - - Args: - path(str): Path to save the configuration file for non-GUI dialog mode. - gui(bool): If True, use the GUI dialog to save the configuration file. - """ - self.fig.save_config(path=path, gui=gui) - - def cleanup(self): - self.fig.cleanup() - return super().cleanup() - - -def main(): # pragma: no cover - from qtpy.QtWidgets import QApplication - - app = QApplication(sys.argv) - widget = BECWaveformWidget() - widget.plot(x_name="samx", y_name="bpm4i") - widget.plot(y_name="bpm3i") - widget.plot(y_name="bpm4a") - widget.plot(y_name="bpm5i") - widget.show() - sys.exit(app.exec_()) - - -if __name__ == "__main__": # pragma: no cover - main() diff --git a/tests/unit_tests/test_bec_dock.py b/tests/unit_tests/test_bec_dock.py index e205a0f5..fe50a219 100644 --- a/tests/unit_tests/test_bec_dock.py +++ b/tests/unit_tests/test_bec_dock.py @@ -115,7 +115,7 @@ def test_undock_and_dock_docks(bec_dock_area, qtbot): def test_toolbar_add_plot_waveform(bec_dock_area): bec_dock_area.toolbar.widgets["menu_plots"].widgets["waveform"].trigger() assert "waveform_1" in bec_dock_area.panels - assert bec_dock_area.panels["waveform_1"].widgets[0].config.widget_class == "BECWaveformWidget" + assert bec_dock_area.panels["waveform_1"].widgets[0].config.widget_class == "Waveform" def test_toolbar_add_plot_image(bec_dock_area): diff --git a/tests/unit_tests/test_crosshair.py b/tests/unit_tests/test_crosshair.py index 8c614a55..5f0bf98a 100644 --- a/tests/unit_tests/test_crosshair.py +++ b/tests/unit_tests/test_crosshair.py @@ -1,9 +1,10 @@ import numpy as np +import pyqtgraph as pg import pytest from qtpy.QtCore import QPointF, Qt +from bec_widgets.utils import Crosshair from bec_widgets.widgets.plots.image.image_widget import BECImageWidget -from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget from .client_mocks import mocked_client @@ -11,14 +12,16 @@ from .client_mocks import mocked_client @pytest.fixture -def plot_widget_with_crosshair(qtbot, mocked_client): - widget = BECWaveformWidget(client=mocked_client()) - widget.plot(x=[1, 2, 3], y=[4, 5, 6]) - widget.waveform.hook_crosshair() +def plot_widget_with_crosshair(qtbot): + widget = pg.PlotWidget() qtbot.addWidget(widget) qtbot.waitExposed(widget) - yield widget.waveform.crosshair, widget.waveform.plot_item + widget.plot(x=[1, 2, 3], y=[4, 5, 6], name="Curve 1") + plot_item = widget.getPlotItem() + crosshair = Crosshair(plot_item=plot_item, precision=3) + + yield crosshair, plot_item @pytest.fixture @@ -35,15 +38,14 @@ def image_widget_with_crosshair(qtbot, mocked_client): def test_mouse_moved_lines(plot_widget_with_crosshair): crosshair, plot_item = plot_widget_with_crosshair - # Simulate a mouse moved event at a specific position pos_in_view = QPointF(2, 5) pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view) event_mock = [pos_in_scene] - # Call the mouse_moved method + # Simulate mouse movement crosshair.mouse_moved(event_mock) - # Assert the expected behavior + # Check that the vertical line is indeed at x=2 assert np.isclose(crosshair.v_line.pos().x(), 2) assert np.isclose(crosshair.h_line.pos().y(), 5) diff --git a/tests/unit_tests/test_waveform_widget.py b/tests/unit_tests/test_waveform_widget.py deleted file mode 100644 index d40a0f4b..00000000 --- a/tests/unit_tests/test_waveform_widget.py +++ /dev/null @@ -1,573 +0,0 @@ -from unittest.mock import MagicMock, patch - -import pyqtgraph as pg -import pytest -from qtpy.QtGui import QColor -from qtpy.QtWidgets import QApplication - -from bec_widgets.qt_utils.settings_dialog import SettingsDialog -from bec_widgets.utils.colors import apply_theme, get_theme_palette, set_theme -from bec_widgets.utils.linear_region_selector import LinearRegionWrapper -from bec_widgets.widgets.containers.figure.plots.axis_settings import AxisSettings -from bec_widgets.widgets.plots.waveform.waveform_popups.curve_dialog.curve_dialog import ( - CurveSettings, -) -from bec_widgets.widgets.plots.waveform.waveform_popups.dap_summary_dialog.dap_summary_dialog import ( - FitSummaryWidget, -) -from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget - -from .client_mocks import mocked_client -from .conftest import create_widget - - -@pytest.fixture -def waveform_widget(qtbot, mocked_client): - models = ["GaussianModel", "LorentzModel", "SineModel"] - mocked_client.dap._available_dap_plugins.keys.return_value = models - widget = BECWaveformWidget(client=mocked_client()) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - yield widget - - -@pytest.fixture -def mock_waveform(waveform_widget): - waveform_mock = MagicMock() - waveform_widget.waveform = waveform_mock - return waveform_mock - - -def test_waveform_widget_init(waveform_widget): - assert waveform_widget is not None - assert waveform_widget.client is not None - assert isinstance(waveform_widget, BECWaveformWidget) - assert waveform_widget.config.widget_class == "BECWaveformWidget" - - -################################### -# Wrapper methods for Waveform -################################### - - -def test_waveform_widget_get_curve(waveform_widget, mock_waveform): - waveform_widget.get_curve("curve_id") - waveform_widget.waveform.get_curve.assert_called_once_with("curve_id") - - -def test_waveform_widget_set_colormap(waveform_widget, mock_waveform): - waveform_widget.set_colormap("colormap") - waveform_widget.waveform.set_colormap.assert_called_once_with("colormap") - - -def test_waveform_widget_set_x(waveform_widget, mock_waveform): - waveform_widget.set_x("samx", "samx") - waveform_widget.waveform.set_x.assert_called_once_with("samx", "samx") - - -def test_waveform_plot_data(waveform_widget, mock_waveform): - waveform_widget.plot(x=[1, 2, 3], y=[1, 2, 3]) - waveform_widget.waveform.plot.assert_called_once_with( - arg1=None, - x=[1, 2, 3], - y=[1, 2, 3], - x_name=None, - y_name=None, - z_name=None, - x_entry=None, - y_entry=None, - z_entry=None, - color=None, - color_map_z="magma", - label=None, - validate=True, - dap=None, - ) - - -def test_waveform_plot_scan_curves(waveform_widget, mock_waveform): - waveform_widget.plot(x_name="samx", y_name="samy", dap="GaussianModel") - waveform_widget.waveform.plot.assert_called_once_with( - arg1=None, - x=None, - y=None, - x_name="samx", - y_name="samy", - z_name=None, - x_entry=None, - y_entry=None, - z_entry=None, - color=None, - color_map_z="magma", - label=None, - validate=True, - dap="GaussianModel", - ) - - -def test_waveform_widget_add_dap(waveform_widget, mock_waveform): - waveform_widget.add_dap(x_name="samx", y_name="bpm4i", dap="GaussianModel") - waveform_widget.waveform.add_dap.assert_called_once_with( - x_name="samx", - y_name="bpm4i", - x_entry=None, - y_entry=None, - color=None, - dap="GaussianModel", - validate_bec=True, - ) - - -def test_waveform_widget_get_dap_params(waveform_widget, mock_waveform): - waveform_widget.get_dap_params() - waveform_widget.waveform.get_dap_params.assert_called_once() - - -def test_waveform_widget_get_dap_summary(waveform_widget, mock_waveform): - waveform_widget.get_dap_summary() - waveform_widget.waveform.get_dap_summary.assert_called_once() - - -def test_waveform_widget_remove_curve(waveform_widget, mock_waveform): - waveform_widget.remove_curve("curve_id") - waveform_widget.waveform.remove_curve.assert_called_once_with("curve_id") - - -def test_waveform_widget_scan_history(waveform_widget, mock_waveform): - waveform_widget.scan_history(0) - waveform_widget.waveform.scan_history.assert_called_once_with(0, None) - - -def test_waveform_widget_get_all_data(waveform_widget, mock_waveform): - waveform_widget.get_all_data() - waveform_widget.waveform.get_all_data.assert_called_once() - - -def test_waveform_widget_set_title(waveform_widget, mock_waveform): - waveform_widget.set_title("Title") - waveform_widget.waveform.set_title.assert_called_once_with("Title") - - -def test_waveform_widget_set_base(waveform_widget, mock_waveform): - waveform_widget.set( - title="Test Title", - x_label="X Label", - y_label="Y Label", - x_scale="linear", - y_scale="log", - x_lim=(0, 10), - y_lim=(0, 10), - legend_label_size=12, - ) - waveform_widget.waveform.set.assert_called_once_with( - title="Test Title", - x_label="X Label", - y_label="Y Label", - x_scale="linear", - y_scale="log", - x_lim=(0, 10), - y_lim=(0, 10), - legend_label_size=12, - ) - - -def test_waveform_widget_set_x_label(waveform_widget, mock_waveform): - waveform_widget.set_x_label("X Label") - waveform_widget.waveform.set_x_label.assert_called_once_with("X Label") - - -def test_waveform_widget_set_y_label(waveform_widget, mock_waveform): - waveform_widget.set_y_label("Y Label") - waveform_widget.waveform.set_y_label.assert_called_once_with("Y Label") - - -def test_waveform_widget_set_x_scale(waveform_widget, mock_waveform): - waveform_widget.set_x_scale("linear") - waveform_widget.waveform.set_x_scale.assert_called_once_with("linear") - - -def test_waveform_widget_set_y_scale(waveform_widget, mock_waveform): - waveform_widget.set_y_scale("log") - waveform_widget.waveform.set_y_scale.assert_called_once_with("log") - - -def test_waveform_widget_set_x_lim(waveform_widget, mock_waveform): - waveform_widget.set_x_lim((0, 10)) - waveform_widget.waveform.set_x_lim.assert_called_once_with((0, 10)) - - -def test_waveform_widget_set_y_lim(waveform_widget, mock_waveform): - waveform_widget.set_y_lim((0, 10)) - waveform_widget.waveform.set_y_lim.assert_called_once_with((0, 10)) - - -def test_waveform_widget_set_legend_label_size(waveform_widget, mock_waveform): - waveform_widget.set_legend_label_size(12) - waveform_widget.waveform.set_legend_label_size.assert_called_once_with(12) - - -def test_waveform_widget_set_auto_range(waveform_widget, mock_waveform): - waveform_widget.set_auto_range(True, "xy") - waveform_widget.waveform.set_auto_range.assert_called_once_with(True, "xy") - - -def test_waveform_widget_set_grid(waveform_widget, mock_waveform): - waveform_widget.set_grid(True, False) - waveform_widget.waveform.set_grid.assert_called_once_with(True, False) - - -def test_waveform_widget_lock_aspect_ratio(waveform_widget, mock_waveform): - waveform_widget.lock_aspect_ratio(True) - waveform_widget.waveform.lock_aspect_ratio.assert_called_once_with(True) - - -def test_waveform_widget_export(waveform_widget, mock_waveform): - waveform_widget.export() - waveform_widget.waveform.export.assert_called_once() - - -################################### -# ToolBar interactions -################################### - - -def test_toolbar_drag_mode_action_triggered(waveform_widget, qtbot): - action_drag = waveform_widget.toolbar.widgets["drag_mode"].action - action_rectangle = waveform_widget.toolbar.widgets["rectangle_mode"].action - action_drag.trigger() - assert action_drag.isChecked() == True - assert action_rectangle.isChecked() == False - - -def test_toolbar_rectangle_mode_action_triggered(waveform_widget, qtbot): - action_drag = waveform_widget.toolbar.widgets["drag_mode"].action - action_rectangle = waveform_widget.toolbar.widgets["rectangle_mode"].action - action_rectangle.trigger() - assert action_drag.isChecked() == False - assert action_rectangle.isChecked() == True - - -def test_toolbar_auto_range_action_triggered(waveform_widget, mock_waveform, qtbot): - action = waveform_widget.toolbar.widgets["auto_range"].action - action.trigger() - qtbot.wait(200) - waveform_widget.waveform.set_auto_range.assert_called_once_with(True, "xy") - - -def test_enable_mouse_pan_mode(qtbot, waveform_widget): - action_drag = waveform_widget.toolbar.widgets["drag_mode"].action - action_rectangle = waveform_widget.toolbar.widgets["rectangle_mode"].action - - mock_view_box = MagicMock() - waveform_widget.waveform.plot_item.getViewBox = MagicMock(return_value=mock_view_box) - - waveform_widget.enable_mouse_pan_mode() - - assert action_drag.isChecked() == True - assert action_rectangle.isChecked() == False - mock_view_box.setMouseMode.assert_called_once_with(pg.ViewBox.PanMode) - - -################################### -# Curve Dialog Tests -################################### -def show_curve_dialog(qtbot, waveform_widget): - curve_dialog = SettingsDialog( - waveform_widget, - settings_widget=CurveSettings(), - window_title="Curve Settings", - config=waveform_widget.waveform._curves_data, - ) - qtbot.addWidget(curve_dialog) - qtbot.waitExposed(curve_dialog) - return curve_dialog - - -def test_curve_dialog_scan_curves_interactions(qtbot, waveform_widget): - waveform_widget.plot(y_name="bpm4i") - waveform_widget.plot(y_name="bpm3a") - - curve_dialog = show_curve_dialog(qtbot, waveform_widget) - - # Check default display of config from waveform widget - assert curve_dialog is not None - assert curve_dialog.widget.ui.scan_table.rowCount() == 2 - assert curve_dialog.widget.ui.scan_table.cellWidget(0, 0).text() == "bpm4i" - assert curve_dialog.widget.ui.scan_table.cellWidget(0, 1).text() == "bpm4i" - assert curve_dialog.widget.ui.scan_table.cellWidget(1, 0).text() == "bpm3a" - assert curve_dialog.widget.ui.scan_table.cellWidget(1, 1).text() == "bpm3a" - assert curve_dialog.widget.ui.x_mode.currentText() == "best_effort" - assert curve_dialog.widget.ui.x_name.isEnabled() == False - assert curve_dialog.widget.ui.x_entry.isEnabled() == False - - # Add a new curve - curve_dialog.widget.ui.add_curve.click() - qtbot.wait(200) - assert curve_dialog.widget.ui.scan_table.rowCount() == 3 - - # Set device to new curve - curve_dialog.widget.ui.scan_table.cellWidget(2, 0).setText("bpm3i") - - # Change the x mode to device - curve_dialog.widget.ui.x_mode.setCurrentText("device") - qtbot.wait(200) - assert curve_dialog.widget.ui.x_name.isEnabled() == True - assert curve_dialog.widget.ui.x_entry.isEnabled() == True - - # Set the x device - curve_dialog.widget.ui.x_name.setText("samx") - - # Delete first curve ('bpm4i') - curve_dialog.widget.ui.scan_table.cellWidget(0, 6).click() - qtbot.wait(200) - assert curve_dialog.widget.ui.scan_table.rowCount() == 2 - assert curve_dialog.widget.ui.scan_table.cellWidget(0, 0).text() == "bpm3a" - assert curve_dialog.widget.ui.scan_table.cellWidget(0, 1).text() == "bpm3a" - assert curve_dialog.widget.ui.scan_table.cellWidget(1, 0).text() == "bpm3i" - - # Close the dialog - curve_dialog.accept() - qtbot.wait(200) - - # Check the curve data in the target widget - assert list(waveform_widget.waveform._curves_data["scan_segment"].keys()) == [ - "bpm3a-bpm3a", - "bpm3i-bpm3i", - ] - assert len(waveform_widget.curves) == 2 - - -def test_curve_dialog_async(qtbot, waveform_widget): - waveform_widget.plot(y_name="bpm4i") - waveform_widget.plot(y_name="async_device") - - curve_dialog = show_curve_dialog(qtbot, waveform_widget) - - assert curve_dialog is not None - assert curve_dialog.widget.ui.scan_table.rowCount() == 2 - assert curve_dialog.widget.ui.scan_table.cellWidget(0, 0).text() == "bpm4i" - assert curve_dialog.widget.ui.scan_table.cellWidget(0, 1).text() == "bpm4i" - assert curve_dialog.widget.ui.scan_table.cellWidget(1, 0).text() == "async_device" - assert curve_dialog.widget.ui.scan_table.cellWidget(1, 1).text() == "async_device" - - -def test_curve_dialog_dap(qtbot, waveform_widget): - # Don't use default dap for curve_dialog dialog - waveform_widget.plot(x_name="samx", y_name="bpm4i", dap="LorentzModel") - - curve_dialog = show_curve_dialog(qtbot, waveform_widget) - - assert curve_dialog is not None - assert curve_dialog.widget.ui.scan_table.rowCount() == 1 - assert curve_dialog.widget.ui.scan_table.cellWidget(0, 0).text() == "bpm4i" - assert curve_dialog.widget.ui.scan_table.cellWidget(0, 1).text() == "bpm4i" - assert curve_dialog.widget.ui.dap_table.isEnabled() == True - assert curve_dialog.widget.ui.dap_table.rowCount() == 1 - assert curve_dialog.widget.ui.dap_table.cellWidget(0, 0).text() == "bpm4i" - assert curve_dialog.widget.ui.dap_table.cellWidget(0, 1).text() == "bpm4i" - assert curve_dialog.widget.ui.dap_table.cellWidget(0, 2).currentText() == "LorentzModel" - assert curve_dialog.widget.ui.x_mode.currentText() == "device" - assert curve_dialog.widget.ui.x_name.isEnabled() == True - assert curve_dialog.widget.ui.x_entry.isEnabled() == True - assert curve_dialog.widget.ui.x_name.text() == "samx" - assert curve_dialog.widget.ui.x_entry.text() == "samx" - - curve_dialog.accept() - qtbot.wait(200) - - assert list(waveform_widget.waveform._curves_data["scan_segment"].keys()) == ["bpm4i-bpm4i"] - assert len(waveform_widget.curves) == 2 - - -def test_fit_dialog_summary(qtbot, waveform_widget): - """Test the fit dialog summary widget""" - waveform_widget.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel") - fit_dialog_summary = create_widget(qtbot, FitSummaryWidget, target_widget=waveform_widget) - assert fit_dialog_summary.dap_dialog.fit_curve_id == "bpm4i-bpm4i-GaussianModel" - assert fit_dialog_summary.dap_dialog.ui.curve_list.count() == 1 - - -################################### -# Axis Dialog Tests -################################### - - -def show_axis_dialog(qtbot, waveform_widget): - axis_dialog = SettingsDialog( - waveform_widget, - settings_widget=AxisSettings(), - window_title="Axis Settings", - config=waveform_widget._config_dict["axis"], - ) - qtbot.addWidget(axis_dialog) - qtbot.waitExposed(axis_dialog) - return axis_dialog - - -def test_axis_dialog_with_axis_limits(qtbot, waveform_widget): - waveform_widget.set( - title="Test Title", - x_label="X Label", - y_label="Y Label", - x_scale="linear", - y_scale="log", - x_lim=(0, 10), - y_lim=(0, 10), - ) - - axis_dialog = show_axis_dialog(qtbot, waveform_widget) - - assert axis_dialog is not None - assert axis_dialog.widget.ui.plot_title.text() == "Test Title" - assert axis_dialog.widget.ui.x_label.text() == "X Label" - assert axis_dialog.widget.ui.y_label.text() == "Y Label" - assert axis_dialog.widget.ui.x_scale.currentText() == "linear" - assert axis_dialog.widget.ui.y_scale.currentText() == "log" - assert axis_dialog.widget.ui.x_min.value() == 0 - assert axis_dialog.widget.ui.x_max.value() == 10 - assert axis_dialog.widget.ui.y_min.value() == 0 - assert axis_dialog.widget.ui.y_max.value() == 10 - - -def test_axis_dialog_without_axis_limits(qtbot, waveform_widget): - waveform_widget.set( - title="Test Title", x_label="X Label", y_label="Y Label", x_scale="linear", y_scale="log" - ) - x_range = waveform_widget.fig.widget_list[0].plot_item.viewRange()[0] - y_range = waveform_widget.fig.widget_list[0].plot_item.viewRange()[1] - - axis_dialog = show_axis_dialog(qtbot, waveform_widget) - - assert axis_dialog is not None - assert axis_dialog.widget.ui.plot_title.text() == "Test Title" - assert axis_dialog.widget.ui.x_label.text() == "X Label" - assert axis_dialog.widget.ui.y_label.text() == "Y Label" - assert axis_dialog.widget.ui.x_scale.currentText() == "linear" - assert axis_dialog.widget.ui.y_scale.currentText() == "log" - assert axis_dialog.widget.ui.x_min.value() == x_range[0] - assert axis_dialog.widget.ui.x_max.value() == x_range[1] - assert axis_dialog.widget.ui.y_min.value() == y_range[0] - assert axis_dialog.widget.ui.y_max.value() == y_range[1] - - -def test_axis_dialog_set_properties(qtbot, waveform_widget): - axis_dialog = show_axis_dialog(qtbot, waveform_widget) - - axis_dialog.widget.ui.plot_title.setText("New Title") - axis_dialog.widget.ui.x_label.setText("New X Label") - axis_dialog.widget.ui.y_label.setText("New Y Label") - axis_dialog.widget.ui.x_scale.setCurrentText("log") - axis_dialog.widget.ui.y_scale.setCurrentText("linear") - axis_dialog.widget.ui.x_min.setValue(5) - axis_dialog.widget.ui.x_max.setValue(15) - axis_dialog.widget.ui.y_min.setValue(5) - axis_dialog.widget.ui.y_max.setValue(15) - - axis_dialog.accept() - - assert waveform_widget._config_dict["axis"]["title"] == "New Title" - assert waveform_widget._config_dict["axis"]["x_label"] == "New X Label" - assert waveform_widget._config_dict["axis"]["y_label"] == "New Y Label" - assert waveform_widget._config_dict["axis"]["x_scale"] == "log" - assert waveform_widget._config_dict["axis"]["y_scale"] == "linear" - assert waveform_widget._config_dict["axis"]["x_lim"] == (5, 15) - assert waveform_widget._config_dict["axis"]["y_lim"] == (5, 15) - - -def test_waveform_widget_theme_update(qtbot, waveform_widget): - """Test theme update for waveform widget.""" - qapp = QApplication.instance() - - # Set the theme directly; equivalent to clicking the dark mode button - # The background color should be black and the axis color should be white - set_theme("dark") - palette = get_theme_palette() - waveform_color_dark = waveform_widget.waveform.plot_item.getAxis("left").pen().color() - bg_color = waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor(20, 20, 20) - assert waveform_color_dark == palette.text().color() - - # Set the theme to light; equivalent to clicking the light mode button - # The background color should be white and the axis color should be black - set_theme("light") - palette = get_theme_palette() - waveform_color_light = waveform_widget.waveform.plot_item.getAxis("left").pen().color() - bg_color = waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor(233, 236, 239) - assert waveform_color_light == palette.text().color() - - assert waveform_color_dark != waveform_color_light - - # Set the theme to auto; equivalent starting the application with no theme set - set_theme("auto") - # Simulate that the OS theme changes to dark - qapp.theme_signal.theme_updated.emit("dark") - apply_theme("dark") - - # The background color should be black and the axis color should be white - # As we don't have access to the listener here, we can't test the palette change. Instead, - # we compare the waveform color to the dark theme color - waveform_color = waveform_widget.waveform.plot_item.getAxis("left").pen().color() - bg_color = waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor(20, 20, 20) - assert waveform_color == waveform_color_dark - - -def test_waveform_roi_selection_creation(waveform_widget, qtbot): - """Test ROI selection for waveform widget. - - This checks that the ROI select is properly created and removed when the button is toggled. - """ - # Check if curve is create upon ROI select slot - # This also checks that the button in the toolbar works - container = [] - - def callback(msg): - container.append(msg) - - waveform_widget.waveform.roi_active.connect(callback) - assert waveform_widget.waveform.roi_select is None - assert waveform_widget.waveform.roi_region == (None, None) - # Toggle the ROI select - waveform_widget.toogle_roi_select(True) - assert isinstance(waveform_widget.waveform.roi_select, LinearRegionWrapper) - # This is the default region for the pg.LinearRegionItem - assert waveform_widget.waveform.roi_region == (0, 1) - # Untoggle the ROI select - waveform_widget.toogle_roi_select(False) - assert waveform_widget.waveform.roi_select is None - assert container[0] is True - assert container[1] is False - - -def test_waveform_roi_selection_updates_fit(waveform_widget, qtbot): - """This test checks that upon selection of a new region, the fit is updated and all signals are emitted as expected.""" - container = [] - - def callback(msg): - container.append(msg) - - waveform_widget.waveform.roi_changed.connect(callback) - # Mock refresh_dap method - with patch.object(waveform_widget.waveform, "refresh_dap") as mock_refresh_dap: - waveform_widget.toogle_roi_select(True) - waveform_widget.waveform.roi_select.linear_region_selector.setRegion([0.5, 1.5]) - qtbot.wait(200) - assert waveform_widget.waveform.roi_region == (0.5, 1.5) - waveform_widget.toogle_roi_select(False) - assert waveform_widget.waveform.roi_region == (None, None) - assert len(container) == 1 - assert container[0] == (0.5, 1.5) - # 3 refresh DAP calls: 1x upon hook, 1x unhook and 1x from roi_changed - assert mock_refresh_dap.call_count == 3 - - -def test_waveform_roi_selection_change_color(waveform_widget, qtbot): - """This test checks that the color of the ROI region can be changed.""" - waveform_widget.toogle_roi_select(True) - waveform_widget.waveform.roi_select.change_roi_color((QColor("red"), QColor("blue"))) - # I can only get the brush from the RegionSelectItem - assert ( - waveform_widget.waveform.roi_select.linear_region_selector.currentBrush.color() - == QColor("red") - )