# Данные на диске Программа хранит данные в трёх местах: наборы предобработки и наборы данных — в `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//<имя>.npz`, `.json` | наборы калибровки | окно предобработки | | `python_app/data/s21_reference//<имя>.npz`, `.json` | наборы референса, в том числе `tmp_reference` | окно предобработки, временный референс | | `python_app/data/s21_object_calibration//<имя>.npz`, `.json` | калибровки по объекту | окно калибровки по объекту | | `python_app/data/preview_png/<вид>/<имя>//i<вход>_o<выход>.png` | картинки развёрток при съёме по вариантам радара | окно предобработки | | `/<набор данных>/` | снимки и записи на диск; по умолчанию `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 знака>`; их можно удалить. ## Наборы предобработки Набор лежит в каталоге своего вида и ключа радара (глава [Файл конфигурации](configuration.md)) и состоит из двух файлов: | Файл | Содержимое | | --- | --- | | `<имя>.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**). Имя каталога — `<префикс>_`, например `librevna_multi_0.001-6GHz_201pts_-10dBm_run_A`; символы кроме латиницы, цифр, `.`, `_` и `-` заменяются на `_`. Префикс: модель, полоса в единицах большей границы, число точек (у радиофотонного радара — `adc`) и мощность. Пустое имя — `snapshot_<ГГГГММДД>_<ЧЧММСС>` по UTC. Существующий каталог программа не трогает. ```text <набор данных>/ 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__<имя>_kind<вид>_<поле>.npy`, результаты пары — в подкаталоге `block__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** создаёт в `/<набор данных>/` по файлу на пару `<имя набора>_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 ```python import json from pathlib import Path import numpy as np # Набор предобработки with np.load("python_app/data/s21_reference//<имя>.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` | живые настройки процессора (глава [Файл конфигурации](configuration.md)) | да; программа запишет его заново | | `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_*`.