added docs
This commit is contained in:
+225
@@ -0,0 +1,225 @@
|
||||
# Данные на диске
|
||||
|
||||
Программа хранит данные в трёх местах: наборы предобработки и наборы данных — в
|
||||
`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_*`.
|
||||
Reference in New Issue
Block a user