18 KiB
Данные на диске
Программа хранит данные в трёх местах: наборы предобработки и наборы данных — в
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_*.