diff --git a/.gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml b/.gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml
index c81f466..f7dc7d4 100644
--- a/.gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml
+++ b/.gitea/ISSUE_TEMPLATE/BUG_TEMPLATE.yml
@@ -17,7 +17,7 @@ body:
description: For which version have you observed this behavior?
placeholder: You find the program version in "Help/About"
validations:
- required: true
+ required: false
- type: textarea
attributes:
label: Expected behavior
diff --git a/src/gui_qt/_py/basewindow.py b/src/gui_qt/_py/basewindow.py
index 3b24795..d916548 100644
--- a/src/gui_qt/_py/basewindow.py
+++ b/src/gui_qt/_py/basewindow.py
@@ -365,6 +365,9 @@ class Ui_BaseWindow(object):
self.actionBinning.setObjectName("actionBinning")
self.actionTNMH = QtWidgets.QAction(BaseWindow)
self.actionTNMH.setObjectName("actionTNMH")
+ self.actionExclude_region = QtWidgets.QAction(BaseWindow)
+ self.actionExclude_region.setCheckable(True)
+ self.actionExclude_region.setObjectName("actionExclude_region")
self.menuSave.addAction(self.actionSave)
self.menuSave.addAction(self.actionExportGraphic)
self.menuSave.addAction(self.action_save_fit_parameter)
@@ -419,6 +422,7 @@ class Ui_BaseWindow(object):
self.menuLimits.addAction(self.action_no_range)
self.menuLimits.addAction(self.action_x_range)
self.menuLimits.addAction(self.action_custom_range)
+ self.menuLimits.addAction(self.actionExclude_region)
self.menuFit.addAction(self.action_FitWidget)
self.menuFit.addSeparator()
self.menuFit.addAction(self.action_create_fit_function)
@@ -631,6 +635,7 @@ class Ui_BaseWindow(object):
self.actionTNMH_model.setText(_translate("BaseWindow", "Tg , Hodge, TNMH,,,"))
self.actionBinning.setText(_translate("BaseWindow", "Binning..."))
self.actionTNMH.setText(_translate("BaseWindow", "TNMH..."))
+ self.actionExclude_region.setText(_translate("BaseWindow", "Exclude region"))
from ..data.datawidget.datawidget import DataWidget
from ..data.integral_widget import IntegralWidget
from ..data.point_select import PointSelectWidget
diff --git a/src/gui_qt/_py/fitresult.py b/src/gui_qt/_py/fitresult.py
index 54becc9..0f73b79 100644
--- a/src/gui_qt/_py/fitresult.py
+++ b/src/gui_qt/_py/fitresult.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'resources/_ui/fitresult.ui'
+# Form implementation generated from reading ui file 'src/resources/_ui/fitresult.ui'
#
# Created by: PyQt5 UI code generator 5.15.10
#
@@ -157,67 +157,91 @@ 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 = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, 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)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.graph_checkBox.sizePolicy().hasHeightForWidth())
- self.graph_checkBox.setSizePolicy(sizePolicy)
- self.graph_checkBox.setChecked(True)
- self.graph_checkBox.setObjectName("graph_checkBox")
- self.gridLayout_2.addWidget(self.graph_checkBox, 1, 5, 1, 1)
+ self.gridLayout_2.addWidget(self.graph_comboBox, 1, 7, 1, 1)
self.minx_line = QtWidgets.QLineEdit(self.groupBox)
self.minx_line.setEnabled(False)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, 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.extrapolate_box = QtWidgets.QCheckBox(self.groupBox)
+ self.extrapolate_box.setObjectName("extrapolate_box")
+ self.gridLayout_2.addWidget(self.extrapolate_box, 1, 0, 1, 1)
+ self.numx_line = QtWidgets.QLineEdit(self.groupBox)
+ self.numx_line.setEnabled(False)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.numx_line.sizePolicy().hasHeightForWidth())
+ self.numx_line.setSizePolicy(sizePolicy)
+ self.numx_line.setObjectName("numx_line")
+ self.gridLayout_2.addWidget(self.numx_line, 1, 3, 1, 1)
+ self.graph_checkBox = QtWidgets.QCheckBox(self.groupBox)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.graph_checkBox.sizePolicy().hasHeightForWidth())
+ self.graph_checkBox.setSizePolicy(sizePolicy)
+ self.graph_checkBox.setChecked(True)
+ self.graph_checkBox.setObjectName("graph_checkBox")
+ self.gridLayout_2.addWidget(self.graph_checkBox, 1, 6, 1, 1)
self.maxx_line = QtWidgets.QLineEdit(self.groupBox)
self.maxx_line.setEnabled(False)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, 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.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, 5, 2, 1)
+ self.newx_log_checkbox = QtWidgets.QCheckBox(self.groupBox)
+ self.newx_log_checkbox.setEnabled(False)
+ self.newx_log_checkbox.setObjectName("newx_log_checkbox")
+ self.gridLayout_2.addWidget(self.newx_log_checkbox, 1, 4, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.curve_checkbox = QtWidgets.QCheckBox(self.groupBox)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.curve_checkbox.sizePolicy().hasHeightForWidth())
+ self.curve_checkbox.setSizePolicy(sizePolicy)
self.curve_checkbox.setChecked(True)
self.curve_checkbox.setObjectName("curve_checkbox")
self.horizontalLayout.addWidget(self.curve_checkbox)
self.partial_checkBox = QtWidgets.QCheckBox(self.groupBox)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.partial_checkBox.sizePolicy().hasHeightForWidth())
+ self.partial_checkBox.setSizePolicy(sizePolicy)
self.partial_checkBox.setObjectName("partial_checkBox")
self.horizontalLayout.addWidget(self.partial_checkBox)
- self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 4)
+ self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 5)
+ self.parameter_checkbox = QtWidgets.QCheckBox(self.groupBox)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.parameter_checkbox.sizePolicy().hasHeightForWidth())
+ self.parameter_checkbox.setSizePolicy(sizePolicy)
+ self.parameter_checkbox.setObjectName("parameter_checkbox")
+ self.gridLayout_2.addWidget(self.parameter_checkbox, 0, 6, 1, 2)
self.gridLayout.addWidget(self.groupBox, 7, 0, 1, 2)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Retry)
@@ -253,16 +277,17 @@ class Ui_Dialog(object):
self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits of this set"))
self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit"))
self.groupBox.setTitle(_translate("Dialog", "Output"))
- 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.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function"))
+ self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves"))
+ self.numx_line.setPlaceholderText(_translate("Dialog", "# pts"))
+ self.graph_checkBox.setText(_translate("Dialog", "New graph for parameter"))
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.newx_log_checkbox.setText(_translate("Dialog", "log-spaced?"))
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"))
from ..lib.forms import ElideComboBox
from pyqtgraph import GraphicsLayoutWidget
diff --git a/src/gui_qt/data/datawidget/datawidget.py b/src/gui_qt/data/datawidget/datawidget.py
index 45ffc52..e24c10d 100644
--- a/src/gui_qt/data/datawidget/datawidget.py
+++ b/src/gui_qt/data/datawidget/datawidget.py
@@ -49,11 +49,16 @@ class DataTree(QtWidgets.QTreeWidget):
def add_graph(self, idd: str, name: str):
item = QtWidgets.QTreeWidgetItem()
- item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable |
- QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
+ item.setFlags(
+ QtCore.Qt.ItemFlag.ItemIsSelectable |
+ QtCore.Qt.ItemFlag.ItemIsDropEnabled |
+ QtCore.Qt.ItemFlag.ItemIsEditable |
+ QtCore.Qt.ItemFlag.ItemIsEnabled |
+ QtCore.Qt.ItemFlag.ItemIsUserCheckable
+ )
item.setText(0, name)
- item.setData(0, QtCore.Qt.UserRole, idd)
- item.setCheckState(0, QtCore.Qt.Checked)
+ item.setData(0, QtCore.Qt.ItemDataRole.UserRole, idd)
+ item.setCheckState(0, QtCore.Qt.CheckState.Checked)
self.addTopLevelItem(item)
self._checked_graphs.add(idd)
@@ -67,14 +72,19 @@ class DataTree(QtWidgets.QTreeWidget):
for row in range(self.invisibleRootItem().childCount()):
graph = self.invisibleRootItem().child(row)
- if graph.data(0, QtCore.Qt.UserRole) == gid:
+ if graph.data(0, QtCore.Qt.ItemDataRole.UserRole) == gid:
for (idd, name, value) in items:
item = QtWidgets.QTreeWidgetItem([name])
item.setToolTip(0, f'Value: {value}')
- item.setData(0, QtCore.Qt.UserRole, idd)
- item.setCheckState(0, QtCore.Qt.Checked)
- item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsEditable |
- QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
+ item.setData(0, QtCore.Qt.ItemDataRole.UserRole, idd)
+ item.setCheckState(0, QtCore.Qt.CheckState.Checked)
+ item.setFlags(
+ QtCore.Qt.ItemFlag.ItemIsSelectable |
+ QtCore.Qt.ItemFlag.ItemIsDragEnabled |
+ QtCore.Qt.ItemFlag.ItemIsEditable |
+ QtCore.Qt.ItemFlag.ItemIsEnabled |
+ QtCore.Qt.ItemFlag.ItemIsUserCheckable
+ )
graph.addChild(item)
self._checked_sets.add(idd)
@@ -85,8 +95,8 @@ class DataTree(QtWidgets.QTreeWidget):
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem)
def data_change(self, item: QtWidgets.QTreeWidgetItem, emit: bool = True) -> tuple[set, set]:
- idd = item.data(0, QtCore.Qt.UserRole)
- is_selected = item.checkState(0) == QtCore.Qt.Checked
+ idd = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
+ is_selected = item.checkState(0) == QtCore.Qt.CheckState.Checked
to_be_hidden = set()
to_be_shown = set()
@@ -104,9 +114,9 @@ class DataTree(QtWidgets.QTreeWidget):
self.blockSignals(True)
for i in range(item.childCount()):
child = item.child(i)
- child.setCheckState(0, QtCore.Qt.Checked)
- to_be_shown.add(child.data(0, QtCore.Qt.UserRole))
- self._checked_sets.add(child.data(0, QtCore.Qt.UserRole))
+ child.setCheckState(0, QtCore.Qt.CheckState.Checked)
+ to_be_shown.add(child.data(0, QtCore.Qt.ItemDataRole.UserRole))
+ self._checked_sets.add(child.data(0, QtCore.Qt.ItemDataRole.UserRole))
self.blockSignals(False)
# check state change to unchecked
@@ -115,10 +125,10 @@ class DataTree(QtWidgets.QTreeWidget):
self.blockSignals(True)
for i in range(item.childCount()):
child = item.child(i)
- child.setCheckState(0, QtCore.Qt.Unchecked)
- to_be_hidden.add(child.data(0, QtCore.Qt.UserRole))
+ child.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
+ to_be_hidden.add(child.data(0, QtCore.Qt.ItemDataRole.UserRole))
try:
- self._checked_sets.remove(child.data(0, QtCore.Qt.UserRole))
+ self._checked_sets.remove(child.data(0, QtCore.Qt.ItemDataRole.UserRole))
except KeyError:
pass
self.blockSignals(False)
@@ -153,7 +163,7 @@ class DataTree(QtWidgets.QTreeWidget):
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem)
def new_selection(self, item: QtWidgets.QTreeWidgetItem):
if item.parent() is None:
- self.management.select_window(item.data(0, QtCore.Qt.UserRole))
+ self.management.select_window(item.data(0, QtCore.Qt.ItemDataRole.UserRole))
def dropEvent(self, evt: QtGui.QDropEvent):
dropped_index = self.indexAt(evt.pos())
@@ -179,7 +189,7 @@ class DataTree(QtWidgets.QTreeWidget):
from_parent.removeChild(it)
tobemoved.append(it)
- take_from.append(from_parent.data(0, QtCore.Qt.UserRole))
+ take_from.append(from_parent.data(0, QtCore.Qt.ItemDataRole.UserRole))
pos = QtCore.QModelIndex(persistent_drop)
if self.dropIndicatorPosition() == QtWidgets.QAbstractItemView.BelowItem:
@@ -191,8 +201,8 @@ class DataTree(QtWidgets.QTreeWidget):
else:
to_parent.insertChildren(row, tobemoved)
- self.management.move_sets([it.data(0, QtCore.Qt.UserRole) for it in tobemoved],
- to_parent.data(0, QtCore.Qt.UserRole), take_from,
+ self.management.move_sets([it.data(0, QtCore.Qt.ItemDataRole.UserRole) for it in tobemoved],
+ to_parent.data(0, QtCore.Qt.ItemDataRole.UserRole), take_from,
pos=-1 if append else row)
self.update_indexes()
@@ -207,7 +217,7 @@ class DataTree(QtWidgets.QTreeWidget):
while iterator.value():
item = iterator.value()
if item is not None:
- data = item.data(0, QtCore.Qt.UserRole)
+ data = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
if data == gid_out:
from_parent = item
@@ -231,7 +241,7 @@ class DataTree(QtWidgets.QTreeWidget):
self.blockSignals(False)
def sort(self, graph_item: QtWidgets.QTreeWidgetItem, mode: str = 'value'):
- graph_id = graph_item.data(0, QtCore.Qt.UserRole)
+ graph_id = graph_item.data(0, QtCore.Qt.ItemDataRole.UserRole)
sets = self.management.get_attributes(graph_id, mode)
sets = [el[0] for el in sorted(sets.items(), key=lambda x: x[1])]
@@ -243,7 +253,7 @@ class DataTree(QtWidgets.QTreeWidget):
for s in sets:
for c in children:
- if c.data(0, QtCore.Qt.UserRole) == s:
+ if c.data(0, QtCore.Qt.ItemDataRole.UserRole) == s:
graph_item.addChild(c)
self.update_indexes()
@@ -276,7 +286,7 @@ class DataTree(QtWidgets.QTreeWidget):
while iterator.value():
item = iterator.value()
if item is not None:
- data = item.data(0, QtCore.Qt.UserRole)
+ data = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
if data == sid:
if name != item.text(0):
item.setText(0, name)
@@ -285,7 +295,7 @@ class DataTree(QtWidgets.QTreeWidget):
iterator += 1
def keyPressEvent(self, evt: QtGui.QKeyEvent):
- if evt.key() == QtCore.Qt.Key_Delete:
+ if evt.key() == QtCore.Qt.Key.Key_Delete:
rm_sets = []
rm_graphs = []
for idx in self.selectedIndexes():
@@ -296,20 +306,20 @@ class DataTree(QtWidgets.QTreeWidget):
if item.parent() is None:
for c_i in range(item.childCount()):
# add sets inside graph to removal
- child_data = item.child(c_i).data(0, QtCore.Qt.UserRole)
+ child_data = item.child(c_i).data(0, QtCore.Qt.ItemDataRole.UserRole)
if child_data not in rm_sets:
rm_sets.append(child_data)
- rm_graphs.append(item.data(0, QtCore.Qt.UserRole))
+ rm_graphs.append(item.data(0, QtCore.Qt.ItemDataRole.UserRole))
else:
- item_data = item.data(0, QtCore.Qt.UserRole)
+ item_data = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
if item_data not in rm_sets:
rm_sets.append(item_data)
# self.deleteItem.emit(rm_sets+rm_graphs)
self.management.delete_sets(rm_sets+rm_graphs)
- elif evt.key() == QtCore.Qt.Key_Space:
+ elif evt.key() == QtCore.Qt.Key.Key_Space:
sets = []
from_parent = []
@@ -329,7 +339,7 @@ class DataTree(QtWidgets.QTreeWidget):
for it in sets:
if it in from_parent:
continue
- it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked)
+ it.setCheckState(0, QtCore.Qt.CheckState.Unchecked if it.checkState(0) == QtCore.Qt.CheckState.Checked else QtCore.Qt.CheckState.Checked)
s1, s2 = self.data_change(it, emit=False)
to_be_hidden |= s2
to_be_shown |= s1
@@ -353,7 +363,7 @@ class DataTree(QtWidgets.QTreeWidget):
# find all items that have to be removed
while iterator.value():
item = iterator.value()
- _id = item.data(0, QtCore.Qt.UserRole)
+ _id = item.data(0, QtCore.Qt.ItemDataRole.UserRole)
if _id in ids:
try:
item_parent = item.parent()
@@ -431,7 +441,7 @@ class DataTree(QtWidgets.QTreeWidget):
if i.column() == 0:
continue
items.append(self.itemFromIndex(i))
- graphs.append(self.itemFromIndex(i).data(0, QtCore.Qt.UserRole))
+ graphs.append(self.itemFromIndex(i).data(0, QtCore.Qt.ItemDataRole.UserRole))
if action == del_action:
for gid in graphs:
@@ -473,12 +483,12 @@ class DataTree(QtWidgets.QTreeWidget):
continue
else:
- graph_id = parent.data(0, QtCore.Qt.UserRole)
+ graph_id = parent.data(0, QtCore.Qt.ItemDataRole.UserRole)
if graph_id not in idx:
idx[graph_id] = []
# collect sets in their graph
- idx[graph_id].append(item.data(0, QtCore.Qt.UserRole))
- data = self.management[item.data(0, QtCore.Qt.UserRole)]
+ idx[graph_id].append(item.data(0, QtCore.Qt.ItemDataRole.UserRole))
+ data = self.management[item.data(0, QtCore.Qt.ItemDataRole.UserRole)]
if data.mode == 'fit':
has_fits = True
@@ -523,7 +533,7 @@ class DataTree(QtWidgets.QTreeWidget):
while iterator.value():
item = iterator.value()
if item is not None:
- if item.data(0, QtCore.Qt.UserRole) == gid:
+ if item.data(0, QtCore.Qt.ItemDataRole.UserRole) == gid:
item.setBackground(0, QtGui.QBrush(QtGui.QColor('gray')))
else:
item.setBackground(0, QtGui.QBrush())
@@ -536,10 +546,10 @@ class DataTree(QtWidgets.QTreeWidget):
while iterator.value():
item = iterator.value()
if item is not None:
- if item.data(0, QtCore.Qt.UserRole) in sets:
- item.setCheckState(0, QtCore.Qt.Unchecked)
+ if item.data(0, QtCore.Qt.ItemDataRole.UserRole) in sets:
+ item.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
else:
- self._checked_sets.add(item.data(0, QtCore.Qt.UserRole))
+ self._checked_sets.add(item.data(0, QtCore.Qt.ItemDataRole.UserRole))
iterator += 1
self.blockSignals(False)
@@ -594,7 +604,7 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
sid = []
for i in self.tree.selectedIndexes():
if i.column() == 0:
- sid.append(i.data(role=QtCore.Qt.UserRole))
+ sid.append(i.data(role=QtCore.Qt.ItemDataRole.UserRole))
self.startShowProperty.emit(sid)
@@ -603,15 +613,23 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
self.proptable.populate(props)
def change_property(self, key1, key2, value):
- ids = [item.data(0, QtCore.Qt.UserRole) for item in self.tree.selectedItems()]
if key2 == 'Value':
try:
value = float(value)
except ValueError:
- QtWidgets.QMessageBox.warning(self, 'Invalid entry',
- 'Value %r is not a valid number for `value`.' % value)
+ QtWidgets.QMessageBox.warning(
+ self,
+ 'Invalid entry',
+ f'Value {value!r} is not a valid number for `value`.')
return
+ ids = []
+ for item in self.tree.selectedItems():
+ ids.append(item.data(0, QtCore.Qt.ItemDataRole.UserRole))
+ item.setToolTip(0, str(value))
+ else:
+ ids = [item.data(0, QtCore.Qt.ItemDataRole.UserRole) for item in self.tree.selectedItems()]
+
self.propertyChanged.emit(ids, key1, key2, value)
def uncheck_sets(self, sets: list[str]):
diff --git a/src/gui_qt/data/datawidget/properties.py b/src/gui_qt/data/datawidget/properties.py
index dd07087..cb4266a 100644
--- a/src/gui_qt/data/datawidget/properties.py
+++ b/src/gui_qt/data/datawidget/properties.py
@@ -41,7 +41,7 @@ class PropWidget(QtWidgets.QWidget):
idx = table.indexFromItem(item)
self.propertyChanged.emit(self.tab.tabText(tab_idx),
table.item(idx.row(), idx.column()-1).text(),
- item.data(QtCore.Qt.DisplayRole))
+ item.data(QtCore.Qt.ItemDataRole.DisplayRole))
@QtCore.pyqtSlot(int)
def tab_change(self, idx: int):
@@ -66,10 +66,10 @@ class PropTable(QtWidgets.QTableWidget):
self.blockSignals(True)
for k, v in prop.items():
value_item = QtWidgets.QTableWidgetItem('')
- value_item.setData(QtCore.Qt.DisplayRole, v)
+ value_item.setData(QtCore.Qt.ItemDataRole.DisplayRole, v)
key_item = QtWidgets.QTableWidgetItem(k)
- key_item.setFlags(QtCore.Qt.NoItemFlags)
+ key_item.setFlags(QtCore.Qt.ItemFlag.NoItemFlags)
key_item.setForeground(QtGui.QBrush(QtGui.QColor(0, 0, 0)))
self.setRowCount(self.rowCount()+1)
diff --git a/src/gui_qt/data/valueeditwidget.py b/src/gui_qt/data/valueeditwidget.py
index c85f1d1..ae95a5f 100644
--- a/src/gui_qt/data/valueeditwidget.py
+++ b/src/gui_qt/data/valueeditwidget.py
@@ -3,10 +3,10 @@ from __future__ import annotations
from typing import Any
from numpy import ndarray, iscomplexobj, asarray
-from pyqtgraph import PlotDataItem
from ..Qt import QtGui, QtCore, QtWidgets
from .._py.valueeditor import Ui_MaskDialog
+from ..lib.pg_objects import PlotItem
class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
@@ -35,13 +35,13 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
self.tableView.setModel(self.model)
self.tableView.setSelectionModel(self.selection_model)
- self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.tableView.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(self.ctx)
- self.selection_real = PlotDataItem(x=[], y=[], symbolSize=25, symbol='x',
- pen=None, symbolPen='#c9308e', symbolBrush='#c9308e')
- self.selection_imag = PlotDataItem(x=[], y=[], symbolSize=25, symbol='+',
- pen=None, symbolPen='#dcdcdc', symbolBrush='#dcdcdc')
+ self.selection_real = PlotItem(x=[], y=[], symbolSize=25, symbol='x',
+ pen=None, symbolPen='#c9308e', symbolBrush='#c9308e')
+ self.selection_imag = PlotItem(x=[], y=[], symbolSize=25, symbol='+',
+ pen=None, symbolPen='#dcdcdc', symbolBrush='#dcdcdc')
def __call__(self, items: dict):
self.items = items
@@ -133,7 +133,7 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
def keyPressEvent(self, evt):
if evt.matches(QtGui.QKeySequence.Copy):
self.copy_selection()
- elif evt.key() == QtCore.Qt.Key_Delete:
+ elif evt.key() == QtCore.Qt.Key.Key_Delete:
self.delete_item()
else:
super().keyPressEvent(evt)
@@ -229,7 +229,7 @@ class ValueModel(QtCore.QAbstractTableModel):
"""
itemChanged = QtCore.pyqtSignal(int, int, str)
load_number = 20
- maskRole = QtCore.Qt.UserRole+321
+ maskRole = QtCore.Qt.ItemDataRole.UserRole+321
def __init__(self, parent=None):
super().__init__(parent=parent)
@@ -240,7 +240,7 @@ class ValueModel(QtCore.QAbstractTableModel):
self.mask = None
self.headers = ['x', 'y', '\u0394y']
for i, hd in enumerate(self.headers):
- self.setHeaderData(i, QtCore.Qt.Horizontal, hd)
+ self.setHeaderData(i, QtCore.Qt.Orientation.Horizontal, hd)
def rowCount(self, *args, **kwargs) -> int:
return self.total_rows
@@ -258,25 +258,28 @@ class ValueModel(QtCore.QAbstractTableModel):
self.mask = mask.tolist()
self.endResetModel()
- self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [QtCore.Qt.DisplayRole])
+ self.dataChanged.emit(
+ self.index(0, 0),
+ self.index(0, 1), [QtCore.Qt.ItemDataRole.DisplayRole]
+ )
- def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole) -> Any:
+ def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any:
if not idx.isValid():
return
row = idx.row()
- if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]:
+ if role in [QtCore.Qt.ItemDataRole.DisplayRole, QtCore.Qt.ItemDataRole.EditRole]:
val = self._data[row][idx.column()]
return self.as_string(val)
- elif role == QtCore.Qt.BackgroundRole:
+ elif role == QtCore.Qt.ItemDataRole.BackgroundRole:
pal = QtGui.QGuiApplication.palette()
if not self.mask[row]:
return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Base)
else:
return pal.color(QtGui.QPalette.Base)
- elif role == QtCore.Qt.ForegroundRole:
+ elif role == QtCore.Qt.ItemDataRole.ForegroundRole:
pal = QtGui.QGuiApplication.palette()
if not self.mask[row]:
return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text)
@@ -289,7 +292,7 @@ class ValueModel(QtCore.QAbstractTableModel):
else:
return
- def setData(self, idx: QtCore.QModelIndex, value: str | bool, role=QtCore.Qt.DisplayRole) -> Any:
+ def setData(self, idx: QtCore.QModelIndex, value: str | bool, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any:
col, row = idx.column(), idx.row()
if role == ValueModel.maskRole:
@@ -299,7 +302,7 @@ class ValueModel(QtCore.QAbstractTableModel):
return True
if value:
- if role == QtCore.Qt.EditRole:
+ if role == QtCore.Qt.ItemDataRole.EditRole:
if value == self.as_string(self._data[row][col]):
return True
@@ -322,9 +325,9 @@ class ValueModel(QtCore.QAbstractTableModel):
else:
return False
- def headerData(self, section: int, orientation, role=QtCore.Qt.DisplayRole) -> Any:
- if role == QtCore.Qt.DisplayRole:
- if orientation == QtCore.Qt.Horizontal:
+ def headerData(self, section: int, orientation, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any:
+ if role == QtCore.Qt.ItemDataRole.DisplayRole:
+ if orientation == QtCore.Qt.Orientation.Horizontal:
return self.headers[section]
else:
return str(section+1)
@@ -346,7 +349,7 @@ class ValueModel(QtCore.QAbstractTableModel):
self.endInsertRows()
def flags(self, idx: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag:
- return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemIsEditable
+ return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemFlag.ItemIsEditable
def removeRows(self, pos: int, rows: int, parent=None, *args, **kwargs) -> bool:
self.beginRemoveRows(parent, pos, pos+rows-1)
diff --git a/src/gui_qt/dsc/glass_dialog.py b/src/gui_qt/dsc/glass_dialog.py
index f815b7a..b7afe09 100644
--- a/src/gui_qt/dsc/glass_dialog.py
+++ b/src/gui_qt/dsc/glass_dialog.py
@@ -136,8 +136,8 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
max_x = max(max_x, data.x.max())
item = QtWidgets.QListWidgetItem(name)
- item.setCheckState(QtCore.Qt.Checked)
- item.setData(QtCore.Qt.UserRole, key)
+ item.setCheckState(QtCore.Qt.CheckState.Checked)
+ item.setData(QtCore.Qt.ItemDataRole.UserRole, key)
item.setForeground(mkBrush(c.rgb()))
self.listWidget.addItem(item)
@@ -191,10 +191,10 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
- if item.checkState() == QtCore.Qt.Unchecked:
+ if item.checkState() == QtCore.Qt.CheckState.Unchecked:
continue
- key = item.data(QtCore.Qt.UserRole)
+ key = item.data(QtCore.Qt.ItemDataRole.UserRole)
plot = self._plots[key]
data, _ = self._dsc[key]
@@ -214,7 +214,7 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
item = self.listWidget.item(idx)
tree_item = QtWidgets.QTreeWidgetItem([item.text()])
- values = self._tg_value.get(item.data(QtCore.Qt.UserRole))
+ values = self._tg_value.get(item.data(QtCore.Qt.ItemDataRole.UserRole))
if values is not None:
for name, pos in values.items():
@@ -223,7 +223,7 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
self.tg_tree.addTopLevelItem(tree_item)
- key = item.data(QtCore.Qt.UserRole)
+ key = item.data(QtCore.Qt.ItemDataRole.UserRole)
plot = self._plots[key]
data, _ = self._dsc[key]
@@ -251,7 +251,7 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
@QtCore.pyqtSlot(QtWidgets.QListWidgetItem)
def change_visibility(self, item: QtWidgets.QListWidgetItem):
is_checked = bool(item.checkState())
- plot = self._plots[item.data(QtCore.Qt.UserRole)]
+ plot = self._plots[item.data(QtCore.Qt.ItemDataRole.UserRole)]
for val in plot:
val.setVisible(is_checked)
@@ -275,10 +275,10 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
self.tnmh_tree.clear()
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
- if item.checkState() == QtCore.Qt.Unchecked:
+ if item.checkState() == QtCore.Qt.CheckState.Unchecked:
continue
- key = item.data(QtCore.Qt.UserRole)
+ key = item.data(QtCore.Qt.ItemDataRole.UserRole)
data = self.get_fictive(key, baselines)
@@ -292,7 +292,7 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
item = self.listWidget.item(idx)
tree_item = QtWidgets.QTreeWidgetItem([item.text()])
- values = self._fit.get(item.data(QtCore.Qt.UserRole))
+ values = self._fit.get(item.data(QtCore.Qt.ItemDataRole.UserRole))
if values is not None:
child_item = QtWidgets.QTreeWidgetItem([values.parameter_string()])
@@ -305,10 +305,10 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
ret_dic = {}
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
- if item.checkState() == QtCore.Qt.Unchecked:
+ if item.checkState() == QtCore.Qt.CheckState.Unchecked:
continue
- key = item.data(QtCore.Qt.UserRole)
+ key = item.data(QtCore.Qt.ItemDataRole.UserRole)
cp = None
if self.fictive_export_check.isChecked():
@@ -332,10 +332,10 @@ class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
m = []
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
- if item.checkState() == QtCore.Qt.Unchecked:
+ if item.checkState() == QtCore.Qt.CheckState.Unchecked:
continue
- key = item.data(QtCore.Qt.UserRole)
+ key = item.data(QtCore.Qt.ItemDataRole.UserRole)
data, _ = self._dsc[key]
try:
tg_value = self._tg_value[key][tg_type][0]
diff --git a/src/gui_qt/fit/fit_toolbar.py b/src/gui_qt/fit/fit_toolbar.py
index 4cfa091..283c335 100644
--- a/src/gui_qt/fit/fit_toolbar.py
+++ b/src/gui_qt/fit/fit_toolbar.py
@@ -58,7 +58,8 @@ class FitToolbar(QtWidgets.QToolBar):
@QtCore.pyqtSlot(QtWidgets.QAction)
def change_limit_type(self, action: QtWidgets.QAction):
- is_custom = (action.text() == 'Custom')
+ is_custom = (action.text() in ['Custom', 'Exclude region'])
+ print(is_custom)
for w in [self.label, self.label2, self.lineedit, self.lineedit2]:
w.setEnabled(is_custom)
@@ -93,5 +94,6 @@ class FitToolbar(QtWidgets.QToolBar):
return {
'None': 'none',
'Visible x range': 'x',
- 'Custom': self.region.getRegion(),
+ 'Custom': ('in', self.region.getRegion()),
+ 'Exclude region': ('out', self.region.getRegion()),
}[action_text]
diff --git a/src/gui_qt/fit/result.py b/src/gui_qt/fit/result.py
index ea5ac51..3676175 100644
--- a/src/gui_qt/fit/result.py
+++ b/src/gui_qt/fit/result.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from math import isnan
from pyqtgraph import mkBrush, mkPen, mkColor
@@ -28,6 +30,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
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.extrapolate_box.stateChanged.connect(lambda x: self.newx_log_checkbox.setEnabled(x))
self._previous_fits = {}
self._opts = []
@@ -363,7 +366,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
parts = self.partial_checkBox.checkState() == QtCore.Qt.CheckState.Checked
- extrapolate = [None, None, None]
+ extrapolate = [None, None, None, None]
error = []
if self.extrapolate_box.isChecked():
try:
@@ -379,6 +382,8 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
except (TypeError, ValueError):
error.append('Number of points is missing')
+ extrapolate[3] = self.newx_log_checkbox.isChecked()
+
if error:
msg = QtWidgets.QMessageBox.warning(self, 'Error', 'Extrapolation failed because:\n' + '\n'.join(error))
return
@@ -416,10 +421,13 @@ class FitExtension(QtWidgets.QDialog):
self.num_pts.setValidator(QtGui.QIntValidator())
gridLayout.addWidget(self.num_pts, 2, 1, 1, 1)
+ self.logx_checkbox = QtWidgets.QCheckBox('Log-spaced?')
+ gridLayout.addWidget(self.logx_checkbox, 3, 0, 1, 2)
+
self.buttonBox = QtWidgets.QDialogButtonBox()
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
- gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2)
+ gridLayout.addWidget(self.buttonBox, 4, 0, 1, 2)
self.setLayout(gridLayout)
@@ -427,12 +435,13 @@ class FitExtension(QtWidgets.QDialog):
self.buttonBox.rejected.connect(self.reject)
@property
- def values(self):
+ def values(self) -> tuple[float, float, int, bool] | None:
try:
xmin = float(self.min_line.text())
xmax = float(self.max_line.text())
nums = int(self.num_pts.text())
+ logx = self.logx_checkbox.isChecked()
except TypeError:
return None
- return xmin, xmax, nums
+ return xmin, xmax, nums, logx
diff --git a/src/gui_qt/graphs/graphwindow.py b/src/gui_qt/graphs/graphwindow.py
index 9369bf3..405e520 100644
--- a/src/gui_qt/graphs/graphwindow.py
+++ b/src/gui_qt/graphs/graphwindow.py
@@ -670,11 +670,14 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
else:
if os.path.exists(outfile):
- if QtWidgets.QMessageBox.warning(self, 'Export graphic',
- f'{os.path.split(outfile)[1]} already exists.\n'
- f'Do you REALLY want to replace it?',
- QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
- QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No:
+ if QtWidgets.QMessageBox.warning(
+ self,
+ 'Export graphic',
+ f'{os.path.split(outfile)[1]} already exists.\n'
+ f'Do you REALLY want to replace it?',
+ QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
+ QtWidgets.QMessageBox.No
+ ) == QtWidgets.QMessageBox.No:
return
bg_color = self._bgcolor
@@ -716,16 +719,20 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
logger.exception(f'{item} could not exported because {e.args}')
continue
- if len(item) == 2:
- # plot can show errorbars
- item_dic['yerr'] = item[1].opts['topData']
-
if item_dic:
+ if len(item) == 2:
+ # plot can show errorbars
+ if len(item_dic['x']):
+ item_dic['yerr'] = item[1].opts['topData']
+ else:
+ item_dic['yerr'] = []
dic['items'].append(item_dic)
for item in self._external_items:
try:
- dic['items'].append(item.get_data_opts())
+ item_dic = item.get_data_opts()
+ if item_dic:
+ dic['items'].append(item_dic)
except Exception as e:
logger.exception(f'{item} could not be exported because {e.args}')
continue
diff --git a/src/gui_qt/io/asciireader.py b/src/gui_qt/io/asciireader.py
index a4c9524..2520844 100644
--- a/src/gui_qt/io/asciireader.py
+++ b/src/gui_qt/io/asciireader.py
@@ -38,7 +38,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
self.changestaggeredrange(0)
self.ascii_table.contextMenuEvent = self.ctx_table
- self.ascii_table.horizontalHeader().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.ascii_table.horizontalHeader().setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.ascii_table.horizontalHeader().customContextMenuRequested.connect(self.ctx_table)
self.skip = False
@@ -65,7 +65,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
self.set_column_names(1)
self.skippy_checkbox.blockSignals(True)
- self.skippy_checkbox.setCheckState(QtCore.Qt.Unchecked)
+ self.skippy_checkbox.setCheckState(QtCore.Qt.CheckState.Unchecked)
self.skippy_checkbox.blockSignals(False)
return self
@@ -232,7 +232,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
@QtCore.pyqtSlot(int, name='on_buttonGroup_buttonClicked')
def show_error(self, val: int):
- self.deltay_lineEdit.setEnabled(val == -3)
+ self.deltay_lineEdit.setEnabled(val == -2)
@QtCore.pyqtSlot(int, name='on_skippy_checkbox_stateChanged')
def skip_next_dial(self, _: int):
diff --git a/src/gui_qt/io/dscreader.py b/src/gui_qt/io/dscreader.py
index b3b0768..1ebda46 100644
--- a/src/gui_qt/io/dscreader.py
+++ b/src/gui_qt/io/dscreader.py
@@ -78,14 +78,22 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
for opts in self.sample.steps:
item = QtWidgets.QListWidgetItem()
- item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable)
- item.setCheckState(QtCore.Qt.Unchecked)
+ item.setFlags(
+ QtCore.Qt.ItemFlag.ItemIsEnabled |
+ QtCore.Qt.ItemFlag.ItemIsSelectable |
+ QtCore.Qt.ItemFlag.ItemIsUserCheckable
+ )
+ item.setCheckState(QtCore.Qt.CheckState.Unchecked)
if opts[0] == 'i':
- item.setFlags(QtCore.Qt.NoItemFlags)
+ item.setFlags(QtCore.Qt.ItemFlag.NoItemFlags)
item.setText(f'{opts[1]:.2f} K for {opts[2] / 60:.0f} min')
else:
- item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable)
+ item.setFlags(
+ QtCore.Qt.ItemFlag.ItemIsEnabled |
+ QtCore.Qt.ItemFlag.ItemIsSelectable |
+ QtCore.Qt.ItemFlag.ItemIsUserCheckable
+ )
item.setText(f'{opts[2]:.2f} K to {opts[3]:.2f} K with {opts[1]} K/min')
self.step_listWidget.addItem(item)
@@ -97,7 +105,12 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
if empty:
self.empty = self.calibrator.set_measurement(empty, mode='empty')
- self.empty_label.setText('~/' + str(self.empty.fname.relative_to(Path.home())))
+
+ # avoid ValueError breaking data update
+ if self.empty.fname.is_relative_to(Path.home()):
+ self.empty_label.setText('~/' + str(self.empty.fname.relative_to(Path.home())))
+ else:
+ self.empty_label.setText(str(self.empty.fname))
self.update_plots()
@@ -118,8 +131,8 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
self.references.append(ref)
item = QtWidgets.QTableWidgetItem(str(ref.fname.name))
- item.setData(QtCore.Qt.UserRole, ref.fname)
- item.setFlags(QtCore.Qt.ItemIsEnabled)
+ item.setData(QtCore.Qt.ItemDataRole.UserRole, ref.fname)
+ item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
rowcnt = self.reference_tableWidget.rowCount()
self.reference_tableWidget.setRowCount(rowcnt+1)
@@ -132,7 +145,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
@QtCore.pyqtSlot(name='on_ref_remove_pushButton_clicked')
def remove_reference(self):
idx = self.reference_tableWidget.currentRow()
- self.calibrator.remove_reference(self.reference_tableWidget.item(idx, 0).data(QtCore.Qt.UserRole))
+ self.calibrator.remove_reference(self.reference_tableWidget.item(idx, 0).data(QtCore.Qt.ItemDataRole.UserRole))
self.reference_tableWidget.removeRow(idx)
self.update_plots()
@@ -145,10 +158,10 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
for row in range(self.step_listWidget.count()):
if idx == row:
continue
- self.step_listWidget.item(row).setCheckState(QtCore.Qt.Unchecked)
+ self.step_listWidget.item(row).setCheckState(QtCore.Qt.CheckState.Unchecked)
self.step_listWidget.blockSignals(False)
- if item.checkState() == QtCore.Qt.Checked:
+ if item.checkState() == QtCore.Qt.CheckState.Checked:
mode, rate, _, _ = self.sample.steps[idx]
self.current_run = (rate, mode)
self.sample_idx = idx
@@ -217,6 +230,8 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
if empty_data is not None:
self.empty_sample.setData(x=empty_data[0], y=empty_data[1])
+ else:
+ self.empty_sample.setData(x=[], y=[])
self.calib_graph.clear()
@@ -249,11 +264,16 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
except TypeError:
return
+ if self.cp_checkBox.isChecked() and self.references:
+ y_label = 'cp'
+ else:
+ y_label = 'q'
+
rate, mode = self.current_run
- new_val = DSC(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate} ({mode})')
+ new_val = DSC(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate}K-min ({mode}, {y_label})')
if filesave:
- new_val.savetxt(self.fname.with_name(f'{self.fname.stem} {rate}K-min {mode}.dat'.replace(' ', '_')))
+ new_val.savetxt(self.fname.with_name(f'{self.fname.stem}_{rate}K-min_{y_label}{mode}.dat'.replace(' ', '_')))
close_after = False
else:
self.data_read.emit([new_val])
diff --git a/src/gui_qt/lib/logger.py b/src/gui_qt/lib/logger.py
index 0570d84..e1cb93c 100644
--- a/src/gui_qt/lib/logger.py
+++ b/src/gui_qt/lib/logger.py
@@ -1,5 +1,8 @@
+import logging
from pathlib import Path
+from PyQt5 import QtWidgets
+
from .codeeditor import _make_textformats
from ..Qt import QtWidgets, QtCore, QtGui
from nmreval.configs import config_paths
@@ -113,3 +116,27 @@ class QLog(QtWidgets.QDialog):
for lines in text[-100:]:
self.plainTextEdit.appendPlainText(lines[:-1])
+
+
+class ConsoleDock(QtWidgets.QDockWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent=parent)
+
+ self.code = QtWidgets.QPlainTextEdit(parent)
+ self.code.highlight = LogHighlighter(self.code.document())
+ self.code.setReadOnly(True)
+ self.code.setMaximumBlockCount(50)
+ self.setWidget(self.code)
+
+
+class QTextHandler(logging.Handler):
+ def __init__(self, parent):
+ super().__init__()
+
+ self.console = ConsoleDock(parent)
+ self.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
+
+ def emit(self, record):
+ msg = self.format(record)
+ self.console.code.appendPlainText(msg)
+ self.console.show()
diff --git a/src/gui_qt/lib/pg_objects.py b/src/gui_qt/lib/pg_objects.py
index 923ca88..fa678e1 100644
--- a/src/gui_qt/lib/pg_objects.py
+++ b/src/gui_qt/lib/pg_objects.py
@@ -183,6 +183,8 @@ class PlotItem(PlotDataItem):
brush = self.opts['symbolBrush']
if isinstance(brush, tuple):
self.opts['symbolcolor'] = brush
+ elif isinstance(brush, str):
+ self.opts['symbolcolor'] = int(f'0x{brush[1:3]}', 16), int(f'0x{brush[3:5]}', 16), int(f'0x{brush[5:7]}', 16)
else:
c = brush.color()
self.opts['symbolcolor'] = c.red(), c.green(), c.blue()
@@ -340,7 +342,8 @@ class PlotItem(PlotDataItem):
opts = self.opts
item_dic = {
- 'x': x, 'y': y,
+ 'x': x,
+ 'y': y,
'name': opts.get('name', ''),
'symbolsize': opts['symbolSize'],
}
diff --git a/src/gui_qt/lib/utils.py b/src/gui_qt/lib/utils.py
index 2f06820..5d87d1c 100644
--- a/src/gui_qt/lib/utils.py
+++ b/src/gui_qt/lib/utils.py
@@ -10,7 +10,7 @@ from ..Qt import QtGui, QtWidgets, QtCore
@contextmanager
def busy_cursor():
try:
- cursor = QtGui.QCursor(QtCore.Qt.ForbiddenCursor)
+ cursor = QtGui.QCursor(QtCore.Qt.CursorShape.ForbiddenCursor)
QtWidgets.QApplication.setOverrideCursor(cursor)
yield
diff --git a/src/gui_qt/main/console.py b/src/gui_qt/main/console.py
new file mode 100644
index 0000000..b28b04f
--- /dev/null
+++ b/src/gui_qt/main/console.py
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py
index 9c5d015..7f6123b 100644
--- a/src/gui_qt/main/mainwindow.py
+++ b/src/gui_qt/main/mainwindow.py
@@ -12,6 +12,7 @@ from nmreval.lib.logger import logger
from nmreval.io.sessionwriter import NMRWriter
from .management import UpperManagement
+from ..lib.logger import ConsoleDock, QTextHandler
from ..Qt import QtGui, QtPrintSupport
from ..data.shift_graphs import QShift
from ..data.signaledit import QPreviewDialog, QBaselineDialog
@@ -62,6 +63,11 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.eval = None
self.editor = None
+ self.logtext = QTextHandler(self)
+ logger.addHandler(self.logtext)
+ self.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, self.logtext.console)
+ self.logtext.console.hide()
+
self.movedialog = QMover(self)
self.current_graph_widget = None
@@ -888,7 +894,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.fit_dialog.load(self.management.active_sets)
for item in self.fit_dialog.preview_lines:
self.current_graph_widget.add_external(item)
- if self.action_custom_range.isChecked():
+ if self.action_custom_range.isChecked() or self.actionExclude_region.isChecked():
self.current_graph_widget.add_external(self.fit_toolbar.region)
block_window = True
@@ -904,7 +910,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
if self.current_graph_widget is None:
return
- if action == self.action_custom_range and self.fit_dialog.isVisible():
+ if action in [self.action_custom_range, self.actionExclude_region] and self.fit_dialog.isVisible():
self.current_graph_widget.add_external(self.fit_toolbar.region)
else:
self.current_graph_widget.remove_external(self.fit_toolbar.region)
@@ -984,7 +990,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
res = w.exec()
if res:
p = w.values
- x = linspace(p[0], p[1], num=p[2])
+ spacefunc = geomspace if p[3] else linspace
+ x = spacefunc(p[0], p[1], num=p[2])
self.management.extend_fits(sets, x)
@QtCore.pyqtSlot(name='on_action_create_fit_function_triggered')
diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py
index 007cb40..a75cc2e 100644
--- a/src/gui_qt/main/management.py
+++ b/src/gui_qt/main/management.py
@@ -503,27 +503,44 @@ class UpperManagement(QtCore.QObject):
we = we_option
if m_complex is None or m_complex == 1:
+ # model is not complex: m_complex = None
+ # model is complex, fit real part: m_complex = 1
_y = data_i.y.real
- elif m_complex == 2 and np.iscomplexobj(data_i.y):
- _y = data_i.y.imag
+ data_complex = 1
+ elif m_complex == 2:
+ # model is complex, fit imag part: m_complex = 2
+ if np.iscomplexobj(data_i.y):
+ # data is complex, use imag part
+ _y = data_i.y.imag
+ data_complex = 2
+ else:
+ # data is real
+ _y = data_i.y
+ data_complex = 1
else:
+ # model is complex, fit complex: m_complex = 0
+ # use data as given (complex or not)
_y = data_i.y
+ data_complex = 0
_x = data_i.x
+ # options for fit limits 'none', 'x', ('in', custom region), ('out', excluded region)
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]))
+ elif fit_limits[0] == 'in':
+ inside = np.where((_x >= fit_limits[1][0]) & (_x <= fit_limits[1][1]))
else:
- inside = np.where((_x >= fit_limits[0]) & (_x <= fit_limits[1]))
+ inside = np.where((_x < fit_limits[1][0]) | (_x > fit_limits[1][1]))
try:
if isinstance(we, str):
- d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id)
+ d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id, complex_type=data_complex)
else:
- d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id)
+ d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id, complex_type=data_complex)
except Exception as e:
raise Exception(f'Setting data failed for {set_id}')
@@ -623,7 +640,7 @@ class UpperManagement(QtCore.QObject):
continue
if not all(e is None for e in extrapolate):
- spacefunc = np.geomspace if fit.islog else np.linspace
+ spacefunc = np.geomspace if extrapolate[3] else np.linspace
xmin = fit.x.min()
xmax = fit.x.max()
diff --git a/src/gui_qt/math/smooth.py b/src/gui_qt/math/smooth.py
index 387865d..d2ca5e7 100644
--- a/src/gui_qt/math/smooth.py
+++ b/src/gui_qt/math/smooth.py
@@ -12,10 +12,10 @@ class QSmooth(QtWidgets.QDialog, Ui_SmoothDialog):
@QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged')
def change_mode(self, idx: int):
- if idx == 2:
+ if idx == 1:
self.widget.show()
self.widget_2.hide()
- elif idx == 3:
+ elif idx == 2:
self.widget.show()
self.widget_2.show()
else:
@@ -29,12 +29,24 @@ class QSmooth(QtWidgets.QDialog, Ui_SmoothDialog):
idx = self.comboBox.currentIndex()
# this order must match the combobox
- para['mode'] = ['mean', 'savgol', 'loess', 'median', 'std', 'var', 'max', 'min', 'sum'][idx]
+ para['mode'] = [
+ 'mean',
+ 'savgol',
+ 'loess',
+ 'median',
+ 'std',
+ 'var',
+ 'max',
+ 'min',
+ 'sum',
+ ][idx]
- if idx == 2:
+ # Savitzky-Golay needs also polynomial degree
+ if idx == 1:
para['deg'] = self.polynom_spinBox.value()
- if idx == 3:
+ # LOESS needs also polynomial degree and number of iterations
+ if idx == 2:
para['deg'] = self.polynom_spinBox.value()
para['it'] = self.iter_spinBox.value()
diff --git a/src/nmreval/data/points.py b/src/nmreval/data/points.py
index b394efa..8332084 100644
--- a/src/nmreval/data/points.py
+++ b/src/nmreval/data/points.py
@@ -320,7 +320,11 @@ class Points:
pts = []
_tmp_x = self._x[self.mask]
- _tmp_y = self._y[self.mask]
+ x_order = np.argsort(_tmp_x)
+ _tmp_x = _tmp_x[x_order]
+ _tmp_y = self._y[self.mask][x_order]
+ _tmp_yerr = self._y_err[self.mask][x_order]
+
if idx is not None:
for idx_i in idx:
if isinstance(idx_i, tuple):
@@ -338,7 +342,7 @@ class Points:
right_b = int(min(len(self), x_idx + avg_range[1] + 1))
if left_b < right_b:
- pts.append([_tmp_x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)])
+ pts.append([_tmp_x[x_idx], *self._average(_tmp_x, _tmp_y, _tmp_yerr, avg_mode, x_idx, left_b, right_b)])
else:
pts.append([_tmp_x[x_idx], _tmp_y[x_idx], self._y_err[x_idx]])
@@ -358,28 +362,37 @@ class Points:
left_b = int(max(0, x_idx - avg_range[0]))
right_b = int(min(len(self), x_idx + avg_range[1] + 1))
- pts.append([_tmp_x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)])
+ pts.append([_tmp_x[x_idx], *self._average(_tmp_x, _tmp_y, _tmp_yerr, avg_mode, x_idx, left_b, right_b)])
return pts
- def _average(self, mode: str, idx, left: int, right: int) -> tuple[float, float]:
+ @staticmethod
+ def _average(
+ x: np.ndarray,
+ y: np.ndarray,
+ y_err: np.ndarray,
+ mode: str,
+ idx: int,
+ left: int,
+ right: int,
+ ) -> tuple[float, float]:
if mode == 'mean':
- y_mean = np.mean(self._y[self.mask][left:right].real)
- y_err = np.linalg.norm(self._y_err[self.mask][left:right]) / (right - left)
+ y_mean = np.mean(y[left:right].real)
+ y_err_mean = np.linalg.norm(y_err[left:right]) / (right - left)
elif mode == 'sum':
- y_mean = np.sum(self._y[self.mask][left:right].real)
- y_err = np.linalg.norm(self._y_err[self.mask][left:right])
+ y_mean = np.sum(y[left:right].real)
+ y_err_mean = np.linalg.norm(y_err[left:right])
elif mode == 'integral':
- y_mean = simpson(self._y[self.mask][left:right].real, x=self._x[left:right])
- y_err = np.linalg.norm(cumulative_trapezoid(self._y_err[self.mask][left:right].real, x=self._x[left:right]))
+ y_mean = simpson(y[left:right].real, x=x[left:right])
+ y_err_mean = np.linalg.norm(cumulative_trapezoid(y_err[left:right].real, x=x[left:right]))
else:
- y_mean = self._y[self.mask][idx].real
- y_err = self._y_err[self.mask][idx]
+ y_mean = y[idx].real
+ y_err_mean = y_err[idx]
- return y_mean, y_err
+ return y_mean, y_err_mean
def concatenate(self, other):
"""
diff --git a/src/nmreval/fit/data.py b/src/nmreval/fit/data.py
index f6f2cc5..b6b94ff 100644
--- a/src/nmreval/fit/data.py
+++ b/src/nmreval/fit/data.py
@@ -6,8 +6,8 @@ from .model import Model
from .parameter import Parameters, Parameter
-class Data(object):
- def __init__(self, x, y, we=None, idx=None):
+class Data:
+ def __init__(self, x, y, we=None, idx=None, complex_type: int = 0):
self.x = np.asarray(x)
self.y = np.asarray(y)
if self.y.shape[0] != self.x.shape[0]:
@@ -20,6 +20,7 @@ class Data(object):
self.parameter = Parameters()
self.para_keys: list = []
self.fun_kwargs = {}
+ self.complex_type = complex_type
def __len__(self):
return self.y.shape[0]
diff --git a/src/nmreval/fit/minimizer.py b/src/nmreval/fit/minimizer.py
index 27e7492..78f4557 100644
--- a/src/nmreval/fit/minimizer.py
+++ b/src/nmreval/fit/minimizer.py
@@ -361,7 +361,7 @@ class FitRoutine(object):
with np.errstate(all='ignore'):
res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=500 * len(p0))
- err, corr, partial_corr = self._calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape)
+ err, corr, partial_corr = _calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape)
self.make_results(data, res.x, var, data.para_keys, res.jac.shape,
err=err, corr=corr, partial_corr=partial_corr)
@@ -375,7 +375,7 @@ class FitRoutine(object):
with np.errstate(all='ignore'):
res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=500 * len(p0))
- err, corr, partial_corr = self._calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape)
+ err, corr, partial_corr = _calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape)
for v, var_pars_k in zip(data, data_pars):
self.make_results(v, res.x, var, var_pars_k, res.jac.shape,
err=err, corr=corr, partial_corr=partial_corr)
@@ -458,9 +458,17 @@ class FitRoutine(object):
self.make_results(v, res.beta, var, var_pars_k, (sum(len(d) for d in data), len(p0)),
err=res.sd_beta, corr=corr, partial_corr=partial_corr)
- def make_results(self, data, p, var_pars, used_pars, shape,
- err=None, corr=None, partial_corr=None):
-
+ def make_results(
+ self,
+ data: Data,
+ p: list[float],
+ var_pars: list[str],
+ used_pars: list[str],
+ shape: tuple[int, int],
+ err: list[float] = None,
+ corr: np.ndarray = None,
+ partial_corr: np.ndarray = None,
+ ):
if err is None:
err = [0] * len(p)
@@ -498,52 +506,54 @@ class FitRoutine(object):
model = data.get_model()
self.result[idx] = FitResultCreator.make_with_model(
- model,
- data.x,
- data.y,
- actual_parameters,
- data.fun_kwargs,
- data.we_string,
- data.idx,
- *shape,
+ model=model,
+ x_orig=data.x,
+ y_orig=data.y,
+ p=actual_parameters,
+ fun_kwargs=data.fun_kwargs,
+ we=data.we_string,
+ idx=data.idx,
+ nobs=shape[0],
+ nvar=shape[1],
corr=actual_corr,
pcorr=actual_pcorr,
+ data_mode=data.complex_type,
)
return self.result
- @staticmethod
- def _calc_error(jac, chi, nobs, nvars):
- # copy of scipy.curve_fit to calculate covariance
- # noinspection PyTupleAssignmentBalance
- try:
- _, s, vt = la.svd(jac, full_matrices=False)
- except ValueError as e:
- # this may be issue #39: On entry to DGESSD parameter had an illegal value
- # catch this exception and ignore error calculation
- logger.error(f'Error calculation failed with {e.args}')
- pcov = None
- else:
- threshold = EPS * max(jac.shape) * s[0]
- s = s[s > threshold]
- vt = vt[:s.size]
- pcov = np.dot(vt.T / s**2, vt) * chi / (nobs - nvars)
- if pcov is None:
- _err = np.zeros(nvars)
- corr = np.zeros((nvars, nvars))
- else:
- _err = np.sqrt(np.diag(pcov))
- corr = pcov / (_err[:, None] * _err[None, :])
+def _calc_error(jac, chi, nobs, nvars):
+ # copy of scipy.curve_fit to calculate covariance
+ # noinspection PyTupleAssignmentBalance
+ try:
+ _, s, vt = la.svd(jac, full_matrices=False)
+ except ValueError as e:
+ # this may be issue #39: On entry to DGESSD parameter had an illegal value
+ # catch this exception and ignore error calculation
+ logger.error(f'Error calculation failed with {e.args}')
+ pcov = None
+ else:
+ threshold = EPS * max(jac.shape) * s[0]
+ s = s[s > threshold]
+ vt = vt[:s.size]
+ pcov = np.dot(vt.T / s**2, vt) * chi / (nobs - nvars)
- corr = corr.astype(np.float64)
- try:
- corr_inv = np.linalg.inv(corr)
- corr_inv_diag = np.diag(np.sqrt(1 / np.diag(corr_inv)))
- partial_corr = -1. * np.dot(np.dot(corr_inv_diag, corr_inv), corr_inv_diag) # Partial correlation matrix
- partial_corr[np.diag_indices_from(partial_corr)] = 1.
- except np.linalg.LinAlgError:
- partial_corr = corr
+ if pcov is None:
+ _err = np.zeros(nvars)
+ corr = np.zeros((nvars, nvars))
+ else:
+ _err = np.sqrt(np.diag(pcov))
+ corr = pcov / (_err[:, None] * _err[None, :])
- return _err, corr, partial_corr
+ corr = corr.astype(np.float64)
+ try:
+ corr_inv = np.linalg.inv(corr)
+ corr_inv_diag = np.diag(np.sqrt(1 / np.diag(corr_inv)))
+ partial_corr = -1. * np.dot(np.dot(corr_inv_diag, corr_inv), corr_inv_diag) # Partial correlation matrix
+ partial_corr[np.diag_indices_from(partial_corr)] = 1.
+ except np.linalg.LinAlgError:
+ partial_corr = corr
+
+ return _err, corr, partial_corr
diff --git a/src/nmreval/fit/model.py b/src/nmreval/fit/model.py
index c80a2a3..46aacee 100644
--- a/src/nmreval/fit/model.py
+++ b/src/nmreval/fit/model.py
@@ -9,7 +9,7 @@ from ._meta import MultiModel
from .parameter import Parameters, Parameter
-class Model(object):
+class Model:
def __init__(self, model, *args, **kwargs):
self.idx = kwargs.pop('idx', None)
diff --git a/src/nmreval/fit/result.py b/src/nmreval/fit/result.py
index 87fa94f..bd954b4 100644
--- a/src/nmreval/fit/result.py
+++ b/src/nmreval/fit/result.py
@@ -11,6 +11,7 @@ from scipy.stats import f as fdist
from scipy.interpolate import interp1d
from ._meta import MultiModel
+from .model import Model
from .parameter import Parameter
from ..data.points import Points
from ..data.signals import Signal
@@ -36,17 +37,30 @@ class FitResultCreator:
else:
resid = kwargs['y'] - y_orig
- stats = FitResultCreator.calc_statistics(resid, _y)
+ stats = 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)
+ return FitResult(
+ x=kwargs['x'],
+ y=kwargs['y'],
+ x_data=x_orig,
+ y_data=y_orig,
+ params=params,
+ fun_kwargs=dict(kwargs['choice']),
+ resid=resid,
+ nobs=0,
+ nvar=0,
+ we='',
+ name=kwargs['name'],
+ stats=stats,
+ idx=idx,
+ )
@staticmethod
def make_with_model(
model: 'Model',
x_orig: np.ndarray,
y_orig: np.ndarray,
- p: 'Parameters',
+ p: list,
fun_kwargs: dict,
we: str,
idx: str | None,
@@ -54,6 +68,7 @@ class FitResultCreator:
nvar: int,
corr: np.ndarray,
pcorr: np.ndarray,
+ data_mode: int,
) -> FitResult:
if np.all(x_orig > 0) and (np.max(x_orig) > 100 * np.min(x_orig)):
islog = True
@@ -83,17 +98,11 @@ class FitResultCreator:
actual_mode = fun_kwargs['complex_mode']
fun_kwargs['complex_mode'] = 0
- _y = model.func(p_final, _x, **fun_kwargs)
+ _y = check_complex(model.func(p_final, _x, **fun_kwargs), actual_mode, data_mode)
- if not actual_mode < 0:
- if actual_mode == 1:
- _y.imag = 0
- elif actual_mode == 2:
- _y.real = 0
+ fun_kwargs['complex_mode'] = actual_mode
- fun_kwargs['complex_mode'] = actual_mode
-
- stats = FitResultCreator.calc_statistics(_y, resid, nobs, nvar)
+ stats = calc_statistics(_y, resid, nobs, nvar)
varied = [p.var for p in parameters.values()]
if corr is None:
@@ -134,38 +143,9 @@ class FitResultCreator:
pcorr=partial_correlation,
islog=islog,
func=model,
+ data_complex=data_mode,
)
- @staticmethod
- def calc_statistics(y, residual, nobs=None, nvar=None):
- chi = (residual**2).sum()
- try:
- r = 1 - chi/((y-np.mean(y))**2).sum()
- except RuntimeWarning:
- r = -9999
-
- if nobs is None:
- nobs = 1
-
- if nvar is None:
- nvar = 0
-
- dof = nobs - nvar
- loglikehood = nobs * np.log(chi / nobs)
-
- stats = {
- 'chi^2': chi,
- 'R^2': r,
- 'AIC': loglikehood + 2 * nvar,
- 'BIC': loglikehood + np.log(nobs) * nvar,
- 'adj. R^2': 1 - (nobs-1) / (dof+1e-13) * (1-r),
- 'red. chi^2': chi / (dof + 1e-13),
- }
-
- stats['AICc'] = stats['AIC'] + 2*(nvar+1)*nvar / (dof - 1 + 1e-13)
-
- return stats
-
class FitResult(Points):
@@ -188,7 +168,8 @@ class FitResult(Points):
pcorr: np.ndarray = None,
islog: bool = False,
func=None,
- **kwargs
+ data_complex: int = 1,
+ **kwargs,
):
self.parameter, name = self._prepare_names(params, name)
@@ -210,6 +191,7 @@ class FitResult(Points):
self.y_data = y_data
self._model_name = name
self._func = func
+ self._data_complex = data_complex
@staticmethod
def _prepare_names(parameter: dict, modelname: str):
@@ -418,20 +400,12 @@ class FitResult(Points):
if self.func is None:
raise ValueError('no fit function available to calculate new y values')
- actual_mode = -1
- if 'complex_mode' in self.fun_kwargs:
- actual_mode = self.fun_kwargs['complex_mode']
- self.fun_kwargs['complex_mode'] = 0
-
new_fit = self.copy()
- y_values = self.func.func(self.p_final, x_values, **self.fun_kwargs)
- if not actual_mode < 0:
- if actual_mode == 1:
- y_values.imag = 0
- elif actual_mode == 2:
- y_values.real = 0
-
- self.fun_kwargs['complex_mode'] = actual_mode
+ fun_kwargs = {k: v for k, v in self.fun_kwargs.items()}
+ if self.fun_kwargs.get('complex_mode', -1) == -1:
+ fun_kwargs.pop('complex_mode', None)
+ y_values = self.func.func(self.p_final, x_values, **fun_kwargs)
+ y_values = check_complex(y_values, self.fun_kwargs.get('complex_mode', -1), self._data_complex)
new_fit.set_data(x_values, y_values, y_err=0.0)
@@ -442,20 +416,16 @@ class FitResult(Points):
raise ValueError('no fit function available to calculate new y values')
part_functions = []
- actual_mode = -1
- if 'complex_mode' in self.fun_kwargs:
- actual_mode = self.fun_kwargs['complex_mode']
- self.fun_kwargs['complex_mode'] = 0
+ actual_mode = self.fun_kwargs.get('complex_mode', -1)
+ fun_kwargs = {k: v for k, v in self.fun_kwargs.items()}
+ if self.fun_kwargs.get('complex_mode', -1) == -1:
+ fun_kwargs.pop('complex_mode', None)
for sub_name, sub_y in zip(self.func.sub_name(), self.func.sub(self.p_final, x_values, **self.fun_kwargs)):
- if not actual_mode < 0:
- if actual_mode == 1:
- sub_y.imag = 0
- elif actual_mode == 2:
- sub_y.real = 0
+ sub_y = check_complex(sub_y, actual_mode, self._data_complex)
+ 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))
@@ -463,3 +433,49 @@ class FitResult(Points):
self.fun_kwargs['complex_mode'] = actual_mode
return part_functions
+
+
+def check_complex(y, model_complex, data_complex):
+ if not np.iscomplexobj(y):
+ return y
+
+ if model_complex == 1:
+ y.imag = 0
+ if data_complex == 1:
+ y = y.real
+ elif model_complex == 2:
+ y.real = 0
+ if data_complex == 1:
+ y = y.imag
+
+ return y
+
+
+def calc_statistics(y, residual, nobs=None, nvar=None):
+ chi = (residual**2).sum()
+ try:
+ r = 1 - chi/((y-np.mean(y))**2).sum()
+ except RuntimeWarning:
+ r = -9999
+
+ if nobs is None:
+ nobs = 1
+
+ if nvar is None:
+ nvar = 0
+
+ dof = nobs - nvar
+ loglikehood = nobs * np.log(chi / nobs)
+
+ stats = {
+ 'chi^2': chi,
+ 'R^2': r,
+ 'AIC': loglikehood + 2 * nvar,
+ 'BIC': loglikehood + np.log(nobs) * nvar,
+ 'adj. R^2': 1 - (nobs-1) / (dof+1e-13) * (1-r),
+ 'red. chi^2': chi / (dof + 1e-13),
+ }
+
+ stats['AICc'] = stats['AIC'] + 2*(nvar+1)*nvar / (dof - 1 + 1e-13)
+
+ return stats
diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py
index e8c0b4f..6a31318 100644
--- a/src/nmreval/io/asciireader.py
+++ b/src/nmreval/io/asciireader.py
@@ -49,7 +49,7 @@ 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.strip('\n\t\r, ')
- line = re.sub(r'[\t ;,] *', ';', line)
+ line = re.sub(r'[\t ;,]+', ';', line)
line = line.split(';')
try:
@@ -183,7 +183,13 @@ class AsciiReader:
single_len = 2
stepsize = 2
- cls = {'points': Points, 'fid': FID, 'spectrum': Spectrum, 'bds': BDS, 'dsc': DSC}[mode]
+ cls = {
+ 'points': Points,
+ 'fid': FID,
+ 'spectrum': Spectrum,
+ 'bds': BDS,
+ 'dsc': DSC,
+ }[mode]
for j in range(1, num_y+1, stepsize):
if col_names is not None:
@@ -191,7 +197,7 @@ class AsciiReader:
kwargs['name'] = col_names[j-1]
elif num_y > single_len:
# more than one axis, append column number
- kwargs['name'] = filename + '_' + str(y[j-1])
+ kwargs['name'] = f'{filename}_{y[j-1]+1}'
if j+num_y < raw_data.shape[2]:
kwargs['y_err'] = raw_data[i, :, j+num_y]
diff --git a/src/nmreval/io/dsc.py b/src/nmreval/io/dsc.py
index b531bd4..4aaf606 100644
--- a/src/nmreval/io/dsc.py
+++ b/src/nmreval/io/dsc.py
@@ -11,7 +11,8 @@ try:
from scipy.integrate import simpson
except ImportError:
from scipy.integrate import simps as simpson
-from scipy.interpolate import interp1d
+from scipy.interpolate import CubicSpline
+
ReferenceValue = namedtuple('Reference', ['name', 'transitions'])
Cyclohexane = ReferenceValue('Cyclohexane', [(-87.06+273.15, 79.58), (6.54+273.15, None)])
@@ -38,7 +39,7 @@ class DSCSample:
def read_file(self, fname: str | Path) -> None:
fname = Path(fname)
- # file contains weird deg C character in stupiod ISO encoding
+ # file contains weird deg C character in stupid ISO encoding
with fname.open('r', encoding='iso-8859-15') as f:
ii = 1
for line in f:
@@ -144,9 +145,12 @@ class DSCCalibrator:
self.reference = []
self.ref_list = []
- def set_measurement(self,
- fname: str | Path | DSCSample, mode: str = 'sample',
- reference: ReferenceValue = Cyclohexane):
+ def set_measurement(
+ self: DSCCalibrator,
+ fname: str | Path | DSCSample,
+ mode: str = 'sample',
+ reference: ReferenceValue = Cyclohexane
+ ):
if mode not in ['sample', 'empty', 'reference']:
raise ValueError(f'Unknown mode {mode}, not "sample", "empty", "reference"')
if mode == 'reference' and not isinstance(reference, ReferenceValue):
@@ -266,7 +270,12 @@ class DSCCalibrator:
return sol
- def get_data(self, idx: int, slope: str = 'iso', limits: tuple[float, float] = None):
+ def get_data(
+ self: DSCCalibrator,
+ idx: int,
+ slope: str = 'iso',
+ limits: tuple[float, float] = None
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray,np.ndarray | None, np.ndarray]:
if self.sample.steps[idx][0] == 'i':
raise ValueError('baseline correction is not implemented for isotherms')
@@ -292,7 +301,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[2, 0], empty_data[1], fill_value='extrapolate')(sample_data[2, 0])
+ empty_y = CubicSpline(empty_data[2]-empty_data[2, 0], empty_data[1], extrapolate=True)(sample_data[2] - sample_data[2, 0])
sample_data[1] -= empty_y
drift_value = sample_data.copy()[(2, 1), :]
@@ -346,9 +355,10 @@ class DSCCalibrator:
offset = region[0, 0]
sample_data[1] -= m * (sample_data[2] - region[1, 0]) + offset
- line = np.array([[sample_data[2, 0], sample_data[2, -1]],
- [m * (sample_data[2, 0] - region[1, 0]) + offset,
- m * (sample_data[2, -1] - region[1, 0]) + offset]])
+ line = np.array([
+ [sample_data[2, 0], sample_data[2, -1]],
+ [m * (sample_data[2, 0] - region[1, 0]) + offset, m * (sample_data[2, -1] - region[1, 0]) + offset]
+ ])
else:
line = np.array([[sample_data[2, 0], sample_data[2, -1]], [0, 0]])
diff --git a/src/resources/_ui/basewindow.ui b/src/resources/_ui/basewindow.ui
index bf3de79..01987b8 100644
--- a/src/resources/_ui/basewindow.ui
+++ b/src/resources/_ui/basewindow.ui
@@ -247,6 +247,7 @@
+
@@ -1021,6 +1022,14 @@
TNMH...
+
+
+ true
+
+
+ Exclude region
+
+
diff --git a/src/resources/_ui/fitresult.ui b/src/resources/_ui/fitresult.ui
index 142a794..9f8bd4f 100644
--- a/src/resources/_ui/fitresult.ui
+++ b/src/resources/_ui/fitresult.ui
@@ -354,59 +354,26 @@
3
- -
-
-
- Extrapolates only main function
-
-
- Extrapolate curves
-
-
-
- -
-
-
- Plot parameter
-
-
-
- -
+
-
false
-
+
0
0
- -
-
-
-
- 0
- 0
-
-
-
- New graph for parameter
-
-
- true
-
-
-
-
false
-
+
0
0
@@ -419,10 +386,45 @@
- -
-
-
- Qt::Vertical
+
-
+
+
+ Extrapolates only main function
+
+
+ Extrapolate curves
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+ # pts
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ New graph for parameter
+
+
+ true
@@ -432,7 +434,7 @@
false
-
+
0
0
@@ -445,20 +447,33 @@
- -
-
-
- false
-
-
- # pts
+
-
+
+
+ Qt::Vertical
- -
+
-
+
+
+ false
+
+
+ log-spaced?
+
+
+
+ -
-
+
+
+ 0
+ 0
+
+
Plot fit curve
@@ -469,6 +484,12 @@
-
+
+
+ 0
+ 0
+
+
Plot partial functions
@@ -476,6 +497,19 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Plot parameter
+
+
+