From 3626cfc7ea7b1c18d080a0896cc66b0e8d873e7d Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 30 Jan 2024 17:01:13 +0000 Subject: [PATCH 1/9] ensure sorted sets before averaging in pick points (#226) should finally fix #189 --- .gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml | 2 +- src/nmreval/data/points.py | 39 +++++++++++++++++--------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml b/.gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml index c81f466..f7dc7d4 100644 --- a/.gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml +++ b/.gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml @@ -17,7 +17,7 @@ body: description: For which version have you observed this behavior? placeholder: You find the program version in "Help/About" validations: - required: true + required: false - type: textarea attributes: label: Expected behavior diff --git a/src/nmreval/data/points.py b/src/nmreval/data/points.py index b394efa..8332084 100644 --- a/src/nmreval/data/points.py +++ b/src/nmreval/data/points.py @@ -320,7 +320,11 @@ class Points: pts = [] _tmp_x = self._x[self.mask] - _tmp_y = self._y[self.mask] + x_order = np.argsort(_tmp_x) + _tmp_x = _tmp_x[x_order] + _tmp_y = self._y[self.mask][x_order] + _tmp_yerr = self._y_err[self.mask][x_order] + if idx is not None: for idx_i in idx: if isinstance(idx_i, tuple): @@ -338,7 +342,7 @@ class Points: right_b = int(min(len(self), x_idx + avg_range[1] + 1)) if left_b < right_b: - pts.append([_tmp_x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)]) + pts.append([_tmp_x[x_idx], *self._average(_tmp_x, _tmp_y, _tmp_yerr, avg_mode, x_idx, left_b, right_b)]) else: pts.append([_tmp_x[x_idx], _tmp_y[x_idx], self._y_err[x_idx]]) @@ -358,28 +362,37 @@ class Points: left_b = int(max(0, x_idx - avg_range[0])) right_b = int(min(len(self), x_idx + avg_range[1] + 1)) - pts.append([_tmp_x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)]) + pts.append([_tmp_x[x_idx], *self._average(_tmp_x, _tmp_y, _tmp_yerr, avg_mode, x_idx, left_b, right_b)]) return pts - def _average(self, mode: str, idx, left: int, right: int) -> tuple[float, float]: + @staticmethod + def _average( + x: np.ndarray, + y: np.ndarray, + y_err: np.ndarray, + mode: str, + idx: int, + left: int, + right: int, + ) -> tuple[float, float]: if mode == 'mean': - y_mean = np.mean(self._y[self.mask][left:right].real) - y_err = np.linalg.norm(self._y_err[self.mask][left:right]) / (right - left) + y_mean = np.mean(y[left:right].real) + y_err_mean = np.linalg.norm(y_err[left:right]) / (right - left) elif mode == 'sum': - y_mean = np.sum(self._y[self.mask][left:right].real) - y_err = np.linalg.norm(self._y_err[self.mask][left:right]) + y_mean = np.sum(y[left:right].real) + y_err_mean = np.linalg.norm(y_err[left:right]) elif mode == 'integral': - y_mean = simpson(self._y[self.mask][left:right].real, x=self._x[left:right]) - y_err = np.linalg.norm(cumulative_trapezoid(self._y_err[self.mask][left:right].real, x=self._x[left:right])) + y_mean = simpson(y[left:right].real, x=x[left:right]) + y_err_mean = np.linalg.norm(cumulative_trapezoid(y_err[left:right].real, x=x[left:right])) else: - y_mean = self._y[self.mask][idx].real - y_err = self._y_err[self.mask][idx] + y_mean = y[idx].real + y_err_mean = y_err[idx] - return y_mean, y_err + return y_mean, y_err_mean def concatenate(self, other): """ From 813e18a7440c1bf6ba39e13c3f3eccecb0df0668 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 30 Jan 2024 18:01:15 +0000 Subject: [PATCH 2/9] make log-spacing explicit option for custom fit x values (#227) closes #225 --- src/gui_qt/_py/fitresult.py | 95 +++++++++++++++--------- src/gui_qt/fit/result.py | 17 ++++- src/gui_qt/main/mainwindow.py | 3 +- src/gui_qt/main/management.py | 2 +- src/resources/_ui/fitresult.ui | 132 +++++++++++++++++++++------------ 5 files changed, 159 insertions(+), 90 deletions(-) diff --git a/src/gui_qt/_py/fitresult.py b/src/gui_qt/_py/fitresult.py index 54becc9..0f73b79 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 'resources/_ui/fitresult.ui' +# Form implementation generated from reading ui file 'src/resources/_ui/fitresult.ui' # # Created by: PyQt5 UI code generator 5.15.10 # @@ -157,67 +157,91 @@ class Ui_Dialog(object): self.gridLayout_2.setContentsMargins(3, 3, 3, 3) self.gridLayout_2.setSpacing(3) self.gridLayout_2.setObjectName("gridLayout_2") - self.extrapolate_box = QtWidgets.QCheckBox(self.groupBox) - self.extrapolate_box.setObjectName("extrapolate_box") - self.gridLayout_2.addWidget(self.extrapolate_box, 1, 0, 1, 1) - self.parameter_checkbox = QtWidgets.QCheckBox(self.groupBox) - self.parameter_checkbox.setObjectName("parameter_checkbox") - self.gridLayout_2.addWidget(self.parameter_checkbox, 0, 5, 1, 1) self.graph_comboBox = QtWidgets.QComboBox(self.groupBox) self.graph_comboBox.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.graph_comboBox.sizePolicy().hasHeightForWidth()) self.graph_comboBox.setSizePolicy(sizePolicy) self.graph_comboBox.setObjectName("graph_comboBox") - self.gridLayout_2.addWidget(self.graph_comboBox, 1, 6, 1, 1) - self.graph_checkBox = QtWidgets.QCheckBox(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.graph_checkBox.sizePolicy().hasHeightForWidth()) - self.graph_checkBox.setSizePolicy(sizePolicy) - self.graph_checkBox.setChecked(True) - self.graph_checkBox.setObjectName("graph_checkBox") - self.gridLayout_2.addWidget(self.graph_checkBox, 1, 5, 1, 1) + self.gridLayout_2.addWidget(self.graph_comboBox, 1, 7, 1, 1) self.minx_line = QtWidgets.QLineEdit(self.groupBox) self.minx_line.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.minx_line.sizePolicy().hasHeightForWidth()) self.minx_line.setSizePolicy(sizePolicy) self.minx_line.setObjectName("minx_line") self.gridLayout_2.addWidget(self.minx_line, 1, 1, 1, 1) - self.line_2 = QtWidgets.QFrame(self.groupBox) - self.line_2.setFrameShape(QtWidgets.QFrame.VLine) - self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_2.setObjectName("line_2") - self.gridLayout_2.addWidget(self.line_2, 0, 4, 2, 1) + self.extrapolate_box = QtWidgets.QCheckBox(self.groupBox) + self.extrapolate_box.setObjectName("extrapolate_box") + self.gridLayout_2.addWidget(self.extrapolate_box, 1, 0, 1, 1) + self.numx_line = QtWidgets.QLineEdit(self.groupBox) + self.numx_line.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.numx_line.sizePolicy().hasHeightForWidth()) + self.numx_line.setSizePolicy(sizePolicy) + self.numx_line.setObjectName("numx_line") + self.gridLayout_2.addWidget(self.numx_line, 1, 3, 1, 1) + self.graph_checkBox = QtWidgets.QCheckBox(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.graph_checkBox.sizePolicy().hasHeightForWidth()) + self.graph_checkBox.setSizePolicy(sizePolicy) + self.graph_checkBox.setChecked(True) + self.graph_checkBox.setObjectName("graph_checkBox") + self.gridLayout_2.addWidget(self.graph_checkBox, 1, 6, 1, 1) self.maxx_line = QtWidgets.QLineEdit(self.groupBox) self.maxx_line.setEnabled(False) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.maxx_line.sizePolicy().hasHeightForWidth()) self.maxx_line.setSizePolicy(sizePolicy) self.maxx_line.setObjectName("maxx_line") self.gridLayout_2.addWidget(self.maxx_line, 1, 2, 1, 1) - self.numx_line = QtWidgets.QLineEdit(self.groupBox) - self.numx_line.setEnabled(False) - self.numx_line.setObjectName("numx_line") - self.gridLayout_2.addWidget(self.numx_line, 1, 3, 1, 1) + self.line_2 = QtWidgets.QFrame(self.groupBox) + self.line_2.setFrameShape(QtWidgets.QFrame.VLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout_2.addWidget(self.line_2, 0, 5, 2, 1) + self.newx_log_checkbox = QtWidgets.QCheckBox(self.groupBox) + self.newx_log_checkbox.setEnabled(False) + self.newx_log_checkbox.setObjectName("newx_log_checkbox") + self.gridLayout_2.addWidget(self.newx_log_checkbox, 1, 4, 1, 1) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.curve_checkbox = QtWidgets.QCheckBox(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.curve_checkbox.sizePolicy().hasHeightForWidth()) + self.curve_checkbox.setSizePolicy(sizePolicy) self.curve_checkbox.setChecked(True) self.curve_checkbox.setObjectName("curve_checkbox") self.horizontalLayout.addWidget(self.curve_checkbox) self.partial_checkBox = QtWidgets.QCheckBox(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.partial_checkBox.sizePolicy().hasHeightForWidth()) + self.partial_checkBox.setSizePolicy(sizePolicy) self.partial_checkBox.setObjectName("partial_checkBox") self.horizontalLayout.addWidget(self.partial_checkBox) - self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 4) + self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 5) + self.parameter_checkbox = QtWidgets.QCheckBox(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.parameter_checkbox.sizePolicy().hasHeightForWidth()) + self.parameter_checkbox.setSizePolicy(sizePolicy) + self.parameter_checkbox.setObjectName("parameter_checkbox") + self.gridLayout_2.addWidget(self.parameter_checkbox, 0, 6, 1, 2) self.gridLayout.addWidget(self.groupBox, 7, 0, 1, 2) self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Retry) @@ -253,16 +277,17 @@ class Ui_Dialog(object): 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")) - self.parameter_checkbox.setText(_translate("Dialog", "Plot parameter")) - self.graph_checkBox.setText(_translate("Dialog", "New graph for parameter")) self.minx_line.setToolTip(_translate("Dialog", "Leave empty to start at lowest point")) self.minx_line.setPlaceholderText(_translate("Dialog", "min x")) + self.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function")) + self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves")) + self.numx_line.setPlaceholderText(_translate("Dialog", "# pts")) + self.graph_checkBox.setText(_translate("Dialog", "New graph for parameter")) self.maxx_line.setToolTip(_translate("Dialog", "Leave empty to start at highest point")) self.maxx_line.setPlaceholderText(_translate("Dialog", "max x")) - self.numx_line.setPlaceholderText(_translate("Dialog", "# pts")) + self.newx_log_checkbox.setText(_translate("Dialog", "log-spaced?")) self.curve_checkbox.setText(_translate("Dialog", "Plot fit curve")) self.partial_checkBox.setText(_translate("Dialog", "Plot partial functions")) + self.parameter_checkbox.setText(_translate("Dialog", "Plot parameter")) 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 17b3799..4706cbc 100644 --- a/src/gui_qt/fit/result.py +++ b/src/gui_qt/fit/result.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from math import isnan from pyqtgraph import mkBrush, mkPen @@ -28,6 +30,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.extrapolate_box.stateChanged.connect(lambda x: self.maxx_line.setEnabled(x)) self.extrapolate_box.stateChanged.connect(lambda x: self.minx_line.setEnabled(x)) self.extrapolate_box.stateChanged.connect(lambda x: self.numx_line.setEnabled(x)) + self.extrapolate_box.stateChanged.connect(lambda x: self.newx_log_checkbox.setEnabled(x)) self._previous_fits = {} self._opts = [] @@ -352,7 +355,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): parts = self.partial_checkBox.checkState() == QtCore.Qt.CheckState.Checked - extrapolate = [None, None, None] + extrapolate = [None, None, None, None] error = [] if self.extrapolate_box.isChecked(): try: @@ -368,6 +371,8 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): except (TypeError, ValueError): error.append('Number of points is missing') + extrapolate[3] = self.newx_log_checkbox.isChecked() + if error: msg = QtWidgets.QMessageBox.warning(self, 'Error', 'Extrapolation failed because:\n' + '\n'.join(error)) return @@ -405,10 +410,13 @@ class FitExtension(QtWidgets.QDialog): self.num_pts.setValidator(QtGui.QIntValidator()) gridLayout.addWidget(self.num_pts, 2, 1, 1, 1) + self.logx_checkbox = QtWidgets.QCheckBox('Log-spaced?') + gridLayout.addWidget(self.logx_checkbox, 3, 0, 1, 2) + self.buttonBox = QtWidgets.QDialogButtonBox() self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok) - gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2) + gridLayout.addWidget(self.buttonBox, 4, 0, 1, 2) self.setLayout(gridLayout) @@ -416,12 +424,13 @@ class FitExtension(QtWidgets.QDialog): self.buttonBox.rejected.connect(self.reject) @property - def values(self): + def values(self) -> tuple[float, float, int, bool] | None: try: xmin = float(self.min_line.text()) xmax = float(self.max_line.text()) nums = int(self.num_pts.text()) + logx = self.logx_checkbox.isChecked() except TypeError: return None - return xmin, xmax, nums + return xmin, xmax, nums, logx diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index 8575319..80935b6 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -984,7 +984,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): res = w.exec() if res: p = w.values - x = linspace(p[0], p[1], num=p[2]) + spacefunc = geomspace if p[3] else linspace + x = spacefunc(p[0], p[1], num=p[2]) self.management.extend_fits(sets, x) @QtCore.pyqtSlot(name='on_action_create_fit_function_triggered') diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py index 3185b30..b8758b2 100644 --- a/src/gui_qt/main/management.py +++ b/src/gui_qt/main/management.py @@ -613,7 +613,7 @@ class UpperManagement(QtCore.QObject): continue if not all(e is None for e in extrapolate): - spacefunc = np.geomspace if fit.islog else np.linspace + spacefunc = np.geomspace if extrapolate[3] else np.linspace xmin = fit.x.min() xmax = fit.x.max() diff --git a/src/resources/_ui/fitresult.ui b/src/resources/_ui/fitresult.ui index 142a794..9f8bd4f 100644 --- a/src/resources/_ui/fitresult.ui +++ b/src/resources/_ui/fitresult.ui @@ -354,59 +354,26 @@ 3 - - - - Extrapolates only main function - - - Extrapolate curves - - - - - - - Plot parameter - - - - + false - + 0 0 - - - - - 0 - 0 - - - - New graph for parameter - - - true - - - false - + 0 0 @@ -419,10 +386,45 @@ - - - - Qt::Vertical + + + + Extrapolates only main function + + + Extrapolate curves + + + + + + + false + + + + 0 + 0 + + + + # pts + + + + + + + + 0 + 0 + + + + New graph for parameter + + + true @@ -432,7 +434,7 @@ false - + 0 0 @@ -445,20 +447,33 @@ - - - - false - - - # pts + + + + Qt::Vertical - + + + + false + + + log-spaced? + + + + + + + 0 + 0 + + Plot fit curve @@ -469,6 +484,12 @@ + + + 0 + 0 + + Plot partial functions @@ -476,6 +497,19 @@ + + + + + 0 + 0 + + + + Plot parameter + + + From 7161a173487981d864d96248ed218b11b33d80e6 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 31 Jan 2024 16:00:07 +0000 Subject: [PATCH 3/9] 228-229-index-problems (#230) bugfixes: closes #228, closes #229 --- src/gui_qt/math/smooth.py | 22 +++++++++++++++++----- src/nmreval/io/asciireader.py | 10 ++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/gui_qt/math/smooth.py b/src/gui_qt/math/smooth.py index 387865d..d2ca5e7 100644 --- a/src/gui_qt/math/smooth.py +++ b/src/gui_qt/math/smooth.py @@ -12,10 +12,10 @@ class QSmooth(QtWidgets.QDialog, Ui_SmoothDialog): @QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged') def change_mode(self, idx: int): - if idx == 2: + if idx == 1: self.widget.show() self.widget_2.hide() - elif idx == 3: + elif idx == 2: self.widget.show() self.widget_2.show() else: @@ -29,12 +29,24 @@ class QSmooth(QtWidgets.QDialog, Ui_SmoothDialog): idx = self.comboBox.currentIndex() # this order must match the combobox - para['mode'] = ['mean', 'savgol', 'loess', 'median', 'std', 'var', 'max', 'min', 'sum'][idx] + para['mode'] = [ + 'mean', + 'savgol', + 'loess', + 'median', + 'std', + 'var', + 'max', + 'min', + 'sum', + ][idx] - if idx == 2: + # Savitzky-Golay needs also polynomial degree + if idx == 1: para['deg'] = self.polynom_spinBox.value() - if idx == 3: + # LOESS needs also polynomial degree and number of iterations + if idx == 2: para['deg'] = self.polynom_spinBox.value() para['it'] = self.iter_spinBox.value() diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py index e8c0b4f..05f84a5 100644 --- a/src/nmreval/io/asciireader.py +++ b/src/nmreval/io/asciireader.py @@ -183,7 +183,13 @@ class AsciiReader: single_len = 2 stepsize = 2 - cls = {'points': Points, 'fid': FID, 'spectrum': Spectrum, 'bds': BDS, 'dsc': DSC}[mode] + cls = { + 'points': Points, + 'fid': FID, + 'spectrum': Spectrum, + 'bds': BDS, + 'dsc': DSC, + }[mode] for j in range(1, num_y+1, stepsize): if col_names is not None: @@ -191,7 +197,7 @@ class AsciiReader: kwargs['name'] = col_names[j-1] elif num_y > single_len: # more than one axis, append column number - kwargs['name'] = filename + '_' + str(y[j-1]) + kwargs['name'] = f'{filename}_{y[j-1]+1}' if j+num_y < raw_data.shape[2]: kwargs['y_err'] = raw_data[i, :, j+num_y] From 39b0fe75cb1375c49aefd0404e0178b2ca15e2cc Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Sat, 3 Feb 2024 11:25:35 +0000 Subject: [PATCH 4/9] check relative path; remove possible cause of #231 (#232) Co-authored-by: Dominik Demuth Reviewed-on: https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/pulls/232 --- src/gui_qt/io/dscreader.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/gui_qt/io/dscreader.py b/src/gui_qt/io/dscreader.py index b3b0768..a9b83a0 100644 --- a/src/gui_qt/io/dscreader.py +++ b/src/gui_qt/io/dscreader.py @@ -78,14 +78,22 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): for opts in self.sample.steps: item = QtWidgets.QListWidgetItem() - item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable) - item.setCheckState(QtCore.Qt.Unchecked) + item.setFlags( + QtCore.Qt.ItemFlag.ItemIsEnabled | + QtCore.Qt.ItemFlag.ItemIsSelectable | + QtCore.Qt.ItemFlag.ItemIsUserCheckable + ) + item.setCheckState(QtCore.Qt.CheckState.Unchecked) if opts[0] == 'i': - item.setFlags(QtCore.Qt.NoItemFlags) + item.setFlags(QtCore.Qt.ItemFlag.NoItemFlags) item.setText(f'{opts[1]:.2f} K for {opts[2] / 60:.0f} min') else: - item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable) + item.setFlags( + QtCore.Qt.ItemFlag.ItemIsEnabled | + QtCore.Qt.ItemFlag.ItemIsSelectable | + QtCore.Qt.ItemFlag.ItemIsUserCheckable + ) item.setText(f'{opts[2]:.2f} K to {opts[3]:.2f} K with {opts[1]} K/min') self.step_listWidget.addItem(item) @@ -97,7 +105,12 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): if empty: self.empty = self.calibrator.set_measurement(empty, mode='empty') - self.empty_label.setText('~/' + str(self.empty.fname.relative_to(Path.home()))) + + # avoid ValueError breaking data update + if self.empty.fname.is_relative_to(Path.home()): + self.empty_label.setText('~/' + str(self.empty.fname.relative_to(Path.home()))) + else: + self.empty_label.setText(str(self.empty.fname)) self.update_plots() @@ -118,8 +131,8 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): self.references.append(ref) item = QtWidgets.QTableWidgetItem(str(ref.fname.name)) - item.setData(QtCore.Qt.UserRole, ref.fname) - item.setFlags(QtCore.Qt.ItemIsEnabled) + item.setData(QtCore.Qt.ItemDataRole.UserRole, ref.fname) + item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled) rowcnt = self.reference_tableWidget.rowCount() self.reference_tableWidget.setRowCount(rowcnt+1) @@ -132,7 +145,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): @QtCore.pyqtSlot(name='on_ref_remove_pushButton_clicked') def remove_reference(self): idx = self.reference_tableWidget.currentRow() - self.calibrator.remove_reference(self.reference_tableWidget.item(idx, 0).data(QtCore.Qt.UserRole)) + self.calibrator.remove_reference(self.reference_tableWidget.item(idx, 0).data(QtCore.Qt.ItemDataRole.UserRole)) self.reference_tableWidget.removeRow(idx) self.update_plots() @@ -145,10 +158,10 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): for row in range(self.step_listWidget.count()): if idx == row: continue - self.step_listWidget.item(row).setCheckState(QtCore.Qt.Unchecked) + self.step_listWidget.item(row).setCheckState(QtCore.Qt.CheckState.Unchecked) self.step_listWidget.blockSignals(False) - if item.checkState() == QtCore.Qt.Checked: + if item.checkState() == QtCore.Qt.CheckState.Checked: mode, rate, _, _ = self.sample.steps[idx] self.current_run = (rate, mode) self.sample_idx = idx @@ -217,6 +230,8 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): if empty_data is not None: self.empty_sample.setData(x=empty_data[0], y=empty_data[1]) + else: + self.empty_sample.setData(x=[], y=[]) self.calib_graph.clear() From 567148b7e656f2e5c35cf366157776b73e399b3c Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 6 Feb 2024 17:23:00 +0000 Subject: [PATCH 5/9] 231-dsc-empty-baseline (#236) closes #231 --- src/gui_qt/dsc/glass_dialog.py | 28 ++++++++++++++-------------- src/gui_qt/io/dscreader.py | 9 +++++++-- src/nmreval/io/dsc.py | 30 ++++++++++++++++++++---------- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/gui_qt/dsc/glass_dialog.py b/src/gui_qt/dsc/glass_dialog.py index f815b7a..b7afe09 100644 --- a/src/gui_qt/dsc/glass_dialog.py +++ b/src/gui_qt/dsc/glass_dialog.py @@ -136,8 +136,8 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): max_x = max(max_x, data.x.max()) item = QtWidgets.QListWidgetItem(name) - item.setCheckState(QtCore.Qt.Checked) - item.setData(QtCore.Qt.UserRole, key) + item.setCheckState(QtCore.Qt.CheckState.Checked) + item.setData(QtCore.Qt.ItemDataRole.UserRole, key) item.setForeground(mkBrush(c.rgb())) self.listWidget.addItem(item) @@ -191,10 +191,10 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): for idx in range(self.listWidget.count()): item = self.listWidget.item(idx) - if item.checkState() == QtCore.Qt.Unchecked: + if item.checkState() == QtCore.Qt.CheckState.Unchecked: continue - key = item.data(QtCore.Qt.UserRole) + key = item.data(QtCore.Qt.ItemDataRole.UserRole) plot = self._plots[key] data, _ = self._dsc[key] @@ -214,7 +214,7 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): item = self.listWidget.item(idx) tree_item = QtWidgets.QTreeWidgetItem([item.text()]) - values = self._tg_value.get(item.data(QtCore.Qt.UserRole)) + values = self._tg_value.get(item.data(QtCore.Qt.ItemDataRole.UserRole)) if values is not None: for name, pos in values.items(): @@ -223,7 +223,7 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): self.tg_tree.addTopLevelItem(tree_item) - key = item.data(QtCore.Qt.UserRole) + key = item.data(QtCore.Qt.ItemDataRole.UserRole) plot = self._plots[key] data, _ = self._dsc[key] @@ -251,7 +251,7 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): @QtCore.pyqtSlot(QtWidgets.QListWidgetItem) def change_visibility(self, item: QtWidgets.QListWidgetItem): is_checked = bool(item.checkState()) - plot = self._plots[item.data(QtCore.Qt.UserRole)] + plot = self._plots[item.data(QtCore.Qt.ItemDataRole.UserRole)] for val in plot: val.setVisible(is_checked) @@ -275,10 +275,10 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): self.tnmh_tree.clear() for idx in range(self.listWidget.count()): item = self.listWidget.item(idx) - if item.checkState() == QtCore.Qt.Unchecked: + if item.checkState() == QtCore.Qt.CheckState.Unchecked: continue - key = item.data(QtCore.Qt.UserRole) + key = item.data(QtCore.Qt.ItemDataRole.UserRole) data = self.get_fictive(key, baselines) @@ -292,7 +292,7 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): item = self.listWidget.item(idx) tree_item = QtWidgets.QTreeWidgetItem([item.text()]) - values = self._fit.get(item.data(QtCore.Qt.UserRole)) + values = self._fit.get(item.data(QtCore.Qt.ItemDataRole.UserRole)) if values is not None: child_item = QtWidgets.QTreeWidgetItem([values.parameter_string()]) @@ -305,10 +305,10 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): ret_dic = {} for idx in range(self.listWidget.count()): item = self.listWidget.item(idx) - if item.checkState() == QtCore.Qt.Unchecked: + if item.checkState() == QtCore.Qt.CheckState.Unchecked: continue - key = item.data(QtCore.Qt.UserRole) + key = item.data(QtCore.Qt.ItemDataRole.UserRole) cp = None if self.fictive_export_check.isChecked(): @@ -332,10 +332,10 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog): m = [] for idx in range(self.listWidget.count()): item = self.listWidget.item(idx) - if item.checkState() == QtCore.Qt.Unchecked: + if item.checkState() == QtCore.Qt.CheckState.Unchecked: continue - key = item.data(QtCore.Qt.UserRole) + key = item.data(QtCore.Qt.ItemDataRole.UserRole) data, _ = self._dsc[key] try: tg_value = self._tg_value[key][tg_type][0] diff --git a/src/gui_qt/io/dscreader.py b/src/gui_qt/io/dscreader.py index a9b83a0..1ebda46 100644 --- a/src/gui_qt/io/dscreader.py +++ b/src/gui_qt/io/dscreader.py @@ -264,11 +264,16 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): except TypeError: return + if self.cp_checkBox.isChecked() and self.references: + y_label = 'cp' + else: + y_label = 'q' + rate, mode = self.current_run - new_val = DSC(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate} ({mode})') + new_val = DSC(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate}K-min ({mode}, {y_label})') if filesave: - new_val.savetxt(self.fname.with_name(f'{self.fname.stem} {rate}K-min {mode}.dat'.replace(' ', '_'))) + new_val.savetxt(self.fname.with_name(f'{self.fname.stem}_{rate}K-min_{y_label}{mode}.dat'.replace(' ', '_'))) close_after = False else: self.data_read.emit([new_val]) diff --git a/src/nmreval/io/dsc.py b/src/nmreval/io/dsc.py index b531bd4..4aaf606 100644 --- a/src/nmreval/io/dsc.py +++ b/src/nmreval/io/dsc.py @@ -11,7 +11,8 @@ try: from scipy.integrate import simpson except ImportError: from scipy.integrate import simps as simpson -from scipy.interpolate import interp1d +from scipy.interpolate import CubicSpline + ReferenceValue = namedtuple('Reference', ['name', 'transitions']) Cyclohexane = ReferenceValue('Cyclohexane', [(-87.06+273.15, 79.58), (6.54+273.15, None)]) @@ -38,7 +39,7 @@ class DSCSample: def read_file(self, fname: str | Path) -> None: fname = Path(fname) - # file contains weird deg C character in stupiod ISO encoding + # file contains weird deg C character in stupid ISO encoding with fname.open('r', encoding='iso-8859-15') as f: ii = 1 for line in f: @@ -144,9 +145,12 @@ class DSCCalibrator: self.reference = [] self.ref_list = [] - def set_measurement(self, - fname: str | Path | DSCSample, mode: str = 'sample', - reference: ReferenceValue = Cyclohexane): + def set_measurement( + self: DSCCalibrator, + fname: str | Path | DSCSample, + mode: str = 'sample', + reference: ReferenceValue = Cyclohexane + ): if mode not in ['sample', 'empty', 'reference']: raise ValueError(f'Unknown mode {mode}, not "sample", "empty", "reference"') if mode == 'reference' and not isinstance(reference, ReferenceValue): @@ -266,7 +270,12 @@ class DSCCalibrator: return sol - def get_data(self, idx: int, slope: str = 'iso', limits: tuple[float, float] = None): + def get_data( + self: DSCCalibrator, + idx: int, + slope: str = 'iso', + limits: tuple[float, float] = None + ) -> tuple[np.ndarray, np.ndarray, np.ndarray,np.ndarray | None, np.ndarray]: if self.sample.steps[idx][0] == 'i': raise ValueError('baseline correction is not implemented for isotherms') @@ -292,7 +301,7 @@ class DSCCalibrator: empty_y = empty_data[1] if self.sample.length(idx) != self.empty.length(idx_empty): with np.errstate(all='ignore'): - empty_y = interp1d(empty_data[2]-empty_data[2, 0], empty_data[1], fill_value='extrapolate')(sample_data[2, 0]) + empty_y = CubicSpline(empty_data[2]-empty_data[2, 0], empty_data[1], extrapolate=True)(sample_data[2] - sample_data[2, 0]) sample_data[1] -= empty_y drift_value = sample_data.copy()[(2, 1), :] @@ -346,9 +355,10 @@ class DSCCalibrator: offset = region[0, 0] sample_data[1] -= m * (sample_data[2] - region[1, 0]) + offset - line = np.array([[sample_data[2, 0], sample_data[2, -1]], - [m * (sample_data[2, 0] - region[1, 0]) + offset, - m * (sample_data[2, -1] - region[1, 0]) + offset]]) + line = np.array([ + [sample_data[2, 0], sample_data[2, -1]], + [m * (sample_data[2, 0] - region[1, 0]) + offset, m * (sample_data[2, -1] - region[1, 0]) + offset] + ]) else: line = np.array([[sample_data[2, 0], sample_data[2, -1]], [0, 0]]) From 40746bfa7c741126f77335619b0b0aa6ea2b3016 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 7 Feb 2024 17:55:07 +0000 Subject: [PATCH 6/9] add exclude range to fit limits (#237) Co-authored-by: Dominik Demuth Reviewed-on: https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/pulls/237 --- src/gui_qt/_py/basewindow.py | 5 +++++ src/gui_qt/fit/fit_toolbar.py | 6 ++++-- src/gui_qt/main/mainwindow.py | 4 ++-- src/gui_qt/main/management.py | 5 ++++- src/resources/_ui/basewindow.ui | 9 +++++++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/gui_qt/_py/basewindow.py b/src/gui_qt/_py/basewindow.py index 3b24795..d916548 100644 --- a/src/gui_qt/_py/basewindow.py +++ b/src/gui_qt/_py/basewindow.py @@ -365,6 +365,9 @@ class Ui_BaseWindow(object): self.actionBinning.setObjectName("actionBinning") self.actionTNMH = QtWidgets.QAction(BaseWindow) self.actionTNMH.setObjectName("actionTNMH") + self.actionExclude_region = QtWidgets.QAction(BaseWindow) + self.actionExclude_region.setCheckable(True) + self.actionExclude_region.setObjectName("actionExclude_region") self.menuSave.addAction(self.actionSave) self.menuSave.addAction(self.actionExportGraphic) self.menuSave.addAction(self.action_save_fit_parameter) @@ -419,6 +422,7 @@ class Ui_BaseWindow(object): self.menuLimits.addAction(self.action_no_range) self.menuLimits.addAction(self.action_x_range) self.menuLimits.addAction(self.action_custom_range) + self.menuLimits.addAction(self.actionExclude_region) self.menuFit.addAction(self.action_FitWidget) self.menuFit.addSeparator() self.menuFit.addAction(self.action_create_fit_function) @@ -631,6 +635,7 @@ class Ui_BaseWindow(object): self.actionTNMH_model.setText(_translate("BaseWindow", "Tg , Hodge, TNMH,,,")) self.actionBinning.setText(_translate("BaseWindow", "Binning...")) self.actionTNMH.setText(_translate("BaseWindow", "TNMH...")) + self.actionExclude_region.setText(_translate("BaseWindow", "Exclude region")) from ..data.datawidget.datawidget import DataWidget from ..data.integral_widget import IntegralWidget from ..data.point_select import PointSelectWidget diff --git a/src/gui_qt/fit/fit_toolbar.py b/src/gui_qt/fit/fit_toolbar.py index 4cfa091..283c335 100644 --- a/src/gui_qt/fit/fit_toolbar.py +++ b/src/gui_qt/fit/fit_toolbar.py @@ -58,7 +58,8 @@ class FitToolbar(QtWidgets.QToolBar): @QtCore.pyqtSlot(QtWidgets.QAction) def change_limit_type(self, action: QtWidgets.QAction): - is_custom = (action.text() == 'Custom') + is_custom = (action.text() in ['Custom', 'Exclude region']) + print(is_custom) for w in [self.label, self.label2, self.lineedit, self.lineedit2]: w.setEnabled(is_custom) @@ -93,5 +94,6 @@ class FitToolbar(QtWidgets.QToolBar): return { 'None': 'none', 'Visible x range': 'x', - 'Custom': self.region.getRegion(), + 'Custom': ('in', self.region.getRegion()), + 'Exclude region': ('out', self.region.getRegion()), }[action_text] diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index 80935b6..d91b3ac 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -888,7 +888,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.fit_dialog.load(self.management.active_sets) for item in self.fit_dialog.preview_lines: self.current_graph_widget.add_external(item) - if self.action_custom_range.isChecked(): + if self.action_custom_range.isChecked() or self.actionExclude_region.isChecked(): self.current_graph_widget.add_external(self.fit_toolbar.region) block_window = True @@ -904,7 +904,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): if self.current_graph_widget is None: return - if action == self.action_custom_range and self.fit_dialog.isVisible(): + if action in [self.action_custom_range, self.actionExclude_region] and self.fit_dialog.isVisible(): self.current_graph_widget.add_external(self.fit_toolbar.region) else: self.current_graph_widget.remove_external(self.fit_toolbar.region) diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py index b8758b2..ab07037 100644 --- a/src/gui_qt/main/management.py +++ b/src/gui_qt/main/management.py @@ -511,13 +511,16 @@ class UpperManagement(QtCore.QObject): _x = data_i.x + # options for fit limits 'none', 'x', ('in', custom region), ('out', excluded region) if fit_limits == 'none': inside = slice(None) elif fit_limits == 'x': x_lim, _ = self.graphs[self.current_graph].ranges inside = np.where((_x >= x_lim[0]) & (_x <= x_lim[1])) + elif fit_limits[0] == 'in': + inside = np.where((_x >= fit_limits[1][0]) & (_x <= fit_limits[1][1])) else: - inside = np.where((_x >= fit_limits[0]) & (_x <= fit_limits[1])) + inside = np.where((_x < fit_limits[1][0]) | (_x > fit_limits[1][1])) try: if isinstance(we, str): diff --git a/src/resources/_ui/basewindow.ui b/src/resources/_ui/basewindow.ui index bf3de79..01987b8 100644 --- a/src/resources/_ui/basewindow.ui +++ b/src/resources/_ui/basewindow.ui @@ -247,6 +247,7 @@ + @@ -1021,6 +1022,14 @@ TNMH... + + + true + + + Exclude region + + From 881eff2770b1b17cbff1db004255c9bb251c5afd Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 7 Feb 2024 18:11:15 +0000 Subject: [PATCH 7/9] update tool tip; fixes #234 Co-authored-by: Dominik Demuth Reviewed-on: https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/pulls/238 --- src/gui_qt/data/datawidget/datawidget.py | 106 +++++++++++++---------- src/gui_qt/data/datawidget/properties.py | 6 +- 2 files changed, 65 insertions(+), 47 deletions(-) diff --git a/src/gui_qt/data/datawidget/datawidget.py b/src/gui_qt/data/datawidget/datawidget.py index 45ffc52..e24c10d 100644 --- a/src/gui_qt/data/datawidget/datawidget.py +++ b/src/gui_qt/data/datawidget/datawidget.py @@ -49,11 +49,16 @@ class DataTree(QtWidgets.QTreeWidget): def add_graph(self, idd: str, name: str): item = QtWidgets.QTreeWidgetItem() - item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable | - QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable) + item.setFlags( + QtCore.Qt.ItemFlag.ItemIsSelectable | + QtCore.Qt.ItemFlag.ItemIsDropEnabled | + QtCore.Qt.ItemFlag.ItemIsEditable | + QtCore.Qt.ItemFlag.ItemIsEnabled | + QtCore.Qt.ItemFlag.ItemIsUserCheckable + ) item.setText(0, name) - item.setData(0, QtCore.Qt.UserRole, idd) - item.setCheckState(0, QtCore.Qt.Checked) + item.setData(0, QtCore.Qt.ItemDataRole.UserRole, idd) + item.setCheckState(0, QtCore.Qt.CheckState.Checked) self.addTopLevelItem(item) self._checked_graphs.add(idd) @@ -67,14 +72,19 @@ class DataTree(QtWidgets.QTreeWidget): for row in range(self.invisibleRootItem().childCount()): graph = self.invisibleRootItem().child(row) - if graph.data(0, QtCore.Qt.UserRole) == gid: + if graph.data(0, QtCore.Qt.ItemDataRole.UserRole) == gid: for (idd, name, value) in items: item = QtWidgets.QTreeWidgetItem([name]) item.setToolTip(0, f'Value: {value}') - item.setData(0, QtCore.Qt.UserRole, idd) - item.setCheckState(0, QtCore.Qt.Checked) - item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsEditable | - QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable) + item.setData(0, QtCore.Qt.ItemDataRole.UserRole, idd) + item.setCheckState(0, QtCore.Qt.CheckState.Checked) + item.setFlags( + QtCore.Qt.ItemFlag.ItemIsSelectable | + QtCore.Qt.ItemFlag.ItemIsDragEnabled | + QtCore.Qt.ItemFlag.ItemIsEditable | + QtCore.Qt.ItemFlag.ItemIsEnabled | + QtCore.Qt.ItemFlag.ItemIsUserCheckable + ) graph.addChild(item) self._checked_sets.add(idd) @@ -85,8 +95,8 @@ class DataTree(QtWidgets.QTreeWidget): @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem) def data_change(self, item: QtWidgets.QTreeWidgetItem, emit: bool = True) -> tuple[set, set]: - idd = item.data(0, QtCore.Qt.UserRole) - is_selected = item.checkState(0) == QtCore.Qt.Checked + idd = item.data(0, QtCore.Qt.ItemDataRole.UserRole) + is_selected = item.checkState(0) == QtCore.Qt.CheckState.Checked to_be_hidden = set() to_be_shown = set() @@ -104,9 +114,9 @@ class DataTree(QtWidgets.QTreeWidget): self.blockSignals(True) for i in range(item.childCount()): child = item.child(i) - child.setCheckState(0, QtCore.Qt.Checked) - to_be_shown.add(child.data(0, QtCore.Qt.UserRole)) - self._checked_sets.add(child.data(0, QtCore.Qt.UserRole)) + child.setCheckState(0, QtCore.Qt.CheckState.Checked) + to_be_shown.add(child.data(0, QtCore.Qt.ItemDataRole.UserRole)) + self._checked_sets.add(child.data(0, QtCore.Qt.ItemDataRole.UserRole)) self.blockSignals(False) # check state change to unchecked @@ -115,10 +125,10 @@ class DataTree(QtWidgets.QTreeWidget): self.blockSignals(True) for i in range(item.childCount()): child = item.child(i) - child.setCheckState(0, QtCore.Qt.Unchecked) - to_be_hidden.add(child.data(0, QtCore.Qt.UserRole)) + child.setCheckState(0, QtCore.Qt.CheckState.Unchecked) + to_be_hidden.add(child.data(0, QtCore.Qt.ItemDataRole.UserRole)) try: - self._checked_sets.remove(child.data(0, QtCore.Qt.UserRole)) + self._checked_sets.remove(child.data(0, QtCore.Qt.ItemDataRole.UserRole)) except KeyError: pass self.blockSignals(False) @@ -153,7 +163,7 @@ class DataTree(QtWidgets.QTreeWidget): @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem) def new_selection(self, item: QtWidgets.QTreeWidgetItem): if item.parent() is None: - self.management.select_window(item.data(0, QtCore.Qt.UserRole)) + self.management.select_window(item.data(0, QtCore.Qt.ItemDataRole.UserRole)) def dropEvent(self, evt: QtGui.QDropEvent): dropped_index = self.indexAt(evt.pos()) @@ -179,7 +189,7 @@ class DataTree(QtWidgets.QTreeWidget): from_parent.removeChild(it) tobemoved.append(it) - take_from.append(from_parent.data(0, QtCore.Qt.UserRole)) + take_from.append(from_parent.data(0, QtCore.Qt.ItemDataRole.UserRole)) pos = QtCore.QModelIndex(persistent_drop) if self.dropIndicatorPosition() == QtWidgets.QAbstractItemView.BelowItem: @@ -191,8 +201,8 @@ class DataTree(QtWidgets.QTreeWidget): else: to_parent.insertChildren(row, tobemoved) - self.management.move_sets([it.data(0, QtCore.Qt.UserRole) for it in tobemoved], - to_parent.data(0, QtCore.Qt.UserRole), take_from, + self.management.move_sets([it.data(0, QtCore.Qt.ItemDataRole.UserRole) for it in tobemoved], + to_parent.data(0, QtCore.Qt.ItemDataRole.UserRole), take_from, pos=-1 if append else row) self.update_indexes() @@ -207,7 +217,7 @@ class DataTree(QtWidgets.QTreeWidget): while iterator.value(): item = iterator.value() if item is not None: - data = item.data(0, QtCore.Qt.UserRole) + data = item.data(0, QtCore.Qt.ItemDataRole.UserRole) if data == gid_out: from_parent = item @@ -231,7 +241,7 @@ class DataTree(QtWidgets.QTreeWidget): self.blockSignals(False) def sort(self, graph_item: QtWidgets.QTreeWidgetItem, mode: str = 'value'): - graph_id = graph_item.data(0, QtCore.Qt.UserRole) + graph_id = graph_item.data(0, QtCore.Qt.ItemDataRole.UserRole) sets = self.management.get_attributes(graph_id, mode) sets = [el[0] for el in sorted(sets.items(), key=lambda x: x[1])] @@ -243,7 +253,7 @@ class DataTree(QtWidgets.QTreeWidget): for s in sets: for c in children: - if c.data(0, QtCore.Qt.UserRole) == s: + if c.data(0, QtCore.Qt.ItemDataRole.UserRole) == s: graph_item.addChild(c) self.update_indexes() @@ -276,7 +286,7 @@ class DataTree(QtWidgets.QTreeWidget): while iterator.value(): item = iterator.value() if item is not None: - data = item.data(0, QtCore.Qt.UserRole) + data = item.data(0, QtCore.Qt.ItemDataRole.UserRole) if data == sid: if name != item.text(0): item.setText(0, name) @@ -285,7 +295,7 @@ class DataTree(QtWidgets.QTreeWidget): iterator += 1 def keyPressEvent(self, evt: QtGui.QKeyEvent): - if evt.key() == QtCore.Qt.Key_Delete: + if evt.key() == QtCore.Qt.Key.Key_Delete: rm_sets = [] rm_graphs = [] for idx in self.selectedIndexes(): @@ -296,20 +306,20 @@ class DataTree(QtWidgets.QTreeWidget): if item.parent() is None: for c_i in range(item.childCount()): # add sets inside graph to removal - child_data = item.child(c_i).data(0, QtCore.Qt.UserRole) + child_data = item.child(c_i).data(0, QtCore.Qt.ItemDataRole.UserRole) if child_data not in rm_sets: rm_sets.append(child_data) - rm_graphs.append(item.data(0, QtCore.Qt.UserRole)) + rm_graphs.append(item.data(0, QtCore.Qt.ItemDataRole.UserRole)) else: - item_data = item.data(0, QtCore.Qt.UserRole) + item_data = item.data(0, QtCore.Qt.ItemDataRole.UserRole) if item_data not in rm_sets: rm_sets.append(item_data) # self.deleteItem.emit(rm_sets+rm_graphs) self.management.delete_sets(rm_sets+rm_graphs) - elif evt.key() == QtCore.Qt.Key_Space: + elif evt.key() == QtCore.Qt.Key.Key_Space: sets = [] from_parent = [] @@ -329,7 +339,7 @@ class DataTree(QtWidgets.QTreeWidget): for it in sets: if it in from_parent: continue - it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked) + it.setCheckState(0, QtCore.Qt.CheckState.Unchecked if it.checkState(0) == QtCore.Qt.CheckState.Checked else QtCore.Qt.CheckState.Checked) s1, s2 = self.data_change(it, emit=False) to_be_hidden |= s2 to_be_shown |= s1 @@ -353,7 +363,7 @@ class DataTree(QtWidgets.QTreeWidget): # find all items that have to be removed while iterator.value(): item = iterator.value() - _id = item.data(0, QtCore.Qt.UserRole) + _id = item.data(0, QtCore.Qt.ItemDataRole.UserRole) if _id in ids: try: item_parent = item.parent() @@ -431,7 +441,7 @@ class DataTree(QtWidgets.QTreeWidget): if i.column() == 0: continue items.append(self.itemFromIndex(i)) - graphs.append(self.itemFromIndex(i).data(0, QtCore.Qt.UserRole)) + graphs.append(self.itemFromIndex(i).data(0, QtCore.Qt.ItemDataRole.UserRole)) if action == del_action: for gid in graphs: @@ -473,12 +483,12 @@ class DataTree(QtWidgets.QTreeWidget): continue else: - graph_id = parent.data(0, QtCore.Qt.UserRole) + graph_id = parent.data(0, QtCore.Qt.ItemDataRole.UserRole) if graph_id not in idx: idx[graph_id] = [] # collect sets in their graph - idx[graph_id].append(item.data(0, QtCore.Qt.UserRole)) - data = self.management[item.data(0, QtCore.Qt.UserRole)] + idx[graph_id].append(item.data(0, QtCore.Qt.ItemDataRole.UserRole)) + data = self.management[item.data(0, QtCore.Qt.ItemDataRole.UserRole)] if data.mode == 'fit': has_fits = True @@ -523,7 +533,7 @@ class DataTree(QtWidgets.QTreeWidget): while iterator.value(): item = iterator.value() if item is not None: - if item.data(0, QtCore.Qt.UserRole) == gid: + if item.data(0, QtCore.Qt.ItemDataRole.UserRole) == gid: item.setBackground(0, QtGui.QBrush(QtGui.QColor('gray'))) else: item.setBackground(0, QtGui.QBrush()) @@ -536,10 +546,10 @@ class DataTree(QtWidgets.QTreeWidget): while iterator.value(): item = iterator.value() if item is not None: - if item.data(0, QtCore.Qt.UserRole) in sets: - item.setCheckState(0, QtCore.Qt.Unchecked) + if item.data(0, QtCore.Qt.ItemDataRole.UserRole) in sets: + item.setCheckState(0, QtCore.Qt.CheckState.Unchecked) else: - self._checked_sets.add(item.data(0, QtCore.Qt.UserRole)) + self._checked_sets.add(item.data(0, QtCore.Qt.ItemDataRole.UserRole)) iterator += 1 self.blockSignals(False) @@ -594,7 +604,7 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget): sid = [] for i in self.tree.selectedIndexes(): if i.column() == 0: - sid.append(i.data(role=QtCore.Qt.UserRole)) + sid.append(i.data(role=QtCore.Qt.ItemDataRole.UserRole)) self.startShowProperty.emit(sid) @@ -603,15 +613,23 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget): self.proptable.populate(props) def change_property(self, key1, key2, value): - ids = [item.data(0, QtCore.Qt.UserRole) for item in self.tree.selectedItems()] if key2 == 'Value': try: value = float(value) except ValueError: - QtWidgets.QMessageBox.warning(self, 'Invalid entry', - 'Value %r is not a valid number for `value`.' % value) + QtWidgets.QMessageBox.warning( + self, + 'Invalid entry', + f'Value {value!r} is not a valid number for `value`.') return + ids = [] + for item in self.tree.selectedItems(): + ids.append(item.data(0, QtCore.Qt.ItemDataRole.UserRole)) + item.setToolTip(0, str(value)) + else: + ids = [item.data(0, QtCore.Qt.ItemDataRole.UserRole) for item in self.tree.selectedItems()] + self.propertyChanged.emit(ids, key1, key2, value) def uncheck_sets(self, sets: list[str]): diff --git a/src/gui_qt/data/datawidget/properties.py b/src/gui_qt/data/datawidget/properties.py index dd07087..21e3e2d 100644 --- a/src/gui_qt/data/datawidget/properties.py +++ b/src/gui_qt/data/datawidget/properties.py @@ -41,7 +41,7 @@ class PropWidget(QtWidgets.QWidget): idx = table.indexFromItem(item) self.propertyChanged.emit(self.tab.tabText(tab_idx), table.item(idx.row(), idx.column()-1).text(), - item.data(QtCore.Qt.DisplayRole)) + item.data(QtCore.Qt.ItemDataRole.DisplayRole)) @QtCore.pyqtSlot(int) def tab_change(self, idx: int): @@ -66,10 +66,10 @@ class PropTable(QtWidgets.QTableWidget): self.blockSignals(True) for k, v in prop.items(): value_item = QtWidgets.QTableWidgetItem('') - value_item.setData(QtCore.Qt.DisplayRole, v) + value_item.setData(QtCore.Qt.ItemDataRoleDisplayRole, v) key_item = QtWidgets.QTableWidgetItem(k) - key_item.setFlags(QtCore.Qt.NoItemFlags) + key_item.setFlags(QtCore.Qt.ItemDataRole.NoItemFlags) key_item.setForeground(QtGui.QBrush(QtGui.QColor(0, 0, 0))) self.setRowCount(self.rowCount()+1) From 24640d374ea418c4e5d2c898cb93b52ec9f400f5 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 7 Feb 2024 18:58:18 +0000 Subject: [PATCH 8/9] handle graph export with empty data (#239); closes #233 Co-authored-by: Dominik Demuth Reviewed-on: https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/pulls/239 --- src/gui_qt/data/valueeditwidget.py | 43 ++++++++++++++++-------------- src/gui_qt/graphs/graphwindow.py | 27 ++++++++++++------- src/gui_qt/lib/pg_objects.py | 5 +++- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/gui_qt/data/valueeditwidget.py b/src/gui_qt/data/valueeditwidget.py index c85f1d1..ae95a5f 100644 --- a/src/gui_qt/data/valueeditwidget.py +++ b/src/gui_qt/data/valueeditwidget.py @@ -3,10 +3,10 @@ from __future__ import annotations from typing import Any from numpy import ndarray, iscomplexobj, asarray -from pyqtgraph import PlotDataItem from ..Qt import QtGui, QtCore, QtWidgets from .._py.valueeditor import Ui_MaskDialog +from ..lib.pg_objects import PlotItem class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): @@ -35,13 +35,13 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): self.tableView.setModel(self.model) self.tableView.setSelectionModel(self.selection_model) - self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.tableView.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu) self.tableView.customContextMenuRequested.connect(self.ctx) - self.selection_real = PlotDataItem(x=[], y=[], symbolSize=25, symbol='x', - pen=None, symbolPen='#c9308e', symbolBrush='#c9308e') - self.selection_imag = PlotDataItem(x=[], y=[], symbolSize=25, symbol='+', - pen=None, symbolPen='#dcdcdc', symbolBrush='#dcdcdc') + self.selection_real = PlotItem(x=[], y=[], symbolSize=25, symbol='x', + pen=None, symbolPen='#c9308e', symbolBrush='#c9308e') + self.selection_imag = PlotItem(x=[], y=[], symbolSize=25, symbol='+', + pen=None, symbolPen='#dcdcdc', symbolBrush='#dcdcdc') def __call__(self, items: dict): self.items = items @@ -133,7 +133,7 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): def keyPressEvent(self, evt): if evt.matches(QtGui.QKeySequence.Copy): self.copy_selection() - elif evt.key() == QtCore.Qt.Key_Delete: + elif evt.key() == QtCore.Qt.Key.Key_Delete: self.delete_item() else: super().keyPressEvent(evt) @@ -229,7 +229,7 @@ class ValueModel(QtCore.QAbstractTableModel): """ itemChanged = QtCore.pyqtSignal(int, int, str) load_number = 20 - maskRole = QtCore.Qt.UserRole+321 + maskRole = QtCore.Qt.ItemDataRole.UserRole+321 def __init__(self, parent=None): super().__init__(parent=parent) @@ -240,7 +240,7 @@ class ValueModel(QtCore.QAbstractTableModel): self.mask = None self.headers = ['x', 'y', '\u0394y'] for i, hd in enumerate(self.headers): - self.setHeaderData(i, QtCore.Qt.Horizontal, hd) + self.setHeaderData(i, QtCore.Qt.Orientation.Horizontal, hd) def rowCount(self, *args, **kwargs) -> int: return self.total_rows @@ -258,25 +258,28 @@ class ValueModel(QtCore.QAbstractTableModel): self.mask = mask.tolist() self.endResetModel() - self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [QtCore.Qt.DisplayRole]) + self.dataChanged.emit( + self.index(0, 0), + self.index(0, 1), [QtCore.Qt.ItemDataRole.DisplayRole] + ) - def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole) -> Any: + def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any: if not idx.isValid(): return row = idx.row() - if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]: + if role in [QtCore.Qt.ItemDataRole.DisplayRole, QtCore.Qt.ItemDataRole.EditRole]: val = self._data[row][idx.column()] return self.as_string(val) - elif role == QtCore.Qt.BackgroundRole: + elif role == QtCore.Qt.ItemDataRole.BackgroundRole: pal = QtGui.QGuiApplication.palette() if not self.mask[row]: return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Base) else: return pal.color(QtGui.QPalette.Base) - elif role == QtCore.Qt.ForegroundRole: + elif role == QtCore.Qt.ItemDataRole.ForegroundRole: pal = QtGui.QGuiApplication.palette() if not self.mask[row]: return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text) @@ -289,7 +292,7 @@ class ValueModel(QtCore.QAbstractTableModel): else: return - def setData(self, idx: QtCore.QModelIndex, value: str | bool, role=QtCore.Qt.DisplayRole) -> Any: + def setData(self, idx: QtCore.QModelIndex, value: str | bool, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any: col, row = idx.column(), idx.row() if role == ValueModel.maskRole: @@ -299,7 +302,7 @@ class ValueModel(QtCore.QAbstractTableModel): return True if value: - if role == QtCore.Qt.EditRole: + if role == QtCore.Qt.ItemDataRole.EditRole: if value == self.as_string(self._data[row][col]): return True @@ -322,9 +325,9 @@ class ValueModel(QtCore.QAbstractTableModel): else: return False - def headerData(self, section: int, orientation, role=QtCore.Qt.DisplayRole) -> Any: - if role == QtCore.Qt.DisplayRole: - if orientation == QtCore.Qt.Horizontal: + def headerData(self, section: int, orientation, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any: + if role == QtCore.Qt.ItemDataRole.DisplayRole: + if orientation == QtCore.Qt.Orientation.Horizontal: return self.headers[section] else: return str(section+1) @@ -346,7 +349,7 @@ class ValueModel(QtCore.QAbstractTableModel): self.endInsertRows() def flags(self, idx: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag: - return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemIsEditable + return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemFlag.ItemIsEditable def removeRows(self, pos: int, rows: int, parent=None, *args, **kwargs) -> bool: self.beginRemoveRows(parent, pos, pos+rows-1) diff --git a/src/gui_qt/graphs/graphwindow.py b/src/gui_qt/graphs/graphwindow.py index 9369bf3..405e520 100644 --- a/src/gui_qt/graphs/graphwindow.py +++ b/src/gui_qt/graphs/graphwindow.py @@ -670,11 +670,14 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow): else: if os.path.exists(outfile): - if QtWidgets.QMessageBox.warning(self, 'Export graphic', - f'{os.path.split(outfile)[1]} already exists.\n' - f'Do you REALLY want to replace it?', - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, - QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No: + if QtWidgets.QMessageBox.warning( + self, + 'Export graphic', + f'{os.path.split(outfile)[1]} already exists.\n' + f'Do you REALLY want to replace it?', + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, + QtWidgets.QMessageBox.No + ) == QtWidgets.QMessageBox.No: return bg_color = self._bgcolor @@ -716,16 +719,20 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow): logger.exception(f'{item} could not exported because {e.args}') continue - if len(item) == 2: - # plot can show errorbars - item_dic['yerr'] = item[1].opts['topData'] - if item_dic: + if len(item) == 2: + # plot can show errorbars + if len(item_dic['x']): + item_dic['yerr'] = item[1].opts['topData'] + else: + item_dic['yerr'] = [] dic['items'].append(item_dic) for item in self._external_items: try: - dic['items'].append(item.get_data_opts()) + item_dic = item.get_data_opts() + if item_dic: + dic['items'].append(item_dic) except Exception as e: logger.exception(f'{item} could not be exported because {e.args}') continue diff --git a/src/gui_qt/lib/pg_objects.py b/src/gui_qt/lib/pg_objects.py index 923ca88..fa678e1 100644 --- a/src/gui_qt/lib/pg_objects.py +++ b/src/gui_qt/lib/pg_objects.py @@ -183,6 +183,8 @@ class PlotItem(PlotDataItem): brush = self.opts['symbolBrush'] if isinstance(brush, tuple): self.opts['symbolcolor'] = brush + elif isinstance(brush, str): + self.opts['symbolcolor'] = int(f'0x{brush[1:3]}', 16), int(f'0x{brush[3:5]}', 16), int(f'0x{brush[5:7]}', 16) else: c = brush.color() self.opts['symbolcolor'] = c.red(), c.green(), c.blue() @@ -340,7 +342,8 @@ class PlotItem(PlotDataItem): opts = self.opts item_dic = { - 'x': x, 'y': y, + 'x': x, + 'y': y, 'name': opts.get('name', ''), 'symbolsize': opts['symbolSize'], } From 8d3ab75c979265bd0aba2f3ac2b7e31b89eb988c Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Sat, 10 Feb 2024 16:46:15 +0000 Subject: [PATCH 9/9] bugfix (#241) Co-authored-by: Dominik Demuth Reviewed-on: https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/pulls/241 --- src/gui_qt/data/datawidget/properties.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui_qt/data/datawidget/properties.py b/src/gui_qt/data/datawidget/properties.py index 21e3e2d..cb4266a 100644 --- a/src/gui_qt/data/datawidget/properties.py +++ b/src/gui_qt/data/datawidget/properties.py @@ -66,10 +66,10 @@ class PropTable(QtWidgets.QTableWidget): self.blockSignals(True) for k, v in prop.items(): value_item = QtWidgets.QTableWidgetItem('') - value_item.setData(QtCore.Qt.ItemDataRoleDisplayRole, v) + value_item.setData(QtCore.Qt.ItemDataRole.DisplayRole, v) key_item = QtWidgets.QTableWidgetItem(k) - key_item.setFlags(QtCore.Qt.ItemDataRole.NoItemFlags) + key_item.setFlags(QtCore.Qt.ItemFlag.NoItemFlags) key_item.setForeground(QtGui.QBrush(QtGui.QColor(0, 0, 0))) self.setRowCount(self.rowCount()+1)