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

147 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Общая обработка графиков и сигналов
Аппроксимация, интерполяция и восстановление кривой по редким отсчётам имеют
один расчётный API. Источник может быть временным графиком, журналом, спектром
или таблицей ручного генератора. Выбор устройства и отрисовка остаются в порте.
| Метод | Назначение | Ограничения |
|---|---|---|
| `polynomial` | Аппроксимация полиномом МНК, степени 1–5; сглаживание шумных отсчётов | Нужно не меньше `degree + 1` различных X; степень задаёт пользователь |
| `linear` | Линейная интерполяция | Минимум два различных X; изломы в узлах |
| `pchip` | Интерполяция с сохранением формы | Минимум два различных X; подходит для фронтов и монотонных участков |
| `spline` | Восстановление естественным кубическим сплайном | Минимум два различных X; возможны выбросы между узлами |
Все методы строят сетку только между крайними выбранными отсчётами.
Восстановление по малому числу точек — оценка, зависящая от метода; утраченные
высокочастотные детали не определяются однозначно. СКО считается на исходных
измерениях, поэтому нулевая СКО интерполяции не означает нулевую ошибку между ними.
Одинаковые X усредняются, нечисловые и бесконечные значения отклоняются.
## Слои и зависимости
```text
График / журнал → 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
```python
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:
```python
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()` открывает немодальное окно с выбором всех четырёх методов,
числа точек, степени полинома, расчётом, удалением и экспортом.
```python
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](SIGNAL_GENERATOR.md).
Для нового численного метода сначала расширьте C API, C-тесты и соответствие
`METHODS` в ctypes-порте; затем добавьте эталон в `test_plot_processing.py`.
Алгоритмы в обработчиках отдельных графиков не дублируются.
Проверки библиотеки:
```text
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 не требуют.