mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 19:21:50 +02:00
refactor(waveform_widget): removed and replaced by Waveform
This commit is contained in:
@ -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"""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -1 +0,0 @@
|
||||
{'files': ['waveform_widget.py']}
|
@ -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 = """
|
||||
<ui language='c++'>
|
||||
<widget class='BECWaveformWidget' name='bec_waveform_widget'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
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()
|
@ -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()
|
@ -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)
|
@ -1,372 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>720</width>
|
||||
<height>806</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="x_group_box">
|
||||
<property name="title">
|
||||
<string>X Axis</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,1,3,1,3">
|
||||
<item>
|
||||
<widget class="QLabel" name="x_mode_label">
|
||||
<property name="text">
|
||||
<string>X Axis Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="x_mode">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>26</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>best_effort</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>device</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>index</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>timestamp</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="x_name_label">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="DeviceLineEdit" name="x_name"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="x_entry_label">
|
||||
<property name="text">
|
||||
<string>Entry</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="x_entry"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="y_group_box">
|
||||
<property name="title">
|
||||
<string>Y Axis</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab_scan">
|
||||
<attribute name="title">
|
||||
<string>Scan</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="normalize_colors_scan">
|
||||
<property name="text">
|
||||
<string>Normalize Colors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="4">
|
||||
<widget class="QTableWidget" name="scan_table">
|
||||
<property name="rowCount">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Entry</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Style</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Width</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Symbol Size</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="add_curve">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="BECColorMapWidget" name="color_map_selector_scan">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_DAP">
|
||||
<attribute name="title">
|
||||
<string>DAP</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item row="1" column="0" colspan="4">
|
||||
<widget class="QTableWidget" name="dap_table">
|
||||
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Entry</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Model</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Style</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Width</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Symbol Size</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="normalize_colors_dap">
|
||||
<property name="text">
|
||||
<string>Normalize Colors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>585</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="add_dap">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="BECColorMapWidget" name="color_map_selector_dap">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DeviceLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>device_line_edit</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BECColorMapWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>bec_color_map_widget</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -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)
|
@ -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()
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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")
|
||||
)
|
Reference in New Issue
Block a user