extrapolate_fit (#21)

New sets with arbitrary x range can be created from fit results

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #21
This commit is contained in:
2023-03-12 19:37:10 +00:00
parent 1601f1455c
commit 28c15ff565
10 changed files with 393 additions and 169 deletions

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './resources/_ui/fitresult.ui'
# Form implementation generated from reading ui file '/autohome/dominik/nmreval-gitea/src/resources/_ui/fitresult.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(817, 584)
Dialog.resize(864, 649)
self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setObjectName("gridLayout")
self.sets_comboBox = ElideComboBox(Dialog)
@ -51,6 +51,21 @@ class Ui_Dialog(object):
self.gridLayout_2.setContentsMargins(3, 3, 3, 3)
self.gridLayout_2.setSpacing(3)
self.gridLayout_2.setObjectName("gridLayout_2")
self.extrapolate_box = QtWidgets.QCheckBox(self.groupBox)
self.extrapolate_box.setObjectName("extrapolate_box")
self.gridLayout_2.addWidget(self.extrapolate_box, 1, 0, 1, 1)
self.parameter_checkbox = QtWidgets.QCheckBox(self.groupBox)
self.parameter_checkbox.setObjectName("parameter_checkbox")
self.gridLayout_2.addWidget(self.parameter_checkbox, 0, 5, 1, 1)
self.graph_comboBox = QtWidgets.QComboBox(self.groupBox)
self.graph_comboBox.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.graph_comboBox.sizePolicy().hasHeightForWidth())
self.graph_comboBox.setSizePolicy(sizePolicy)
self.graph_comboBox.setObjectName("graph_comboBox")
self.gridLayout_2.addWidget(self.graph_comboBox, 1, 6, 1, 1)
self.graph_checkBox = QtWidgets.QCheckBox(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@ -59,22 +74,44 @@ class Ui_Dialog(object):
self.graph_checkBox.setSizePolicy(sizePolicy)
self.graph_checkBox.setChecked(True)
self.graph_checkBox.setObjectName("graph_checkBox")
self.gridLayout_2.addWidget(self.graph_checkBox, 1, 1, 1, 1)
self.graph_comboBox = QtWidgets.QComboBox(self.groupBox)
self.graph_comboBox.setEnabled(False)
self.graph_comboBox.setObjectName("graph_comboBox")
self.gridLayout_2.addWidget(self.graph_comboBox, 1, 2, 1, 1)
self.gridLayout_2.addWidget(self.graph_checkBox, 1, 5, 1, 1)
self.minx_line = QtWidgets.QLineEdit(self.groupBox)
self.minx_line.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.minx_line.sizePolicy().hasHeightForWidth())
self.minx_line.setSizePolicy(sizePolicy)
self.minx_line.setObjectName("minx_line")
self.gridLayout_2.addWidget(self.minx_line, 1, 1, 1, 1)
self.line_2 = QtWidgets.QFrame(self.groupBox)
self.line_2.setFrameShape(QtWidgets.QFrame.VLine)
self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line_2.setObjectName("line_2")
self.gridLayout_2.addWidget(self.line_2, 0, 4, 2, 1)
self.maxx_line = QtWidgets.QLineEdit(self.groupBox)
self.maxx_line.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.maxx_line.sizePolicy().hasHeightForWidth())
self.maxx_line.setSizePolicy(sizePolicy)
self.maxx_line.setObjectName("maxx_line")
self.gridLayout_2.addWidget(self.maxx_line, 1, 2, 1, 1)
self.numx_line = QtWidgets.QLineEdit(self.groupBox)
self.numx_line.setEnabled(False)
self.numx_line.setObjectName("numx_line")
self.gridLayout_2.addWidget(self.numx_line, 1, 3, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.curve_checkbox = QtWidgets.QCheckBox(self.groupBox)
self.curve_checkbox.setChecked(True)
self.curve_checkbox.setObjectName("curve_checkbox")
self.gridLayout_2.addWidget(self.curve_checkbox, 0, 0, 1, 1)
self.horizontalLayout.addWidget(self.curve_checkbox)
self.partial_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.partial_checkBox.setObjectName("partial_checkBox")
self.gridLayout_2.addWidget(self.partial_checkBox, 1, 0, 1, 1)
self.parameter_checkbox = QtWidgets.QCheckBox(self.groupBox)
self.parameter_checkbox.setChecked(True)
self.parameter_checkbox.setObjectName("parameter_checkbox")
self.gridLayout_2.addWidget(self.parameter_checkbox, 0, 1, 1, 1)
self.horizontalLayout.addWidget(self.partial_checkBox)
self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 4)
self.gridLayout.addWidget(self.groupBox, 5, 0, 1, 2)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setSpacing(3)
@ -91,40 +128,38 @@ class Ui_Dialog(object):
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.gridLayout.addWidget(self.line, 3, 0, 1, 2)
self.stack = QtWidgets.QToolBox(Dialog)
self.stack = QtWidgets.QTabWidget(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.stack.sizePolicy().hasHeightForWidth())
self.stack.setSizePolicy(sizePolicy)
self.stack.setObjectName("stack")
self.page = QtWidgets.QWidget()
self.page.setGeometry(QtCore.QRect(0, 0, 399, 346))
self.page.setObjectName("page")
self.gridLayout_3 = QtWidgets.QGridLayout(self.page)
self.stackPage1 = QtWidgets.QWidget()
self.stackPage1.setObjectName("stackPage1")
self.gridLayout_3 = QtWidgets.QGridLayout(self.stackPage1)
self.gridLayout_3.setContentsMargins(3, 3, 3, 3)
self.gridLayout_3.setSpacing(3)
self.gridLayout_3.setObjectName("gridLayout_3")
self.logy_box = QtWidgets.QCheckBox(self.page)
self.logy_box = QtWidgets.QCheckBox(self.stackPage1)
self.logy_box.setLayoutDirection(QtCore.Qt.RightToLeft)
self.logy_box.setObjectName("logy_box")
self.gridLayout_3.addWidget(self.logy_box, 2, 1, 1, 1)
self.logx_box = QtWidgets.QCheckBox(self.page)
self.logx_box = QtWidgets.QCheckBox(self.stackPage1)
self.logx_box.setLayoutDirection(QtCore.Qt.RightToLeft)
self.logx_box.setObjectName("logx_box")
self.gridLayout_3.addWidget(self.logx_box, 2, 0, 1, 1)
self.graphicsView = GraphicsLayoutWidget(self.page)
self.graphicsView = GraphicsLayoutWidget(self.stackPage1)
self.graphicsView.setObjectName("graphicsView")
self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 2)
self.stack.addItem(self.page, "")
self.page_2 = QtWidgets.QWidget()
self.page_2.setGeometry(QtCore.QRect(0, 0, 399, 346))
self.page_2.setObjectName("page_2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.page_2)
self.stack.addTab(self.stackPage1, "")
self.stackPage2 = QtWidgets.QWidget()
self.stackPage2.setObjectName("stackPage2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.stackPage2)
self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_2.setSpacing(3)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.stats_tableWidget = QtWidgets.QTableWidget(self.page_2)
self.stats_tableWidget = QtWidgets.QTableWidget(self.stackPage2)
self.stats_tableWidget.setFrameShape(QtWidgets.QFrame.Box)
self.stats_tableWidget.setGridStyle(QtCore.Qt.NoPen)
self.stats_tableWidget.setColumnCount(1)
@ -133,15 +168,14 @@ class Ui_Dialog(object):
self.stats_tableWidget.horizontalHeader().setVisible(False)
self.stats_tableWidget.horizontalHeader().setSortIndicatorShown(True)
self.verticalLayout_2.addWidget(self.stats_tableWidget)
self.stack.addItem(self.page_2, "")
self.page_3 = QtWidgets.QWidget()
self.page_3.setGeometry(QtCore.QRect(0, 0, 399, 346))
self.page_3.setObjectName("page_3")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.page_3)
self.stack.addTab(self.stackPage2, "")
self.stackPage3 = QtWidgets.QWidget()
self.stackPage3.setObjectName("stackPage3")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.stackPage3)
self.verticalLayout_3.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_3.setSpacing(3)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.corr_tableWidget = QtWidgets.QTableWidget(self.page_3)
self.corr_tableWidget = QtWidgets.QTableWidget(self.stackPage3)
self.corr_tableWidget.setFrameShape(QtWidgets.QFrame.Box)
self.corr_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.corr_tableWidget.setGridStyle(QtCore.Qt.NoPen)
@ -159,28 +193,34 @@ class Ui_Dialog(object):
self.corr_tableWidget.horizontalHeader().setStretchLastSection(True)
self.corr_tableWidget.verticalHeader().setVisible(False)
self.verticalLayout_3.addWidget(self.corr_tableWidget)
self.stack.addItem(self.page_3, "")
self.stack.addTab(self.stackPage3, "")
self.gridLayout.addWidget(self.stack, 0, 1, 3, 1)
self.retranslateUi(Dialog)
self.stack.setCurrentIndex(0)
self.stack.layout().setSpacing(0)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Fit results"))
self.groupBox.setTitle(_translate("Dialog", "Output"))
self.graph_checkBox.setText(_translate("Dialog", "New graph"))
self.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function"))
self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves"))
self.parameter_checkbox.setText(_translate("Dialog", "Plot parameter"))
self.graph_checkBox.setText(_translate("Dialog", "New graph for parameter"))
self.minx_line.setToolTip(_translate("Dialog", "Leave empty to start at lowest point"))
self.minx_line.setPlaceholderText(_translate("Dialog", "min x"))
self.maxx_line.setToolTip(_translate("Dialog", "Leave empty to start at highest point"))
self.maxx_line.setPlaceholderText(_translate("Dialog", "max x"))
self.numx_line.setPlaceholderText(_translate("Dialog", "# pts"))
self.curve_checkbox.setText(_translate("Dialog", "Plot fit curve"))
self.partial_checkBox.setText(_translate("Dialog", "Plot partial functions"))
self.parameter_checkbox.setText(_translate("Dialog", "Plot parameter"))
self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit"))
self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits"))
self.logy_box.setText(_translate("Dialog", "logarithmic y axis"))
self.logx_box.setText(_translate("Dialog", "logarithmic x axis"))
self.stack.setItemText(self.stack.indexOf(self.page), _translate("Dialog", "Plot"))
self.stack.setItemText(self.stack.indexOf(self.page_2), _translate("Dialog", "Statistics"))
self.stack.setTabText(self.stack.indexOf(self.stackPage1), _translate("Dialog", "Plot"))
self.stack.setTabText(self.stack.indexOf(self.stackPage2), _translate("Dialog", "Statistics"))
item = self.corr_tableWidget.horizontalHeaderItem(0)
item.setText(_translate("Dialog", "Parameter 1"))
item = self.corr_tableWidget.horizontalHeaderItem(1)
@ -189,6 +229,6 @@ class Ui_Dialog(object):
item.setText(_translate("Dialog", "Corr."))
item = self.corr_tableWidget.horizontalHeaderItem(3)
item.setText(_translate("Dialog", "Partial Corr."))
self.stack.setItemText(self.stack.indexOf(self.page_3), _translate("Dialog", "Correlations"))
self.stack.setTabText(self.stack.indexOf(self.stackPage3), _translate("Dialog", "Correlations"))
from ..lib.forms import ElideComboBox
from pyqtgraph import GraphicsLayoutWidget

View File

@ -553,7 +553,9 @@ class FitContainer(ExperimentContainer):
setattr(self, n, getattr(data, n))
def _init_plot(self, **kwargs):
color = kwargs.get('color', (0, 0, 0))
color = kwargs.get('color')
if color is None:
color = kwargs.get('linecolor', (0, 0, 0))
if isinstance(color, BaseColor):
color = color.rgb()
@ -605,7 +607,7 @@ class SignalContainer(ExperimentContainer):
linecolor = kwargs.get('linecolor', color)
if symcolor is None and linecolor is None:
color = next(self.colors)
color = next(self.colors) if color is None else color
symcolor = color
linecolor = color
elif symcolor is None:

View File

@ -17,6 +17,7 @@ class DataTree(QtWidgets.QTreeWidget):
moveItem = QtCore.pyqtSignal(list, str, str, int) # items, from, to, new row
copyItem = QtCore.pyqtSignal(list, str)
saveFits = QtCore.pyqtSignal(list)
extendFits = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super().__init__(parent=parent)
@ -387,6 +388,10 @@ class DataTree(QtWidgets.QTreeWidget):
for c in available_cycles.keys():
col_menu.addAction(c)
action = menu.exec(evt.globalPos())
if action is None:
return
graphs = []
items = []
for i in self.selectedIndexes():
@ -395,7 +400,6 @@ class DataTree(QtWidgets.QTreeWidget):
items.append(self.itemFromIndex(i))
graphs.append(self.itemFromIndex(i).data(0, QtCore.Qt.UserRole))
action = menu.exec(evt.globalPos())
if action == del_action:
for gid in graphs:
self.management.delete_graph(gid)
@ -414,8 +418,7 @@ class DataTree(QtWidgets.QTreeWidget):
del_action = menu.addAction('Exterminate sets')
cp_action = menu.addAction('Replicate sets')
cat_action = menu.addAction('Join us!')
plt_action = None
save_action = None
plt_action = save_action = extend_action = None
menu.addSeparator()
col_menu = menu.addMenu('Color cycle')
for c in available_cycles.keys():
@ -446,6 +449,7 @@ class DataTree(QtWidgets.QTreeWidget):
menu.addSeparator()
plt_action = menu.addAction('Plot fit parameter')
save_action = menu.addAction('Save fit parameter')
extend_action = menu.addAction('Extrapolate fit')
action = menu.exec(evt.globalPos())
@ -469,6 +473,9 @@ class DataTree(QtWidgets.QTreeWidget):
elif action == save_action:
self.saveFits.emit(s)
elif action == extend_action:
self.extendFits.emit(s)
elif action.parent() == col_menu:
self.management.set_cycle(s, action.text())

View File

@ -11,7 +11,7 @@ from ..lib.pg_objects import PlotItem
class QFitResult(QtWidgets.QDialog, Ui_Dialog):
closed = QtCore.pyqtSignal(dict, list, str, bool, dict)
closed = QtCore.pyqtSignal(dict, list, str, bool, bool, list)
redoFit = QtCore.pyqtSignal(dict)
def __init__(self, results: list, management, parent=None):
@ -20,10 +20,17 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self._management = management
self.maxx_line.setValidator(QtGui.QDoubleValidator())
self.minx_line.setValidator(QtGui.QDoubleValidator())
self.numx_line.setValidator(QtGui.QIntValidator())
self.extrapolate_box.stateChanged.connect(lambda x: self.maxx_line.setEnabled(x))
self.extrapolate_box.stateChanged.connect(lambda x: self.minx_line.setEnabled(x))
self.extrapolate_box.stateChanged.connect(lambda x: self.numx_line.setEnabled(x))
self._prevs = {}
self._models = {}
for (res, parts) in results:
for res in results:
idx = res.idx
data_k = management.data[idx]
@ -36,8 +43,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
for fit in data_k.get_fits():
self._prevs[idx].append((fit.name, fit.statistics, fit.nobs-fit.nvar))
self._results = {res.idx: res for (res, _) in results}
self._parts = {res.idx: parts for (res, parts) in results}
self._results = {res.idx: res for res in results}
self._opts = [(False, False) for _ in range(len(self._results))]
self.residplot = self.graphicsView.addPlot(row=0, col=0)
@ -273,12 +279,75 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
plot_fits = self.curve_checkbox.isChecked()
if self.partial_checkBox.checkState() == QtCore.Qt.Checked:
self.closed.emit(self._results, self._opts, graph, plot_fits, self._parts)
else:
self.closed.emit(self._results, self._opts, graph, plot_fits, {})
parts = self.partial_checkBox.checkState() == QtCore.Qt.Checked
extrapolate = [None, None, None]
if self.extrapolate_box.isChecked():
try:
extrapolate[0] = float(self.minx_line.text())
except TypeError:
pass
try:
extrapolate[1] = float(self.maxx_line.text())
except TypeError:
pass
try:
extrapolate[2] = int(self.numx_line.text())
except TypeError:
pass
self.closed.emit(self._results, self._opts, graph, plot_fits, parts, extrapolate)
self.accept()
else:
self.reject()
class FitExtension(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent=parent)
gridLayout = QtWidgets.QGridLayout(self)
self.label = QtWidgets.QLabel('Minimum value')
gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.min_line = QtWidgets.QLineEdit()
self.min_line.setValidator(QtGui.QDoubleValidator())
gridLayout.addWidget(self.min_line, 0, 1, 1, 1)
self.label_2 = QtWidgets.QLabel('Maximum value')
gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
self.max_line = QtWidgets.QLineEdit()
self.max_line.setValidator(QtGui.QDoubleValidator())
gridLayout.addWidget(self.max_line, 1, 1, 1, 1)
self.label_3 = QtWidgets.QLabel('Number of pts.')
gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
self.num_pts = QtWidgets.QLineEdit()
self.num_pts.setValidator(QtGui.QIntValidator())
gridLayout.addWidget(self.num_pts, 2, 1, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox()
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2)
self.setLayout(gridLayout)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
@property
def values(self):
try:
xmin = float(self.min_line.text())
xmax = float(self.max_line.text())
nums = int(self.num_pts.text())
except TypeError:
return None
return xmin, xmax, nums

View File

@ -11,10 +11,10 @@ from pyqtgraph import ViewBox
from nmreval.configs import *
from .management import UpperManagement
from ..Qt import QtCore, QtGui, QtPrintSupport, QtWidgets
from ..Qt import QtGui, QtPrintSupport
from ..data.shift_graphs import QShift
from ..data.signaledit import QApodDialog, QBaselineDialog, QPhasedialog
from ..fit.result import QFitResult
from ..fit.result import FitExtension, QFitResult
from ..graphs.graphwindow import QGraphWindow
from ..graphs.movedialog import QMover
from ..io.fcbatchreader import QFCReader
@ -164,6 +164,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.datawidget.startShowProperty.connect(self.management.get_properties)
self.datawidget.propertyChanged.connect(self.management.update_property)
self.datawidget.tree.saveFits.connect(self.save_fit_parameter)
self.datawidget.tree.extendFits.connect(self.extend_fit)
self.management.newData.connect(self.show_new_data)
self.management.newGraph.connect(self.new_graph)
@ -907,10 +908,10 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
res_dialog.redoFit.connect(self.management.redo_fits)
res_dialog.show()
@QtCore.pyqtSlot(dict, list, str, bool, dict)
def accepts_fit(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: dict) -> None:
@QtCore.pyqtSlot(dict, list, str, bool, bool, list)
def accepts_fit(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: bool, extrapolate: list) -> None:
self.fit_dialog.set_parameter(res)
self.management.make_fits(res, opts, param_graph, show_fit, parts)
self.management.make_fits(res, opts, param_graph, show_fit, parts, extrapolate)
@QtCore.pyqtSlot(name='on_actionFunction_editor_triggered')
def edit_models(self):
@ -922,6 +923,16 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.editor.setWindowModality(QtCore.Qt.ApplicationModal)
self.editor.show()
@QtCore.pyqtSlot(list)
def extend_fit(self, sets: list):
w = FitExtension(self)
res = w.exec()
print(res)
if res:
p = w.values
x = linspace(p[0], p[1], num=p[2])
self.management.extend_fits(sets, x)
@QtCore.pyqtSlot(name='on_action_create_fit_function_triggered')
def open_fitmodel_wizard(self):
from ..fit.function_creation_dialog import QUserFitCreator
@ -931,7 +942,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
helper.show()
@QtCore.pyqtSlot(name='on_actionShift_triggered')
def shift_dialog(self):
s = QShift(self)

View File

@ -4,6 +4,8 @@ import pathlib
import re
import uuid
import numpy as np
from nmreval.fit import data as fit_d
from nmreval.fit.model import Model
from nmreval.fit.result import FitResult
@ -482,7 +484,7 @@ class UpperManagement(QtCore.QObject):
parameter[set_id] = (new_values, set_parameter[1])
self.start_fit(*self.__fit_options)
def make_fits(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: dict) -> None:
def make_fits(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: bool, extrapolate: list) -> None:
"""
Args:
@ -491,6 +493,7 @@ class UpperManagement(QtCore.QObject):
param_graph: None if no parameter to plot, '' for new graph, or id of existig graph
show_fit: plot fit curve?
parts: key is that of original data, value is list of subplots
extrapolate:
"""
f_id_list = []
@ -503,6 +506,26 @@ class UpperManagement(QtCore.QObject):
if reject:
continue
if not all(e is None for e in extrapolate):
spacefunc = np.geomspace if fit.islog else np.linspace
xmin = fit.x.min()
xmax = fit.x.max()
len_data = len(fit.x_data)
num_pts = 20*len_data-9 if len_data < 51 else 3*len_data
if extrapolate[0] is not None:
xmin = extrapolate[0]
if extrapolate[1] is not None:
xmax = extrapolate[1]
if extrapolate[2] is not None:
num_pts = extrapolate[2]
_x = spacefunc(xmin, xmax, num=num_pts)
fit = fit.with_new_x(_x)
data_k = self.data[k]
if delete_prev:
tobedeleted.extend([f.id for f in data_k.get_fits()])
@ -527,18 +550,16 @@ class UpperManagement(QtCore.QObject):
f_id_list.append(f_id)
data_k.set_fits(f_id)
if parts:
color_scheme = available_cycles['colorblind']
for subfunc, col in zip(fit.sub(fit.x), cycle(color_scheme)):
subfunc.value = data_k.value
subfunc.group = data_k.group
subfunc.name += data_name
sub_f_id = self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No)
f_id_list.append(sub_f_id)
gid = data_k.graph
if k in parts and show_fit:
color_scheme = available_cycles['colorblind']
for subfunc, col in zip(parts[k], cycle(color_scheme)):
subfunc.value = data_k.value
subfunc.group = data_k.group
subfunc.name += data_name
sub_f_id = self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No)
f_id_list.append(sub_f_id)
self.delete_sets(tobedeleted)
if accepted and (param_graph != '-1'):
@ -546,6 +567,27 @@ class UpperManagement(QtCore.QObject):
self.newData.emit(f_id_list, gid)
def extend_fits(self, set_id: list, x_range: np.ndarray):
graphs = {}
for sid in set_id:
data = self[sid]
fit = data.copy(full=True, keep_color=True)
fit.data = fit.data.with_new_x(x_range)
graph_id = data.graph
if graph_id not in graphs:
graphs[graph_id] = []
graphs[graph_id].append(self.add(fit))
color_scheme = available_cycles['colorblind']
for subfunc, col in zip(fit.data.sub(fit.x), cycle(color_scheme)):
subfunc.value = fit.value
subfunc.group = fit.group
graphs[graph_id].append(self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No))
for k, v in graphs.items():
self.newData.emit(v, k)
def make_fit_parameter(self, fit_sets: list[str | FitResult], graph_id: str = None):
fit_dict = self._collect_fit_parameter(fit_sets)

View File

@ -126,12 +126,12 @@ class Model(object):
kwargs = self.fun_kwargs
if not self.is_multi:
return [self.func(p, x, **kwargs)]
return []
else:
return list(self._int_iter(x, *p, *self.fun_args, **kwargs))
def sub_name(self):
if not self.is_multi:
return [self.name]
return []
else:
return list(self._iter_name())

View File

@ -9,6 +9,7 @@ import numpy as np
from scipy.stats import f as fdist
from scipy.interpolate import interp1d
from ._meta import MultiModel
from .parameter import Parameter
from ..data.points import Points
from ..data.signals import Signal
@ -17,7 +18,7 @@ from ..utils.text import convert
class FitResultCreator:
@staticmethod
def make_from_session(x_orig: np.ndarray, y_orig: np.ndarray, idx: int, kwargs: dict[Any]) -> (dict, list):
def make_from_session(x_orig: np.ndarray, y_orig: np.ndarray, idx: int, kwargs: dict[Any]) -> FitResult:
params = OrderedDict()
for key, pbest, err in zip(kwargs['pnames'], kwargs['parameter'], kwargs['error']):
@ -37,10 +38,10 @@ class FitResultCreator:
stats = FitResultCreator.calc_statistics(resid, _y)
return FitResult(kwargs['x'], kwargs['y'], x_orig, y_orig, params, dict(kwargs['choice']), resid, 0, 0,
kwargs['name'], stats, idx), []
kwargs['name'], stats, idx)
@staticmethod
def make_with_model(model, x_orig, y_orig, p, fun_kwargs, idx, nobs, nvar, corr, pcorr) -> (dict, list):
def make_with_model(model, x_orig, y_orig, p, fun_kwargs, idx, nobs, nvar, corr, pcorr) -> FitResult:
if np.all(x_orig > 0) and (np.max(x_orig) > 100 * np.min(x_orig)):
islog = True
else:
@ -48,7 +49,7 @@ class FitResultCreator:
if len(x_orig) < 51:
if islog:
_x = np.logspace(np.log10(np.min(x_orig)), np.log10(np.max(x_orig)), num=10*x_orig.size-9)
_x = np.geomspace(np.min(x_orig), np.max(x_orig), num=10*x_orig.size-9)
else:
_x = np.linspace(np.min(x_orig), np.max(x_orig), num=10*x_orig.size-9)
else:
@ -62,15 +63,6 @@ class FitResultCreator:
parameters = OrderedDict([(k, v) for k, v in zip(pnames, p)])
p_final = [p.value for p in parameters.values()]
part_functions = []
if model.is_multi:
for sub_name, sub_y in zip(model.sub_name(), model.sub(p_final, _x, **fun_kwargs)):
if np.iscomplexobj(sub_y):
part_functions.append(Signal(_x, sub_y, name=sub_name))
else:
part_functions.append(Points(_x, sub_y, name=sub_name))
_y = model.func(p_final, _x, **fun_kwargs)
resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig
@ -97,13 +89,10 @@ class FitResultCreator:
correlation = corr
partial_correlation = pcorr
return (
FitResult(_x, _y, x_orig, y_orig, parameters, fun_kwargs, resid,
nobs, nvar, model.name, stats,
idx=idx, corr=correlation, pcorr=partial_correlation,
islog=islog),
part_functions,
)
return FitResult(_x, _y, x_orig, y_orig, parameters, fun_kwargs, resid,
nobs, nvar, model.name, stats,
idx=idx, corr=correlation, pcorr=partial_correlation,
islog=islog, func=model)
@staticmethod
def calc_statistics(y, residual, nobs=None, nvar=None):
@ -141,8 +130,11 @@ class FitResultCreator:
class FitResult(Points):
def __init__(self, x, y, x_data, y_data, params, fun_kwargs, resid, nobs, nvar, name, stats,
idx=None, corr=None, pcorr=None, islog=False,
def __init__(self, x: np.ndarray, y: np.ndarray,
x_data: np.ndarray, y_data: np.ndarray,
params: dict, fun_kwargs: dict,
resid: np.ndarray, nobs: int, nvar: int, name: str, stats: dict,
idx=None, corr=None, pcorr=None, islog=False, func=None,
**kwargs):
self.parameter, name = self._prepare_names(params, name)
@ -162,6 +154,7 @@ class FitResult(Points):
self.x_data = x_data
self.y_data = y_data
self._model_name = name
self._func = func
@staticmethod
def _prepare_names(parameter: dict, modelname: str):
@ -200,6 +193,13 @@ class FitResult(Points):
except AttributeError:
return 'FitObject'
@property
def func(self):
if isinstance(self._func, MultiModel):
return self._func.func
else:
return self._func
@property
def p_final(self):
return [pp.value for pp in self.parameter.values()]
@ -215,6 +215,7 @@ class FitResult(Points):
print(' #var :', self.nvar)
print('\nParameter')
print(self._parameter_string())
if statistics:
print('Statistics')
for k, v in self.statistics.items():
@ -342,3 +343,24 @@ class FitResult(Points):
data = FitResult(**state)
return data
def with_new_x(self, x_values):
if self.func is None:
raise ValueError('no fit function available to calcualate new y values')
new_fit = self.copy()
y_values = self.func.func(self.p_final, x_values, **self.fun_kwargs)
new_fit.set_data(x_values, y_values)
return new_fit
def sub(self, x_values):
part_functions = []
for sub_name, sub_y in zip(self.func.sub_name(), self.func.sub(self.p_final, x_values, **self.fun_kwargs)):
if np.iscomplexobj(sub_y):
part_functions.append(Signal(x_values, sub_y, name=sub_name))
else:
part_functions.append(Points(x_values, sub_y, name=sub_name))
return part_functions

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>817</width>
<height>584</height>
<width>864</width>
<height>649</height>
</rect>
</property>
<property name="windowTitle">
@ -98,7 +98,37 @@
<property name="spacing">
<number>3</number>
</property>
<item row="1" column="1">
<item row="1" column="0">
<widget class="QCheckBox" name="extrapolate_box">
<property name="toolTip">
<string>Extrapolates only main function</string>
</property>
<property name="text">
<string>Extrapolate curves</string>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QCheckBox" name="parameter_checkbox">
<property name="text">
<string>Plot parameter</string>
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="QComboBox" name="graph_comboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QCheckBox" name="graph_checkBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@ -107,46 +137,88 @@
</sizepolicy>
</property>
<property name="text">
<string>New graph</string>
<string>New graph for parameter</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="minx_line">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Leave empty to start at lowest point</string>
</property>
<property name="placeholderText">
<string>min x</string>
</property>
</widget>
</item>
<item row="0" column="4" rowspan="2">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="graph_comboBox">
<widget class="QLineEdit" name="maxx_line">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="curve_checkbox">
<property name="text">
<string>Plot fit curve</string>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="checked">
<bool>true</bool>
<property name="toolTip">
<string>Leave empty to start at highest point</string>
</property>
<property name="placeholderText">
<string>max x</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="partial_checkBox">
<property name="text">
<string>Plot partial functions</string>
<item row="1" column="3">
<widget class="QLineEdit" name="numx_line">
<property name="enabled">
<bool>false</bool>
</property>
<property name="placeholderText">
<string># pts</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="parameter_checkbox">
<property name="text">
<string>Plot parameter</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<item row="0" column="0" colspan="4">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="curve_checkbox">
<property name="text">
<string>Plot fit curve</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="partial_checkBox">
<property name="text">
<string>Plot partial functions</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@ -180,7 +252,7 @@
</widget>
</item>
<item row="0" column="1" rowspan="3">
<widget class="QToolBox" name="stack">
<widget class="QTabWidget" name="stack">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@ -190,19 +262,8 @@
<property name="currentIndex">
<number>0</number>
</property>
<property name="tabSpacing">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>399</width>
<height>346</height>
</rect>
</property>
<attribute name="label">
<widget class="QWidget" name="stackPage1" native="true">
<attribute name="title">
<string>Plot</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
@ -246,16 +307,8 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>399</width>
<height>346</height>
</rect>
</property>
<attribute name="label">
<widget class="QWidget" name="stackPage2" native="true">
<attribute name="title">
<string>Statistics</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -296,16 +349,8 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>399</width>
<height>346</height>
</rect>
</property>
<attribute name="label">
<widget class="QWidget" name="stackPage3" native="true">
<attribute name="title">
<string>Correlations</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">