From e364ffdf919496d703afe2097c659ed121c304da Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Sat, 30 Dec 2023 16:11:16 +0100 Subject: [PATCH] add residual buttons --- src/gui_qt/_py/fitresult.py | 97 +++++++++++------- src/gui_qt/fit/result.py | 64 ++++++++---- src/resources/_ui/fitresult.ui | 181 ++++++++++++++++++++++----------- 3 files changed, 227 insertions(+), 115 deletions(-) diff --git a/src/gui_qt/_py/fitresult.py b/src/gui_qt/_py/fitresult.py index 1e62a66..54becc9 100644 --- a/src/gui_qt/_py/fitresult.py +++ b/src/gui_qt/_py/fitresult.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'src/resources/_ui/fitresult.ui' +# Form implementation generated from reading ui file 'resources/_ui/fitresult.ui' # # Created by: PyQt5 UI code generator 5.15.10 # @@ -27,25 +27,48 @@ class Ui_Dialog(object): self.stackPage1 = QtWidgets.QWidget() self.stackPage1.setObjectName("stackPage1") self.gridLayout_3 = QtWidgets.QGridLayout(self.stackPage1) - self.gridLayout_3.setContentsMargins(3, 3, 3, 3) + self.gridLayout_3.setContentsMargins(6, 3, 6, 3) self.gridLayout_3.setSpacing(3) self.gridLayout_3.setObjectName("gridLayout_3") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem, 2, 3, 1, 1) self.autoscale_box = QtWidgets.QToolButton(self.stackPage1) self.autoscale_box.setObjectName("autoscale_box") - self.gridLayout_3.addWidget(self.autoscale_box, 2, 0, 1, 1) - self.logy_box = QtWidgets.QCheckBox(self.stackPage1) - self.logy_box.setLayoutDirection(QtCore.Qt.LeftToRight) - self.logy_box.setObjectName("logy_box") - self.gridLayout_3.addWidget(self.logy_box, 2, 3, 1, 1) - self.graphicsView = GraphicsLayoutWidget(self.stackPage1) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 4) - self.logx_box = QtWidgets.QCheckBox(self.stackPage1) + self.gridLayout_3.addWidget(self.autoscale_box, 2, 4, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem1, 2, 1, 1, 1) + self.verticalGroupBox_2 = QtWidgets.QGroupBox(self.stackPage1) + self.verticalGroupBox_2.setObjectName("verticalGroupBox_2") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.verticalGroupBox_2) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.logx_box = QtWidgets.QCheckBox(self.verticalGroupBox_2) self.logx_box.setLayoutDirection(QtCore.Qt.LeftToRight) self.logx_box.setObjectName("logx_box") - self.gridLayout_3.addWidget(self.logx_box, 2, 2, 1, 1) - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.gridLayout_3.addItem(spacerItem, 2, 1, 1, 1) + self.verticalLayout_4.addWidget(self.logx_box) + self.logy_box = QtWidgets.QCheckBox(self.verticalGroupBox_2) + self.logy_box.setLayoutDirection(QtCore.Qt.LeftToRight) + self.logy_box.setObjectName("logy_box") + self.verticalLayout_4.addWidget(self.logy_box) + self.gridLayout_3.addWidget(self.verticalGroupBox_2, 2, 2, 1, 1) + self.verticalGroupBox = QtWidgets.QGroupBox(self.stackPage1) + self.verticalGroupBox.setObjectName("verticalGroupBox") + self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalGroupBox) + self.verticalLayout.setObjectName("verticalLayout") + self.rel_dev_button = QtWidgets.QRadioButton(self.verticalGroupBox) + self.rel_dev_button.setObjectName("rel_dev_button") + self.buttonGroup = QtWidgets.QButtonGroup(Dialog) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.rel_dev_button) + self.verticalLayout.addWidget(self.rel_dev_button) + self.abs_dev_button = QtWidgets.QRadioButton(self.verticalGroupBox) + self.abs_dev_button.setChecked(True) + self.abs_dev_button.setObjectName("abs_dev_button") + self.buttonGroup.addButton(self.abs_dev_button) + self.verticalLayout.addWidget(self.abs_dev_button) + self.gridLayout_3.addWidget(self.verticalGroupBox, 2, 0, 1, 1) + self.graphicsView = GraphicsLayoutWidget(self.stackPage1) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 5) self.stack.addTab(self.stackPage1, "") self.stackPage2 = QtWidgets.QWidget() self.stackPage2.setObjectName("stackPage2") @@ -89,10 +112,6 @@ class Ui_Dialog(object): self.verticalLayout_3.addWidget(self.corr_tableWidget) self.stack.addTab(self.stackPage3, "") self.gridLayout.addWidget(self.stack, 0, 1, 5, 1) - self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) - self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Retry) - self.buttonBox.setObjectName("buttonBox") - self.gridLayout.addWidget(self.buttonBox, 8, 0, 1, 2) self.param_tableWidget = QtWidgets.QTableWidget(Dialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) @@ -118,6 +137,20 @@ class Ui_Dialog(object): self.line.setFrameShadow(QtWidgets.QFrame.Sunken) self.line.setObjectName("line") self.gridLayout.addWidget(self.line, 5, 0, 1, 2) + self.sets_comboBox = ElideComboBox(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.sets_comboBox.sizePolicy().hasHeightForWidth()) + self.sets_comboBox.setSizePolicy(sizePolicy) + self.sets_comboBox.setMaximumSize(QtCore.QSize(400, 16777215)) + self.sets_comboBox.setBaseSize(QtCore.QSize(200, 0)) + self.sets_comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) + self.sets_comboBox.setObjectName("sets_comboBox") + self.gridLayout.addWidget(self.sets_comboBox, 0, 0, 1, 1) + self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog) + self.reject_fit_checkBox.setObjectName("reject_fit_checkBox") + self.gridLayout.addWidget(self.reject_fit_checkBox, 2, 0, 1, 1) self.groupBox = QtWidgets.QGroupBox(Dialog) self.groupBox.setObjectName("groupBox") self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) @@ -186,20 +219,10 @@ class Ui_Dialog(object): self.horizontalLayout.addWidget(self.partial_checkBox) self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 4) self.gridLayout.addWidget(self.groupBox, 7, 0, 1, 2) - self.sets_comboBox = ElideComboBox(Dialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.sets_comboBox.sizePolicy().hasHeightForWidth()) - self.sets_comboBox.setSizePolicy(sizePolicy) - self.sets_comboBox.setMaximumSize(QtCore.QSize(400, 16777215)) - self.sets_comboBox.setBaseSize(QtCore.QSize(200, 0)) - self.sets_comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) - self.sets_comboBox.setObjectName("sets_comboBox") - self.gridLayout.addWidget(self.sets_comboBox, 0, 0, 1, 1) - self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog) - self.reject_fit_checkBox.setObjectName("reject_fit_checkBox") - self.gridLayout.addWidget(self.reject_fit_checkBox, 2, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Retry) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 8, 0, 1, 2) self.retranslateUi(Dialog) self.stack.setCurrentIndex(0) @@ -210,8 +233,12 @@ class Ui_Dialog(object): Dialog.setWindowTitle(_translate("Dialog", "Fit results")) self.autoscale_box.setToolTip(_translate("Dialog", "Auto-scale graph for all sets")) self.autoscale_box.setText(_translate("Dialog", "Autoscale all sets")) - self.logy_box.setText(_translate("Dialog", "logarithmic y axis")) - self.logx_box.setText(_translate("Dialog", "logarithmic x axis")) + self.verticalGroupBox_2.setTitle(_translate("Dialog", "Logarithmic axes")) + self.logx_box.setText(_translate("Dialog", "x axis")) + self.logy_box.setText(_translate("Dialog", "y axis")) + self.verticalGroupBox.setTitle(_translate("Dialog", "Residuals")) + self.rel_dev_button.setText(_translate("Dialog", "relative deviation")) + self.abs_dev_button.setText(_translate("Dialog", "absolute deviation")) self.stack.setTabText(self.stack.indexOf(self.stackPage1), _translate("Dialog", "Plot")) self.stack.setTabText(self.stack.indexOf(self.stackPage2), _translate("Dialog", "Statistics")) item = self.corr_tableWidget.horizontalHeaderItem(0) @@ -224,6 +251,7 @@ class Ui_Dialog(object): item.setText(_translate("Dialog", "Partial Corr.")) self.stack.setTabText(self.stack.indexOf(self.stackPage3), _translate("Dialog", "Correlations")) self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits of this set")) + self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit")) self.groupBox.setTitle(_translate("Dialog", "Output")) self.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function")) self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves")) @@ -236,6 +264,5 @@ class Ui_Dialog(object): self.numx_line.setPlaceholderText(_translate("Dialog", "# pts")) self.curve_checkbox.setText(_translate("Dialog", "Plot fit curve")) self.partial_checkBox.setText(_translate("Dialog", "Plot partial functions")) - self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit")) from ..lib.forms import ElideComboBox from pyqtgraph import GraphicsLayoutWidget diff --git a/src/gui_qt/fit/result.py b/src/gui_qt/fit/result.py index 7c4b85c..92bde0c 100644 --- a/src/gui_qt/fit/result.py +++ b/src/gui_qt/fit/result.py @@ -1,6 +1,7 @@ from math import isnan from pyqtgraph import mkBrush, mkPen +from numpy import abs as np_abs, isfinite as np_isfinite from nmreval.utils.text import convert from ..lib.graph_items import logTickValues @@ -69,12 +70,17 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.cmap = RdBuCMap(vmin=-1, vmax=1) - self.graph_checkBox.stateChanged.connect(lambda x: self.graph_comboBox.setEnabled(x == QtCore.Qt.Unchecked)) + self.graph_checkBox.stateChanged.connect( + lambda x: self.graph_comboBox.setEnabled(x == QtCore.Qt.CheckState.Unchecked) + ) self.logy_box.stateChanged.connect(lambda x: self.fit_plot.setLogMode(y=bool(x))) self.logx_box.stateChanged.connect(lambda x: self.fit_plot.setLogMode(x=bool(x))) self.resid_plot.setXLink(self.fit_plot) + self.abs_dev_button.clicked.connect(self._plot_residuals) + self.abs_dev_button.clicked.connect(self._plot_residuals) + self.set_results(results) def __call__(self, results: list): @@ -112,7 +118,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): @QtCore.pyqtSlot(int, name='on_sets_comboBox_currentIndexChanged') def set_parameter(self, idx: int): - set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole) + set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.ItemDataRole.UserRole) res = self._results[set_id] self.param_tableWidget.setRowCount(len(res.parameter)) @@ -138,11 +144,11 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): def change_opts(self, _): idx = self.sets_comboBox.currentIndex() - self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.Checked, - self.del_prev_checkBox.checkState() == QtCore.Qt.Checked) + self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.CheckState.Checked, + self.del_prev_checkBox.checkState() == QtCore.Qt.CheckState.Checked) def show_results(self, idx): - set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole) + set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.ItemDataRole.UserRole) self.set_plot(set_id) self.set_correlation(set_id) self.set_statistics(set_id) @@ -184,8 +190,6 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.data_graph_imag.setData(x=res.x_data, y=res.y_data.imag) self.fit_graph.setData(x=res.x, y=res.y.real) self.fit_graph_imag.setData(x=res.x, y=res.y.imag) - self.resid_graph.setData(x=res.x_data, y=res.residual.real) - self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag) for i, f in enumerate(sub_funcs): item = PlotItem(x=f.x, y=f.y.real, pen=mkPen({'color': i, 'style': 2})) @@ -194,8 +198,6 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.fit_plot.addItem(item) else: - self.resid_graph.setData(x=res.x_data, y=res.residual) - self.resid_graph_imag.setData(x=[], y=[]) self.data_graph.setData(x=res.x_data, y=res.y_data) self.data_graph_imag.setData(x=[], y=[]) self.fit_graph.setData(x=res.x, y=res.y) @@ -205,6 +207,8 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): item = PlotItem(x=f.x, y=f.y, pen=mkPen({'color': i, 'style': 2})) self.fit_plot.addItem(item) + self._plot_residuals(idx) + self.logx_box.blockSignals(True) self.logx_box.setChecked(res.islog) self.logx_box.blockSignals(False) @@ -225,6 +229,30 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): else: self.fit_plot.enableAutoRange() + def _plot_residuals(self, idx: str = None): + print(idx) + if idx is None: + idx = self.sets_comboBox.currentData(QtCore.Qt.ItemDataRole.UserRole) + + print(idx, self._results.keys()) + + res = self._results[idx] + if res.iscomplex: + if self.rel_dev_button.isChecked(): + self.resid_graph.setData(x=res.x_data, y=res.residual.real/np_abs(res.y_data.real)) + if all(np_isfinite(res.y_data.imag)): + self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag/np_abs(res.y_data.imag)) + else: + self.resid_graph.setData(x=res.x_data, y=res.residual.real) + self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag) + + else: + if self.rel_dev_button.isChecked(): + self.resid_graph.setData(x=res.x_data, y=res.residual / np_abs(res.y_data)) + else: + self.resid_graph.setData(x=res.x_data, y=res.residual) + self.resid_graph_imag.setData(x=[], y=[]) + def set_correlation(self, idx: str): while self.corr_tableWidget.rowCount(): self.corr_tableWidget.removeRow(0) @@ -258,20 +286,20 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.stats_tableWidget.setRowCount(len(res.statistics)+3) it = QtWidgets.QTableWidgetItem(f'{res.dof}') - it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + it.setFlags(it.flags() ^ QtCore.Qt.ItemFlag.ItemIsEditable) self.stats_tableWidget.setVerticalHeaderItem(0, QtWidgets.QTableWidgetItem('DoF')) self.stats_tableWidget.setItem(0, 0, it) for col, (name, _, dof) in enumerate(self._previous_fits[idx], start=1): self.stats_tableWidget.setHorizontalHeaderItem(0, QtWidgets.QTableWidgetItem(name)) it = QtWidgets.QTableWidgetItem(f'{dof}') - it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + it.setFlags(it.flags() ^ QtCore.Qt.ItemFlag.ItemIsEditable) self.stats_tableWidget.setItem(0, col, it) for row, (k, v) in enumerate(res.statistics.items(), start=1): self.stats_tableWidget.setVerticalHeaderItem(row, QtWidgets.QTableWidgetItem(k)) it = QtWidgets.QTableWidgetItem(f'{v:.4f}') - it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + it.setFlags(it.flags() ^ QtCore.Qt.ItemFlag.ItemIsEditable) self.stats_tableWidget.setItem(row, 0, it) best_idx = -1 @@ -282,7 +310,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): else: best_idx = col if best_val > stats[k] else max(0, best_idx) it = QtWidgets.QTableWidgetItem(f'{stats[k]:.4f}') - it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + it.setFlags(it.flags() ^ QtCore.Qt.ItemFlag.ItemIsEditable) self.stats_tableWidget.setItem(row, col, it) if best_idx > -1: @@ -299,11 +327,11 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): for col, (_, stats, dof) in enumerate(self._previous_fits[idx], start=1): f_value, prob_f = res.f_test(stats['chi^2'], dof) it = QtWidgets.QTableWidgetItem(f'{f_value:.4g}') - it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + it.setFlags(it.flags() ^ QtCore.Qt.ItemFlag.ItemIsEditable) self.corr_tableWidget.setItem(row, col, it) it = QtWidgets.QTableWidgetItem(f'{prob_f:.4g}') - it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + it.setFlags(it.flags() ^ QtCore.Qt.ItemFlag.ItemIsEditable) if prob_f < 0.05: it.setBackground(QtGui.QColor('green')) it.setForeground(QtGui.QColor('white')) @@ -319,14 +347,14 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): elif button_type == self.buttonBox.Ok: graph = '-1' if self.parameter_checkbox.isChecked(): - if self.graph_checkBox.checkState() == QtCore.Qt.Checked: + if self.graph_checkBox.checkState() == QtCore.Qt.CheckState.Checked: graph = '' else: graph = self.graph_comboBox.currentData() plot_fits = self.curve_checkbox.isChecked() - parts = self.partial_checkBox.checkState() == QtCore.Qt.Checked + parts = self.partial_checkBox.checkState() == QtCore.Qt.CheckState.Checked extrapolate = [None, None, None] error = [] @@ -382,7 +410,7 @@ class FitExtension(QtWidgets.QDialog): gridLayout.addWidget(self.num_pts, 2, 1, 1, 1) self.buttonBox = QtWidgets.QDialogButtonBox() - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok) gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2) diff --git a/src/resources/_ui/fitresult.ui b/src/resources/_ui/fitresult.ui index d821e3c..142a794 100644 --- a/src/resources/_ui/fitresult.ui +++ b/src/resources/_ui/fitresult.ui @@ -31,13 +31,13 @@ - 3 + 6 3 - 3 + 6 3 @@ -45,7 +45,20 @@ 3 - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + Auto-scale graph for all sets @@ -55,29 +68,6 @@ - - - - Qt::LeftToRight - - - logarithmic y axis - - - - - - - - - - Qt::LeftToRight - - - logarithmic x axis - - - @@ -91,6 +81,70 @@ + + + + Logarithmic axes + + + + + + Qt::LeftToRight + + + x axis + + + + + + + Qt::LeftToRight + + + y axis + + + + + + + + + + Residuals + + + + + + relative deviation + + + buttonGroup + + + + + + + absolute deviation + + + true + + + buttonGroup + + + + + + + + + @@ -198,13 +252,6 @@ - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry - - - @@ -254,6 +301,38 @@ + + + + + 0 + 0 + + + + + 400 + 16777215 + + + + + 200 + 0 + + + + QComboBox::AdjustToMinimumContentsLength + + + + + + + Reject this fit + + + @@ -400,35 +479,10 @@ - - - - - 0 - 0 - - - - - 400 - 16777215 - - - - - 200 - 0 - - - - QComboBox::AdjustToMinimumContentsLength - - - - - - - Reject this fit + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry @@ -448,4 +502,7 @@ + + +