summaryrefslogtreecommitdiff
path: root/ui/windows/signals_window.py
diff options
context:
space:
mode:
Diffstat (limited to 'ui/windows/signals_window.py')
-rw-r--r--ui/windows/signals_window.py128
1 files changed, 11 insertions, 117 deletions
diff --git a/ui/windows/signals_window.py b/ui/windows/signals_window.py
index eb7730a..68cceda 100644
--- a/ui/windows/signals_window.py
+++ b/ui/windows/signals_window.py
@@ -4,14 +4,10 @@ ui/windows/signals_window.py
SIGNALS window — toolbar section 3.
Tabs:
- Pipeline — per-channel filter stack (filters, calibration, scale/offset)
+ Pipeline — per-channel filter stack (filters, scale/offset)
Derived — create virtual channels from physical ones
Channels — visibility & color management for all channels
-CALIBRATION: A dedicated calibration section inside Pipeline lets the operator
-enter known input→output pairs and fits a linear (or polynomial) cal curve,
-which is applied as a ScaleOffset filter.
-
DERIVED includes:
- displacement from pot voltage (calibrated)
- velocity from displacement (backward difference)
@@ -22,15 +18,13 @@ DERIVED includes:
"""
from __future__ import annotations
-import math
-from typing import List, Optional, Tuple
+from typing import List, Optional
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QScrollArea, QFrame, QTabWidget, QComboBox, QLineEdit,
QDoubleSpinBox, QSpinBox, QCheckBox, QTextEdit, QGroupBox,
- QToolButton, QSizePolicy, QTableWidget, QTableWidgetItem,
- QHeaderView, QAbstractItemView, QFormLayout, QMessageBox,
+ QToolButton, QSizePolicy, QFormLayout, QMessageBox,
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QColor, QFont, QCloseEvent
@@ -39,7 +33,7 @@ from devices.device_registry import DeviceRegistry
from core.signal_processor import (
SignalProcessor, ChannelPipeline, DerivedChannel,
FilterBase, MovingAverageFilter, MedianFilter, LowPassFilter,
- HighPassFilter, ScaleOffsetFilter, DerivativeFilter, IntegralFilter,
+ HighPassFilter, DerivativeFilter, IntegralFilter,
FILTER_CLASSES,
)
@@ -129,92 +123,6 @@ _QLabel.also = _also
# ══════════════════════════════════════════════════════════════════════════════
-# Calibration widget
-# ══════════════════════════════════════════════════════════════════════════════
-
-class CalibrationWidget(QGroupBox):
- """
- Linear calibration: enter N (raw, engineering) point pairs,
- fit y = scale*x + offset, apply as ScaleOffsetFilter.
- """
- cal_applied = pyqtSignal(float, float) # scale, offset
-
- def __init__(self, parent=None):
- super().__init__("Calibration", parent)
- self._points: List[Tuple[float,float]] = []
- self._build()
-
- def _build(self):
- lay = QVBoxLayout(self); lay.setSpacing(4)
-
- # Table of raw→engineering pairs
- self._tbl = QTableWidget(0, 2)
- self._tbl.setHorizontalHeaderLabels(["Raw (sensor)", "Engineering"])
- self._tbl.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
- self._tbl.setMaximumHeight(130)
- self._tbl.setObjectName("channelTable")
- lay.addWidget(self._tbl)
-
- btn_row = QHBoxLayout()
- add_pt = QPushButton("+ Point"); add_pt.setObjectName("addTraceBtn")
- add_pt.clicked.connect(self._add_row)
- rm_pt = QPushButton("✕ Remove"); rm_pt.setObjectName("configButton")
- rm_pt.clicked.connect(self._rm_row)
- btn_row.addWidget(add_pt); btn_row.addWidget(rm_pt); btn_row.addStretch()
- lay.addLayout(btn_row)
-
- # Result
- self._result_lbl = QLabel("")
- self._result_lbl.setObjectName("traceSource"); lay.addWidget(self._result_lbl)
-
- fit_btn = QPushButton("Fit & Apply"); fit_btn.setObjectName("applyButton")
- fit_btn.clicked.connect(self._fit); lay.addWidget(fit_btn)
-
- def _add_row(self):
- r = self._tbl.rowCount(); self._tbl.insertRow(r)
- self._tbl.setItem(r, 0, QTableWidgetItem("0.0"))
- self._tbl.setItem(r, 1, QTableWidgetItem("0.0"))
-
- def _rm_row(self):
- rows = {i.row() for i in self._tbl.selectedItems()}
- for r in sorted(rows, reverse=True):
- self._tbl.removeRow(r)
-
- def _fit(self):
- pts = []
- for r in range(self._tbl.rowCount()):
- try:
- x = float(self._tbl.item(r,0).text())
- y = float(self._tbl.item(r,1).text())
- pts.append((x,y))
- except Exception:
- pass
- if len(pts) < 2:
- self._result_lbl.setText("⚠ Need ≥2 points"); return
- xs = [p[0] for p in pts]; ys = [p[1] for p in pts]
- n = len(pts)
- sx = sum(xs); sy = sum(ys)
- sxx = sum(x*x for x in xs); sxy = sum(x*y for x,y in pts)
- denom = n*sxx - sx*sx
- if abs(denom) < 1e-12:
- self._result_lbl.setText("⚠ Singular — all raw values identical"); return
- scale = (n*sxy - sx*sy) / denom
- offset = (sy - scale*sx) / n
- self._result_lbl.setText(
- f"y = {scale:.5g} × x + {offset:.5g} (R²≈{self._r2(xs,ys,scale,offset):.4f})"
- )
- self._result_lbl.setStyleSheet("color:#22c55e;")
- self.cal_applied.emit(scale, offset)
-
- @staticmethod
- def _r2(xs, ys, scale, offset):
- y_mean = sum(ys)/len(ys)
- ss_tot = sum((y-y_mean)**2 for y in ys)
- ss_res = sum((y - (scale*x+offset))**2 for x,y in zip(xs,ys))
- return 1 - ss_res/ss_tot if ss_tot else 1.0
-
-
-# ══════════════════════════════════════════════════════════════════════════════
# Pipeline Tab — filter stack + calibration for one channel
# ══════════════════════════════════════════════════════════════════════════════
@@ -272,10 +180,13 @@ class PipelineTab(QWidget):
add_bar.addWidget(add_btn)
self._inner.addLayout(add_bar)
- # Calibration
- self._cal_widget = CalibrationWidget()
- self._cal_widget.cal_applied.connect(self._apply_calibration)
- self._inner.addWidget(self._cal_widget)
+ # Apply button
+ ap_row = QHBoxLayout()
+ ap_row.addStretch()
+ ap_btn = QPushButton("✓ Apply Pipeline"); ap_btn.setObjectName("applyButton")
+ ap_btn.clicked.connect(self._push)
+ ap_row.addWidget(ap_btn)
+ self._inner.addLayout(ap_row)
self._inner.addStretch()
scroll.setWidget(self._cont)
@@ -327,23 +238,6 @@ class PipelineTab(QWidget):
if self._cur_pipeline:
self._cur_pipeline.enabled = v; self._push()
- def _apply_calibration(self, scale: float, offset: float):
- """Insert/replace a scale_offset filter at the END of the pipeline."""
- if self._cur_pipeline is None: return
- # Remove existing scale_offset
- self._cur_pipeline.filters = [
- f for f in self._cur_pipeline.filters if f.name != "scale_offset"
- ]
- cal_filt = ScaleOffsetFilter(scale=scale, offset=offset)
- self._cur_pipeline.filters.append(cal_filt)
- # Refresh rows
- for r in self._rows:
- self._filter_lay.removeWidget(r); r.deleteLater()
- self._rows.clear()
- for f in self._cur_pipeline.filters:
- self._add_filter_row(f)
- self._push()
-
def _push(self):
if self._cur_pipeline:
self.processor.set_pipeline(self._cur_pipeline)