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()