From 531eac22b72fe6e68e0d9526426891124601c252 Mon Sep 17 00:00:00 2001
From: dominik
Masked rows are shown in green
")) self.mask_button.setText(_translate("MaskDialog", "Hide/Show selected")) + self.unmaskbutton.setText(_translate("MaskDialog", "Show all")) diff --git a/nmreval/gui_qt/data/valueeditwidget.py b/nmreval/gui_qt/data/valueeditwidget.py index f79e32c..ae13ba9 100644 --- a/nmreval/gui_qt/data/valueeditwidget.py +++ b/nmreval/gui_qt/data/valueeditwidget.py @@ -1,4 +1,6 @@ -from typing import Union, List +from __future__ import annotations + +from typing import Any, List import numpy as np @@ -13,6 +15,7 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): itemDeleted = QtCore.pyqtSignal(str, list) itemAdded = QtCore.pyqtSignal(str) values_selected = QtCore.pyqtSignal(str, list, list) + split_signal = QtCore.pyqtSignal(str, int) def __init__(self, parent=None): super().__init__(parent=parent) @@ -34,54 +37,54 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tableView.customContextMenuRequested.connect(self.ctx) - self.comboBox.currentIndexChanged.connect(self._populate_sets) - self.comboBox_2.currentIndexChanged.connect(self._populate_table) + self.graph_combobox.currentIndexChanged.connect(self._populate_sets) + self.set_combobox.currentIndexChanged.connect(self._populate_table) def __call__(self, items: dict): self.items = items - self.comboBox.blockSignals(True) - self.comboBox.clear() + self.graph_combobox.blockSignals(True) + self.graph_combobox.clear() for k, v in items.items(): - self.comboBox.addItem(k[1], userData=k[0]) - self.comboBox.blockSignals(False) + self.graph_combobox.addItem(k[1], userData=k[0]) + self.graph_combobox.blockSignals(False) - idx = self.comboBox.findData(self.graph_shown) + idx = self.graph_combobox.findData(self.graph_shown) if idx == -1: idx = 0 - self.comboBox.setCurrentIndex(idx) - self.comboBox.currentIndexChanged.emit(idx) + self.graph_combobox.setCurrentIndex(idx) + self.graph_combobox.currentIndexChanged.emit(idx) return self @QtCore.pyqtSlot(int) def _populate_sets(self, idx: int): if idx == -1: - self.comboBox.setCurrentIndex(0) + self.graph_combobox.setCurrentIndex(0) return - self.comboBox_2.blockSignals(True) - self.comboBox_2.clear() - self.graph_shown = self.comboBox.currentData() + self.set_combobox.blockSignals(True) + self.set_combobox.clear() + self.graph_shown = self.graph_combobox.currentData() if self.items: - for sid, name in self.items[(self.comboBox.currentData(), self.comboBox.currentText())]: - self.comboBox_2.addItem(name, userData=sid) - self.comboBox_2.blockSignals(False) + for sid, name in self.items[(self.graph_combobox.currentData(), self.graph_combobox.currentText())]: + self.set_combobox.addItem(name, userData=sid) + self.set_combobox.blockSignals(False) - sidx = self.comboBox_2.findData(self.shown_set) + sidx = self.set_combobox.findData(self.shown_set) if sidx == -1: sidx = 0 - self.comboBox_2.setCurrentIndex(sidx) - self.comboBox_2.currentIndexChanged.emit(sidx) + self.set_combobox.setCurrentIndex(sidx) + self.set_combobox.currentIndexChanged.emit(sidx) @QtCore.pyqtSlot(int) def _populate_table(self, idx): self.selection_model.clearSelection() - self.shown_set = self.comboBox_2.itemData(idx) - self.requestData.emit(self.comboBox_2.itemData(idx)) + self.shown_set = self.set_combobox.itemData(idx) + self.requestData.emit(self.set_combobox.itemData(idx)) def set_data(self, data: list, mask: np.ndarray): self.selection_model.clearSelection() @@ -97,9 +100,8 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): menu.addSeparator() hide_action = menu.addAction('Hide/Show') - menu.addSeparator() del_action = menu.addAction('Delete') - menu.addSeparator() + split_action = menu.addAction('Split after selection') cpc_action = menu.addAction('Copy to clipboard') action = menu.exec(self.tableView.viewport().mapToGlobal(pos)) @@ -109,6 +111,8 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): self.delete_item() elif action == cpc_action: self.copy_selection() + elif action == split_action: + self.split() def keyPressEvent(self, evt): if evt.matches(QtGui.QKeySequence.Copy): @@ -128,16 +132,21 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): return table + @QtCore.pyqtSlot(name='on_split_button_clicked') + def split(self): + self.split_signal.emit(self.set_combobox.currentData(), + self.selection_model.selectedRows()[-1].row()+1) + @QtCore.pyqtSlot(name='on_mask_button_clicked') def mask_row(self): for r in self.selection_model.selectedRows(): self.model.setData(r, not self.model.data(r, ValueModel.maskRole), ValueModel.maskRole) - self.maskSignal.emit(self.comboBox_2.currentData(), self.model.mask) + self.maskSignal.emit(self.set_combobox.currentData(), self.model.mask) @QtCore.pyqtSlot(name='on_unmaskbutton_clicked') def unmask(self): self.model.unmask() - self.maskSignal.emit(self.comboBox_2.currentData(), self.model.mask) + self.maskSignal.emit(self.set_combobox.currentData(), self.model.mask) @QtCore.pyqtSlot(name='on_delete_button_clicked') def delete_item(self): @@ -147,7 +156,7 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): success = self.model.removeRow(i) if success: - self.itemDeleted.emit(self.comboBox_2.currentData(), idx) + self.itemDeleted.emit(self.set_combobox.currentData(), idx) self.spinBox.setMaximum(self.spinBox.maximum()-len(idx)) @QtCore.pyqtSlot(name='on_add_button_clicked') @@ -155,18 +164,18 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): success = self.model.addRows() if success: self.spinBox.setMaximum(self.spinBox.maximum()+1) - self.itemAdded.emit(self.comboBox_2.currentData()) + self.itemAdded.emit(self.set_combobox.currentData()) @QtCore.pyqtSlot(int, int, str) def update_items(self, col, row, val): - sid = self.comboBox_2.currentData() + sid = self.set_combobox.currentData() new_value = complex(val) new_value = new_value.real if new_value.imag == 0 else new_value self.itemChanged.emit(sid, (col, row), new_value) @QtCore.pyqtSlot(QtCore.QItemSelection, QtCore.QItemSelection) - def show_position(self, items_selected, items_deselected): + def show_position(self, *_): xvals = [] yvals = [] for idx in self.selection_model.selectedRows(): @@ -220,7 +229,7 @@ class ValueModel(QtCore.QAbstractTableModel): self.endResetModel() self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [QtCore.Qt.DisplayRole]) - def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole) -> object: + def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole) -> Any: if not idx.isValid(): return @@ -252,7 +261,7 @@ class ValueModel(QtCore.QAbstractTableModel): else: return - def setData(self, idx: QtCore.QModelIndex, value: Union[str, bool], role=QtCore.Qt.DisplayRole) -> object: + def setData(self, idx: QtCore.QModelIndex, value: str | bool, role=QtCore.Qt.DisplayRole) -> Any: col, row = idx.column(), idx.row() if role == ValueModel.maskRole: @@ -280,7 +289,7 @@ class ValueModel(QtCore.QAbstractTableModel): else: return False - def headerData(self, section: int, orientation, role=QtCore.Qt.DisplayRole) -> object: + def headerData(self, section: int, orientation, role=QtCore.Qt.DisplayRole) -> Any: if role == QtCore.Qt.DisplayRole: if orientation == QtCore.Qt.Horizontal: return self.headers[section] @@ -303,7 +312,7 @@ class ValueModel(QtCore.QAbstractTableModel): self.rows_loaded += to_be_loaded self.endInsertRows() - def flags(self, idx: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags: + def flags(self, idx: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag: return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemIsEditable def removeRows(self, pos: int, rows: int, parent=None, *args, **kwargs) -> bool: diff --git a/nmreval/gui_qt/main/mainwindow.py b/nmreval/gui_qt/main/mainwindow.py index e5c6600..5eb8e42 100644 --- a/nmreval/gui_qt/main/mainwindow.py +++ b/nmreval/gui_qt/main/mainwindow.py @@ -193,6 +193,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.valuewidget.itemAdded.connect(self.management.append) self.valuewidget.maskSignal.connect(self.management.mask_value) self.valuewidget.values_selected.connect(self.plot_selected_values) + self.valuewidget.split_signal.connect(self.management.split_set) self.actionMaximize.triggered.connect(lambda: self.current_graph_widget.showMaximized()) self.actionNext_window.triggered.connect(lambda: self.area.activateNextSubWindow()) @@ -308,6 +309,9 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): if graph == self.fit_dialog.connected_figure: self.fit_dialog.load(self.management.graphs.active(graph)) + if self.valuewidget.isVisible(): + self.valuewidget(self.management.graphs.tree()) + @QtCore.pyqtSlot(name='on_actionDelete_window_triggered') def delete_windows(self): self.management.delete_sets() @@ -722,10 +726,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): for s in sets: self.datawidget.tree.move_sets(s, dest, src) - @QtCore.pyqtSlot(name='on_action_idx_cut_triggered') - def cut_with_idx(self): - print('schnippschnapp') - @QtCore.pyqtSlot(str) def show_data_values(self, sid: str): if sid == '': diff --git a/nmreval/gui_qt/main/management.py b/nmreval/gui_qt/main/management.py index 2b42314..76eced2 100644 --- a/nmreval/gui_qt/main/management.py +++ b/nmreval/gui_qt/main/management.py @@ -1029,12 +1029,25 @@ class UpperManagement(QtCore.QObject): self.newData.emit([self.add(pts)], opts['graph']) self.sender().update_graphs(self.graphs.list()) + @QtCore.pyqtSlot(str, list) def mask_value(self, idx: str, m: list): self.data[idx].mask = m + @QtCore.pyqtSlot(str, list) def remove_values(self, idx: str, m: list): self.data[idx].remove(m) + @QtCore.pyqtSlot(str, int) + def split_set(self, idx: str, row: int): + selected_data = self.data[idx] + popular_front_of_judea = selected_data.copy(full=True) + + selected_data.remove(slice(row, None)) + popular_front_of_judea.remove(slice(None, row)) + + new_id = self.add(popular_front_of_judea) + self.newData.emit([new_id], selected_data.graph) + def set_values(self, idx: str, pos: tuple, value): self.data[idx].setvalues(pos, value) diff --git a/nmreval/io/nmrreader.py b/nmreval/io/nmrreader.py index 0205762..24bd2fe 100644 --- a/nmreval/io/nmrreader.py +++ b/nmreval/io/nmrreader.py @@ -5,7 +5,7 @@ from typing import Tuple, Union from ..data.nmr import FID, Spectrum from ..data.points import Points from ..fit.result import FitResult, FitResultCreator -from .read_old_nmr import _read_file_v1 +from .read_old_nmr import HAS_BSDDB3, _read_file_v1 from ..lib.colors import Colors from ..lib.lines import LineStyle from ..lib.symbols import SymbolStyle @@ -61,6 +61,9 @@ class NMRReader: return datalist, states['graphs'] def _make_old(self, fname: str) -> dict: + if not HAS_BSDDB3: + raise IOError('Legacy .nmr cannot be read without bsddb3') + datadic = _read_file_v1(fname) datalist = OrderedDict() diff --git a/nmreval/io/read_old_nmr.py b/nmreval/io/read_old_nmr.py index 27dab68..fd8f1e4 100644 --- a/nmreval/io/read_old_nmr.py +++ b/nmreval/io/read_old_nmr.py @@ -1,3 +1,4 @@ +import warnings from copyreg import _inverted_registry, _extension_cache import sys from sys import maxsize @@ -8,7 +9,12 @@ import _compat_pickle from pickle import * from pickle import _Unframer, bytes_types, _Stop, _getattribute -import bsddb3 +try: + import bsddb3 + HAS_BSDDB3 = True +except ImportError: + warnings.warn('bsdbb3 is not installed, reading legacy .nmr files is not possible.') + HAS_BSDDB3 = False """ @@ -24,7 +30,7 @@ returns the dicts instead. There are also pickled numpy arrays but they are unpr copied the Unpickler completely. It seems that bsddb3 will be changed to berkeleydb in the future (https://www.jcea.es/programacion/pybsddb.htm) -so this might break eventually. +so this will break in the future. """ diff --git a/nmreval/nmr/relaxation.py b/nmreval/nmr/relaxation.py index 042c9d9..fba9ecb 100755 --- a/nmreval/nmr/relaxation.py +++ b/nmreval/nmr/relaxation.py @@ -358,7 +358,8 @@ class Relaxation: tau (array-like): Correlation times in s *specdens_args (optional): Additional parameter for spectral density. If given this will replace previously set arguments during calculation - inverse (bool): Function returs relaxation time if True else relaxation rate. Default is `True` + inverse (bool): Function returs relaxation time if True else relaxation rate. + Default is `True` prefactor (float, optional): If given it is used as prefactor `C` instead of previously set coupling prefactor. @@ -382,9 +383,17 @@ class Relaxation: else: return rate - def t2_dipolar(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, - inverse: bool = True, prefactor: float = None, omega_coup: ArrayLike = None, - gamma_coup: str = None, gamma_obs: str = None) -> np.ndarray | float: + def t2_dipolar( + self, + omega: ArrayLike, + tau: ArrayLike, + *specdens_args: Any, + inverse: bool = True, + prefactor: float = None, + omega_coup: ArrayLike = None, + gamma_coup: str = None, + gamma_obs: str = None, + ) -> np.ndarray | float: r"""Calculate T2 under heteronuclear dipolar coupling. .. math:: @@ -439,8 +448,14 @@ class Relaxation: else: return rate - def t2_csa(self, omega: ArrayLike, tau: ArrayLike, *specdens_args: Any, - inverse: bool = True, prefactor: float = None) -> np.ndarray | float: + def t2_csa( + self, + omega: ArrayLike, + tau: ArrayLike, + *specdens_args: Any, + inverse: bool = True, + prefactor: float = None, + ) -> np.ndarray | float: r"""Calculate T1 under chemical shift anisotropy. .. math:: @@ -502,9 +517,16 @@ class RelaxationEvaluation(Relaxation): self.y = t1[sortidx] self.calculate_t1_min() - def get_increase(self, height: float = None, idx: int = 0, mode: str = None, omega: float = None, - dist_parameter: tuple | list = None, prefactor: tuple | list | float = None, - coupling_kwargs: dict = None): + def get_increase( + self, + height: float = None, + idx: int = 0, + mode: str = None, + omega: float = None, + dist_parameter: tuple | list = None, + prefactor: tuple | list | float = None, + coupling_kwargs: dict = None, + ) -> None: """ Determine a single parameter from a T1 minimum. It replaces the previously set value. @@ -639,8 +661,12 @@ class RelaxationEvaluation(Relaxation): return stretching, minimon - def calculate_t1_min(self, interpolate: int = None, trange: Tuple[float, float] = None, use_log: bool = False) -> \ - Tuple[Tuple[float, float], Tuple[np.ndarray, np.ndarray] | None]: + def calculate_t1_min( + self, + interpolate: int = None, + trange: Tuple[float, float] = None, + use_log: bool = False, + ) -> Tuple[Tuple[float, float], Tuple[np.ndarray, np.ndarray] | None]: """ Determine a minimum position for given T1 data @@ -707,9 +733,16 @@ class RelaxationEvaluation(Relaxation): return t1_min, parabola - def correlation_from_t1(self, mode: str = 'raw', interpolate: bool = False, omega: float = None, - dist_parameter: list | tuple = None, prefactor: float = None, - coupling_param: list = None, coupling_kwargs: dict = None) -> Tuple[np.ndarray, dict]: + def correlation_from_t1( + self, + mode: str = 'raw', + interpolate: bool = False, + omega: float = None, + dist_parameter: list | tuple = None, + prefactor: float = None, + coupling_param: list = None, + coupling_kwargs: dict = None, + ) -> Tuple[np.ndarray, dict]: """ Calculate correlation times from set T1 data. Optional arguments overwrite previousliy set parameter. @@ -721,10 +754,11 @@ class RelaxationEvaluation(Relaxation): omega (float, optional): Larmor frequency (in 1/s) dist_parameter (list, optional): List of parameter of spectral density prefactor (float, optional): Prefactor of T1 calculation, will - coupling_param (list, optional): Parameter for coupling constant, ignored if `prefactor`is given. - coupling_kwargs (dict, optional): Keyword arguments for coupling constant, ignored if `prefactor`is given. + coupling_param (list, optional): Parameter for coupling constant, ignored if `prefactor` is given. + coupling_kwargs (dict, optional): Keyword arguments for coupling constant, ignored if `prefactor` is given. Returns: + Array of temperature and correlation times and a dictionary of used parameter during calculation. """ if self.x is None: @@ -820,13 +854,3 @@ class RelaxationEvaluation(Relaxation): opts['coupling'] = (self.coupling.name, coupling_param, coupling_kwargs) return np.c_[self.x, self.distribution.mean_value(correlation_times, *dist_parameter, mode=mode)], opts - - def _create_minimum_file(self, filename): - x = np.geomspace(0.1, 1, num=10001) - with Path(filename).open('w') as f: - f.write('# broadening\tT1_min/min_Debye\n') - for i, a in enumerate(np.geomspace(0.1, 10, num=10000)): - alpha = a - t1_i = self.t1_bpp(1, x, alpha) - f.write(f'{alpha}\t{min(t1_i) / 0.701667864144}\n') - f.flush() diff --git a/resources/_ui/basewindow.ui b/resources/_ui/basewindow.ui index 3f79593..7d72d0e 100644 --- a/resources/_ui/basewindow.ui +++ b/resources/_ui/basewindow.ui @@ -21,7 +21,7 @@