Уточнить обработку плотных трендов и восстановление сигналов
This commit is contained in:
@@ -10,6 +10,7 @@ from dataclasses import dataclass
|
||||
from protocan.legacycan import PROJECTS, LegacyCanFormat, decode
|
||||
|
||||
from .can485_board import BoardFrame, SdLogImport, parse_sd_log
|
||||
from .trend_limits import MAX_TOTAL_POINTS
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -63,7 +64,7 @@ def _values(frame: BoardFrame):
|
||||
|
||||
|
||||
def read_can_log(lines, *, reference_timestamp: float, capacity: int,
|
||||
max_total_points: int = 1_000_000,
|
||||
max_total_points: int = MAX_TOTAL_POINTS,
|
||||
frame_limit: int = 100_000) -> tuple[list[CanLogChannel], SdLogImport]:
|
||||
imported = parse_sd_log(lines, reference_timestamp, limit=frame_limit)
|
||||
channels = {}
|
||||
|
||||
@@ -9,11 +9,35 @@ are separate objects, never channels in the acquisition or measurement model.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import csv
|
||||
from bisect import bisect_left, bisect_right
|
||||
from operator import itemgetter
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from .signal_reconstruction import METHODS, reconstruct
|
||||
|
||||
|
||||
class FrozenPoints(tuple):
|
||||
"""Normalized immutable samples, reusable across processing requests."""
|
||||
|
||||
def __new__(cls, points):
|
||||
if isinstance(points, cls):
|
||||
return points
|
||||
result = super().__new__(cls, ((float(x), float(y)) for x, y in points))
|
||||
result.ordered = all(result[i - 1][0] <= result[i][0] for i in range(1, len(result)))
|
||||
return result
|
||||
|
||||
def between(self, left, right):
|
||||
if self.ordered:
|
||||
start = bisect_left(self, left, key=itemgetter(0))
|
||||
stop = bisect_right(self, right, key=itemgetter(0))
|
||||
if start == 0 and stop == len(self):
|
||||
return self
|
||||
result = tuple.__new__(type(self), self[start:stop])
|
||||
result.ordered = True
|
||||
return result
|
||||
return FrozenPoints((x, y) for x, y in self if left <= x <= right)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Axis:
|
||||
label: str = "Время"
|
||||
@@ -36,7 +60,7 @@ class Series:
|
||||
|
||||
def __post_init__(self):
|
||||
# A source may reuse mutable buffers immediately after publication.
|
||||
object.__setattr__(self, "points", tuple((float(x), float(y)) for x, y in self.points))
|
||||
object.__setattr__(self, "points", FrozenPoints(self.points))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -103,7 +127,7 @@ def prepare(snapshot, key, method="pchip", output_count=1000, degree=2):
|
||||
if snapshot.x_range is not None:
|
||||
left, right = snapshot.x_range
|
||||
selected = Series(selected.key, selected.label,
|
||||
tuple((x, y) for x, y in selected.points if left <= x <= right),
|
||||
selected.points.between(left, right),
|
||||
y_unit=selected.y_unit)
|
||||
return Request(selected, snapshot.axis, snapshot.source, method, output_count, degree, snapshot.x_range)
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ from protocan.plot import Bounds, Viewport
|
||||
|
||||
|
||||
def small_amplitude(values, threshold=30):
|
||||
extent = getattr(values, "finite_extent", None)
|
||||
if extent is not None:
|
||||
return max(abs(extent[0]), abs(extent[1])) <= threshold
|
||||
magnitude = max((abs(v) for v in values if math.isfinite(v)), default=None)
|
||||
return magnitude is not None and magnitude <= threshold
|
||||
|
||||
@@ -52,7 +55,8 @@ class DualAxisPlot:
|
||||
for series in self._series:
|
||||
if series.visible and not series.discrete:
|
||||
groups[series.key in self.right_axis_keys].extend(
|
||||
v for v in series.values if math.isfinite(v))
|
||||
getattr(series.values, "finite_extent", None) or
|
||||
(v for v in series.values if math.isfinite(v)))
|
||||
ranges = []
|
||||
for values in groups:
|
||||
low, high = (min(values), max(values)) if values else (0, 1)
|
||||
|
||||
@@ -21,7 +21,7 @@ except ImportError:
|
||||
QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
from set_devices.signal_reconstruction import METHODS
|
||||
from set_devices.signal_reconstruction import MAX_OUTPUT_POINTS, METHODS
|
||||
from set_devices.plot_processing import Snapshot, prepare, process, write_csv
|
||||
|
||||
RESULT_COLORS = ("#FF70D0", "#50D5FF", "#FFD166", "#83E377", "#B99AFF", "#FF9870")
|
||||
@@ -88,8 +88,8 @@ class SignalProcessingPanel(QWidget):
|
||||
row.addWidget(self.degree)
|
||||
row.addWidget(QLabel("Точек результата"))
|
||||
self.count = QSpinBox()
|
||||
self.count.setRange(2, 10000)
|
||||
self.count.setValue(1000)
|
||||
self.count.setRange(2, MAX_OUTPUT_POINTS)
|
||||
self.count.setValue(10000)
|
||||
row.addWidget(self.count)
|
||||
self.apply_button = QPushButton("Рассчитать")
|
||||
self.apply_button.clicked.connect(self.calculate)
|
||||
|
||||
@@ -9,6 +9,8 @@ from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from protocan.native import NativeProtocol
|
||||
|
||||
MAX_OUTPUT_POINTS = 1_000_000
|
||||
|
||||
METHODS = {"polynomial": "Аппроксимация: полином МНК", "linear": "Интерполяция: линейная",
|
||||
"pchip": "Интерполяция: PCHIP", "spline": "Восстановление: кубический сплайн"}
|
||||
|
||||
@@ -35,8 +37,8 @@ def library():
|
||||
raise ValueError("Пересоберите SETProtocol с set_signal.c и set_wavegen.c") from error
|
||||
|
||||
def reconstruct(points, method="pchip", output_count=1000, degree=2, *, endpoint=True, with_model=False):
|
||||
if method not in METHODS or type(output_count) is not int or not 2 <= output_count <= 10000:
|
||||
raise ValueError("Неизвестный метод или число выходных точек вне 2…10000")
|
||||
if method not in METHODS or type(output_count) is not int or not 2 <= output_count <= MAX_OUTPUT_POINTS:
|
||||
raise ValueError(f"Неизвестный метод или число выходных точек вне 2…{MAX_OUTPUT_POINTS}")
|
||||
count = len(points)
|
||||
if not 2 <= count <= 100000 or type(degree) is not int or not 1 <= degree <= 5:
|
||||
raise ValueError("Нужно 2…100000 исходных точек, степень 1…5")
|
||||
|
||||
5
python/set_devices/trend_limits.py
Normal file
5
python/set_devices/trend_limits.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Shared limits for host-side trend history buffers."""
|
||||
|
||||
DEFAULT_CAPACITY = 100_000
|
||||
MAX_CAPACITY = 5_000_000
|
||||
MAX_TOTAL_POINTS = 10_000_000
|
||||
Reference in New Issue
Block a user