1
0
forked from IPKM/nmreval

Merge branch 'main' into fit_constraints

# Conflicts:
#	src/gui_qt/fit/fit_forms.py
#	src/gui_qt/main/management.py
#	src/nmreval/fit/minimizer.py
This commit is contained in:
Dominik Demuth 2023-09-11 18:18:30 +02:00
commit 869901596b
46 changed files with 428 additions and 216 deletions

View File

@ -32,7 +32,7 @@ AppDir:
arch: amd64
allow_unauthenticated: true
sources:
- sourceline: 'deb [arch=amd64] http://mirror.infra.pkm/ bullseye main contrib non-free'
- sourceline: 'deb [arch=amd64] http://ftp.uni-mainz.de/debian bullseye main contrib non-free'
include:
# for /usr/bin/env
@ -42,7 +42,7 @@ AppDir:
# - hicolor-icon-theme
- libatlas3-base
- gnuplot-nox
- python3.9-minimal
- python3-minimal
- python3-numpy
- python3-scipy
- python3-bsddb3

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '_ui/apod_dialog.ui'
# Form implementation generated from reading ui file 'src/resources/_ui/apod_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
@ -23,7 +24,7 @@ class Ui_ApodEdit(object):
self.gridLayout.setContentsMargins(3, 3, 3, 3)
self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout")
self.graphicsView = PlotWidget(ApodEdit)
self.graphicsView = NMRPlotWidget(ApodEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -31,7 +32,7 @@ class Ui_ApodEdit(object):
self.graphicsView.setSizePolicy(sizePolicy)
self.graphicsView.setObjectName("graphicsView")
self.gridLayout.addWidget(self.graphicsView, 2, 0, 1, 1)
self.graphicsView_2 = PlotWidget(ApodEdit)
self.graphicsView_2 = NMRPlotWidget(ApodEdit)
self.graphicsView_2.setObjectName("graphicsView_2")
self.gridLayout.addWidget(self.graphicsView_2, 2, 1, 1, 1)
self.apodcombobox = QtWidgets.QComboBox(ApodEdit)
@ -63,12 +64,12 @@ class Ui_ApodEdit(object):
self.gridLayout.addWidget(self.eqn_label, 0, 1, 1, 1)
self.retranslateUi(ApodEdit)
self.buttonBox.accepted.connect(ApodEdit.accept)
self.buttonBox.rejected.connect(ApodEdit.close)
self.buttonBox.accepted.connect(ApodEdit.accept) # type: ignore
self.buttonBox.rejected.connect(ApodEdit.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(ApodEdit)
def retranslateUi(self, ApodEdit):
_translate = QtCore.QCoreApplication.translate
ApodEdit.setWindowTitle(_translate("ApodEdit", "Apodization"))
self.eqn_label.setText(_translate("ApodEdit", "TextLabel"))
from pyqtgraph import PlotWidget
from ..lib.graph_items import NMRPlotWidget

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '_ui/baseline_dialog.ui'
# Form implementation generated from reading ui file 'src/resources/_ui/baseline_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
@ -44,7 +45,7 @@ class Ui_SignalEdit(object):
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 3)
self.graphicsView = PlotWidget(SignalEdit)
self.graphicsView = NMRPlotWidget(SignalEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -54,11 +55,11 @@ class Ui_SignalEdit(object):
self.gridLayout.addWidget(self.graphicsView, 0, 2, 1, 1)
self.retranslateUi(SignalEdit)
self.buttonBox.accepted.connect(SignalEdit.accept)
self.buttonBox.rejected.connect(SignalEdit.close)
self.buttonBox.accepted.connect(SignalEdit.accept) # type: ignore
self.buttonBox.rejected.connect(SignalEdit.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(SignalEdit)
def retranslateUi(self, SignalEdit):
_translate = QtCore.QCoreApplication.translate
SignalEdit.setWindowTitle(_translate("SignalEdit", "Dialog"))
from pyqtgraph import PlotWidget
from ..lib.graph_items import NMRPlotWidget

View File

@ -166,7 +166,7 @@ class Ui_Dialog(object):
self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.raw_graph = PlotWidget(self.layoutWidget)
self.raw_graph = NMRPlotWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -175,7 +175,7 @@ class Ui_Dialog(object):
self.raw_graph.setMinimumSize(QtCore.QSize(300, 200))
self.raw_graph.setObjectName("raw_graph")
self.gridLayout.addWidget(self.raw_graph, 0, 0, 1, 1)
self.calib_graph = PlotWidget(self.layoutWidget)
self.calib_graph = NMRPlotWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -184,7 +184,7 @@ class Ui_Dialog(object):
self.calib_graph.setMinimumSize(QtCore.QSize(300, 200))
self.calib_graph.setObjectName("calib_graph")
self.gridLayout.addWidget(self.calib_graph, 1, 0, 1, 1)
self.baseline_graph = PlotWidget(self.layoutWidget)
self.baseline_graph = NMRPlotWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -193,7 +193,7 @@ class Ui_Dialog(object):
self.baseline_graph.setMinimumSize(QtCore.QSize(300, 200))
self.baseline_graph.setObjectName("baseline_graph")
self.gridLayout.addWidget(self.baseline_graph, 0, 1, 1, 1)
self.end_graph = PlotWidget(self.layoutWidget)
self.end_graph = NMRPlotWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -228,4 +228,4 @@ class Ui_Dialog(object):
self.cp_checkBox.setText(_translate("Dialog", "Use reference to convert to heat capacity"))
self.ref_add_pushButton.setText(_translate("Dialog", "Add reference"))
self.ref_remove_pushButton.setText(_translate("Dialog", "Remove reference"))
from pyqtgraph import PlotWidget
from ..lib.graph_items import NMRPlotWidget

View File

@ -38,7 +38,6 @@ class Ui_FitDialog(object):
self.weight_combobox.addItem("")
self.weight_combobox.addItem("")
self.weight_combobox.addItem("")
self.weight_combobox.addItem("")
self.gridLayout_2.addWidget(self.weight_combobox, 6, 1, 1, 1)
self.newmodel_button = QtWidgets.QPushButton(self.scrollAreaWidgetContents_2)
self.newmodel_button.setEnabled(False)
@ -144,7 +143,6 @@ class Ui_FitDialog(object):
self.weight_combobox.setItemText(1, _translate("FitDialog", "y"))
self.weight_combobox.setItemText(2, _translate("FitDialog", ""))
self.weight_combobox.setItemText(3, _translate("FitDialog", "Δy"))
self.weight_combobox.setItemText(4, _translate("FitDialog", "log(y)"))
self.newmodel_button.setText(_translate("FitDialog", "New model"))
self.deletemodel_button.setText(_translate("FitDialog", "Delete model"))
self.label_3.setText(_translate("FitDialog", "Weight"))

View File

@ -210,7 +210,7 @@ class Ui_GraphWindow(object):
self.checkBox.setChecked(True)
self.checkBox.setObjectName("checkBox")
self.gridLayout.addWidget(self.checkBox, 0, 1, 1, 1)
self.graphic = PlotWidget(GraphWindow)
self.graphic = NMRPlotWidget(GraphWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -274,5 +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.graph_items import NMRPlotWidget
from ..lib.listwidget import QListWidgetSelect
from pyqtgraph import PlotWidget

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/phase_corr_dialog.ui'
# Form implementation generated from reading ui file 'src/resources/_ui/phase_corr_dialog.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.
@ -24,7 +24,7 @@ class Ui_SignalEdit(object):
self.gridLayout.setContentsMargins(6, 6, 6, 6)
self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout")
self.graphicsView = PlotWidget(SignalEdit)
self.graphicsView = NMRPlotWidget(SignalEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -83,8 +83,8 @@ class Ui_SignalEdit(object):
self.gridLayout.addItem(spacerItem1, 1, 5, 1, 1)
self.retranslateUi(SignalEdit)
self.buttonBox.accepted.connect(SignalEdit.accept)
self.buttonBox.rejected.connect(SignalEdit.close)
self.buttonBox.accepted.connect(SignalEdit.accept) # type: ignore
self.buttonBox.rejected.connect(SignalEdit.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(SignalEdit)
def retranslateUi(self, SignalEdit):
@ -94,4 +94,4 @@ class Ui_SignalEdit(object):
self.label_8.setText(_translate("SignalEdit", "Pivot"))
self.label_6.setText(_translate("SignalEdit", "Phase 1"))
self.label.setText(_translate("SignalEdit", "Phase 0"))
from pyqtgraph import PlotWidget
from ..lib.graph_items import NMRPlotWidget

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/shift_scale_dialog.ui'
# Form implementation generated from reading ui file 'src/resources/_ui/shift_scale_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
@ -162,7 +163,7 @@ class Ui_shift_dialog(object):
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalFrame_2)
self.verticalLayout_2.setSpacing(3)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.graphicsView = PlotWidget(self.verticalFrame_2)
self.graphicsView = NMRPlotWidget(self.verticalFrame_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -267,8 +268,8 @@ class Ui_shift_dialog(object):
self.retranslateUi(shift_dialog)
self.tabWidget.setCurrentIndex(0)
self.buttonBox.accepted.connect(shift_dialog.accept)
self.buttonBox.rejected.connect(shift_dialog.reject)
self.buttonBox.accepted.connect(shift_dialog.accept) # type: ignore
self.buttonBox.rejected.connect(shift_dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(shift_dialog)
shift_dialog.setTabOrder(self.tabWidget, self.shift_table)
shift_dialog.setTabOrder(self.shift_table, self.x_shift_spinbox)
@ -310,5 +311,5 @@ class Ui_shift_dialog(object):
self.overwrite_checkbox.setText(_translate("shift_dialog", "Overwrite data"))
self.data_newgraph.setText(_translate("shift_dialog", "New graph"))
self.values_newgraph.setText(_translate("shift_dialog", "New graph"))
from ..lib.graph_items import NMRPlotWidget
from ..lib.spinboxes import SciSpinBox
from pyqtgraph import PlotWidget

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/t1dialog.ui'
# Form implementation generated from reading ui file 'src/resources/_ui/t1dialog.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
@ -160,6 +161,7 @@ class Ui_t1dialog(object):
self.tau_combox.addItem("")
self.gridLayout_4.addWidget(self.tau_combox, 1, 0, 1, 2)
self.checkBox_interpol = QtWidgets.QCheckBox(self.groupBox_3)
self.checkBox_interpol.setEnabled(False)
self.checkBox_interpol.setObjectName("checkBox_interpol")
self.gridLayout_4.addWidget(self.checkBox_interpol, 2, 0, 1, 2)
self.graph_checkbox = QtWidgets.QCheckBox(self.groupBox_3)

View File

@ -59,7 +59,7 @@ class Ui_DSCEvalDialog(object):
self.tg_tree.headerItem().setText(0, "1")
self.tg_tree.header().setVisible(False)
self.gridLayout_2.addWidget(self.tg_tree, 2, 0, 1, 1)
self.dsc_plot = PlotWidget(self.page)
self.dsc_plot = NMRPlotWidget(self.page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -89,10 +89,10 @@ class Ui_DSCEvalDialog(object):
self.label_4 = QtWidgets.QLabel(self.page_2)
self.label_4.setObjectName("label_4")
self.gridLayout_3.addWidget(self.label_4, 2, 0, 1, 1)
self.tghodge_graph = PlotWidget(self.page_2)
self.tghodge_graph = NMRPlotWidget(self.page_2)
self.tghodge_graph.setObjectName("tghodge_graph")
self.gridLayout_3.addWidget(self.tghodge_graph, 1, 0, 1, 1)
self.tau_plot = PlotWidget(self.page_2)
self.tau_plot = NMRPlotWidget(self.page_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@ -155,7 +155,7 @@ class Ui_DSCEvalDialog(object):
self.page_3.setObjectName("page_3")
self.gridLayout_6 = QtWidgets.QGridLayout(self.page_3)
self.gridLayout_6.setObjectName("gridLayout_6")
self.tnmh_graphics = PlotWidget(self.page_3)
self.tnmh_graphics = NMRPlotWidget(self.page_3)
self.tnmh_graphics.setObjectName("tnmh_graphics")
self.gridLayout_6.addWidget(self.tnmh_graphics, 1, 0, 1, 2)
self.tnmh_tree = QtWidgets.QTreeWidget(self.page_3)
@ -249,5 +249,5 @@ class Ui_DSCEvalDialog(object):
self.back_button.setText(_translate("DSCEvalDialog", "Back"))
self.next_button.setText(_translate("DSCEvalDialog", "Next"))
self.close_button.setText(_translate("DSCEvalDialog", "Close"))
from ..lib.graph_items import NMRPlotWidget
from ..lib.listwidget import QListWidgetSelect
from pyqtgraph import PlotWidget

View File

@ -463,6 +463,16 @@ class ExperimentContainer(QtCore.QObject):
return offset
@plot_update
def shift_scale(self, shift_factor: tuple[float, float], scaling_factor: tuple[float, float]):
scale_x, scale_y = scaling_factor
shift_x, shift_y = shift_factor
self.data.x = self.data.x * scale_x + shift_x
self.data.y = self.data.y * scale_y + shift_y
self.data.y_err = self.data.y_err * scale_y
self.update({'shift': scaling_factor, 'scale': shift_factor})
def get_namespace(self, i: int = None, j: int = None) -> dict:
if (i is None) and (j is None):
prefix = ''

View File

@ -1,7 +1,7 @@
import numpy as np
from itertools import cycle
from pyqtgraph import mkColor, mkPen
from pyqtgraph import mkColor, mkPen, mkBrush
from nmreval.lib.colors import Tab10
@ -42,11 +42,17 @@ class QShift(QtWidgets.QDialog, Ui_shift_dialog):
def add_item(self, idx, name, x, y):
color = mkColor(next(self._colors).rgb())
if np.iscomplexobj(y):
pl = [PlotItem(x=x, y=y.real, name=name, pen=mkPen(color=color)),
PlotItem(x=x, y=y.imag, name=name, pen=mkPen(color=color))]
if len(y) == 1:
sym_kwds = {'symbol': 'o', 'symbolBrush': mkBrush(color=color), 'symbolPen': mkPen(color=color)}
else:
pl = [PlotItem(x=x, y=y, name=name, pen=mkPen(color=color))]
sym_kwds = {'symbol': None, 'symbolBrush': mkBrush(color=color), 'symbolPen': mkPen(color=color)}
if np.iscomplexobj(y):
pl = [PlotItem(x=x, y=y.real, name=name, pen=mkPen(color=color), **sym_kwds),
PlotItem(x=x, y=y.imag, name=name, pen=mkPen(color=color), **sym_kwds)]
else:
pl = [PlotItem(x=x, y=y, name=name, pen=mkPen(color=color), **sym_kwds)]
self.data[idx] = (pl, x, y)

View File

@ -6,12 +6,14 @@ from ..Qt import QtCore, QtWidgets, QtGui
from .._py.fitmodelwidget import Ui_FitParameter
from .._py.save_fitmodel_dialog import Ui_SaveDialog
from ..lib.iconloading import get_icon
from ..lib.tables import TableWidget
class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter):
value_requested = QtCore.pyqtSignal(object)
value_changed = QtCore.pyqtSignal(str)
state_changed = QtCore.pyqtSignal()
replace_single_value = QtCore.pyqtSignal(object)
def __init__(self, label: str = 'Fitparameter', parent=None, fixed: bool = False):
super().__init__(parent)
@ -30,6 +32,7 @@ class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter):
self.global_checkbox.stateChanged.connect(lambda: self.state_changed.emit())
self.parameter_line.editingFinished.connect(self.update_parameter)
self.parameter_line.values_requested.connect(lambda: self.value_requested.emit(self))
self.parameter_line.replace_single_values.connect(lambda: self.replace_single_value.emit(None))
self.parameter_line.editingFinished.connect(lambda: self.value_changed.emit(self.parameter_line.text()))
self.fixed_check.toggled.connect(self.set_fixed)
@ -311,7 +314,7 @@ class FitModelTree(QtWidgets.QTreeWidget):
return funcs
class FitTableWidget(QtWidgets.QTableWidget):
class FitTableWidget(TableWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)

View File

@ -78,6 +78,7 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
widgt.state_changed.connect(self.make_global)
widgt.value_requested.connect(self.look_for_value)
widgt.value_changed.connect(self.change_global_parameter)
widgt.replace_single_value.connect(self.delete_single_parameter)
self.global_parameter.append(widgt)
self.scrollwidget.layout().addWidget(widgt)
@ -150,6 +151,13 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
if value is None:
self.change_data(self.comboBox.currentIndex())
def delete_single_parameter(self):
idx = self.global_parameter.index(self.sender())
for i in range(self.comboBox.count()):
set_id = self.comboBox.itemData(i)
self.data_values[set_id][idx] = None
self.change_data(self.comboBox.currentIndex())
def change_single_choice(self, _, value, sender=None):
if sender is None:
sender = self.sender()
@ -298,7 +306,8 @@ 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.label.setToolTip('If 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)

View File

@ -14,7 +14,7 @@ from gui_qt.lib.namespace import QNamespaceWidget
__all__ = ['QUserFitCreator']
validator = QtGui.QRegExpValidator(QtCore.QRegExp('[A-Za-z]\S*'))
validator = QtGui.QRegExpValidator(QtCore.QRegExp('[_A-Za-z][_A-Za-z0-9]*'))
pattern = re.compile(r'def func\(.*\):', flags=re.MULTILINE)
@ -145,6 +145,7 @@ class QUserFitCreator(QtWidgets.QDialog, Ui_Dialog):
self.classCreated.emit()
super().accept()
class KwargsWidget(QtWidgets.QWidget):
Changed = QtCore.pyqtSignal()
@ -209,7 +210,7 @@ class KwargsWidget(QtWidgets.QWidget):
def get_strings(self) -> str:
kwargs = []
if self.use_nuclei.isChecked():
kwargs.append("(r'\gamma', 'nucleus', gamma)")
kwargs.append(r"(r'\gamma', 'nucleus', gamma)")
for i in range(self.choices.count()):
kwargs.append(self.choices.widget(i).get_strings())
@ -300,7 +301,7 @@ class ChoiceWidget(QtWidgets.QWidget):
def get_strings(self) -> str:
opts = []
for i in range(self.table.rowCount()):
name = self.table.item(i, 0).text()
name = self.table.cellWidget(i, 0).text()
val = self._make_value(i)
opts.append(f'{name!r}: {val!r}')

View File

@ -3,6 +3,7 @@ from math import isnan
from pyqtgraph import mkBrush, mkPen
from nmreval.utils.text import convert
from ..lib.graph_items import logTickValues
from ..lib.utils import RdBuCMap
from ..Qt import QtWidgets, QtGui, QtCore
@ -33,8 +34,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.resid_plot = self.graphicsView.addPlot(row=0, col=0, title='Residual')
for orient in ['top', 'bottom', 'left', 'right']:
self.fit_plot.getAxis(orient).logTickValues = logTickValues
self.resid_plot.getAxis(orient).logTickValues = logTickValues
self.graphicsView.ci.layout.setRowStretchFactor(0, 1)
self.graphicsView.ci.layout.setRowStretchFactor(1, 2)

View File

@ -4,7 +4,7 @@ import itertools
import os
import uuid
from math import isnan
from math import isfinite
from pathlib import Path
import numpy as np
@ -45,7 +45,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
self.id = str(uuid.uuid4())
self.sets = []
self.active = []
self._active = []
self.real_plots = {}
self.imag_plots = {}
@ -75,6 +75,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
self.scene.contextMenu[0].disconnect()
self.scene.contextMenu[0].triggered.connect(self.export_dialog)
self.bwbutton.toggled.connect(self.change_background)
def _init_gui(self):
self.setWindowTitle('Graph ' + str(next(QGraphWindow.counter)))
@ -114,11 +116,11 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return iter(self.active)
def __len__(self):
return len(self.active)
return len(self._active)
def curves(self) -> tuple:
for set_id in self.sets:
if set_id in self.active:
if set_id in self._active:
if self.real_button.isChecked():
if self.error_plots[set_id] is not None:
yield self.real_plots[set_id], self.error_plots[set_id]
@ -144,7 +146,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
tmp = [np.nan, np.nan]
for j, x in enumerate(r[i]):
try:
tmp[j] = 10**x
tmp[j] = 10**min(x, 199)
except OverflowError:
pass
r[i] = tuple(tmp)
@ -153,6 +155,14 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return tuple(r)
@property
def active(self) -> list:
return [set_id for set_id in self.sets if set_id in self._active]
@active.setter
def active(self, value: list):
self._active = value
def block(self, state: bool):
self._block = state
@ -201,8 +211,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
for plot in [self.real_plots, self.imag_plots, self.error_plots]:
self.graphic.removeItem(plot[n])
if n in self.active:
self.active.remove(n)
if n in self._active:
self._active.remove(n)
# remove from label list
self.listWidget.blockSignals(True)
@ -246,8 +256,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return
for a in idlist:
if a not in self.active:
self.active.append(a)
if a not in self._active:
self._active.append(a)
for (bttn, plot_dic) in [
(self.real_button, self.real_plots),
@ -266,8 +276,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return
for r in idlist:
if r in self.active:
self.active.remove(r)
if r in self._active:
self._active.remove(r)
for plt in [self.real_plots, self.imag_plots, self.error_plots]:
item = plt[r]
@ -289,7 +299,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
else:
func = self.graphic.removeItem
for a in self.active:
for a in self._active:
item = plots[a]
if item is not None:
func(item)
@ -306,12 +316,12 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return
if visible:
for a in self.active:
for a in self._active:
item = self.error_plots[a]
if (item is not None) and (item not in self.graphic.items()):
self.graphic.addItem(item)
else:
for a in self.active:
for a in self._active:
item = self.error_plots[a]
if (item is not None) and (item in self.graphic.items()):
self.graphic.removeItem(item)
@ -423,6 +433,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
def set_logmode(self, xmode: bool = None, ymode: bool = None):
r = self.ranges
self.plotItem.setXRange(*r[0])
self.plotItem.setYRange(*r[1])
if xmode is None:
xmode = self.plotItem.ctrl.logXCheck.isChecked()
else:
@ -439,6 +452,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
item.logmode[0] = self.log[:]
self.plotItem.updateLogMode()
self.set_range(x=r[0], y=r[1])
self.plotItem.enableAutoRange()
@ -481,9 +495,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
with errstate(all='ignore'):
xy = [log10(val) for val in xy]
if isnan(xy[1]):
if not isfinite(xy[1]):
xy = [-1, 1]
elif isnan(xy[0]):
elif not isfinite(xy[0]):
xy[0] = xy[1]-4
func(xy[0], xy[1], padding=0)
@ -669,7 +683,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
'legend': self.legend.isVisible(),
'plots': (self.real_button.isChecked(), self.imag_button.isChecked(), self.error_button.isChecked()),
'children': self.sets,
'active': self.active,
'active': self._active,
}
in_legend = []
@ -760,8 +774,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
if y is not None:
self.plotItem.setLabel('left', y, **{'font-size': '10pt', 'color': self._fgcolor.name()})
@QtCore.pyqtSlot(bool, name='on_bwbutton_toggled')
def change_background(self, _):
temp = self._fgcolor, self._bgcolor
self.set_color(foreground=self._prev_colors[0], background=self._prev_colors[1])
self._prev_colors = temp

View File

@ -4,6 +4,7 @@ from nmreval.lib.lines import LineStyle
from nmreval.lib.symbols import SymbolStyle
from nmreval.data.points import Points
from nmreval.io.graceeditor import GraceEditor
from nmreval.utils.text import convert
from ..Qt import QtCore, QtWidgets, QtGui
from .._py.gracereader import Ui_Dialog
@ -55,7 +56,7 @@ class QGraceReader(QtWidgets.QDialog, Ui_Dialog):
if ds is None:
continue
item_2 = QtWidgets.QTreeWidgetItem([f'Set {gset.idx} (Label: {gset.get_property("legend")}, '
item_2 = QtWidgets.QTreeWidgetItem([f'Set {gset.idx} (Label: {convert(gset.get_property("legend"), old="agr", new="str")}, '
f'shape: {ds.shape})'])
item_2.setCheckState(0, QtCore.Qt.Checked)
item_2.setData(0, QtCore.Qt.UserRole, (graphs.idx, gset.idx))
@ -94,6 +95,7 @@ class QGraceReader(QtWidgets.QDialog, Ui_Dialog):
label = ''
else:
label = label.replace('"', '')
label = convert(label, old='agr', new='str')
sd = s.data
sd = np.atleast_2d(sd)
if s.type == 'xydy':

View File

@ -237,7 +237,7 @@ class CodeEditor(QtWidgets.QPlainTextEdit):
if block.isVisible() and (bottom >= evt.rect().top()):
number = str(block_number + 1)
painter.setPen(QtCore.Qt.black)
painter.drawText(0, top, self.current_linenumber.width() - 3, height,
painter.drawText(0, int(top), self.current_linenumber.width() - 3, height,
QtCore.Qt.AlignRight, number)
block = block.next()

View File

@ -52,19 +52,24 @@ class QDelayWidget(QtWidgets.QWidget):
class LineEdit(QtWidgets.QLineEdit):
values_requested = QtCore.pyqtSignal()
replace_single_values = QtCore.pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent=parent)
def contextMenuEvent(self, evt):
menu = self.createStandardContextMenu()
request_action = menu.addAction('Use value of sets')
request_action = menu.addAction('Use numeric value of sets')
set_value_action = menu.addAction('Replace single set values')
action = menu.exec(evt.globalPos())
if action == request_action:
self.values_requested.emit()
elif action == set_value_action:
self.replace_single_values.emit()
class LineEditPost(QtWidgets.QLineEdit):
values_requested = QtCore.pyqtSignal()

View File

@ -0,0 +1,53 @@
from numpy import log10, arange, floor, ceil
from pyqtgraph import PlotWidget, PlotItem
__all__ = ['NMRPlotWidget', 'logTickValues']
class NMRPlotWidget(PlotWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for orient in ['top', 'bottom', 'left', 'right']:
# BAD HACK!!! but seems to work, see function for explanation
self.plotItem.getAxis(orient).logTickValues = logTickValues
class NMRPlotItem(PlotItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for orient in ['top', 'bottom', 'left', 'right']:
self.plotItem.getAxis(orient).logTickValues = logTickValues
def logTickValues(minVal, maxVal, size, stdTicks):
# TODO FIND A BETTER SOLUTION!!!
# Sometimes minVal and maxVal are not log-scaled values and the loop from v1 to v2 is humongous,
# The minor list then fills the RAM completely and freezes everything
# Until there is a better solution, we overwrite this function for every AxesItem
# and do not draw minor ticks at all if there are too many
# start with the tick spacing given by tickValues().
# Any level whose spacing is < 1 needs to be converted to log scale
ticks = []
for (spacing, t) in stdTicks:
if spacing >= 1.0:
ticks.append((spacing, t))
if len(ticks) < 3:
v1 = int(floor(minVal))
v2 = int(ceil(maxVal))
# major = list(range(v1+1, v2))
minor = []
if v2 - v1 < 400:
for v in range(v1, v2):
minor.extend(v + log10(arange(1, 10)))
minor = [x for x in minor if minVal < x < maxVal]
ticks.append((None, minor))
return ticks

View File

@ -375,9 +375,6 @@ class PlotItem(PlotDataItem):
class RegionItem(LinearRegionItem):
def __init__(self, *args, **kwargs):
self.mode = kwargs.pop('mode', 'half')
print(args, kwargs)
super().__init__(*args, **kwargs)
self.logmode = False
@ -475,11 +472,18 @@ class LegendItemBlock(LegendItem):
def mouseDragEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton:
ev.accept()
dpos = ev.pos() - ev.lastPos()
upper_left = self.pos()
lower_right = self.pos()
lower_right.setX(lower_right.x() + self.width())
lower_right.setY(lower_right.y() + self.height())
vb_rect = self.parentItem().rect()
pos = self.pos()
# upper left corner and a point a little more to the bottom right must be inside
if vb_rect.contains(pos+dpos) and vb_rect.contains(pos+dpos+QtCore.QPointF(20., 20.)):
self.autoAnchor(pos + dpos)
# upper left and lower right corner must be inside viewbox
if vb_rect.contains(upper_left + dpos) and vb_rect.contains(lower_right + dpos):
self.autoAnchor(upper_left + dpos)
else:
self.autoAnchor(pos)
self.autoAnchor(upper_left)

View File

@ -28,4 +28,17 @@ class TreeWidget(QtWidgets.QTreeWidget):
continue
it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked)
else:
super().keyPressEvent(evt)
super().keyPressEvent(evt)
class TableWidget(QtWidgets.QTableWidget):
def keyPressEvent(self, evt: QtGui.QKeyEvent):
if evt.key() == QtCore.Qt.Key.Key_Space:
for idx in self.selectedIndexes():
item = self.itemFromIndex(idx)
cs = item.checkState()
item.setCheckState(QtCore.Qt.CheckState.Unchecked if cs == QtCore.Qt.CheckState.Checked
else QtCore.Qt.CheckState.Checked)
else:
super().keyPressEvent(evt)

View File

@ -211,7 +211,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.ptsselectwidget.points_selected.connect(self.management.extract_points)
self.t1tauwidget.newData.connect(self.management.add_new_data)
self.t1tauwidget.newData.connect(self.management.add_new_data)
self.editsignalwidget.do_something.connect(self.management.apply)
@ -917,10 +916,12 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.action_odr_fit: 'odr'
}[self.ac_group.checkedAction()]
self.fit_dialog.fit_button.setEnabled(False)
self.management.start_fit(parameter, links, fit_options)
self.status.setText('Fit running...'.format(self.management.fitter.step))
self.fit_timer.start(500)
fit_is_ready = self.management.prepare_fit(parameter, links, fit_options)
if fit_is_ready:
self.management.start_fit()
self.fit_dialog.fit_button.setEnabled(False)
self.status.setText('Fit running...'.format(self.management.fitter.step))
self.fit_timer.start(500)
@QtCore.pyqtSlot(dict, int, bool)
def show_fit_preview(self, funcs: dict, num: int, show: bool):

View File

@ -58,11 +58,18 @@ class GraphDict(OrderedDict):
def list(self):
return [(k, v.title) for k, v in self.items()]
def active(self, key: str):
if key:
return [(self._data[i].id, self._data[i].name) for i in self[key]]
else:
def active(self, key: str, return_val: str = 'both'):
if not key:
return []
else:
if return_val == 'both':
return [(self._data[i].id, self._data[i].name) for i in self[key]]
elif return_val == 'id':
return [self._data[i].id for i in self[key]]
elif return_val == 'name':
return [self._data[i].name for i in self[key]]
else:
raise ValueError(f'return_val got wrong value {return_val!r}')
def current_sets(self, key: str):
if key:
@ -148,6 +155,10 @@ class UpperManagement(QtCore.QObject):
def active_sets(self):
return self.graphs.active(self.current_graph)
@property
def active_id(self):
return self.graphs.active(self.current_graph, return_val='id')
def get_attributes(self, graph_id: str, attr: str) -> dict[str, Any]:
return {self.data[i].id: getattr(self.data[i], attr) for i in self.graphs[graph_id].sets}
@ -413,9 +424,9 @@ class UpperManagement(QtCore.QObject):
for d in self.data.values():
d.mask = np.ones_like(d.mask, dtype=bool)
def start_fit(self, parameter: dict, links: list, fit_options: dict):
def prepare_fit(self, parameter: dict, links: list, fit_options: dict) -> bool:
if self._fit_active:
return
return False
self.__fit_options = (parameter, links, fit_options)
@ -423,60 +434,84 @@ class UpperManagement(QtCore.QObject):
models = {}
fit_limits = fit_options['limits']
fit_mode = fit_options['fit_mode']
we = fit_options['we']
we_option = fit_options['we']
for model_id, model_p in parameter.items():
m = Model(model_p['func'])
models[model_id] = m
self.fitter.fitmethod = fit_mode
m_complex = model_p['complex']
# all-encompassing error catch
try:
for model_id, model_p in parameter.items():
m = Model(model_p['func'])
models[model_id] = m
for set_id, set_params in model_p['parameter'].items():
data_i = self.data[set_id]
if we.lower() == 'deltay':
we = data_i.y_err**2
m_complex = model_p['complex']
if m_complex is None or m_complex == 1:
_y = data_i.y.real
elif m_complex == 2 and np.iscomplexobj(data_i.y):
_y = data_i.y.imag
else:
_y = data_i.y
# sets are not in active order but in order they first appeared in fit dialog
# iterate over order of set id in active order and access parameter inside loop
# instead of directly looping
list_ids = list(model_p['parameter'].keys())
set_order = [self.active_id.index(i) for i in list_ids]
for pos in set_order:
set_id = list_ids[pos]
_x = data_i.x
data_i = self.data[set_id]
set_params = model_p['parameter'][set_id]
if fit_limits == 'none':
inside = slice(None)
elif fit_limits == 'x':
x_lim, _ = self.graphs[self.current_graph].ranges
inside = np.where((_x >= x_lim[0]) & (_x <= x_lim[1]))
else:
inside = np.where((_x >= fit_limits[0]) & (_x <= fit_limits[1]))
if we_option.lower() == 'deltay':
we = data_i.y_err**2
else:
we = we_option
if isinstance(we, str):
d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id)
else:
d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id)
if m_complex is None or m_complex == 1:
_y = data_i.y.real
elif m_complex == 2 and np.iscomplexobj(data_i.y):
_y = data_i.y.imag
else:
_y = data_i.y
d.set_model(m)
d.set_parameter(set_params[0], var=model_p['var'],
lb=model_p['lb'], ub=model_p['ub'],
fun_kwargs=set_params[1])
_x = data_i.x
self.fitter.add_data(d)
if fit_limits == 'none':
inside = slice(None)
elif fit_limits == 'x':
x_lim, _ = self.graphs[self.current_graph].ranges
inside = np.where((_x >= x_lim[0]) & (_x <= x_lim[1]))
else:
inside = np.where((_x >= fit_limits[0]) & (_x <= fit_limits[1]))
model_globs = model_p['glob']
if model_globs:
for parameter_args in zip(*model_globs.values()):
m.set_global_parameter(**{k: v for k, v in zip(model_globs.keys(), parameter_args)})
# m.set_global_parameter(**model_p['glob'])
if isinstance(we, str):
d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id)
else:
d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id)
for links_i in links:
self.fitter.set_link_parameter((models[links_i[0]], links_i[1]),
(models[links_i[2]], links_i[3]))
d.set_model(m)
d.set_parameter(set_params[0], var=model_p['var'],
lb=model_p['lb'], ub=model_p['ub'],
fun_kwargs=set_params[1])
self.fitter.add_data(d)
model_globs = model_p['glob']
if model_globs:
for parameter_args in zip(*model_globs.values()):
m.set_global_parameter(**{k: v for k, v in zip(model_globs.keys(), parameter_args)})
# m.set_global_parameter(**model_p['glob'])
for links_i in links:
self.fitter.set_link_parameter((models[links_i[0]], links_i[1]),
(models[links_i[2]], links_i[3]))
return True
except Exception as e:
logger.error('Fit preparation failed', *e.args)
QtWidgets.QMessageBox.warning(QtWidgets.QWidget(),
'Fit prep failed',
f'Fit preparation failed with message\n{e.args}')
return False
def start_fit(self):
with busy_cursor():
self.fit_worker = FitWorker(self.fitter, fit_mode)
self.fit_worker = FitWorker(self.fitter)
self.fit_thread = QtCore.QThread()
self.fit_worker.moveToThread(self.fit_thread)
@ -512,7 +547,8 @@ class UpperManagement(QtCore.QObject):
for set_id, set_parameter in parameter.items():
new_values = [v.value for v in res[set_id].parameter.values()]
parameter[set_id] = (new_values, set_parameter[1])
self.start_fit(*self.__fit_options)
if self.prepare_fit(*self.__fit_options):
self.start_fit()
def make_fits(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: bool, extrapolate: list) -> None:
"""
@ -637,7 +673,7 @@ class UpperManagement(QtCore.QObject):
def save_fit_parameter(self, fname: str | pathlib.Path, fit_sets: list[str] = None):
if fit_sets is None:
fit_sets = [s for (s, _) in self.active_sets]
fit_sets = [s for s in self.active_id]
for set_id in fit_sets:
data = self.data[set_id]
@ -843,13 +879,10 @@ class UpperManagement(QtCore.QObject):
d_k = self.data[k]
if copy_data is None:
d_k.x = d_k.x*v[1][0] + v[0][0]
d_k.y = d_k.y*v[1][1] + v[0][1]
d_k.shift_scale(v[0], v[1])
else:
new_data = d_k.copy(full=True)
new_data.update({'shift': v[0], 'scale': v[1]})
new_data.data.x = new_data.x*v[1][0] + v[0][0]
new_data.y = new_data.y*v[1][1] + v[0][1]
new_data.shift_scale(v[0], v[1])
sid = self.add(new_data)
sid_list.append(sid)
@ -1009,7 +1042,7 @@ class UpperManagement(QtCore.QObject):
def show_statistics(self, mode):
x, y, = [], []
for i, _ in self.active_sets:
for i in self.active_id:
_temp = self.data[i]
try:
x.append(float(_temp.name))
@ -1020,7 +1053,7 @@ class UpperManagement(QtCore.QObject):
@QtCore.pyqtSlot()
def calc_magn(self):
new_id = []
for k, _ in self.active_sets:
for k in self.active_id:
dataset = self.data[k]
if isinstance(dataset, SignalContainer):
new_value = dataset.copy(full=True)
@ -1032,7 +1065,7 @@ class UpperManagement(QtCore.QObject):
@QtCore.pyqtSlot()
def center(self):
new_id = []
for k, _ in self.active_sets:
for k in self.active_id:
new_value = self.data[k].copy(full=True)
new_value.x -= new_value.x[np.argmax(new_value.y.real)]
new_id.append(self.add(new_value))
@ -1071,7 +1104,7 @@ class UpperManagement(QtCore.QObject):
def bds_deriv(self):
new_sets = []
for (set_id, _) in self.active_sets:
for set_id in self.active_id:
data_i = self.data[set_id]
diff = data_i.data.diff(log=True)
new_data = Points(x=diff.x, y=-np.pi/2*diff.y.real)
@ -1098,7 +1131,7 @@ class UpperManagement(QtCore.QObject):
self.newData.emit(new_sets, kwargs['graph'])
def skip_points(self, offset: int, step: int, invert: bool = False, copy: bool = False):
for k, _ in self.active_sets:
for k in self.active_id:
src = self.data[k]
if invert:
mask = np.mod(np.arange(offset, src.x.size+offset), step) != 0
@ -1253,16 +1286,15 @@ class UpperManagement(QtCore.QObject):
class FitWorker(QtCore.QObject):
finished = QtCore.pyqtSignal(list, bool)
def __init__(self, fitter, mode):
def __init__(self, fitter):
super().__init__()
self.fitter = fitter
self.mode = mode
@QtCore.pyqtSlot()
def run(self):
try:
res = self.fitter.run(mode=self.mode)
res = self.fitter.run()
success = True
except Exception as e:
res = [e]

View File

@ -19,7 +19,7 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
self.graphs = {}
self.specdens = [ColeCole, ColeDavidson, HavriliakNegami, KWW]
self.specdens = [ColeCole, ColeDavidson, HavriliakNegami, KWW, LogGaussian]
self.coupling = [Quadrupolar, HomoDipolar, Czjzek]
self.tau_parameter = []
@ -199,3 +199,9 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
def accept(self):
self.calc_relaxation()
super().accept()
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
def on_buttonBox_clicked(self, button: QtWidgets.QAbstractButton):
role = self.buttonBox.buttonRole(button)
if role == self.buttonBox.ApplyRole:
self.calc_relaxation()

View File

@ -61,6 +61,8 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
self.freq_combox.currentIndexChanged.connect(lambda x: self.update_model())
self.freq_spinbox.valueChanged.connect(lambda x: self.update_model())
self.checkBox_interpol.setVisible(False)
self.update_specdens(0)
self.update_coupling(0)

View File

@ -54,6 +54,9 @@ class ColeCole(Distribution):
tau (array_like):
alpha (float):
"""
if alpha == 1:
return tau / (1 + omega**2 * tau**2)
omtau = (omega*tau)**alpha
return np.sin(alpha*np.pi/2) * omtau / (1 + omtau**2 + 2*np.cos(alpha*np.pi/2)*omtau) / omega

View File

@ -5,6 +5,7 @@ from typing import Callable
import numpy as np
from scipy import LowLevelCallable
from scipy.special import erf
from nmreval.lib.utils import ArrayLike
@ -32,7 +33,7 @@ class LogGaussian(Distribution):
return np.exp(-0.5*(np.log(tau/tau0)/sigma)**2)/np.sqrt(2*np.pi)/sigma
@staticmethod
def correlation(t, tau0, sigma: float):
def correlation(t: ArrayLike, tau0: ArrayLike, sigma: float):
_t = np.atleast_1d(t)
_tau = np.atleast_1d(tau0)
@ -44,7 +45,7 @@ class LogGaussian(Distribution):
return res.squeeze()
@staticmethod
def susceptibility(omega, tau0, sigma: float):
def susceptibility(omega: ArrayLike, tau0: ArrayLike, sigma: float):
_omega = np.atleast_1d(omega)
_tau = np.atleast_1d(tau0)
@ -68,6 +69,7 @@ class LogGaussian(Distribution):
ret_val = _integration_parallel(_omega, _tau, sigma, _integrate_process_imag)
ret_val /= _omega[:, None]
ret_val[_omega == 0, :] = tau[None, :] * np.exp(sigma**2 / 2)
return ret_val.squeeze()
@ -113,18 +115,16 @@ def _integrate_susc_c(lowfunc, highfunc, omega, tau, sigma):
return res
def _integrate_process_imag(args):
omega_i, tau_j, sigma = args
area = quad(_integrand_freq_imag_high, 0, 50, args=(omega_i, tau_j, sigma), epsabs=1e-12, epsrel=1e-12)[0]
area += quad(_integrand_freq_imag_low, -50, 0, args=(omega_i, tau_j, sigma), epsabs=1e-12, epsrel=1e-12)[0]
def _integrate_process_imag(omega, tau, sigma):
area = quad(_integrand_freq_imag_high, 0, 50, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
area += quad(_integrand_freq_imag_low, -50, 0, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
return area
def _integrate_process_real(args):
omega_i, tau_j, sigma = args
area = quad(_integrand_freq_real_high, 0, 50, args=(omega_i, tau_j, sigma))[0]
area += quad(_integrand_freq_real_low, -50, 0, args=(omega_i, tau_j, sigma))[0]
def _integrate_process_real(omega: float, tau: float, sigma: float):
area = quad(_integrand_freq_real_high, 0, 50, args=(omega, tau, sigma))[0]
area += quad(_integrand_freq_real_low, -50, 0, args=(omega, tau, sigma))[0]
return area
@ -145,9 +145,8 @@ def _integrate_correlation_c(t, tau, sigma):
return res
def _integrate_process_time(args):
omega_i, tau_j, sigma = args
return quad(_integrand_time, -50, 50, args=(omega_i, tau_j, sigma), epsabs=1e-12, epsrel=1e-12)[0]
def _integrate_process_time(omega, tau, sigma):
return quad(_integrand_time, -50, 50, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
def _integrand_time(u, t, tau, sigma):

View File

@ -24,9 +24,10 @@ class ModelFactory:
param_len.append(len(func['func'].params))
if func['children']:
right, _, _ = ModelFactory.create_from_list(func['children'], left=func['func'], left_cnt=func['pos'],
right, _, _ = ModelFactory.create_from_list(func['children'], left_cnt=func['pos'],
func_order=func_order, param_len=param_len)
right_cnt = None
right = MultiModel(func['func'], right, func['children'][0]['op'], left_idx=func['cnt'], right_idx=None)
else:
right = func['func']
right_cnt = func['cnt']
@ -46,7 +47,13 @@ class MultiModel:
str_op = {'+': operator.add, '*': operator.mul, '-': operator.sub, '/': operator.truediv}
int_op = {0: operator.add, 1: operator.mul, 2: operator.sub, 3: operator.truediv}
def __init__(self, left: Any, right: Any, op: str | Callable | int = '+', left_idx=0, right_idx=1):
def __init__(self,
left: Any,
right: Any,
op: str | Callable | int = '+',
left_idx: int | None = 0,
right_idx: int | None = 1,
):
self._left = left
self._right = right

View File

@ -82,7 +82,7 @@ def _update_parameter(data: Data, varied_keys: list[str], parameter: list[float]
class FitRoutine(object):
def __init__(self, mode='lsq'):
self._fitmethod = mode
self.fitmethod = mode
self.data = []
self.fit_model = None
self._no_own_model = []
@ -220,9 +220,12 @@ class FitRoutine(object):
logger.info('Fit aborted by user')
self._abort = True
def run(self, mode: str = 'lsq'):
def run(self, mode: str=None):
self._abort = False
if mode is None:
mode = self.fitmethod
fit_groups, linked_parameter = self.prepare_links()
for data_groups in fit_groups:
if len(data_groups) == 1 and not self.linked:

View File

@ -15,7 +15,7 @@ NUMBERRE = re.compile(r'[0-9]\.*[0-9]*[Ee]*[+-]*[0-9]*')
class AsciiReader:
delimiters = ['\t', ' ', ',']
# delimiters = ['\t', ' ', ',']
def __init__(self, fname):
self.fname = None
@ -49,7 +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, ')
line = re.split(r'[\s,;]', line)
line = re.sub(r'[\t ;,] *', ';', line)
line = line.split(';')
try:
comment_start = line.index('#')

View File

@ -292,7 +292,7 @@ class DSCCalibrator:
empty_y = empty_data[1]
if self.sample.length(idx) != self.empty.length(idx_empty):
with np.errstate(all='ignore'):
empty_y = interp1d(empty_data[2], empty_data[1], fill_value='extrapolate')(sample_data[2])
empty_y = interp1d(empty_data[2]-empty_data[2, 0], empty_data[1], fill_value='extrapolate')(sample_data[2, 0])
sample_data[1] -= empty_y
drift_value = sample_data.copy()[(2, 1), :]

View File

@ -24,8 +24,8 @@ def interpolate(data, new_x, xlog=False, ylog=False, kind='cubic', extrapolate=T
new_y = f(new_x)
if ylog:
ret_val.set_data(x=new_x, y=10**new_y, y_err=None)
ret_val.set_data(x=new_x, y=10**new_y, y_err=0)
else:
ret_val.set_data(x=new_x, y=new_y, y_err=None)
ret_val.set_data(x=new_x, y=new_y, y_err=0)
return ret_val

View File

@ -54,14 +54,35 @@ class HavriliakNegamiBDS(_AbstractBDS):
name = 'Havriliak-Negami'
equation = r'\Delta\epsilon / [1-(i\omega\tau)^{\gamma}]^{\alpha}'
params = _AbstractBDS.params + [r'\alpha', r'\gamma']
bounds = _AbstractBDS.bounds + [(0, 1), (0, 1)]
bounds = _AbstractBDS.bounds + [(0, 1), (0, None)]
susceptibility = HavriliakNegami.susceptibility
class HavriliakNegamiAlphaGammaBDS:
type = 'Dielectric Spectroscopy'
name = 'Havriliak-Negami (ind. slopes)'
equation = r'\Delta\epsilon / [1-(i\omega\tau)^{\gamma}]^{\alpha}'
params = [r'\Delta\epsilon', r'\tau_{0}', r'\alpha', r'\alpha\gamma']
bounds = [(0, None), (0, None), (0, 1), (0, 1)]
iscomplex = True
@staticmethod
def func(x, deps, tau, alpha, alphagamma, complex_mode: int = 0, **kwargs):
chi = deps * HavriliakNegami.susceptibility(2*np.pi*x, tau, alpha, alphagamma/alpha, **kwargs)
if complex_mode == 0:
return chi
elif complex_mode == 1:
return chi.real
elif complex_mode == 2:
return chi.imag
else:
raise ValueError(f'{complex_mode!r} is not 0, 1, 2')
class KWWBDS(_AbstractBDS):
name = 'KWW'
params = _AbstractBDS.params + [r'\beta']
bounds = _AbstractBDS.bounds + [(0, 1)]
bounds = _AbstractBDS.bounds + [(0.1, 1)]
susceptibility = KWW.susceptibility

View File

@ -149,7 +149,6 @@ class DiffusionGradients:
else:
tm = t2
tp = x
# T2 decay happens twice
q2 = (g1**2 - g2**2) * (nucleus * tp)**2
t_eff = (2 * tp) / 3 + tm

View File

@ -66,7 +66,7 @@ class HavriliakNegamiFC(_AbstractFC):
class KWWFC(_AbstractFC):
name = 'KWW'
params = _AbstractFC.params + [r'\beta']
bounds = _AbstractFC.bounds + [(0, 1)]
bounds = _AbstractFC.bounds + [(0.1, 1)]
relax = Relaxation(distribution=KWW)

View File

@ -80,6 +80,10 @@ def _replace_delims(text, src, dest):
return text
def _replace_agr_controls(text: str):
return re.sub(r'\\[hvzfx]\{(\d*.?\d+.?)?\}', '', text)
def convert(text: str, old: str = 'tex', new: str = 'html', brackets: bool = True):
t = {'latex': 0, 'tex': 0, 'html': 1,
'agr': 2, 'plain': 3, 'str': 3}
@ -101,5 +105,8 @@ def convert(text: str, old: str = 'tex', new: str = 'html', brackets: bool = Tru
if idx_out == 3 and not brackets:
text = text.replace('{', '').replace('}', '')
if idx_in == 2:
text = _replace_agr_controls(text)
return text

View File

@ -36,7 +36,7 @@
<number>3</number>
</property>
<item row="2" column="0">
<widget class="PlotWidget" name="graphicsView">
<widget class="NMRPlotWidget" name="graphicsView">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -46,7 +46,7 @@
</widget>
</item>
<item row="2" column="1">
<widget class="PlotWidget" name="graphicsView_2"/>
<widget class="NMRPlotWidget" name="graphicsView_2"/>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="apodcombobox">
@ -98,9 +98,9 @@
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<class>NMRPlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
<header>..lib.graph_items</header>
</customwidget>
</customwidgets>
<resources/>

View File

@ -53,7 +53,7 @@
</widget>
</item>
<item row="0" column="2">
<widget class="PlotWidget" name="graphicsView">
<widget class="NMRPlotWidget" name="graphicsView">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -66,9 +66,9 @@
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<class>NMRPlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
<header>..lib.graph_items</header>
</customwidget>
</customwidgets>
<resources/>

View File

@ -378,7 +378,7 @@
<widget class="QWidget" name="layoutWidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="PlotWidget" name="raw_graph">
<widget class="NMRPlotWidget" name="raw_graph">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -394,7 +394,7 @@
</widget>
</item>
<item row="1" column="0">
<widget class="PlotWidget" name="calib_graph">
<widget class="NMRPlotWidget" name="calib_graph">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -410,7 +410,7 @@
</widget>
</item>
<item row="0" column="1">
<widget class="PlotWidget" name="baseline_graph">
<widget class="NMRPlotWidget" name="baseline_graph">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -426,7 +426,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="PlotWidget" name="end_graph">
<widget class="NMRPlotWidget" name="end_graph">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -449,9 +449,9 @@
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<class>NMRPlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
<header>..lib.graph_items</header>
</customwidget>
</customwidgets>
<resources/>

View File

@ -538,7 +538,7 @@
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="PlotWidget" name="graphic">
<widget class="NMRPlotWidget" name="graphic">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -552,16 +552,16 @@
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
</customwidget>
<customwidget>
<class>QListWidgetSelect</class>
<extends>QListWidget</extends>
<header>..lib.listwidget</header>
</customwidget>
<customwidget>
<class>NMRPlotWidget</class>
<extends>QGraphicsView</extends>
<header>..lib.graph_items</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>logx_button</tabstop>

View File

@ -36,7 +36,7 @@
<number>3</number>
</property>
<item row="0" column="0" colspan="8">
<widget class="PlotWidget" name="graphicsView">
<widget class="NMRPlotWidget" name="graphicsView">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -160,9 +160,9 @@
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<class>NMRPlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
<header>..lib.graph_items</header>
</customwidget>
</customwidgets>
<resources/>

View File

@ -317,7 +317,7 @@
<number>3</number>
</property>
<item>
<widget class="PlotWidget" name="graphicsView">
<widget class="NMRPlotWidget" name="graphicsView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -503,9 +503,9 @@
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<class>NMRPlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
<header>..lib.graph_items</header>
</customwidget>
<customwidget>
<class>SciSpinBox</class>

View File

@ -451,6 +451,9 @@
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="checkBox_interpol">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Use minimon interpolation</string>
</property>

View File

@ -100,7 +100,7 @@
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="PlotWidget" name="dsc_plot">
<widget class="NMRPlotWidget" name="dsc_plot">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -145,10 +145,10 @@
</widget>
</item>
<item row="1" column="0">
<widget class="PlotWidget" name="tghodge_graph"/>
<widget class="NMRPlotWidget" name="tghodge_graph"/>
</item>
<item row="1" column="1">
<widget class="PlotWidget" name="tau_plot">
<widget class="NMRPlotWidget" name="tau_plot">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -277,7 +277,7 @@
<widget class="QWidget" name="page_3">
<layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="0" colspan="2">
<widget class="PlotWidget" name="tnmh_graphics"/>
<widget class="NMRPlotWidget" name="tnmh_graphics"/>
</item>
<item row="2" column="0">
<widget class="QTreeWidget" name="tnmh_tree">
@ -427,16 +427,16 @@
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
</customwidget>
<customwidget>
<class>QListWidgetSelect</class>
<extends>QListWidget</extends>
<header>..lib.listwidget</header>
</customwidget>
<customwidget>
<class>NMRPlotWidget</class>
<extends>QGraphicsView</extends>
<header>..lib.graph_items</header>
</customwidget>
</customwidgets>
<resources/>
<connections>