diff --git a/api-docs/adr/ADR-287-coherent-wideband-rf-tomography-crate.md b/api-docs/adr/ADR-287-coherent-wideband-rf-tomography-crate.md new file mode 100644 index 00000000..6b377eae --- /dev/null +++ b/api-docs/adr/ADR-287-coherent-wideband-rf-tomography-crate.md @@ -0,0 +1,67 @@ +# ADR-287: `wifi-densepose-sar` — coherent wideband RF tomography research crate + +| Field | Value | +|-------|-------| +| **Status** | Accepted — implemented (P1), **published** | +| **Date** | 2026-07-30 | +| **Parent** | ADR-278 (radar inverse rendering research program), ADR-282 (mandatory L0–L5 evidence ladder) | +| **Relates to** | ADR-273/274 (`ruview-unified`'s `FmcwRadarCube` adapter, the eventual integration point), ADR-275 (`GaussianMap`, ditto), ADR-286 (`wifi-densepose-sar-harness`, the MetaHarness minted for this crate) | +| **Published** | [`wifi-densepose-sar` v0.3.1](https://crates.io/crates/wifi-densepose-sar) on crates.io (2026-07-31) | + +## 0. PROOF discipline + +Every accuracy number this crate produces is **SYNTHETIC / evidence level L0** (ADR-282): generated by the crate's own forward simulator (`measurement::simulate_measurement`), scored against its own known ground truth (`ScatteringTarget` positions). Nothing here has been validated against real wideband RF hardware, and the crate contains no such hardware integration. + +## 1. Context + +A YC-backed company, Applied Electrodynamics ("WaveSight"), publicly launched a handheld "camera that can see through walls" using undisclosed radio-imaging technology. Comparing it against this repo's capabilities surfaced a real gap: `wifi-densepose-signal::ruvsense::tomography` implements *radio tomographic imaging* (Wilson & Patwari 2010) — RSS-based shadowing attenuation on a fixed-link topology, no coherent phase, no multi-frequency stepping, no synthetic aperture. It is a different technique from what a SAR-style through-wall imager needs: coherent, wideband, multi-position backprojection. + +`ruview-unified`'s `FmcwRadarCube` adapter (ADR-274) already normalizes wideband radar cubes into range profiles per position, and ADR-278 already names a radar-cube-output extension of the ADR-276 synthetic world generator as the intended sandbox for any future radar-inverse research. Neither, before this ADR, contained an actual backprojection reconstruction kernel — the primitive every candidate technique (matched-filter SAR, GPR imaging, RISE/DiffRadar-style inversion) is built on. + +## 2. Decision + +Ship `wifi-densepose-sar` as a standalone leaf crate (the `nvsim` pattern: pure Rust, deterministic ChaCha20 seeding, zero coupling to `wifi-densepose-hardware` or any real ingestion path) implementing: + +1. **Forward measurement model** (`measurement.rs`): simulates the complex, stepped-frequency returns a monostatic synthetic-aperture radar would record from known point scatterers — `y_{m,k} = Σ_j σ_j/R_{m,j}² · exp(-i·4π·f_k·R_{m,j}/c) + noise`. +2. **Backprojection reconstruction** (`reconstruct.rs`): the matched-filter inverse of (1) onto a 3D voxel grid, parallelized over voxels (rayon). +3. **Point-cloud extraction** (`pointcloud.rs`): threshold + local-maximum extraction from the dense voxel image. +4. **Closed-form resolution/coherence formulas** (`resolution.rs`): `ΔR = c/2B` (range resolution), `δ_CR ≈ λR/2L` (cross-range/synthetic-aperture resolution), `Δp ≤ λ/8` (antenna-pose coherence budget, derived from a quarter-wavelength round-trip-path tolerance) — checked against the reconstruction's actual behavior in `tests/physics_validation.rs`, not merely documented. + +This is deliberately scoped **one level below** ADR-278's RISE/DiffRadar/GeRaF reproduction program: it is the bare measurement-model + backprojection primitive, not a reproduction of any specific published system, and not a claim about Applied Electrodynamics' undisclosed product (their waveform, antenna count, bandwidth, and algorithm are unknown; this crate applies the same well-established SAR/GPR physics — see Skolnik, *Radar Handbook* — to synthetic data). + +## 3. What this explicitly is NOT + +- Not a hardware driver. No VNA/SDR/wideband-RF-frontend code exists anywhere in this crate or was added to `wifi-densepose-hardware`. +- Not wired into `ruview-unified`'s `FmcwRadarCube` adapter or `GaussianMap`. That integration is real future work (§5), deliberately deferred so the reconstruction physics validates in isolation first — the same staging ADR-278 §2.3 already prescribes ("sandbox-first... before hardware"). +- Not a reproduction of RISE, DiffRadar, or GeRaF. ADR-278's gates (G1–G4) are untouched by this ADR. +- Not a real-world through-wall imaging performance claim. The forward model is free-space propagation only — no multipath, no per-material attenuation, no antenna gain pattern, no receiver noise figure. Real-world performance depends on all of these. + +## 4. Simplifications (honesty boundary) + +- **Monostatic, not MIMO.** A single antenna acts as both transmitter and receiver at each synthetic-aperture position (the standard stripmap-SAR simplification), not a multi-element MIMO array. Extending to bistatic/MIMO `(m, n)` transmitter/receiver pairs is straightforward given the existing measurement-model structure but not implemented. +- **Isotropic antenna, no gain pattern.** Every antenna position radiates/receives equally in all directions. +- **Free-space propagation only.** No multipath, no material transmission/reflection/attenuation (contrast `ruview-unified::synth::room`'s Fresnel material model, which is narrowband-CW and not yet extended to wideband — a natural follow-up, §5). +- **`1/R²` two-way amplitude falloff, no calibration.** Real receivers have finite dynamic range, noise figures, and require calibration against a known reference target; none of that is modeled. + +## 5. Follow-up (not in this ADR's scope) + +1. Extend `ruview-unified::synth::room`'s image-method ray tracer to emit wideband stepped-frequency multi-position cubes (per ADR-278 §2.3), and wire `wifi-densepose-sar::reconstruct` against that richer (multipath-aware) synthetic generator instead of the free-space-only model here. +2. A `ruview-unified` integration adapter converting `ReflectivityImage`/`PointCloudPoint` output into `RfGaussian`/`GaussianMap` primitives (ADR-278 §2.4's stated integration contract). +3. Bistatic/MIMO measurement model. +4. Any of ADR-278's actual gated reproductions (RISE first), if and when that program proceeds — this crate would be a component, not a substitute. + +## 6. Consequences + +- The workspace gains a real (if intentionally scoped-down) coherent-imaging primitive where before there was none — useful groundwork for ADR-278 if that research program proceeds, and a direct, honest answer to "could this repo build a WaveSight-like device" (no, not without the hardware program described in the motivating comparison; yes, this is the reconstruction-algorithm groundwork such a program would need). +- Zero risk to the existing `wifi-densepose-signal::ruvsense::tomography` (RSS-based RTI) code path or any production pipeline — this crate is not referenced by any of them. +- 25 tests (22 unit + 3 integration physics-validation), 0 failed, clippy-clean. Criterion bench: MEASURED 512/4096/32768-voxel backprojection reconstruction throughput (see crate README for the numbers as last recorded). The incremental-phasor-rotation optimization (§7) cut reconstruction time ~4.4-4.5x, proven equivalent to the direct per-frequency computation it replaced. + +## 7. Follow-up optimization: incremental phasor rotation (2026-07-30, MEASURED) + +`focus_at_point` originally called `Complex64::from_polar` (one `sin`/`cos` pair) per (pose, frequency) term. Since [`FrequencySweep::frequencies`](../../v2/crates/wifi-densepose-sar/src/measurement.rs) produces evenly-spaced frequencies by construction, the per-term phase is an arithmetic progression in the frequency index — so the phasor can be evaluated once per pose and advanced by a fixed complex-multiply step per frequency, replacing K trig evaluations with 2. `focus_at_point`'s signature changed from a raw `&[f64]` frequency slice to `&FrequencySweep`, making the evenly-spaced-frequencies precondition this optimization depends on a type-level invariant rather than a caller-observed one. + +**MEASURED (criterion regression detection, p < 0.001): ~4.4-4.5x faster** across 512/4096/32768-voxel grids. **Proven equivalent**, not just faster: `reconstruct::tests::backprojection_incremental_rotation_matches_direct_per_frequency_computation` checks the optimized path against an independently reimplemented direct per-frequency reference, across four sweep sizes (including the `n_steps=1` degenerate case) and both on-target and off-target evaluation points, to <1e-9 relative error. + +## 8. Published (2026-07-31) + +`wifi-densepose-sar` v0.3.1 is live on [crates.io](https://crates.io/crates/wifi-densepose-sar) — `cargo add wifi-densepose-sar` resolves it from any Rust project. A MetaHarness minted for this crate (ADR-286, `wifi-densepose-sar-harness`) is published to npm alongside it. Publishing happened after this ADR's implementation and §7 optimization landed; no code changed as part of publishing itself. diff --git a/api-docs/tutorials/coherent-rf-tomography-backprojection.md b/api-docs/tutorials/coherent-rf-tomography-backprojection.md new file mode 100644 index 00000000..9215f6ea --- /dev/null +++ b/api-docs/tutorials/coherent-rf-tomography-backprojection.md @@ -0,0 +1,306 @@ +# Coherent Wideband RF Tomography: Simulating and Reconstructing with `wifi-densepose-sar` + +A walkthrough of the `wifi-densepose-sar` crate (ADR-287): simulating +synthetic-aperture radar (SAR) style measurements and reconstructing a 3D +reflectivity image from them via delay-and-sum backprojection. + +**Estimated time:** 30 minutes. + +**What you will build:** A small Rust program that simulates a handheld +stepped-frequency radar sweep past a couple of point targets, reconstructs +a 3D image from the resulting complex measurements, and extracts a sparse +point cloud from it — then verifies the reconstruction's resolution +against closed-form theory. + +**Who this is for:** Rust developers comfortable with basic signal +processing terminology (frequency, bandwidth, phase) who want to +understand what a coherent RF imaging pipeline actually computes, or who +are evaluating whether this crate is a useful building block for their own +radar-imaging research. + +--- + +## Table of Contents + +1. [What This Is (and Isn't)](#1-what-this-is-and-isnt) +2. [Prerequisites](#2-prerequisites) +3. [The Physics in Five Minutes](#3-the-physics-in-five-minutes) +4. [Your First Reconstruction](#4-your-first-reconstruction) +5. [Range Resolution: Why Bandwidth Matters](#5-range-resolution-why-bandwidth-matters) +6. [Cross-Range Resolution: Why You Need to Move the Antenna](#6-cross-range-resolution-why-you-need-to-move-the-antenna) +7. [The Antenna-Pose Coherence Budget](#7-the-antenna-pose-coherence-budget) +8. [Extracting a Point Cloud](#8-extracting-a-point-cloud) +9. [Benchmarking Your Own Scenario](#9-benchmarking-your-own-scenario) +10. [Where This Could Go Next](#10-where-this-could-go-next) +11. [Troubleshooting](#11-troubleshooting) + +--- + +## 1. What This Is (and Isn't) + +This crate exists because of a real question: could this repo build +something like [Applied Electrodynamics' WaveSight](https://www.ae-dyn.com/) +— a handheld device that images through walls using radio waves? The +honest answer, worked out in ADR-287, is **no, not as a hardware product** +— that needs a custom coherent RF front end, a calibrated antenna array, +and real-time reconstruction hardware, which is an 18–36 month, high +six-to-seven-figure hardware engineering program, not a software change. + +What *is* useful to build, and what this crate is, is the **reconstruction +algorithm** such a device needs: given coherent, phase-preserving, +stepped-frequency measurements recorded from several known antenna +positions, recover the 3D locations of the things that reflected the +signal. That's a well-understood problem (synthetic-aperture radar, +ground-penetrating radar imaging, and microwave tomography all solve +versions of it) with textbook closed-form math behind it. + +Every number in this crate comes from its own **synthetic forward +simulator** — there is no real radio hardware anywhere in this crate, and +none of its tests, benchmarks, or accuracy numbers say anything about how +well a real device would perform through a real wall. That's evidence +level **L0 (Synthetic)** in this repo's [ADR-282](../adr/ADR-282-ruview-ecosystem-positioning.md) +evidence ladder, and it stays L0 until (if ever) real wideband RF hardware +feeds this pipeline real measurements. + +## 2. Prerequisites + +- Rust 1.75+ (workspace MSRV), already set up if you can build the rest of + this repo's `v2/` workspace. +- No special hardware. Everything in this tutorial runs from synthetic + data. + +```bash +cd v2 +cargo test -p wifi-densepose-sar --no-default-features +``` + +If that passes (24 tests, 0 failed), you're ready. + +## 3. The Physics in Five Minutes + +A stepped-frequency radar sweeps `K` frequencies `f_0..f_{K-1}` across a +band of total width `B` (the bandwidth). At each of `M` antenna positions +`p_0..p_{M-1}` along a handheld sweep, it records one complex number per +frequency — amplitude and phase, not just amplitude, which is what makes +this "coherent." + +For a point scatterer at position `x` with reflectivity `σ`, range +`R = |p_m - x|` from antenna position `m`, the forward model this crate +simulates is: + +```text +y_{m,k} = sigma / R^2 * exp(-i * 4*pi * f_k * R / c) +``` + +`4*pi*f*R/c` is the two-way (round-trip) propagation phase; `1/R^2` is the +two-way free-space spreading loss. With several targets, the measurement +is just the sum of each target's contribution (superposition — this crate +never models multipath/interaction between targets, only free-space direct +paths). + +**Reconstruction (backprojection)** inverts this: for every candidate +voxel `x` in a 3D grid, it multiplies each measurement by the *complex +conjugate* of the phase the forward model would have applied for a target +at `x`, then sums: + +```text +I(x) = | (1/MK) * sum_m sum_k y_{m,k} * R_{m,x}^2 * exp(+i * 4*pi * f_k * R_{m,x} / c) | +``` + +If `x` coincides with a real target, every term's phase correction exactly +cancels the phase the forward model applied — the sum adds up +constructively ("coherent gain"). At any other voxel, the phases are +essentially uncorrelated across the `(m, k)` grid and the sum averages +toward zero. That's the entire algorithm: matched filtering, done in 3D, +one voxel at a time. + +## 4. Your First Reconstruction + +Add `wifi-densepose-sar` to a scratch binary or run this in a workspace +example. It simulates two targets, reconstructs, and finds the brightest +voxel: + +```rust +use wifi_densepose_sar::{ + backproject, linear_aperture, simulate_measurement, FrequencySweep, + Point3, ScatteringTarget, VoxelGrid, +}; + +fn main() { + // A 1-meter handheld sweep, 21 antenna positions along it. + let poses = linear_aperture( + Point3::new(-0.5, 0.0, 0.0), + Point3::new(0.5, 0.0, 0.0), + 21, + ); + + // Sweep 2-6 GHz (4 GHz of bandwidth) in 32 steps. + let sweep = FrequencySweep::new(2.0e9, 6.0e9, 32); + + // One target, 2 meters downrange, reflectivity 1.0 (arbitrary units). + let target = ScatteringTarget::new(Point3::new(0.0, 2.0, 0.0), 1.0); + + // Simulate the measurement with a touch of noise (seeded -- rerunning + // with the same seed gives byte-identical output). + let measurement = simulate_measurement(&poses, &sweep, &[target], 0.01, 42); + + // Reconstruct a 21x21x21 voxel grid around where we expect the target. + let grid = VoxelGrid::new(Point3::new(-0.3, 1.7, -0.3), 0.03, 21, 21, 21); + let image = backproject(&measurement, &poses, &sweep, &grid); + + let (peak_location, peak_magnitude) = image.peak(); + println!("true target: {:?}", target.position); + println!("reconstructed peak: {peak_location:?} (magnitude {peak_magnitude:.4})"); +} +``` + +Run it and you should see the reconstructed peak within a couple of +centimeters of the true target position — well inside the voxel spacing +used here (3 cm). That's `tests/reconstruct.rs::single_point_target_reconstructs_at_its_true_location` +running live. + +## 5. Range Resolution: Why Bandwidth Matters + +How close together can two targets be *along the same bearing* (same +antenna, different distance) before they blur into one blob? The classic +radar answer: `ΔR = c / (2B)` — resolution improves with more swept +bandwidth, full stop. Carrier frequency, antenna count, and aperture +length don't enter into it at all. + +```rust +use wifi_densepose_sar::resolution::range_resolution_m; + +let dr = range_resolution_m(4.0e9); // 4 GHz swept bandwidth +println!("range resolution: {:.1} cm", dr * 100.0); +// -> range resolution: 3.7 cm +``` + +`tests/physics_validation.rs::range_separated_targets_resolve_only_beyond_range_resolution` +proves this isn't just a formula sitting in a doc comment: it forward-simulates +two targets 4x `ΔR` apart (they resolve into two distinct peaks) and 0.25x +`ΔR` apart (they merge into one), using the *same* `range_resolution_m` +call to pick the separations. + +## 6. Cross-Range Resolution: Why You Need to Move the Antenna + +A single antenna position, no matter how much bandwidth it sweeps, cannot +tell two targets apart if they're at the same range but different bearing +— all it measures is round-trip distance, which is the same for both. This +is exactly why "handheld... sweep the antenna around" matters: moving the +antenna across a synthetic aperture of length `L` gives you angular +information, with cross-range resolution: + +```text +delta_CR ~= lambda * R / (2 * L) +``` + +— finer with a longer aperture, a shorter wavelength (higher carrier +frequency), or a closer target. + +```rust +use wifi_densepose_sar::resolution::cross_range_resolution_m; + +let short = cross_range_resolution_m(4.0e9, 0.05, 2.0); // 5cm sweep +let long = cross_range_resolution_m(4.0e9, 1.0, 2.0); // 1m sweep +println!("5cm aperture: {:.2} m cross-range resolution", short); +println!("1m aperture: {:.2} m cross-range resolution", long); +// -> a 20x longer aperture gives 20x finer cross-range resolution +``` + +`tests/physics_validation.rs::cross_range_separated_targets_resolve_only_with_long_enough_aperture` +demonstrates this end-to-end: the same pair of cross-range-separated +targets resolves into two peaks with a 1m synthetic aperture and collapses +into one with a 5cm aperture, no other change. + +## 7. The Antenna-Pose Coherence Budget + +Backprojection assumes you know exactly where the antenna was at each +measurement. If your position tracking (in a real device: visual-inertial +odometry, encoders, whatever) is off by `Δp`, the phase correction applied +during reconstruction is wrong by an amount that grows with `Δp` and with +frequency. The classical rule of thumb for "still well focused": keep the +round-trip path error under a quarter wavelength, which works out to an +antenna-position tolerance of `λ/8`: + +```rust +use wifi_densepose_sar::resolution::max_coherent_pose_error_m; + +let budget = max_coherent_pose_error_m(8.0e9); // 8 GHz carrier +println!("position tolerance at 8 GHz: {:.1} mm", budget * 1000.0); +// -> position tolerance at 8 GHz: 4.7 mm +``` + +`tests/physics_validation.rs::phase_error_from_pose_jitter_degrades_focus_beyond_pose_budget` +verifies this isn't just asserted: it perturbs the *true* antenna positions +away from the *assumed* ones used in reconstruction, and shows focus at the +true target location degrades as that perturbation grows — the concrete +mechanism behind why real SAR/GPR imaging systems need accurate pose +tracking, not just a good radio. + +## 8. Extracting a Point Cloud + +A dense voxel grid isn't a useful end product — you want a short list of +detected points: + +Continuing the program from §4 (which already has `image` in scope): + +```rust +use wifi_densepose_sar::extract_point_cloud; + +let points = extract_point_cloud(&image, 0.5); // 50%-of-peak threshold +for p in &points { + println!("{:?} magnitude={:.3}", p.position, p.magnitude); +} +``` + +`extract_point_cloud` does threshold + 6-connected local-maximum +extraction — a real blob will still yield one point, not one per voxel +inside it. There is deliberately no clustering, material classification, +or confidence calibration here (ADR-287 §5): that needs real data to +calibrate against, which this crate does not have. + +## 9. Benchmarking Your Own Scenario + +```bash +cargo bench -p wifi-densepose-sar +``` + +The shipped benchmark (`benches/backprojection_bench.rs`) sweeps 512 / +4,096 / 32,768-voxel grids with 21 poses x 32 frequencies. Reconstruction +is embarrassingly parallel over voxels (each voxel's cost is independent), +so it's rayon-parallelized already — see the crate README for the last +recorded MEASURED numbers on the reference machine. + +## 10. Where This Could Go Next + +This crate deliberately stops short of several things (ADR-287 §5): + +- It's monostatic (one antenna, both TX and RX) — real handheld SAR/MIMO + devices often use multiple simultaneous antenna elements. +- The forward model is free-space only — no multipath, no per-material + attenuation (contrast `ruview-unified`'s narrowband Fresnel material + model, which isn't yet extended to wideband). +- It isn't wired into `ruview-unified`'s `FmcwRadarCube` adapter or + `GaussianMap` — ADR-278 names that as the eventual integration point, + once (and if) a reconstruction system is ready for it. + +If you're picking this up to extend it, start with ADR-287's "Follow-up" +section rather than guessing at scope. + +## 11. Troubleshooting + +**"My reconstructed peak isn't near my target."** Check your voxel grid +actually covers the target's true location — `backproject` happily +reconstructs whatever region you ask for; if the target is outside the +grid, you'll get whatever's brightest inside it instead (usually noise). + +**"Two targets I expected to resolve didn't."** Compute +`range_resolution_m`/`cross_range_resolution_m` for your actual bandwidth +and aperture length and check your separation against them — resolution +is a hard physical limit here, not a tuning parameter. + +**"Backprojection is slow for my grid size."** Cost is +`O(voxels x poses x freqs)` and already parallelized over voxels via +rayon; the only way to go faster is fewer voxels, fewer poses, or fewer +frequency steps (each is a hard tradeoff against resolution or aperture +coverage — see §5/§6). diff --git a/api-docs/user-guide.md b/api-docs/user-guide.md index c8ca06ab..61d27411 100644 --- a/api-docs/user-guide.md +++ b/api-docs/user-guide.md @@ -135,7 +135,7 @@ The compiled binary is at `target/release/sensing-server`. ### From crates.io (Individual Crates) -All 16 crates are published to crates.io at v0.3.0. Add individual crates to your own Rust project: +The workspace's crates publish independently, so versions vary crate to crate (`wifi-densepose-core` is at 0.3.2, `wifi-densepose-signal` at 0.3.6, etc. as of this writing) — `cargo add` resolves each to its own latest by default, so you don't need to track exact numbers yourself. Add individual crates to your own Rust project: ```bash # Core types and traits @@ -161,6 +161,11 @@ cargo add wifi-densepose-wasm # WASM edge runtime (lightweight, for embedded/IoT) cargo add wifi-densepose-wasm-edge + +# Coherent wideband RF tomography research crate (ADR-287) — synthetic +# stepped-frequency backprojection reconstruction. SYNTHETIC/L0 evidence +# only; not wired into any sensing pipeline above. See its own README. +cargo add wifi-densepose-sar ``` See the full crate list and dependency order in [CLAUDE.md](../CLAUDE.md#crate-publishing-order).