diff --git a/bec_widgets/assets/toolbar_icons/fitting_parameters.svg b/bec_widgets/assets/toolbar_icons/fitting_parameters.svg new file mode 100644 index 00000000..42b3bb15 --- /dev/null +++ b/bec_widgets/assets/toolbar_icons/fitting_parameters.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bec_widgets/widgets/figure/plots/waveform/waveform.py b/bec_widgets/widgets/figure/plots/waveform/waveform.py index 5c7d9eca..be900ae9 100644 --- a/bec_widgets/widgets/figure/plots/waveform/waveform.py +++ b/bec_widgets/widgets/figure/plots/waveform/waveform.py @@ -75,6 +75,7 @@ class BECWaveform(BECPlotBase): scan_signal_update = pyqtSignal() async_signal_update = pyqtSignal() dap_params_update = pyqtSignal(dict) + dap_summary_update = pyqtSignal(dict) autorange_signal = pyqtSignal() def __init__( @@ -655,6 +656,19 @@ class BECWaveform(BECPlotBase): params[curve_id] = curve.dap_params return params + @pyqtSlot() + def get_dap_summary(self) -> dict: + """ + Get the DAP summary of all DAP curves. + + Returns: + dict: DAP summary of all DAP curves. + """ + summary = {} + for curve_id, curve in self._curves_data["DAP"].items(): + summary[curve_id] = curve.dap_summary + return summary + def _add_curve_object( self, name: str, @@ -1071,7 +1085,9 @@ class BECWaveform(BECPlotBase): y = msg["data"][0]["y"] curve.setData(x, y) curve.dap_params = msg["data"][1]["fit_parameters"] + curve.dap_summary = msg["data"][1]["fit_summary"] self.dap_params_update.emit(curve.dap_params) + self.dap_summary_update.emit(curve.dap_summary) break @pyqtSlot(dict, dict) diff --git a/bec_widgets/widgets/figure/plots/waveform/waveform_curve.py b/bec_widgets/widgets/figure/plots/waveform/waveform_curve.py index cec0b4c4..2d5d910b 100644 --- a/bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +++ b/bec_widgets/widgets/figure/plots/waveform/waveform_curve.py @@ -101,6 +101,7 @@ class BECCurve(BECConnector, pg.PlotDataItem): self.parent_item = parent_item self.apply_config() self.dap_params = None + self.dap_summary = None if kwargs: self.set(**kwargs) @@ -132,6 +133,14 @@ class BECCurve(BECConnector, pg.PlotDataItem): def dap_params(self, value): self._dap_params = value + @property + def dap_summary(self): + return self._dap_report + + @dap_summary.setter + def dap_summary(self, value): + self._dap_report = value + def set_data(self, x, y): if self.config.source == "custom": self.setData(x, y) diff --git a/bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/__init__.py b/bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary.ui b/bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary.ui new file mode 100644 index 00000000..49160460 --- /dev/null +++ b/bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary.ui @@ -0,0 +1,127 @@ + + + Form + + + + 0 + 0 + 800 + 600 + + + + Form + + + + + + + 1 + 0 + + + + QFrame::Shape::VLine + + + QFrame::Shadow::Plain + + + 1 + + + Qt::Orientation::Horizontal + + + true + + + true + + + + Select Curve + + + + + + Refresh DAP Summary + + + + + + + + + + + + 2 + 0 + + + + Qt::Orientation::Vertical + + + + Fit Summary + + + + + + false + + + + Property + + + + + Value + + + + + + + + + Parameter Details + + + + + + + Parameter + + + + + Value + + + + + Std + + + + + + + + + + + + + + diff --git a/bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary_dialog.py b/bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary_dialog.py new file mode 100644 index 00000000..6b4aeb1f --- /dev/null +++ b/bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary_dialog.py @@ -0,0 +1,69 @@ +import os + +from qtpy.QtCore import Slot +from qtpy.QtWidgets import QDialog, QTreeWidgetItem, QVBoxLayout + +from bec_widgets.utils import UILoader + + +class FitSummaryWidget(QDialog): + def __init__(self, parent=None, target_widget=None): + super().__init__(parent=parent) + + self.target_widget = target_widget + self.summary_data = self.target_widget.get_dap_summary() + + self.setModal(True) + + current_path = os.path.dirname(__file__) + self.ui = UILoader(self).loader(os.path.join(current_path, "dap_summary.ui")) + self.layout = QVBoxLayout(self) + self.layout.addWidget(self.ui) + + self.ui.curve_list.currentItemChanged.connect(self.display_fit_details) + self.ui.refresh_button.clicked.connect(self.refresh_dap) + + self.populate_curve_list() + + def populate_curve_list(self): + 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]) diff --git a/bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py b/bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py index bb887a86..e369cb3a 100644 --- a/bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py +++ b/bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py @@ -41,6 +41,17 @@ class CurveAction(ToolBarAction): toolbar.addAction(self.action) +class FitParamsAction(ToolBarAction): + def add_to_toolbar(self, toolbar, target): + icon = QIcon() + icon.addFile( + os.path.join(MODULE_PATH, "assets", "toolbar_icons", "fitting_parameters.svg"), + size=QSize(20, 20), + ) + self.action = QAction(icon, "Open Fitting Parameters", target) + toolbar.addAction(self.action) + + class SettingsAction(ToolBarAction): def add_to_toolbar(self, toolbar, target): icon = QIcon() diff --git a/bec_widgets/widgets/waveform/waveform_widget.py b/bec_widgets/widgets/waveform/waveform_widget.py index 5efaf5a2..d429f42e 100644 --- a/bec_widgets/widgets/waveform/waveform_widget.py +++ b/bec_widgets/widgets/waveform/waveform_widget.py @@ -6,7 +6,7 @@ from typing import Literal import numpy as np from qtpy.QtWidgets import QVBoxLayout, QWidget -from bec_widgets.qt_utils.error_popups import WarningPopupUtility, SafeSlot +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 ModularToolBar, SeparatorAction from bec_widgets.utils import BECConnector @@ -15,6 +15,9 @@ from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings from bec_widgets.widgets.figure.plots.waveform.waveform import Waveform1DConfig from bec_widgets.widgets.figure.plots.waveform.waveform_curve import BECCurve from bec_widgets.widgets.waveform.waveform_toolbar.curve_dialog.curve_dialog import CurveSettings +from bec_widgets.widgets.waveform.waveform_toolbar.dap_summary_dialog.dap_summary_dialog import ( + FitSummaryWidget, +) from bec_widgets.widgets.waveform.waveform_toolbar.waveform_toolbar import * try: @@ -73,6 +76,7 @@ class BECWaveformWidget(BECConnector, QWidget): "matplotlib": MatplotlibAction(), "separator_1": SeparatorAction(), "curves": CurveAction(), + "fit_params": FitParamsAction(), "axis_settings": SettingsAction(), "separator_2": SeparatorAction(), "import": ImportAction(), @@ -95,13 +99,14 @@ class BECWaveformWidget(BECConnector, QWidget): # TEst actions self.plot(x_name="samx", y_name="bpm4i", dap="GaussianModel") - self.plot(x_name="samx", y_name="bpm3a") + self.plot(x_name="samx", y_name="bpm3a", dap="GaussianModel") self.plot(x_name="samx", y_name="bpm6i") 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["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["import"].action.triggered.connect( lambda: self.load_config(path=None, gui=True) @@ -129,6 +134,11 @@ class BECWaveformWidget(BECConnector, QWidget): dialog.resize(800, 600) dialog.exec() + def show_fit_summary_dialog(self): + dialog = FitSummaryWidget(target_widget=self) + dialog.resize(800, 600) + dialog.show() + def _check_if_scans_have_same_x( self, enabled=True, x_name_to_check: str = None ) -> bool: # TODO probably not needed anymore @@ -307,7 +317,16 @@ class BECWaveformWidget(BECConnector, QWidget): dict: DAP parameters of all DAP curves. """ - self.waveform.get_dap_params() + 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): """