From 233cdd9f80fb717c4e18869cd6e1a45aa0e81cc1 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 10 May 2023 17:09:11 +0200 Subject: [PATCH 01/37] add gnuplot to requirements for plots of FC reading --- AppImageBuilder.yml | 3 ++- src/nmreval/io/fcbatchreader.py | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 80b4572..fb8b178 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -41,6 +41,7 @@ AppDir: # - zsync # - hicolor-icon-theme - libatlas3-base + - gnuplot-nox - python3.9-minimal - python3-numpy - python3-scipy @@ -70,7 +71,7 @@ AppDir: - usr/share/doc/*/README.* - usr/share/doc/*/changelog.* - usr/share/doc/*/NEWS.* - - usr/share/doc/*/TODO.}* + - usr/share/doc/*/TODO.* runtime: # if needed, apparently replaces hardcoded location with APPDIR location # path_mappings: diff --git a/src/nmreval/io/fcbatchreader.py b/src/nmreval/io/fcbatchreader.py index b3168cf..f221bfe 100644 --- a/src/nmreval/io/fcbatchreader.py +++ b/src/nmreval/io/fcbatchreader.py @@ -1,6 +1,8 @@ from __future__ import annotations import pathlib +import subprocess +import tempfile # import matplotlib.pyplot as plt from scipy.optimize import curve_fit @@ -178,6 +180,7 @@ class FCReader: fit_path.mkdir(parents=True, exist_ok=True) if save_fig: + data_path = fname_no_ext.joinpath('data') image_path = fname_no_ext.joinpath('png') image_path.mkdir(parents=True, exist_ok=True) @@ -209,7 +212,7 @@ class FCReader: except KeyError: self.f_params[k] = [new_entry] - if save_fits or save_fig: + if True: # save_fits or save_fig: xplot = np.geomspace(v.x[0], v.x[-1], num=10*len(v.x)) yplot = FCReader.kww(xplot, *p0) save_name = f'{filename.stem}_{k:011.2f}'.replace('.', 'p') + '.dat' @@ -218,6 +221,26 @@ class FCReader: np.savetxt(fit_path.joinpath(save_name), np.c_[xplot, yplot], header=header+'\t'.join([f'{p}+/-{err}' for p, err in zip(p0, perr)])) + img_file = image_path.joinpath(save_name).with_suffix(".png") + + gnuplot_args = [ + 'gnuplot', + '-e', + 'set terminal png', + f'set output "{img_file}"', + f'set title "f = {k:.4g} Hz\n{p0[2]:.4g}(+/-{perr[2]:.4g}) beta: {p0[3]:.4g}(+/-{perr[3]:.4g})"', + 'set xlabel "t / s"', + 'set logscale x', + 'set format x "10^{{%L}}"', + 'set ylabel "M"', + 'set key off', + f'plot "{data_path.joinpath(save_name)}" with points pointtype 5, "{fit_path.joinpath(save_name)}" with lines', + ] + + print(gnuplot_args) + + subprocess.Popen(gnuplot_args) + # if save_fig: # fig, ax = plt.subplots() # ax.set_xlabel('t / s') From 267554b252fa1d6e1e6c9b499877ef90928101c1 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Thu, 11 May 2023 18:00:29 +0200 Subject: [PATCH 02/37] check for empty line moved to avoid error --- src/nmreval/io/asciireader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py index 04fd19e..e2c0464 100644 --- a/src/nmreval/io/asciireader.py +++ b/src/nmreval/io/asciireader.py @@ -49,10 +49,8 @@ class AsciiReader: with self.fname.open('r') as f: for i, line in enumerate(islice(f, len(self.header)+len(self.lines), num_lines)): line = line.rstrip('\n\t\r, ') - - is_empty = len(line) == 0 - line = re.split(r'[\s,;]', line) + try: comment_start = line.index('#') self.line_comment.append(' '.join(line[comment_start:])) @@ -60,6 +58,8 @@ class AsciiReader: except ValueError: self.line_comment.append('') + is_empty = len(line) == 0 + if not is_empty: self.width.append(len(line)) self.lines.append(line) From e10b85b9043062db310149cb074993fee75fab53 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Thu, 11 May 2023 18:28:03 +0200 Subject: [PATCH 03/37] more correct lines preview in asciireader --- src/gui_qt/io/asciireader.py | 8 ++++++-- src/nmreval/io/asciireader.py | 15 ++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/gui_qt/io/asciireader.py b/src/gui_qt/io/asciireader.py index 4a41bc0..10581a3 100644 --- a/src/gui_qt/io/asciireader.py +++ b/src/gui_qt/io/asciireader.py @@ -98,15 +98,19 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader): @QtCore.pyqtSlot(int, name='on_preview_spinBox_valueChanged') def show_preview(self, line_no: int): - preview, width = self.reader.make_preview(line_no) + preview, width, comments = self.reader.make_preview(line_no) self.ascii_table.setRowCount(min(line_no, len(preview))) - self.ascii_table.setColumnCount(width) + self.ascii_table.setColumnCount(width + 1) for i, line in enumerate(preview): + comment_line = comments[i] for j, field in enumerate(line): it = QtWidgets.QTableWidgetItem(field) self.ascii_table.setItem(i, j, it) + it = QtWidgets.QTableWidgetItem(comment_line) + self.ascii_table.setItem(i, len(line), it) + self.ascii_table.resizeColumnsToContents() @QtCore.pyqtSlot(int, name='on_column_checkBox_stateChanged') diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py index e2c0464..e8a19ea 100644 --- a/src/nmreval/io/asciireader.py +++ b/src/nmreval/io/asciireader.py @@ -19,10 +19,8 @@ class AsciiReader: self.fname = None self.header = [] self.lines = [] - self.num_data = [] self.delays = None self.width = [] - self.num_width = [] self.line_comment = [] self._last_read_pos = 0 @@ -61,13 +59,12 @@ class AsciiReader: is_empty = len(line) == 0 if not is_empty: - self.width.append(len(line)) self.lines.append(line) - if not line[0].startswith('#'): - self.num_data.append(line) - self.num_width.append(len(line)) + self.width.append(len(line)) + else: + self.lines.append('') - return self.lines, max(self.width) + return self.lines, max(self.width), self.line_comment def look_for_delay(self, fname=None): if fname is None: @@ -105,7 +102,7 @@ class AsciiReader: raise ValueError(f'x is {type(x)} not int') if y is None: - y = list(range(1, max(self.num_width))) + y = list(range(1, max(self.width))) cols = x + y + yerr with self.fname.open('rb') as fh: @@ -115,7 +112,7 @@ class AsciiReader: if raw_data.ndim == 1: # only one row or column - if len(self.num_data) == 1: + if len(self.lines) == 1: # one row raw_data = raw_data.reshape(1, -1) else: From edf858da29db371b16798f89c37208201217f4f8 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Thu, 11 May 2023 20:12:32 +0200 Subject: [PATCH 04/37] hacky solution to have correct tuda colors in agr file; closes #63 --- src/gui_qt/io/exporters.py | 6 +++--- src/gui_qt/lib/delegates.py | 8 ++++---- src/nmreval/io/graceeditor.py | 21 ++++++++++++++++++--- src/nmreval/lib/colors.py | 1 + src/resources/Default.agr | 8 ++++---- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/gui_qt/io/exporters.py b/src/gui_qt/io/exporters.py index e089991..75b4a55 100644 --- a/src/gui_qt/io/exporters.py +++ b/src/gui_qt/io/exporters.py @@ -55,9 +55,9 @@ class GraceExporter: break if c_num == -1: - c_num = max(colors.keys()) - colors[c_num + 1] = (f'color{c_num + 1}', sc) - new_colors.append((c_num + 1, f'color{c_num + 1}', sc)) + c_num = max(colors.keys())+1 + colors[c_num] = (f'color{c_num}', sc) + new_colors.append((c_num, f'color{c_num}', sc)) new_s.set_symbol(**{'symbol': item['symbol'].value, 'size': item['symbolsize'] / 10., 'color': c_num, 'fill color': c_num, 'fill pattern': 1}) diff --git a/src/gui_qt/lib/delegates.py b/src/gui_qt/lib/delegates.py index d6f0c9a..2ef865c 100644 --- a/src/gui_qt/lib/delegates.py +++ b/src/gui_qt/lib/delegates.py @@ -30,7 +30,7 @@ class PropertyDelegate(QtWidgets.QStyledItemDelegate): rect = options.rect rect.adjust(5, 0, -5, 0) - mid = (rect.bottom()+rect.top()) / 2 + mid = int((rect.bottom()+rect.top()) / 2) painter.drawLine(rect.left(), mid, rect.right(), mid) painter.restore() @@ -42,7 +42,7 @@ class PropertyDelegate(QtWidgets.QStyledItemDelegate): painter.setPen(pen) pm = make_symbol_pixmap(r) - painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, (options.rect.height()-pm.height())/2), pm) + painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, int((options.rect.height()-pm.height())/2)), pm) style = QtWidgets.QApplication.style() text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None) @@ -171,7 +171,7 @@ class LineStyleEditor(QtWidgets.QComboBox): rect = painter.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, opt, QtWidgets.QStyle.SC_ComboBoxEditField, None) rect.adjust(+10, 0, -10, 0) - mid = (rect.bottom() + rect.top()) / 2 + mid = int((rect.bottom() + rect.top()) / 2) painter.drawLine(rect.left(), mid, rect.right(), mid) painter.end() else: @@ -193,7 +193,7 @@ class LineStyleDelegate(QtWidgets.QStyledItemDelegate): rect = option.rect rect.adjust(+10, 0, -10, 0) - mid = (rect.bottom()+rect.top()) / 2 + mid = int((rect.bottom()+rect.top()) / 2) painter.drawLine(rect.left(), mid, rect.right(), mid) else: QtWidgets.QStyledItemDelegate.paint(self, painter, option, index) diff --git a/src/nmreval/io/graceeditor.py b/src/nmreval/io/graceeditor.py index 58a114e..f657f68 100644 --- a/src/nmreval/io/graceeditor.py +++ b/src/nmreval/io/graceeditor.py @@ -1,9 +1,7 @@ from __future__ import annotations -import codecs import pathlib import re -import warnings from io import StringIO from typing import Optional, Tuple @@ -46,6 +44,22 @@ class GraceEditor: if filename is not None: self.parse(filename) + def _fix_tuda_colors(self): + # 2023-05-11: Default.agr had wrong TUDa colors (4a, 7b, 9b, 9d), so set the values given in colors.py + color_mapping = [ + ('tuda4a', (175, 204, 80)), + ('tuda7b', (245, 163, 0)), + ('tuda9b', (230, 0, 26)), + ('tuda9d', (156, 28, 38)), + ] + + for i, line in enumerate(self.header): + m = self._RE_COLOR.match(line) + if m: + for name, right_color in color_mapping: + if m['disp'].lower() == name: + self.header[i] = f'@map color {m["id"]} to {right_color}, "{m["disp"]}"\n' + def __call__(self, filename: str): self.clear() self.parse(filename) @@ -193,6 +207,8 @@ class GraceEditor: self.graphs[-1].append(line) + self._fix_tuda_colors() + def _make_graph(self, line: str): m = self._RE_GRAPH_START.match(line) g_idx = int(m.group(1)) @@ -212,7 +228,6 @@ class GraceEditor: m = self._RE_COLOR.match(line) if m: _colors[int(m['id'])] = (m['disp'], (int(m['red']), int(m['green']), int(m['blue']))) - return _colors def get_color(self, color_num): diff --git a/src/nmreval/lib/colors.py b/src/nmreval/lib/colors.py index 90765a6..d3aba66 100644 --- a/src/nmreval/lib/colors.py +++ b/src/nmreval/lib/colors.py @@ -247,6 +247,7 @@ class Tab10(BaseColor): TabChartreuse = TUColorsC.TUDa5c.value TabTurquoise = TUColorsA.TUDa2a.value + class Tab20(BaseColor): TabBlue = (31, 119, 180) TabBlue2 = (174, 199, 232) diff --git a/src/resources/Default.agr b/src/resources/Default.agr index 8d08a08..10e7a0c 100644 --- a/src/resources/Default.agr +++ b/src/resources/Default.agr @@ -64,7 +64,7 @@ @map color 2 to (93, 133, 195), "TUDa1a" @map color 3 to (0, 156, 218), "TUDa2a" @map color 4 to (80, 182, 149), "TUDa3a" -@map color 5 to (176, 204, 80), "TUDa4a" +@map color 5 to (175, 204, 80), "TUDa4a" @map color 6 to (221, 223, 72), "TUDa5a" @map color 7 to (255, 224, 92), "TUDa6a" @map color 8 to (248, 186, 60), "TUDa7a" @@ -78,9 +78,9 @@ @map color 16 to (153, 192, 0), "TUDa4b" @map color 17 to (201, 212, 0), "TUDa5b" @map color 18 to (253, 202, 0), "TUDa6b" -@map color 19 to (248, 163, 0), "TUDa7b" +@map color 19 to (245, 163, 0), "TUDa7b" @map color 20 to (236, 101, 0), "TUDa8b" -@map color 21 to (239, 0, 26), "TUDa9b" +@map color 21 to (230, 0, 26), "TUDa9b" @map color 22 to (166, 0, 132), "TUDa10b" @map color 23 to (114, 16, 133), "TUDa11b" @map color 24 to (0, 78, 138), "TUDa1c" @@ -102,7 +102,7 @@ @map color 40 to (174, 142, 0), "TUDa6d" @map color 41 to (190, 111, 0), "TUDa7d" @map color 42 to (169, 73, 19), "TUDa8d" -@map color 43 to (188, 28, 38), "TUDa9d" +@map color 43 to (156, 28, 38), "TUDa9d" @map color 44 to (115, 32, 84), "TUDa10d" @map color 45 to (76, 34, 106), "TUDa11d" @map color 46 to (220, 220, 220), "TUDa0a" From 45d319834bfe9e2d2bcd09e8eef27127864d4b49 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 12 May 2023 20:13:56 +0200 Subject: [PATCH 05/37] fitresult window is reused and remembers fitplot range/log; closes #65 add color to sub-functions in fit result window Fitresult window shows one set at a time, more space for plot; closes #66 --- src/gui_qt/_py/fitresult.py | 109 ++++++++++-------- src/gui_qt/fit/result.py | 202 +++++++++++++++++---------------- src/gui_qt/lib/utils.py | 2 +- src/gui_qt/main/mainwindow.py | 18 +-- src/nmreval/fit/result.py | 1 + src/resources/_ui/fitresult.ui | 186 ++++++++++++++++-------------- 6 files changed, 281 insertions(+), 237 deletions(-) diff --git a/src/gui_qt/_py/fitresult.py b/src/gui_qt/_py/fitresult.py index ae2d077..ebb298e 100644 --- a/src/gui_qt/_py/fitresult.py +++ b/src/gui_qt/_py/fitresult.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'src/resources/_ui/fitresult.ui' # -# Created by: PyQt5 UI code generator 5.15.2 +# Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -14,37 +14,28 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(864, 649) + Dialog.resize(864, 712) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") - 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.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setSpacing(3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog) + self.reject_fit_checkBox.setObjectName("reject_fit_checkBox") + self.horizontalLayout_2.addWidget(self.reject_fit_checkBox) + self.del_prev_checkBox = QtWidgets.QCheckBox(Dialog) + self.del_prev_checkBox.setObjectName("del_prev_checkBox") + self.horizontalLayout_2.addWidget(self.del_prev_checkBox) + self.gridLayout.addLayout(self.horizontalLayout_2, 2, 0, 1, 2) + self.line = QtWidgets.QFrame(Dialog) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 3, 0, 1, 2) 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, 6, 0, 1, 2) - self.param_tableWidget = QtWidgets.QTableWidget(Dialog) - self.param_tableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - self.param_tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored) - self.param_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - self.param_tableWidget.setAlternatingRowColors(True) - self.param_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) - self.param_tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectColumns) - self.param_tableWidget.setShowGrid(False) - self.param_tableWidget.setColumnCount(0) - self.param_tableWidget.setObjectName("param_tableWidget") - self.param_tableWidget.setRowCount(0) - self.param_tableWidget.horizontalHeader().setStretchLastSection(False) - self.gridLayout.addWidget(self.param_tableWidget, 1, 0, 1, 1) self.groupBox = QtWidgets.QGroupBox(Dialog) self.groupBox.setObjectName("groupBox") self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) @@ -113,21 +104,34 @@ 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, 5, 0, 1, 2) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setSpacing(3) - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog) - self.reject_fit_checkBox.setObjectName("reject_fit_checkBox") - self.horizontalLayout_2.addWidget(self.reject_fit_checkBox) - self.del_prev_checkBox = QtWidgets.QCheckBox(Dialog) - self.del_prev_checkBox.setObjectName("del_prev_checkBox") - self.horizontalLayout_2.addWidget(self.del_prev_checkBox) - self.gridLayout.addLayout(self.horizontalLayout_2, 2, 0, 1, 1) - self.line = QtWidgets.QFrame(Dialog) - self.line.setFrameShape(QtWidgets.QFrame.HLine) - self.line.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line.setObjectName("line") - self.gridLayout.addWidget(self.line, 3, 0, 1, 2) + self.param_tableWidget = QtWidgets.QTableWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.param_tableWidget.sizePolicy().hasHeightForWidth()) + self.param_tableWidget.setSizePolicy(sizePolicy) + self.param_tableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.param_tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored) + self.param_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.param_tableWidget.setAlternatingRowColors(True) + self.param_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + self.param_tableWidget.setColumnCount(1) + self.param_tableWidget.setObjectName("param_tableWidget") + self.param_tableWidget.setRowCount(0) + self.param_tableWidget.horizontalHeader().setVisible(False) + self.param_tableWidget.horizontalHeader().setStretchLastSection(True) + self.gridLayout.addWidget(self.param_tableWidget, 1, 0, 1, 1) + 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.stack = QtWidgets.QTabWidget(Dialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) @@ -149,10 +153,18 @@ class Ui_Dialog(object): self.logx_box.setLayoutDirection(QtCore.Qt.RightToLeft) self.logx_box.setObjectName("logx_box") self.gridLayout_3.addWidget(self.logx_box, 2, 0, 1, 1) - self.graphicsView = GraphicsLayoutWidget(self.stackPage1) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 2) + self.fit_plot = PlotWidget(self.stackPage1) + self.fit_plot.setObjectName("fit_plot") + self.gridLayout_3.addWidget(self.fit_plot, 0, 0, 1, 2) self.stack.addTab(self.stackPage1, "") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.verticalLayout = QtWidgets.QVBoxLayout(self.tab) + self.verticalLayout.setObjectName("verticalLayout") + self.resid_plot = PlotWidget(self.tab) + self.resid_plot.setObjectName("resid_plot") + self.verticalLayout.addWidget(self.resid_plot) + self.stack.addTab(self.tab, "") self.stackPage2 = QtWidgets.QWidget() self.stackPage2.setObjectName("stackPage2") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.stackPage2) @@ -194,7 +206,7 @@ class Ui_Dialog(object): self.corr_tableWidget.verticalHeader().setVisible(False) self.verticalLayout_3.addWidget(self.corr_tableWidget) self.stack.addTab(self.stackPage3, "") - self.gridLayout.addWidget(self.stack, 0, 1, 3, 1) + self.gridLayout.addWidget(self.stack, 0, 1, 2, 1) self.retranslateUi(Dialog) self.stack.setCurrentIndex(0) @@ -203,6 +215,8 @@ class Ui_Dialog(object): def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Fit results")) + self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit")) + self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits")) self.groupBox.setTitle(_translate("Dialog", "Output")) self.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function")) self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves")) @@ -215,11 +229,10 @@ 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")) - self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits")) self.logy_box.setText(_translate("Dialog", "logarithmic y axis")) self.logx_box.setText(_translate("Dialog", "logarithmic x axis")) self.stack.setTabText(self.stack.indexOf(self.stackPage1), _translate("Dialog", "Plot")) + self.stack.setTabText(self.stack.indexOf(self.tab), _translate("Dialog", "Residuals")) self.stack.setTabText(self.stack.indexOf(self.stackPage2), _translate("Dialog", "Statistics")) item = self.corr_tableWidget.horizontalHeaderItem(0) item.setText(_translate("Dialog", "Parameter 1")) @@ -231,4 +244,4 @@ class Ui_Dialog(object): item.setText(_translate("Dialog", "Partial Corr.")) self.stack.setTabText(self.stack.indexOf(self.stackPage3), _translate("Dialog", "Correlations")) from ..lib.forms import ElideComboBox -from pyqtgraph import GraphicsLayoutWidget +from pyqtgraph import PlotWidget diff --git a/src/gui_qt/fit/result.py b/src/gui_qt/fit/result.py index 92a4f74..f8c79b9 100644 --- a/src/gui_qt/fit/result.py +++ b/src/gui_qt/fit/result.py @@ -27,70 +27,76 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): 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._prevs = {} - self._models = {} + self._previous_fits = {} + self._opts = [] + self._results = {} + self.graph_opts = {} + self.last_idx = None - for res in results: - idx = res.idx - data_k = management.data[idx] - - if res.name not in self._models: - self._models[res.name] = [] - - self._models[res.name].append(idx) - - self._prevs[idx] = [] - for fit in data_k.get_fits(): - self._prevs[idx].append((fit.name, fit.statistics, fit.nobs-fit.nvar)) - - self._results = {res.idx: res for res in results} - self._opts = [(False, False) for _ in range(len(self._results))] - - self.residplot = self.graphicsView.addPlot(row=0, col=0) self.resid_graph = PlotItem(x=[], y=[], symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)), pen=None) self.resid_graph_imag = PlotItem(x=[], y=[], symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)), pen=None) - self.residplot.addItem(self.resid_graph) - self.residplot.addItem(self.resid_graph_imag) - self.residplot.setLabel('left', 'Residual') + self.resid_plot.addItem(self.resid_graph) + self.resid_plot.addItem(self.resid_graph_imag) + self.resid_plot.setLabel('left', 'Residual') - self.fitplot = self.graphicsView.addPlot(row=1, col=0) self.data_graph = PlotItem(x=[], y=[], symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)), pen=None) self.data_graph_imag = PlotItem(x=[], y=[], symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)), pen=None) - self.fitplot.addItem(self.data_graph) - self.fitplot.addItem(self.data_graph_imag) - self.fitplot.setLabel('left', 'Function') + self.fit_plot.addItem(self.data_graph) + self.fit_plot.addItem(self.data_graph_imag) + self.fit_plot.setLabel('left', 'Function') self.fit_graph = PlotItem(x=[], y=[]) self.fit_graph_imag = PlotItem(x=[], y=[]) - self.fitplot.addItem(self.fit_graph) - self.fitplot.addItem(self.fit_graph_imag) + self.fit_plot.addItem(self.fit_graph) + self.fit_plot.addItem(self.fit_graph_imag) self.cmap = RdBuCMap(vmin=-1, vmax=1) - self.sets_comboBox.blockSignals(True) - for n in self._models.keys(): - self.sets_comboBox.addItem(n) - self.sets_comboBox.blockSignals(False) - - self.set_parameter(0) self.buttonBox.accepted.connect(self.accept) - self.param_tableWidget.itemClicked.connect(self.show_results) - self.param_tableWidget.horizontalHeader().sectionClicked.connect(lambda i: self.show_results(None, idx=i)) - self.graph_checkBox.stateChanged.connect(lambda x: self.graph_comboBox.setEnabled(x == QtCore.Qt.Unchecked)) - self.logy_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(y=bool(x))) - self.logx_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(x=bool(x))) - self.residplot.setXLink(self.fitplot) + 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.set_results(results) + + def __call__(self, results: list): + self._previous_fits = {} + self.sets_comboBox.blockSignals(True) + self.sets_comboBox.clear() + self.sets_comboBox.blockSignals(False) + self._results = {} + self._opts = {} + + self.set_results(results) + + def set_results(self, results: list): + self.sets_comboBox.blockSignals(True) + for res in results: + idx = res.idx + data_k = self._management.data[idx] + + self._previous_fits[idx] = [] + for fit in data_k.get_fits(): + self._previous_fits[idx].append((fit.name, fit.statistics, fit.nobs - fit.nvar)) + + self.sets_comboBox.addItem(data_k.name, userData=idx) + self.sets_comboBox.blockSignals(False) + + self._results = {res.idx: res for res in results} + self._opts = [(False, False) for _ in range(len(self._results))] + + self.set_parameter(0) def add_graphs(self, graphs: list): self.graph_comboBox.clear() @@ -99,56 +105,40 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): @QtCore.pyqtSlot(int, name='on_sets_comboBox_currentIndexChanged') def set_parameter(self, idx: int): - model_name = self.sets_comboBox.itemText(idx) - sets = self._models[model_name] - self.param_tableWidget.setColumnCount(len(sets)) + set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole) - r = self._results[sets[0]] - self.param_tableWidget.setRowCount(len(r.parameter)) + res = self._results[set_id] + self.param_tableWidget.setRowCount(len(res.parameter)) + for j, pvalue in enumerate(res.parameter.values()): + name = pvalue.name + p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'str', brackets=True)) + self.param_tableWidget.setVerticalHeaderItem(j, p_header) - for i, pval in enumerate(r.parameter.values()): - name = pval.full_name - p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'html', brackets=False)) - self.param_tableWidget.setVerticalHeaderItem(i, p_header) + item_text = f'{pvalue.value:.4g}' + if pvalue.error is not None: + item_text += f' \u00b1 {pvalue.error:.4g}' + self.param_tableWidget.setItem(2*j+1, 0, QtWidgets.QTableWidgetItem('-')) + else: + self.param_tableWidget.setItem(2*j+1, 0, QtWidgets.QTableWidgetItem()) + item = QtWidgets.QTableWidgetItem(item_text) + self.param_tableWidget.setItem(j, 0, item) - for i, set_id in enumerate(sets): - data_i = self._management[set_id] - header_item = QtWidgets.QTableWidgetItem(data_i.name) - header_item.setData(QtCore.Qt.UserRole, set_id) - self.param_tableWidget.setHorizontalHeaderItem(i, header_item) - - res = self._results[set_id] - for j, pvalue in enumerate(res.parameter.values()): - item_text = f'{pvalue.value:.4g}' - if pvalue.error is not None: - item_text += f' \u00b1 {pvalue.error:.4g}' - self.param_tableWidget.setItem(2*j+1, i, QtWidgets.QTableWidgetItem('-')) - else: - self.param_tableWidget.setItem(2*j+1, i, QtWidgets.QTableWidgetItem()) - item = QtWidgets.QTableWidgetItem(item_text) - self.param_tableWidget.setItem(j, i, item) - - self.param_tableWidget.resizeColumnsToContents() - self.param_tableWidget.selectColumn(0) - self.show_results(None, idx=0) + self.param_tableWidget.resizeColumnToContents(0) + self.show_results(idx) @QtCore.pyqtSlot(int, name='on_reject_fit_checkBox_stateChanged') @QtCore.pyqtSlot(int, name='on_del_prev_checkBox_stateChanged') def change_opts(self, _): - idx = self.param_tableWidget.currentIndex().column() + idx = self.sets_comboBox.currentIndex() self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.Checked, self.del_prev_checkBox.checkState() == QtCore.Qt.Checked) - def show_results(self, item, idx=None): - if item is not None: - idx = self.param_tableWidget.indexFromItem(item).column() - - set_id = self.param_tableWidget.horizontalHeaderItem(idx).data(QtCore.Qt.UserRole) + def show_results(self, idx): + set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole) self.set_plot(set_id) self.set_correlation(set_id) self.set_statistics(set_id) - self.reject_fit_checkBox.blockSignals(True) self.reject_fit_checkBox.setChecked(self._opts[idx][0]) self.reject_fit_checkBox.blockSignals(False) @@ -157,13 +147,21 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.del_prev_checkBox.blockSignals(False) def set_plot(self, idx: str): + if self.last_idx is not None: + self.graph_opts[self.last_idx] = ( + self.fit_plot.getPlotItem().viewRange(), + self.logx_box.isChecked(), + self.logy_box.isChecked(), + ) + + self.last_idx = idx res = self._results[idx] iscomplex = res.iscomplex sub_funcs = res.sub(res.x) - for item in self.fitplot.curves[::-1]: + for item in self.fit_plot.plotItem.items[::-1]: if item not in [self.data_graph, self.data_graph_imag, self.fit_graph, self.fit_graph_imag]: - self.fitplot.removeItem(item) + self.fit_plot.removeItem(item) if iscomplex: self.data_graph.setData(x=res.x_data, y=res.y_data.real) @@ -173,11 +171,11 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): 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 f in sub_funcs: - item = PlotItem(x=f.x, y=f.y.real) - self.fitplot.addItem(item) - item = PlotItem(x=f.x, y=f.y.imag) - self.fitplot.addItem(item) + for i, f in enumerate(sub_funcs): + item = PlotItem(x=f.x, y=f.y.real, pen=mkPen({'color': i, 'style': 2})) + self.fit_plot.addItem(item) + item = PlotItem(x=f.x, y=f.y.imag, pen=mkPen({'color': i, 'style': 2})) + self.fit_plot.addItem(item) else: self.resid_graph.setData(x=res.x_data, y=res.residual) @@ -187,12 +185,27 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.fit_graph.setData(x=res.x, y=res.y) self.fit_graph_imag.setData(x=[], y=[]) - for f in sub_funcs: - item = PlotItem(x=f.x, y=f.y, pen=mkPen({'style': 2})) - self.fitplot.addItem(item) + for i, f in enumerate(sub_funcs): + item = PlotItem(x=f.x, y=f.y, pen=mkPen({'color': i, 'style': 2})) + self.fit_plot.addItem(item) - self.fitplot.setLogMode(x=res.islog) - self.residplot.setLogMode(x=res.islog) + self.logx_box.blockSignals(True) + self.logx_box.setChecked(res.islog) + self.logx_box.blockSignals(False) + + self.fit_plot.setLogMode(x=res.islog) + self.resid_plot.setLogMode(x=res.islog) + + if idx in self.graph_opts: + view_range, logx, logy = self.graph_opts[idx] + self.fit_plot.setRange(xRange=view_range[0], yRange=view_range[1], padding=0) + self.fit_plot.setLogMode(x=logx, y=logy) + self.logx_box.blockSignals(True) + self.logx_box.setChecked(logx) + self.logx_box.blockSignals(False) + self.logy_box.blockSignals(True) + self.logy_box.setChecked(logy) + self.logy_box.blockSignals(False) def set_correlation(self, idx: str): while self.corr_tableWidget.rowCount(): @@ -223,7 +236,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): res = self._results[idx] - self.stats_tableWidget.setColumnCount(1 + len(self._prevs[idx])) + self.stats_tableWidget.setColumnCount(1 + len(self._previous_fits[idx])) self.stats_tableWidget.setRowCount(len(res.statistics)+3) it = QtWidgets.QTableWidgetItem(f'{res.dof}') @@ -231,7 +244,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.stats_tableWidget.setVerticalHeaderItem(0, QtWidgets.QTableWidgetItem('DoF')) self.stats_tableWidget.setItem(0, 0, it) - for col, (name, _, dof) in enumerate(self._prevs[idx], start=1): + 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) @@ -245,7 +258,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): best_idx = -1 best_val = v - for col, (_, stats, _) in enumerate(self._prevs[idx], start=1): + for col, (_, stats, _) in enumerate(self._previous_fits[idx], start=1): if k in ['adj. R^2', 'R^2']: best_idx = col if best_val < stats[k] else max(0, best_idx) else: @@ -265,7 +278,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.stats_tableWidget.setVerticalHeaderItem(row+1, QtWidgets.QTableWidgetItem('Pr(>F)')) self.stats_tableWidget.setItem(row+1, 0, QtWidgets.QTableWidgetItem('-')) - for col, (_, stats, dof) in enumerate(self._prevs[idx], start=1): + 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) @@ -312,7 +325,6 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): except TypeError: pass - self.closed.emit(self._results, self._opts, graph, plot_fits, parts, extrapolate) self.accept() @@ -348,7 +360,7 @@ class FitExtension(QtWidgets.QDialog): self.buttonBox = QtWidgets.QDialogButtonBox() self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok) gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2) self.setLayout(gridLayout) @@ -365,4 +377,4 @@ class FitExtension(QtWidgets.QDialog): except TypeError: return None - return xmin, xmax, nums \ No newline at end of file + return xmin, xmax, nums diff --git a/src/gui_qt/lib/utils.py b/src/gui_qt/lib/utils.py index db0de99..2849e3f 100644 --- a/src/gui_qt/lib/utils.py +++ b/src/gui_qt/lib/utils.py @@ -58,7 +58,7 @@ class RdBuCMap: elif val < self.min: col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[-1]) else: - col = QtGui.QColor.fromRgbF(*(float(self.spline[i](val)) for i in range(3))) + col = QtGui.QColor.fromRgb(*(int(self.spline[i](val)) for i in range(3))) return col diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index e228552..0bb4938 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -55,7 +55,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.fitlimitvalues = [None, None] self.fitpreview = [] self._fit_plot_id = None - self.savefitdialog = None + self.fitresult_dialog = None self.eval = None self.editor = None @@ -77,7 +77,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.__timer = QtCore.QTimer() self.__backup_path = config_paths() / f'{datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")}.nmr' - self.__timer.start(3*60*1000) # every three minutese + self.__timer.start(3*60*1000) # every three minutes self.__timer.timeout.connect(self._autosave) self.fit_timer = QtCore.QTimer() @@ -920,11 +920,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.fit_timer.stop() self.status.setText('') if results: - res_dialog = QFitResult(results, self.management, parent=self) - res_dialog.add_graphs(self.management.graphs.list()) - res_dialog.closed.connect(self.accepts_fit) - res_dialog.redoFit.connect(self.management.redo_fits) - res_dialog.show() + if self.fitresult_dialog is None: + self.fitresult_dialog = QFitResult(results, self.management, parent=self) + self.fitresult_dialog.add_graphs(self.management.graphs.list()) + self.fitresult_dialog.closed.connect(self.accepts_fit) + self.fitresult_dialog.redoFit.connect(self.management.redo_fits) + else: + self.fitresult_dialog(results) + self.fitresult_dialog.add_graphs(self.management.graphs.list()) + self.fitresult_dialog.show() @QtCore.pyqtSlot(dict, list, str, bool, bool, list) def accepts_fit(self, res: dict, opts: list, param_graph: str, diff --git a/src/nmreval/fit/result.py b/src/nmreval/fit/result.py index 74c1c47..95037f0 100644 --- a/src/nmreval/fit/result.py +++ b/src/nmreval/fit/result.py @@ -230,6 +230,7 @@ class FitResult(Points): ret_val = '' for pval in self.parameter.values(): + print(pval) ret_val += convert(str(pval), old='tex', new='str') + '\n' if self.fun_kwargs: diff --git a/src/resources/_ui/fitresult.ui b/src/resources/_ui/fitresult.ui index c6dd2c0..cd2b130 100644 --- a/src/resources/_ui/fitresult.ui +++ b/src/resources/_ui/fitresult.ui @@ -7,35 +7,38 @@ 0 0 864 - 649 + 712 Fit results - - - - - 0 - 0 - + + + + 3 - - - 400 - 16777215 - - - - - 200 - 0 - - - - QComboBox::AdjustToMinimumContentsLength + + + + Reject this fit + + + + + + + Delete previous fits + + + + + + + + + Qt::Horizontal @@ -46,37 +49,6 @@ - - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectColumns - - - false - - - 0 - - - false - - - @@ -223,35 +195,67 @@ - - - - 3 + + + + + 0 + 0 + - - - - Reject this fit - - - - - - - Delete previous fits - - - - + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::NoSelection + + + 1 + + + false + + + true + + + - - - - Qt::Horizontal + + + + + 0 + 0 + + + + + 400 + 16777215 + + + + + 200 + 0 + + + + QComboBox::AdjustToMinimumContentsLength - + @@ -262,7 +266,7 @@ 0 - + Plot @@ -303,11 +307,21 @@ - + - + + + Residuals + + + + + + + + Statistics @@ -349,7 +363,7 @@ - + Correlations @@ -415,16 +429,16 @@ - - GraphicsLayoutWidget - QGraphicsView -
pyqtgraph
-
ElideComboBox QComboBox
..lib.forms
+ + PlotWidget + QGraphicsView +
pyqtgraph
+
From 753cd06dd1c391b6f83fe30fc21788a6c3d07002 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Sun, 14 May 2023 18:16:23 +0200 Subject: [PATCH 06/37] pick position remembers selected destination graph; closes #62 --- src/gui_qt/data/point_select.py | 8 +++++++- src/gui_qt/main/mainwindow.py | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/gui_qt/data/point_select.py b/src/gui_qt/data/point_select.py index f821ccd..9abfc4f 100644 --- a/src/gui_qt/data/point_select.py +++ b/src/gui_qt/data/point_select.py @@ -187,9 +187,15 @@ class PointSelectWidget(QtWidgets.QWidget, Ui_Form): self.peaktable.blockSignals(False) def set_graphs(self, graphs: list): + last_graph = self.graph_combobox.currentData() self.graph_combobox.clear() - for g in graphs: + idx = 0 + for i, g in enumerate(graphs): self.graph_combobox.addItem(g[1], userData=g[0]) + if g[0] == last_graph: + idx = i + + self.graph_combobox.setCurrentIndex(idx) @QtCore.pyqtSlot(int, name='on_graph_checkbox_stateChanged') def changed_state(self, checked): diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index 0bb4938..8f0ce3f 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -239,6 +239,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.actionConcatenate_sets.triggered.connect(lambda : self.management.cat()) + self.management.graphs.valueChanged.connect(self.update_graph_list) + @QtCore.pyqtSlot(name='on_action_open_triggered') def open(self): filedialog = OpenFileDialog(directory=self.path, caption='Open files', @@ -412,6 +414,12 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): if self.area.subWindowList(): self.area.activateNextSubWindow() + @QtCore.pyqtSlot() + def update_graph_list(self): + graph_list = self.management.graphs.list() + self.t1tauwidget.set_graphs(graph_list) + self.ptsselectwidget.set_graphs(graph_list) + @QtCore.pyqtSlot(str) def set_graph(self, key: str): w = self.management.graphs[key] From b127cc15e2a2ff28637398cd26080cd7f5c32a73 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 16 May 2023 19:22:50 +0200 Subject: [PATCH 07/37] dsc: only use low temperature to baseline correct calibration without enthalpy --- src/nmreval/io/dsc.py | 64 ++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/nmreval/io/dsc.py b/src/nmreval/io/dsc.py index 532df8f..19ee743 100644 --- a/src/nmreval/io/dsc.py +++ b/src/nmreval/io/dsc.py @@ -5,6 +5,8 @@ import re from collections import namedtuple import numpy as np +from matplotlib import pyplot as plt +from scipy.stats import linregress try: from scipy.integrate import simpson @@ -200,35 +202,24 @@ class DSCCalibrator: high_border = np.argmin(np.abs(measurement[0] - t_high_lim)) ref_zoom = measurement[:, low_border:high_border] - x_val = np.array([[ref_zoom[0, 0], 1], [ref_zoom[0, -1], 1]]) - y_val = np.array([ref_zoom[1, 0], ref_zoom[1, -1]]) - - sol = np.linalg.solve(x_val, y_val) - ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1]) - - ref_grad = np.gradient(ref_zoom[1]) - crossing = np.where(np.diff(np.sign(np.abs(ref_grad)-0.001)))[0] - - almost_flat = np.sort(crossing-np.argmax(ref_zoom[1])) - integ_limit = (almost_flat[np.where((almost_flat < -20))[0][-1]]+np.argmax(ref_zoom[1]), - almost_flat[np.where((almost_flat > 20))[0][0]]+np.argmax(ref_zoom[1])) - - # subtract baseline around reference peak - sol = self.solve_linear_eq(integ_limit, ref_zoom) - ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1]) - - # calculate onset slope (use points at position of maximum gradient - 100/rate (+50/rate)) - ref_grad = np.gradient(ref_zoom[1]) - max_grad = np.argmax(ref_grad) - - grad_pos = max_grad-max(1, int(160 / rate)), max_grad - - sol = self.solve_linear_eq(grad_pos, ref_zoom) - onset = sol[0] * ref_zoom[0] + sol[1] - - melts.append(-sol[1] / sol[0]) - if enthalpy is not None: + x_val = np.array([[ref_zoom[0, 0], 1], [ref_zoom[0, -1], 1]]) + y_val = np.array([ref_zoom[1, 0], ref_zoom[1, -1]]) + + sol = np.linalg.solve(x_val, y_val) + ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1]) + + ref_grad = np.gradient(ref_zoom[1]) + crossing = np.where(np.diff(np.sign(np.abs(ref_grad)-0.001)))[0] + + almost_flat = np.sort(crossing-np.argmax(ref_zoom[1])) + integ_limit = (almost_flat[np.where((almost_flat < -40))[0][-1]]+np.argmax(ref_zoom[1]), + almost_flat[np.where((almost_flat > 40))[0][0]]+np.argmax(ref_zoom[1])) + + # subtract baseline around reference peak + sol = self.solve_linear_eq(integ_limit, ref_zoom) + ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1]) + # integrate over peak to calibrate y axis # delta H in J/g: Integrate Peak over time and divide by weight area = simpson(ref_zoom[1, integ_limit[0]:integ_limit[1]], @@ -236,6 +227,23 @@ class DSCCalibrator: even='avg') * 1e-3 calib_y.append(enthalpy / (area / data.weight)) + else: + ref_grad = np.gradient(ref_zoom[1]) + res = linregress(ref_zoom[0, :len(ref_grad)//5], ref_zoom[1, :len(ref_grad)//5]) + + ref_zoom[1] -= (res.slope*ref_zoom[0] + res.intercept) + + # calculate onset slope (use points at position of maximum gradient - 100/rate (+50/rate)) + ref_grad = np.gradient(ref_zoom[1]) + max_grad = np.argmax(ref_grad) + + grad_pos = max_grad - max(1, int(160 / rate)), max_grad + + sol = self.solve_linear_eq(grad_pos, ref_zoom) + onset = sol[0] * ref_zoom[0] + sol[1] + + melts.append(-sol[1] / sol[0]) + results.append([ref_zoom, onset, ref_zoom[:, grad_pos]]) if len(melts) > 1: From 12dacb73b3195d79bf4db150de9c0ac758644434 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 16 May 2023 20:05:55 +0200 Subject: [PATCH 08/37] add QMessageBox to display existing backup files --- src/gui_qt/main/mainwindow.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index 8f0ce3f..4a6f92a 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -75,6 +75,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): if Updater.get_update_information(os.getenv('APPIMAGE'))[0]: self.look_for_update() + self.check_for_backup() + self.__timer = QtCore.QTimer() self.__backup_path = config_paths() / f'{datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")}.nmr' self.__timer.start(3*60*1000) # every three minutes @@ -1082,6 +1084,33 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): NMRWriter(self.management.graphs, self.management.data).export(self.__backup_path) self.status.setText('') + def check_for_backup(self): + backups = [] + for filename in config_paths().glob('*.nmr'): + try: + backups.append((filename, datetime.datetime.strptime(filename.stem, "%Y-%m-%d_%H%M%S"))) + except ValueError: + continue + + if backups: + backup_by_date = sorted(backups, key=lambda x: x[1]) + msg = QtWidgets.QMessageBox.information( + self, 'Backup found', + f'{len(backups)} backup files in directory {backup_by_date[-1][0].parent} found.\n\n' + f'Latest backup date: {backup_by_date[-1][1]}\n\n' + f'Press Ok to load, Cancel to delete backup, Close to do nothing.', + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Close + ) + + if msg == QtWidgets.QMessageBox.Ok: + self.management.load_files([str(backup_by_date[-1][0])]) + backup_by_date[-1][0].unlink() + elif msg == QtWidgets.QMessageBox.Cancel: + backup_by_date[-1][0].unlink() + else: + pass + + @QtCore.pyqtSlot(name='on_actionCreate_starter_triggered') def create_starter(self): make_starter(os.getenv('APPIMAGE')) From 255d7c78625558b06c1b581b2c4a79b36249711a Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 17 May 2023 15:39:36 +0000 Subject: [PATCH 09/37] Update 'src/nmreval/io/dsc.py' remove matplotlib import --- src/nmreval/io/dsc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nmreval/io/dsc.py b/src/nmreval/io/dsc.py index 19ee743..e64c60f 100644 --- a/src/nmreval/io/dsc.py +++ b/src/nmreval/io/dsc.py @@ -5,7 +5,6 @@ import re from collections import namedtuple import numpy as np -from matplotlib import pyplot as plt from scipy.stats import linregress try: From 49101565a3b8c1a37875727696316efcaefd06d1 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 17 May 2023 19:41:44 +0200 Subject: [PATCH 10/37] fixed problem with setting color from context menu --- src/gui_qt/data/container.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gui_qt/data/container.py b/src/gui_qt/data/container.py index b4c2d69..24cf09d 100644 --- a/src/gui_qt/data/container.py +++ b/src/gui_qt/data/container.py @@ -312,10 +312,8 @@ class ExperimentContainer(QtCore.QObject): err_pen.setColor(QtGui.QColor(*self.plot_real.symbolcolor.rgb())) self.plot_error.setData(pen=err_pen) - elif mode == 'imag' and self.plot_imag is not None: + if mode in ['imag', 'all'] and self.plot_imag is not None: self.plot_imag.set_color(color, symbol=symbol, line=line) - else: - print('Updating color failed for ' + str(self.id)) def setSymbol(self, symbol=None, color=None, size=None, mode='real'): if mode in ['real', 'all']: From 67d60949b516fda92287d4281ea6cd312042cb09 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Thu, 18 May 2023 20:43:45 +0200 Subject: [PATCH 11/37] plot fit and residual together with bigger fit --- src/gui_qt/_py/fitresult.py | 19 +++++-------------- src/gui_qt/fit/result.py | 10 +++++++--- src/resources/_ui/fitresult.ui | 18 ++++-------------- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/gui_qt/_py/fitresult.py b/src/gui_qt/_py/fitresult.py index ebb298e..5a9a4e4 100644 --- a/src/gui_qt/_py/fitresult.py +++ b/src/gui_qt/_py/fitresult.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(864, 712) + Dialog.resize(969, 974) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") self.horizontalLayout_2 = QtWidgets.QHBoxLayout() @@ -153,18 +153,10 @@ class Ui_Dialog(object): self.logx_box.setLayoutDirection(QtCore.Qt.RightToLeft) self.logx_box.setObjectName("logx_box") self.gridLayout_3.addWidget(self.logx_box, 2, 0, 1, 1) - self.fit_plot = PlotWidget(self.stackPage1) - self.fit_plot.setObjectName("fit_plot") - self.gridLayout_3.addWidget(self.fit_plot, 0, 0, 1, 2) + self.graphicsView = GraphicsLayoutWidget(self.stackPage1) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 2) self.stack.addTab(self.stackPage1, "") - self.tab = QtWidgets.QWidget() - self.tab.setObjectName("tab") - self.verticalLayout = QtWidgets.QVBoxLayout(self.tab) - self.verticalLayout.setObjectName("verticalLayout") - self.resid_plot = PlotWidget(self.tab) - self.resid_plot.setObjectName("resid_plot") - self.verticalLayout.addWidget(self.resid_plot) - self.stack.addTab(self.tab, "") self.stackPage2 = QtWidgets.QWidget() self.stackPage2.setObjectName("stackPage2") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.stackPage2) @@ -232,7 +224,6 @@ class Ui_Dialog(object): self.logy_box.setText(_translate("Dialog", "logarithmic y axis")) self.logx_box.setText(_translate("Dialog", "logarithmic x axis")) self.stack.setTabText(self.stack.indexOf(self.stackPage1), _translate("Dialog", "Plot")) - self.stack.setTabText(self.stack.indexOf(self.tab), _translate("Dialog", "Residuals")) self.stack.setTabText(self.stack.indexOf(self.stackPage2), _translate("Dialog", "Statistics")) item = self.corr_tableWidget.horizontalHeaderItem(0) item.setText(_translate("Dialog", "Parameter 1")) @@ -244,4 +235,4 @@ class Ui_Dialog(object): item.setText(_translate("Dialog", "Partial Corr.")) self.stack.setTabText(self.stack.indexOf(self.stackPage3), _translate("Dialog", "Correlations")) from ..lib.forms import ElideComboBox -from pyqtgraph import PlotWidget +from pyqtgraph import GraphicsLayoutWidget diff --git a/src/gui_qt/fit/result.py b/src/gui_qt/fit/result.py index f8c79b9..7bb927b 100644 --- a/src/gui_qt/fit/result.py +++ b/src/gui_qt/fit/result.py @@ -33,6 +33,12 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): self.graph_opts = {} self.last_idx = None + self.resid_plot = self.graphicsView.addPlot(row=0, col=0, title='Residual') + self.fit_plot = self.graphicsView.addPlot(row=1, col=0, title='Fit') + + self.graphicsView.ci.layout.setRowStretchFactor(0, 1) + self.graphicsView.ci.layout.setRowStretchFactor(1, 2) + self.resid_graph = PlotItem(x=[], y=[], symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)), pen=None) @@ -41,7 +47,6 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): pen=None) self.resid_plot.addItem(self.resid_graph) self.resid_plot.addItem(self.resid_graph_imag) - self.resid_plot.setLabel('left', 'Residual') self.data_graph = PlotItem(x=[], y=[], symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)), @@ -51,7 +56,6 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): pen=None) self.fit_plot.addItem(self.data_graph) self.fit_plot.addItem(self.data_graph_imag) - self.fit_plot.setLabel('left', 'Function') self.fit_graph = PlotItem(x=[], y=[]) self.fit_graph_imag = PlotItem(x=[], y=[]) @@ -159,7 +163,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): iscomplex = res.iscomplex sub_funcs = res.sub(res.x) - for item in self.fit_plot.plotItem.items[::-1]: + for item in self.fit_plot.items[::-1]: if item not in [self.data_graph, self.data_graph_imag, self.fit_graph, self.fit_graph_imag]: self.fit_plot.removeItem(item) diff --git a/src/resources/_ui/fitresult.ui b/src/resources/_ui/fitresult.ui index cd2b130..bd81cb1 100644 --- a/src/resources/_ui/fitresult.ui +++ b/src/resources/_ui/fitresult.ui @@ -6,8 +6,8 @@ 0 0 - 864 - 712 + 969 + 974 @@ -307,17 +307,7 @@
- - - - - - - Residuals - - - - + @@ -435,7 +425,7 @@
..lib.forms
- PlotWidget + GraphicsLayoutWidget QGraphicsView
pyqtgraph
From 888d86d9fe64eeed4710b43a0c70986fa7dc6dcf Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Thu, 18 May 2023 20:50:39 +0200 Subject: [PATCH 12/37] remove typo in AppImageBuilder.yml --- AppImageBuilder.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 80b4572..fb2f22f 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -70,7 +70,7 @@ AppDir: - usr/share/doc/*/README.* - usr/share/doc/*/changelog.* - usr/share/doc/*/NEWS.* - - usr/share/doc/*/TODO.}* + - usr/share/doc/*/TODO.* runtime: # if needed, apparently replaces hardcoded location with APPDIR location # path_mappings: From 6e9dd4d45f8055dc9b55898e425ba375089d33b6 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 11:33:02 +0200 Subject: [PATCH 13/37] use gnuplot as lightweight plotter --- src/nmreval/io/fcbatchreader.py | 52 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/nmreval/io/fcbatchreader.py b/src/nmreval/io/fcbatchreader.py index f221bfe..9bb09fb 100644 --- a/src/nmreval/io/fcbatchreader.py +++ b/src/nmreval/io/fcbatchreader.py @@ -11,6 +11,7 @@ import numpy as np from nmreval.data.points import Points from nmreval.io.asciireader import AsciiReader from nmreval.io.hdfreader import HdfReader +from nmreval.lib.logger import logger from nmreval.utils.utils import get_temperature, roundrobin @@ -56,7 +57,7 @@ class FCReader: _temp = self._read_from_dir(filename) else: - raise TypeError + raise TypeError(f'{filename} is of unknown type') if not _temp: raise OSError(-666, f'No magnetization found for {filename.name}.', filename.name) @@ -212,7 +213,7 @@ class FCReader: except KeyError: self.f_params[k] = [new_entry] - if True: # save_fits or save_fig: + if True: # save_fits or save_fig: xplot = np.geomspace(v.x[0], v.x[-1], num=10*len(v.x)) yplot = FCReader.kww(xplot, *p0) save_name = f'{filename.stem}_{k:011.2f}'.replace('.', 'p') + '.dat' @@ -224,33 +225,30 @@ class FCReader: img_file = image_path.joinpath(save_name).with_suffix(".png") gnuplot_args = [ - 'gnuplot', - '-e', - 'set terminal png', - f'set output "{img_file}"', - f'set title "f = {k:.4g} Hz\n{p0[2]:.4g}(+/-{perr[2]:.4g}) beta: {p0[3]:.4g}(+/-{perr[3]:.4g})"', - 'set xlabel "t / s"', - 'set logscale x', - 'set format x "10^{{%L}}"', - 'set ylabel "M"', - 'set key off', - f'plot "{data_path.joinpath(save_name)}" with points pointtype 5, "{fit_path.joinpath(save_name)}" with lines', + 'set terminal png;', + f'set output "{img_file}";', + f'set title "f = {k:.4g} Hz\\n{p0[2]:.4g}(+/-{perr[2]:.4g}) beta: {p0[3]:.4g}(+/-{perr[3]:.4g})";', + 'set xlabel "t / s";', + 'set logscale x;', + 'set format x "10^{{%L}}";', + 'set ylabel "M";', + 'set key off;', + f'plot "{data_path.joinpath(save_name)}" with points pointtype 5, "{fit_path.joinpath(save_name)}" with lines;', ] - print(gnuplot_args) - - subprocess.Popen(gnuplot_args) - - # if save_fig: - # fig, ax = plt.subplots() - # ax.set_xlabel('t / s') - # ax.set_ylabel('M') - # axheader = f'T1: {p0[2]:.4g}(+/-{perr[2]:.4g}) beta: {p0[3]:.4g}(+/-{perr[3]:.4g})' - # ax.set_title(f'f = {k:.4g} Hz\n{axheader}') - # ax.semilogx(v.x, v.y, 'o') - # ax.semilogx(xplot, yplot, '-') - # fig.savefig(image_path.joinpath(save_name).with_suffix('.png')) - # plt.close(fig) + try: + proc = subprocess.Popen( + ['gnuplot', '-p'], + shell=True, + stdin=subprocess.PIPE, + encoding='utf8', + ) + for args in gnuplot_args: + proc.stdin.write(args) + proc.stdin.write('quit\n') + proc.stdin.flush() + except Exception as e: + logger.error(f'saving image {save_name} failed', e) freqs = np.asanyarray(freqs) params = np.asanyarray(params) From 3dcd44c3eefc7d2ed911622f31a235e86e3a0db0 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 11:37:24 +0200 Subject: [PATCH 14/37] restore optionality to figure saving --- src/nmreval/io/fcbatchreader.py | 53 +++++++++++++++++---------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/nmreval/io/fcbatchreader.py b/src/nmreval/io/fcbatchreader.py index 9bb09fb..f67a661 100644 --- a/src/nmreval/io/fcbatchreader.py +++ b/src/nmreval/io/fcbatchreader.py @@ -213,7 +213,7 @@ class FCReader: except KeyError: self.f_params[k] = [new_entry] - if True: # save_fits or save_fig: + if save_fits or save_fig: xplot = np.geomspace(v.x[0], v.x[-1], num=10*len(v.x)) yplot = FCReader.kww(xplot, *p0) save_name = f'{filename.stem}_{k:011.2f}'.replace('.', 'p') + '.dat' @@ -222,33 +222,34 @@ class FCReader: np.savetxt(fit_path.joinpath(save_name), np.c_[xplot, yplot], header=header+'\t'.join([f'{p}+/-{err}' for p, err in zip(p0, perr)])) - img_file = image_path.joinpath(save_name).with_suffix(".png") + if save_fig: + img_file = image_path.joinpath(save_name).with_suffix(".png") - gnuplot_args = [ - 'set terminal png;', - f'set output "{img_file}";', - f'set title "f = {k:.4g} Hz\\n{p0[2]:.4g}(+/-{perr[2]:.4g}) beta: {p0[3]:.4g}(+/-{perr[3]:.4g})";', - 'set xlabel "t / s";', - 'set logscale x;', - 'set format x "10^{{%L}}";', - 'set ylabel "M";', - 'set key off;', - f'plot "{data_path.joinpath(save_name)}" with points pointtype 5, "{fit_path.joinpath(save_name)}" with lines;', - ] + gnuplot_args = [ + 'set terminal png;', + f'set output "{img_file}";', + f'set title "f = {k:.4g} Hz\\n{p0[2]:.4g}(+/-{perr[2]:.4g}) beta: {p0[3]:.4g}(+/-{perr[3]:.4g})";', + 'set xlabel "t / s";', + 'set logscale x;', + 'set format x "10^{{%L}}";', + 'set ylabel "M";', + 'set key off;', + f'plot "{data_path.joinpath(save_name)}" with points pointtype 5, "{fit_path.joinpath(save_name)}" with lines;', + ] - try: - proc = subprocess.Popen( - ['gnuplot', '-p'], - shell=True, - stdin=subprocess.PIPE, - encoding='utf8', - ) - for args in gnuplot_args: - proc.stdin.write(args) - proc.stdin.write('quit\n') - proc.stdin.flush() - except Exception as e: - logger.error(f'saving image {save_name} failed', e) + try: + proc = subprocess.Popen( + ['gnuplot', '-p'], + shell=True, + stdin=subprocess.PIPE, + encoding='utf8', + ) + for args in gnuplot_args: + proc.stdin.write(args) + proc.stdin.write('quit\n') + proc.stdin.flush() + except Exception as e: + logger.error(f'saving image {save_name} failed', e) freqs = np.asanyarray(freqs) params = np.asanyarray(params) From a1ab6335c5d0d2875ac9cd127e1f7288734faca2 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 13:25:56 +0200 Subject: [PATCH 15/37] remove remnants of fitresult redesign; closes #68 --- src/gui_qt/fit/result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui_qt/fit/result.py b/src/gui_qt/fit/result.py index 7bb927b..22ef2a1 100644 --- a/src/gui_qt/fit/result.py +++ b/src/gui_qt/fit/result.py @@ -153,7 +153,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): def set_plot(self, idx: str): if self.last_idx is not None: self.graph_opts[self.last_idx] = ( - self.fit_plot.getPlotItem().viewRange(), + self.fit_plot.viewRange(), self.logx_box.isChecked(), self.logy_box.isChecked(), ) From 66a0e40a23fcf59b973302cb7871076d0e671c48 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 15:48:32 +0200 Subject: [PATCH 16/37] display correct start parameter --- src/gui_qt/fit/fit_parameter.py | 12 ++++++------ src/gui_qt/fit/fitwindow.py | 9 ++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/gui_qt/fit/fit_parameter.py b/src/gui_qt/fit/fit_parameter.py index bcb3d1f..69b7301 100644 --- a/src/gui_qt/fit/fit_parameter.py +++ b/src/gui_qt/fit/fit_parameter.py @@ -263,17 +263,17 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit): return data_parameter, lb, ub, is_fixed, global_p, is_linked def set_parameter(self, set_id: str | None, parameter: list[float]) -> int: - param_len = len(list(filter(lambda g: not isinstance(g, SelectionWidget), self.global_parameter))) - + num_parameter = list(filter(lambda g: not isinstance(g, SelectionWidget), self.global_parameter)) + param_len = len(num_parameter) if set_id is None: - for val, g in zip(parameter, self.global_parameter): - if isinstance(g, SelectionWidget): - continue + for i, g in enumerate(num_parameter): + val = parameter[i] g.set_parameter(val) + self.glob_values[i] = val else: new_param = self.data_values[set_id] - min_len = min(param_len, len(new_param), len(new_param)) + min_len = min(param_len, len(new_param)) for i in range(min_len): new_param[i] = parameter[i] diff --git a/src/gui_qt/fit/fitwindow.py b/src/gui_qt/fit/fitwindow.py index 9a77171..5ba0ea6 100644 --- a/src/gui_qt/fit/fitwindow.py +++ b/src/gui_qt/fit/fitwindow.py @@ -77,16 +77,11 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog): w.deleteLater() del self.param_widgets[idx] - if len(self.functionwidget) == 0: + self._current_function = None + if len(self.param_widgets) == 0: # empty model self.newmodel_button.setEnabled(False) self.deletemodel_button.setEnabled(False) - self._current_function = None - - else: - f_tree = self.functionwidget.functree - func_idx = f_tree.currentItem().data(0, f_tree.counterRole) - self._current_function = self.functionwidget.functions[func_idx] @QtCore.pyqtSlot(int) def show_function_parameter(self, function_id: int, function_idx: int = None): From 8673e5acdb821ea152fefb5d8b520ef1110651dc Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 16:00:04 +0200 Subject: [PATCH 17/37] catch error in f-test --- src/nmreval/fit/result.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/nmreval/fit/result.py b/src/nmreval/fit/result.py index 95037f0..c888b32 100644 --- a/src/nmreval/fit/result.py +++ b/src/nmreval/fit/result.py @@ -320,7 +320,13 @@ class FitResult(Points): f_value = 1e318 else: f_value = (chi2-self.statistics['chi^2']) / (dof-self.dof) / self.statistics['red. chi^2'] - return f_value, 1-fdist.cdf(f_value, dof-self.dof, self.dof) + + try: + prob_f = 1-fdist.cdf(f_value, dof-self.dof, self.dof) + except: + prob_f = 0 + + return f_value, prob_f def get_state(self): state = super().get_state() From 1e9a390ae2a286f75997dd2f85416b1ec54e91b7 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 16:17:49 +0200 Subject: [PATCH 18/37] attempt to distinguish general and set-specifi parameter --- src/gui_qt/fit/fit_parameter.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/gui_qt/fit/fit_parameter.py b/src/gui_qt/fit/fit_parameter.py index 69b7301..fb36072 100644 --- a/src/gui_qt/fit/fit_parameter.py +++ b/src/gui_qt/fit/fit_parameter.py @@ -181,6 +181,7 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit): for i, value in enumerate(self.data_values[sid]): w = self.data_parameter[i] w.blockSignals(True) + w.show_as_local_parameter(value is not None) if value is None: w.value = self.glob_values[i] else: @@ -293,6 +294,7 @@ class ParameterSingleWidget(QtWidgets.QWidget): self._name = name self.label.setText(convert(name)) + self.label.setToolTip('IIf this is bold then this parameter is only for this data. otherwise the general parameter is used and displayed') self.value_line.setValidator(QtGui.QDoubleValidator()) self.value_line.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0) @@ -309,10 +311,12 @@ class ParameterSingleWidget(QtWidgets.QWidget): layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)) self.value_line = QtWidgets.QLineEdit(self) + self.value_line.textEdited.connect(lambda x: self.show_as_local_parameter(True)) layout.addWidget(self.value_line) self.reset_button = QtWidgets.QToolButton(self) self.reset_button.setText('Use global') + self.reset_button.clicked.connect(lambda: self.show_as_local_parameter(False)) layout.addWidget(self.reset_button) self.setLayout(layout) @@ -327,3 +331,9 @@ class ParameterSingleWidget(QtWidgets.QWidget): @value.setter def value(self, val): self.value_line.setText(f'{float(val):.5g}') + + def show_as_local_parameter(self, is_local): + if is_local: + self.label.setStyleSheet('font-weight: bold;') + else: + self.label.setStyleSheet('') From f60e125487be76d412c0f2e3c7c77cca709c6877 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 16:41:24 +0200 Subject: [PATCH 19/37] display parameter name with number ind window --- src/gui_qt/fit/result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui_qt/fit/result.py b/src/gui_qt/fit/result.py index 22ef2a1..d854ce4 100644 --- a/src/gui_qt/fit/result.py +++ b/src/gui_qt/fit/result.py @@ -113,8 +113,8 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog): res = self._results[set_id] self.param_tableWidget.setRowCount(len(res.parameter)) - for j, pvalue in enumerate(res.parameter.values()): - name = pvalue.name + for j, (pkey, pvalue) in enumerate(res.parameter.items()): + name = pkey p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'str', brackets=True)) self.param_tableWidget.setVerticalHeaderItem(j, p_header) From 84d136dd4c4de70719c84ac9c3ed26905352ea31 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 16:53:54 +0200 Subject: [PATCH 20/37] .nmr files recognize all data types; closes #71 --- src/gui_qt/data/container.py | 4 ++++ src/nmreval/io/nmrreader.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gui_qt/data/container.py b/src/gui_qt/data/container.py index 24cf09d..30cdb14 100644 --- a/src/gui_qt/data/container.py +++ b/src/gui_qt/data/container.py @@ -10,6 +10,7 @@ from nmreval.data.points import Points from nmreval.data.signals import Signal from nmreval.utils.text import convert from nmreval.data.bds import BDS +from nmreval.data.dsc import DSC from nmreval.lib.colors import BaseColor, TUColors from nmreval.lib.lines import LineStyle from nmreval.lib.symbols import SymbolStyle, symbolcycle @@ -488,6 +489,9 @@ class PointContainer(ExperimentContainer): self.mode = 'pts' self._init_plot(**kwargs) + if isinstance(self._data, DSC): + self.mode = 'dsc' + def _init_plot(self, **kwargs): self.plot_imag = None diff --git a/src/nmreval/io/nmrreader.py b/src/nmreval/io/nmrreader.py index 2268c8c..0a33339 100644 --- a/src/nmreval/io/nmrreader.py +++ b/src/nmreval/io/nmrreader.py @@ -5,6 +5,8 @@ from collections import OrderedDict from .read_old_nmr import HAS_BSDDB3, _read_file_v1 from ..data.nmr import FID, Spectrum +from ..data.bds import BDS +from ..data.dsc import DSC from ..data.points import Points from ..fit.result import FitResult, FitResultCreator from ..lib.colors import Colors @@ -51,7 +53,7 @@ class NMRReader: states = pickle.load(fp) datalist = OrderedDict() - _dtypes = {'pts': Points, 'fit': FitResult, 'fid': FID} + _dtypes = {'pts': Points, 'fit': FitResult, 'fid': FID, 'bds': BDS, 'dsc': DSC} for s in states['sets']: set_id = s.pop('id') From cd9c85c12bfd1103e2827db7d26b6073f6cd9150 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 16:56:01 +0200 Subject: [PATCH 21/37] add FID as fallback option for dtype in nmrreader --- src/nmreval/io/nmrreader.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nmreval/io/nmrreader.py b/src/nmreval/io/nmrreader.py index 0a33339..18bae10 100644 --- a/src/nmreval/io/nmrreader.py +++ b/src/nmreval/io/nmrreader.py @@ -53,11 +53,17 @@ class NMRReader: states = pickle.load(fp) datalist = OrderedDict() - _dtypes = {'pts': Points, 'fit': FitResult, 'fid': FID, 'bds': BDS, 'dsc': DSC} + _dtypes = {'pts': Points, + 'fit': FitResult, + 'fid': FID, + 'bds': BDS, + 'dsc': DSC, + 'spectrum': Spectrum + } for s in states['sets']: set_id = s.pop('id') - dtype = _dtypes[s.pop('mode')] + dtype = _dtypes.get(s.pop('mode'), FID) data = dtype.set_state(s.pop('data')) datalist[set_id] = (data, s) From 2adf15104f052ad5e47e6d623ecd62015cf2a7dc Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 17:35:32 +0200 Subject: [PATCH 22/37] attempt to only show real/imag part of complex fit functions; closes #72 --- src/nmreval/fit/result.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/nmreval/fit/result.py b/src/nmreval/fit/result.py index c888b32..c7bd517 100644 --- a/src/nmreval/fit/result.py +++ b/src/nmreval/fit/result.py @@ -63,9 +63,19 @@ class FitResultCreator: parameters = OrderedDict([(k, v) for k, v in zip(pnames, p)]) p_final = [p.value for p in parameters.values()] - _y = model.func(p_final, _x, **fun_kwargs) resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig + actual_complex_mode = 0 + if 'complex_mode' in fun_kwargs: + actual_complex_mode = fun_kwargs['complex_mode'] + fun_kwargs['complex_mode'] = 0 + + _y = model.func(p_final, _x, **fun_kwargs) + if actual_complex_mode == 1: + _y.imag = 0 + elif actual_complex_mode == 2: + _y.real = 0 + stats = FitResultCreator.calc_statistics(_y, resid, nobs, nvar) varied = [p.var for p in parameters.values()] @@ -356,7 +366,7 @@ class FitResult(Points): def with_new_x(self, x_values): if self.func is None: - raise ValueError('no fit function available to calcualate new y values') + raise ValueError('no fit function available to calculate new y values') new_fit = self.copy() y_values = self.func.func(self.p_final, x_values, **self.fun_kwargs) @@ -372,4 +382,4 @@ class FitResult(Points): else: part_functions.append(Points(x_values, sub_y, name=sub_name)) - return part_functions \ No newline at end of file + return part_functions From 65034d45189630e8531b5844edba3c145710aa29 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Fri, 19 May 2023 18:13:57 +0200 Subject: [PATCH 23/37] sub function show numbering; closes #70 --- src/gui_qt/main/management.py | 6 ++--- src/nmreval/fit/_meta.py | 5 ++-- src/nmreval/fit/result.py | 45 +++++++++++++++++++++++++++++------ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py index 0a817c1..a85e4d1 100644 --- a/src/gui_qt/main/management.py +++ b/src/gui_qt/main/management.py @@ -653,12 +653,10 @@ class UpperManagement(QtCore.QObject): else: continue - for key, pvalue in data.parameter.items(): - name = pvalue.full_name - fit_key = key + data.model_name + for fit_key, pvalue in data.parameter.items(): if fit_key not in fit_dict: - fit_dict[fit_key] = [[], name] + fit_dict[fit_key] = [[], fit_key] err = 0 if pvalue.error is None else pvalue.error diff --git a/src/nmreval/fit/_meta.py b/src/nmreval/fit/_meta.py index 230cb68..9fe9c34 100644 --- a/src/nmreval/fit/_meta.py +++ b/src/nmreval/fit/_meta.py @@ -68,6 +68,7 @@ class MultiModel: self._kwargs_right = {} self._kwargs_left = {} self.fun_kwargs = {} + self.idx = (left_idx, right_idx) # mapping kwargs to kwargs of underlying functions self._ext_int_kw = {} @@ -178,13 +179,13 @@ class MultiModel: if isinstance(self._left, MultiModel): yield from self._left.sub_name() elif hasattr(self._left, 'name'): - yield self._left.name + yield f'{self._left.name}({self.idx[0]})' else: yield self.name + '(lhs)' if isinstance(self._right, MultiModel): yield from self._right.sub_name() elif hasattr(self._right, 'name'): - yield self._right.name + yield f'{self._right.name}({self.idx[1]})' else: yield self.name + '(rhs)' diff --git a/src/nmreval/fit/result.py b/src/nmreval/fit/result.py index c7bd517..65d340d 100644 --- a/src/nmreval/fit/result.py +++ b/src/nmreval/fit/result.py @@ -65,16 +65,20 @@ class FitResultCreator: resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig - actual_complex_mode = 0 + actual_mode = -1 if 'complex_mode' in fun_kwargs: - actual_complex_mode = fun_kwargs['complex_mode'] + actual_mode = fun_kwargs['complex_mode'] fun_kwargs['complex_mode'] = 0 _y = model.func(p_final, _x, **fun_kwargs) - if actual_complex_mode == 1: - _y.imag = 0 - elif actual_complex_mode == 2: - _y.real = 0 + + if not actual_mode < 0: + if actual_mode == 1: + _y.imag = 0 + elif actual_mode == 2: + _y.real = 0 + + fun_kwargs['complex_mode'] = actual_mode stats = FitResultCreator.calc_statistics(_y, resid, nobs, nvar) varied = [p.var for p in parameters.values()] @@ -368,18 +372,45 @@ class FitResult(Points): if self.func is None: raise ValueError('no fit function available to calculate new y values') + actual_mode = -1 + if 'complex_mode' in self.fun_kwargs: + actual_mode = self.fun_kwargs['complex_mode'] + self.fun_kwargs['complex_mode'] = 0 + new_fit = self.copy() y_values = self.func.func(self.p_final, x_values, **self.fun_kwargs) + if not actual_mode < 0: + if actual_mode == 1: + y_values.imag = 0 + elif actual_mode == 2: + y_values.real = 0 + + self.fun_kwargs['complex_mode'] = actual_mode + new_fit.set_data(x_values, y_values) return new_fit def sub(self, x_values): part_functions = [] + actual_mode = -1 + if 'complex_mode' in self.fun_kwargs: + actual_mode = self.fun_kwargs['complex_mode'] + self.fun_kwargs['complex_mode'] = 0 + for sub_name, sub_y in zip(self.func.sub_name(), self.func.sub(self.p_final, x_values, **self.fun_kwargs)): - if np.iscomplexobj(sub_y): + if not actual_mode < 0: + if actual_mode == 1: + sub_y.imag = 0 + elif actual_mode == 2: + sub_y.real = 0 + part_functions.append(Signal(x_values, sub_y, name=sub_name)) + else: part_functions.append(Points(x_values, sub_y, name=sub_name)) + if actual_mode < 0: + self.fun_kwargs['complex_mode'] = actual_mode + return part_functions From 4e2938b2a2eb881d10a115748b21711e9484c612 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 23 May 2023 06:40:08 +0000 Subject: [PATCH 24/37] proper handling of sets without label,; closes #73 --- src/gui_qt/io/gracereader.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui_qt/io/gracereader.py b/src/gui_qt/io/gracereader.py index 26b15ff..be3d62a 100644 --- a/src/gui_qt/io/gracereader.py +++ b/src/gui_qt/io/gracereader.py @@ -89,8 +89,11 @@ class QGraceReader(QtWidgets.QDialog, Ui_Dialog): item = iterator.value() key = (item.data(0, QtCore.Qt.UserRole)) s = self._reader.dataset(*key) - label = self._reader.get_property(*key, 'legend').replace('"', '') - # label = self._reader.graphs[key[0]].sets[key[1]]['legend'].replace('"', '') + label = self._reader.get_property(*key, 'legend') + if label is None: + label = '' + else: + label = label.replace('"', '') sd = s.data sd = np.atleast_2d(sd) if s.type == 'xydy': From 5590b5cd16121568c4e75da8f8c2a4bdeada0dfe Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 24 May 2023 09:00:31 +0000 Subject: [PATCH 25/37] SelectionWidget has no function show_as_local_parameter; fixes #75 --- src/gui_qt/fit/fit_parameter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gui_qt/fit/fit_parameter.py b/src/gui_qt/fit/fit_parameter.py index fb36072..682893e 100644 --- a/src/gui_qt/fit/fit_parameter.py +++ b/src/gui_qt/fit/fit_parameter.py @@ -181,7 +181,11 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit): for i, value in enumerate(self.data_values[sid]): w = self.data_parameter[i] w.blockSignals(True) - w.show_as_local_parameter(value is not None) + try: + w.show_as_local_parameter(value is not None) + except AttributeError: + pass + if value is None: w.value = self.glob_values[i] else: From c8287c2dbf660807b7c1bfb5244ab2a7409e2f26 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 30 May 2023 17:42:12 +0200 Subject: [PATCH 26/37] fix missing return of comments in asciireader --- src/nmreval/io/asciireader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py index e8a19ea..28ddbf8 100644 --- a/src/nmreval/io/asciireader.py +++ b/src/nmreval/io/asciireader.py @@ -41,7 +41,7 @@ class AsciiReader: def make_preview(self, num_lines: int): if num_lines <= len(self.lines): - return self.lines[:num_lines], max(self.width[:num_lines]) + return self.lines[:num_lines], max(self.width[:num_lines]), self.line_comment num_lines += len(self.header) with self.fname.open('r') as f: From 79a424c4bd773e182089290a85e20971957d99f5 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Wed, 31 May 2023 17:21:56 +0200 Subject: [PATCH 27/37] fix mismatched indexes between data and labels in asciireader --- src/nmreval/io/asciireader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py index 28ddbf8..62ed70f 100644 --- a/src/nmreval/io/asciireader.py +++ b/src/nmreval/io/asciireader.py @@ -156,7 +156,7 @@ class AsciiReader: for j in range(1, num_y+1, stepsize): if col_names is not None: # predefined name - kwargs['name'] = col_names[j] + 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]) From 26fea8f69fb489dc7cf430abcf20383a7d40db00 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Sun, 4 Jun 2023 15:57:17 +0200 Subject: [PATCH 28/37] remove temporary elements from graphs before deletion --- src/gui_qt/main/mainwindow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index 4a6f92a..d6bac83 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -376,10 +376,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): if wdgt == self.current_graph_widget: if self.ptsselectwidget.connected_figure == gid: self.ptsselectwidget.connected_figure = None + for line in self.ptsselectwidget.pts_lines: + self.current_graph_widget.remove_external(line) + self.tabWidget.removeTab(self.tabWidget.indexOf(self.ptsselectwidget)) if self.t1tauwidget.connected_figure == gid: self.t1tauwidget.connected_figure = None + self.current_graph_widget.add_external(self.t1tauwidget.min_pos) + self.current_graph_widget.add_external(self.t1tauwidget.parabola) self.tabWidget.removeTab(self.tabWidget.indexOf(self.t1tauwidget)) if self.fit_dialog.connected_figure == gid: From 90bd33a6808fa7d12a8aec3e39ec7d07837f787c Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Sun, 4 Jun 2023 19:39:12 +0200 Subject: [PATCH 29/37] check also for individual files in config directory; closes #76 --- src/nmreval/configs.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/nmreval/configs.py b/src/nmreval/configs.py index abdf2a3..3527139 100644 --- a/src/nmreval/configs.py +++ b/src/nmreval/configs.py @@ -10,23 +10,25 @@ __all__ = ['config_paths', 'check_for_config', 'read_configuration', 'write_conf def check_for_config(make=True): try: - config_paths() + conf_path = config_paths() except FileNotFoundError as e: if make: conf_path = pathlib.Path('~/.auswerten').expanduser() conf_path.mkdir(parents=True) - cwd = pathlib.Path(__file__).parent - copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py') - - with resource_path('resources', 'Default.agr') as fp: - copyfile(fp, conf_path / 'Default.agr') - - with resource_path('resources', 'logo.png') as fp: - copyfile(fp, conf_path / 'logo.png') else: raise e + for filename in ('Default.agr', 'logo.png'): + _file = conf_path / filename + if not _file.exists(): + with resource_path('resources', filename) as fp: + copyfile(fp, _file) + + if not (conf_path / 'usermodels.py').exists(): + cwd = pathlib.Path(__file__).parent + copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py') + def config_paths() -> pathlib.Path: searchpaths = ['~/.config/nmreval', '~/.auswerten', '/usr/share/nmreval'] From 6e976e1404d61b253e686cf4c59405b58a038225 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Mon, 12 Jun 2023 17:26:30 +0200 Subject: [PATCH 30/37] closes #78 interpolieren, auswahl von sets --- src/gui_qt/_py/interpol_dialog.py | 15 +++++++++------ src/resources/_ui/interpol_dialog.ui | 12 +++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/gui_qt/_py/interpol_dialog.py b/src/gui_qt/_py/interpol_dialog.py index c819070..c3b0827 100644 --- a/src/gui_qt/_py/interpol_dialog.py +++ b/src/gui_qt/_py/interpol_dialog.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'resources/_ui/interpol_dialog.ui' +# Form implementation generated from reading ui file 'src/resources/_ui/interpol_dialog.ui' # -# Created by: PyQt5 UI code generator 5.12.3 +# Created by: PyQt5 UI code generator 5.15.9 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets @@ -65,12 +66,13 @@ class Ui_Dialog(object): self.label_2 = QtWidgets.QLabel(Dialog) self.label_2.setObjectName("label_2") self.gridLayout.addWidget(self.label_2, 6, 0, 1, 1) - self.listWidget = QtWidgets.QListWidget(Dialog) + self.listWidget = QListWidgetSelect(Dialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) self.listWidget.setObjectName("listWidget") self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 2) self.sampling_widget = QtWidgets.QWidget(Dialog) @@ -130,8 +132,8 @@ class Ui_Dialog(object): self.label_8.setBuddy(self.dest_combobox) self.retranslateUi(Dialog) - self.buttonBox.accepted.connect(Dialog.accept) - self.buttonBox.rejected.connect(Dialog.reject) + self.buttonBox.accepted.connect(Dialog.accept) # type: ignore + self.buttonBox.rejected.connect(Dialog.reject) # type: ignore QtCore.QMetaObject.connectSlotsByName(Dialog) Dialog.setTabOrder(self.listWidget, self.ylog_checkBox) Dialog.setTabOrder(self.ylog_checkBox, self.interp_comboBox) @@ -164,3 +166,4 @@ class Ui_Dialog(object): self.xaxis_comboBox.setItemText(1, _translate("Dialog", "from data")) self.label_8.setText(_translate("Dialog", "Add interpolated data to")) self.xlog_checkBox.setText(_translate("Dialog", "use log(x)")) +from ..lib.listwidget import QListWidgetSelect diff --git a/src/resources/_ui/interpol_dialog.ui b/src/resources/_ui/interpol_dialog.ui index 8aabd2f..28fa858 100644 --- a/src/resources/_ui/interpol_dialog.ui +++ b/src/resources/_ui/interpol_dialog.ui @@ -141,7 +141,7 @@ - + 0 @@ -151,6 +151,9 @@ Select sets that shall be interpolated. No selection will create interpolations of all visible sets. + + QAbstractItemView::MultiSelection + @@ -276,6 +279,13 @@ + + + QListWidgetSelect + QListWidget +
..lib.listwidget
+
+
listWidget ylog_checkBox From f9341045874d451ad2294a459d79739ec638a740 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Mon, 12 Jun 2023 18:07:51 +0200 Subject: [PATCH 31/37] =?UTF-8?q?closes=20#80;=20evaluate=20expression=20a?= =?UTF-8?q?n=20Datens=C3=A4tzen=20mit=20gehiddenen=20Values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui_qt/data/container.py | 4 ++-- src/nmreval/data/points.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/gui_qt/data/container.py b/src/gui_qt/data/container.py index 30cdb14..940ac62 100644 --- a/src/gui_qt/data/container.py +++ b/src/gui_qt/data/container.py @@ -459,7 +459,7 @@ class ExperimentContainer(QtCore.QObject): return namespace def eval_expression(self, cmds, namespace): - namespace.update({'x': self.x, 'y': self.y, 'y_err': self.y_err, 'value': self.value}) + namespace.update({'x': self._data.x, 'y': self._data.y, 'y_err': self._data.y_err, 'value': self.value}) if len(self._fits) == 1: namespace.update({"fit['%s']" % (convert(pname, old='tex', new='str')): pvalue.value @@ -474,7 +474,7 @@ class ExperimentContainer(QtCore.QObject): if c: exec(c, globals(), namespace) - new_data.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err']) + new_data.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err'], replace_mask=False) new_data.value = namespace['value'] return new_data diff --git a/src/nmreval/data/points.py b/src/nmreval/data/points.py index 3282627..c2169ff 100644 --- a/src/nmreval/data/points.py +++ b/src/nmreval/data/points.py @@ -483,15 +483,17 @@ class Points: return self - def set_data(self, x: np.ndarray = None, y: np.ndarray = None, y_err: np.ndarray = None) -> PointLike: + def set_data(self, x: np.ndarray = None, y: np.ndarray = None, y_err: np.ndarray = None, replace_mask=True) -> PointLike: if x is None: x = self._x if y is None: y = self._y - if y_err is not None: + if y_err is None: y_err = self._y_err - self._x, self._y, self._y_err, self.mask = self._prepare_xy(x, y, y_err) + self._x, self._y, self._y_err, mask = self._prepare_xy(x, y, y_err) + if replace_mask: + self.mask = mask return self From 2499aac7a12dd09f1a464088245c7b5037387867 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Mon, 12 Jun 2023 17:52:25 +0200 Subject: [PATCH 32/37] check for value changes while tabbing in edit mode, avoid signal; closes #79 --- src/gui_qt/_py/valueeditor.py | 4 ++-- src/gui_qt/data/valueeditwidget.py | 17 +++++++++++++---- src/gui_qt/main/mainwindow.py | 1 - 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/gui_qt/_py/valueeditor.py b/src/gui_qt/_py/valueeditor.py index f2e2aeb..cac1bcf 100644 --- a/src/gui_qt/_py/valueeditor.py +++ b/src/gui_qt/_py/valueeditor.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'resources/_ui/valueeditor.ui' +# Form implementation generated from reading ui file 'src/resources/_ui/valueeditor.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. diff --git a/src/gui_qt/data/valueeditwidget.py b/src/gui_qt/data/valueeditwidget.py index bb94965..540c268 100644 --- a/src/gui_qt/data/valueeditwidget.py +++ b/src/gui_qt/data/valueeditwidget.py @@ -259,10 +259,7 @@ class ValueModel(QtCore.QAbstractTableModel): row = idx.row() if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]: val = self._data[row][idx.column()] - if isinstance(val, complex): - return f'{val.real:.8g}{val.imag:+.8g}j' - else: - return f'{val:.8g}' + return self.as_string(val) elif role == QtCore.Qt.BackgroundRole: pal = QtGui.QGuiApplication.palette() @@ -295,11 +292,16 @@ class ValueModel(QtCore.QAbstractTableModel): if value: if role == QtCore.Qt.EditRole: + if value == self.as_string(self._data[row][col]): + return True + try: value = complex(value) except ValueError: # not a number return False + + value = value.real if value.imag == 0 else value self._data[row][col] = value.real if value.imag == 0 else value self.itemChanged.emit(col, row, str(value)) self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role]) @@ -368,3 +370,10 @@ class ValueModel(QtCore.QAbstractTableModel): def unmask(self): self.mask = [True] * self.total_rows self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [ValueModel.maskRole]) + + @staticmethod + def as_string(value) -> str: + if isinstance(value, complex): + return f'{value.real:.8g}{value.imag:+.8g}j' + else: + return f'{value:.8g}' diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index d6bac83..c09433b 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -1115,7 +1115,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): else: pass - @QtCore.pyqtSlot(name='on_actionCreate_starter_triggered') def create_starter(self): make_starter(os.getenv('APPIMAGE')) From 775b5e7e8ac78200151fb5575bf1c951f6ab0774 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 13 Jun 2023 18:31:39 +0200 Subject: [PATCH 33/37] correct tab behavior after value change (part of #79) --- src/gui_qt/data/valueeditwidget.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gui_qt/data/valueeditwidget.py b/src/gui_qt/data/valueeditwidget.py index 540c268..c85f1d1 100644 --- a/src/gui_qt/data/valueeditwidget.py +++ b/src/gui_qt/data/valueeditwidget.py @@ -188,7 +188,15 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): new_value = complex(val) new_value = new_value.real if new_value.imag == 0 else new_value + # table view loses focus when itemChanged is emitted + # if edit of item is cause of change resume editing at next item + prev_state = self.tableView.state() + idx = self.tableView.currentIndex() + idx = idx.sibling((col+1)//3+row, (col+1) % 3) self.itemChanged.emit(sid, (col, row), new_value) + if prev_state == self.tableView.State.EditingState: + self.tableView.setCurrentIndex(idx) + self.tableView.edit(idx) @QtCore.pyqtSlot(QtCore.QItemSelection, QtCore.QItemSelection) def show_position(self, *_): From 4268bdc5aeccf48940065a81b85a38d3508d66d3 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 13 Jun 2023 19:08:24 +0200 Subject: [PATCH 34/37] #81 make t1 data real and catch more errors; --- src/nmreval/io/fcbatchreader.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/nmreval/io/fcbatchreader.py b/src/nmreval/io/fcbatchreader.py index f67a661..ea319b4 100644 --- a/src/nmreval/io/fcbatchreader.py +++ b/src/nmreval/io/fcbatchreader.py @@ -133,7 +133,7 @@ class FCReader: no_bevo = True break - if str(k.parent) != 'ACC_ABS_FID_sig (group)': + if str(k.parent) != 'ABS_ACC_FID_sig (group)': continue tevo = k.parameter['tevo'] @@ -144,14 +144,14 @@ class FCReader: if bevo not in _temp: _temp[bevo] = [] - _temp[bevo].append((tevo, *[pp[1] for pp in pts])) + _temp[bevo].append((tevo, *[np.real(pp[1]) for pp in pts])) if no_bevo: break if no_bevo: # hopefully only for old scripts - sig = reader.get_selected('/data/B=*/ACC_ABS_FID*', dtype='signal') + sig = reader.get_selected('/data/B=*/ABS_ACC_FID*', dtype='signal') _temp = {} for s in sig: pts = s.points([region]) @@ -159,7 +159,7 @@ class FCReader: if b not in _temp: _temp[b] = [] - _temp[b].append([s.value, *[pp[1] for pp in pts]]) + _temp[b].append([s.value, *[np.real(pp[1]) for pp in pts]]) for b, m in sorted(_temp.items()): m = np.array(m) @@ -192,15 +192,16 @@ class FCReader: freqs = [] for k, v in sorted(self.data[temperature].items()): - freqs.append(k) - # fit p0 = [v.y[0], v.y[-1]-v.y[0], v.x[int(0.5*len(v.x) - 0.5)], 1] try: p0, pcov = curve_fit(FCReader.kww, v.x, v.y, p0=p0, bounds=bounds, max_nfev=500*len(v)) - except RuntimeError: + except Exception as e: + logger.error(f'Fit of field {k} failed with exception', exc_info=e) continue + freqs.append(k) + perr = np.sqrt(np.diag(pcov)) params.append(p0) errors.append(perr) From 89c640e5912dbc8be6db6318904a7fffb7fc1ecd Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Thu, 15 Jun 2023 20:00:52 +0200 Subject: [PATCH 35/37] bugfix: remove value display item from graph before deletion --- src/gui_qt/main/mainwindow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index c09433b..4c566c2 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -393,6 +393,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.current_graph_widget.remove_external(item) if val_figure == gid: + self.current_graph_widget.remove_external(self.valuewidget.selection_real) + self.current_graph_widget.remove_external(self.valuewidget.selection_imag) self.tabWidget.setCurrentIndex(0) self.current_graph_widget.enable_picking(False) From 1a225b2cd296274b00bdb632df2c92457e77af23 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Thu, 15 Jun 2023 20:16:42 +0200 Subject: [PATCH 36/37] multiselection and check in graph and bdsdialog; part of #82 --- src/gui_qt/_py/bdsdialog.py | 18 +++++++++++------- src/gui_qt/_py/graph.py | 11 +++++++---- src/resources/_ui/bdsdialog.ui | 12 +++++++++++- src/resources/_ui/graph.ui | 10 +++++++++- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/gui_qt/_py/bdsdialog.py b/src/gui_qt/_py/bdsdialog.py index 3014019..795bff1 100644 --- a/src/gui_qt/_py/bdsdialog.py +++ b/src/gui_qt/_py/bdsdialog.py @@ -1,13 +1,16 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'resources/_ui/bdsdialog.ui' +# Form implementation generated from reading ui file 'src/resources/_ui/bdsdialog.ui' # -# Created by: PyQt5 UI code generator 5.9.2 +# Created by: PyQt5 UI code generator 5.15.9 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") @@ -16,12 +19,13 @@ class Ui_Dialog(object): self.gridLayout.setContentsMargins(3, 3, 3, 3) self.gridLayout.setSpacing(3) self.gridLayout.setObjectName("gridLayout") - self.listWidget = QtWidgets.QListWidget(Dialog) + self.listWidget = QListWidgetSelect(Dialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.listWidget.setObjectName("listWidget") self.gridLayout.addWidget(self.listWidget, 1, 0, 2, 1) self.groupBox_2 = QtWidgets.QGroupBox(Dialog) @@ -93,8 +97,8 @@ class Ui_Dialog(object): self.gridLayout.addWidget(self.label, 0, 0, 1, 2) self.retranslateUi(Dialog) - self.buttonBox.accepted.connect(Dialog.accept) - self.buttonBox.rejected.connect(Dialog.reject) + self.buttonBox.accepted.connect(Dialog.accept) # type: ignore + self.buttonBox.rejected.connect(Dialog.reject) # type: ignore QtCore.QMetaObject.connectSlotsByName(Dialog) Dialog.setTabOrder(self.freq_button, self.temp_button) Dialog.setTabOrder(self.temp_button, self.eps_checkBox) @@ -117,4 +121,4 @@ class Ui_Dialog(object): self.temp_checkBox.setText(_translate("Dialog", "Meas. temperature")) self.time_checkBox.setText(_translate("Dialog", "Meas. time")) self.label.setText(_translate("Dialog", "Found entries")) - +from ..lib.listwidget import QListWidgetSelect diff --git a/src/gui_qt/_py/graph.py b/src/gui_qt/_py/graph.py index 32e7c0e..8ecac0b 100644 --- a/src/gui_qt/_py/graph.py +++ b/src/gui_qt/_py/graph.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'resources/_ui/graph.ui' +# Form implementation generated from reading ui file 'src/resources/_ui/graph.ui' # -# Created by: PyQt5 UI code generator 5.12.3 +# Created by: PyQt5 UI code generator 5.15.9 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets @@ -196,12 +197,13 @@ class Ui_GraphWindow(object): self.gridLayout.setHorizontalSpacing(3) self.gridLayout.setVerticalSpacing(0) self.gridLayout.setObjectName("gridLayout") - self.listWidget = QtWidgets.QListWidget(GraphWindow) + self.listWidget = QListWidgetSelect(GraphWindow) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.listWidget.setObjectName("listWidget") self.gridLayout.addWidget(self.listWidget, 1, 1, 1, 1) self.checkBox = QtWidgets.QCheckBox(GraphWindow) @@ -272,4 +274,5 @@ class Ui_GraphWindow(object): self.label_6.setText(_translate("GraphWindow", "X Axis")) self.label_7.setText(_translate("GraphWindow", "Y Axis")) self.checkBox.setText(_translate("GraphWindow", "Show legend")) +from ..lib.listwidget import QListWidgetSelect from pyqtgraph import PlotWidget diff --git a/src/resources/_ui/bdsdialog.ui b/src/resources/_ui/bdsdialog.ui index fa275ee..9779e8e 100644 --- a/src/resources/_ui/bdsdialog.ui +++ b/src/resources/_ui/bdsdialog.ui @@ -30,13 +30,16 @@ 3 - + 0 0 + + QAbstractItemView::ExtendedSelection + @@ -207,6 +210,13 @@ + + + QListWidgetSelect + QListWidget +
..lib.listwidget
+
+
freq_button temp_button diff --git a/src/resources/_ui/graph.ui b/src/resources/_ui/graph.ui index 7c829d1..02f573f 100644 --- a/src/resources/_ui/graph.ui +++ b/src/resources/_ui/graph.ui @@ -515,13 +515,16 @@ 0 - + 0 0 + + QAbstractItemView::ExtendedSelection + @@ -554,6 +557,11 @@ QGraphicsView
pyqtgraph
+ + QListWidgetSelect + QListWidget +
..lib.listwidget
+
logx_button From 22f8bc80ed41169a328ccc9d4ab5257af110ef9a Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Mon, 19 Jun 2023 18:15:25 +0200 Subject: [PATCH 37/37] concat mask also; closes #84 --- src/gui_qt/data/container.py | 15 ++++++++------- src/gui_qt/main/management.py | 2 +- src/nmreval/data/points.py | 7 +++++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/gui_qt/data/container.py b/src/gui_qt/data/container.py index 940ac62..fd268d8 100644 --- a/src/gui_qt/data/container.py +++ b/src/gui_qt/data/container.py @@ -45,6 +45,7 @@ class ExperimentContainer(QtCore.QObject): self.actions = {} self._update_actions() + @plot_update def _init_plot(self): raise NotImplementedError @@ -528,17 +529,17 @@ class PointContainer(ExperimentContainer): line_kwargs['style'] = LineStyle.No sym_kwargs['symbol'] = next(PointContainer.symbols) - self.plot_real = PlotItem(x=self._data.x, y=self._data.y, name=self.name, + self.plot_real = PlotItem(x=self.x, y=self.y, name=self.name, symbol=None, pen=None, connect='finite') self.setSymbol(mode='real', **sym_kwargs) self.setLine(mode='real', **line_kwargs) if sym_kwargs['symbol'] != SymbolStyle.No: - self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err, + self.plot_error = ErrorBars(x=self.x, y=self.y, top=self.y_err, bottom=self.y_err, pen=mkPen({'color': self.plot_real.symbolcolor.rgb()})) else: - self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err, + self.plot_error = ErrorBars(x=self.x, y=self.y, top=self.y_err, bottom=self.y_err, pen=mkPen({'color': self.plot_real.linecolor.rgb()})) @@ -561,12 +562,12 @@ class FitContainer(ExperimentContainer): if isinstance(color, BaseColor): color = color.rgb() - self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name, + self.plot_real = PlotItem(x=self.x, y=self.y.real, name=self.name, pen=mkPen({'color': color}), connect='finite', symbol=None) if np.iscomplexobj(self._data.y): - self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name, + self.plot_imag = PlotItem(x=self.x, y=self.y.imag, name=self.name, pen=mkPen({'color': color}), connect='finite', symbol=None) @@ -599,9 +600,9 @@ class SignalContainer(ExperimentContainer): self._init_plot(symbol=symbol, **kwargs) def _init_plot(self, **kwargs): - self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name, + self.plot_real = PlotItem(x=self.x, y=self.y.real, name=self.name, symbol=None, pen=None, connect='finite') - self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name, + self.plot_imag = PlotItem(x=self.x, y=self.y.imag, name=self.name, symbol=None, pen=None, connect='finite') color = kwargs.get('color', None) diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py index a85e4d1..9655835 100644 --- a/src/gui_qt/main/management.py +++ b/src/gui_qt/main/management.py @@ -343,7 +343,7 @@ class UpperManagement(QtCore.QObject): if joined is None: joined = data_i.copy() else: - joined.append(data_i.x, data_i.y, data_i.y_err) + joined.append(data_i.data.x, data_i.data.y, y_err=data_i.data.y_err, mask=data_i.data.mask) name_set.add(data_i.name) group_set.add(data_i.group) diff --git a/src/nmreval/data/points.py b/src/nmreval/data/points.py index c2169ff..671142d 100644 --- a/src/nmreval/data/points.py +++ b/src/nmreval/data/points.py @@ -497,8 +497,11 @@ class Points: return self - def append(self, x: ArrayLike, y: ArrayLike, y_err: ArrayLike = None): - x, y, y_err, mask = self._prepare_xy(x, y, y_err) + def append(self, x: ArrayLike, y: ArrayLike, y_err: ArrayLike = None, mask: ArrayLike = None): + if mask is None: + x, y, y_err, mask = self._prepare_xy(x, y, y_err) + else: + x, y, y_err, _ = self._prepare_xy(x, y, y_err) self._x = np.r_[self._x, x] self._y = np.r_[self._y, y]