From a26595695c8334a742b34e2a2c90287dd035bd23 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Tue, 20 Jun 2023 17:13:13 +0000 Subject: [PATCH] Binning and Tg (#85) add binning; determine Tg of DSC; closes #60; part of #61 Co-authored-by: Dominik Demuth Reviewed-on: https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/pulls/85 --- src/gui_qt/_py/asciidialog.py | 248 +++++----- src/gui_qt/_py/basewindow.py | 28 +- src/gui_qt/_py/tnmh_dialog.py | 190 ++++++++ src/gui_qt/data/container.py | 10 +- src/gui_qt/dsc/glass_dialog.py | 299 ++++++++++++ src/gui_qt/fit/fitfunction.py | 1 + src/gui_qt/io/asciireader.py | 4 +- src/gui_qt/io/dscreader.py | 5 +- src/gui_qt/lib/forms.py | 20 + src/gui_qt/lib/pg_objects.py | 6 +- src/gui_qt/main/mainwindow.py | 23 +- src/gui_qt/main/management.py | 33 ++ src/gui_qt/math/binning.py | 21 + src/nmreval/data/dsc.py | 133 ++++- src/nmreval/data/points.py | 29 ++ src/nmreval/dsc/hodge.py | 26 + src/nmreval/dsc/tnmh_model.py | 46 ++ src/nmreval/fit/parameter.py | 1 + src/nmreval/io/asciireader.py | 28 +- src/nmreval/io/hdfreader.py | 9 +- src/nmreval/models/bds.py | 10 +- src/nmreval/utils/constants.py | 2 +- src/resources/_ui/asciidialog.ui | 459 ++++++++++-------- src/resources/_ui/basewindow.ui | 20 +- src/resources/_ui/tnmh_dialog.ui | 345 +++++++++++++ src/{ => resources}/pkm.vogel.nmreval.desktop | 0 26 files changed, 1643 insertions(+), 353 deletions(-) create mode 100644 src/gui_qt/_py/tnmh_dialog.py create mode 100644 src/gui_qt/dsc/glass_dialog.py create mode 100644 src/gui_qt/math/binning.py create mode 100644 src/nmreval/dsc/hodge.py create mode 100644 src/nmreval/dsc/tnmh_model.py create mode 100644 src/resources/_ui/tnmh_dialog.ui rename src/{ => resources}/pkm.vogel.nmreval.desktop (100%) diff --git a/src/gui_qt/_py/asciidialog.py b/src/gui_qt/_py/asciidialog.py index 66ceccf..f3c89db 100644 --- a/src/gui_qt/_py/asciidialog.py +++ b/src/gui_qt/_py/asciidialog.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'src/resources/_ui/asciidialog.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# 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. @@ -28,92 +28,25 @@ class Ui_ascii_reader(object): self.header_widget.setObjectName("header_widget") self.verticalLayout_3.addWidget(self.header_widget) self.groupBox = QtWidgets.QGroupBox(self.tabWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) + self.groupBox.setSizePolicy(sizePolicy) self.groupBox.setObjectName("groupBox") - self.gridLayout = QtWidgets.QGridLayout(self.groupBox) - self.gridLayout.setContentsMargins(3, 3, 3, 3) - self.gridLayout.setHorizontalSpacing(9) - self.gridLayout.setObjectName("gridLayout") - self.FID_radioButton = QtWidgets.QRadioButton(self.groupBox) - self.FID_radioButton.setAutoExclusive(True) - self.FID_radioButton.setObjectName("FID_radioButton") - self.buttonGroup = QtWidgets.QButtonGroup(ascii_reader) - self.buttonGroup.setObjectName("buttonGroup") - self.buttonGroup.addButton(self.FID_radioButton) - self.gridLayout.addWidget(self.FID_radioButton, 2, 1, 1, 1) - self.x_lineedit = QtWidgets.QLineEdit(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.x_lineedit.sizePolicy().hasHeightForWidth()) - self.x_lineedit.setSizePolicy(sizePolicy) - self.x_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers) - self.x_lineedit.setObjectName("x_lineedit") - self.gridLayout.addWidget(self.x_lineedit, 1, 3, 1, 1) - self.y_label = QtWidgets.QLabel(self.groupBox) - self.y_label.setObjectName("y_label") - self.gridLayout.addWidget(self.y_label, 2, 2, 1, 1) - self.pts_radioButton = QtWidgets.QRadioButton(self.groupBox) - self.pts_radioButton.setChecked(True) - self.pts_radioButton.setAutoExclusive(True) - self.pts_radioButton.setObjectName("pts_radioButton") - self.buttonGroup.addButton(self.pts_radioButton) - self.gridLayout.addWidget(self.pts_radioButton, 1, 1, 1, 1) - self.deltay_lineEdit = QtWidgets.QLineEdit(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.deltay_lineEdit.sizePolicy().hasHeightForWidth()) - self.deltay_lineEdit.setSizePolicy(sizePolicy) - self.deltay_lineEdit.setObjectName("deltay_lineEdit") - self.gridLayout.addWidget(self.deltay_lineEdit, 3, 3, 1, 1) - self.label_5 = QtWidgets.QLabel(self.groupBox) - self.label_5.setObjectName("label_5") - self.gridLayout.addWidget(self.label_5, 3, 2, 1, 1) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.groupBox) + self.horizontalLayout_4.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.verticalLayout_6 = QtWidgets.QVBoxLayout() + self.verticalLayout_6.setObjectName("verticalLayout_6") self.column_checkBox = QtWidgets.QCheckBox(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.column_checkBox.sizePolicy().hasHeightForWidth()) self.column_checkBox.setSizePolicy(sizePolicy) self.column_checkBox.setObjectName("column_checkBox") - self.gridLayout.addWidget(self.column_checkBox, 0, 0, 1, 1) - self.label = QtWidgets.QLabel(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) - self.label.setSizePolicy(sizePolicy) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 1, 1, 1) - self.label_7 = QtWidgets.QLabel(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth()) - self.label_7.setSizePolicy(sizePolicy) - self.label_7.setObjectName("label_7") - self.gridLayout.addWidget(self.label_7, 0, 2, 1, 2) - self.y_lineedit = QtWidgets.QLineEdit(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.y_lineedit.sizePolicy().hasHeightForWidth()) - self.y_lineedit.setSizePolicy(sizePolicy) - self.y_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers) - self.y_lineedit.setObjectName("y_lineedit") - self.gridLayout.addWidget(self.y_lineedit, 2, 3, 1, 1) - self.spectrum_radioButton = QtWidgets.QRadioButton(self.groupBox) - self.spectrum_radioButton.setObjectName("spectrum_radioButton") - self.buttonGroup.addButton(self.spectrum_radioButton) - self.gridLayout.addWidget(self.spectrum_radioButton, 3, 1, 1, 1) - self.x_label = QtWidgets.QLabel(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.x_label.sizePolicy().hasHeightForWidth()) - self.x_label.setSizePolicy(sizePolicy) - self.x_label.setObjectName("x_label") - self.gridLayout.addWidget(self.x_label, 1, 2, 1, 1) + self.verticalLayout_6.addWidget(self.column_checkBox) self.line_spinBox = QtWidgets.QSpinBox(self.groupBox) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -122,17 +55,114 @@ class Ui_ascii_reader(object): self.line_spinBox.setSizePolicy(sizePolicy) self.line_spinBox.setMinimum(1) self.line_spinBox.setObjectName("line_spinBox") - self.gridLayout.addWidget(self.line_spinBox, 1, 0, 1, 1) + self.verticalLayout_6.addWidget(self.line_spinBox) + self.label_6 = QtWidgets.QLabel(self.groupBox) + self.label_6.setObjectName("label_6") + self.verticalLayout_6.addWidget(self.label_6) self.preview_spinBox = QtWidgets.QSpinBox(self.groupBox) self.preview_spinBox.setMinimum(1) self.preview_spinBox.setProperty("value", 10) self.preview_spinBox.setObjectName("preview_spinBox") - self.gridLayout.addWidget(self.preview_spinBox, 3, 0, 1, 1) - self.label_6 = QtWidgets.QLabel(self.groupBox) - self.label_6.setObjectName("label_6") - self.gridLayout.addWidget(self.label_6, 2, 0, 1, 1) + self.verticalLayout_6.addWidget(self.preview_spinBox) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_6.addItem(spacerItem) + self.horizontalLayout_4.addLayout(self.verticalLayout_6) + self.verticalLayout_5 = QtWidgets.QVBoxLayout() + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.label = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setObjectName("label") + self.verticalLayout_5.addWidget(self.label) + self.pts_radioButton = QtWidgets.QRadioButton(self.groupBox) + self.pts_radioButton.setChecked(True) + self.pts_radioButton.setAutoExclusive(True) + self.pts_radioButton.setObjectName("pts_radioButton") + self.buttonGroup = QtWidgets.QButtonGroup(ascii_reader) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.pts_radioButton) + self.verticalLayout_5.addWidget(self.pts_radioButton) + self.dsc_radioButton = QtWidgets.QRadioButton(self.groupBox) + self.dsc_radioButton.setObjectName("dsc_radioButton") + self.buttonGroup.addButton(self.dsc_radioButton) + self.verticalLayout_5.addWidget(self.dsc_radioButton) + self.FID_radioButton = QtWidgets.QRadioButton(self.groupBox) + self.FID_radioButton.setAutoExclusive(True) + self.FID_radioButton.setObjectName("FID_radioButton") + self.buttonGroup.addButton(self.FID_radioButton) + self.verticalLayout_5.addWidget(self.FID_radioButton) + self.spectrum_radioButton = QtWidgets.QRadioButton(self.groupBox) + self.spectrum_radioButton.setObjectName("spectrum_radioButton") + self.buttonGroup.addButton(self.spectrum_radioButton) + self.verticalLayout_5.addWidget(self.spectrum_radioButton) + self.bds_radioButton = QtWidgets.QRadioButton(self.groupBox) + self.bds_radioButton.setObjectName("bds_radioButton") + self.buttonGroup.addButton(self.bds_radioButton) + self.verticalLayout_5.addWidget(self.bds_radioButton) + self.horizontalLayout_4.addLayout(self.verticalLayout_5) + self.gridLayout_2 = QtWidgets.QGridLayout() + self.gridLayout_2.setObjectName("gridLayout_2") + self.deltay_lineEdit = QtWidgets.QLineEdit(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.deltay_lineEdit.sizePolicy().hasHeightForWidth()) + self.deltay_lineEdit.setSizePolicy(sizePolicy) + self.deltay_lineEdit.setObjectName("deltay_lineEdit") + self.gridLayout_2.addWidget(self.deltay_lineEdit, 3, 1, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth()) + self.label_7.setSizePolicy(sizePolicy) + self.label_7.setObjectName("label_7") + self.gridLayout_2.addWidget(self.label_7, 0, 0, 1, 2) + self.y_lineedit = QtWidgets.QLineEdit(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.y_lineedit.sizePolicy().hasHeightForWidth()) + self.y_lineedit.setSizePolicy(sizePolicy) + self.y_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers) + self.y_lineedit.setObjectName("y_lineedit") + self.gridLayout_2.addWidget(self.y_lineedit, 2, 1, 1, 1) + self.y_label = QtWidgets.QLabel(self.groupBox) + self.y_label.setObjectName("y_label") + self.gridLayout_2.addWidget(self.y_label, 2, 0, 1, 1) + self.x_lineedit = QtWidgets.QLineEdit(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.x_lineedit.sizePolicy().hasHeightForWidth()) + self.x_lineedit.setSizePolicy(sizePolicy) + self.x_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers) + self.x_lineedit.setObjectName("x_lineedit") + self.gridLayout_2.addWidget(self.x_lineedit, 1, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(self.groupBox) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 3, 0, 1, 1) + self.x_label = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.x_label.sizePolicy().hasHeightForWidth()) + self.x_label.setSizePolicy(sizePolicy) + self.x_label.setObjectName("x_label") + self.gridLayout_2.addWidget(self.x_label, 1, 0, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem1, 4, 1, 1, 1) + self.horizontalLayout_4.addLayout(self.gridLayout_2) self.verticalLayout_3.addWidget(self.groupBox) self.groupBox_2 = QtWidgets.QGroupBox(self.tabWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) + self.groupBox_2.setSizePolicy(sizePolicy) self.groupBox_2.setObjectName("groupBox_2") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2) self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) @@ -215,24 +245,24 @@ class Ui_ascii_reader(object): self.pushButton.setSizePolicy(sizePolicy) self.pushButton.setObjectName("pushButton") self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.pushButton) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.formLayout.setItem(6, QtWidgets.QFormLayout.FieldRole, spacerItem) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.formLayout.setItem(6, QtWidgets.QFormLayout.FieldRole, spacerItem2) self.horizontalLayout_3.addLayout(self.formLayout) self.tabWidget.addTab(self.tabWidgetPage2, "") self.verticalLayout.addWidget(self.tabWidget) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") - spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacerItem1) + spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem3) self.verticalLayout.addLayout(self.horizontalLayout) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem4) self.skippy_checkbox = QtWidgets.QCheckBox(ascii_reader) self.skippy_checkbox.setObjectName("skippy_checkbox") self.horizontalLayout_2.addWidget(self.skippy_checkbox) - spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.horizontalLayout_2.addItem(spacerItem2) self.verticalLayout.addLayout(self.horizontalLayout_2) self.buttonbox = QtWidgets.QDialogButtonBox(ascii_reader) self.buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) @@ -243,18 +273,8 @@ class Ui_ascii_reader(object): self.tabWidget.setCurrentIndex(0) self.buttonbox.rejected.connect(ascii_reader.close) # type: ignore QtCore.QMetaObject.connectSlotsByName(ascii_reader) - ascii_reader.setTabOrder(self.tabWidget, self.column_checkBox) - ascii_reader.setTabOrder(self.column_checkBox, self.line_spinBox) - ascii_reader.setTabOrder(self.line_spinBox, self.preview_spinBox) - ascii_reader.setTabOrder(self.preview_spinBox, self.pts_radioButton) - ascii_reader.setTabOrder(self.pts_radioButton, self.FID_radioButton) - ascii_reader.setTabOrder(self.FID_radioButton, self.spectrum_radioButton) - ascii_reader.setTabOrder(self.spectrum_radioButton, self.x_lineedit) - ascii_reader.setTabOrder(self.x_lineedit, self.y_lineedit) - ascii_reader.setTabOrder(self.y_lineedit, self.deltay_lineEdit) - ascii_reader.setTabOrder(self.deltay_lineEdit, self.ascii_table) - ascii_reader.setTabOrder(self.ascii_table, self.skippy_checkbox) - ascii_reader.setTabOrder(self.skippy_checkbox, self.delay_textfield) + ascii_reader.setTabOrder(self.tabWidget, self.ascii_table) + ascii_reader.setTabOrder(self.ascii_table, self.delay_textfield) ascii_reader.setTabOrder(self.delay_textfield, self.delay_lineedit) ascii_reader.setTabOrder(self.delay_lineedit, self.start_lineedit) ascii_reader.setTabOrder(self.start_lineedit, self.end_lineedit) @@ -267,19 +287,21 @@ class Ui_ascii_reader(object): _translate = QtCore.QCoreApplication.translate ascii_reader.setWindowTitle(_translate("ascii_reader", "Read text file")) self.groupBox.setTitle(_translate("ascii_reader", "Options")) - self.FID_radioButton.setText(_translate("ascii_reader", "FID")) - self.x_lineedit.setToolTip(_translate("ascii_reader", "

Specify which column is used as x-value.

")) - self.y_label.setText(_translate("ascii_reader", "y")) - self.pts_radioButton.setText(_translate("ascii_reader", "Points")) - self.label_5.setText(_translate("ascii_reader", "

Δy

")) self.column_checkBox.setText(_translate("ascii_reader", "Column name")) - self.label.setText(_translate("ascii_reader", "Import as")) - self.label_7.setText(_translate("ascii_reader", "Use columns as")) - self.y_lineedit.setToolTip(_translate("ascii_reader", "

Specify which columns are read for y-values. (\'Points\': Every number creates a new data set;\'FID\'/\'Spectrum\': Numbers at even positions are used for real parts, at odd positions for imaginary parts.)

")) - self.spectrum_radioButton.setText(_translate("ascii_reader", "Spectrum")) - self.x_label.setText(_translate("ascii_reader", "x")) self.line_spinBox.setPrefix(_translate("ascii_reader", "header line ")) self.label_6.setText(_translate("ascii_reader", "Preview length")) + self.label.setText(_translate("ascii_reader", "Import as")) + self.pts_radioButton.setText(_translate("ascii_reader", "Points")) + self.dsc_radioButton.setText(_translate("ascii_reader", "DSC")) + self.FID_radioButton.setText(_translate("ascii_reader", "FID")) + self.spectrum_radioButton.setText(_translate("ascii_reader", "Spectrum")) + self.bds_radioButton.setText(_translate("ascii_reader", "BDS")) + self.label_7.setText(_translate("ascii_reader", "Use columns as")) + self.y_lineedit.setToolTip(_translate("ascii_reader", "

Specify which columns are read for y-values. (\'Points\': Every number creates a new data set;\'FID\'/\'Spectrum\': Numbers at even positions are used for real parts, at odd positions for imaginary parts.)

")) + self.y_label.setText(_translate("ascii_reader", "y")) + self.x_lineedit.setToolTip(_translate("ascii_reader", "

Specify which column is used as x-value.

")) + self.label_5.setText(_translate("ascii_reader", "

Δy

")) + self.x_label.setText(_translate("ascii_reader", "x")) self.groupBox_2.setTitle(_translate("ascii_reader", "Preview")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), _translate("ascii_reader", "Data")) self.label_2.setText(_translate("ascii_reader", "Number of delays")) diff --git a/src/gui_qt/_py/basewindow.py b/src/gui_qt/_py/basewindow.py index 4abf721..df4defd 100644 --- a/src/gui_qt/_py/basewindow.py +++ b/src/gui_qt/_py/basewindow.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'src/resources/_ui/basewindow.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. @@ -64,6 +64,9 @@ class Ui_BaseWindow(object): icon5 = QtGui.QIcon() icon5.addPixmap(QtGui.QPixmap(":/eval_t1_dock"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.tabWidget.addTab(self.t1tauwidget, icon5, "") + self.drawingswidget = DrawingsWidget() + self.drawingswidget.setObjectName("drawingswidget") + self.tabWidget.addTab(self.drawingswidget, "") self.integralwidget = IntegralWidget() self.integralwidget.setObjectName("integralwidget") self.tabWidget.addTab(self.integralwidget, "") @@ -72,7 +75,7 @@ class Ui_BaseWindow(object): self.horizontalLayout.addWidget(self.splitter) BaseWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(BaseWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1386, 22)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1386, 20)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") @@ -114,6 +117,8 @@ class Ui_BaseWindow(object): self.menuStuff = QtWidgets.QMenu(self.menubar) self.menuStuff.setTitle("") self.menuStuff.setObjectName("menuStuff") + self.menuDSC = QtWidgets.QMenu(self.menubar) + self.menuDSC.setObjectName("menuDSC") BaseWindow.setMenuBar(self.menubar) self.toolBar = QtWidgets.QToolBar(BaseWindow) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) @@ -353,6 +358,8 @@ class Ui_BaseWindow(object): self.actionUpdate.setObjectName("actionUpdate") self.actionMine = QtWidgets.QAction(BaseWindow) self.actionMine.setObjectName("actionMine") + self.action_draw_object = QtWidgets.QAction(BaseWindow) + self.action_draw_object.setObjectName("action_draw_object") self.actionBugs = QtWidgets.QAction(BaseWindow) self.actionBugs.setObjectName("actionBugs") self.actionShow_error_log = QtWidgets.QAction(BaseWindow) @@ -361,6 +368,10 @@ class Ui_BaseWindow(object): self.actionCreate_starter.setObjectName("actionCreate_starter") self.actionAbout = QtWidgets.QAction(BaseWindow) self.actionAbout.setObjectName("actionAbout") + self.actionTNMH_model = QtWidgets.QAction(BaseWindow) + self.actionTNMH_model.setObjectName("actionTNMH_model") + self.actionBinning = QtWidgets.QAction(BaseWindow) + self.actionBinning.setObjectName("actionBinning") self.menuSave.addAction(self.actionSave) self.menuSave.addAction(self.actionExportGraphic) self.menuSave.addAction(self.action_save_fit_parameter) @@ -404,6 +415,7 @@ class Ui_BaseWindow(object): self.menuExtra.addSeparator() self.menuExtra.addAction(self.menuNormalize.menuAction()) self.menuExtra.addAction(self.actionInterpolation) + self.menuExtra.addAction(self.actionBinning) self.menuExtra.addAction(self.actionRunning_values) self.menuExtra.addAction(self.actionShift) self.menuExtra.addSeparator() @@ -441,6 +453,7 @@ class Ui_BaseWindow(object): self.menuWindow.addSeparator() self.menuWindow.addAction(self.actionRefresh) self.menuWindow.addSeparator() + self.menuWindow.addAction(self.action_draw_object) self.menuNMR.addAction(self.t1action) self.menuNMR.addAction(self.actionCalculateT1) self.menuNMR.addAction(self.action_coup_calc) @@ -456,6 +469,7 @@ class Ui_BaseWindow(object): self.menuStuff.addAction(self.actionLife) self.menuStuff.addAction(self.actionTetris) self.menuStuff.addAction(self.actionMine) + self.menuDSC.addAction(self.actionTNMH_model) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuWindow.menuAction()) self.menubar.addAction(self.menuData.menuAction()) @@ -464,6 +478,7 @@ class Ui_BaseWindow(object): self.menubar.addAction(self.menuFit.menuAction()) self.menubar.addAction(self.menuNMR.menuAction()) self.menubar.addAction(self.menuBDS.menuAction()) + self.menubar.addAction(self.menuDSC.menuAction()) self.menubar.addAction(self.menuOptions.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.menubar.addAction(self.menuStuff.menuAction()) @@ -487,7 +502,7 @@ class Ui_BaseWindow(object): self.retranslateUi(BaseWindow) self.tabWidget.setCurrentIndex(0) - self.action_close.triggered.connect(BaseWindow.close) + self.action_close.triggered.connect(BaseWindow.close) # type: ignore QtCore.QMetaObject.connectSlotsByName(BaseWindow) def retranslateUi(self, BaseWindow): @@ -499,6 +514,7 @@ class Ui_BaseWindow(object): self.tabWidget.setTabText(self.tabWidget.indexOf(self.editsignalwidget), _translate("BaseWindow", "Signals")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.ptsselectwidget), _translate("BaseWindow", "Pick points")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.t1tauwidget), _translate("BaseWindow", "SLR")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.drawingswidget), _translate("BaseWindow", "Drawings")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.integralwidget), _translate("BaseWindow", "Integrate")) self.menuFile.setTitle(_translate("BaseWindow", "&File")) self.menuSave.setTitle(_translate("BaseWindow", "&Save...")) @@ -515,6 +531,7 @@ class Ui_BaseWindow(object): self.menuNMR.setTitle(_translate("BaseWindow", "NMR")) self.menuBDS.setTitle(_translate("BaseWindow", "BDS")) self.menuSpectrum.setTitle(_translate("BaseWindow", "Spectrum")) + self.menuDSC.setTitle(_translate("BaseWindow", "DSC")) self.toolBar.setWindowTitle(_translate("BaseWindow", "Main")) self.toolbar_edit.setWindowTitle(_translate("BaseWindow", "Math")) self.toolBar_nmr.setWindowTitle(_translate("BaseWindow", "NMR")) @@ -567,6 +584,7 @@ class Ui_BaseWindow(object): self.actionFit_parameter_saving.setText(_translate("BaseWindow", "Fit parameter saving...")) self.actionShow_fit_parameter.setText(_translate("BaseWindow", "Parameter...")) self.actionSkip_points.setText(_translate("BaseWindow", "Skip points...")) + self.actionGuide_lines.setText(_translate("BaseWindow", "Draw lines...")) self.actionMaximize.setText(_translate("BaseWindow", "Maximize")) self.actionTile.setText(_translate("BaseWindow", "Tile windows")) self.actionTileVertical.setText(_translate("BaseWindow", "Tile windows vertically")) @@ -614,15 +632,19 @@ class Ui_BaseWindow(object): self.actionTetris.setText(_translate("BaseWindow", "Not Tetris")) self.actionUpdate.setText(_translate("BaseWindow", "Look for updates")) self.actionMine.setText(_translate("BaseWindow", "Mine")) + self.action_draw_object.setText(_translate("BaseWindow", "Draw objects...")) self.actionBugs.setText(_translate("BaseWindow", "Bugs! Problems! Wishes!")) self.actionShow_error_log.setText(_translate("BaseWindow", "Show error log")) self.actionCreate_starter.setText(_translate("BaseWindow", "Create starter..")) self.actionAbout.setText(_translate("BaseWindow", "About...")) + self.actionTNMH_model.setText(_translate("BaseWindow", "Tg determination,,,")) + self.actionBinning.setText(_translate("BaseWindow", "Binning...")) from ..data.datawidget.datawidget import DataWidget from ..data.integral_widget import IntegralWidget from ..data.point_select import PointSelectWidget from ..data.signaledit.editsignalwidget import EditSignalWidget from ..data.valueeditwidget import ValueEditWidget from ..fit.fitwindow import QFitDialog +from ..graphs.drawings import DrawingsWidget from ..lib.mdiarea import MdiAreaTile from ..nmr.t1widget import QT1Widget diff --git a/src/gui_qt/_py/tnmh_dialog.py b/src/gui_qt/_py/tnmh_dialog.py new file mode 100644 index 0000000..e984863 --- /dev/null +++ b/src/gui_qt/_py/tnmh_dialog.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'src/resources/_ui/tnmh_dialog.ui' +# +# 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. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(1042, 683) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close|QtWidgets.QDialogButtonBox.Save) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 5, 1, 1, 1) + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.listWidget = QtWidgets.QListWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) + self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setObjectName("listWidget") + self.verticalLayout.addWidget(self.listWidget) + self.label = QtWidgets.QLabel(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.label.setObjectName("label") + self.verticalLayout.addWidget(self.label) + self.tg_value_label = QtWidgets.QLabel(Dialog) + self.tg_value_label.setText("") + self.tg_value_label.setObjectName("tg_value_label") + self.verticalLayout.addWidget(self.tg_value_label) + self.label_4 = QtWidgets.QLabel(Dialog) + self.label_4.setObjectName("label_4") + self.verticalLayout.addWidget(self.label_4) + self.label_5 = QtWidgets.QLabel(Dialog) + self.label_5.setText("") + self.label_5.setObjectName("label_5") + self.verticalLayout.addWidget(self.label_5) + self.gridLayout.addLayout(self.verticalLayout, 0, 0, 5, 1) + self.stackedWidget = QtWidgets.QStackedWidget(Dialog) + self.stackedWidget.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.stackedWidget.setFrameShadow(QtWidgets.QFrame.Raised) + self.stackedWidget.setObjectName("stackedWidget") + self.stackedWidgetPage1 = QtWidgets.QWidget() + self.stackedWidgetPage1.setObjectName("stackedWidgetPage1") + self.gridLayout_3 = QtWidgets.QGridLayout(self.stackedWidgetPage1) + self.gridLayout_3.setObjectName("gridLayout_3") + self.hodge_selection = CheckCombobox(self.stackedWidgetPage1) + self.hodge_selection.setObjectName("hodge_selection") + self.hodge_selection.addItem("") + self.hodge_selection.addItem("") + self.hodge_selection.addItem("") + self.hodge_selection.addItem("") + self.gridLayout_3.addWidget(self.hodge_selection, 4, 2, 1, 1) + self.tau_plot = PlotWidget(self.stackedWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tau_plot.sizePolicy().hasHeightForWidth()) + self.tau_plot.setSizePolicy(sizePolicy) + self.tau_plot.setObjectName("tau_plot") + self.gridLayout_3.addWidget(self.tau_plot, 0, 0, 7, 1) + self.tg_export_check = QtWidgets.QCheckBox(self.stackedWidgetPage1) + self.tg_export_check.setChecked(True) + self.tg_export_check.setObjectName("tg_export_check") + self.gridLayout_3.addWidget(self.tg_export_check, 2, 1, 1, 1) + self.tglines_export_check = QtWidgets.QCheckBox(self.stackedWidgetPage1) + self.tglines_export_check.setChecked(True) + self.tglines_export_check.setObjectName("tglines_export_check") + self.gridLayout_3.addWidget(self.tglines_export_check, 2, 2, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_3.addItem(spacerItem, 6, 2, 1, 1) + self.new_graph_tau_combo = QtWidgets.QComboBox(self.stackedWidgetPage1) + self.new_graph_tau_combo.setObjectName("new_graph_tau_combo") + self.gridLayout_3.addWidget(self.new_graph_tau_combo, 5, 2, 1, 1) + self.label_6 = QtWidgets.QLabel(self.stackedWidgetPage1) + self.label_6.setObjectName("label_6") + self.gridLayout_3.addWidget(self.label_6, 4, 1, 1, 1) + self.new_graph_tau_check = QtWidgets.QCheckBox(self.stackedWidgetPage1) + self.new_graph_tau_check.setChecked(True) + self.new_graph_tau_check.setObjectName("new_graph_tau_check") + self.gridLayout_3.addWidget(self.new_graph_tau_check, 5, 1, 1, 1) + self.line = QtWidgets.QFrame(self.stackedWidgetPage1) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout_3.addWidget(self.line, 3, 1, 1, 2) + self.stackedWidget.addWidget(self.stackedWidgetPage1) + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.gridLayout_2 = QtWidgets.QGridLayout(self.page) + self.gridLayout_2.setObjectName("gridLayout_2") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.label_2 = QtWidgets.QLabel(self.page) + self.label_2.setObjectName("label_2") + self.horizontalLayout.addWidget(self.label_2) + self.comboBox = QtWidgets.QComboBox(self.page) + self.comboBox.setObjectName("comboBox") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.horizontalLayout.addWidget(self.comboBox) + self.gridLayout_2.addLayout(self.horizontalLayout, 1, 1, 1, 1) + self.pushButton_3 = QtWidgets.QPushButton(self.page) + self.pushButton_3.setObjectName("pushButton_3") + self.gridLayout_2.addWidget(self.pushButton_3, 2, 1, 1, 1) + self.graphicsView_2 = PlotWidget(self.page) + self.graphicsView_2.setObjectName("graphicsView_2") + self.gridLayout_2.addWidget(self.graphicsView_2, 0, 0, 3, 1) + self.stackedWidget.addWidget(self.page) + self.gridLayout.addWidget(self.stackedWidget, 4, 1, 1, 1) + self.dsc_plot = PlotWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.dsc_plot.sizePolicy().hasHeightForWidth()) + self.dsc_plot.setSizePolicy(sizePolicy) + self.dsc_plot.setObjectName("dsc_plot") + self.gridLayout.addWidget(self.dsc_plot, 0, 1, 1, 1) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.calctg_button = QtWidgets.QPushButton(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.calctg_button.sizePolicy().hasHeightForWidth()) + self.calctg_button.setSizePolicy(sizePolicy) + self.calctg_button.setObjectName("calctg_button") + self.horizontalLayout_2.addWidget(self.calctg_button) + self.pushButton_2 = QtWidgets.QPushButton(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_2.sizePolicy().hasHeightForWidth()) + self.pushButton_2.setSizePolicy(sizePolicy) + self.pushButton_2.setObjectName("pushButton_2") + self.horizontalLayout_2.addWidget(self.pushButton_2) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.gridLayout.addLayout(self.horizontalLayout_2, 2, 1, 1, 1) + + self.retranslateUi(Dialog) + self.stackedWidget.setCurrentIndex(0) + self.buttonBox.accepted.connect(Dialog.accept) # type: ignore + self.buttonBox.rejected.connect(Dialog.reject) # type: ignore + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "SImba, everything the light touches is our kingdom")) + self.label.setText(_translate("Dialog", "

Tg values:

")) + self.label_4.setText(_translate("Dialog", "

TNMH parameter:

")) + self.hodge_selection.setItemText(0, _translate("Dialog", "Onset")) + self.hodge_selection.setItemText(1, _translate("Dialog", "Midpoint")) + self.hodge_selection.setItemText(2, _translate("Dialog", "End")) + self.hodge_selection.setItemText(3, _translate("Dialog", "Inflection")) + self.tg_export_check.setText(_translate("Dialog", "Export Tg")) + self.tglines_export_check.setText(_translate("Dialog", "Export lines")) + self.label_6.setText(_translate("Dialog", "Hodge:")) + self.new_graph_tau_check.setText(_translate("Dialog", "New graph")) + self.label_2.setText(_translate("Dialog", "Tg for TNMH")) + self.comboBox.setItemText(0, _translate("Dialog", "Fictive")) + self.comboBox.setItemText(1, _translate("Dialog", "Onset")) + self.comboBox.setItemText(2, _translate("Dialog", "Midpoint")) + self.comboBox.setItemText(3, _translate("Dialog", "End")) + self.comboBox.setItemText(4, _translate("Dialog", "Inflection")) + self.pushButton_3.setText(_translate("Dialog", "Fit TNMH model")) + self.calctg_button.setText(_translate("Dialog", "Calculate Tg")) + self.pushButton_2.setText(_translate("Dialog", "Calculate fictive Cp")) +from ..lib.forms import CheckCombobox +from pyqtgraph import PlotWidget diff --git a/src/gui_qt/data/container.py b/src/gui_qt/data/container.py index fd268d8..93d2f12 100644 --- a/src/gui_qt/data/container.py +++ b/src/gui_qt/data/container.py @@ -317,7 +317,7 @@ class ExperimentContainer(QtCore.QObject): if mode in ['imag', 'all'] and self.plot_imag is not None: self.plot_imag.set_color(color, symbol=symbol, line=line) - def setSymbol(self, symbol=None, color=None, size=None, mode='real'): + def setSymbol(self, *, symbol=None, color=None, size=None, mode='real'): if mode in ['real', 'all']: self.plot_real.set_symbol(symbol=symbol, size=size, color=color) if color is not None and self.plot_error is not None and self.plot_real.symbol != SymbolStyle.No: @@ -329,7 +329,7 @@ class ExperimentContainer(QtCore.QObject): else: print('Updating symbol failed for ' + str(self.id)) - def setLine(self, width=None, style=None, color=None, mode='real'): + def setLine(self, *, width=None, style=None, color=None, mode='real'): if mode in ['real', 'all']: self.plot_real.set_line(width=width, style=style, color=color) if color is not None and self.plot_error is not None and self.plot_real.symbol == SymbolStyle.No: @@ -480,6 +480,12 @@ class ExperimentContainer(QtCore.QObject): return new_data + def binning(self, digits: float): + new_data = self.copy(full=True) + new_data.data = self._data.binning(value=digits) + + return new_data + class PointContainer(ExperimentContainer): symbols = symbolcycle() diff --git a/src/gui_qt/dsc/glass_dialog.py b/src/gui_qt/dsc/glass_dialog.py new file mode 100644 index 0000000..d30ea57 --- /dev/null +++ b/src/gui_qt/dsc/glass_dialog.py @@ -0,0 +1,299 @@ +import pprint +from itertools import cycle + +from numpy import array, nan, isnan +from pyqtgraph import mkPen, mkBrush, LegendItem + +from nmreval.dsc.hodge import tau_hodge +from nmreval.lib.colors import Tab10 +from ..Qt import QtWidgets, QtCore +from .._py.tnmh_dialog import Ui_Dialog +from ..lib.pg_objects import PlotItem, RegionItem + +from nmreval.data import DSC, Points + + +class TgCalculator(QtWidgets.QDialog, Ui_Dialog): + newTg = QtCore.pyqtSignal(dict, list, str) + + def __init__(self, management, parent=None): + super().__init__(parent=parent) + + self.setupUi(self) + self._management = management + self._colors = cycle(Tab10) + + self._dsc = {} + self._plots = {} + self._tg_value = {} + self._fit = {} + self._lines = {} + self._hodge = { + 'onset': (PlotItem(x=[], y=[], pen=None, symbol='o', symbolBrush=Tab10.TabBlue.rgb(), name='Onset'), None), + 'midpoint': (PlotItem(x=[], y=[], pen=None, symbol='s', symbolBrush=Tab10.TabOrange.rgb(), name='Midpoint'), None), + 'end': (PlotItem(x=[], y=[], pen=None, symbol='t', symbolBrush=Tab10.TabGreen.rgb(), name='End'), None), + 'inflection': (PlotItem(x=[], y=[], pen=None, symbol='d', symbolBrush=Tab10.TabRed.rgb(), name='Inflection'), None), + # 'fictive': PlotItem(x=[], y=[], pen=None, symbol='t1', symbolBrush=Tab10.TabPurple.rgb(), name='Fictive'), + # 'TNMH': PlotItem(x=[], y=[], pen=None, symbol='star', symbolBrush=Tab10.TabPurple.rgb(), name='TNMH'), + } + self.tau_plot.getPlotItem().addLegend() + for plt, _ in self._hodge.values(): + self.tau_plot.addItem(plt) + self.tau_plot.setLogMode(y=True) + self.pushButton_2.hide() + self.label_4.hide() + self.label_5.hide() + + self.limits = RegionItem(), RegionItem() + for lim in self.limits: + self.dsc_plot.addItem(lim) + self._limitless = True + + self.add_sets() + + self.listWidget.itemClicked.connect(self.show_tg_values) + + self.new_graph_tau_combo.setEnabled(False) + self.new_graph_tau_check.stateChanged.connect(lambda state: self.new_graph_tau_combo.setEnabled(not bool(state))) + + def __call__(self): + self.clear() + self._colors = cycle(Tab10) + self.add_sets() + + return self + + def clear(self): + self.listWidget.clear() + for plots in self._plots.values(): + for val in plots: + self.dsc_plot.removeItem(val) + self.graphicsView_2.removeItem(val) + + for key, plt in self._hodge.items(): + plt[0].setData(x=[], y=[]) + self._hodge[key] = (plt[0], None) + + self._dsc = {} + self._plots = {} + self._tg_value = {} + self._lines = {} + self._fit = {} + + def add_sets(self): + self.new_graph_tau_combo.clear() + for graphs in self._management.graphs.list(): + self.new_graph_tau_combo.addItem(graphs[1], userData=graphs[0]) + + min_x = 10_000_000 + max_x = -10_000_000 + for (key, name), c in zip(self._management.active_sets, self._colors): + data = self._management[key].data + if not isinstance(data, DSC): + continue + + min_x = min(min_x, data.x.min()) + max_x = max(max_x, data.x.max()) + + item = QtWidgets.QListWidgetItem(name) + item.setCheckState(QtCore.Qt.Checked) + item.setData(QtCore.Qt.UserRole, key) + item.setForeground(mkBrush(c.rgb())) + self.listWidget.addItem(item) + + self._dsc[key] = (data, None) + + data_plot = PlotItem(x=data.x, y=data.y, pen=mkPen(c.rgb())) + self.dsc_plot.addItem(data_plot) + + glass = PlotItem() + glass.set_line(style=2, color=c) + self.dsc_plot.addItem(glass) + + liquid = PlotItem() + liquid.set_line(style=2, color=c) + self.dsc_plot.addItem(liquid) + + tangent = PlotItem() + tangent.set_line(style=2, color=c) + self.dsc_plot.addItem(tangent) + + tg_plot = PlotItem(pen=None, symbolBrush=c.rgb(), symbol='o') + self.dsc_plot.addItem(tg_plot) + + fictive_cp = PlotItem(pen=mkPen(c.rgb())) + self.graphicsView_2.addItem(fictive_cp) + + tnmh_fit = PlotItem() + tnmh_fit.set_line(style=2, color=c) + self.graphicsView_2.addItem(tnmh_fit) + + self._plots[key] = (data_plot, tg_plot, glass, liquid, tangent, fictive_cp, tnmh_fit) + self._tg_value[key] = {'onset': (nan, nan), 'mid': (nan, nan), 'end': (nan, nan), 'inflection': (nan, nan)} # , 'fictive': (nan, nan)} + + if self._limitless: + dist = max_x - min_x + self.limits[0].setRegion((min_x, min_x+min(0.1*dist, 5))) + self.limits[1].setRegion((max_x-min(5, 0.1*dist), max_x)) + self._limitless = False + + @QtCore.pyqtSlot(name='on_calctg_button_clicked') + def calc_tg(self): + baselines = tuple(lim.getRegion() for lim in self.limits) + if baselines[0][0] > baselines[1][0]: + baselines = baselines[1], baselines[0] + + for idx in range(self.listWidget.count()): + item = self.listWidget.item(idx) + if item.checkState() == QtCore.Qt.Unchecked: + continue + + key = item.data(QtCore.Qt.UserRole) + plot = self._plots[key] + data, _ = self._dsc[key] + + tg_results, glass, liquid, tangent = data.glass_transition(*baselines) + self._lines[key] = (glass, liquid, tangent) + + for i, line in enumerate((glass, liquid, tangent)): + plot[i+2].setData(x=line.x, y=line.y) + + self._tg_value[key].update(tg_results) + + self._update_tg_plots() + + def show_tg_values(self, item): + values = self._tg_value.get(item.data(QtCore.Qt.UserRole)) + + if values is not None: + label = '\n'.join((f'{name.capitalize()}: {pos[0]:.2f} K' for name, pos in values.items())) + self.tg_value_label.setText(label) + + fit = self._fit.get(item.data(QtCore.Qt.UserRole)) + if fit is not None: + self.label_5.setText(fit._parameter_string()) + + def _update_tg_plots(self): + for idx in range(self.listWidget.count()): + item = self.listWidget.item(idx) + + key = item.data(QtCore.Qt.UserRole) + plot = self._plots[key] + data, _ = self._dsc[key] + + plot[1].setData(array(list(self._tg_value[key].values()))) + + self.hodge() + + @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, name='on_listWidget_itemChanged') + def change_visibility(self, item: QtWidgets.QListWidgetItem): + is_checked = bool(item.checkState()) + plot = self._plots[item.data(QtCore.Qt.UserRole)] + for val in plot: + val.setVisible(is_checked) + + @QtCore.pyqtSlot(name='on_pushButton_2_clicked') + def get_fictive(self): + baselines = tuple(lim.getRegion() for lim in self.limits) + if baselines[0][0] > baselines[1][0]: + baselines = baselines[1], baselines[0] + + for idx in range(self.listWidget.count()): + item = self.listWidget.item(idx) + if item.checkState() == QtCore.Qt.Unchecked: + continue + + key = item.data(QtCore.Qt.UserRole) + plot = self._plots[key] + data, _ = self._dsc[key] + + cp, tg = data.get_fictive_cp(*baselines) + + plot[5].setData(cp.x, cp.y) + self._dsc[key] = (data, cp) + + self._tg_value[key]['fictive'] = (tg, 0) + + self._update_tg_plots() + + @QtCore.pyqtSlot(name='on_pushButton_3_clicked') + def make_tnmh(self): + baselines = tuple(lim.getRegion() for lim in self.limits) + if baselines[0][0] > baselines[1][0]: + baselines = baselines[1], baselines[0] + + for idx in range(self.listWidget.count()): + item = self.listWidget.item(idx) + if item.checkState() == QtCore.Qt.Unchecked: + continue + + key = item.data(QtCore.Qt.UserRole) + plot = self._plots[key] + _, data = self._dsc[key] + + if data is None: + continue + + res = data.calculate_tnmh([60, 0.5, 1, 2e5], *baselines, return_fictive=False) + self._fit[key] = res + plot[-1].setData(res.x, res.y) + + def hodge(self): + for tg_type, (plot, data) in self._hodge.items(): + + m = [] + for idx in range(self.listWidget.count()): + item = self.listWidget.item(idx) + + key = item.data(QtCore.Qt.UserRole) + data, _ = self._dsc[key] + + tg_value = self._tg_value[key][tg_type][0] + if isnan(tg_value): + continue + + m.append([tg_value, data.value]) + + if len(m) > 1: + data = tau_hodge(*array(m).T) + data.name = f'{data.name} ({tg_type.capitalize()})' + plot.setData(data.x, data.y) + + self._hodge[tg_type] = (plot, data) + + def close(self) -> bool: + self.clear() + return super().close() + + def accept(self) -> None: + if self.new_graph_tau_check.isChecked(): + graph_id = '' + else: + graph_id = self.new_graph_tau_combo.currentData() + + ret_dic = {} + for key, tg in self._tg_value.items(): + tgx = [x for x, y in tg.values()] + tgy = [y for x, y in tg.values()] + if self.tg_export_check.isChecked(): + tg_pts = Points(x=tgx, y=tgy, name=self._management[key].name + ' (Tg)', value=self._management[key].value) + else: + tg_pts = None + + if self.tglines_export_check.isChecked(): + line = self._lines[key] + else: + line = [] + + ret_dic[key] = (tg_pts, line) + + ret_dic2 = [] + + for i in range(self.hodge_selection.count()): + if self.hodge_selection.isChecked(i): + item = self.hodge_selection.itemText(i).lower() + v = self._hodge.get(item) + ret_dic2.append(v[1]) + + self.newTg.emit(ret_dic, ret_dic2, graph_id) + self.close() diff --git a/src/gui_qt/fit/fitfunction.py b/src/gui_qt/fit/fitfunction.py index f1c85fa..11a6d1b 100644 --- a/src/gui_qt/fit/fitfunction.py +++ b/src/gui_qt/fit/fitfunction.py @@ -12,6 +12,7 @@ from ..lib import get_icon from .._py.fitfunctionwidget import Ui_Form from ..Qt import QtWidgets, QtCore, QtGui + class QFunctionWidget(QtWidgets.QWidget, Ui_Form): func_cnt = count() func_colors = cycle(Tab10) diff --git a/src/gui_qt/io/asciireader.py b/src/gui_qt/io/asciireader.py index 10581a3..66f5d45 100644 --- a/src/gui_qt/io/asciireader.py +++ b/src/gui_qt/io/asciireader.py @@ -171,7 +171,6 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader): # default row for x is the first row, it will be superseded if an integer number is given. try: x = int(self.x_lineedit.text())-1 - print(x) except ValueError: x = None @@ -203,7 +202,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader): col_names=col_header) self.data_read.emit(ret_dic) - except Exception as e: + except ImportError as e: _ = QtWidgets.QMessageBox.information(self, 'Reading failed', f'Import data failed with {e.args}') @@ -215,5 +214,4 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader): @QtCore.pyqtSlot(int, name='on_skippy_checkbox_stateChanged') def skip_next_dial(self, _: int): - print('skippy das buschkänguru', _) self.skip = self.skippy_checkbox.isChecked() diff --git a/src/gui_qt/io/dscreader.py b/src/gui_qt/io/dscreader.py index 8091e29..b672a78 100644 --- a/src/gui_qt/io/dscreader.py +++ b/src/gui_qt/io/dscreader.py @@ -5,7 +5,7 @@ from pathlib import Path import numpy as np from pyqtgraph import PlotDataItem -from nmreval.data.points import Points +from nmreval.data import DSC from nmreval.io.dsc import Cyclohexane, DSCCalibrator, DSCSample from ..Qt import QtWidgets, QtCore @@ -250,10 +250,11 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): return rate, mode = self.current_run - new_val = Points(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} ({mode})') if filesave: new_val.savetxt(self.fname.with_name(f'{self.fname.stem} {rate}K-min {mode}.dat'.replace(' ', '_'))) + close_after = False else: self.data_read.emit([new_val]) diff --git a/src/gui_qt/lib/forms.py b/src/gui_qt/lib/forms.py index 6a51c3a..c0b73aa 100644 --- a/src/gui_qt/lib/forms.py +++ b/src/gui_qt/lib/forms.py @@ -1,3 +1,5 @@ +from typing import Any + from numpy import inf from nmreval.utils.text import convert @@ -404,3 +406,21 @@ class ElideComboBox(QtWidgets.QComboBox): opt.currentText = painter.fontMetrics().elidedText(opt.currentText, QtCore.Qt.ElideRight, rect.width()) painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt) + + +class CheckCombobox(QtWidgets.QComboBox): + + def addItem(self, text: str, userData: Any=None) -> None: + super().addItem(text, userData=userData) + + item = self.model().item(self.count()-1) + item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable) + item.setCheckState(QtCore.Qt.Checked) + + def addItems(self, text): + for text_i in text: + self.addItem(text_i) + + def isChecked(self, idx: int) -> bool: + return bool(self.model().item(idx).checkState()) + diff --git a/src/gui_qt/lib/pg_objects.py b/src/gui_qt/lib/pg_objects.py index 4ab0f30..fba116b 100644 --- a/src/gui_qt/lib/pg_objects.py +++ b/src/gui_qt/lib/pg_objects.py @@ -174,6 +174,7 @@ class PlotItem(PlotDataItem): pen = self.opts['pen'] if isinstance(pen, tuple): self.opts['linecolor'] = pen + self.opts['pen'] = mkPen(color=pen) else: c = pen.color() self.opts['linecolor'] = c.red(), c.green(), c.blue() @@ -279,7 +280,7 @@ class PlotItem(PlotDataItem): else: self.scatter.hide() - def set_symbol(self, symbol=None, size=None, color=None): + def set_symbol(self, *, symbol=None, size=None, color=None): if symbol is not None: if isinstance(symbol, int): self.setSymbol(SymbolStyle(symbol).to_str()) @@ -313,14 +314,13 @@ class PlotItem(PlotDataItem): self.opts['pen'] = pen self.updateItems() - def set_line(self, style=None, width=None, color=None): + def set_line(self, *, style=None, width=None, color=None): pen = self.opts['pen'] if pen is None: pen = mkPen(style=QtCore.Qt.NoPen) if width is not None: pen.setWidthF(width) - if style is not None: if isinstance(style, LineStyle): style = style.value diff --git a/src/gui_qt/main/mainwindow.py b/src/gui_qt/main/mainwindow.py index 4c566c2..4d7327f 100644 --- a/src/gui_qt/main/mainwindow.py +++ b/src/gui_qt/main/mainwindow.py @@ -16,6 +16,7 @@ from .management import UpperManagement from ..Qt import QtGui, QtPrintSupport from ..data.shift_graphs import QShift from ..data.signaledit import QApodDialog, QBaselineDialog, QPhasedialog +from ..dsc.glass_dialog import TgCalculator from ..fit.result import FitExtension, QFitResult from ..graphs.graphwindow import QGraphWindow from ..graphs.movedialog import QMover @@ -24,6 +25,7 @@ from ..io.filedialog import * from ..lib import get_icon, make_action_icons from ..lib.pg_objects import RegionItem from ..lib.starter import make_starter +from ..math.binning import BinningWindow from ..math.evaluation import QEvalDialog from ..math.interpol import InterpolDialog from ..math.mean_dialog import QMeanTimes @@ -55,6 +57,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.fitlimitvalues = [None, None] self.fitpreview = [] self._fit_plot_id = None + self.savefitdialog = None + self._tg_dialog = None self.fitresult_dialog = None self.eval = None self.editor = None @@ -239,7 +243,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.action_norm_area.triggered.connect(lambda: self.management.apply('norm', ('area',))) self.action_cut.triggered.connect(lambda: self.management.cut()) - self.actionConcatenate_sets.triggered.connect(lambda : self.management.cat()) + self.actionConcatenate_sets.triggered.connect(lambda: self.management.cat()) self.management.graphs.valueChanged.connect(self.update_graph_list) @@ -710,6 +714,14 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): self.eval.exec() + @QtCore.pyqtSlot(name='on_actionBinning_triggered') + def open_binning(self): + dialog = BinningWindow(self) + res = dialog.exec() + if res: + digits = float(dialog.spinbox.text()) + self.management.binning(digits) + @QtCore.pyqtSlot(name='on_actionDerivation_triggered') # @QtCore.pyqtSlot(name='on_actionIntegration_triggered') @QtCore.pyqtSlot(name='on_actionFilon_triggered') @@ -1125,3 +1137,12 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): def show_version(self): from nmreval.version import __version__ QtWidgets.QMessageBox.about(self, 'Version', f'Build date of AppImage: {__version__}') + + @QtCore.pyqtSlot(name='on_actionTNMH_model_triggered') + def show_tg_dialog(self): + if self._tg_dialog is None: + self._tg_dialog = TgCalculator(self.management, parent=self) + self._tg_dialog.newTg.connect(self.management.addTg) + else: + self._tg_dialog() + self._tg_dialog.show() diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py index 9655835..d62aca8 100644 --- a/src/gui_qt/main/management.py +++ b/src/gui_qt/main/management.py @@ -764,6 +764,39 @@ class UpperManagement(QtCore.QObject): self.newData.emit(new_key, dest_graph) + def binning(self, digits: float): + _active = self.graphs[self.current_graph].active + new_data = [] + for sid in _active: + key = self.add(self.data[sid].binning(digits=digits)) + new_data.append(key) + + self.newData.emit(new_data, self.current_graph) + + @QtCore.pyqtSlot(dict, list, str) + def addTg(self, dic1: dict, dic2: list, graph_id: str): + for k, (tg, lines) in dic1.items(): + p: ExperimentContainer = self[k] + col = p.plot_real.linecolor + + tg_data_id = [] + + if tg is not None: + tg_data_id.append(self.add(tg, color=col)) + + for line in lines: + set_id = self.add(line, color=col) + self[set_id].setLine(style=LineStyle.Dashed) + self[set_id].setSymbol(symbol=SymbolStyle.No) + tg_data_id.append(set_id) + + self.newData.emit(tg_data_id, self.current_graph) + + set_id_list = [] + for v in dic2: + set_id_list.append(self.add(v)) + self.newData.emit(set_id_list, graph_id) + @QtCore.pyqtSlot(int, dict) def smooth_data(self, npoints, param_kwargs): _active = self.graphs[self.current_graph].active diff --git a/src/gui_qt/math/binning.py b/src/gui_qt/math/binning.py new file mode 100644 index 0000000..cc38f87 --- /dev/null +++ b/src/gui_qt/math/binning.py @@ -0,0 +1,21 @@ +from ..Qt import QtWidgets, QtGui + + +class BinningWindow(QtWidgets.QDialog): + def __init__(self, parent=None): + super().__init__(parent=parent) + + layout = QtWidgets.QFormLayout() + + self.label = QtWidgets.QLabel('Digits (negative values position left of decimal point)') + self.spinbox = QtWidgets.QLineEdit() + self.spinbox.setValidator(QtGui.QDoubleValidator()) + self.spinbox.setText('1') + layout.addRow(self.label, self.spinbox) + + self.dialogbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) + self.dialogbox.accepted.connect(self.accept) + self.dialogbox.rejected.connect(self.reject) + layout.addWidget(self.dialogbox) + + self.setLayout(layout) diff --git a/src/nmreval/data/dsc.py b/src/nmreval/data/dsc.py index 429ddcd..f9c82fa 100644 --- a/src/nmreval/data/dsc.py +++ b/src/nmreval/data/dsc.py @@ -1,6 +1,137 @@ +from __future__ import annotations + +from typing import Optional + +import numpy as np +from scipy.optimize import fsolve +from scipy.signal import savgol_filter + +try: + from scipy.integrate import cumulative_trapezoid +except ImportError: + from scipy.integrate import cumtrapz as cumulative_trapezoid +from scipy.stats import linregress + from .points import Points +from ..dsc.tnmh_model import TNMH + class DSC(Points): def __init__(self, x, y, **kwargs): - super().__init__(x, y, **kwargs) + + y = np.asarray(y).reshape(np.asarray(x).shape) + + x, unique = np.unique(x, return_index=True) + if kwargs.get('y_err', None) is not None: + _yerr = np.asarray(kwargs['y_err']).reshape(np.asarray(x).shape) + kwargs['y_err'] = _yerr[unique] + + self.tg = {'onset': np.nan, 'mid': np.nan, 'end': np.nan, 'inflection': np.nan, 'fictive': np.nan} + + super().__init__(x, y[unique], **kwargs) + + def get_fictive_cp(self, glass: tuple[float, float], liquid: tuple[float, float]) -> ('DSC', float): + min_glass, max_glass = min(glass), max(glass) + min_liquid, max_liquid = min(liquid), max(liquid) + + region = self.copy() + region.cut(min_glass, max_liquid) + + glass_regime = (min_glass < region.x) & (region.x < max_glass) + regress = linregress(region.x[glass_regime], region.y[glass_regime]) + glass_extrapolation = regress.slope * region.x + regress.intercept + + liquid_regime = (min_liquid < region.x) & (region.x < max_liquid) + regress2 = linregress(region.x[liquid_regime], region.y[liquid_regime]) + + region.y -= glass_extrapolation + + plt.plot(region.x, regress2.slope * region.x + regress2.intercept) + real_area = cumulative_trapezoid(region.y, region.x, initial=0) + real_area -= real_area[-1] + + t = regress2.intercept - regress.intercept + m = regress2.slope - regress.slope + c0 = 0.5 * m * region.x.max() ** 2 + t * region.x.max() + + def equiv(_x, _i): + return (0.5 * m * _x ** 2 + t * _x - c0) - real_area[_i] + + def equiv_prime(_x, _i): + return m * _x + t + + fictive_temperature = np.array( + [fsolve(equiv, region.x[i], fprime=equiv_prime, args=(i,))[0] for i in range(len(region.x))]) + + t_g_fictive = fictive_temperature[:20].mean() + region.y = np.gradient(fictive_temperature, region.x) + + return region, t_g_fictive + + def calculate_tnmh(self, p0: list, glass: tuple[float, float], liquid: tuple[float, float], + tg: float = None, num_points: int = 200, return_fictive: bool = True) \ + -> ('FitResult', Optional[float], Optional[DSC]): + + dtf_dt, fictive_tg = self.get_fictive_cp(glass, liquid) + if tg is None: + tg = fictive_tg + + temp_equidist = np.linspace(dtf_dt.x[0], dtf_dt.x[-1], num_points) + dtf_dt_equidist = np.interp(temp_equidist, dtf_dt.x, dtf_dt.y) + + from ..fit.minimizer import FitRoutine + fitter = FitRoutine() + fitter.set_model(TNMH) + data = fitter.add_data(temp_equidist, dtf_dt_equidist) + data.set_parameter(p0 + [tg, self.value], var=[True] * 4 + [False] * 2, default_bounds=True) + + res = fitter.run()[0] + + if return_fictive: + return res, tg, dtf_dt + else: + return res + + def glass_transition(self, glass, liquid): + low_idx = tuple(np.argmin(np.abs(self.x - g)) for g in glass) + high_idx = tuple(np.argmin(np.abs(self.x - l)) for l in liquid) + + x = self.x[low_idx[0]:high_idx[1]] + y = self.y[low_idx[0]:high_idx[1]] + + yy = savgol_filter(y, window_length=min(len(x) // 20, 50), polyorder=1, deriv=1) / np.mean(np.diff(x)) + + high_idx = (high_idx[0] - low_idx[0], high_idx[1] - low_idx[0]) + low_idx = (0, low_idx[1] - low_idx[0]) + + inflection = np.argmax(yy) + + p1 = linregress(x[low_idx[0]:low_idx[1]], y[low_idx[0]:low_idx[1]]) + glass_baseline = p1.slope * x + p1.intercept + + p2 = linregress(x[high_idx[0]:high_idx[1]], y[high_idx[0]:high_idx[1]]) + liquid_baseline = p2.slope * x + p2.intercept + + tangent_line = yy[inflection] * (x - x[inflection]) + y[inflection] + + onset = np.argmin(np.abs(tangent_line - glass_baseline)) + end = np.argmin(np.abs(tangent_line - liquid_baseline)) + + midpoint = np.argmin(np.abs(y - 0.5 * (liquid_baseline[end] - glass_baseline[onset]))) + cut_tangent = np.where((tangent_line > y.min() - 1) & (tangent_line < y.max() + 1)) + + glass = Points(x, glass_baseline, name=f'Glass baseline ({self.name})', value=self.value) + tangent = Points(x[cut_tangent], tangent_line[cut_tangent], name=f'Tangent ({self.name})', value=self.value) + liquid = Points(x, liquid_baseline, name=f'Liquid baseline ({self.name})', value=self.value) + + ret_dic = { + 'onset': (x[onset], glass_baseline[onset]), + 'midpoint': (x[midpoint], y[midpoint]), + 'end': (x[end], liquid_baseline[end]), + 'inflection': (x[inflection], y[inflection]), + } + + self.tg.update(ret_dic) + + return ret_dic, glass, liquid, tangent diff --git a/src/nmreval/data/points.py b/src/nmreval/data/points.py index 671142d..cd3553a 100644 --- a/src/nmreval/data/points.py +++ b/src/nmreval/data/points.py @@ -1,6 +1,7 @@ from __future__ import annotations import copy +from math import log10 from numbers import Number, Real from pathlib import Path from typing import Any, TypeVar @@ -546,6 +547,34 @@ class Points: return self + def binning(self, value: float): + if value <= 0: + raise ValueError('value must be a positive number') + + copy = self.copy() + + upper_lim = (self.x[-1]//value + 1) * value + lower_lim = (self.x[0]//value) * value + + offset = value / 2 + + xbins = np.linspace(lower_lim - offset, upper_lim + offset, num=int((upper_lim-lower_lim)/value + 2)) + + n, _ = np.histogram(copy.x, bins=xbins) + sum_y, _ = np.histogram(copy.x, bins=xbins, weights=copy.y) + sum_yerr_2, _ = np.histogram(copy.x, bins=xbins, weights=copy.y_err**2) + + isnan = n != 0 + + n = n[isnan] + sum_y = sum_y[isnan] + sum_yerr_2 = sum_yerr_2[isnan] + xaxis = (xbins[:-1] + offset)[isnan] + + copy.set_data(xaxis, sum_y/n, y_err=np.sqrt(sum_yerr_2/n)) + + return copy + def shift(self, points: int) -> PointLike: """ Shift indexes of y values. diff --git a/src/nmreval/dsc/hodge.py b/src/nmreval/dsc/hodge.py new file mode 100644 index 0000000..b2dc3ba --- /dev/null +++ b/src/nmreval/dsc/hodge.py @@ -0,0 +1,26 @@ +import numpy as np +from scipy.stats import linregress + +from nmreval.data import Points +from nmreval.fit.minimizer import FitRoutine +from nmreval.lib.utils import ArrayLike +from nmreval.models import Arrhenius +from nmreval.utils import kB + + +def tau_hodge(tg: ArrayLike, rate: ArrayLike) -> Points: + rate = np.asanyarray(rate) / 60 + tg = np.asanyarray(tg) + fitter = FitRoutine() + fitter.set_model(Arrhenius) + d = fitter.add_data(tg, rate) + + init = linregress(1/tg, np.log(rate)) + + d.set_parameter([np.exp(init.intercept), init.slope*kB], fun_kwargs={'invt': None}) + + res = fitter.run()[0] + de = res.parameter['E_{A}'] + tau = kB*tg**2/np.abs(de.value)/rate + + return Points(x=1000/tg, y=tau, y_err=tau*de.error/np.abs(de.value), name='Hodge') diff --git a/src/nmreval/dsc/tnmh_model.py b/src/nmreval/dsc/tnmh_model.py new file mode 100644 index 0000000..4575862 --- /dev/null +++ b/src/nmreval/dsc/tnmh_model.py @@ -0,0 +1,46 @@ +import numpy as np + +from nmreval.utils.constants import R_joule as R + + +class TNMH: + type = 'DSC' + name = 'TNMH model' + equation = r'' + params = [r'\tau_{g}', 'x', r'\beta', r'\Delta H', 'T_{g}', 'rate'] + bounds = [(0, None), (0, 1), (0, 1), (0, None), (0, None), (0, None)] + + @staticmethod + def func(x: np.ndarray, tau_g: float, xx: float, beta: float, energy: float, tg: float, rate: float) -> np.ndarray: + model_temp = np.r_[x[::-1], x[1:]] + + curve = TNMH.tnm_function(model_temp, tau_g, xx, beta, energy, tg, rate)[x.size - 1:] + res = np.gradient(curve, x) + + return res + + @staticmethod + def tnm_function(x: np.ndarray, tau_g: float, xx: float, beta: float, energy: float, tg: float, rate: float) -> np.ndarray: + step = x.size + + Tf = np.empty(step) + Tf[0] = x[0] + + delta_temp = np.diff(x) + delta_time = np.abs(delta_temp) * 60 / rate + + tau = np.empty(step) + dt_by_tau = np.zeros(step) + temp_0 = x[0] + + for i in range(0, step - 1): + tau[i] = TNMH.relax(x[i+1], Tf[i], tau_g, tg, energy, xx) + dt_by_tau[:i] += delta_time[i] / tau[i] + Tf[i + 1] = np.sum(delta_temp[:i] * (1 - np.exp(-dt_by_tau[:i] ** beta))) + temp_0 + + return Tf + + @staticmethod + def relax(t, tf, tau_g, t_glass, ea, x): + h = ea/R + return tau_g * np.exp((x*h / t) + ((1 - x) * h / tf) - h / t_glass) diff --git a/src/nmreval/fit/parameter.py b/src/nmreval/fit/parameter.py index cc18c85..fcaa284 100644 --- a/src/nmreval/fit/parameter.py +++ b/src/nmreval/fit/parameter.py @@ -88,6 +88,7 @@ class Parameter: if self.lb <= value <= self.ub: self.value = value else: + print(value, self.lb, self.ub) raise ValueError('Value of parameter is outside bounds') self.init_val = value diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py index 62ed70f..f8232b1 100644 --- a/src/nmreval/io/asciireader.py +++ b/src/nmreval/io/asciireader.py @@ -7,6 +7,8 @@ import numpy as np from ..data.points import Points from ..data.nmr import FID, Spectrum +from ..data.bds import BDS +from ..data.dsc import DSC from ..utils.utils import staggered_range NUMBERRE = re.compile(r'[0-9]\.*[0-9]*[Ee]*[+-]*[0-9]*') @@ -85,7 +87,12 @@ class AsciiReader: self.delays = staggered_range(self.delays, stepsize=stag_size) def export(self, x: int = None, y: list = None, yerr: list = None, - mode: str = 'Points', col_names=None) -> list: + mode: str = 'points', col_names=None) -> list: + + mode = mode.lower() + if mode not in ['points', 'fid', 'spectrum', 'dsc', 'bds']: + raise ValueError(f'Unknown mode {mode!r} as selected class') + if yerr is None: yerr = [] elif y is None: @@ -138,20 +145,15 @@ class AsciiReader: kwargs = {'value': value, 'filename': self.fname, 'name': filename, 'group': filename, 'y_err': None} num_y = len(y) - if mode == 'Points': - single_len = 1 - cls = Points - stepsize = 1 - elif mode == 'FID': - cls = FID + single_len = 1 + stepsize = 1 + + if mode in ['spectrum', 'fid', 'bds']: + # complex data types single_len = 2 stepsize = 2 - elif mode == 'Spectrum': - cls = Spectrum - single_len = 2 - stepsize = 2 - else: - raise ValueError(f'Unknown mode {mode}, mot ´Points´, ´FID´ or ´Spectrum´.') + + 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: diff --git a/src/nmreval/io/hdfreader.py b/src/nmreval/io/hdfreader.py index b5b85cb..8c681ce 100644 --- a/src/nmreval/io/hdfreader.py +++ b/src/nmreval/io/hdfreader.py @@ -388,10 +388,11 @@ class HdfReader(HdfNode): if multi: var_key = None - try: - value = param_dic[var_key.lower()] - except KeyError: - pass + if var_key is not None: + try: + value = param_dic[var_key.lower()] + except KeyError: + pass return value diff --git a/src/nmreval/models/bds.py b/src/nmreval/models/bds.py index 1594652..137c5ab 100644 --- a/src/nmreval/models/bds.py +++ b/src/nmreval/models/bds.py @@ -88,7 +88,7 @@ class EpsInfty: return ret_val -class _HNWithHF: +class HNWithHF: name = 'HN + HF wing' type = 'Dielectric Spectroscopy' equation = r'\Delta\epsilon HN(\omega, \tau, \alpha, \gamma) / CD(\omega, \tau_{c}, \alpha\gamma-\delta)' @@ -114,7 +114,7 @@ class _HNWithHF: raise ValueError(f'{complex_mode!r} has not value 0, 1, or 2') -class CCWithHF: +class _CCWithHF: name = 'CC + HF wing' type = 'Dielectric Spectroscopy' equation = r'\Delta\epsilon CC(\omega, \tau, \alpha) / CD(\omega, \tau_{c}, \beta-\delta)' @@ -124,10 +124,10 @@ class CCWithHF: @staticmethod def func(x, deps, tau, alpha, tauc, delta, complex_mode: int = 0): - return _HNWithHF.func(x, deps, tau, alpha, 1, tauc, delta, complex_mode=complex_mode) + return HNWithHF.func(x, deps, tau, alpha, 1, tauc, delta, complex_mode=complex_mode) -class CDWithHF: +class _CDWithHF: name = 'CD + HF wing' type = 'Dielectric Spectroscopy' equation = r'\Delta\epsilon CD(\omega, \tau, \gamma) / CD(\omega, \tau_{c}, \gamma-\delta)' @@ -137,7 +137,7 @@ class CDWithHF: @staticmethod def func(x, deps, tau, gamma, tauc, delta, complex_mode: int = 0): - return _HNWithHF.func(x, deps, tau, 1, gamma, tauc, delta, complex_mode=complex_mode) + return HNWithHF.func(x, deps, tau, 1, gamma, tauc, delta, complex_mode=complex_mode) class PowerLawBDS: diff --git a/src/nmreval/utils/constants.py b/src/nmreval/utils/constants.py index 0c15f01..43ab2fd 100755 --- a/src/nmreval/utils/constants.py +++ b/src/nmreval/utils/constants.py @@ -4,7 +4,7 @@ from collections import OrderedDict, namedtuple from scipy.special import psi __all__ = ['NA', 'kb_joule', 'h_joule', 'hbar_joule', - 'e', 'h', 'mu0', 'epsilon0', 'kB', 'R', 'hbar', 'pi', 'Eu', + 'e', 'h', 'mu0', 'epsilon0', 'kB', 'R', 'R_joule', 'hbar', 'pi', 'Eu', 'nuclei', 'gamma', 'gamma_full', 'energy_converter'] # Boltzmann constant in Joule, elementary charge and Avogadro constant are CODATA 2018 definitions diff --git a/src/resources/_ui/asciidialog.ui b/src/resources/_ui/asciidialog.ui index cb9e338..7218f42 100644 --- a/src/resources/_ui/asciidialog.ui +++ b/src/resources/_ui/asciidialog.ui @@ -41,10 +41,16 @@ + + + 0 + 0 + + Options - + 3 @@ -57,194 +63,255 @@ 3 - - 9 - - - - - FID - - - true - - - buttonGroup - - + + + + + + + 0 + 0 + + + + Column name + + + + + + + + 0 + 0 + + + + header line + + + 1 + + + + + + + Preview length + + + + + + + 1 + + + 10 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + - - - - - 0 - 0 - - - - <html><head/><body><p>Specify which column is used as x-value.</p></body></html> - - - Qt::ImhFormattedNumbersOnly|Qt::ImhPreferNumbers - - + + + + + + + 0 + 0 + + + + Import as + + + + + + + Points + + + true + + + true + + + buttonGroup + + + + + + + DSC + + + buttonGroup + + + + + + + FID + + + true + + + buttonGroup + + + + + + + Spectrum + + + buttonGroup + + + + + + + BDS + + + buttonGroup + + + + - - - - y - - - - - - - Points - - - true - - - true - - - buttonGroup - - - - - - - - 0 - 0 - - - - - - - - <html><head/><body><p>Δy</p></body></html> - - - - - - - - 0 - 0 - - - - Column name - - - - - - - - 0 - 0 - - - - Import as - - - - - - - - 0 - 0 - - - - Use columns as - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Specify which columns are read for y-values. ('Points': Every number creates a new data set;'FID'/'Spectrum': Numbers at even positions are used for real parts, at odd positions for imaginary parts.)</p></body></html> - - - Qt::ImhFormattedNumbersOnly|Qt::ImhPreferNumbers - - - - - - - Spectrum - - - buttonGroup - - - - - - - - 0 - 0 - - - - x - - - - - - - - 0 - 0 - - - - header line - - - 1 - - - - - - - 1 - - - 10 - - - - - - - Preview length - - + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Use columns as + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Specify which columns are read for y-values. ('Points': Every number creates a new data set;'FID'/'Spectrum': Numbers at even positions are used for real parts, at odd positions for imaginary parts.)</p></body></html> + + + Qt::ImhFormattedNumbersOnly|Qt::ImhPreferNumbers + + + + + + + y + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Specify which column is used as x-value.</p></body></html> + + + Qt::ImhFormattedNumbersOnly|Qt::ImhPreferNumbers + + + + + + + <html><head/><body><p>Δy</p></body></html> + + + + + + + + 0 + 0 + + + + x + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + 0 + + Preview @@ -445,16 +512,6 @@ 0 - - - - Use selection for next files. Deletes possible delay values. - - - Skip next dialogues? - - - @@ -468,6 +525,16 @@ + + + + Use selection for next files. Deletes possible delay values. + + + Skip next dialogues? + + + @@ -489,17 +556,7 @@ tabWidget - column_checkBox - line_spinBox - preview_spinBox - pts_radioButton - FID_radioButton - spectrum_radioButton - x_lineedit - y_lineedit - deltay_lineEdit ascii_table - skippy_checkbox delay_textfield delay_lineedit start_lineedit diff --git a/src/resources/_ui/basewindow.ui b/src/resources/_ui/basewindow.ui index d1f36f4..3712e76 100644 --- a/src/resources/_ui/basewindow.ui +++ b/src/resources/_ui/basewindow.ui @@ -136,7 +136,7 @@ 0 0 1386 - 22 + 20 @@ -222,6 +222,7 @@ + @@ -326,6 +327,12 @@ + + + DSC + + + @@ -334,6 +341,7 @@ + @@ -1022,6 +1030,16 @@ About... + + + Tg determination,,, + + + + + Binning... + + diff --git a/src/resources/_ui/tnmh_dialog.ui b/src/resources/_ui/tnmh_dialog.ui new file mode 100644 index 0000000..dec99ec --- /dev/null +++ b/src/resources/_ui/tnmh_dialog.ui @@ -0,0 +1,345 @@ + + + Dialog + + + + 0 + 0 + 1042 + 683 + + + + SImba, everything the light touches is our kingdom + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Save + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:600;">Tg values:</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">TNMH parameter:</span></p></body></html> + + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + 0 + + + + + + + + Onset + + + + + Midpoint + + + + + End + + + + + Inflection + + + + + + + + + 0 + 0 + + + + + + + + Export Tg + + + true + + + + + + + Export lines + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Hodge: + + + + + + + New graph + + + true + + + + + + + Qt::Horizontal + + + + + + + + + + + + + Tg for TNMH + + + + + + + + Fictive + + + + + Onset + + + + + Midpoint + + + + + End + + + + + Inflection + + + + + + + + + + Fit TNMH model + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + Calculate Tg + + + + + + + + 0 + 0 + + + + Calculate fictive Cp + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + CheckCombobox + QComboBox +
..lib.forms
+
+
+ + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/pkm.vogel.nmreval.desktop b/src/resources/pkm.vogel.nmreval.desktop similarity index 100% rename from src/pkm.vogel.nmreval.desktop rename to src/resources/pkm.vogel.nmreval.desktop