Refactor radar system

This commit is contained in:
Ayzen
2026-10-05 16:46:28 +03:00
parent 515049d2ed
commit 8f097dfa2e
179 changed files with 8212 additions and 6676 deletions
+91 -24
View File
@@ -44,8 +44,6 @@ controls plus a pixel-identical live plot through an embedded web UI.
- **librevna** — single LibreVNA.
- **librevna_multi** — synchronized multi-device LibreVNA.
- **compact_m_k209** — Compact-M K209 / S2VNA (including Raspberry Pi remote mode).
- **sn9000** — SNVNA SN9000 over VISA (pure-Python `@py` backend).
- **kamil_adc** — external Kamil ADC acquisition process, with optional laser control.
- **mock / simulator** — driver-less mode for development without hardware.
@@ -56,13 +54,28 @@ controls plus a pixel-identical live plot through an embedded web UI.
- **gpr** — coherent back-projection GPR imaging with object detection.
- **legacy_gpr** — ellipse-intersection MIMO localization (point and extended/region modes).
- **sar3d** — 3D SAR of the reference notebook `Multi_antenna_3D_clean_v1.ipynb`
on an NVIDIA GPU: front (X-Y), side (Z-Y), top (X-Z) and 3D views of the imaged
on an NVIDIA GPU through CUDA: front (X-Y), side (Z-Y), top (X-Z)
and oblique maximum projections of the imaged
volume. Its settings carry the notebook's parameter names; the scene window is
fixed (X −2…2 m, height −0.5…2 m above the ground), its range extent follows
`MIN_DEPTH`/`MAX_DEPTH`, and the voxels are cubes with edge `SAR_VOXEL_M`
(default 2 cm). The radar motion is corrected from each trace's capture
timestamps, so the sweep timing needs no parameters.
For matrix-radar B-scans, **Next combination** advances output first, then input,
and wraps to the first pair. The desktop fields and browser channel selector
redraw the selected cached B-scan immediately without restarting acquisition.
**Save Last 1000** and **Save S21 JSON** use a fixed limit of 1000 recent
measurements from each available stage; older GUI profiles may still contain
`save_count`, which is ignored. **Start + Record** retains its separate count for
recording future measurements to disk.
Tx/Rx antenna positions are edited in tables with switch position and X/Y/Z in
metres, on both the desktop and web interface. Coordinate and legacy-GPR medium
changes apply to the running processor and recompute its retained result. Profiles
preserve all three coordinates. GPR/SAR views wait for a result marked with the
current settings revision, so an older in-flight result cannot overwrite new geometry.
## Repository layout
| Path | Contents |
@@ -72,22 +85,26 @@ controls plus a pixel-identical live plot through an embedded web UI.
| `run_config_examples/` | Ready-made configuration profiles, one per hardware setup. |
| `deploy/` | systemd daemon installer, and the USB update kit for the Pi and Jetson (`pack-usb.sh`, `update.sh`). |
| `build/bin/` | Compiled C++ binaries. |
| `device_firmware/`, `docs/` | Device firmware and vendor manuals. |
| `device_firmware/` | Device firmware and protocol documentation. |
## Requirements
- Linux (POSIX shared memory; GPIO on Raspberry Pi). The deployment target is a
**Raspberry Pi 5**; hardware PWM gating for the generator sweep is Pi 5
specific (GPIO12/13/18/19, `dtoverlay=pwm-2chan`).
- A C++20 compiler and `make` — Eigen and nlohmann/json are vendored, so no extra C++ dependencies are needed.
- A C++20 compiler and `make`; Eigen and nlohmann/json are vendored.
- `libusb-1.0` development headers and `libatomic.so.1` (`libatomic1` on Debian/Ubuntu).
Python ring readers/writers use lock-free atomic loads, stores and cursor CAS
through libatomic, including memory barriers for ARM. Slot sizes must be multiples
of 8 bytes, and the complete ring mapping must not exceed 1 TiB.
- **Python 3.10 or newer**, in a virtual environment.
- For the `sar3d` processor: an NVIDIA GPU with its driver (`libcuda`). The
- For `sar3d`: an NVIDIA GPU with its driver (`libcuda`). The
kernels are compiled at run time by NVRTC, so there is no nvcc step; `make`
needs only `nvrtc.h`, `cuda.h` and `libnvrtc`, taken from the pip
`nvidia-cuda-*` packages in `.venv` (`requirements.txt`, x86_64; CUDA 13 covers
Turing and newer GPUs) or from a system toolkit in `/usr/local/cuda` (Jetson).
Without them everything else still builds, and sar3d reports that CUDA is
missing — a Raspberry Pi needs none of it.
`nvidia-cuda-*` packages in `.venv` (`requirements-cuda.txt`, x86_64) or from
a system toolkit in `/usr/local/cuda` (Jetson).
Without these dependencies or a usable GPU, SAR3D shows a warning above the
plots and does not calculate an image. Other processing modes remain available.
## Build and run
@@ -103,6 +120,8 @@ make # build the C++ binaries into build/bin/
`make` builds the sar3d GPU code when it finds CUDA (see Requirements) and
prints a note when it does not. After installing the CUDA requirements into an
existing venv, run `make` again: it notices the change and rebuilds that part.
For SAR3D on x86_64, install the optional packages with
`.venv/bin/python -m pip install -r requirements-cuda.txt`, then run `make`.
Run the desktop GUI:
@@ -153,17 +172,27 @@ Two ways to run the full pipeline with no radar attached:
**Mock producer (synthetic data).** The default `run_config.json` already sets
`radar.driver_mode: "mock"`, so the producer generates synthetic S-parameters.
The preprocessor still requires S21 calibration/reference sets; create them once:
Mock acquisition supports the LibreVNA models; Kamil ADC uses native acquisition
or recorded-dataset replay without devices.
Both LibreVNA modes share one simulator: a point reflector moves back and forth
along the antenna array and between depths 3.25–4.25 m over an eight-second cycle.
Each S21 uses the actual transmitter–target–receiver distance from the configured
XYZ coordinates; coincident antennas produce identical signals. All combinations
share the same target position and measured capture window, with collections paced
at 50 ms intervals. B-scan's default range is 0–8 m; stored profiles retain their
chosen range. The simulator models a stationary radar, so radar speed should be zero.
The scene uses the antenna positions from its startup profile; live geometry edits
change the reconstruction of captured sweeps, whose samples remain unchanged.
The preprocessor still requires S21 calibration/reference sets. Start the GUI:
```bash
.venv/bin/python -m python_app.scripts.manual_smoke_run --duration 1.0 # creates smoke_cal/smoke_ref
./start.sh # then in the GUI: Preprocessing Panel -> pick the sets -> Start
./start.sh
```
(Alternatively use the GUI's "Create neutral sets" button, which makes
identity calibration + zero reference so preprocessing is a pass-through.)
In **Preprocessing Panel**, use **Create neutral sets**, then **Start**. This makes
identity calibration + zero reference, preserving the simulated S21 samples.
**Dataset replay (recorded scene, meaningful sar3d picture).** When
**Dataset replay (recorded scene).** When
`radar.replay.dataset_dir` is set in the profile, the supervisor launches
`python_app.scripts.replay_raw_producer` instead of the normal producer: it
replays recorded snapshots into the raw ring at the recorded cadence, looping
@@ -179,15 +208,32 @@ A ready profile for the VNA_AlGo tank005 dataset is provided:
./start.sh --profile run_config_examples/run_config_replay_vna.example.json
```
The sar3d processor keeps about 75 MB of buffers on the GPU at the default
2 cm voxel and depth 2…8 m (plus the CUDA context). The volume grows as
1/`SAR_VOXEL_M`³: 1 cm takes about 600 MB and ran about 6 times slower in
testing. If the GPU has no room, it logs the CUDA error once and publishes empty
results until memory frees.
Finer voxels increase SAR3D work as 1/`SAR_VOXEL_M`³. Increase the voxel size when
a faster preview matters more than spatial resolution. Processing failures appear
above the plots and a failed single capture
finishes with its explanation while retaining the acquired data.
The web interface is served in-process at `http://<host>:8080`. It mirrors the live
plot and offers Start / Stop / Capture controls and the active processor settings.
Manual dataset and JSON export keep the last 1000 available collections from each
stage independently, including raw or preprocessed sweeps without a result.
Streaming recording keeps every collection received after arming until the requested
result count or Stop; Stop and closing the window flush accepted data. Acquisition
rings use latest-wins delivery, so disk recording cannot recover sweeps overwritten
before the GUI receives them. NumPy datasets preserve both S-parameters, capture
windows, every supported result payload, and processing time. Failed exports remove
their staging files, leaving existing datasets intact.
Calibration and reference sets keep their trace arrays and metadata together in one
atomically replaced NPZ archive, with a readable JSON sidecar. Earlier NPZ/JSON pairs
remain supported. Invalid trace shapes, duplicate combinations and non-finite
calibration/reference values are rejected before a saved set is replaced.
Median captures also require identical frequency axes and channel coverage.
Dataset replay restores both recorded S-parameters and orders long recordings by
their numeric index; older S21-only datasets still replay with zero S11. Offline
JSON conversion and the GUI share the same sweep-matching code.
## Configuration
The active profile is `run_config.json`. It selects the radar model and sweep, the
@@ -217,13 +263,34 @@ the current version is renamed to `radar_system<N>`, the next free number. Kept
it: `run_config.json`, profiles in `run_config_examples/` changed or added on the
device (the release version of a changed one lands next to it as `.new`),
`python_app/runtime/` without logs, `python_app/data*` and any other top-level
folder that is not part of the release, as hard links. Then it carries the venv over
(or builds a new one with `--python`), builds the C++ part and restarts
file or folder that is not part of the release, as hard links. The venv is copied
independently so package updates cannot modify the rollback environment (or a new
one is built with `--python`). It builds the C++ pipeline and ADC collector and restarts
`radar.service` if it was running. To go back:
`rm -rf radar_system && mv radar_system<N> radar_system`.
## Tests
```bash
.venv/bin/python -m unittest discover -s python_app/tests
QT_QPA_PLATFORM=offscreen .venv/bin/python -m unittest discover -s python_app/tests
make check-native
make check-sar3d PYTHON=.venv/bin/python
ASAN_OPTIONS=detect_leaks=0 make check-native NATIVE_SANITIZERS=address,undefined
```
The tests cover failed writes and settings commits, recordings during manual Save,
repeated Start/Stop/Close, web-action cancellation, malformed IPC frames, hardware
resource cleanup with mock failures, and an isolated deployment update. Native tests
also exercise mutated frames, ring concurrency, locator input and ADC spool recovery.
Firmware host tests check cart timer rollover and SBUS packing; they do not validate
physical timing. Real devices, GPIO and the CUDA backend need their own hardware run.
Invalid device/switch settings and undersized raw/preprocessed slots fail before
acquisition starts. Oversized processed results report the required slot size in
the log; switching to a mode that fits resumes processing without a restart.
GUI profiles, run configs, session state and live processing settings use shared
atomic writers with unique temporary files. Profiles and run/session settings sync
their file and directory; frequent live settings avoid extra disk syncs. A failed
write before replacement leaves the previous file intact. The launcher and supervisor
share acquisition producer selection, with replay taking precedence over the model.