221 lines
6.4 KiB
Python
221 lines
6.4 KiB
Python
import PySide6.QtWidgets as qw
|
|
import PySide6.QtCore as qc
|
|
from PySide6.QtCore import Slot, Qt
|
|
import PySide6.QtGui as qg
|
|
import pyqtgraph as pg
|
|
|
|
import sys
|
|
|
|
sys.path.append('../build')
|
|
|
|
import slopefit as sf
|
|
|
|
class ParamTableModel(qc.QAbstractTableModel):
|
|
HEADERS = ['property', 'initial value', 'delta']
|
|
init_changed = qc.Signal(int, float)
|
|
delta_changed = qc.Signal(int, float)
|
|
|
|
def __init__(self, parent = None):
|
|
super().__init__(parent)
|
|
self._rows = []
|
|
|
|
def rowCount(self, parent=qc.QModelIndex()):
|
|
return len(self._rows)
|
|
|
|
def columnCount(self, parent=qc.QModelIndex()):
|
|
return len(self.HEADERS)
|
|
|
|
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
|
|
if not index.isValid():
|
|
return None
|
|
row, col = index.row(), index.column()
|
|
value = self._rows[row][col]
|
|
|
|
if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
|
|
return "" if value is None else str(value)
|
|
if role == Qt.ItemDataRole.TextAlignmentRole and col > 0:
|
|
return int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
|
return None
|
|
|
|
def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
|
|
if not index.isValid() or role != Qt.ItemDataRole.EditRole:
|
|
return False
|
|
row, col = index.row(), index.column()
|
|
if col == 0: # name column: plain text
|
|
return False
|
|
else: # x / dx columns: float
|
|
try:
|
|
self._rows[row][col] = float(value) if value != "" else None
|
|
except ValueError:
|
|
return False
|
|
self.dataChanged.emit(index, index, [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole])
|
|
if col == 1:
|
|
self.init_changed.emit(row, float(value))
|
|
elif col == 2:
|
|
self.delta_changed.emit(row, float(value))
|
|
return True
|
|
|
|
def flags(self, index):
|
|
if not index.isValid() or index.column() == 0:
|
|
return Qt.ItemFlag.NoItemFlags
|
|
return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEditable
|
|
|
|
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
|
|
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
|
|
return self.HEADERS[section]
|
|
return None
|
|
|
|
def set_params(self, params):
|
|
self.beginResetModel()
|
|
self._rows = [[name, v, d] for (name, v, d) in params]
|
|
self.endResetModel()
|
|
|
|
|
|
|
|
class MainWindow(qw.QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle('slopefit')
|
|
|
|
|
|
self.input_plot = pg.PlotWidget()
|
|
|
|
self.lower_bounds = qw.QDoubleSpinBox()
|
|
self.lower_bounds.setValue(0.0)
|
|
self.lower_bounds.valueChanged.connect(self.set_lb)
|
|
|
|
self.upper_bounds = qw.QDoubleSpinBox()
|
|
self.upper_bounds.setValue(0.0)
|
|
self.upper_bounds.valueChanged.connect(self.set_ub)
|
|
|
|
|
|
bounds_layout = qw.QHBoxLayout()
|
|
bounds_layout.addWidget(self.lower_bounds)
|
|
bounds_layout.addWidget(self.upper_bounds)
|
|
|
|
bounds_widget = qw.QWidget()
|
|
bounds_widget.setLayout(bounds_layout)
|
|
|
|
self.slope_plot = pg.PlotWidget()
|
|
|
|
self.curve_box = qw.QComboBox()
|
|
self.curve_box.addItems(['parabola', 'ellipse'])
|
|
self.curve_box.currentIndexChanged.connect(self.set_curve_i)
|
|
|
|
self.table = qw.QTableView()
|
|
self.param_model = ParamTableModel()
|
|
self.param_model.init_changed.connect(self.set_init)
|
|
self.param_model.delta_changed.connect(self.set_delta)
|
|
self.table.setModel(self.param_model)
|
|
self.table.horizontalHeader().setSectionResizeMode(qw.QHeaderView.Stretch)
|
|
|
|
self.flip_curve = qw.QCheckBox()
|
|
self.flip_curve.checkStateChanged.connect(self.set_check)
|
|
|
|
self.fit_button = qw.QPushButton()
|
|
self.fit_button.setText("calculate fit")
|
|
self.fit_button.clicked.connect(self.do_fit)
|
|
|
|
self.error_plot = pg.PlotWidget()
|
|
plot_group_1 = qw.QHBoxLayout()
|
|
plot_group_1.addWidget(self.input_plot)
|
|
plot_group_1.addWidget(self.slope_plot)
|
|
|
|
layout = qw.QVBoxLayout()
|
|
layout.addWidget(qw.QWidget(layout=plot_group_1))
|
|
layout.addWidget(bounds_widget)
|
|
layout.addWidget(self.flip_curve)
|
|
layout.addWidget(self.curve_box)
|
|
layout.addWidget(self.table)
|
|
layout.addWidget(self.fit_button)
|
|
layout.addWidget(self.error_plot)
|
|
|
|
central_widget = qw.QWidget()
|
|
central_widget.setLayout(layout)
|
|
self.setCentralWidget(central_widget)
|
|
|
|
self.init_menu()
|
|
|
|
self.optimizer = sf.Optimizer()
|
|
self.optimizer.set_plot_input_cb(self.plot_input_cb)
|
|
self.optimizer.set_init_bounds_cb(self.init_bounds_cb)
|
|
self.optimizer.set_plot_slope_cb(self.plot_slope_cb)
|
|
self.optimizer.set_update_table_cb(self.update_table_cb)
|
|
self.optimizer.set_plot_error_cb(self.plot_error_cb)
|
|
|
|
@Slot(float)
|
|
def set_lb(self, lb):
|
|
self.optimizer.set_lower_bound(lb)
|
|
|
|
@Slot(float)
|
|
def set_ub(self, ub):
|
|
self.optimizer.set_upper_bound(ub)
|
|
|
|
@Slot(int)
|
|
def set_curve_i(self, i):
|
|
self.optimizer.set_curve_type(i)
|
|
|
|
@Slot(int, float)
|
|
def set_init(self, i, v):
|
|
self.optimizer.set_initial(i, v)
|
|
|
|
@Slot(int, float)
|
|
def set_delta(self, i, v):
|
|
self.optimizer.set_delta(i, v)
|
|
|
|
@Slot(bool)
|
|
def set_check(self, flip):
|
|
self.optimizer.flip_curve()
|
|
|
|
@Slot()
|
|
def do_fit(self):
|
|
self.optimizer.calc_fit()
|
|
|
|
def init_menu(self):
|
|
import_action = qg.QAction('&import', self)
|
|
import_action.triggered.connect(self.import_file)
|
|
|
|
menu = self.menuBar()
|
|
file_menu = menu.addMenu('&file')
|
|
file_menu.addAction(import_action)
|
|
|
|
def import_file(self):
|
|
print("import file...")
|
|
fn, _ = qw.QFileDialog.getOpenFileName(self, caption='import file...', filter='data files (*.dat)')
|
|
print(f"open file {fn}")
|
|
self.optimizer.import_file(fn)
|
|
|
|
def plot_input_cb(self, xs, yss):
|
|
self.input_plot.clear()
|
|
for ys in yss:
|
|
self.input_plot.plot(xs, ys)
|
|
|
|
def init_bounds_cb(self, lb, ub, step):
|
|
self.lower_bounds.setRange(lb, ub)
|
|
self.lower_bounds.setSingleStep(step)
|
|
self.lower_bounds.setValue(lb)
|
|
|
|
self.upper_bounds.setRange(lb, ub)
|
|
self.upper_bounds.setSingleStep(step)
|
|
self.upper_bounds.setValue(ub)
|
|
|
|
def plot_slope_cb(self, xs, ys_data, ys_curve):
|
|
self.slope_plot.clear()
|
|
self.slope_plot.plot(xs, ys_data)
|
|
self.slope_plot.plot(xs, ys_curve)
|
|
|
|
def update_table_cb(self, entries):
|
|
self.param_model.set_params(entries)
|
|
print(entries)
|
|
|
|
def plot_error_cb(self, xs, ys):
|
|
self.error_plot.clear()
|
|
self.error_plot.plot(xs, ys)
|
|
|
|
|
|
app = qw.QApplication(sys.argv)
|
|
|
|
window = MainWindow()
|
|
window.show()
|
|
|
|
app.exec() |