This commit is contained in:
vls committed 2026-10-06 17:20:47 +02:00
1 parent cc7ae6d49a
commit ec1e6846ab
32 files changed
+75060 -630

No files matched your search

+221
View File
@@ -0,0 +1,221 @@
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()