147 lines
12 KiB
Markdown
147 lines
12 KiB
Markdown
# Общая обработка графиков и сигналов
|
||
|
||
Аппроксимация, интерполяция и восстановление кривой по редким отсчётам имеют
|
||
один расчётный 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 не требуют.
|