303 lines
11 KiB
Python
303 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
from pyqtgraph import PlotDataItem
|
|
|
|
from nmreval.data import DSC
|
|
from nmreval.io.dsc import Cyclohexane, DSCCalibrator, DSCSample
|
|
|
|
from ..Qt import QtWidgets, QtCore
|
|
from .._py.dscfile_dialog import Ui_Dialog
|
|
|
|
|
|
class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
|
|
data_read = QtCore.pyqtSignal(list)
|
|
file_ext = ['.txt', '.dsc']
|
|
|
|
def __init__(self, sample=None, empty=None, reference=None, parent=None):
|
|
super().__init__(parent=parent)
|
|
self.setupUi(self)
|
|
|
|
self.calibrator = DSCCalibrator()
|
|
self.current_run = (-1, 'h')
|
|
self.sample_idx = None
|
|
self.fname = None
|
|
|
|
self.raw_graph.setLabel('bottom', text='T', units='K')
|
|
self.raw_graph.setTitle('Raw data')
|
|
self.raw_graph.addLegend()
|
|
self.raw_sample = PlotDataItem(x=[], y=[], name='Sample')
|
|
self.empty_sample = PlotDataItem(x=[], y=[], pen={'dash': (5, 5)}, name='Empty')
|
|
self.raw_graph.addItem(self.raw_sample)
|
|
self.raw_graph.addItem(self.empty_sample)
|
|
|
|
self.end_graph.setTitle('End result')
|
|
self.end_graph.setLabel('bottom', text='T', units='K')
|
|
self.baseline_sample = PlotDataItem(x=[], y=[])
|
|
self.end_graph.addItem(self.baseline_sample)
|
|
|
|
self.baseline_graph.setTitle('Time dependence')
|
|
self.baseline_graph.setLabel('bottom', text='t', units='min')
|
|
self.drift_sample = PlotDataItem(x=[], y=[])
|
|
self.slope_graph = PlotDataItem(x=[], y=[], pen={'color': 'r', 'dash': (5, 5)})
|
|
self.baseline_graph.addItem(self.drift_sample)
|
|
self.baseline_graph.addItem(self.slope_graph)
|
|
|
|
self.calib_graph.setTitle('Calibration')
|
|
self.calib_graph.setLabel('bottom', text='T', units='K')
|
|
self.ref_plotitems = []
|
|
|
|
self.sample = None
|
|
if sample is not None:
|
|
self.add_sample(sample)
|
|
|
|
self.empty = None
|
|
if empty is not None:
|
|
self.add_empty(empty=empty)
|
|
|
|
self.references = []
|
|
if reference is not None:
|
|
if isinstance(reference, (str, Path, DSCSample)):
|
|
reference = [reference]
|
|
for r in reference:
|
|
self.add_reference(ref=r)
|
|
|
|
def __call__(self, fname: Path | str):
|
|
self.clear_plots()
|
|
self.add_sample(fname)
|
|
|
|
return self
|
|
|
|
def add_sample(self, fname: Path | str):
|
|
self.sample = self.calibrator.set_measurement(fname, mode='sample')
|
|
self.fname = self.sample.fname
|
|
|
|
self.step_listWidget.clear()
|
|
|
|
for opts in self.sample.steps:
|
|
item = QtWidgets.QListWidgetItem()
|
|
item.setFlags(
|
|
QtCore.Qt.ItemFlag.ItemIsEnabled |
|
|
QtCore.Qt.ItemFlag.ItemIsSelectable |
|
|
QtCore.Qt.ItemFlag.ItemIsUserCheckable
|
|
)
|
|
item.setCheckState(QtCore.Qt.CheckState.Unchecked)
|
|
|
|
if opts[0] == 'i':
|
|
item.setFlags(QtCore.Qt.ItemFlag.NoItemFlags)
|
|
item.setText(f'{opts[1]:.2f} K for {opts[2] / 60:.0f} min')
|
|
else:
|
|
item.setFlags(
|
|
QtCore.Qt.ItemFlag.ItemIsEnabled |
|
|
QtCore.Qt.ItemFlag.ItemIsSelectable |
|
|
QtCore.Qt.ItemFlag.ItemIsUserCheckable
|
|
)
|
|
item.setText(f'{opts[2]:.2f} K to {opts[3]:.2f} K with {opts[1]} K/min')
|
|
|
|
self.step_listWidget.addItem(item)
|
|
|
|
@QtCore.pyqtSlot(name='on_loadempty_button_clicked')
|
|
def add_empty(self, empty=None):
|
|
if empty is None:
|
|
empty, _ = QtWidgets.QFileDialog.getOpenFileName(directory=str(self.fname.parent))
|
|
|
|
if empty:
|
|
self.empty = self.calibrator.set_measurement(empty, mode='empty')
|
|
|
|
# avoid ValueError breaking data update
|
|
if self.empty.fname.is_relative_to(Path.home()):
|
|
self.empty_label.setText('~/' + str(self.empty.fname.relative_to(Path.home())))
|
|
else:
|
|
self.empty_label.setText(str(self.empty.fname))
|
|
|
|
self.update_plots()
|
|
|
|
@QtCore.pyqtSlot(name='on_delempty_button_clicked')
|
|
def remove_empty(self):
|
|
self.empty_label.setText('No empty measurement')
|
|
self.calibrator.empty = None
|
|
|
|
self.update_plots()
|
|
|
|
@QtCore.pyqtSlot(name='on_ref_add_pushButton_clicked')
|
|
def add_reference(self, ref: str | Path = None):
|
|
if ref is None:
|
|
ref, _ = QtWidgets.QFileDialog.getOpenFileName(directory=str(self.fname.parent))
|
|
|
|
if ref:
|
|
ref = self.calibrator.set_measurement(ref, mode='reference')
|
|
|
|
self.references.append(ref)
|
|
item = QtWidgets.QTableWidgetItem(str(ref.fname.name))
|
|
item.setData(QtCore.Qt.ItemDataRole.UserRole, ref.fname)
|
|
item.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
|
|
|
|
rowcnt = self.reference_tableWidget.rowCount()
|
|
self.reference_tableWidget.setRowCount(rowcnt+1)
|
|
self.reference_tableWidget.setItem(rowcnt, 0, item)
|
|
self.reference_tableWidget.setCellWidget(rowcnt, 1, ReferenceComboBox())
|
|
self.reference_tableWidget.resizeColumnsToContents()
|
|
|
|
self.update_plots()
|
|
|
|
@QtCore.pyqtSlot(name='on_ref_remove_pushButton_clicked')
|
|
def remove_reference(self):
|
|
idx = self.reference_tableWidget.currentRow()
|
|
self.calibrator.remove_reference(self.reference_tableWidget.item(idx, 0).data(QtCore.Qt.ItemDataRole.UserRole))
|
|
|
|
self.reference_tableWidget.removeRow(idx)
|
|
self.update_plots()
|
|
|
|
@QtCore.pyqtSlot(QtWidgets.QListWidgetItem, name='on_step_listWidget_itemChanged')
|
|
def select_run(self, item: QtWidgets.QListWidgetItem):
|
|
|
|
idx = self.step_listWidget.indexFromItem(item).row()
|
|
self.step_listWidget.blockSignals(True)
|
|
for row in range(self.step_listWidget.count()):
|
|
if idx == row:
|
|
continue
|
|
self.step_listWidget.item(row).setCheckState(QtCore.Qt.CheckState.Unchecked)
|
|
self.step_listWidget.blockSignals(False)
|
|
|
|
if item.checkState() == QtCore.Qt.CheckState.Checked:
|
|
mode, rate, _, _ = self.sample.steps[idx]
|
|
self.current_run = (rate, mode)
|
|
self.sample_idx = idx
|
|
self.update_plots()
|
|
else:
|
|
self.current_run = (-1, 'h')
|
|
self.sample_idx = None
|
|
self.clear_plots()
|
|
|
|
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()]
|
|
|
|
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, limits=limit)
|
|
except ValueError as e:
|
|
_msg = QtWidgets.QMessageBox.warning(self, f'Data collection with error', e.args[0])
|
|
return
|
|
|
|
self.calibrator.ref_list = []
|
|
for row in range(self.reference_tableWidget.rowCount()):
|
|
self.calibrator.ref_list.append(self.reference_tableWidget.cellWidget(row, 1).get_reference())
|
|
|
|
calib_x, calib_y, regions = self.calibrator.get_calibration(rate)
|
|
|
|
drift_value[0] /= 60.
|
|
slope[0] /= 60.
|
|
|
|
if calib_x is not None:
|
|
sample_data[0] *= calib_x[0]
|
|
sample_data[0] += calib_x[1]
|
|
|
|
if self.cp_checkBox.isChecked():
|
|
sample_data[1] *= calib_y
|
|
|
|
return sample_data, raw_sample, empty_data, drift_value, slope, calib_x, calib_y, regions
|
|
|
|
@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:
|
|
return
|
|
|
|
sample_data, raw_sample, empty_data, drift_value, slope, calib_x, calib_y, regions = res
|
|
|
|
self.raw_sample.setData(x=raw_sample[0], y=raw_sample[1])
|
|
self.drift_sample.setData(x=drift_value[0], y=drift_value[1])
|
|
self.slope_graph.setData(x=slope[0], y=slope[1])
|
|
|
|
if empty_data is not None:
|
|
self.empty_sample.setData(x=empty_data[0], y=empty_data[1])
|
|
else:
|
|
self.empty_sample.setData(x=[], y=[])
|
|
|
|
self.calib_graph.clear()
|
|
|
|
if calib_x is not None:
|
|
for ref_zoom, onset, grad_points in regions:
|
|
ref_plot = PlotDataItem(x=ref_zoom[0], y=ref_zoom[1])
|
|
self.calib_graph.addItem(ref_plot)
|
|
|
|
self.calib_graph.addItem(PlotDataItem(x=grad_points[0], y=grad_points[1],
|
|
symbol='x'))
|
|
|
|
view_limits = -np.max(ref_zoom[1])/10, np.max(ref_zoom[1]) * 1.1
|
|
cut_idx, = np.where((onset < view_limits[1]) & (onset > view_limits[0]))
|
|
self.calib_graph.addItem(PlotDataItem(x=ref_zoom[0, cut_idx], y=onset[cut_idx],
|
|
pen={'color': 'r', 'dash': (2, 5)}))
|
|
self.calib_graph.addItem(PlotDataItem(x=[ref_zoom[0, 0], ref_zoom[0, -1]], y=[0, 0],
|
|
pen={'color': 'r', 'dash': (2, 5)}))
|
|
|
|
self.baseline_sample.setData(x=sample_data[0], y=sample_data[1])
|
|
|
|
def clear_plots(self):
|
|
for plot in [self.raw_sample, self.baseline_sample, self.empty_sample, self.drift_sample, self.slope_graph]:
|
|
plot.setData(x=[], y=[])
|
|
|
|
self.calib_graph.clear()
|
|
|
|
def export_data(self, filesave=False, close_after=True):
|
|
try:
|
|
sample_data = self.get_data()[0]
|
|
except TypeError:
|
|
return
|
|
|
|
if self.cp_checkBox.isChecked() and self.references:
|
|
y_label = 'cp'
|
|
else:
|
|
y_label = 'q'
|
|
|
|
rate, mode = self.current_run
|
|
new_val = DSC(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate}K-min ({mode}, {y_label})')
|
|
|
|
if filesave:
|
|
new_val.savetxt(self.fname.with_name(f'{self.fname.stem}_{rate}K-min_{y_label}{mode}.dat'.replace(' ', '_')))
|
|
close_after = False
|
|
else:
|
|
self.data_read.emit([new_val])
|
|
|
|
if close_after:
|
|
super().accept()
|
|
|
|
@QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonBox_clicked')
|
|
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)
|
|
else:
|
|
super().close()
|
|
|
|
|
|
class ReferenceComboBox(QtWidgets.QComboBox):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.references = [Cyclohexane]
|
|
for ref in self.references:
|
|
self.addItem(ref.name)
|
|
|
|
def get_reference(self):
|
|
return self.references[self.currentIndex()]
|