Files
templates/c/set-protocol/docs/PLOT_PROCESSING.md

12 KiB
Raw Blame History

Общая обработка графиков и сигналов

Аппроксимация, интерполяция и восстановление кривой по редким отсчётам имеют один расчётный API. Источник может быть временным графиком, журналом, спектром или таблицей ручного генератора. Выбор устройства и отрисовка остаются в порте.

Метод Назначение Ограничения
polynomial Аппроксимация полиномом МНК, степени 1–5; сглаживание шумных отсчётов Нужно не меньше degree + 1 различных X; степень задаёт пользователь
linear Линейная интерполяция Минимум два различных X; изломы в узлах
pchip Интерполяция с сохранением формы Минимум два различных X; подходит для фронтов и монотонных участков
spline Восстановление естественным кубическим сплайном Минимум два различных X; возможны выбросы между узлами

Все методы строят сетку только между крайними выбранными отсчётами. Восстановление по малому числу точек — оценка, зависящая от метода; утраченные высокочастотные детали не определяются однозначно. СКО считается на исходных измерениях, поэтому нулевая СКО интерполяции не означает нулевую ошибку между ними. Одинаковые X усредняются, нечисловые и бесконечные значения отклоняются.

Слои и зависимости

График / журнал → Snapshot → Request → set_signal_reconstruct (C99) → Curve
Ручные точки генератора → waveform.generate → то же C99-ядро → таблица ЦАП
Qt-панель → worker → Curve → отдельный слой графика / CSV
МК / Android / другой GUI → C ABI → свой порт отображения или вывода
Файл Роль / зависимости
include/set_signal.h, src/set_signal.c Все четыре численных метода и квантование ЦАП; C99, без Qt, heap и файловой системы
python/set_devices/signal_reconstruction.py Тонкий вызов C через ctypes, без второго алгоритма
python/set_devices/plot_processing.py Неизменяемые модели, выбор канала и диапазона, явные единицы, CSV; без Qt
python/set_devices/qt_ports/plot_processing.py Общая панель, фоновый расчёт, проверка актуальности, слой отрисовки; PySide6 или PySide2
python/set_devices/waveform.py Периодическая сетка генератора и DAC12 через то же ядро

Перед вызовом расчёта потребитель задаёт SETPROTOCOL_LIBRARY либо устанавливает собранную библиотеку в штатный каталог protocan/native. Никакие соседние репозитории или каталоги приложений автоматически не подключаются.

Контракт графика

  • Series(key, label, points, visible, discrete, y_unit) копирует пары X/Y. Ключи уникальны внутри снимка. Расчёт доступен только видимым аналоговым каналам.
  • Axis(label, unit, encoding) явно задаёт область X. numeric сохраняет числа, unix_ms сохраняет UTC ISO timestamp. Большое число само по себе не является датой.
  • Snapshot(series, axis, source, x_range, blocked_reason) описывает снимок источника. x_range=(left, right) включает точки на обеих границах; None означает весь снимок. source должен различать файлы/источники, если переключение между ними требует сброса результата даже при совпадающих числах. blocked_reason запрещает расчёт.
  • prepare(snapshot, key, method, output_count, degree) создаёт сравнимый запрос. process(request) возвращает Curve с точками, числом измерений/уникальных X и СКО.
  • write_csv(curve, stream) сохраняет отдельный результат, подпись метода и единицы. Исходные данные не изменяются. При визуальном множителе Y адаптер передаёт отображаемые значения и указывает множитель в y_unit, например В (×2).

Нативное ядро допускает до 100000 входных и 10000 выходных точек. UI задаёт 2–10000 точек результата. После обрезки по X должно остаться достаточно узлов для выбранного метода. Расчёт не добавляет искусственные узлы на границах окна.

Пример без GUI

from set_devices.plot_processing import Axis, Series, Snapshot, prepare, process, write_csv

samples = [(0, 0), (10, 2), (20, 1), (30, 0)]
snapshot = Snapshot((Series("voltage", "Напряжение", samples, y_unit="В"),),
                    Axis("Время", "мс"), source="bench-1")
request = prepare(snapshot, "voltage", method="pchip", output_count=301)
curve = process(request)
with open("calculated.csv", "w", encoding="utf-8-sig", newline="") as stream:
    write_csv(curve, stream)

Рабочий CLI-пример для всех методов: python python/examples/plot_processing.py --method spline --output curve.csv. Добавьте templates/python в PYTHONPATH или установите пакет из этого каталога.

Подключение нового Qt-графика

PlotProcessingAttachment(parent, snapshot, repaint) принимает два callback:

def snapshot() -> Snapshot: ...  # текущие отображаемые данные и видимые границы X
def repaint() -> None: ...      # обычно QWidget.update
  1. Создайте attachment и добавьте его button в панель графика.
  2. После изменения данных, каналов, единиц или окна вызывайте source_changed(). Пока панель не открывали, снимки не строятся. Уведомления одного прохода event loop объединяются; устаревшая линия сразу скрывается.
  3. После исходных кривых вызывайте attachment.paint(painter, analog_rect, project). project(x, y, rect) -> QPointF использует ту же проекцию, что исходные данные.
  4. Вызов open() открывает немодальное окно с выбором всех четырёх методов, числа точек, степени полинома, расчётом, удалением и экспортом.
self.processing = PlotProcessingAttachment(self, self.processing_snapshot, self.update)
self.toolbar.layout().addWidget(self.processing.button)
# после обновления источника/масштаба:
self.processing.source_changed()
# внутри paintEvent, после исходных линий:
self.processing.paint(painter, self.analog_rect(), self.project)

Для встроенной панели используйте SignalProcessingPanel.set_snapshot(snapshot) и сигнал changed для перерисовки. Передавайте panel.curve слою через set_external_curve(curve, x_offset=0). В этом варианте владелец обновляет снимок и слой вместе. x_offset используется только при рисовании, например при сдвиге epoch на графике наносекундного масштаба; исходные X и CSV сохраняются.

Панель сравнивает снимок выбранного канала, единицы, источник, диапазон и параметры. При изменении результат убирается, запоздалый ответ worker игнорируется. Для обработки живого потока остановите его обновление. Y-масштаб не меняет запрос, если X и данные остались прежними. Кривая обрезается текущей областью Y; при выбросе сплайна можно увеличить диапазон Y. Панель не меняет историю, autoscale, курсоры, FFT и цифровые дорожки графика.

Порты и расширение

SETGUI использует компонент в «Логах и графиках», SignalPlot (CAN, температуры, УМП), TrendPlot и SpectrumPlot. В спектре методы обрабатывают зависимость уровня от частоты в Гц; восстановление временного сигнала из амплитудного спектра этим не выполняется. Временная панель «Логов» блокируется при включённом FFT. Генератор использует те же методы через waveform.generate; для циклической таблицы последний отсчёт периода не дублируется.

Для нового графика достаточно адаптера Snapshot и существующей панели/слоя. Для другого GUI используйте модель без Qt или непосредственно C ABI. Для MCU вызывайте set_signal_reconstruct с буфером 14 * count + 128 double, выделенным вызывающей стороной; рабочая память должна соответствовать RAM платы. Контракт вывода ЦАП и готовые порты F407/G474 описаны в SIGNAL_GENERATOR.md.

Для нового численного метода сначала расширьте C API, C-тесты и соответствие METHODS в ctypes-порте; затем добавьте эталон в test_plot_processing.py. Алгоритмы в обработчиках отдельных графиков не дублируются.

Проверки библиотеки:

python -m unittest discover -s python/tests -p test_plot_processing*.py
python -m unittest discover -s python/tests -p test_shared_library_boundary.py

Для тестов Qt нужен PySide6/PySide2; для headless-прогона задайте QT_QPA_PLATFORM=offscreen. Численные тесты test_plot_processing.py Qt не требуют.