Files
2026-10-16 18:35:02 +03:00

18 KiB
Raw Permalink Blame History

Данные на диске

Программа хранит данные в трёх местах: наборы предобработки и наборы данных — в python_app/data/ (или в каталоге, выбранном в Path), своё рабочее состояние — в python_app/runtime/, обмен между процессами — в разделяемой памяти /dev/shm. Каталоги python_app/data* и python_app/runtime в git не попадают.

Внимание. Наборы калибровки и референса в python_app/data/ — незаменимые измерения. При обновлении программы их переносит update.sh; при ручном переносе копируйте этот каталог целиком.

Где что лежит

Путь Что Кто пишет
python_app/data/s21_calibration/<radar_key>/<имя>.npz, .json наборы калибровки окно предобработки
python_app/data/s21_reference/<radar_key>/<имя>.npz, .json наборы референса, в том числе tmp_reference окно предобработки, временный референс
python_app/data/s21_object_calibration/<radar_key>/<имя>.npz, .json калибровки по объекту окно калибровки по объекту
python_app/data/preview_png/<вид>/<имя>/<radar_key>/i<вход>_o<выход>.png картинки развёрток при съёме по вариантам радара окно предобработки
<Path>/<набор данных>/ снимки и записи на диск; по умолчанию python_app/data/snapshots Save Last 1000, Save S21 JSON, Start + Record to Disk
python_app/runtime/ рабочее состояние программы (ниже) программа
/dev/shm/<имя кольца> кольца разделяемой памяти процессы конвейера
/tmp/radar_system.lock блокировка единственного экземпляра start.sh
radar.rph_radar.tty_path, например /tmp/ttyADC_data ссылка на псевдотерминал коллектора E-502 rph_radar_collector
/etc/udev/rules.d/99-radar-librevna.rules доступ к LibreVNA по USB start.sh
/etc/systemd/system/radar.service, /etc/sudoers.d/radar служба и право её перезапускать deploy/install-daemon.sh

Все файлы JSON и наборы программа записывает через временный файл и замену, а важные ещё и сбрасывает на диск (fsync). Оборванная запись оставляет прежний файл целым; от неё может остаться временный файл вида .<имя>.<32 знака>.tmp, а от оборванного сохранения набора данных — каталог .<имя набора>.<32 знака>; их можно удалить.

Наборы предобработки

Набор лежит в каталоге своего вида и ключа радара (глава Файл конфигурации) и состоит из двух файлов:

Файл Содержимое
<имя>.npz архив NumPy без сжатия: для каждой пары freq_i<вход>_o<выход> (частоты, float32) и s21_i<вход>_o<выход> (S21, complex64), плюс metadata_json — описание набора
<имя>.json то же описание в читаемом виде

Описание: collection_id, monotonic_ns, capture_start_ns, capture_end_ns, список combos (вход, выход, окно съёма, имена массивов) и metadata. У калибровки по объекту в metadata записано, при каких условиях она снята: ключ радара, геометрия, имя и хеш набора калибровки, настройки расчёта, координаты отражателя, параметры антенн, диагностика фаз и пары без поправки (unchanged_combos).

Старые наборы без metadata_json читаются по файлу .json; лишние массивы (например, S11) пропускаются.

Имя набора — любая строка, кроме пустой, ., .. и строк с / или \. Существующее имя программа не перезаписывает; исключение — tmp_reference, который заменяется при каждом съёме. Перед записью проверяется, что пары не повторяются, массивы одной длины и значения конечны.

Наборы данных

Набор данных — каталог со снимком истории (Save Last 1000) или записью на диск (Start + Record to Disk). Имя каталога — <префикс>_<Name>, например librevna_multi_0.001-6GHz_201pts_-10dBm_run_A; символы кроме латиницы, цифр, ., _ и - заменяются на _. Префикс: модель, полоса в единицах большей границы, число точек (у радиофотонного радара — adc) и мощность. Пустое имя — snapshot_<ГГГГММДД>_<ЧЧММСС> по UTC. Существующий каталог программа не трогает.

<набор данных>/
  config_profile.json          профиль, с которым сняты данные
  processing_settings.json     ревизии настроек обработки
  raw/                         сырые коллекции
    0000_id<номер>_ns<время>/
  preprocessed/                предобработанные коллекции
  results/                     результаты обработки

Коллекции внутри стадии нумеруются с нуля. Время в имени каталога — метка своей стадии, поэтому у одной коллекции оно разное в raw, preprocessed и results. Сопоставляйте стадии по паре collection_id и capture_start_ns из meta.json.

Каталог коллекции в raw/ и preprocessed/:

Файл Содержимое
i<вход>_o<выход>_freq.npy частоты, float32
i<вход>_o<выход>_s21.npy S21, complex64
meta.json collection_id, monotonic_ns, capture_start_ns, capture_end_ns, trace_count, список трасс с их окнами съёма и именами файлов

Каталог коллекции в results/ содержит meta.json (номер, время обработки, settings_revision, окно съёма, использованная скорость speed_m_s, список результатов) и массивы результатов. Результаты всей коллекции — файлы collection_<NNN>_<имя>_kind<вид>_<поле>.npy, результаты пары — в подкаталоге block_<NNN>_i<вход>_o<выход>/.

Вид Поля
1 — трасса freq float32, trace complex64
2 — число scalar float32
3 — изображение x_axis, y_axis float32, image float32 размером (строки по y, столбцы по x)
4 — таблица table float32 (строки, столбцы)
Режим Что сохраняется
pass-through для каждой пары pass_through (вид 1): частота и S21
B-scan для каждой пары bscan (вид 1): в поле freq — дальность в метрах, в trace — значение (мнимая часть 0)
GPR gpr_accumulator (вид 3): карта, оси X и Z; gpr_points (вид 4): строки [x_m, z_m, score]
3D SAR sar3d_front, sar3d_side, sar3d_top, sar3d_oblique (вид 3); sar3d_oblique_basis; sar3d_peak [уровень, X, Y, Z]; sar3d_background [отношение, 6, 8.5]; sar3d_objects [X, Y, Z, уровень] и sar3d_marks [класс, X0, X1, Y0, Y1, Z0, Z1] или sar3d_target_width [X, Y, Z]; sar3d_status [код, байты текста UTF-8]

Классы рамок 3D SAR: 0 — очень низко, 1 — на земле, 2 — над землёй, 3 — у края. Коды состояния 3D SAR: 0 — посчитано, 1 — нет сигнала, 2 — ошибка, 3 — нет CUDA.

processing_settings.json — словарь «номер ревизии → настройки обработки» в формате processing_live.json: по settings_revision из meta.json результата видно, с какими настройками он посчитан. При ручном сохранении туда попадают все ревизии текущего сеанса, при записи — только использованные. config_profile.json — полный профиль; разделы радара, ключей, пар, предобработки и геометрии взяты из конфигурации, с которой шёл сбор, даже если потом загрузили другой профиль.

Запись на диск

Start + Record to Disk создаёт каталог сразу и пишет в него коллекции, снятые после нажатия; счётчик считает результаты. Данные копятся порциями по 200 коллекций и уходят в очередь записи на 8 порций; каждая коллекция появляется на диске целиком. Если диск не успевает и очередь заполнилась, запись останавливается с Disk recording stopped because the writer could not keep up; записанное остаётся. Если сбор так и не начался и ничего не пришло, каталог удаляется.

Экспорт S21 JSON

Save S21 JSON создаёт в <Path>/<набор данных>/ по файлу на пару <имя набора>_i<вход>_o<выход>_s21_vna_bscan_history.json и профиль vna_config_profile.json. Основные поля файла:

Поле Что
format "vna-system-history-v1"
input_index, output_index, channel пара и канал ("s21")
primary_stage "preprocessed"
sweep_history список коллекций: timestamp, sweep_points (сырая развёртка, пары [re, im]), calibrated_points (предобработанная), reference_points (всегда пусто), vna_config (начало, конец, число точек)

timestamp — время конца съёма развёртки этой пары (если его нет — коллекции) в секундах монотонных часов компьютера (отсчёт от его загрузки), а не дата и время. Сырая и предобработанная развёртки сопоставляются по паре номер — начало съёма; если пары нет, массив пуст.

Как открыть данные в Python

import json
from pathlib import Path

import numpy as np

# Набор предобработки
with np.load("python_app/data/s21_reference/<radar_key>/<имя>.npz", allow_pickle=False) as archive:
    meta = json.loads(str(archive["metadata_json"].item()))
    for combo in meta["combos"]:
        frequency, s21 = archive[combo["freq_key"]], archive[combo["s21_key"]]

# Предобработанные коллекции набора данных
from python_app.storage.npz.vna_history_json import collection_directories

for directory in collection_directories(Path("<набор данных>") / "preprocessed"):
    meta = json.loads((directory / "meta.json").read_text())
    for trace in meta["traces"]:
        frequency = np.load(directory / trace["freq_file"])
        s21 = np.load(directory / trace["s21_file"])

# Результаты (например, карта GPR)
for directory in collection_directories(Path("<набор данных>") / "results"):
    meta = json.loads((directory / "meta.json").read_text())
    for payload in meta["collection_payloads"]:
        if payload["name"] == "gpr_accumulator":
            image = np.load(directory / payload["image_file"])

collection_directories возвращает каталоги коллекций по порядку номеров. Загрузить набор данных обратно в окно программы нельзя: он предназначен для обработки отдельно.

Рабочие файлы

Каталог python_app/runtime/ программа создаёт и ведёт сама.

Файл Что Можно удалить
gui_session_state.json путь последнего профиля: с него программа стартует да; программа стартует с корневого профиля. Это и способ выйти из ситуации, когда запомненный профиль не даёт программе запуститься
processing_live.json живые настройки процессора (глава Файл конфигурации) да; программа запишет его заново
run_config.json рабочая копия профиля последнего Start при остановленном сборе
s21_calibration_bundle.bin, s21_reference_bundle.bin, s21_object_calibration_bundle.bin выбранные наборы в формате для препроцессора, пересобираются при каждом Start при остановленном сборе
supervisor_children.pids номера процессов конвейера: если программа упала, следующий запуск завершает оставшиеся когда процессы конвейера не работают
logs/ журналы (ниже) при закрытой программе

Журналы

Где Что
python_app/runtime/logs/radar.log и radar.log.1 … radar.log.5 журнал программы: то же, что в окне журнала, строки дата время | уровень | источник | сообщение. Файл до 2 МиБ, хранятся пять предыдущих
python_app/runtime/logs/acquisition.out.log, acquisition.err.log вывод процесса сбора, в том числе ожидание приборов и сообщения коллектора E-502 (Collector: …)
python_app/runtime/logs/data_preprocessor.*.log, data_processor.*.log вывод препроцессора и процессора: отброшенные коллекции, отклонённый live-файл, сообщения 3D SAR (sar3d: …) и локатора (locator: …)
*.prev рядом вывод прошлого запуска процесса или начало текущего, если файл превысил 8 МиБ
journalctl -u radar.service при работе службой: вывод программы, включая строки [start.sh] …

Уровень записей задаёт logging.level профиля или список Level в окне. Подробности ошибки, показанной окном (например, трассировка вызовов), видны в окне ошибки и в окне журнала, а в radar.log попадает само сообщение. Если процесс конвейера завершился сам, отчёт с последними 16 КиБ его вывода попадает в журнал программы. Раз в 50 замеров программа пишет строку metrics: <стадия> n=… avg=…ms min=…ms max=…ms со временем сбора, обработки и отрисовки.

Разделяемая память

Кольца лежат в /dev/shm под именами из раздела rings профиля (без начальной /). При ёмкости 50 и слоте 2 МиБ кольцо занимает около 100 МиБ виртуальной памяти, но страницы выделяются только по мере записи. Кольца создаёт первый запустивший их процесс с правами 0660 и при выходе не удаляет. ./start.sh --clean-shm удаляет кольца /radar_raw, /radar_raw_tap, /radar_preprocessed, /radar_preprocessed_tap, /radar_results и те же имена с окончанием _rph_radar. Кольца с окончанием _simulator он не трогает, а их используют корневой профиль, оба профиля симулятора и оба примера с переключением стимула (run_config_librevna_multi_per_point*.example.json). Их удаляйте вручную при закрытой программе: rm /dev/shm/radar_*_simulator.

Внимание. Если программу хоть раз запустили от root (например, тесты через sudo), кольца станут принадлежать root, и обычный пользователь получит отказ в доступе. Удалите их при закрытой программе: sudo rm -f /dev/shm/radar_*.