From d8cc99cea4cf2915184e974c19ac3e076a240245 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Sun, 30 Apr 2023 18:21:16 +0000 Subject: [PATCH] dsc (#55) closes #53 Co-authored-by: Dominik Demuth Reviewed-on: https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/pulls/55 --- src/gui_qt/_py/dscfile_dialog.py | 252 ++++--- src/gui_qt/_py/eval_expr_dialog.py | 2 +- src/gui_qt/_py/fitdialog.py | 7 +- src/gui_qt/_py/fitresult.py | 4 +- src/gui_qt/io/dscreader.py | 28 +- src/gui_qt/main/management.py | 6 +- src/nmreval/dsc/calibration.py | 289 -------- src/nmreval/dsc/dsc_calibration_fast_neu.py | 292 -------- src/nmreval/io/dsc.py | 50 +- src/resources/_ui/dscfile_dialog.ui | 762 +++++++++++--------- 10 files changed, 626 insertions(+), 1066 deletions(-) delete mode 100644 src/nmreval/dsc/calibration.py delete mode 100644 src/nmreval/dsc/dsc_calibration_fast_neu.py diff --git a/src/gui_qt/_py/dscfile_dialog.py b/src/gui_qt/_py/dscfile_dialog.py index 12cb6d5..3169e44 100644 --- a/src/gui_qt/_py/dscfile_dialog.py +++ b/src/gui_qt/_py/dscfile_dialog.py @@ -1,29 +1,113 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'resources/_ui/dscfile_dialog.ui' +# Form implementation generated from reading ui file 'src/resources/_ui/dscfile_dialog.ui' # -# Created by: PyQt5 UI code generator 5.9.2 +# Created by: PyQt5 UI code generator 5.15.2 # -# 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") - Dialog.resize(962, 662) - self.gridLayout_2 = QtWidgets.QGridLayout(Dialog) - self.gridLayout_2.setObjectName("gridLayout_2") - self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Save) - self.buttonBox.setObjectName("buttonBox") - self.gridLayout_2.addWidget(self.buttonBox, 1, 1, 1, 1) - self.gridLayout_4 = QtWidgets.QGridLayout() - self.gridLayout_4.setContentsMargins(-1, 0, 0, -1) - self.gridLayout_4.setSpacing(3) - self.gridLayout_4.setObjectName("gridLayout_4") - self.cp_checkBox = QtWidgets.QCheckBox(Dialog) + Dialog.resize(1341, 799) + self.verticalLayout_5 = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.splitter = QtWidgets.QSplitter(Dialog) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName("splitter") + self.verticalLayoutWidget = QtWidgets.QWidget(self.splitter) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.verticalLayout_4.setContentsMargins(6, 6, 6, 6) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.groupBox = QtWidgets.QGroupBox(self.verticalLayoutWidget) + self.groupBox.setFlat(False) + self.groupBox.setObjectName("groupBox") + self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox) + self.verticalLayout.setContentsMargins(6, 6, 6, 6) + self.verticalLayout.setObjectName("verticalLayout") + self.step_listWidget = QtWidgets.QListWidget(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.step_listWidget.sizePolicy().hasHeightForWidth()) + self.step_listWidget.setSizePolicy(sizePolicy) + self.step_listWidget.setMinimumSize(QtCore.QSize(0, 0)) + self.step_listWidget.setObjectName("step_listWidget") + self.verticalLayout.addWidget(self.step_listWidget) + self.verticalLayout_4.addWidget(self.groupBox) + self.groupBox_2 = QtWidgets.QGroupBox(self.verticalLayoutWidget) + self.groupBox_2.setObjectName("groupBox_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2) + self.verticalLayout_2.setContentsMargins(6, 6, 6, 6) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.groupBox_4 = QtWidgets.QGroupBox(self.groupBox_2) + self.groupBox_4.setObjectName("groupBox_4") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_4) + self.verticalLayout_6.setContentsMargins(6, 6, 6, 6) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.empty_label = QtWidgets.QLabel(self.groupBox_4) + self.empty_label.setObjectName("empty_label") + self.verticalLayout_6.addWidget(self.empty_label) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setSpacing(3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.loadempty_button = QtWidgets.QPushButton(self.groupBox_4) + self.loadempty_button.setObjectName("loadempty_button") + self.horizontalLayout_2.addWidget(self.loadempty_button) + self.delempty_button = QtWidgets.QPushButton(self.groupBox_4) + self.delempty_button.setObjectName("delempty_button") + self.horizontalLayout_2.addWidget(self.delempty_button) + self.verticalLayout_6.addLayout(self.horizontalLayout_2) + self.verticalLayout_2.addWidget(self.groupBox_4) + self.groupBox_5 = QtWidgets.QGroupBox(self.groupBox_2) + self.groupBox_5.setObjectName("groupBox_5") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox_5) + self.verticalLayout_7.setContentsMargins(6, 6, 6, 6) + self.verticalLayout_7.setSpacing(3) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.none_radioButton = QtWidgets.QRadioButton(self.groupBox_5) + self.none_radioButton.setObjectName("none_radioButton") + self.buttonGroup = QtWidgets.QButtonGroup(Dialog) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.none_radioButton) + self.verticalLayout_7.addWidget(self.none_radioButton) + self.isotherm_radioButton = QtWidgets.QRadioButton(self.groupBox_5) + self.isotherm_radioButton.setChecked(True) + self.isotherm_radioButton.setObjectName("isotherm_radioButton") + self.buttonGroup.addButton(self.isotherm_radioButton) + self.verticalLayout_7.addWidget(self.isotherm_radioButton) + self.slope_radioButton = QtWidgets.QRadioButton(self.groupBox_5) + self.slope_radioButton.setObjectName("slope_radioButton") + self.buttonGroup.addButton(self.slope_radioButton) + self.verticalLayout_7.addWidget(self.slope_radioButton) + self.widget = QtWidgets.QWidget(self.groupBox_5) + self.widget.setMinimumSize(QtCore.QSize(0, 33)) + self.widget.setObjectName("widget") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout_3.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.limit1_lineedit = QtWidgets.QLineEdit(self.widget) + self.limit1_lineedit.setObjectName("limit1_lineedit") + self.horizontalLayout_3.addWidget(self.limit1_lineedit) + self.limit2_lineedit = QtWidgets.QLineEdit(self.widget) + self.limit2_lineedit.setText("") + self.limit2_lineedit.setObjectName("limit2_lineedit") + self.horizontalLayout_3.addWidget(self.limit2_lineedit) + self.verticalLayout_7.addWidget(self.widget) + self.verticalLayout_2.addWidget(self.groupBox_5) + self.verticalLayout_4.addWidget(self.groupBox_2) + self.groupBox_3 = QtWidgets.QGroupBox(self.verticalLayoutWidget) + self.groupBox_3.setObjectName("groupBox_3") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_3) + self.verticalLayout_3.setContentsMargins(6, 6, 6, 6) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.cp_checkBox = QtWidgets.QCheckBox(self.groupBox_3) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -31,36 +115,8 @@ class Ui_Dialog(object): self.cp_checkBox.setSizePolicy(sizePolicy) self.cp_checkBox.setChecked(True) self.cp_checkBox.setObjectName("cp_checkBox") - self.gridLayout_4.addWidget(self.cp_checkBox, 11, 0, 1, 4) - self.horizontalLayout_2 = QtWidgets.QHBoxLayout() - self.horizontalLayout_2.setSpacing(3) - self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.loadempty_button = QtWidgets.QPushButton(Dialog) - self.loadempty_button.setObjectName("loadempty_button") - self.horizontalLayout_2.addWidget(self.loadempty_button) - self.delempty_button = QtWidgets.QPushButton(Dialog) - self.delempty_button.setObjectName("delempty_button") - self.horizontalLayout_2.addWidget(self.delempty_button) - self.gridLayout_4.addLayout(self.horizontalLayout_2, 5, 0, 1, 4) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.gridLayout_4.addItem(spacerItem, 12, 0, 1, 1) - self.isotherm_radioButton = QtWidgets.QRadioButton(Dialog) - self.isotherm_radioButton.setChecked(True) - self.isotherm_radioButton.setObjectName("isotherm_radioButton") - self.buttonGroup = QtWidgets.QButtonGroup(Dialog) - self.buttonGroup.setObjectName("buttonGroup") - self.buttonGroup.addButton(self.isotherm_radioButton) - self.gridLayout_4.addWidget(self.isotherm_radioButton, 6, 1, 1, 1) - self.label_4 = QtWidgets.QLabel(Dialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) - self.label_4.setSizePolicy(sizePolicy) - self.label_4.setStyleSheet("font-weight: bold") - self.label_4.setObjectName("label_4") - self.gridLayout_4.addWidget(self.label_4, 0, 0, 1, 4) - self.reference_tableWidget = QtWidgets.QTableWidget(Dialog) + self.verticalLayout_3.addWidget(self.cp_checkBox) + self.reference_tableWidget = QtWidgets.QTableWidget(self.groupBox_3) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -75,57 +131,11 @@ class Ui_Dialog(object): self.reference_tableWidget.horizontalHeader().setVisible(False) self.reference_tableWidget.horizontalHeader().setStretchLastSection(True) self.reference_tableWidget.verticalHeader().setVisible(False) - self.gridLayout_4.addWidget(self.reference_tableWidget, 9, 0, 1, 4) - self.step_listWidget = QtWidgets.QListWidget(Dialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.step_listWidget.sizePolicy().hasHeightForWidth()) - self.step_listWidget.setSizePolicy(sizePolicy) - self.step_listWidget.setMinimumSize(QtCore.QSize(0, 0)) - self.step_listWidget.setObjectName("step_listWidget") - self.gridLayout_4.addWidget(self.step_listWidget, 1, 0, 1, 4) - self.label = QtWidgets.QLabel(Dialog) - self.label.setObjectName("label") - self.gridLayout_4.addWidget(self.label, 6, 0, 1, 1) - self.slope_radioButton = QtWidgets.QRadioButton(Dialog) - self.slope_radioButton.setObjectName("slope_radioButton") - self.buttonGroup.addButton(self.slope_radioButton) - self.gridLayout_4.addWidget(self.slope_radioButton, 6, 2, 1, 1) - self.empty_label = QtWidgets.QLabel(Dialog) - self.empty_label.setObjectName("empty_label") - self.gridLayout_4.addWidget(self.empty_label, 4, 0, 1, 4) - self.label_3 = QtWidgets.QLabel(Dialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) - self.label_3.setSizePolicy(sizePolicy) - self.label_3.setStyleSheet("font-weight: bold") - self.label_3.setObjectName("label_3") - self.gridLayout_4.addWidget(self.label_3, 8, 0, 1, 4) - self.label_2 = QtWidgets.QLabel(Dialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) - self.label_2.setSizePolicy(sizePolicy) - self.label_2.setStyleSheet("font-weight: bold") - self.label_2.setObjectName("label_2") - self.gridLayout_4.addWidget(self.label_2, 3, 0, 1, 4) - self.line = QtWidgets.QFrame(Dialog) - self.line.setFrameShape(QtWidgets.QFrame.HLine) - self.line.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line.setObjectName("line") - self.gridLayout_4.addWidget(self.line, 7, 0, 1, 4) - self.none_radioButton = QtWidgets.QRadioButton(Dialog) - self.none_radioButton.setObjectName("none_radioButton") - self.buttonGroup.addButton(self.none_radioButton) - self.gridLayout_4.addWidget(self.none_radioButton, 6, 3, 1, 1) + self.verticalLayout_3.addWidget(self.reference_tableWidget) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSpacing(3) self.horizontalLayout.setObjectName("horizontalLayout") - self.ref_add_pushButton = QtWidgets.QPushButton(Dialog) + self.ref_add_pushButton = QtWidgets.QPushButton(self.groupBox_3) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -133,7 +143,7 @@ class Ui_Dialog(object): self.ref_add_pushButton.setSizePolicy(sizePolicy) self.ref_add_pushButton.setObjectName("ref_add_pushButton") self.horizontalLayout.addWidget(self.ref_add_pushButton) - self.ref_remove_pushButton = QtWidgets.QPushButton(Dialog) + self.ref_remove_pushButton = QtWidgets.QPushButton(self.groupBox_3) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -141,16 +151,22 @@ class Ui_Dialog(object): self.ref_remove_pushButton.setSizePolicy(sizePolicy) self.ref_remove_pushButton.setObjectName("ref_remove_pushButton") self.horizontalLayout.addWidget(self.ref_remove_pushButton) - self.gridLayout_4.addLayout(self.horizontalLayout, 10, 0, 1, 4) - self.line_2 = QtWidgets.QFrame(Dialog) - self.line_2.setFrameShape(QtWidgets.QFrame.HLine) - self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_2.setObjectName("line_2") - self.gridLayout_4.addWidget(self.line_2, 2, 0, 1, 4) - self.gridLayout_2.addLayout(self.gridLayout_4, 0, 0, 1, 1) - self.gridLayout = QtWidgets.QGridLayout() + self.verticalLayout_3.addLayout(self.horizontalLayout) + self.verticalLayout_4.addWidget(self.groupBox_3) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_4.addItem(spacerItem) + self.buttonBox = QtWidgets.QDialogButtonBox(self.verticalLayoutWidget) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Save) + self.buttonBox.setCenterButtons(True) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout_4.addWidget(self.buttonBox) + self.widget1 = QtWidgets.QWidget(self.splitter) + self.widget1.setObjectName("widget1") + self.gridLayout = QtWidgets.QGridLayout(self.widget1) + self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") - self.raw_graph = PlotWidget(Dialog) + self.raw_graph = PlotWidget(self.widget1) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -159,7 +175,7 @@ class Ui_Dialog(object): self.raw_graph.setMinimumSize(QtCore.QSize(300, 200)) self.raw_graph.setObjectName("raw_graph") self.gridLayout.addWidget(self.raw_graph, 0, 0, 1, 1) - self.calib_graph = PlotWidget(Dialog) + self.calib_graph = PlotWidget(self.widget1) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -168,7 +184,7 @@ class Ui_Dialog(object): self.calib_graph.setMinimumSize(QtCore.QSize(300, 200)) self.calib_graph.setObjectName("calib_graph") self.gridLayout.addWidget(self.calib_graph, 1, 0, 1, 1) - self.baseline_graph = PlotWidget(Dialog) + self.baseline_graph = PlotWidget(self.widget1) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -177,7 +193,7 @@ class Ui_Dialog(object): self.baseline_graph.setMinimumSize(QtCore.QSize(300, 200)) self.baseline_graph.setObjectName("baseline_graph") self.gridLayout.addWidget(self.baseline_graph, 0, 1, 1, 1) - self.end_graph = PlotWidget(Dialog) + self.end_graph = PlotWidget(self.widget1) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -186,7 +202,7 @@ class Ui_Dialog(object): self.end_graph.setMinimumSize(QtCore.QSize(0, 0)) self.end_graph.setObjectName("end_graph") self.gridLayout.addWidget(self.end_graph, 1, 1, 1, 1) - self.gridLayout_2.addLayout(self.gridLayout, 0, 1, 1, 1) + self.verticalLayout_5.addWidget(self.splitter) self.retranslateUi(Dialog) self.buttonBox.accepted.connect(Dialog.accept) @@ -196,18 +212,20 @@ class Ui_Dialog(object): def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Read DSC file")) - self.cp_checkBox.setText(_translate("Dialog", "Convert to heat capacity")) + self.groupBox.setTitle(_translate("Dialog", "Detected steps")) + self.groupBox_2.setTitle(_translate("Dialog", "Baseline corrections")) + self.groupBox_4.setTitle(_translate("Dialog", "Empty measurement")) + self.empty_label.setText(_translate("Dialog", "No emtpy measurement")) self.loadempty_button.setText(_translate("Dialog", "Load empty")) self.delempty_button.setText(_translate("Dialog", "Remove empty")) - self.isotherm_radioButton.setText(_translate("Dialog", "Isotherms")) - self.label_4.setText(_translate("Dialog", "Detected steps")) - self.label.setText(_translate("Dialog", "Slope")) - self.slope_radioButton.setText(_translate("Dialog", "Initial slope")) - self.empty_label.setText(_translate("Dialog", "Empty measurement")) - self.label_3.setText(_translate("Dialog", "Calibration")) - self.label_2.setText(_translate("Dialog", "Baseline")) + self.groupBox_5.setTitle(_translate("Dialog", "Slope correction")) self.none_radioButton.setText(_translate("Dialog", "None")) + self.isotherm_radioButton.setText(_translate("Dialog", "Isotherms")) + self.slope_radioButton.setText(_translate("Dialog", "Initial slope")) + self.limit1_lineedit.setPlaceholderText(_translate("Dialog", "start (in min)")) + self.limit2_lineedit.setPlaceholderText(_translate("Dialog", "stop (in min)")) + self.groupBox_3.setTitle(_translate("Dialog", "References")) + self.cp_checkBox.setText(_translate("Dialog", "Use reference to convert to heat capacity")) self.ref_add_pushButton.setText(_translate("Dialog", "Add reference")) self.ref_remove_pushButton.setText(_translate("Dialog", "Remove reference")) - from pyqtgraph import PlotWidget diff --git a/src/gui_qt/_py/eval_expr_dialog.py b/src/gui_qt/_py/eval_expr_dialog.py index 5bda16f..844b343 100644 --- a/src/gui_qt/_py/eval_expr_dialog.py +++ b/src/gui_qt/_py/eval_expr_dialog.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'src/resources/_ui/eval_expr_dialog.ui' # -# Created by: PyQt5 UI code generator 5.15.9 +# Created by: PyQt5 UI code generator 5.15.2 # # 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/_py/fitdialog.py b/src/gui_qt/_py/fitdialog.py index 8ed9207..cf7d7f6 100644 --- a/src/gui_qt/_py/fitdialog.py +++ b/src/gui_qt/_py/fitdialog.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'resources/_ui/fitdialog.ui' +# Form implementation generated from reading ui file 'src/resources/_ui/fitdialog.ui' # -# Created by: PyQt5 UI code generator 5.12.3 +# Created by: PyQt5 UI code generator 5.15.2 # -# 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 diff --git a/src/gui_qt/_py/fitresult.py b/src/gui_qt/_py/fitresult.py index 987e4c6..ae2d077 100644 --- a/src/gui_qt/_py/fitresult.py +++ b/src/gui_qt/_py/fitresult.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file '/autohome/dominik/nmreval-gitea/src/resources/_ui/fitresult.ui' +# Form implementation generated from reading ui file 'src/resources/_ui/fitresult.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.2 # # 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/io/dscreader.py b/src/gui_qt/io/dscreader.py index c9fb6a3..8091e29 100644 --- a/src/gui_qt/io/dscreader.py +++ b/src/gui_qt/io/dscreader.py @@ -97,7 +97,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): if empty: self.empty = self.calibrator.set_measurement(empty, mode='empty') - self.empty_label.setText(str(self.empty.fname.name)) + self.empty_label.setText('~/' + str(self.empty.fname.relative_to(Path.home()))) self.update_plots() @@ -158,20 +158,28 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): self.sample_idx = None self.clear_plots() - def get_data(self, ): + def get_data(self): if self.sample_idx is None: return rate = self.current_run[0] - slope_type = {self.none_radioButton: None, - self.isotherm_radioButton: 'iso', - self.slope_radioButton: 'curve'}[self.buttonGroup.checkedButton()] + slope_type = { + self.none_radioButton: None, + self.isotherm_radioButton: 'iso', + self.slope_radioButton: 'curve', + }[self.buttonGroup.checkedButton()] + + limit = None + if slope_type == 'curve': + try: + limit = float(self.limit1_lineedit.text())*60, float(self.limit2_lineedit.text())*60 + except ValueError: + limit = None try: - raw_sample, drift_value, sample_data, empty_data, slope = self.calibrator.get_data(self.sample_idx, - slope=slope_type) + raw_sample, drift_value, sample_data, empty_data, slope = self.calibrator.get_data(self.sample_idx, slope=slope_type, limits=limit) except ValueError as e: - _msg = QtWidgets.QMessageBox.warning(self, 'No rate found', e.args[0]) + _msg = QtWidgets.QMessageBox.warning(self, f'Data collection with error', e.args[0]) return self.calibrator.ref_list = [] @@ -194,6 +202,8 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): @QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonGroup_buttonClicked') @QtCore.pyqtSlot(int, name='on_cp_checkBox_stateChanged') + @QtCore.pyqtSlot(str, name='on_limit1_lineedit_textChanged') + @QtCore.pyqtSlot(str, name='on_limit2_lineedit_textChanged') def update_plots(self, _=None): res = self.get_data() if res is None: @@ -254,7 +264,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog): def button_clicked(self, bttn: QtWidgets.QAbstractButton): bttn_value = self.buttonBox.standardButton(bttn) if bttn_value in (self.buttonBox.Ok, self.buttonBox.Apply, self.buttonBox.Save): - self.export_data(filesave=bttn_value==self.buttonBox.Save, close_after=bttn_value==self.buttonBox.Ok) + self.export_data(filesave=bttn_value == self.buttonBox.Save, close_after=bttn_value == self.buttonBox.Ok) else: super().close() diff --git a/src/gui_qt/main/management.py b/src/gui_qt/main/management.py index 1e118d2..aa2449f 100644 --- a/src/gui_qt/main/management.py +++ b/src/gui_qt/main/management.py @@ -911,7 +911,7 @@ class UpperManagement(QtCore.QObject): self.data[new_id[0]].data = new_data except Exception as e: failures.append((data_i, e)) - print(str(data_i) + ' failed with Exception: ' + ''.join(e.args)) + logger.warning(str(data_i) + ' failed with Exception: ' + ''.join(e.args)) continue if overwrite: @@ -946,9 +946,9 @@ class UpperManagement(QtCore.QObject): self.newData.emit([s_id], graph) except Exception as err: - print('Creation failed with error: ' + ', '.join(err.args)) + logger.exception('Creation failed with error: ' + ', '.join(err.args)) err_msg = QtWidgets.QMessageBox(parent=self.sender()) - err_msg.setText('One or more errors occured during evaluation.') + err_msg.setText('One or more errors occurred during evaluation.') err_msg.setDetailedText('Creation failed with error: ' + ', '.join(err.args)) err_msg.exec() diff --git a/src/nmreval/dsc/calibration.py b/src/nmreval/dsc/calibration.py deleted file mode 100644 index bfae765..0000000 --- a/src/nmreval/dsc/calibration.py +++ /dev/null @@ -1,289 +0,0 @@ -import os -import argparse -import sys - -import numpy as np -import matplotlib.pyplot as plt -import scipy.interpolate -from scipy.integrate import simps - -from ..io.dsc import DSCSample, Cyclohexane - - -parser = argparse.ArgumentParser(description='Calibrate DSC data') -parser.add_argument('sample', type=str, help='filename of DSC sample') -parser.add_argument('empty', type=str, help='filename of empty pan') -parser.add_argument('reference', help='filename of reference', type=str) -parser.add_argument('--cooling', help='Show figure of found cooling rates', action='store_true') - - -def evaluate(sample, empty, reference, ref_points=Cyclohexane, show_cooling=False): - sample = DSCSample(sample) - empty = DSCSample(empty) - reference = DSCSample(reference) - - if show_cooling: - fig, ax = plt.subplots() - print('\n') - for k, v in sample.cooling.items(): - print('Plot run {} with cooling rate {} K/min'.format(k, v)) - c = sample.flow_data(v, mode='c') - ax.plot(c[0], c[1], label=str(v)+' K/min') - ax.set_xlabel('T / K') - plt.legend() - plt.show() - - return - - run_list = [] - if len(sample.heating) > 1: - run = None - print('\nMultiple heat rates found:') - for k, v in sample.heating.items(): - print(' run {}: {} K/min'.format(k, v)) - while run not in sample.heating: - # choose your own adventure!!! - value = input('\nPlease select a run (press Enter for all heat rates): ') - if value == '': - run_list = list(sample.heating.keys()) - break - else: - run = int(value) - run_list = [run] - else: - run_list = list(sample.heating.keys()) - - for run in run_list: - rate = sample.heating[run] - - print('\nProcessing heat rate {} K/min'.format(rate)) - - print('Load data of heating data') - len_sample = sample.length(run) - - # sanity checks - try: - reference_data = reference.flow_data(rate) - except IndexError: - print('ERROR: Reference measurement has no heat rate {} K/min'.format(rate)) - print('Stop evaluation') - sys.exit() - - try: - run_baseline = empty.get_run(rate) - except ValueError: - print('ERROR: Empty measurement has no heat rate {} K/min'.format(rate)) - print('Stop evaluation') - sys.exit() - - len_baseline = empty.length(run_baseline) - if len_baseline != len_sample: - print('WARNING: measurements differ by {} points'.format(abs(len_baseline - len_sample))) - # max_length = min(len_baseline, len_sample) - - sample_data = sample.flow_data(rate, length=None) - empty_data = empty.flow_data(rate, length=None) - - # plot input data - fig1, ax1 = plt.subplots(2, 3, **{'figsize': (10, 6)}) - ax1[0, 0].set_title('raw data') - ax1[0, 0].set_xlabel('T / K') - - ax1[0, 0].plot(sample_data[0], sample_data[1], 'k-', label='Sample') - ax1[0, 0].plot(empty_data[0], empty_data[1], 'b-', label='Empty') - ax1[0, 0].plot(reference_data[0], reference_data[1], 'r-', label='Reference') - ax1[0, 0].legend() - - print('Substract empty data\n') - sample_baseline = sample_data.copy() - empty_y = empty_data[1] - if len_sample != len_baseline: - with np.errstate(all='ignore'): - empty_y = scipy.interpolate.interp1d(empty_data[0], empty_data[1], - fill_value='extrapolate')(sample_data[0]) - sample_baseline[1] = sample_data[1] - empty_y - - # plot baseline correction - ax1[0, 1].set_title('baseline correction') - ax1[0, 1].set_xlabel('T / K') - - ax1[0, 1].plot(sample_data[0], sample_data[1], 'k--', label='Raw') - ax1[0, 1].plot(sample_baseline[0], sample_baseline[1], 'k-', label='Baseline corrected') - ax1[0, 1].plot(empty_data[0], empty_data[1], 'b-', label='Empty') - ax1[0, 1].legend() - - print('Load isothermal data around heat rate') - mean_isotherms = [] - for offset, where, ls in [(-1, 'low', '-'), (1, 'high', '--')]: - # read isotherms and baseline correct - len_baseline = empty.length(run_baseline+offset) - len_sample = sample.length(run+offset) - if len_baseline != len_sample: - print('WARNING: {} T isotherms differ by {} points'.format(where, abs(len_baseline-len_sample))) - - max_length = min(len_baseline, len_sample) - isotherm_sample = sample.isotherm_data(run_baseline+offset, length=max_length) - isotherm_empty = empty.isotherm_data(run+offset, length=max_length) - - isotherm_sample[1] -= isotherm_empty[1] - - # get mean isotherm value - m = np.polyfit(isotherm_sample[0, 200:-200], isotherm_sample[1, 200:-200], 0)[0] - mean_isotherms.append(m) - print('Calculated {} heat flow: {:.4} mW'.format(where, m)) - - ax1[0, 2].plot(isotherm_sample[0], isotherm_sample[1], 'k--') - - # calculate slope from difference between isotherms - slope = (mean_isotherms[1]-mean_isotherms[0]) / (sample_data[2, -1] - empty_data[2, 0]) - print('Heat flow slope from isotherms: {:.4} per minute'.format(slope*60)) - - # calculate mean slope of heat flow at points in the beginning - slope_baseline = np.gradient(sample_baseline[1, int(4000/rate):int(9000/rate)], - sample_baseline[2, 300]-sample_baseline[2, 299]).mean() - print('Heat flow slope from initial heating: {:.4f} per minute\n'.format(slope_baseline*60)) - - drift_corrected = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope - drift_from_slope = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope_baseline - - # plot - ax1[0, 2].axhline(mean_isotherms[0], linestyle=':') - ax1[0, 2].axhline(mean_isotherms[1], linestyle=':') - - ax1[0, 2].plot(sample_baseline[2], sample_baseline[1], 'k-', label='Baseline corrected') - ax1[0, 2].plot(sample_baseline[2], drift_corrected, 'g-', label='Corrected (isotherm)') - ax1[0, 2].plot(sample_baseline[2], drift_from_slope, 'b-', label='Corrected (heating)') - - ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + (sample_baseline[2]-empty_data[2, 0])*slope, 'g--') - ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + slope_baseline*(sample_baseline[2]-empty_data[2, 0]), - 'b--') - - ax1[0, 2].plot(sample_baseline[2, int(4000/rate):int(9000/rate)], - sample_baseline[1, int(4000/rate):int(9000/rate)], 'r--') - - ax1[0, 2].set_title('time dependence') - ax1[0, 2].set_xlabel('t / s') - ax1[0, 2].legend() - - melts = [] - for i, (ref_temp, enthalpy) in enumerate(ref_points.transitions): - # region around reference peaks - t_low_lim = ref_temp - 15 - t_high_lim = ref_temp + 15 - low_border = np.argmin(np.abs(reference_data[0]-t_low_lim)) - high_border = np.argmin(np.abs(reference_data[0]-t_high_lim)) - ref_zoom = reference_data[:, 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]]) - print('Baseline correct reference of %.2f transition' % ref_temp) - sol = np.linalg.solve(x_val, y_val) - ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1]) - peak_max = ref_zoom[:, np.argmax(ref_zoom[1])] - integration_limit = np.argmin(abs(ref_zoom[0]-peak_max[0]+3)), np.argmin(abs(ref_zoom[0]-peak_max[0]-3)) - - # substract baseline around reference peaks - x_val = np.array([[ref_zoom[0, integration_limit[0]], 1], - [ref_zoom[0, integration_limit[1]], 1]]) - y_val = np.array([ref_zoom[1, integration_limit[0]], - ref_zoom[1, integration_limit[1]]]) - - print('Baseline correct reference of %.2f transition' % ref_temp) - sol = np.linalg.solve(x_val, y_val) - ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1]) - - # calculate onset slope (use points at position of maximum gradient +/- 100/rate) - ref_grad = np.gradient(ref_zoom[1]) - max_grad = np.argmax(ref_grad) - - x_val = np.array([[ref_zoom[0, max_grad-int(100/rate)], 1], - [ref_zoom[0, max_grad+int(100/rate)+1], 1]]) - y_val = np.array([ref_zoom[1, max_grad-int(100/rate)], - ref_zoom[1, max_grad+int(100/rate)+1]]) - sol = np.linalg.solve(x_val, y_val) - onset = sol[0]*ref_zoom[0] + sol[1] - - melts.append(-sol[1]/sol[0]) - - # plot - ax1[1, i].set_title(f'reference: {ref_temp:.2f}') - ax1[1, i].set_xlabel('T / K') - - ax1[1, i].plot(reference_data[0], reference_data[1], 'r-') - ax1[1, i].plot(ref_zoom[0, max_grad], ref_zoom[0, max_grad], 'kx') - ax1[1, i].plot(ref_zoom[0], onset, 'k--') - ax1[1, i].axhline(0, color='k', linestyle='--') - - ax1[1, i].set_xlim(ref_zoom[0, integration_limit[0]], ref_zoom[0, integration_limit[1]]) - ax1[1, i].set_ylim(-max(ref_zoom[1])/10, max(ref_zoom[1])*1.1) - - print('Onset of transition: %.2f K found at %.2f' % (ref_temp, melts[-1])) - if enthalpy is not None: - # integrate over low temperature peak to calibrate y axis - # delta H in J/g: Integrate Peak over time and divide by weight - area = 1e-3 * simps(ref_zoom[1, integration_limit[0]:integration_limit[1]], - ref_zoom[2, integration_limit[0]:integration_limit[1]], - even='avg') - calib_y_axis = enthalpy / (area / reference.weight) - print("Calibration factor of peak: %f\n" % calib_y_axis) - - sample_baseline[1] *= calib_y_axis - - fig1.delaxes(ax1[1, 2]) - fig1.tight_layout() - - plt.show() - - # give a choice how to compensate for long-time drift - mode = None - while mode not in ['i', 'h']: - mode = input('\nUse [i]sotherms or initial [h]eating for long-time correction? (Default: i) ') - if mode == '': - mode = 'i' - - if mode == 'h': - print('\nCorrect slope from initial heating') - sample_baseline[1] = drift_from_slope - else: - print('\nCorrect slope from isotherm') - sample_baseline[1] = drift_corrected - - # calibrate T axis - print('\nCalibrate temperature') - real_trans = np.array([temp for (temp, _) in ref_points.transitions]) - t_vals = np.array([[melts[0], 1], - [melts[1], 1]]) - calibration_temp = np.linalg.solve(t_vals, real_trans) - print('T_real = {:.4f} * T_meas {:+.4f}'.format(*calibration_temp)) - - sample_baseline[0] = calibration_temp[0] * sample_baseline[0] + calibration_temp[1] - - print('Convert to capacity') - cp = sample_baseline[1] * 60. / rate / sample.weight / 1000. - if sample.weight is None: - raise ValueError('No sample weight given') - - # plot final results in separate figure - fig2, ax2 = plt.subplots() - ax2.set_title('{} K/min: Heat flow vs. heat capacity (close to cont.)'.format(rate)) - ax2.set_xlabel('Temperature / K') - - ax2.plot(sample_baseline[0], sample_baseline[1], label='heat flow') - ax2.plot(sample_baseline[0], cp, label='heat capacity') - - plt.legend() - plt.show() - - outname = os.path.splitext(sample.name)[0] + '_' + str(rate) + 'K-min.dat' - header = 'Made with version: {}\n'.format(__version__) - header += 'T/K\tCp/J/(gK)\theat flow/mW' - - print() - print('Save to', outname) - np.savetxt(outname, np.c_[sample_baseline[0], cp, sample_baseline[1]], header=header) - - -if __name__ == '__main__': - args = parser.parse_args() - evaluate(args.sample, args.empty, args.reference, show_cooling=args.cooling) diff --git a/src/nmreval/dsc/dsc_calibration_fast_neu.py b/src/nmreval/dsc/dsc_calibration_fast_neu.py deleted file mode 100644 index 0575213..0000000 --- a/src/nmreval/dsc/dsc_calibration_fast_neu.py +++ /dev/null @@ -1,292 +0,0 @@ -from __future__ import annotations - -__version__ = '0.1.2' - -import os -from argparse import ArgumentParser -from pathlib import Path -import sys -import numpy as np -import matplotlib.pyplot as plt -from scipy.integrate import simps - -from nmreval.io.dsc import DSCReader, Cyclohexane, ReferenceValue - -parser = ArgumentParser(description='Calibrate DSC data') -parser.add_argument('sample', type=str, help='filename of DSC sample') -parser.add_argument('empty', type=str, help='filename of empty pan') -parser.add_argument('reference', help='filename of reference', type=str) -parser.add_argument('--cooling', help='Plot found cooling rates', action='store_true') - - -def evaluate(sample: str|Path, empty: str|Path, reference: str|Path, - ref_points: ReferenceValue = Cyclohexane, show_cooling: bool = False): - - sample = DSCReader(sample) - empty = DSCReader(empty) - reference = DSCReader(reference) - print(sample) - - if show_cooling: - fig, ax = plt.subplots() - print('\n') - for k, v in sample.cooling.items(): - print('Plot run {} with cooling rate {} K/min'.format(k, v)) - c = sample.flow_data(v, mode='c') - ax.plot(c[0], c[1], label=str(v)+' K/min') - ax.set_xlabel('T / K') - plt.legend() - plt.show() - - return - - run_list = [] - if len(sample.heating) > 1: - run = None - print('\nMultiple heat rates found:') - for k, v in sample.heating.items(): - print(' run {}: {} K/min'.format(k, v)) - while run not in sample.heating: - # choose your own adventure!!! - value = input('\nPlease select a run (press Enter for all heat rates): ') - if value == '': - run_list = list(sample.heating.keys()) - break - else: - run = int(value) - run_list = [run] - else: - run_list = list(sample.heating.keys()) - - for run in run_list: - rate = sample.heating[run] - - print('\nProcessing heat rate {} K/min'.format(rate)) - - print('Load data of heating data') - len_sample = sample.length(run) - - # sanity checks - try: - reference_data = reference.flow_data(rate) - except IndexError: - print('ERROR: Reference measurement has no heat rate {} K/min'.format(rate)) - print('Stop evaluation') - sys.exit() - - try: - run_baseline = empty.get_run(rate) - except ValueError: - print('ERROR: Empty measurement has no heat rate {} K/min'.format(rate)) - print('Stop evaluation') - sys.exit() - - len_baseline = empty.length(run_baseline) - max_length = None - if len_baseline != len_sample: - print('WARNING: measurements differ by {} points'.format(abs(len_baseline - len_sample))) - max_length = min(len_baseline, len_sample) - - sample_data = sample.flow_data(rate, length=max_length) - empty_data = empty.flow_data(rate, length=max_length) - - # plot input data - fig1, ax1 = plt.subplots(2, 3, **{'figsize': (10, 6)}) - ax1[0, 0].set_title('raw data') - ax1[0, 0].set_xlabel('T / K') - - ax1[0, 0].plot(sample_data[0], sample_data[1], 'k-', label='Sample') - ax1[0, 0].plot(empty_data[0], empty_data[1], 'b-', label='Empty') - ax1[0, 0].plot(reference_data[0], reference_data[1], 'r-', label='Reference') - ax1[0, 0].legend() - - print('Substract empty data') - sample_baseline = sample_data.copy() - sample_baseline[1] = sample_data[1] - empty_data[1] - - # plot baseline correction - ax1[0, 1].set_title('baseline correction') - ax1[0, 1].set_xlabel('T / K') - - ax1[0, 1].plot(sample_data[0], sample_data[1], 'k--', label='Raw') - ax1[0, 1].plot(sample_baseline[0], sample_baseline[1], 'k-', label='Baseline corrected') - ax1[0, 1].plot(empty_data[0], empty_data[1], 'b-', label='Empty') - ax1[0, 1].legend() - - print('Load isothermal data around heat rate') - mean_isotherms = [] - for offset, where, ls in [(-1, 'low', '-'), (1, 'high', '--')]: - # read isotherms and baseline correct - len_baseline = empty.length(run_baseline+offset) - len_sample = sample.length(run+offset) - if len_baseline != len_sample: - print('WARNING: {} T isotherms differ by {} points'.format(where, abs(len_baseline-len_sample))) - - max_length = min(len_baseline, len_sample) - isotherm_sample = sample.isotherm_data(run_baseline+offset, length=max_length) - isotherm_empty = empty.isotherm_data(run+offset, length=max_length) - - isotherm_sample[1] -= isotherm_empty[1] - - # get mean isotherm value - m = np.polyfit(isotherm_sample[0, 200:-200], isotherm_sample[1, 200:-200], 0)[0] - mean_isotherms.append(m) - print('Calculated {} heat flow: {} mW'.format(where, m)) - - ax1[0, 2].plot(isotherm_sample[0], isotherm_sample[1], 'k--') - - # calculate slope from difference between isotherms - slope = (mean_isotherms[1]-mean_isotherms[0]) / (sample_data[2, -1] - empty_data[2, 0]) - print('Heat flow slope from isotherms: {} per minute'.format(slope*60)) - - # calculate mean slope of heat flow at points in the beginning - slope_baseline = np.gradient(sample_baseline[1, int(4000/rate):int(9000/rate)], - sample_baseline[2, 300]-sample_baseline[2, 299]).mean() - print('Heat flow slope from initial heating: {} per minute'.format(slope_baseline*60)) - - drift_corrected = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope - drift_from_slope = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope_baseline - - # plot - ax1[0, 2].axhline(mean_isotherms[0], linestyle=':') - ax1[0, 2].axhline(mean_isotherms[1], linestyle=':') - - ax1[0, 2].plot(sample_baseline[2], sample_baseline[1], 'k-', label='Baseline corrected') - ax1[0, 2].plot(sample_baseline[2], drift_corrected, 'g-', label='Corrected (isotherm)') - ax1[0, 2].plot(sample_baseline[2], drift_from_slope, 'b-', label='Corrected (heating)') - - ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + (sample_baseline[2]-empty_data[2, 0])*slope, 'g--') - ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + slope_baseline*(sample_baseline[2]-empty_data[2, 0]), - 'b--') - - ax1[0, 2].plot(sample_baseline[2, int(4000/rate):int(9000/rate)], - sample_baseline[1, int(4000/rate):int(9000/rate)], 'r--') - - ax1[0, 2].set_title('time dependence') - ax1[0, 2].set_xlabel('t / s') - ax1[0, 2].legend() - - melts = [] - for i, (trans_temp, enthalpy) in enumerate(ref_points.transitions): - print(trans_temp, enthalpy) - - # region around reference peaks - # NOTE: limits are hard coded for cyclohexane, other references need other limits - low_border = np.argmin(abs(reference_data[0]-(trans_temp-15))) - high_border = np.argmin(abs(reference_data[0]-(trans_temp+15))) - ref_zoom = reference_data[:, 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]]) - print('Baseline correct reference of {} transition'.format(trans_temp)) - sol = np.linalg.solve(x_val, y_val) - ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1]) - peak_max = ref_zoom[:, np.argmax(ref_zoom[1])] - integration_limit = np.argmin(abs(ref_zoom[0]-peak_max[0]+3)), np.argmin(abs(ref_zoom[0]-peak_max[0]-3)) - - # substract baseline around reference peaks - x_val = np.array([[ref_zoom[0, integration_limit[0]], 1], - [ref_zoom[0, integration_limit[1]], 1]]) - y_val = np.array([ref_zoom[1, integration_limit[0]], - ref_zoom[1, integration_limit[1]]]) - - print('Baseline correct reference of {} transition'.format(trans_temp)) - sol = np.linalg.solve(x_val, y_val) - ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1]) - - # calculate onset slope (use points at position of maximum gradient +/- 100/rate) - ref_grad = np.gradient(ref_zoom[1]) - max_grad = np.argmax(ref_grad) - - x_val = np.array([[ref_zoom[0, max_grad-int(100/rate)], 1], - [ref_zoom[0, max_grad+int(100/rate)+1], 1]]) - y_val = np.array([ref_zoom[1, max_grad-int(100/rate)], - ref_zoom[1, max_grad+int(100/rate)+1]]) - sol = np.linalg.solve(x_val, y_val) - onset = sol[0]*ref_zoom[0] + sol[1] - - melts.append(-sol[1]/sol[0]) - - # plot - ax1[1, i].set_title('reference: {:.2f} K'.format(trans_temp)) - ax1[1, i].set_xlabel('T / K') - - ax1[1, i].plot(reference_data[0], reference_data[1], 'r-') - ax1[1, i].plot(ref_zoom[0, max_grad], ref_zoom[0, max_grad], 'kx') - ax1[1, i].plot(ref_zoom[0], onset, 'k--') - ax1[1, i].axhline(0, color='k', linestyle='--') - - ax1[1, i].set_xlim(ref_zoom[0, integration_limit[0]], ref_zoom[0, integration_limit[1]]) - ax1[1, i].set_ylim(-max(ref_zoom[1])/10, max(ref_zoom[1])*1.1) - - print('Onset of transition: {:.2f} K, should be at {:.2f}'.format(melts[-1], trans_temp)) - if enthalpy is not None: - # integrate over low temperature peak to calibrate y axis - # NOTE: again, this is only valid for cyclohexane - # delta H in J/g: Integrate Peak over time and divide by weight - area = 1e-3 * simps(ref_zoom[1, integration_limit[0]:integration_limit[1]], - ref_zoom[2, integration_limit[0]:integration_limit[1]], - even='avg') - calib_y_axis = enthalpy / (area / reference.weight) - print("Calibration factor of peak: {}".format(calib_y_axis)) - - sample_baseline[1] *= calib_y_axis - - fig1.delaxes(ax1[1, 2]) - fig1.tight_layout() - - plt.show() - - # give a choice how to compensate for long-time drift - mode = None - while mode not in ['i', 'h']: - mode = input('\nUse [i]sotherms or initial [h]eating for long-time correction? (Default: i) ') - if mode == '': - mode = 'i' - - if mode == 'h': - print('\nCorrect slope from initial heating') - sample_baseline[1] = drift_from_slope - else: - print('\nCorrect slope from isotherm') - sample_baseline[1] = drift_corrected - - # calibrate T axis - print('\nCalibrate temperature') - real_trans = np.array([ref_points.transition1, ref_points.transition2]) - t_vals = np.array([[melts[0], 1], - [melts[1], 1]]) - calibration_temp = np.linalg.solve(t_vals, real_trans) - print('T_real = {:.4f} * T_meas {:+.4f}'.format(*calibration_temp)) - - sample_baseline[0] = calibration_temp[0] * sample_baseline[0] + calibration_temp[1] - - print('Convert to capacity') - cp = sample_baseline[1] * 60. / rate / sample.weight / 1000. - if sample.weight is None: - raise ValueError('No sample weight given') - - # plot final results in separate figure - fig2, ax2 = plt.subplots() - ax2.set_title('{} K/min: Heat flow vs. heat capacity (close to cont.)'.format(rate)) - ax2.set_xlabel('Temperature / K') - - ax2.plot(sample_baseline[0], sample_baseline[1], label='heat flow') - ax2.plot(sample_baseline[0], cp, label='heat capacity') - - plt.legend() - plt.show() - - outname = os.path.splitext(sample.name)[0] + '_' + str(rate) + 'K-min.dat' - header = 'Made with version: {}\n'.format(__version__) - header += 'T/K\tCp/J/(gK)\theat flow/mW' - - print() - print('Save to', outname) - np.savetxt(outname, np.c_[sample_baseline[0], cp, sample_baseline[1]], header=header) - - -if __name__ == '__main__': - args = parser.parse_args() - evaluate(args.sample, args.empty, args.reference, show_cooling=args.cooling) diff --git a/src/nmreval/io/dsc.py b/src/nmreval/io/dsc.py index 84c024f..be6cff4 100644 --- a/src/nmreval/io/dsc.py +++ b/src/nmreval/io/dsc.py @@ -33,7 +33,7 @@ class DSCSample: self.read_file(fname) - def read_file(self, fname: str | Path): + def read_file(self, fname: str | Path) -> None: fname = Path(fname) # file contains weird deg C character in stupiod ISO encoding @@ -141,7 +141,7 @@ class DSCCalibrator: def __init__(self): self.sample = None self.empty = None - self.reference =[] + self.reference = [] self.ref_list = [] def set_measurement(self, @@ -207,11 +207,8 @@ class DSCCalibrator: integ_limit = (np.argmin(np.abs(ref_zoom[0] - peak_max[0] + 3)), np.argmin(np.abs(ref_zoom[0] - peak_max[0] - 3))) - # substract baseline around reference peaks - x_val = np.array([[ref_zoom[0, integ_limit[0]], 1], [ref_zoom[0, integ_limit[1]], 1]]) - y_val = np.array([ref_zoom[1, integ_limit[0]], ref_zoom[1, integ_limit[1]]]) - - sol = np.linalg.solve(x_val, y_val) + # 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)) @@ -220,11 +217,7 @@ class DSCCalibrator: grad_pos = max_grad-int(50 / rate), max_grad - x_val = np.array([[ref_zoom[0, grad_pos[0]], 1], - [ref_zoom[0, grad_pos[1]], 1]]) - y_val = np.array([ref_zoom[1, grad_pos[0]], - ref_zoom[1,grad_pos[1]]]) - sol = np.linalg.solve(x_val, y_val) + sol = self.solve_linear_eq(grad_pos, ref_zoom) onset = sol[0] * ref_zoom[0] + sol[1] melts.append(-sol[1] / sol[0]) @@ -254,7 +247,15 @@ class DSCCalibrator: return calib_x, calib_y, results - def get_data(self, idx, slope='iso'): + @staticmethod + def solve_linear_eq(limits: tuple, ref_zoom: np.ndarray) -> np.ndarray: + x_val = np.array([[ref_zoom[0, limits[0]], 1], [ref_zoom[0, limits[1]], 1]]) + y_val = np.array([ref_zoom[1, limits[0]], ref_zoom[1, limits[1]]]) + sol = np.linalg.solve(x_val, y_val) + + return sol + + def get_data(self, idx: int, slope: str = 'iso', limits: tuple[float, float] = None): if self.sample.steps[idx][0] == 'i': raise ValueError('baseline correction is not implemented for isotherms') @@ -304,21 +305,36 @@ class DSCCalibrator: drift_value = np.c_[drift_value, isotherm_sample] if slope is not None: + offset = sample_data[1, 200] if slope == 'iso': # calculate slope from difference between isotherms m = (mean_isotherms[1] - mean_isotherms[0]) / (sample_data[2, -1] - sample_data[2, 0]) - offset = sample_data[1, 200] + else: # calculate mean slope of heat flow from points in the beginning - offset = sample_data[1, 200] - grad = np.gradient(sample_data[1, :], sample_data[2, :]) + region = None + if limits is not None: + if len(limits) != 2: + raise ValueError(f'limits must be tuple of len 2, not {limits!r}') + min_lim, max_lim = min(limits), max(limits) + window = (sample_data[2, :] >= min_lim) * (sample_data[2, :] <= max_lim) + region = sample_data[1:, window] + if region.shape[1] <= 2: + # raise ValueError(f'No data inside selected time window {min_lim/60} min and {max_lim/60} min') + region = None + + if region is None: + # if no limits, use all + region = sample_data[1:, :] + + grad = np.gradient(region[0, :], region[1, :]) grad = grad[~np.isnan(grad)] m = grad[(grad < grad.mean()+grad.std()/5)*(grad > grad.mean()-grad.std()/5)].mean() sample_data[1] -= m * (sample_data[2] - sample_data[2, 200]) + offset line = np.array([[sample_data[2, 0], sample_data[2, -1]], - [m * (sample_data[2, 200] - sample_data[2, 200]) + offset, + [m * (sample_data[2, 0] - sample_data[2, 200]) + offset, m * (sample_data[2, -1] - sample_data[2, 200]) + offset]]) else: diff --git a/src/resources/_ui/dscfile_dialog.ui b/src/resources/_ui/dscfile_dialog.ui index 6b7143e..2276ceb 100644 --- a/src/resources/_ui/dscfile_dialog.ui +++ b/src/resources/_ui/dscfile_dialog.ui @@ -6,349 +6,445 @@ 0 0 - 962 - 662 + 1341 + 799 Read DSC file - - - + + + Qt::Horizontal - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Save - + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Detected steps + + + false + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + + + Baseline corrections + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Empty measurement + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + No emtpy measurement + + + + + + + 3 + + + + + Load empty + + + + + + + Remove empty + + + + + + + + + + + + Slope correction + + + + 3 + + + 6 + + + 6 + + + 6 + + + 6 + + + + + None + + + buttonGroup + + + + + + + Isotherms + + + true + + + buttonGroup + + + + + + + Initial slope + + + buttonGroup + + + + + + + + 0 + 33 + + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + start (in min) + + + + + + + + + + stop (in min) + + + + + + + + + + + + + + + + References + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + Use reference to convert to heat capacity + + + true + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + 2 + + + false + + + true + + + false + + + + + + + + + 3 + + + + + + 0 + 0 + + + + Add reference + + + + + + + + 0 + 0 + + + + Remove reference + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Save + + + true + + + + + + + + + + + + 0 + 0 + + + + + 300 + 200 + + + + + + + + + 0 + 0 + + + + + 300 + 200 + + + + + + + + + 0 + 0 + + + + + 300 + 200 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + - - - - 0 - - - 0 - - - 3 - - - - - - 0 - 0 - - - - Convert to heat capacity - - - true - - - - - - - 3 - - - - - Load empty - - - - - - - Remove empty - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Isotherms - - - true - - - buttonGroup - - - - - - - - 0 - 0 - - - - font-weight: bold - - - Detected steps - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - 2 - - - false - - - true - - - false - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - - - - Slope - - - - - - - Initial slope - - - buttonGroup - - - - - - - Empty measurement - - - - - - - - 0 - 0 - - - - font-weight: bold - - - Calibration - - - - - - - - 0 - 0 - - - - font-weight: bold - - - Baseline - - - - - - - Qt::Horizontal - - - - - - - None - - - buttonGroup - - - - - - - 3 - - - - - - 0 - 0 - - - - Add reference - - - - - - - - 0 - 0 - - - - Remove reference - - - - - - - - - Qt::Horizontal - - - - - - - - - - - - 0 - 0 - - - - - 300 - 200 - - - - - - - - - 0 - 0 - - - - - 300 - 200 - - - - - - - - - 0 - 0 - - - - - 300 - 200 - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - -