12 KiB
Общая обработка графиков и сигналов
Аппроксимация, интерполяция и восстановление кривой по редким отсчётам имеют один расчётный 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
- Создайте attachment и добавьте его
buttonв панель графика. - После изменения данных, каналов, единиц или окна вызывайте
source_changed(). Пока панель не открывали, снимки не строятся. Уведомления одного прохода event loop объединяются; устаревшая линия сразу скрывается. - После исходных кривых вызывайте
attachment.paint(painter, analog_rect, project).project(x, y, rect) -> QPointFиспользует ту же проекцию, что исходные данные. - Вызов
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 не требуют.