mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
feat: add Dap dialog widget
This commit is contained in:
@ -26,6 +26,7 @@ class Widgets(str, enum.Enum):
|
|||||||
DeviceBrowser = "DeviceBrowser"
|
DeviceBrowser = "DeviceBrowser"
|
||||||
DeviceComboBox = "DeviceComboBox"
|
DeviceComboBox = "DeviceComboBox"
|
||||||
DeviceLineEdit = "DeviceLineEdit"
|
DeviceLineEdit = "DeviceLineEdit"
|
||||||
|
LMFitDialog = "LMFitDialog"
|
||||||
PositionerBox = "PositionerBox"
|
PositionerBox = "PositionerBox"
|
||||||
PositionerControlLine = "PositionerControlLine"
|
PositionerControlLine = "PositionerControlLine"
|
||||||
ResetButton = "ResetButton"
|
ResetButton = "ResetButton"
|
||||||
@ -1825,15 +1826,6 @@ class BECWaveform(RPCBase):
|
|||||||
x_entry(str): Entry of the x signal.
|
x_entry(str): Entry of the x signal.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@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
|
@rpc_call
|
||||||
def remove_curve(self, *identifiers):
|
def remove_curve(self, *identifiers):
|
||||||
"""
|
"""
|
||||||
@ -2096,10 +2088,10 @@ class BECWaveformWidget(RPCBase):
|
|||||||
self,
|
self,
|
||||||
x_name: "str",
|
x_name: "str",
|
||||||
y_name: "str",
|
y_name: "str",
|
||||||
|
dap: "str",
|
||||||
x_entry: "str | None" = None,
|
x_entry: "str | None" = None,
|
||||||
y_entry: "str | None" = None,
|
y_entry: "str | None" = None,
|
||||||
color: "str | None" = None,
|
color: "str | None" = None,
|
||||||
dap: "str" = "GaussianModel",
|
|
||||||
validate_bec: "bool" = True,
|
validate_bec: "bool" = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> "BECCurve":
|
) -> "BECCurve":
|
||||||
@ -2120,15 +2112,6 @@ class BECWaveformWidget(RPCBase):
|
|||||||
BECCurve: The curve object.
|
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
|
@rpc_call
|
||||||
def remove_curve(self, *identifiers):
|
def remove_curve(self, *identifiers):
|
||||||
"""
|
"""
|
||||||
@ -2313,7 +2296,7 @@ class BECWaveformWidget(RPCBase):
|
|||||||
|
|
||||||
class DarkModeButton(RPCBase):
|
class DarkModeButton(RPCBase):
|
||||||
@rpc_call
|
@rpc_call
|
||||||
def toggle_dark_mode(self) -> None:
|
def toggle_dark_mode(self) -> "None":
|
||||||
"""
|
"""
|
||||||
Toggle the dark mode state. This will change the theme of the entire
|
Toggle the dark mode state. This will change the theme of the entire
|
||||||
application to dark or light mode.
|
application to dark or light mode.
|
||||||
@ -2392,6 +2375,24 @@ class DeviceLineEdit(RPCBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class LMFitDialog(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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class PositionerBox(RPCBase):
|
class PositionerBox(RPCBase):
|
||||||
@rpc_call
|
@rpc_call
|
||||||
def set_positioner(self, positioner: "str | Positioner"):
|
def set_positioner(self, positioner: "str | Positioner"):
|
||||||
|
@ -50,7 +50,6 @@ class BECWaveform(BECPlotBase):
|
|||||||
"plot",
|
"plot",
|
||||||
"add_dap",
|
"add_dap",
|
||||||
"set_x",
|
"set_x",
|
||||||
"get_dap_params",
|
|
||||||
"remove_curve",
|
"remove_curve",
|
||||||
"scan_history",
|
"scan_history",
|
||||||
"curves",
|
"curves",
|
||||||
@ -74,8 +73,8 @@ class BECWaveform(BECPlotBase):
|
|||||||
]
|
]
|
||||||
scan_signal_update = pyqtSignal()
|
scan_signal_update = pyqtSignal()
|
||||||
async_signal_update = pyqtSignal()
|
async_signal_update = pyqtSignal()
|
||||||
dap_params_update = pyqtSignal(dict)
|
dap_params_update = pyqtSignal(dict, dict)
|
||||||
dap_summary_update = pyqtSignal(dict)
|
dap_summary_update = pyqtSignal(dict, dict)
|
||||||
autorange_signal = pyqtSignal()
|
autorange_signal = pyqtSignal()
|
||||||
new_scan = pyqtSignal()
|
new_scan = pyqtSignal()
|
||||||
|
|
||||||
@ -1085,8 +1084,9 @@ class BECWaveform(BECPlotBase):
|
|||||||
curve.setData(x, y)
|
curve.setData(x, y)
|
||||||
curve.dap_params = msg["data"][1]["fit_parameters"]
|
curve.dap_params = msg["data"][1]["fit_parameters"]
|
||||||
curve.dap_summary = msg["data"][1]["fit_summary"]
|
curve.dap_summary = msg["data"][1]["fit_summary"]
|
||||||
self.dap_params_update.emit(curve.dap_params)
|
metadata.update({"curve_id": curve_id_request})
|
||||||
self.dap_summary_update.emit(curve.dap_summary)
|
self.dap_params_update.emit(curve.dap_params, metadata)
|
||||||
|
self.dap_summary_update.emit(curve.dap_summary, metadata)
|
||||||
break
|
break
|
||||||
|
|
||||||
@Slot(dict, dict)
|
@Slot(dict, dict)
|
||||||
|
0
bec_widgets/widgets/lmfit_dialog/__init__.py
Normal file
0
bec_widgets/widgets/lmfit_dialog/__init__.py
Normal file
1
bec_widgets/widgets/lmfit_dialog/lm_fit_dialog.pyproject
Normal file
1
bec_widgets/widgets/lmfit_dialog/lm_fit_dialog.pyproject
Normal file
@ -0,0 +1 @@
|
|||||||
|
{'files': ['lmfit_dialog.py']}
|
54
bec_widgets/widgets/lmfit_dialog/lm_fit_dialog_plugin.py
Normal file
54
bec_widgets/widgets/lmfit_dialog/lm_fit_dialog_plugin.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||||
|
|
||||||
|
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||||
|
from bec_widgets.widgets.lmfit_dialog.lmfit_dialog import LMFitDialog
|
||||||
|
|
||||||
|
DOM_XML = """
|
||||||
|
<ui language='c++'>
|
||||||
|
<widget class='LMFitDialog' name='lm_fit_dialog'>
|
||||||
|
</widget>
|
||||||
|
</ui>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class LMFitDialogPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._form_editor = None
|
||||||
|
|
||||||
|
def createWidget(self, parent):
|
||||||
|
t = LMFitDialog(parent)
|
||||||
|
return t
|
||||||
|
|
||||||
|
def domXml(self):
|
||||||
|
return DOM_XML
|
||||||
|
|
||||||
|
def group(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def icon(self):
|
||||||
|
return designer_material_icon(LMFitDialog.ICON_NAME)
|
||||||
|
|
||||||
|
def includeFile(self):
|
||||||
|
return "lm_fit_dialog"
|
||||||
|
|
||||||
|
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 "LMFitDialog"
|
||||||
|
|
||||||
|
def toolTip(self):
|
||||||
|
return "LMFitDialog"
|
||||||
|
|
||||||
|
def whatsThis(self):
|
||||||
|
return self.toolTip()
|
185
bec_widgets/widgets/lmfit_dialog/lmfit_dialog.py
Normal file
185
bec_widgets/widgets/lmfit_dialog/lmfit_dialog.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from qtpy.QtCore import Property, Signal, Slot
|
||||||
|
from qtpy.QtWidgets import QTreeWidgetItem, QVBoxLayout, QWidget
|
||||||
|
|
||||||
|
from bec_widgets.utils import UILoader
|
||||||
|
from bec_widgets.utils.bec_widget import BECWidget
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
|
class LMFitDialog(BECWidget, QWidget):
|
||||||
|
|
||||||
|
ICON_NAME = "bike_lane"
|
||||||
|
selected_fit = Signal(str)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent=None,
|
||||||
|
client=None,
|
||||||
|
config=None,
|
||||||
|
target_widget=None,
|
||||||
|
gui_id: str | None = None,
|
||||||
|
ui_file="lmfit_dialog_vertical.ui",
|
||||||
|
):
|
||||||
|
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||||
|
QWidget.__init__(self, parent=parent)
|
||||||
|
self._ui_file = ui_file
|
||||||
|
self.target_widget = target_widget
|
||||||
|
|
||||||
|
current_path = os.path.dirname(__file__)
|
||||||
|
self.ui = UILoader(self).loader(os.path.join(current_path, self._ui_file))
|
||||||
|
self.layout = QVBoxLayout(self)
|
||||||
|
self.layout.addWidget(self.ui)
|
||||||
|
self.summary_data = {}
|
||||||
|
self._fit_curve_id = None
|
||||||
|
self._deci_precision = 3
|
||||||
|
self.ui.curve_list.currentItemChanged.connect(self.display_fit_details)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
@Property(bool)
|
||||||
|
def hide_curve_selection(self):
|
||||||
|
"""Property for showing the curve selection."""
|
||||||
|
return not self.ui.group_curve_selection.isVisible()
|
||||||
|
|
||||||
|
@hide_curve_selection.setter
|
||||||
|
def hide_curve_selection(self, show: bool):
|
||||||
|
"""Setter for showing the curve selection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
show (bool): Whether to show the curve selection.
|
||||||
|
"""
|
||||||
|
self.ui.group_curve_selection.setVisible(not show)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fit_curve_id(self):
|
||||||
|
"""Property for the currently displayed fit curve_id."""
|
||||||
|
return self._fit_curve_id
|
||||||
|
|
||||||
|
@fit_curve_id.setter
|
||||||
|
def fit_curve_id(self, curve_id: str):
|
||||||
|
"""Setter for the currently displayed fit curve_id.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fit_curve_id (str): The curve_id of the fit curve to be displayed.
|
||||||
|
"""
|
||||||
|
self._fit_curve_id = curve_id
|
||||||
|
self.selected_fit.emit(curve_id)
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def remove_dap_data(self, curve_id: str):
|
||||||
|
"""Remove the DAP data for the given curve_id.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
curve_id (str): The curve_id of the DAP data to be removed.
|
||||||
|
"""
|
||||||
|
self.summary_data.pop(curve_id, None)
|
||||||
|
self.refresh_curve_list()
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def select_curve(self, curve_id: str):
|
||||||
|
"""Select active curve_id in the curve list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
curve_id (str): curve_id to be selected.
|
||||||
|
"""
|
||||||
|
self.fit_curve_id = curve_id
|
||||||
|
|
||||||
|
@Slot(dict, dict)
|
||||||
|
def update_summary_tree(self, data: dict, metadata: dict):
|
||||||
|
"""Update the summary tree with the given data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (dict): Data for the DAP Summary.
|
||||||
|
metadata (dict): Metadata of the fit curve.
|
||||||
|
"""
|
||||||
|
curve_id = metadata.get("curve_id", "")
|
||||||
|
self.summary_data.update({curve_id: data})
|
||||||
|
self.refresh_curve_list()
|
||||||
|
if self.fit_curve_id is None:
|
||||||
|
self.fit_curve_id = curve_id
|
||||||
|
for index in range(self.ui.curve_list.count()):
|
||||||
|
item = self.ui.curve_list.item(index)
|
||||||
|
if item.text() == curve_id:
|
||||||
|
self.ui.curve_list.setCurrentItem(item)
|
||||||
|
if curve_id != self.fit_curve_id:
|
||||||
|
return
|
||||||
|
if data is None:
|
||||||
|
return
|
||||||
|
self.ui.summary_tree.clear()
|
||||||
|
properties = [
|
||||||
|
("Model", data.get("model", "")),
|
||||||
|
("Method", data.get("method", "")),
|
||||||
|
("Chi-Squared", f"{data.get('chisqr', 0.0):.{self._deci_precision}f}"),
|
||||||
|
("Reduced Chi-Squared", f"{data.get('redchi', 0.0):.{self._deci_precision}f}"),
|
||||||
|
("R-Squared", f"{data.get('rsquared', 0.0):.{self._deci_precision}f}"),
|
||||||
|
("Message", data.get("message", "")),
|
||||||
|
]
|
||||||
|
for prop, val in properties:
|
||||||
|
QTreeWidgetItem(self.ui.summary_tree, [prop, val])
|
||||||
|
self.update_param_tree(data.get("params", []))
|
||||||
|
|
||||||
|
def _update_summary_data(self, curve_id: str, data: dict):
|
||||||
|
"""Update the summary data with the given data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
curve_id (str): The curve_id of the fit curve.
|
||||||
|
data (dict): The data to be updated.
|
||||||
|
"""
|
||||||
|
self.summary_data.update({curve_id: data})
|
||||||
|
if self.fit_curve_id is not None:
|
||||||
|
return
|
||||||
|
self.fit_curve_id = curve_id
|
||||||
|
|
||||||
|
def update_param_tree(self, params):
|
||||||
|
"""Update the parameter tree with the given parameters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
params (list): List of LMFit parameters for the fit curve.
|
||||||
|
"""
|
||||||
|
self.ui.param_tree.clear()
|
||||||
|
for param in params:
|
||||||
|
param_name, param_value, param_std = (
|
||||||
|
param[0],
|
||||||
|
f"{param[1]:.{self._deci_precision}f}",
|
||||||
|
f"{param[7]:.{self._deci_precision}f}",
|
||||||
|
)
|
||||||
|
QTreeWidgetItem(self.ui.param_tree, [param_name, param_value, param_std])
|
||||||
|
|
||||||
|
def populate_curve_list(self):
|
||||||
|
"""Populate the curve list with the available fit curves."""
|
||||||
|
for curve_name in self.summary_data.keys():
|
||||||
|
self.ui.curve_list.addItem(curve_name)
|
||||||
|
|
||||||
|
def refresh_curve_list(self):
|
||||||
|
"""Refresh the curve list with the updated data."""
|
||||||
|
self.ui.curve_list.clear()
|
||||||
|
self.populate_curve_list()
|
||||||
|
|
||||||
|
def display_fit_details(self, current):
|
||||||
|
"""Callback for displaying the fit details of the selected curve.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current: The current item in the curve list.
|
||||||
|
"""
|
||||||
|
if current:
|
||||||
|
curve_name = current.text()
|
||||||
|
self.fit_curve_id = curve_name
|
||||||
|
data = self.summary_data[curve_name]
|
||||||
|
if data is None:
|
||||||
|
return
|
||||||
|
self.update_summary_tree(data, {"curve_id": curve_name})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
dialog = LMFitDialog()
|
||||||
|
dialog.show()
|
||||||
|
sys.exit(app.exec_())
|
120
bec_widgets/widgets/lmfit_dialog/lmfit_dialog_compact.ui
Normal file
120
bec_widgets/widgets/lmfit_dialog/lmfit_dialog_compact.ui
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?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>655</width>
|
||||||
|
<height>520</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QSplitter" name="splitter_2">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::Shape::VLine</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Shadow::Plain</enum>
|
||||||
|
</property>
|
||||||
|
<property name="lineWidth">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="opaqueResize">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="childrenCollapsible">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QGroupBox" name="group_curve_selection">
|
||||||
|
<property name="title">
|
||||||
|
<string>Select Curve</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="curve_list"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
|
<horstretch>2</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="QGroupBox" name="group_summary">
|
||||||
|
<property name="title">
|
||||||
|
<string>Fit Summary</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="summary_tree">
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Property</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Value</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QGroupBox" name="group_parameters">
|
||||||
|
<property name="title">
|
||||||
|
<string>Parameter Details</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="param_tree">
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Parameter</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Value</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Std</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
147
bec_widgets/widgets/lmfit_dialog/lmfit_dialog_vertical.ui
Normal file
147
bec_widgets/widgets/lmfit_dialog/lmfit_dialog_vertical.ui
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<?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>274</width>
|
||||||
|
<height>568</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0,0">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="group_curve_selection">
|
||||||
|
<property name="title">
|
||||||
|
<string>Select Curve</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="3">
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="curve_list"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="group_summary">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>250</width>
|
||||||
|
<height>200</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Fit Summary</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="summary_tree">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="headerDefaultSectionSize">
|
||||||
|
<number>80</number>
|
||||||
|
</attribute>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Property</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Value</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="group_parameters">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>250</width>
|
||||||
|
<height>200</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Parameter Details</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="param_tree">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<attribute name="headerDefaultSectionSize">
|
||||||
|
<number>80</number>
|
||||||
|
</attribute>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Parameter</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Value</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Std</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
15
bec_widgets/widgets/lmfit_dialog/register_lm_fit_dialog.py
Normal file
15
bec_widgets/widgets/lmfit_dialog/register_lm_fit_dialog.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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.lmfit_dialog.lm_fit_dialog_plugin import LMFitDialogPlugin
|
||||||
|
|
||||||
|
QPyDesignerCustomWidgetCollection.addCustomWidget(LMFitDialogPlugin())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
main()
|
@ -1,127 +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>800</width>
|
|
||||||
<height>600</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Form</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QSplitter" name="splitter_2">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
|
||||||
<horstretch>1</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::Shape::VLine</enum>
|
|
||||||
</property>
|
|
||||||
<property name="frameShadow">
|
|
||||||
<enum>QFrame::Shadow::Plain</enum>
|
|
||||||
</property>
|
|
||||||
<property name="lineWidth">
|
|
||||||
<number>1</number>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="opaqueResize">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="childrenCollapsible">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<widget class="QGroupBox" name="group_curve_selection">
|
|
||||||
<property name="title">
|
|
||||||
<string>Select Curve</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="refresh_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>Refresh DAP Summary</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QListWidget" name="curve_list"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QSplitter" name="splitter">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
|
||||||
<horstretch>2</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Orientation::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<widget class="QGroupBox" name="group_summary">
|
|
||||||
<property name="title">
|
|
||||||
<string>Fit Summary</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<item>
|
|
||||||
<widget class="QTreeWidget" name="summary_tree">
|
|
||||||
<property name="uniformRowHeights">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Property</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Value</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QGroupBox" name="group_parameters">
|
|
||||||
<property name="title">
|
|
||||||
<string>Parameter Details</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
|
||||||
<item>
|
|
||||||
<widget class="QTreeWidget" name="param_tree">
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Parameter</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Value</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Std</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
@ -4,66 +4,26 @@ from qtpy.QtWidgets import QDialog, QTreeWidgetItem, QVBoxLayout
|
|||||||
|
|
||||||
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
||||||
from bec_widgets.utils import UILoader
|
from bec_widgets.utils import UILoader
|
||||||
|
from bec_widgets.widgets.lmfit_dialog.lmfit_dialog import LMFitDialog
|
||||||
|
|
||||||
|
|
||||||
class FitSummaryWidget(QDialog):
|
class FitSummaryWidget(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent=None, target_widget=None):
|
def __init__(self, parent=None, target_widget=None):
|
||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
self.target_widget = target_widget
|
|
||||||
self.summary_data = self.target_widget.get_dap_summary()
|
|
||||||
|
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
|
self.target_widget = target_widget
|
||||||
current_path = os.path.dirname(__file__)
|
self.dap_dialog = LMFitDialog(parent=self, ui_file="lmfit_dialog_compact.ui")
|
||||||
self.ui = UILoader(self).loader(os.path.join(current_path, "dap_summary.ui"))
|
|
||||||
self.layout = QVBoxLayout(self)
|
self.layout = QVBoxLayout(self)
|
||||||
self.layout.addWidget(self.ui)
|
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()
|
||||||
|
|
||||||
self.ui.curve_list.currentItemChanged.connect(self.display_fit_details)
|
def _get_dap_from_target_widget(self) -> None:
|
||||||
self.ui.refresh_button.clicked.connect(self.refresh_dap)
|
"""Get the DAP data from the target widget and update the DAP dialog manually on creation."""
|
||||||
|
dap_summary = self.target_widget.get_dap_summary()
|
||||||
self.populate_curve_list()
|
for curve_id, data in dap_summary.items():
|
||||||
|
md = {"curve_id": curve_id}
|
||||||
def populate_curve_list(self):
|
self.dap_dialog.update_summary_tree(data=data, metadata=md)
|
||||||
for curve_name in self.summary_data.keys():
|
|
||||||
self.ui.curve_list.addItem(curve_name)
|
|
||||||
|
|
||||||
def display_fit_details(self, current):
|
|
||||||
if current:
|
|
||||||
curve_name = current.text()
|
|
||||||
data = self.summary_data[curve_name]
|
|
||||||
if data is None:
|
|
||||||
return
|
|
||||||
self.refresh_trees(data)
|
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def refresh_dap(self):
|
|
||||||
self.ui.curve_list.clear()
|
|
||||||
self.summary_data = self.target_widget.get_dap_summary()
|
|
||||||
self.populate_curve_list()
|
|
||||||
|
|
||||||
def refresh_trees(self, data):
|
|
||||||
self.update_summary_tree(data)
|
|
||||||
self.update_param_tree(data["params"])
|
|
||||||
|
|
||||||
def update_summary_tree(self, data):
|
|
||||||
self.ui.summary_tree.clear()
|
|
||||||
properties = [
|
|
||||||
("Model", data.get("model", "")),
|
|
||||||
("Method", data.get("method", "")),
|
|
||||||
("Chi-Squared", str(data.get("chisqr", ""))),
|
|
||||||
("Reduced Chi-Squared", str(data.get("redchi", ""))),
|
|
||||||
("AIC", str(data.get("aic", ""))),
|
|
||||||
("BIC", str(data.get("bic", ""))),
|
|
||||||
("R-Squared", str(data.get("rsquared", ""))),
|
|
||||||
("Message", data.get("message", "")),
|
|
||||||
]
|
|
||||||
for prop, val in properties:
|
|
||||||
QTreeWidgetItem(self.ui.summary_tree, [prop, val])
|
|
||||||
|
|
||||||
def update_param_tree(self, params):
|
|
||||||
self.ui.param_tree.clear()
|
|
||||||
for param in params:
|
|
||||||
param_name, param_value, param_std = param[0], str(param[1]), str(param[7])
|
|
||||||
QTreeWidgetItem(self.ui.param_tree, [param_name, param_value, param_std])
|
|
||||||
|
@ -33,7 +33,6 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|||||||
"curves",
|
"curves",
|
||||||
"plot",
|
"plot",
|
||||||
"add_dap",
|
"add_dap",
|
||||||
"get_dap_params",
|
|
||||||
"remove_curve",
|
"remove_curve",
|
||||||
"scan_history",
|
"scan_history",
|
||||||
"get_all_data",
|
"get_all_data",
|
||||||
@ -55,8 +54,8 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|||||||
]
|
]
|
||||||
scan_signal_update = Signal()
|
scan_signal_update = Signal()
|
||||||
async_signal_update = Signal()
|
async_signal_update = Signal()
|
||||||
dap_params_update = Signal(dict)
|
dap_summary_update = Signal(dict, dict)
|
||||||
dap_summary_update = Signal(dict)
|
dap_params_update = Signal(dict, dict)
|
||||||
autorange_signal = Signal()
|
autorange_signal = Signal()
|
||||||
new_scan = Signal()
|
new_scan = Signal()
|
||||||
crosshair_position_changed = Signal(tuple)
|
crosshair_position_changed = Signal(tuple)
|
||||||
@ -218,7 +217,7 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|||||||
def show_fit_summary_dialog(self):
|
def show_fit_summary_dialog(self):
|
||||||
dialog = FitSummaryWidget(target_widget=self)
|
dialog = FitSummaryWidget(target_widget=self)
|
||||||
dialog.resize(800, 600)
|
dialog.resize(800, 600)
|
||||||
dialog.show()
|
dialog.exec()
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
# User Access Methods from Waveform
|
# User Access Methods from Waveform
|
||||||
@ -257,7 +256,7 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|||||||
"""
|
"""
|
||||||
self.waveform.set_colormap(colormap)
|
self.waveform.set_colormap(colormap)
|
||||||
|
|
||||||
@SafeSlot(popup_error=True)
|
@SafeSlot(str, popup_error=True)
|
||||||
def set_x(self, x_name: str, x_entry: str | None = None):
|
def set_x(self, x_name: str, x_entry: str | None = None):
|
||||||
"""
|
"""
|
||||||
Change the x axis of the plot widget.
|
Change the x axis of the plot widget.
|
||||||
@ -272,7 +271,7 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|||||||
"""
|
"""
|
||||||
self.waveform.set_x(x_name, x_entry)
|
self.waveform.set_x(x_name, x_entry)
|
||||||
|
|
||||||
@SafeSlot(popup_error=True)
|
@SafeSlot(str, popup_error=True)
|
||||||
def plot(
|
def plot(
|
||||||
self,
|
self,
|
||||||
arg1: list | np.ndarray | str | None = None,
|
arg1: list | np.ndarray | str | None = None,
|
||||||
@ -331,15 +330,16 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
@SafeSlot(popup_error=True)
|
@SafeSlot(str, str, str, popup_error=True)
|
||||||
def add_dap(
|
def add_dap(
|
||||||
self,
|
self,
|
||||||
x_name: str,
|
x_name: str,
|
||||||
y_name: str,
|
y_name: str,
|
||||||
|
dap: str,
|
||||||
x_entry: str | None = None,
|
x_entry: str | None = None,
|
||||||
y_entry: str | None = None,
|
y_entry: str | None = None,
|
||||||
color: str | None = None,
|
color: str | None = None,
|
||||||
dap: str = "GaussianModel",
|
# dap: str = "GaussianModel",
|
||||||
validate_bec: bool = True,
|
validate_bec: bool = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> BECCurve:
|
) -> BECCurve:
|
||||||
|
BIN
docs/assets/widget_screenshots/lmfit_dialog.png
Normal file
BIN
docs/assets/widget_screenshots/lmfit_dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
BIN
docs/assets/widget_screenshots/lmfit_dialog_connect.png
Normal file
BIN
docs/assets/widget_screenshots/lmfit_dialog_connect.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
50
docs/user/widgets/lmfit_dialog/lmfit_dialog.md
Normal file
50
docs/user/widgets/lmfit_dialog/lmfit_dialog.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
(user.widgets.lmfit_dialog)=
|
||||||
|
|
||||||
|
# LMFit Dialog
|
||||||
|
|
||||||
|
````{tab} Overview
|
||||||
|
|
||||||
|
The [`LMFit Dialog`](/api_reference/_autosummary/bec_widgets.widgets.lmfit_dialog.lmfit_dialog.LMFitDialog) is a widget that is developed to be used togther with the [`BECWaveformWidget`](/api_reference/_autosummary/bec_widgets.widgets.waveform.waveform_widget.BECWaveformWidget). The `BECWaveformWidget` allows user to submit a fit request to BEC's [DAP server](https://bec.readthedocs.io/en/latest/developer/getting_started/architecture.html) choosing from a selection of [LMFit models](https://lmfit.github.io/lmfit-py/builtin_models.html#) to fit monitored data sources. The `LMFit Dialog` provides an interface to monitor these fits, including statistics and fit parameters in real time.
|
||||||
|
Within the `BECWaveformWidget`, the dialog is accessible via the toolbar and will be automatically linked to the current waveform widget. For a more customised use, we can embed the `LMFit Dialog` in a larger GUI using the *BEC Designer*. In this case, one has to connect the [`update_summary_tree`](/api_reference/_autosummary/bec_widgets.widgets.lmfit_dialog.lmfit_dialog.LMFitDialog.rst#bec_widgets.widgets.lmfit_dialog.lmfit_dialog.LMFitDialog.update_summary_tree) slot of the LMFit Dialog to the [`dap_summary_update`](/api_reference/_autosummary/bec_widgets.widgets.waveform.waveform_widget.BECWaveformWidget.rst#bec_widgets.widgets.waveform.waveform_widget.BECWaveformWidget.dap_summary_update) signal of the BECWaveformWidget to ensure its functionality.
|
||||||
|
|
||||||
|
|
||||||
|
## Key Features:
|
||||||
|
- **Fit Summary**: Display updates on LMFit DAP processes and fit statistics.
|
||||||
|
- **Fit Parameter**: Display current fit parameter.
|
||||||
|
- **BECWaveformWidget Integration**: Directly connect to BECWaveformWidget to display fit statistics and parameters.
|
||||||
|
```{figure} /assets/widget_screenshots/lmfit_dialog.png
|
||||||
|
---
|
||||||
|
name: lmfit_dialog
|
||||||
|
---
|
||||||
|
LMFit Dialog
|
||||||
|
```
|
||||||
|
````
|
||||||
|
````{tab} Connect in BEC Designer
|
||||||
|
The `LMFit Dialog` widget can be connected to a `BECWaveformWidget` to display fit statistics and parameters from the LMFit DAP process hooked up to the waveform widget. You can use the signal/slot editor from the BEC Designer to connect the `dap_summary_update` signal of the BECWaveformWidget to the `update_summary_tree` slot of the LMFit Dialog.
|
||||||
|
|
||||||
|
```{figure} /assets/widget_screenshots/lmfit_dialog_connect.png
|
||||||
|
````
|
||||||
|
````{tab} Connect in Python
|
||||||
|
It is also possible to directly connect the `dap_summary_update` signal of the BECWaveformWidget to the `update_summary_tree` slot of the LMFit Dialog in Python.
|
||||||
|
|
||||||
|
```python
|
||||||
|
waveform = BECWaveformWidget(...)
|
||||||
|
lmfit_dialog = LMFitDialog(...)
|
||||||
|
waveform.dap_summary_update.connect(lmfit_dialog.update_summary_tree)
|
||||||
|
|
||||||
|
```
|
||||||
|
````
|
||||||
|
````{tab} API
|
||||||
|
```{eval-rst}
|
||||||
|
.. include:: /api_reference/_autosummary/bec_widgets.cli.client.LMFitDialog.rst
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -190,6 +190,14 @@ Display spinner widget for loading or device movement.
|
|||||||
|
|
||||||
Display position of motor withing its limits.
|
Display position of motor withing its limits.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```{grid-item-card} LMFit Dialog
|
||||||
|
:link: user.widgets.lmfit_dialog
|
||||||
|
:link-type: ref
|
||||||
|
:img-top: /assets/widget_screenshots/lmfit_dialog.png
|
||||||
|
|
||||||
|
Display DAP summaries of LMFit models in a window.
|
||||||
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
```{toctree}
|
```{toctree}
|
||||||
@ -216,5 +224,6 @@ toggle/toggle.md
|
|||||||
spinner/spinner.md
|
spinner/spinner.md
|
||||||
device_input/device_input.md
|
device_input/device_input.md
|
||||||
position_indicator/position_indicator.md
|
position_indicator/position_indicator.md
|
||||||
|
lmfit_dialog/lmfit_dialog.md
|
||||||
|
|
||||||
```
|
```
|
183
tests/unit_tests/test_lmfit_dialog.py
Normal file
183
tests/unit_tests/test_lmfit_dialog.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from bec_widgets.widgets.lmfit_dialog.lmfit_dialog import LMFitDialog
|
||||||
|
|
||||||
|
from .client_mocks import mocked_client
|
||||||
|
from .conftest import create_widget
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def lmfit_dialog(qtbot, mocked_client):
|
||||||
|
"""Fixture for LMFitDialog widget"""
|
||||||
|
db = create_widget(qtbot, LMFitDialog, client=mocked_client)
|
||||||
|
yield db
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def lmfit_message():
|
||||||
|
"""Fixture for lmfit summary message"""
|
||||||
|
yield {
|
||||||
|
"model": "Model(breit_wigner)",
|
||||||
|
"method": "leastsq",
|
||||||
|
"ndata": 4,
|
||||||
|
"nvarys": 4,
|
||||||
|
"nfree": 0,
|
||||||
|
"chisqr": 1.2583132407517716,
|
||||||
|
"redchi": 1.2583132407517716,
|
||||||
|
"aic": 3.3739110606840716,
|
||||||
|
"bic": 0.9190885051636339,
|
||||||
|
"rsquared": 0.9650468544235619,
|
||||||
|
"nfev": 2498,
|
||||||
|
"max_nfev": 10000,
|
||||||
|
"aborted": False,
|
||||||
|
"errorbars": True,
|
||||||
|
"success": True,
|
||||||
|
"message": "Fit succeeded.",
|
||||||
|
"lmdif_message": "Both actual and predicted relative reductions in the sum of squares\n are at most 0.000000",
|
||||||
|
"ier": 1,
|
||||||
|
"nan_policy": "raise",
|
||||||
|
"scale_covar": True,
|
||||||
|
"calc_covar": True,
|
||||||
|
"ci_out": None,
|
||||||
|
"col_deriv": False,
|
||||||
|
"flatchain": None,
|
||||||
|
"call_kws": {
|
||||||
|
"Dfun": None,
|
||||||
|
"full_output": 1,
|
||||||
|
"col_deriv": 0,
|
||||||
|
"ftol": 1.5e-08,
|
||||||
|
"xtol": 1.5e-08,
|
||||||
|
"gtol": 0.0,
|
||||||
|
"maxfev": 20000,
|
||||||
|
"epsfcn": 1e-10,
|
||||||
|
"factor": 100,
|
||||||
|
"diag": None,
|
||||||
|
},
|
||||||
|
"var_names": ["amplitude", "center", "sigma", "q"],
|
||||||
|
"user_options": None,
|
||||||
|
"kws": {},
|
||||||
|
"init_values": {"amplitude": 1.0, "center": 0.0, "sigma": 1.0, "q": 1.0},
|
||||||
|
"best_values": {
|
||||||
|
"amplitude": 1.5824142042890903,
|
||||||
|
"center": -2.8415356591834326,
|
||||||
|
"sigma": 0.0002550847234503717,
|
||||||
|
"q": -259.8514775889427,
|
||||||
|
},
|
||||||
|
"params": [
|
||||||
|
[
|
||||||
|
"amplitude",
|
||||||
|
1.5824142042890903,
|
||||||
|
True,
|
||||||
|
None,
|
||||||
|
-np.inf,
|
||||||
|
np.inf,
|
||||||
|
None,
|
||||||
|
1.3249185295752495,
|
||||||
|
{
|
||||||
|
"center": 0.8429146627203449,
|
||||||
|
"sigma": -0.8362891947010586,
|
||||||
|
"q": -0.8362890089256452,
|
||||||
|
},
|
||||||
|
1.0,
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"center",
|
||||||
|
-2.8415356591834326,
|
||||||
|
True,
|
||||||
|
None,
|
||||||
|
-np.inf,
|
||||||
|
np.inf,
|
||||||
|
None,
|
||||||
|
0.5077201488266584,
|
||||||
|
{
|
||||||
|
"amplitude": 0.8429146627203449,
|
||||||
|
"sigma": -0.9987662050702767,
|
||||||
|
"q": -0.9987662962832818,
|
||||||
|
},
|
||||||
|
0.0,
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sigma",
|
||||||
|
0.0002550847234503717,
|
||||||
|
True,
|
||||||
|
None,
|
||||||
|
0.0,
|
||||||
|
np.inf,
|
||||||
|
None,
|
||||||
|
113.2533064536711,
|
||||||
|
{
|
||||||
|
"amplitude": -0.8362891947010586,
|
||||||
|
"center": -0.9987662050702767,
|
||||||
|
"q": 0.999999999997876,
|
||||||
|
},
|
||||||
|
1.0,
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"q",
|
||||||
|
-259.8514775889427,
|
||||||
|
True,
|
||||||
|
None,
|
||||||
|
-np.inf,
|
||||||
|
np.inf,
|
||||||
|
None,
|
||||||
|
114893884.64553572,
|
||||||
|
{
|
||||||
|
"amplitude": -0.8362890089256452,
|
||||||
|
"center": -0.9987662962832818,
|
||||||
|
"sigma": 0.999999999997876,
|
||||||
|
},
|
||||||
|
1.0,
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_fit_curve_id(lmfit_dialog):
|
||||||
|
"""Test hide_curve_selection property"""
|
||||||
|
my_callback = mock.MagicMock()
|
||||||
|
lmfit_dialog.selected_fit.connect(my_callback)
|
||||||
|
assert lmfit_dialog.fit_curve_id is None
|
||||||
|
lmfit_dialog.fit_curve_id = "test_curve_id"
|
||||||
|
assert lmfit_dialog.fit_curve_id == "test_curve_id"
|
||||||
|
assert my_callback.call_count == 1
|
||||||
|
assert my_callback.call_args == mock.call("test_curve_id")
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_dap_data(lmfit_dialog):
|
||||||
|
"""Test remove_dap_data method"""
|
||||||
|
lmfit_dialog.summary_data = {"test": "data", "test2": "data2"}
|
||||||
|
lmfit_dialog.refresh_curve_list()
|
||||||
|
# Only 2 items
|
||||||
|
assert lmfit_dialog.ui.curve_list.count() == 2
|
||||||
|
lmfit_dialog.remove_dap_data("test")
|
||||||
|
assert lmfit_dialog.summary_data == {"test2": "data2"}
|
||||||
|
assert lmfit_dialog.ui.curve_list.count() == 1
|
||||||
|
# Test removing non-existing data
|
||||||
|
# Nothing should happen
|
||||||
|
lmfit_dialog.remove_dap_data("test_not_there")
|
||||||
|
assert lmfit_dialog.summary_data == {"test2": "data2"}
|
||||||
|
assert lmfit_dialog.ui.curve_list.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_summary_tree(lmfit_dialog, lmfit_message):
|
||||||
|
"""Test display_fit_details method"""
|
||||||
|
lmfit_dialog.update_summary_tree(data=lmfit_message, metadata={"curve_id": "test_curve_id"})
|
||||||
|
# Check if the data is updated
|
||||||
|
assert lmfit_dialog.summary_data == {"test_curve_id": lmfit_message}
|
||||||
|
# Check if the curve list is updated
|
||||||
|
assert lmfit_dialog.ui.curve_list.count() == 1
|
||||||
|
# Check summary tree is updated
|
||||||
|
assert lmfit_dialog.ui.summary_tree.topLevelItemCount() == 6
|
||||||
|
assert lmfit_dialog.ui.summary_tree.topLevelItem(0).text(0) == "Model"
|
||||||
|
assert lmfit_dialog.ui.summary_tree.topLevelItem(0).text(1) == "Model(breit_wigner)"
|
||||||
|
# Check fit params tree is updated
|
||||||
|
assert lmfit_dialog.ui.param_tree.topLevelItemCount() == 4
|
||||||
|
assert lmfit_dialog.ui.param_tree.topLevelItem(0).text(0) == "amplitude"
|
||||||
|
assert lmfit_dialog.ui.param_tree.topLevelItem(0).text(1) == "1.582"
|
@ -9,9 +9,13 @@ 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.colors import apply_theme, get_theme_palette, set_theme
|
||||||
from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings
|
from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings
|
||||||
from bec_widgets.widgets.waveform.waveform_popups.curve_dialog.curve_dialog import CurveSettings
|
from bec_widgets.widgets.waveform.waveform_popups.curve_dialog.curve_dialog import CurveSettings
|
||||||
|
from bec_widgets.widgets.waveform.waveform_popups.dap_summary_dialog.dap_summary_dialog import (
|
||||||
|
FitSummaryWidget,
|
||||||
|
)
|
||||||
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
|
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
|
||||||
|
|
||||||
from .client_mocks import mocked_client
|
from .client_mocks import mocked_client
|
||||||
|
from .conftest import create_widget
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -97,7 +101,7 @@ def test_waveform_plot_scan_curves(waveform_widget, mock_waveform):
|
|||||||
|
|
||||||
|
|
||||||
def test_waveform_widget_add_dap(waveform_widget, mock_waveform):
|
def test_waveform_widget_add_dap(waveform_widget, mock_waveform):
|
||||||
waveform_widget.add_dap(x_name="samx", y_name="bpm4i")
|
waveform_widget.add_dap(x_name="samx", y_name="bpm4i", dap="GaussianModel")
|
||||||
waveform_widget.waveform.add_dap.assert_called_once_with(
|
waveform_widget.waveform.add_dap.assert_called_once_with(
|
||||||
x_name="samx",
|
x_name="samx",
|
||||||
y_name="bpm4i",
|
y_name="bpm4i",
|
||||||
@ -252,7 +256,7 @@ def test_toolbar_fit_params_action_triggered(qtbot, waveform_widget):
|
|||||||
) as MockFitSummaryWidget:
|
) as MockFitSummaryWidget:
|
||||||
mock_dialog_instance = MockFitSummaryWidget.return_value
|
mock_dialog_instance = MockFitSummaryWidget.return_value
|
||||||
action.trigger()
|
action.trigger()
|
||||||
mock_dialog_instance.show.assert_called_once()
|
mock_dialog_instance.exec.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_enable_mouse_pan_mode(qtbot, waveform_widget):
|
def test_enable_mouse_pan_mode(qtbot, waveform_widget):
|
||||||
@ -378,6 +382,14 @@ def test_curve_dialog_dap(qtbot, waveform_widget):
|
|||||||
assert len(waveform_widget.curves) == 2
|
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
|
# Axis Dialog Tests
|
||||||
###################################
|
###################################
|
||||||
|
Reference in New Issue
Block a user