diff --git a/src/gui_qt/_py/basewindow.py b/src/gui_qt/_py/basewindow.py index d916548..8d23235 100644 --- a/src/gui_qt/_py/basewindow.py +++ b/src/gui_qt/_py/basewindow.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'src/resources/_ui/basewindow.ui' +# Form implementation generated from reading ui file './nmreval/src/resources/_ui/basewindow.ui' # # Created by: PyQt5 UI code generator 5.15.10 # @@ -87,6 +87,8 @@ class Ui_BaseWindow(object): self.menuSave.setObjectName("menuSave") self.menuData = QtWidgets.QMenu(self.menubar) self.menuData.setObjectName("menuData") + self.menuCut_to_visible_range = QtWidgets.QMenu(self.menuData) + self.menuCut_to_visible_range.setObjectName("menuCut_to_visible_range") self.menuHelp = QtWidgets.QMenu(self.menubar) self.menuHelp.setObjectName("menuHelp") self.menuExtra = QtWidgets.QMenu(self.menubar) @@ -304,8 +306,6 @@ class Ui_BaseWindow(object): self.actionDerivation.setObjectName("actionDerivation") self.actionIntegration = QtWidgets.QAction(BaseWindow) self.actionIntegration.setObjectName("actionIntegration") - self.action_cut = QtWidgets.QAction(BaseWindow) - self.action_cut.setObjectName("action_cut") self.actionMove_between_plots = QtWidgets.QAction(BaseWindow) self.actionMove_between_plots.setObjectName("actionMove_between_plots") self.actionBaseline = QtWidgets.QAction(BaseWindow) @@ -368,6 +368,10 @@ class Ui_BaseWindow(object): self.actionExclude_region = QtWidgets.QAction(BaseWindow) self.actionExclude_region.setCheckable(True) self.actionExclude_region.setObjectName("actionExclude_region") + self.action_cut_xaxis = QtWidgets.QAction(BaseWindow) + self.action_cut_xaxis.setObjectName("action_cut_xaxis") + self.action_cut_yaxis = QtWidgets.QAction(BaseWindow) + self.action_cut_yaxis.setObjectName("action_cut_yaxis") self.menuSave.addAction(self.actionSave) self.menuSave.addAction(self.actionExportGraphic) self.menuSave.addAction(self.action_save_fit_parameter) @@ -380,6 +384,9 @@ class Ui_BaseWindow(object): self.menuFile.addSeparator() self.menuFile.addAction(self.action_close) self.menuFile.addSeparator() + self.menuCut_to_visible_range.addSeparator() + self.menuCut_to_visible_range.addAction(self.action_cut_xaxis) + self.menuCut_to_visible_range.addAction(self.action_cut_yaxis) self.menuData.addAction(self.action_new_set) self.menuData.addAction(self.action_delete_sets) self.menuData.addAction(self.actionMove_between_plots) @@ -389,7 +396,7 @@ class Ui_BaseWindow(object): self.menuData.addAction(self.action_sort_pts) self.menuData.addAction(self.actionSkip_points) self.menuData.addSeparator() - self.menuData.addAction(self.action_cut) + self.menuData.addAction(self.menuCut_to_visible_range.menuAction()) self.menuData.addSeparator() self.menuData.addAction(self.actionChange_datatypes) self.menuHelp.addAction(self.actionShow_error_log) @@ -515,6 +522,7 @@ class Ui_BaseWindow(object): self.menuFile.setTitle(_translate("BaseWindow", "&File")) self.menuSave.setTitle(_translate("BaseWindow", "&Save...")) self.menuData.setTitle(_translate("BaseWindow", "&Data")) + self.menuCut_to_visible_range.setTitle(_translate("BaseWindow", "Cut to visible range")) self.menuHelp.setTitle(_translate("BaseWindow", "&Help")) self.menuExtra.setTitle(_translate("BaseWindow", "Math")) self.menuNormalize.setTitle(_translate("BaseWindow", "&Normalize")) @@ -608,7 +616,6 @@ class Ui_BaseWindow(object): self.actionIntegrate.setText(_translate("BaseWindow", "Integrate")) self.actionDerivation.setText(_translate("BaseWindow", "Differentiation...")) self.actionIntegration.setText(_translate("BaseWindow", "Integration...")) - self.action_cut.setText(_translate("BaseWindow", "Cut to visible range")) self.actionMove_between_plots.setText(_translate("BaseWindow", "Move sets...")) self.actionBaseline.setText(_translate("BaseWindow", "Baseline...")) self.actionCalculateT1.setText(_translate("BaseWindow", "Calculate relaxation...")) @@ -636,6 +643,10 @@ class Ui_BaseWindow(object): self.actionBinning.setText(_translate("BaseWindow", "Binning...")) self.actionTNMH.setText(_translate("BaseWindow", "TNMH...")) self.actionExclude_region.setText(_translate("BaseWindow", "Exclude region")) + self.action_cut_xaxis.setText(_translate("BaseWindow", "x axis")) + self.action_cut_xaxis.setToolTip(_translate("BaseWindow", "Remove data points outside visible x range.")) + self.action_cut_yaxis.setText(_translate("BaseWindow", "y axis")) + self.action_cut_yaxis.setToolTip(_translate("BaseWindow", "Remove data points outside visible y range. Uses real part of points.")) from ..data.datawidget.datawidget import DataWidget from ..data.integral_widget import IntegralWidget from ..data.point_select import PointSelectWidget diff --git a/src/gui_qt/_py/interpol_dialog.py b/src/gui_qt/_py/interpol_dialog.py index c3b0827..c0ef858 100644 --- a/src/gui_qt/_py/interpol_dialog.py +++ b/src/gui_qt/_py/interpol_dialog.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'src/resources/_ui/interpol_dialog.ui' +# Form implementation generated from reading ui file './nmreval/src/resources/_ui/interpol_dialog.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.10 # # 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. @@ -55,7 +55,7 @@ class Ui_Dialog(object): self.gridLayout.addWidget(self.interp_comboBox, 4, 1, 1, 1) self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.gridLayout.addWidget(self.buttonBox, 12, 0, 1, 2) self.line = QtWidgets.QFrame(Dialog) @@ -132,8 +132,6 @@ class Ui_Dialog(object): self.label_8.setBuddy(self.dest_combobox) self.retranslateUi(Dialog) - 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) diff --git a/src/gui_qt/data/container.py b/src/gui_qt/data/container.py index 71bf9f4..7364579 100644 --- a/src/gui_qt/data/container.py +++ b/src/gui_qt/data/container.py @@ -300,10 +300,12 @@ class ExperimentContainer(QtCore.QObject): self._relations.pop(relation_type) def _update_actions(self): - self.actions.update({'sort': self._data.sort, - 'cut': self._data.cut, - 'norm': self._data.normalize, - 'center': self.center}) + self.actions.update({ + 'sort': self._data.sort, + 'cut': self._data.cut, + 'norm': self._data.normalize, + 'center': self.center, + }) @plot_update def update(self, opts: dict): @@ -311,9 +313,11 @@ class ExperimentContainer(QtCore.QObject): def get_properties(self) -> dict: props = OrderedDict() - props['General'] = OrderedDict([('Name', self.name), - ('Value', str(self.value)), - ('Group', str(self.group))]) + props['General'] = OrderedDict([ + ('Name', self.name), + ('Value', str(self.value)), + ('Group', str(self.group)), + ]) props['Symbol'] = OrderedDict() props['Line'] = OrderedDict() @@ -480,10 +484,12 @@ class ExperimentContainer(QtCore.QObject): else: prefix = f'g[{i}].s[{j}].' - namespace = {prefix + 'x': (self.x, 'x values'), - prefix + 'y': [self.y, 'y values'], - prefix + 'y_err': (self.y_err, 'y error values'), - prefix + 'value': (self.value, str(self.value))} + namespace = { + prefix + 'x': (self.x, 'x values'), + prefix + 'y': [self.y, 'y values'], + prefix + 'y_err': (self.y_err, 'y error values'), + prefix + 'value': (self.value, str(self.value)), + } if len(self._fits) == 1: namespace.update({ diff --git a/src/gui_qt/data/valueeditwidget.py b/src/gui_qt/data/valueeditwidget.py index ae95a5f..de2376b 100644 --- a/src/gui_qt/data/valueeditwidget.py +++ b/src/gui_qt/data/valueeditwidget.py @@ -385,6 +385,6 @@ class ValueModel(QtCore.QAbstractTableModel): @staticmethod def as_string(value) -> str: if isinstance(value, complex): - return f'{value.real:.8g}{value.imag:+.8g}j' + return f'{value.real:.13g}{value.imag:+.13g}j' else: - return f'{value:.8g}' + return f'{value:.13g}' diff --git a/src/gui_qt/io/fcbatchreader.py b/src/gui_qt/io/fcbatchreader.py index c4bb80b..a4444e6 100644 --- a/src/gui_qt/io/fcbatchreader.py +++ b/src/gui_qt/io/fcbatchreader.py @@ -28,6 +28,12 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog): self.listWidget.installEventFilter(self) + def __call__(self, path=None): + if path is None: + path = pathlib.Path().home() + self.path = path + self.listWidget.clear() + def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent) -> bool: # intercept key press in listwidget to allow deletion with Del if evt.type() == QtCore.QEvent.Type.KeyPress: @@ -82,6 +88,7 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog): def accept(self): items = [self.listWidget.item(i).text() for i in range(self.listWidget.count())] + print(items) if items: with busy_cursor(): self.read(items) @@ -116,6 +123,7 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog): ret_vals = [] ret_vals.extend(fc_eval.get_parameter(path=self.label.text(), kind='temp', parameter=save_variables)) + print(ret_vals) grp = '' if not self.graph_checkbox.isChecked(): diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index 7f6123b..34e94ba 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -62,6 +62,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.fitresult_dialog = None self.eval = None self.editor = None + self._interpol_dialog = None + self.fc_reader = None self.logtext = QTextHandler(self) logger.addHandler(self.logtext) @@ -233,7 +235,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.action_norm_first.triggered.connect(lambda: self.management.apply('norm', ('first',))) self.action_norm_last.triggered.connect(lambda: self.management.apply('norm', ('last',))) self.action_norm_area.triggered.connect(lambda: self.management.apply('norm', ('area',))) - self.action_cut.triggered.connect(lambda: self.management.cut()) + self.action_cut_xaxis.triggered.connect(lambda: self.management.cut(True, False)) + self.action_cut_yaxis.triggered.connect(lambda: self.management.cut(False, True)) self.actionConcatenate_sets.triggered.connect(lambda: self.management.cat()) @@ -264,14 +267,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): @QtCore.pyqtSlot(name='on_actionOpen_FC_triggered') def read_fc(self): - reader = QFCReader(path=self.path, parent=self) - reader.add_graphs(self.management.graphs.list()) - reader.data_read.connect(self.management.add_new_data) - reader.exec() + if self.fc_reader is None: + self.fc_reader = QFCReader(path=self.path, parent=self) + self.fc_reader.data_read.connect(self.management.add_new_data) + else: + self.fc_reader(path=self.path) + self.fc_reader.add_graphs(self.management.graphs.list()) + self.fc_reader.exec() - self.path = reader.path - - del reader + self.path = self.fc_reader.path @QtCore.pyqtSlot(name='on_actionPrint_triggered') def print(self): @@ -701,10 +705,13 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): return gnames = self.management.graphs.tree() - dialog = InterpolDialog(parent=self) - dialog.set_data(gnames, self.current_graph_widget.id) - dialog.new_data.connect(self.management.interpolate_data) - dialog.show() + if self._interpol_dialog is None: + self._interpol_dialog = InterpolDialog(parent=self) + self._interpol_dialog.new_data.connect(self.management.interpolate_data) + else: + self._interpol_dialog() + self._interpol_dialog.set_data(gnames, self.current_graph_widget.id) + self._interpol_dialog.show() @QtCore.pyqtSlot(name='on_action_calc_triggered') def open_eval_dialog(self): diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py index 56c387a..f47362b 100644 --- a/src/gui_qt/main/management.py +++ b/src/gui_qt/main/management.py @@ -450,10 +450,17 @@ class UpperManagement(QtCore.QObject): self.undostack.push(single_undo) self.undostack.endMacro() - def cut(self): + def cut(self, x: bool = False, y: bool = False) -> None: if self.current_graph: - xlim, _ = self.graphs[self.current_graph].ranges - self.apply('cut', xlim) + xlim, ylim = self.graphs[self.current_graph].ranges + + if x is False: + xlim = (None, None) + + if y is False: + ylim = (None, None) + + self.apply('cut', (*xlim, *ylim)) @QtCore.pyqtSlot() def unmask(self): diff --git a/src/gui_qt/math/interpol.py b/src/gui_qt/math/interpol.py index c58d03c..046a7bc 100644 --- a/src/gui_qt/math/interpol.py +++ b/src/gui_qt/math/interpol.py @@ -16,6 +16,12 @@ class InterpolDialog(QtWidgets.QDialog, Ui_Dialog): self.step_lineEdit.setValidator(QtGui.QIntValidator()) self._data = {} + self._src_id = None + self._dest_graph = '' + + def __call__(self): + self.listWidget.clear() + self._data = {} @QtCore.pyqtSlot(int, name='on_xaxis_comboBox_currentIndexChanged') def change_x_source(self, idx: int): @@ -25,29 +31,41 @@ class InterpolDialog(QtWidgets.QDialog, Ui_Dialog): def set_data(self, data, current_gid): self.graph_combobox.blockSignals(True) self._data = {} + dest_idx = 0 for (gid, graph_name), sets in data.items(): self.graph_combobox.addItem(graph_name, userData=gid) self.dest_combobox.addItem(graph_name, userData=gid) + if self._dest_graph == gid: + dest_idx = self.dest_combobox.currentIndex() if gid == current_gid: self.make_list(sets) self._data[gid] = sets self.graph_combobox.blockSignals(False) - self.change_graph(0) + self.change_graph(dest_idx) def make_list(self, current_sets): for sid, set_name in current_sets: item = QtWidgets.QListWidgetItem(set_name) - item.setData(QtCore.Qt.UserRole, sid) - item.setCheckState(QtCore.Qt.Checked) + item.setData(QtCore.Qt.ItemDataRole.UserRole, sid) + item.setCheckState(QtCore.Qt.CheckState.Checked) self.listWidget.addItem(item) @QtCore.pyqtSlot(int, name='on_graph_combobox_currentIndexChanged') def change_graph(self, idx: int): self.set_combobox.clear() - gid = self.graph_combobox.itemData(idx, QtCore.Qt.UserRole) + gid = self.graph_combobox.itemData(idx, QtCore.Qt.ItemDataRole.UserRole) + set_idx = -1 if gid is not None: - for set_key, set_name in self._data[gid]: + for i, (set_key, set_name) in enumerate(self._data[gid]): + print(self._src_id, set_key, set_name, i) self.set_combobox.addItem(set_name, userData=set_key) + print(self.set_combobox.currentIndex()) + if self._src_id == set_key: + set_idx = i + + print(set_idx) + if set_idx > -1: + self.set_combobox.setCurrentIndex(set_idx) def collect_parameter(self): xlog = self.xlog_checkBox.isChecked() @@ -71,21 +89,35 @@ class InterpolDialog(QtWidgets.QDialog, Ui_Dialog): x_src = (start, stop, step, loggy) else: - x_src = (self.set_combobox.currentData(QtCore.Qt.UserRole),) + self._src_id = self.set_combobox.currentData(QtCore.Qt.ItemDataRole.UserRole) + x_src = (self._src_id,) - dest_graph = self.dest_combobox.currentData(QtCore.Qt.UserRole) + self._dest_graph = self.dest_combobox.currentData(QtCore.Qt.ItemDataRole.UserRole) use_data = [] for i in range(self.listWidget.count()): item = self.listWidget.item(i) - if item.checkState() == QtCore.Qt.Checked: - use_data.append(item.data(QtCore.Qt.UserRole)) + if item.checkState() == QtCore.Qt.CheckState.Checked: + use_data.append(item.data(QtCore.Qt.ItemDataRole.UserRole)) - self.new_data.emit(use_data, mode, xlog, ylog, x_src, dest_graph) + self.new_data.emit(use_data, mode, xlog, ylog, x_src, self._dest_graph) return True - def accept(self): - success = self.collect_parameter() - if success: - super().accept() + def _save_state(self): + self._src_id = self.set_combobox.currentData(QtCore.Qt.ItemDataRole.UserRole) + self._dest_graph = self.dest_combobox.currentData(QtCore.Qt.ItemDataRole.UserRole) + + @QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonBox_clicked') + def check_next_actions(self, bttn: QtWidgets.QAbstractButton): + role = self.buttonBox.buttonRole(bttn) + self._save_state() + + if role == self.buttonBox.ButtonRole.RejectRole: + self.close() + else: + success = self.collect_parameter() + + if success and role == self.buttonBox.ButtonRole.AcceptRole: + self.close() + diff --git a/src/nmreval/data/points.py b/src/nmreval/data/points.py index 8332084..1f891ae 100644 --- a/src/nmreval/data/points.py +++ b/src/nmreval/data/points.py @@ -540,26 +540,37 @@ class Points: return self - def cut(self, low_lim: float = None, high_lim: float = None): + def cut(self, x_low: float = None, x_high: float = None, y_low: float = None, y_high: float = None): """ Cut Args: - low_lim: - high_lim: + x_low: Lower limit + x_high: Upper limit for x values + y_low: Lower limit + y_high: Upper limit for x valuew Returns: """ - if low_lim is None and high_lim is None: + + if x_low is None and x_high is None and y_low is None and y_high is None: return self - if low_lim is None: - low_lim = np.min(self._x) + if x_low is None: + x_low = np.min(self._x)-1 - if high_lim is None: - high_lim = np.max(self._x) + if x_high is None: + x_high = np.max(self._x)+1 - _mask = np.ma.masked_inside(self._x, low_lim, high_lim).mask + if y_low is None: + y_low = np.min(self._y.real)-1 + + if y_high is None: + y_high = np.max(self._y.real)+1 + + x_mask = (self._x >= x_low) & (self._x <= x_high) + y_mask = (self._y.real >= y_low) & (self._y.real <= y_high) + _mask = x_mask & y_mask self._x = self._x[_mask] self._y = self._y[_mask] diff --git a/src/nmreval/models/basic.py b/src/nmreval/models/basic.py index 411e218..ce4308d 100644 --- a/src/nmreval/models/basic.py +++ b/src/nmreval/models/basic.py @@ -191,6 +191,18 @@ class PowerLawCross: return ret_val +class Sinc: + type = 'Basic' + name = 'Sinc' + equation = 'C * sinc((x-x_{0})/w)' + params = ['C', 'x_{0}', 'w'] + + @staticmethod + def func(x, c: float, x0: float, w: float): + # numpy sinc is defined as sin(pi*x)/(pi*x) + return c * np.sinc(((x-x0)/w)/np.pi) + + class Sine: """ Wavy sine function diff --git a/src/resources/_ui/basewindow.ui b/src/resources/_ui/basewindow.ui index 01987b8..85c1f68 100644 --- a/src/resources/_ui/basewindow.ui +++ b/src/resources/_ui/basewindow.ui @@ -172,6 +172,14 @@ &Data + + + Cut to visible range + + + + + @@ -181,7 +189,7 @@ - + @@ -862,11 +870,6 @@ Integration... - - - Cut to visible range - - Move sets... @@ -1030,6 +1033,22 @@ Exclude region + + + x axis + + + Remove data points outside visible x range. + + + + + y axis + + + Remove data points outside visible y range. Uses real part of points. + + diff --git a/src/resources/_ui/interpol_dialog.ui b/src/resources/_ui/interpol_dialog.ui index 28fa858..865f70b 100644 --- a/src/resources/_ui/interpol_dialog.ui +++ b/src/resources/_ui/interpol_dialog.ui @@ -119,7 +119,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -300,38 +300,5 @@ dest_combobox - - - buttonBox - accepted() - Dialog - accept() - - - 251 - 490 - - - 157 - 274 - - - - - buttonBox - rejected() - Dialog - reject() - - - 319 - 490 - - - 286 - 274 - - - - +