diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml
index 80b4572..fb8b178 100644
--- a/AppImageBuilder.yml
+++ b/AppImageBuilder.yml
@@ -41,6 +41,7 @@ AppDir:
# - zsync
# - hicolor-icon-theme
- libatlas3-base
+ - gnuplot-nox
- python3.9-minimal
- python3-numpy
- python3-scipy
@@ -70,7 +71,7 @@ AppDir:
- usr/share/doc/*/README.*
- usr/share/doc/*/changelog.*
- usr/share/doc/*/NEWS.*
- - usr/share/doc/*/TODO.}*
+ - usr/share/doc/*/TODO.*
runtime:
# if needed, apparently replaces hardcoded location with APPDIR location
# path_mappings:
diff --git a/src/gui_qt/_py/bdsdialog.py b/src/gui_qt/_py/bdsdialog.py
index 3014019..795bff1 100644
--- a/src/gui_qt/_py/bdsdialog.py
+++ b/src/gui_qt/_py/bdsdialog.py
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'resources/_ui/bdsdialog.ui'
+# Form implementation generated from reading ui file 'src/resources/_ui/bdsdialog.ui'
#
-# Created by: PyQt5 UI code generator 5.9.2
+# Created by: PyQt5 UI code generator 5.15.9
#
-# WARNING! All changes made in this file will be lost!
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
from PyQt5 import QtCore, QtGui, QtWidgets
+
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
@@ -16,12 +19,13 @@ class Ui_Dialog(object):
self.gridLayout.setContentsMargins(3, 3, 3, 3)
self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout")
- self.listWidget = QtWidgets.QListWidget(Dialog)
+ self.listWidget = QListWidgetSelect(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy)
+ self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 1, 0, 2, 1)
self.groupBox_2 = QtWidgets.QGroupBox(Dialog)
@@ -93,8 +97,8 @@ class Ui_Dialog(object):
self.gridLayout.addWidget(self.label, 0, 0, 1, 2)
self.retranslateUi(Dialog)
- self.buttonBox.accepted.connect(Dialog.accept)
- self.buttonBox.rejected.connect(Dialog.reject)
+ self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
+ self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
Dialog.setTabOrder(self.freq_button, self.temp_button)
Dialog.setTabOrder(self.temp_button, self.eps_checkBox)
@@ -117,4 +121,4 @@ class Ui_Dialog(object):
self.temp_checkBox.setText(_translate("Dialog", "Meas. temperature"))
self.time_checkBox.setText(_translate("Dialog", "Meas. time"))
self.label.setText(_translate("Dialog", "Found entries"))
-
+from ..lib.listwidget import QListWidgetSelect
diff --git a/src/gui_qt/_py/fitresult.py b/src/gui_qt/_py/fitresult.py
index ae2d077..5a9a4e4 100644
--- a/src/gui_qt/_py/fitresult.py
+++ b/src/gui_qt/_py/fitresult.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'src/resources/_ui/fitresult.ui'
#
-# Created by: PyQt5 UI code generator 5.15.2
+# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@@ -14,37 +14,28 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
- Dialog.resize(864, 649)
+ Dialog.resize(969, 974)
self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setObjectName("gridLayout")
- self.sets_comboBox = ElideComboBox(Dialog)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.sets_comboBox.sizePolicy().hasHeightForWidth())
- self.sets_comboBox.setSizePolicy(sizePolicy)
- self.sets_comboBox.setMaximumSize(QtCore.QSize(400, 16777215))
- self.sets_comboBox.setBaseSize(QtCore.QSize(200, 0))
- self.sets_comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
- self.sets_comboBox.setObjectName("sets_comboBox")
- self.gridLayout.addWidget(self.sets_comboBox, 0, 0, 1, 1)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setSpacing(3)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog)
+ self.reject_fit_checkBox.setObjectName("reject_fit_checkBox")
+ self.horizontalLayout_2.addWidget(self.reject_fit_checkBox)
+ self.del_prev_checkBox = QtWidgets.QCheckBox(Dialog)
+ self.del_prev_checkBox.setObjectName("del_prev_checkBox")
+ self.horizontalLayout_2.addWidget(self.del_prev_checkBox)
+ self.gridLayout.addLayout(self.horizontalLayout_2, 2, 0, 1, 2)
+ self.line = QtWidgets.QFrame(Dialog)
+ self.line.setFrameShape(QtWidgets.QFrame.HLine)
+ self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.line.setObjectName("line")
+ self.gridLayout.addWidget(self.line, 3, 0, 1, 2)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Retry)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 6, 0, 1, 2)
- self.param_tableWidget = QtWidgets.QTableWidget(Dialog)
- self.param_tableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
- self.param_tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
- self.param_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
- self.param_tableWidget.setAlternatingRowColors(True)
- self.param_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
- self.param_tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectColumns)
- self.param_tableWidget.setShowGrid(False)
- self.param_tableWidget.setColumnCount(0)
- self.param_tableWidget.setObjectName("param_tableWidget")
- self.param_tableWidget.setRowCount(0)
- self.param_tableWidget.horizontalHeader().setStretchLastSection(False)
- self.gridLayout.addWidget(self.param_tableWidget, 1, 0, 1, 1)
self.groupBox = QtWidgets.QGroupBox(Dialog)
self.groupBox.setObjectName("groupBox")
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox)
@@ -113,21 +104,34 @@ class Ui_Dialog(object):
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)
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog)
- self.reject_fit_checkBox.setObjectName("reject_fit_checkBox")
- self.horizontalLayout_2.addWidget(self.reject_fit_checkBox)
- self.del_prev_checkBox = QtWidgets.QCheckBox(Dialog)
- self.del_prev_checkBox.setObjectName("del_prev_checkBox")
- self.horizontalLayout_2.addWidget(self.del_prev_checkBox)
- self.gridLayout.addLayout(self.horizontalLayout_2, 2, 0, 1, 1)
- self.line = QtWidgets.QFrame(Dialog)
- self.line.setFrameShape(QtWidgets.QFrame.HLine)
- self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
- self.line.setObjectName("line")
- self.gridLayout.addWidget(self.line, 3, 0, 1, 2)
+ self.param_tableWidget = QtWidgets.QTableWidget(Dialog)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.param_tableWidget.sizePolicy().hasHeightForWidth())
+ self.param_tableWidget.setSizePolicy(sizePolicy)
+ self.param_tableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.param_tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
+ self.param_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
+ self.param_tableWidget.setAlternatingRowColors(True)
+ self.param_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
+ self.param_tableWidget.setColumnCount(1)
+ self.param_tableWidget.setObjectName("param_tableWidget")
+ self.param_tableWidget.setRowCount(0)
+ self.param_tableWidget.horizontalHeader().setVisible(False)
+ self.param_tableWidget.horizontalHeader().setStretchLastSection(True)
+ self.gridLayout.addWidget(self.param_tableWidget, 1, 0, 1, 1)
+ self.sets_comboBox = ElideComboBox(Dialog)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.sets_comboBox.sizePolicy().hasHeightForWidth())
+ self.sets_comboBox.setSizePolicy(sizePolicy)
+ self.sets_comboBox.setMaximumSize(QtCore.QSize(400, 16777215))
+ self.sets_comboBox.setBaseSize(QtCore.QSize(200, 0))
+ self.sets_comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
+ self.sets_comboBox.setObjectName("sets_comboBox")
+ self.gridLayout.addWidget(self.sets_comboBox, 0, 0, 1, 1)
self.stack = QtWidgets.QTabWidget(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -194,7 +198,7 @@ class Ui_Dialog(object):
self.corr_tableWidget.verticalHeader().setVisible(False)
self.verticalLayout_3.addWidget(self.corr_tableWidget)
self.stack.addTab(self.stackPage3, "")
- self.gridLayout.addWidget(self.stack, 0, 1, 3, 1)
+ self.gridLayout.addWidget(self.stack, 0, 1, 2, 1)
self.retranslateUi(Dialog)
self.stack.setCurrentIndex(0)
@@ -203,6 +207,8 @@ class Ui_Dialog(object):
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Fit results"))
+ self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit"))
+ self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits"))
self.groupBox.setTitle(_translate("Dialog", "Output"))
self.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function"))
self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves"))
@@ -215,8 +221,6 @@ class Ui_Dialog(object):
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.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.setTabText(self.stack.indexOf(self.stackPage1), _translate("Dialog", "Plot"))
diff --git a/src/gui_qt/_py/graph.py b/src/gui_qt/_py/graph.py
index 32e7c0e..8ecac0b 100644
--- a/src/gui_qt/_py/graph.py
+++ b/src/gui_qt/_py/graph.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'resources/_ui/graph.ui'
+# Form implementation generated from reading ui file 'src/resources/_ui/graph.ui'
#
-# Created by: PyQt5 UI code generator 5.12.3
+# Created by: PyQt5 UI code generator 5.15.9
#
-# WARNING! All changes made in this file will be lost!
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -196,12 +197,13 @@ class Ui_GraphWindow(object):
self.gridLayout.setHorizontalSpacing(3)
self.gridLayout.setVerticalSpacing(0)
self.gridLayout.setObjectName("gridLayout")
- self.listWidget = QtWidgets.QListWidget(GraphWindow)
+ self.listWidget = QListWidgetSelect(GraphWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy)
+ self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 1, 1, 1, 1)
self.checkBox = QtWidgets.QCheckBox(GraphWindow)
@@ -272,4 +274,5 @@ class Ui_GraphWindow(object):
self.label_6.setText(_translate("GraphWindow", "X Axis"))
self.label_7.setText(_translate("GraphWindow", "Y Axis"))
self.checkBox.setText(_translate("GraphWindow", "Show legend"))
+from ..lib.listwidget import QListWidgetSelect
from pyqtgraph import PlotWidget
diff --git a/src/gui_qt/_py/interpol_dialog.py b/src/gui_qt/_py/interpol_dialog.py
index c819070..c3b0827 100644
--- a/src/gui_qt/_py/interpol_dialog.py
+++ b/src/gui_qt/_py/interpol_dialog.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'resources/_ui/interpol_dialog.ui'
+# Form implementation generated from reading ui file 'src/resources/_ui/interpol_dialog.ui'
#
-# Created by: PyQt5 UI code generator 5.12.3
+# Created by: PyQt5 UI code generator 5.15.9
#
-# WARNING! All changes made in this file will be lost!
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -65,12 +66,13 @@ class Ui_Dialog(object):
self.label_2 = QtWidgets.QLabel(Dialog)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 6, 0, 1, 1)
- self.listWidget = QtWidgets.QListWidget(Dialog)
+ self.listWidget = QListWidgetSelect(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy)
+ self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 2)
self.sampling_widget = QtWidgets.QWidget(Dialog)
@@ -130,8 +132,8 @@ class Ui_Dialog(object):
self.label_8.setBuddy(self.dest_combobox)
self.retranslateUi(Dialog)
- self.buttonBox.accepted.connect(Dialog.accept)
- self.buttonBox.rejected.connect(Dialog.reject)
+ self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
+ self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog)
Dialog.setTabOrder(self.listWidget, self.ylog_checkBox)
Dialog.setTabOrder(self.ylog_checkBox, self.interp_comboBox)
@@ -164,3 +166,4 @@ class Ui_Dialog(object):
self.xaxis_comboBox.setItemText(1, _translate("Dialog", "from data"))
self.label_8.setText(_translate("Dialog", "Add interpolated data to"))
self.xlog_checkBox.setText(_translate("Dialog", "use log(x)"))
+from ..lib.listwidget import QListWidgetSelect
diff --git a/src/gui_qt/_py/valueeditor.py b/src/gui_qt/_py/valueeditor.py
index f2e2aeb..cac1bcf 100644
--- a/src/gui_qt/_py/valueeditor.py
+++ b/src/gui_qt/_py/valueeditor.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'resources/_ui/valueeditor.ui'
+# Form implementation generated from reading ui file 'src/resources/_ui/valueeditor.ui'
#
-# Created by: PyQt5 UI code generator 5.15.4
+# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
diff --git a/src/gui_qt/data/container.py b/src/gui_qt/data/container.py
index 12375ff..fca491b 100644
--- a/src/gui_qt/data/container.py
+++ b/src/gui_qt/data/container.py
@@ -10,6 +10,7 @@ from nmreval.data.points import Points
from nmreval.data.signals import Signal
from nmreval.utils.text import convert
from nmreval.data.bds import BDS
+from nmreval.data.dsc import DSC
from nmreval.lib.colors import BaseColor, TUColors
from nmreval.lib.lines import LineStyle
from nmreval.lib.symbols import SymbolStyle, symbolcycle
@@ -44,6 +45,7 @@ class ExperimentContainer(QtCore.QObject):
self.actions = {}
self._update_actions()
+ @plot_update
def _init_plot(self):
raise NotImplementedError
@@ -312,10 +314,8 @@ class ExperimentContainer(QtCore.QObject):
err_pen.setColor(QtGui.QColor(*self.plot_real.symbolcolor.rgb()))
self.plot_error.setData(pen=err_pen)
- elif mode == 'imag' and self.plot_imag is not None:
+ if mode in ['imag', 'all'] and self.plot_imag is not None:
self.plot_imag.set_color(color, symbol=symbol, line=line)
- else:
- print('Updating color failed for ' + str(self.id))
def setSymbol(self, symbol=None, color=None, size=None, mode='real'):
if mode in ['real', 'all']:
@@ -460,7 +460,7 @@ class ExperimentContainer(QtCore.QObject):
return namespace
def eval_expression(self, cmds, namespace):
- namespace.update({'x': self.x, 'y': self.y, 'y_err': self.y_err, 'value': self.value})
+ namespace.update({'x': self._data.x, 'y': self._data.y, 'y_err': self._data.y_err, 'value': self.value})
if len(self._fits) == 1:
namespace.update({"fit['%s']" % (convert(pname, old='tex', new='str')): pvalue.value
@@ -475,7 +475,7 @@ class ExperimentContainer(QtCore.QObject):
if c:
exec(c, globals(), namespace)
- new_data.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err'])
+ new_data.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err'], replace_mask=False)
new_data.value = namespace['value']
return new_data
@@ -496,6 +496,9 @@ class PointContainer(ExperimentContainer):
self.mode = 'pts'
self._init_plot(**kwargs)
+ if isinstance(self._data, DSC):
+ self.mode = 'dsc'
+
def _init_plot(self, **kwargs):
self.plot_imag = None
@@ -532,17 +535,17 @@ class PointContainer(ExperimentContainer):
line_kwargs['style'] = LineStyle.No
sym_kwargs['symbol'] = next(PointContainer.symbols)
- self.plot_real = PlotItem(x=self._data.x, y=self._data.y, name=self.name,
+ self.plot_real = PlotItem(x=self.x, y=self.y, name=self.name,
symbol=None, pen=None, connect='finite')
self.setSymbol(mode='real', **sym_kwargs)
self.setLine(mode='real', **line_kwargs)
if sym_kwargs['symbol'] != SymbolStyle.No:
- self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err,
+ self.plot_error = ErrorBars(x=self.x, y=self.y, top=self.y_err, bottom=self.y_err,
pen=mkPen({'color': self.plot_real.symbolcolor.rgb()}))
else:
- self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err,
+ self.plot_error = ErrorBars(x=self.x, y=self.y, top=self.y_err, bottom=self.y_err,
pen=mkPen({'color': self.plot_real.linecolor.rgb()}))
@@ -565,12 +568,12 @@ class FitContainer(ExperimentContainer):
if isinstance(color, BaseColor):
color = color.rgb()
- self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name,
+ self.plot_real = PlotItem(x=self.x, y=self.y.real, name=self.name,
pen=mkPen({'color': color}),
connect='finite', symbol=None)
if np.iscomplexobj(self._data.y):
- self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name,
+ self.plot_imag = PlotItem(x=self.x, y=self.y.imag, name=self.name,
pen=mkPen({'color': color}),
connect='finite', symbol=None)
@@ -603,9 +606,9 @@ class SignalContainer(ExperimentContainer):
self._init_plot(symbol=symbol, **kwargs)
def _init_plot(self, **kwargs):
- self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name,
+ self.plot_real = PlotItem(x=self.x, y=self.y.real, name=self.name,
symbol=None, pen=None, connect='finite')
- self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name,
+ self.plot_imag = PlotItem(x=self.x, y=self.y.imag, name=self.name,
symbol=None, pen=None, connect='finite')
color = kwargs.get('color', None)
diff --git a/src/gui_qt/data/point_select.py b/src/gui_qt/data/point_select.py
index f821ccd..9abfc4f 100644
--- a/src/gui_qt/data/point_select.py
+++ b/src/gui_qt/data/point_select.py
@@ -187,9 +187,15 @@ class PointSelectWidget(QtWidgets.QWidget, Ui_Form):
self.peaktable.blockSignals(False)
def set_graphs(self, graphs: list):
+ last_graph = self.graph_combobox.currentData()
self.graph_combobox.clear()
- for g in graphs:
+ idx = 0
+ for i, g in enumerate(graphs):
self.graph_combobox.addItem(g[1], userData=g[0])
+ if g[0] == last_graph:
+ idx = i
+
+ self.graph_combobox.setCurrentIndex(idx)
@QtCore.pyqtSlot(int, name='on_graph_checkbox_stateChanged')
def changed_state(self, checked):
diff --git a/src/gui_qt/data/valueeditwidget.py b/src/gui_qt/data/valueeditwidget.py
index bb94965..c85f1d1 100644
--- a/src/gui_qt/data/valueeditwidget.py
+++ b/src/gui_qt/data/valueeditwidget.py
@@ -188,7 +188,15 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
new_value = complex(val)
new_value = new_value.real if new_value.imag == 0 else new_value
+ # table view loses focus when itemChanged is emitted
+ # if edit of item is cause of change resume editing at next item
+ prev_state = self.tableView.state()
+ idx = self.tableView.currentIndex()
+ idx = idx.sibling((col+1)//3+row, (col+1) % 3)
self.itemChanged.emit(sid, (col, row), new_value)
+ if prev_state == self.tableView.State.EditingState:
+ self.tableView.setCurrentIndex(idx)
+ self.tableView.edit(idx)
@QtCore.pyqtSlot(QtCore.QItemSelection, QtCore.QItemSelection)
def show_position(self, *_):
@@ -259,10 +267,7 @@ class ValueModel(QtCore.QAbstractTableModel):
row = idx.row()
if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]:
val = self._data[row][idx.column()]
- if isinstance(val, complex):
- return f'{val.real:.8g}{val.imag:+.8g}j'
- else:
- return f'{val:.8g}'
+ return self.as_string(val)
elif role == QtCore.Qt.BackgroundRole:
pal = QtGui.QGuiApplication.palette()
@@ -295,11 +300,16 @@ class ValueModel(QtCore.QAbstractTableModel):
if value:
if role == QtCore.Qt.EditRole:
+ if value == self.as_string(self._data[row][col]):
+ return True
+
try:
value = complex(value)
except ValueError:
# not a number
return False
+
+ value = value.real if value.imag == 0 else value
self._data[row][col] = value.real if value.imag == 0 else value
self.itemChanged.emit(col, row, str(value))
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role])
@@ -368,3 +378,10 @@ class ValueModel(QtCore.QAbstractTableModel):
def unmask(self):
self.mask = [True] * self.total_rows
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [ValueModel.maskRole])
+
+ @staticmethod
+ def as_string(value) -> str:
+ if isinstance(value, complex):
+ return f'{value.real:.8g}{value.imag:+.8g}j'
+ else:
+ return f'{value:.8g}'
diff --git a/src/gui_qt/fit/fit_parameter.py b/src/gui_qt/fit/fit_parameter.py
index bcb3d1f..682893e 100644
--- a/src/gui_qt/fit/fit_parameter.py
+++ b/src/gui_qt/fit/fit_parameter.py
@@ -181,6 +181,11 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
for i, value in enumerate(self.data_values[sid]):
w = self.data_parameter[i]
w.blockSignals(True)
+ try:
+ w.show_as_local_parameter(value is not None)
+ except AttributeError:
+ pass
+
if value is None:
w.value = self.glob_values[i]
else:
@@ -263,17 +268,17 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
return data_parameter, lb, ub, is_fixed, global_p, is_linked
def set_parameter(self, set_id: str | None, parameter: list[float]) -> int:
- param_len = len(list(filter(lambda g: not isinstance(g, SelectionWidget), self.global_parameter)))
-
+ num_parameter = list(filter(lambda g: not isinstance(g, SelectionWidget), self.global_parameter))
+ param_len = len(num_parameter)
if set_id is None:
- for val, g in zip(parameter, self.global_parameter):
- if isinstance(g, SelectionWidget):
- continue
+ for i, g in enumerate(num_parameter):
+ val = parameter[i]
g.set_parameter(val)
+ self.glob_values[i] = val
else:
new_param = self.data_values[set_id]
- min_len = min(param_len, len(new_param), len(new_param))
+ min_len = min(param_len, len(new_param))
for i in range(min_len):
new_param[i] = parameter[i]
@@ -293,6 +298,7 @@ class ParameterSingleWidget(QtWidgets.QWidget):
self._name = name
self.label.setText(convert(name))
+ self.label.setToolTip('IIf this is bold then this parameter is only for this data. otherwise the general parameter is used and displayed')
self.value_line.setValidator(QtGui.QDoubleValidator())
self.value_line.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0)
@@ -309,10 +315,12 @@ class ParameterSingleWidget(QtWidgets.QWidget):
layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum))
self.value_line = QtWidgets.QLineEdit(self)
+ self.value_line.textEdited.connect(lambda x: self.show_as_local_parameter(True))
layout.addWidget(self.value_line)
self.reset_button = QtWidgets.QToolButton(self)
self.reset_button.setText('Use global')
+ self.reset_button.clicked.connect(lambda: self.show_as_local_parameter(False))
layout.addWidget(self.reset_button)
self.setLayout(layout)
@@ -327,3 +335,9 @@ class ParameterSingleWidget(QtWidgets.QWidget):
@value.setter
def value(self, val):
self.value_line.setText(f'{float(val):.5g}')
+
+ def show_as_local_parameter(self, is_local):
+ if is_local:
+ self.label.setStyleSheet('font-weight: bold;')
+ else:
+ self.label.setStyleSheet('')
diff --git a/src/gui_qt/fit/fitwindow.py b/src/gui_qt/fit/fitwindow.py
index 9a77171..5ba0ea6 100644
--- a/src/gui_qt/fit/fitwindow.py
+++ b/src/gui_qt/fit/fitwindow.py
@@ -77,16 +77,11 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
w.deleteLater()
del self.param_widgets[idx]
- if len(self.functionwidget) == 0:
+ self._current_function = None
+ if len(self.param_widgets) == 0:
# empty model
self.newmodel_button.setEnabled(False)
self.deletemodel_button.setEnabled(False)
- self._current_function = None
-
- else:
- f_tree = self.functionwidget.functree
- func_idx = f_tree.currentItem().data(0, f_tree.counterRole)
- self._current_function = self.functionwidget.functions[func_idx]
@QtCore.pyqtSlot(int)
def show_function_parameter(self, function_id: int, function_idx: int = None):
diff --git a/src/gui_qt/fit/result.py b/src/gui_qt/fit/result.py
index 92a4f74..d854ce4 100644
--- a/src/gui_qt/fit/result.py
+++ b/src/gui_qt/fit/result.py
@@ -27,70 +27,80 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
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 = {}
+ self._previous_fits = {}
+ self._opts = []
+ self._results = {}
+ self.graph_opts = {}
+ self.last_idx = None
- for res in results:
- idx = res.idx
- data_k = management.data[idx]
+ self.resid_plot = self.graphicsView.addPlot(row=0, col=0, title='Residual')
+ self.fit_plot = self.graphicsView.addPlot(row=1, col=0, title='Fit')
- if res.name not in self._models:
- self._models[res.name] = []
+ self.graphicsView.ci.layout.setRowStretchFactor(0, 1)
+ self.graphicsView.ci.layout.setRowStretchFactor(1, 2)
- self._models[res.name].append(idx)
-
- self._prevs[idx] = []
- 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._opts = [(False, False) for _ in range(len(self._results))]
-
- self.residplot = self.graphicsView.addPlot(row=0, col=0)
self.resid_graph = PlotItem(x=[], y=[],
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)),
pen=None)
self.resid_graph_imag = PlotItem(x=[], y=[],
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
pen=None)
- self.residplot.addItem(self.resid_graph)
- self.residplot.addItem(self.resid_graph_imag)
- self.residplot.setLabel('left', 'Residual')
+ self.resid_plot.addItem(self.resid_graph)
+ self.resid_plot.addItem(self.resid_graph_imag)
- self.fitplot = self.graphicsView.addPlot(row=1, col=0)
self.data_graph = PlotItem(x=[], y=[],
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)),
pen=None)
self.data_graph_imag = PlotItem(x=[], y=[],
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
pen=None)
- self.fitplot.addItem(self.data_graph)
- self.fitplot.addItem(self.data_graph_imag)
- self.fitplot.setLabel('left', 'Function')
+ self.fit_plot.addItem(self.data_graph)
+ self.fit_plot.addItem(self.data_graph_imag)
self.fit_graph = PlotItem(x=[], y=[])
self.fit_graph_imag = PlotItem(x=[], y=[])
- self.fitplot.addItem(self.fit_graph)
- self.fitplot.addItem(self.fit_graph_imag)
+ self.fit_plot.addItem(self.fit_graph)
+ self.fit_plot.addItem(self.fit_graph_imag)
self.cmap = RdBuCMap(vmin=-1, vmax=1)
- self.sets_comboBox.blockSignals(True)
- for n in self._models.keys():
- self.sets_comboBox.addItem(n)
- self.sets_comboBox.blockSignals(False)
-
- self.set_parameter(0)
self.buttonBox.accepted.connect(self.accept)
- self.param_tableWidget.itemClicked.connect(self.show_results)
- self.param_tableWidget.horizontalHeader().sectionClicked.connect(lambda i: self.show_results(None, idx=i))
-
self.graph_checkBox.stateChanged.connect(lambda x: self.graph_comboBox.setEnabled(x == QtCore.Qt.Unchecked))
- self.logy_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(y=bool(x)))
- self.logx_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(x=bool(x)))
- self.residplot.setXLink(self.fitplot)
+ self.logy_box.stateChanged.connect(lambda x: self.fit_plot.setLogMode(y=bool(x)))
+ self.logx_box.stateChanged.connect(lambda x: self.fit_plot.setLogMode(x=bool(x)))
+ self.resid_plot.setXLink(self.fit_plot)
+
+ self.set_results(results)
+
+ def __call__(self, results: list):
+ self._previous_fits = {}
+ self.sets_comboBox.blockSignals(True)
+ self.sets_comboBox.clear()
+ self.sets_comboBox.blockSignals(False)
+ self._results = {}
+ self._opts = {}
+
+ self.set_results(results)
+
+ def set_results(self, results: list):
+ self.sets_comboBox.blockSignals(True)
+ for res in results:
+ idx = res.idx
+ data_k = self._management.data[idx]
+
+ self._previous_fits[idx] = []
+ for fit in data_k.get_fits():
+ self._previous_fits[idx].append((fit.name, fit.statistics, fit.nobs - fit.nvar))
+
+ self.sets_comboBox.addItem(data_k.name, userData=idx)
+ self.sets_comboBox.blockSignals(False)
+
+ self._results = {res.idx: res for res in results}
+ self._opts = [(False, False) for _ in range(len(self._results))]
+
+ self.set_parameter(0)
def add_graphs(self, graphs: list):
self.graph_comboBox.clear()
@@ -99,56 +109,40 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
@QtCore.pyqtSlot(int, name='on_sets_comboBox_currentIndexChanged')
def set_parameter(self, idx: int):
- model_name = self.sets_comboBox.itemText(idx)
- sets = self._models[model_name]
- self.param_tableWidget.setColumnCount(len(sets))
+ set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole)
- r = self._results[sets[0]]
- self.param_tableWidget.setRowCount(len(r.parameter))
+ res = self._results[set_id]
+ self.param_tableWidget.setRowCount(len(res.parameter))
+ for j, (pkey, pvalue) in enumerate(res.parameter.items()):
+ name = pkey
+ p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'str', brackets=True))
+ self.param_tableWidget.setVerticalHeaderItem(j, p_header)
- for i, pval in enumerate(r.parameter.values()):
- name = pval.full_name
- p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'html', brackets=False))
- self.param_tableWidget.setVerticalHeaderItem(i, p_header)
+ item_text = f'{pvalue.value:.4g}'
+ if pvalue.error is not None:
+ item_text += f' \u00b1 {pvalue.error:.4g}'
+ self.param_tableWidget.setItem(2*j+1, 0, QtWidgets.QTableWidgetItem('-'))
+ else:
+ self.param_tableWidget.setItem(2*j+1, 0, QtWidgets.QTableWidgetItem())
+ item = QtWidgets.QTableWidgetItem(item_text)
+ self.param_tableWidget.setItem(j, 0, item)
- for i, set_id in enumerate(sets):
- data_i = self._management[set_id]
- header_item = QtWidgets.QTableWidgetItem(data_i.name)
- header_item.setData(QtCore.Qt.UserRole, set_id)
- self.param_tableWidget.setHorizontalHeaderItem(i, header_item)
-
- res = self._results[set_id]
- for j, pvalue in enumerate(res.parameter.values()):
- item_text = f'{pvalue.value:.4g}'
- if pvalue.error is not None:
- item_text += f' \u00b1 {pvalue.error:.4g}'
- self.param_tableWidget.setItem(2*j+1, i, QtWidgets.QTableWidgetItem('-'))
- else:
- self.param_tableWidget.setItem(2*j+1, i, QtWidgets.QTableWidgetItem())
- item = QtWidgets.QTableWidgetItem(item_text)
- self.param_tableWidget.setItem(j, i, item)
-
- self.param_tableWidget.resizeColumnsToContents()
- self.param_tableWidget.selectColumn(0)
- self.show_results(None, idx=0)
+ self.param_tableWidget.resizeColumnToContents(0)
+ self.show_results(idx)
@QtCore.pyqtSlot(int, name='on_reject_fit_checkBox_stateChanged')
@QtCore.pyqtSlot(int, name='on_del_prev_checkBox_stateChanged')
def change_opts(self, _):
- idx = self.param_tableWidget.currentIndex().column()
+ idx = self.sets_comboBox.currentIndex()
self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.Checked,
self.del_prev_checkBox.checkState() == QtCore.Qt.Checked)
- def show_results(self, item, idx=None):
- if item is not None:
- idx = self.param_tableWidget.indexFromItem(item).column()
-
- set_id = self.param_tableWidget.horizontalHeaderItem(idx).data(QtCore.Qt.UserRole)
+ def show_results(self, idx):
+ set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole)
self.set_plot(set_id)
self.set_correlation(set_id)
self.set_statistics(set_id)
-
self.reject_fit_checkBox.blockSignals(True)
self.reject_fit_checkBox.setChecked(self._opts[idx][0])
self.reject_fit_checkBox.blockSignals(False)
@@ -157,13 +151,21 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.del_prev_checkBox.blockSignals(False)
def set_plot(self, idx: str):
+ if self.last_idx is not None:
+ self.graph_opts[self.last_idx] = (
+ self.fit_plot.viewRange(),
+ self.logx_box.isChecked(),
+ self.logy_box.isChecked(),
+ )
+
+ self.last_idx = idx
res = self._results[idx]
iscomplex = res.iscomplex
sub_funcs = res.sub(res.x)
- for item in self.fitplot.curves[::-1]:
+ for item in self.fit_plot.items[::-1]:
if item not in [self.data_graph, self.data_graph_imag, self.fit_graph, self.fit_graph_imag]:
- self.fitplot.removeItem(item)
+ self.fit_plot.removeItem(item)
if iscomplex:
self.data_graph.setData(x=res.x_data, y=res.y_data.real)
@@ -173,11 +175,11 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.resid_graph.setData(x=res.x_data, y=res.residual.real)
self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag)
- for f in sub_funcs:
- item = PlotItem(x=f.x, y=f.y.real)
- self.fitplot.addItem(item)
- item = PlotItem(x=f.x, y=f.y.imag)
- self.fitplot.addItem(item)
+ for i, f in enumerate(sub_funcs):
+ item = PlotItem(x=f.x, y=f.y.real, pen=mkPen({'color': i, 'style': 2}))
+ self.fit_plot.addItem(item)
+ item = PlotItem(x=f.x, y=f.y.imag, pen=mkPen({'color': i, 'style': 2}))
+ self.fit_plot.addItem(item)
else:
self.resid_graph.setData(x=res.x_data, y=res.residual)
@@ -187,12 +189,27 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.fit_graph.setData(x=res.x, y=res.y)
self.fit_graph_imag.setData(x=[], y=[])
- for f in sub_funcs:
- item = PlotItem(x=f.x, y=f.y, pen=mkPen({'style': 2}))
- self.fitplot.addItem(item)
+ for i, f in enumerate(sub_funcs):
+ item = PlotItem(x=f.x, y=f.y, pen=mkPen({'color': i, 'style': 2}))
+ self.fit_plot.addItem(item)
- self.fitplot.setLogMode(x=res.islog)
- self.residplot.setLogMode(x=res.islog)
+ self.logx_box.blockSignals(True)
+ self.logx_box.setChecked(res.islog)
+ self.logx_box.blockSignals(False)
+
+ self.fit_plot.setLogMode(x=res.islog)
+ self.resid_plot.setLogMode(x=res.islog)
+
+ if idx in self.graph_opts:
+ view_range, logx, logy = self.graph_opts[idx]
+ self.fit_plot.setRange(xRange=view_range[0], yRange=view_range[1], padding=0)
+ self.fit_plot.setLogMode(x=logx, y=logy)
+ self.logx_box.blockSignals(True)
+ self.logx_box.setChecked(logx)
+ self.logx_box.blockSignals(False)
+ self.logy_box.blockSignals(True)
+ self.logy_box.setChecked(logy)
+ self.logy_box.blockSignals(False)
def set_correlation(self, idx: str):
while self.corr_tableWidget.rowCount():
@@ -223,7 +240,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
res = self._results[idx]
- self.stats_tableWidget.setColumnCount(1 + len(self._prevs[idx]))
+ self.stats_tableWidget.setColumnCount(1 + len(self._previous_fits[idx]))
self.stats_tableWidget.setRowCount(len(res.statistics)+3)
it = QtWidgets.QTableWidgetItem(f'{res.dof}')
@@ -231,7 +248,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.stats_tableWidget.setVerticalHeaderItem(0, QtWidgets.QTableWidgetItem('DoF'))
self.stats_tableWidget.setItem(0, 0, it)
- for col, (name, _, dof) in enumerate(self._prevs[idx], start=1):
+ for col, (name, _, dof) in enumerate(self._previous_fits[idx], start=1):
self.stats_tableWidget.setHorizontalHeaderItem(0, QtWidgets.QTableWidgetItem(name))
it = QtWidgets.QTableWidgetItem(f'{dof}')
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
@@ -245,7 +262,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
best_idx = -1
best_val = v
- for col, (_, stats, _) in enumerate(self._prevs[idx], start=1):
+ for col, (_, stats, _) in enumerate(self._previous_fits[idx], start=1):
if k in ['adj. R^2', 'R^2']:
best_idx = col if best_val < stats[k] else max(0, best_idx)
else:
@@ -265,7 +282,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.stats_tableWidget.setVerticalHeaderItem(row+1, QtWidgets.QTableWidgetItem('Pr(>F)'))
self.stats_tableWidget.setItem(row+1, 0, QtWidgets.QTableWidgetItem('-'))
- for col, (_, stats, dof) in enumerate(self._prevs[idx], start=1):
+ for col, (_, stats, dof) in enumerate(self._previous_fits[idx], start=1):
f_value, prob_f = res.f_test(stats['chi^2'], dof)
it = QtWidgets.QTableWidgetItem(f'{f_value:.4g}')
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
@@ -312,7 +329,6 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
except TypeError:
pass
-
self.closed.emit(self._results, self._opts, graph, plot_fits, parts, extrapolate)
self.accept()
@@ -348,7 +364,7 @@ class FitExtension(QtWidgets.QDialog):
self.buttonBox = QtWidgets.QDialogButtonBox()
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2)
self.setLayout(gridLayout)
@@ -365,4 +381,4 @@ class FitExtension(QtWidgets.QDialog):
except TypeError:
return None
- return xmin, xmax, nums
\ No newline at end of file
+ return xmin, xmax, nums
diff --git a/src/gui_qt/io/asciireader.py b/src/gui_qt/io/asciireader.py
index 153b069..66f5d45 100644
--- a/src/gui_qt/io/asciireader.py
+++ b/src/gui_qt/io/asciireader.py
@@ -98,15 +98,19 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
@QtCore.pyqtSlot(int, name='on_preview_spinBox_valueChanged')
def show_preview(self, line_no: int):
- preview, width = self.reader.make_preview(line_no)
+ preview, width, comments = self.reader.make_preview(line_no)
self.ascii_table.setRowCount(min(line_no, len(preview)))
- self.ascii_table.setColumnCount(width)
+ self.ascii_table.setColumnCount(width + 1)
for i, line in enumerate(preview):
+ comment_line = comments[i]
for j, field in enumerate(line):
it = QtWidgets.QTableWidgetItem(field)
self.ascii_table.setItem(i, j, it)
+ it = QtWidgets.QTableWidgetItem(comment_line)
+ self.ascii_table.setItem(i, len(line), it)
+
self.ascii_table.resizeColumnsToContents()
@QtCore.pyqtSlot(int, name='on_column_checkBox_stateChanged')
diff --git a/src/gui_qt/io/exporters.py b/src/gui_qt/io/exporters.py
index e089991..75b4a55 100644
--- a/src/gui_qt/io/exporters.py
+++ b/src/gui_qt/io/exporters.py
@@ -55,9 +55,9 @@ class GraceExporter:
break
if c_num == -1:
- c_num = max(colors.keys())
- colors[c_num + 1] = (f'color{c_num + 1}', sc)
- new_colors.append((c_num + 1, f'color{c_num + 1}', sc))
+ c_num = max(colors.keys())+1
+ colors[c_num] = (f'color{c_num}', sc)
+ new_colors.append((c_num, f'color{c_num}', sc))
new_s.set_symbol(**{'symbol': item['symbol'].value, 'size': item['symbolsize'] / 10., 'color': c_num,
'fill color': c_num, 'fill pattern': 1})
diff --git a/src/gui_qt/io/gracereader.py b/src/gui_qt/io/gracereader.py
index 26b15ff..be3d62a 100644
--- a/src/gui_qt/io/gracereader.py
+++ b/src/gui_qt/io/gracereader.py
@@ -89,8 +89,11 @@ class QGraceReader(QtWidgets.QDialog, Ui_Dialog):
item = iterator.value()
key = (item.data(0, QtCore.Qt.UserRole))
s = self._reader.dataset(*key)
- label = self._reader.get_property(*key, 'legend').replace('"', '')
- # label = self._reader.graphs[key[0]].sets[key[1]]['legend'].replace('"', '')
+ label = self._reader.get_property(*key, 'legend')
+ if label is None:
+ label = ''
+ else:
+ label = label.replace('"', '')
sd = s.data
sd = np.atleast_2d(sd)
if s.type == 'xydy':
diff --git a/src/gui_qt/lib/delegates.py b/src/gui_qt/lib/delegates.py
index 6990276..2ef865c 100644
--- a/src/gui_qt/lib/delegates.py
+++ b/src/gui_qt/lib/delegates.py
@@ -42,7 +42,7 @@ class PropertyDelegate(QtWidgets.QStyledItemDelegate):
painter.setPen(pen)
pm = make_symbol_pixmap(r)
- painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, (options.rect.height()-pm.height())/2), pm)
+ painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, int((options.rect.height()-pm.height())/2)), pm)
style = QtWidgets.QApplication.style()
text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None)
@@ -171,7 +171,7 @@ class LineStyleEditor(QtWidgets.QComboBox):
rect = painter.style().subControlRect(QtWidgets.QStyle.CC_ComboBox,
opt, QtWidgets.QStyle.SC_ComboBoxEditField, None)
rect.adjust(+10, 0, -10, 0)
- mid = (rect.bottom() + rect.top()) / 2
+ mid = int((rect.bottom() + rect.top()) / 2)
painter.drawLine(rect.left(), mid, rect.right(), mid)
painter.end()
else:
@@ -193,7 +193,7 @@ class LineStyleDelegate(QtWidgets.QStyledItemDelegate):
rect = option.rect
rect.adjust(+10, 0, -10, 0)
- mid = (rect.bottom()+rect.top()) / 2
+ mid = int((rect.bottom()+rect.top()) / 2)
painter.drawLine(rect.left(), mid, rect.right(), mid)
else:
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
diff --git a/src/gui_qt/lib/utils.py b/src/gui_qt/lib/utils.py
index db0de99..2849e3f 100644
--- a/src/gui_qt/lib/utils.py
+++ b/src/gui_qt/lib/utils.py
@@ -58,7 +58,7 @@ class RdBuCMap:
elif val < self.min:
col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[-1])
else:
- col = QtGui.QColor.fromRgbF(*(float(self.spline[i](val)) for i in range(3)))
+ col = QtGui.QColor.fromRgb(*(int(self.spline[i](val)) for i in range(3)))
return col
diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py
index 5453cff..4d7327f 100644
--- a/src/gui_qt/main/mainwindow.py
+++ b/src/gui_qt/main/mainwindow.py
@@ -59,6 +59,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self._fit_plot_id = None
self.savefitdialog = None
self._tg_dialog = None
+ self.fitresult_dialog = None
self.eval = None
self.editor = None
@@ -78,9 +79,11 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
if Updater.get_update_information(os.getenv('APPIMAGE'))[0]:
self.look_for_update()
+ self.check_for_backup()
+
self.__timer = QtCore.QTimer()
self.__backup_path = config_paths() / f'{datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")}.nmr'
- self.__timer.start(3*60*1000) # every three minutese
+ self.__timer.start(3*60*1000) # every three minutes
self.__timer.timeout.connect(self._autosave)
self.fit_timer = QtCore.QTimer()
@@ -242,6 +245,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.actionConcatenate_sets.triggered.connect(lambda: self.management.cat())
+ self.management.graphs.valueChanged.connect(self.update_graph_list)
@QtCore.pyqtSlot(name='on_action_open_triggered')
def open(self):
@@ -376,10 +380,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
if wdgt == self.current_graph_widget:
if self.ptsselectwidget.connected_figure == gid:
self.ptsselectwidget.connected_figure = None
+ for line in self.ptsselectwidget.pts_lines:
+ self.current_graph_widget.remove_external(line)
+
self.tabWidget.removeTab(self.tabWidget.indexOf(self.ptsselectwidget))
if self.t1tauwidget.connected_figure == gid:
self.t1tauwidget.connected_figure = None
+ self.current_graph_widget.add_external(self.t1tauwidget.min_pos)
+ self.current_graph_widget.add_external(self.t1tauwidget.parabola)
self.tabWidget.removeTab(self.tabWidget.indexOf(self.t1tauwidget))
if self.fit_dialog.connected_figure == gid:
@@ -388,6 +397,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.current_graph_widget.remove_external(item)
if val_figure == gid:
+ self.current_graph_widget.remove_external(self.valuewidget.selection_real)
+ self.current_graph_widget.remove_external(self.valuewidget.selection_imag)
self.tabWidget.setCurrentIndex(0)
self.current_graph_widget.enable_picking(False)
@@ -416,6 +427,12 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
if self.area.subWindowList():
self.area.activateNextSubWindow()
+ @QtCore.pyqtSlot()
+ def update_graph_list(self):
+ graph_list = self.management.graphs.list()
+ self.t1tauwidget.set_graphs(graph_list)
+ self.ptsselectwidget.set_graphs(graph_list)
+
@QtCore.pyqtSlot(str)
def set_graph(self, key: str):
w = self.management.graphs[key]
@@ -932,11 +949,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.fit_timer.stop()
self.status.setText('')
if results:
- res_dialog = QFitResult(results, self.management, parent=self)
- res_dialog.add_graphs(self.management.graphs.list())
- res_dialog.closed.connect(self.accepts_fit)
- res_dialog.redoFit.connect(self.management.redo_fits)
- res_dialog.show()
+ if self.fitresult_dialog is None:
+ self.fitresult_dialog = QFitResult(results, self.management, parent=self)
+ self.fitresult_dialog.add_graphs(self.management.graphs.list())
+ self.fitresult_dialog.closed.connect(self.accepts_fit)
+ self.fitresult_dialog.redoFit.connect(self.management.redo_fits)
+ else:
+ self.fitresult_dialog(results)
+ self.fitresult_dialog.add_graphs(self.management.graphs.list())
+ self.fitresult_dialog.show()
@QtCore.pyqtSlot(dict, list, str, bool, bool, list)
def accepts_fit(self, res: dict, opts: list, param_graph: str,
@@ -1082,6 +1103,32 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
NMRWriter(self.management.graphs, self.management.data).export(self.__backup_path)
self.status.setText('')
+ def check_for_backup(self):
+ backups = []
+ for filename in config_paths().glob('*.nmr'):
+ try:
+ backups.append((filename, datetime.datetime.strptime(filename.stem, "%Y-%m-%d_%H%M%S")))
+ except ValueError:
+ continue
+
+ if backups:
+ backup_by_date = sorted(backups, key=lambda x: x[1])
+ msg = QtWidgets.QMessageBox.information(
+ self, 'Backup found',
+ f'{len(backups)} backup files in directory {backup_by_date[-1][0].parent} found.\n\n'
+ f'Latest backup date: {backup_by_date[-1][1]}\n\n'
+ f'Press Ok to load, Cancel to delete backup, Close to do nothing.',
+ QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Close
+ )
+
+ if msg == QtWidgets.QMessageBox.Ok:
+ self.management.load_files([str(backup_by_date[-1][0])])
+ backup_by_date[-1][0].unlink()
+ elif msg == QtWidgets.QMessageBox.Cancel:
+ backup_by_date[-1][0].unlink()
+ else:
+ pass
+
@QtCore.pyqtSlot(name='on_actionCreate_starter_triggered')
def create_starter(self):
make_starter(os.getenv('APPIMAGE'))
diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py
index b47732e..5a6f878 100644
--- a/src/gui_qt/main/management.py
+++ b/src/gui_qt/main/management.py
@@ -343,7 +343,7 @@ class UpperManagement(QtCore.QObject):
if joined is None:
joined = data_i.copy()
else:
- joined.append(data_i.x, data_i.y, data_i.y_err)
+ joined.append(data_i.data.x, data_i.data.y, y_err=data_i.data.y_err, mask=data_i.data.mask)
name_set.add(data_i.name)
group_set.add(data_i.group)
@@ -653,12 +653,10 @@ class UpperManagement(QtCore.QObject):
else:
continue
- for key, pvalue in data.parameter.items():
- name = pvalue.full_name
- fit_key = key + data.model_name
+ for fit_key, pvalue in data.parameter.items():
if fit_key not in fit_dict:
- fit_dict[fit_key] = [[], name]
+ fit_dict[fit_key] = [[], fit_key]
err = 0 if pvalue.error is None else pvalue.error
diff --git a/src/nmreval/configs.py b/src/nmreval/configs.py
index abdf2a3..3527139 100644
--- a/src/nmreval/configs.py
+++ b/src/nmreval/configs.py
@@ -10,23 +10,25 @@ __all__ = ['config_paths', 'check_for_config', 'read_configuration', 'write_conf
def check_for_config(make=True):
try:
- config_paths()
+ conf_path = config_paths()
except FileNotFoundError as e:
if make:
conf_path = pathlib.Path('~/.auswerten').expanduser()
conf_path.mkdir(parents=True)
- cwd = pathlib.Path(__file__).parent
- copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py')
-
- with resource_path('resources', 'Default.agr') as fp:
- copyfile(fp, conf_path / 'Default.agr')
-
- with resource_path('resources', 'logo.png') as fp:
- copyfile(fp, conf_path / 'logo.png')
else:
raise e
+ for filename in ('Default.agr', 'logo.png'):
+ _file = conf_path / filename
+ if not _file.exists():
+ with resource_path('resources', filename) as fp:
+ copyfile(fp, _file)
+
+ if not (conf_path / 'usermodels.py').exists():
+ cwd = pathlib.Path(__file__).parent
+ copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py')
+
def config_paths() -> pathlib.Path:
searchpaths = ['~/.config/nmreval', '~/.auswerten', '/usr/share/nmreval']
diff --git a/src/nmreval/data/points.py b/src/nmreval/data/points.py
index 8744ad7..cd3553a 100644
--- a/src/nmreval/data/points.py
+++ b/src/nmreval/data/points.py
@@ -484,7 +484,7 @@ class Points:
return self
- def set_data(self, x: np.ndarray = None, y: np.ndarray = None, y_err: np.ndarray = None) -> PointLike:
+ def set_data(self, x: np.ndarray = None, y: np.ndarray = None, y_err: np.ndarray = None, replace_mask=True) -> PointLike:
if x is None:
x = self._x
if y is None:
@@ -492,12 +492,17 @@ class Points:
if y_err is None:
y_err = self._y_err
- self._x, self._y, self._y_err, self.mask = self._prepare_xy(x, y, y_err)
+ self._x, self._y, self._y_err, mask = self._prepare_xy(x, y, y_err)
+ if replace_mask:
+ self.mask = mask
return self
- def append(self, x: ArrayLike, y: ArrayLike, y_err: ArrayLike = None):
- x, y, y_err, mask = self._prepare_xy(x, y, y_err)
+ def append(self, x: ArrayLike, y: ArrayLike, y_err: ArrayLike = None, mask: ArrayLike = None):
+ if mask is None:
+ x, y, y_err, mask = self._prepare_xy(x, y, y_err)
+ else:
+ x, y, y_err, _ = self._prepare_xy(x, y, y_err)
self._x = np.r_[self._x, x]
self._y = np.r_[self._y, y]
diff --git a/src/nmreval/fit/_meta.py b/src/nmreval/fit/_meta.py
index 230cb68..9fe9c34 100644
--- a/src/nmreval/fit/_meta.py
+++ b/src/nmreval/fit/_meta.py
@@ -68,6 +68,7 @@ class MultiModel:
self._kwargs_right = {}
self._kwargs_left = {}
self.fun_kwargs = {}
+ self.idx = (left_idx, right_idx)
# mapping kwargs to kwargs of underlying functions
self._ext_int_kw = {}
@@ -178,13 +179,13 @@ class MultiModel:
if isinstance(self._left, MultiModel):
yield from self._left.sub_name()
elif hasattr(self._left, 'name'):
- yield self._left.name
+ yield f'{self._left.name}({self.idx[0]})'
else:
yield self.name + '(lhs)'
if isinstance(self._right, MultiModel):
yield from self._right.sub_name()
elif hasattr(self._right, 'name'):
- yield self._right.name
+ yield f'{self._right.name}({self.idx[1]})'
else:
yield self.name + '(rhs)'
diff --git a/src/nmreval/fit/result.py b/src/nmreval/fit/result.py
index 74c1c47..65d340d 100644
--- a/src/nmreval/fit/result.py
+++ b/src/nmreval/fit/result.py
@@ -63,9 +63,23 @@ class FitResultCreator:
parameters = OrderedDict([(k, v) for k, v in zip(pnames, p)])
p_final = [p.value for p in parameters.values()]
- _y = model.func(p_final, _x, **fun_kwargs)
resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig
+ actual_mode = -1
+ if 'complex_mode' in fun_kwargs:
+ actual_mode = fun_kwargs['complex_mode']
+ fun_kwargs['complex_mode'] = 0
+
+ _y = model.func(p_final, _x, **fun_kwargs)
+
+ 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
+
stats = FitResultCreator.calc_statistics(_y, resid, nobs, nvar)
varied = [p.var for p in parameters.values()]
@@ -230,6 +244,7 @@ class FitResult(Points):
ret_val = ''
for pval in self.parameter.values():
+ print(pval)
ret_val += convert(str(pval), old='tex', new='str') + '\n'
if self.fun_kwargs:
@@ -319,7 +334,13 @@ class FitResult(Points):
f_value = 1e318
else:
f_value = (chi2-self.statistics['chi^2']) / (dof-self.dof) / self.statistics['red. chi^2']
- return f_value, 1-fdist.cdf(f_value, dof-self.dof, self.dof)
+
+ try:
+ prob_f = 1-fdist.cdf(f_value, dof-self.dof, self.dof)
+ except:
+ prob_f = 0
+
+ return f_value, prob_f
def get_state(self):
state = super().get_state()
@@ -349,20 +370,47 @@ class FitResult(Points):
def with_new_x(self, x_values):
if self.func is None:
- raise ValueError('no fit function available to calcualate new y values')
+ 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
+
new_fit.set_data(x_values, y_values)
return new_fit
def sub(self, x_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
+
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):
+ if not actual_mode < 0:
+ if actual_mode == 1:
+ sub_y.imag = 0
+ elif actual_mode == 2:
+ sub_y.real = 0
+
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
\ No newline at end of file
+ if actual_mode < 0:
+ self.fun_kwargs['complex_mode'] = actual_mode
+
+ return part_functions
diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py
index a998cc8..f8232b1 100644
--- a/src/nmreval/io/asciireader.py
+++ b/src/nmreval/io/asciireader.py
@@ -21,10 +21,8 @@ class AsciiReader:
self.fname = None
self.header = []
self.lines = []
- self.num_data = []
self.delays = None
self.width = []
- self.num_width = []
self.line_comment = []
self._last_read_pos = 0
@@ -45,16 +43,14 @@ class AsciiReader:
def make_preview(self, num_lines: int):
if num_lines <= len(self.lines):
- return self.lines[:num_lines], max(self.width[:num_lines])
+ return self.lines[:num_lines], max(self.width[:num_lines]), self.line_comment
num_lines += len(self.header)
with self.fname.open('r') as f:
for i, line in enumerate(islice(f, len(self.header)+len(self.lines), num_lines)):
line = line.rstrip('\n\t\r, ')
-
- is_empty = len(line) == 0
-
line = re.split(r'[\s,;]', line)
+
try:
comment_start = line.index('#')
self.line_comment.append(' '.join(line[comment_start:]))
@@ -62,14 +58,15 @@ class AsciiReader:
except ValueError:
self.line_comment.append('')
- if not is_empty:
- self.width.append(len(line))
- self.lines.append(line)
- if not line[0].startswith('#'):
- self.num_data.append(line)
- self.num_width.append(len(line))
+ is_empty = len(line) == 0
- return self.lines, max(self.width)
+ if not is_empty:
+ self.lines.append(line)
+ self.width.append(len(line))
+ else:
+ self.lines.append('')
+
+ return self.lines, max(self.width), self.line_comment
def look_for_delay(self, fname=None):
if fname is None:
@@ -112,7 +109,7 @@ class AsciiReader:
raise ValueError(f'x is {type(x)} not int')
if y is None:
- y = list(range(1, max(self.num_width)))
+ y = list(range(1, max(self.width)))
cols = x + y + yerr
with self.fname.open('rb') as fh:
@@ -122,7 +119,7 @@ class AsciiReader:
if raw_data.ndim == 1:
# only one row or column
- if len(self.num_data) == 1:
+ if len(self.lines) == 1:
# one row
raw_data = raw_data.reshape(1, -1)
else:
@@ -161,7 +158,7 @@ class AsciiReader:
for j in range(1, num_y+1, stepsize):
if col_names is not None:
# predefined name
- kwargs['name'] = col_names[j]
+ 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])
diff --git a/src/nmreval/io/dsc.py b/src/nmreval/io/dsc.py
index 532df8f..e64c60f 100644
--- a/src/nmreval/io/dsc.py
+++ b/src/nmreval/io/dsc.py
@@ -5,6 +5,7 @@ import re
from collections import namedtuple
import numpy as np
+from scipy.stats import linregress
try:
from scipy.integrate import simpson
@@ -200,35 +201,24 @@ class DSCCalibrator:
high_border = np.argmin(np.abs(measurement[0] - t_high_lim))
ref_zoom = measurement[:, low_border:high_border]
- x_val = np.array([[ref_zoom[0, 0], 1], [ref_zoom[0, -1], 1]])
- y_val = np.array([ref_zoom[1, 0], ref_zoom[1, -1]])
-
- sol = np.linalg.solve(x_val, y_val)
- ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
-
- ref_grad = np.gradient(ref_zoom[1])
- crossing = np.where(np.diff(np.sign(np.abs(ref_grad)-0.001)))[0]
-
- almost_flat = np.sort(crossing-np.argmax(ref_zoom[1]))
- integ_limit = (almost_flat[np.where((almost_flat < -20))[0][-1]]+np.argmax(ref_zoom[1]),
- almost_flat[np.where((almost_flat > 20))[0][0]]+np.argmax(ref_zoom[1]))
-
- # subtract baseline around reference peak
- sol = self.solve_linear_eq(integ_limit, ref_zoom)
- ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
-
- # calculate onset slope (use points at position of maximum gradient - 100/rate (+50/rate))
- ref_grad = np.gradient(ref_zoom[1])
- max_grad = np.argmax(ref_grad)
-
- grad_pos = max_grad-max(1, int(160 / rate)), max_grad
-
- sol = self.solve_linear_eq(grad_pos, ref_zoom)
- onset = sol[0] * ref_zoom[0] + sol[1]
-
- melts.append(-sol[1] / sol[0])
-
if enthalpy is not None:
+ x_val = np.array([[ref_zoom[0, 0], 1], [ref_zoom[0, -1], 1]])
+ y_val = np.array([ref_zoom[1, 0], ref_zoom[1, -1]])
+
+ sol = np.linalg.solve(x_val, y_val)
+ ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
+
+ ref_grad = np.gradient(ref_zoom[1])
+ crossing = np.where(np.diff(np.sign(np.abs(ref_grad)-0.001)))[0]
+
+ almost_flat = np.sort(crossing-np.argmax(ref_zoom[1]))
+ integ_limit = (almost_flat[np.where((almost_flat < -40))[0][-1]]+np.argmax(ref_zoom[1]),
+ almost_flat[np.where((almost_flat > 40))[0][0]]+np.argmax(ref_zoom[1]))
+
+ # subtract baseline around reference peak
+ sol = self.solve_linear_eq(integ_limit, ref_zoom)
+ ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
+
# integrate over peak to calibrate y axis
# delta H in J/g: Integrate Peak over time and divide by weight
area = simpson(ref_zoom[1, integ_limit[0]:integ_limit[1]],
@@ -236,6 +226,23 @@ class DSCCalibrator:
even='avg') * 1e-3
calib_y.append(enthalpy / (area / data.weight))
+ else:
+ ref_grad = np.gradient(ref_zoom[1])
+ res = linregress(ref_zoom[0, :len(ref_grad)//5], ref_zoom[1, :len(ref_grad)//5])
+
+ ref_zoom[1] -= (res.slope*ref_zoom[0] + res.intercept)
+
+ # calculate onset slope (use points at position of maximum gradient - 100/rate (+50/rate))
+ ref_grad = np.gradient(ref_zoom[1])
+ max_grad = np.argmax(ref_grad)
+
+ grad_pos = max_grad - max(1, int(160 / rate)), max_grad
+
+ sol = self.solve_linear_eq(grad_pos, ref_zoom)
+ onset = sol[0] * ref_zoom[0] + sol[1]
+
+ melts.append(-sol[1] / sol[0])
+
results.append([ref_zoom, onset, ref_zoom[:, grad_pos]])
if len(melts) > 1:
diff --git a/src/nmreval/io/fcbatchreader.py b/src/nmreval/io/fcbatchreader.py
index b3168cf..ea319b4 100644
--- a/src/nmreval/io/fcbatchreader.py
+++ b/src/nmreval/io/fcbatchreader.py
@@ -1,6 +1,8 @@
from __future__ import annotations
import pathlib
+import subprocess
+import tempfile
# import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
@@ -9,6 +11,7 @@ import numpy as np
from nmreval.data.points import Points
from nmreval.io.asciireader import AsciiReader
from nmreval.io.hdfreader import HdfReader
+from nmreval.lib.logger import logger
from nmreval.utils.utils import get_temperature, roundrobin
@@ -54,7 +57,7 @@ class FCReader:
_temp = self._read_from_dir(filename)
else:
- raise TypeError
+ raise TypeError(f'{filename} is of unknown type')
if not _temp:
raise OSError(-666, f'No magnetization found for {filename.name}.', filename.name)
@@ -130,7 +133,7 @@ class FCReader:
no_bevo = True
break
- if str(k.parent) != 'ACC_ABS_FID_sig (group)':
+ if str(k.parent) != 'ABS_ACC_FID_sig (group)':
continue
tevo = k.parameter['tevo']
@@ -141,14 +144,14 @@ class FCReader:
if bevo not in _temp:
_temp[bevo] = []
- _temp[bevo].append((tevo, *[pp[1] for pp in pts]))
+ _temp[bevo].append((tevo, *[np.real(pp[1]) for pp in pts]))
if no_bevo:
break
if no_bevo:
# hopefully only for old scripts
- sig = reader.get_selected('/data/B=*/ACC_ABS_FID*', dtype='signal')
+ sig = reader.get_selected('/data/B=*/ABS_ACC_FID*', dtype='signal')
_temp = {}
for s in sig:
pts = s.points([region])
@@ -156,7 +159,7 @@ class FCReader:
if b not in _temp:
_temp[b] = []
- _temp[b].append([s.value, *[pp[1] for pp in pts]])
+ _temp[b].append([s.value, *[np.real(pp[1]) for pp in pts]])
for b, m in sorted(_temp.items()):
m = np.array(m)
@@ -178,6 +181,7 @@ class FCReader:
fit_path.mkdir(parents=True, exist_ok=True)
if save_fig:
+ data_path = fname_no_ext.joinpath('data')
image_path = fname_no_ext.joinpath('png')
image_path.mkdir(parents=True, exist_ok=True)
@@ -188,15 +192,16 @@ class FCReader:
freqs = []
for k, v in sorted(self.data[temperature].items()):
- freqs.append(k)
-
# fit
p0 = [v.y[0], v.y[-1]-v.y[0], v.x[int(0.5*len(v.x) - 0.5)], 1]
try:
p0, pcov = curve_fit(FCReader.kww, v.x, v.y, p0=p0, bounds=bounds, max_nfev=500*len(v))
- except RuntimeError:
+ except Exception as e:
+ logger.error(f'Fit of field {k} failed with exception', exc_info=e)
continue
+ freqs.append(k)
+
perr = np.sqrt(np.diag(pcov))
params.append(p0)
errors.append(perr)
@@ -218,16 +223,34 @@ class FCReader:
np.savetxt(fit_path.joinpath(save_name), np.c_[xplot, yplot],
header=header+'\t'.join([f'{p}+/-{err}' for p, err in zip(p0, perr)]))
- # if save_fig:
- # fig, ax = plt.subplots()
- # ax.set_xlabel('t / s')
- # ax.set_ylabel('M')
- # axheader = f'T1: {p0[2]:.4g}(+/-{perr[2]:.4g}) beta: {p0[3]:.4g}(+/-{perr[3]:.4g})'
- # ax.set_title(f'f = {k:.4g} Hz\n{axheader}')
- # ax.semilogx(v.x, v.y, 'o')
- # ax.semilogx(xplot, yplot, '-')
- # fig.savefig(image_path.joinpath(save_name).with_suffix('.png'))
- # plt.close(fig)
+ if save_fig:
+ img_file = image_path.joinpath(save_name).with_suffix(".png")
+
+ gnuplot_args = [
+ 'set terminal png;',
+ f'set output "{img_file}";',
+ f'set title "f = {k:.4g} Hz\\n{p0[2]:.4g}(+/-{perr[2]:.4g}) beta: {p0[3]:.4g}(+/-{perr[3]:.4g})";',
+ 'set xlabel "t / s";',
+ 'set logscale x;',
+ 'set format x "10^{{%L}}";',
+ 'set ylabel "M";',
+ 'set key off;',
+ f'plot "{data_path.joinpath(save_name)}" with points pointtype 5, "{fit_path.joinpath(save_name)}" with lines;',
+ ]
+
+ try:
+ proc = subprocess.Popen(
+ ['gnuplot', '-p'],
+ shell=True,
+ stdin=subprocess.PIPE,
+ encoding='utf8',
+ )
+ for args in gnuplot_args:
+ proc.stdin.write(args)
+ proc.stdin.write('quit\n')
+ proc.stdin.flush()
+ except Exception as e:
+ logger.error(f'saving image {save_name} failed', e)
freqs = np.asanyarray(freqs)
params = np.asanyarray(params)
diff --git a/src/nmreval/io/graceeditor.py b/src/nmreval/io/graceeditor.py
index 58a114e..f657f68 100644
--- a/src/nmreval/io/graceeditor.py
+++ b/src/nmreval/io/graceeditor.py
@@ -1,9 +1,7 @@
from __future__ import annotations
-import codecs
import pathlib
import re
-import warnings
from io import StringIO
from typing import Optional, Tuple
@@ -46,6 +44,22 @@ class GraceEditor:
if filename is not None:
self.parse(filename)
+ def _fix_tuda_colors(self):
+ # 2023-05-11: Default.agr had wrong TUDa colors (4a, 7b, 9b, 9d), so set the values given in colors.py
+ color_mapping = [
+ ('tuda4a', (175, 204, 80)),
+ ('tuda7b', (245, 163, 0)),
+ ('tuda9b', (230, 0, 26)),
+ ('tuda9d', (156, 28, 38)),
+ ]
+
+ for i, line in enumerate(self.header):
+ m = self._RE_COLOR.match(line)
+ if m:
+ for name, right_color in color_mapping:
+ if m['disp'].lower() == name:
+ self.header[i] = f'@map color {m["id"]} to {right_color}, "{m["disp"]}"\n'
+
def __call__(self, filename: str):
self.clear()
self.parse(filename)
@@ -193,6 +207,8 @@ class GraceEditor:
self.graphs[-1].append(line)
+ self._fix_tuda_colors()
+
def _make_graph(self, line: str):
m = self._RE_GRAPH_START.match(line)
g_idx = int(m.group(1))
@@ -212,7 +228,6 @@ class GraceEditor:
m = self._RE_COLOR.match(line)
if m:
_colors[int(m['id'])] = (m['disp'], (int(m['red']), int(m['green']), int(m['blue'])))
-
return _colors
def get_color(self, color_num):
diff --git a/src/nmreval/io/nmrreader.py b/src/nmreval/io/nmrreader.py
index 2268c8c..18bae10 100644
--- a/src/nmreval/io/nmrreader.py
+++ b/src/nmreval/io/nmrreader.py
@@ -5,6 +5,8 @@ from collections import OrderedDict
from .read_old_nmr import HAS_BSDDB3, _read_file_v1
from ..data.nmr import FID, Spectrum
+from ..data.bds import BDS
+from ..data.dsc import DSC
from ..data.points import Points
from ..fit.result import FitResult, FitResultCreator
from ..lib.colors import Colors
@@ -51,11 +53,17 @@ class NMRReader:
states = pickle.load(fp)
datalist = OrderedDict()
- _dtypes = {'pts': Points, 'fit': FitResult, 'fid': FID}
+ _dtypes = {'pts': Points,
+ 'fit': FitResult,
+ 'fid': FID,
+ 'bds': BDS,
+ 'dsc': DSC,
+ 'spectrum': Spectrum
+ }
for s in states['sets']:
set_id = s.pop('id')
- dtype = _dtypes[s.pop('mode')]
+ dtype = _dtypes.get(s.pop('mode'), FID)
data = dtype.set_state(s.pop('data'))
datalist[set_id] = (data, s)
diff --git a/src/nmreval/lib/colors.py b/src/nmreval/lib/colors.py
index 90765a6..d3aba66 100644
--- a/src/nmreval/lib/colors.py
+++ b/src/nmreval/lib/colors.py
@@ -247,6 +247,7 @@ class Tab10(BaseColor):
TabChartreuse = TUColorsC.TUDa5c.value
TabTurquoise = TUColorsA.TUDa2a.value
+
class Tab20(BaseColor):
TabBlue = (31, 119, 180)
TabBlue2 = (174, 199, 232)
diff --git a/src/resources/Default.agr b/src/resources/Default.agr
index 8d08a08..10e7a0c 100644
--- a/src/resources/Default.agr
+++ b/src/resources/Default.agr
@@ -64,7 +64,7 @@
@map color 2 to (93, 133, 195), "TUDa1a"
@map color 3 to (0, 156, 218), "TUDa2a"
@map color 4 to (80, 182, 149), "TUDa3a"
-@map color 5 to (176, 204, 80), "TUDa4a"
+@map color 5 to (175, 204, 80), "TUDa4a"
@map color 6 to (221, 223, 72), "TUDa5a"
@map color 7 to (255, 224, 92), "TUDa6a"
@map color 8 to (248, 186, 60), "TUDa7a"
@@ -78,9 +78,9 @@
@map color 16 to (153, 192, 0), "TUDa4b"
@map color 17 to (201, 212, 0), "TUDa5b"
@map color 18 to (253, 202, 0), "TUDa6b"
-@map color 19 to (248, 163, 0), "TUDa7b"
+@map color 19 to (245, 163, 0), "TUDa7b"
@map color 20 to (236, 101, 0), "TUDa8b"
-@map color 21 to (239, 0, 26), "TUDa9b"
+@map color 21 to (230, 0, 26), "TUDa9b"
@map color 22 to (166, 0, 132), "TUDa10b"
@map color 23 to (114, 16, 133), "TUDa11b"
@map color 24 to (0, 78, 138), "TUDa1c"
@@ -102,7 +102,7 @@
@map color 40 to (174, 142, 0), "TUDa6d"
@map color 41 to (190, 111, 0), "TUDa7d"
@map color 42 to (169, 73, 19), "TUDa8d"
-@map color 43 to (188, 28, 38), "TUDa9d"
+@map color 43 to (156, 28, 38), "TUDa9d"
@map color 44 to (115, 32, 84), "TUDa10d"
@map color 45 to (76, 34, 106), "TUDa11d"
@map color 46 to (220, 220, 220), "TUDa0a"
diff --git a/src/resources/_ui/bdsdialog.ui b/src/resources/_ui/bdsdialog.ui
index fa275ee..9779e8e 100644
--- a/src/resources/_ui/bdsdialog.ui
+++ b/src/resources/_ui/bdsdialog.ui
@@ -30,13 +30,16 @@
3
-
-
+
0
0
+
+ QAbstractItemView::ExtendedSelection
+
-
@@ -207,6 +210,13 @@
+
+
+ QListWidgetSelect
+ QListWidget
+
+
+
freq_button
temp_button
diff --git a/src/resources/_ui/fitresult.ui b/src/resources/_ui/fitresult.ui
index c6dd2c0..bd81cb1 100644
--- a/src/resources/_ui/fitresult.ui
+++ b/src/resources/_ui/fitresult.ui
@@ -6,36 +6,39 @@
0
0
- 864
- 649
+ 969
+ 974
Fit results
- -
-
-
-
- 0
- 0
-
+
-
+
+
+ 3
-
-
- 400
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QComboBox::AdjustToMinimumContentsLength
+
-
+
+
+ Reject this fit
+
+
+
+ -
+
+
+ Delete previous fits
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
@@ -46,37 +49,6 @@
- -
-
-
- Qt::ScrollBarAsNeeded
-
-
- QAbstractScrollArea::AdjustIgnored
-
-
- QAbstractItemView::NoEditTriggers
-
-
- true
-
-
- QAbstractItemView::SingleSelection
-
-
- QAbstractItemView::SelectColumns
-
-
- false
-
-
- 0
-
-
- false
-
-
-
-
@@ -223,35 +195,67 @@
- -
-
-
- 3
+
-
+
+
+
+ 0
+ 0
+
-
-
-
-
- Reject this fit
-
-
-
- -
-
-
- Delete previous fits
-
-
-
-
+
+ Qt::ScrollBarAsNeeded
+
+
+ QAbstractScrollArea::AdjustIgnored
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ true
+
+
+ QAbstractItemView::NoSelection
+
+
+ 1
+
+
+ false
+
+
+ true
+
+
+
- -
-
-
- Qt::Horizontal
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 400
+ 16777215
+
+
+
+
+ 200
+ 0
+
+
+
+ QComboBox::AdjustToMinimumContentsLength
- -
+
-
@@ -262,7 +266,7 @@
0
-
+
Plot
@@ -307,7 +311,7 @@
-
+
Statistics
@@ -349,7 +353,7 @@
-
+
Correlations
@@ -415,16 +419,16 @@
-
- GraphicsLayoutWidget
- QGraphicsView
-
-
ElideComboBox
QComboBox
+
+ GraphicsLayoutWidget
+ QGraphicsView
+
+
diff --git a/src/resources/_ui/graph.ui b/src/resources/_ui/graph.ui
index 7c829d1..02f573f 100644
--- a/src/resources/_ui/graph.ui
+++ b/src/resources/_ui/graph.ui
@@ -515,13 +515,16 @@
0
-
-
+
0
0
+
+ QAbstractItemView::ExtendedSelection
+
-
@@ -554,6 +557,11 @@
QGraphicsView
+
+ QListWidgetSelect
+ QListWidget
+
+
logx_button
diff --git a/src/resources/_ui/interpol_dialog.ui b/src/resources/_ui/interpol_dialog.ui
index 8aabd2f..28fa858 100644
--- a/src/resources/_ui/interpol_dialog.ui
+++ b/src/resources/_ui/interpol_dialog.ui
@@ -141,7 +141,7 @@
-
-
+
0
@@ -151,6 +151,9 @@
Select sets that shall be interpolated. No selection will create interpolations of all visible sets.
+
+ QAbstractItemView::MultiSelection
+
-
@@ -276,6 +279,13 @@
+
+
+ QListWidgetSelect
+ QListWidget
+
+
+
listWidget
ylog_checkBox