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

226 lines
18 KiB
Markdown
Raw Permalink 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.
# Данные на диске
Программа хранит данные в трёх местах: наборы предобработки и наборы данных — в
`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 знака>`; их можно удалить.
## Наборы предобработки
Набор лежит в каталоге своего вида и ключа радара (глава
[Файл конфигурации](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**). Имя каталога — `<префикс>_<Name>`, например
`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_<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
```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` | живые настройки процессора (глава [Файл конфигурации](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_*`.