Files
ruvnet--RuView/python
ruv 65da488add feat(adr-185): add weight-loading capability to AETHER EmbeddingExtractor (§13.a)
Closes the ADR-185 §13.a follow-up (the tractable, data-independent one of
the three §6.7 gaps): the bound EmbeddingExtractor was random-Xavier-init
only, with NO way to load real weights — so it was structurally untrained.
This adds the *capability* to load weights whenever a trained checkpoint
exists. It does NOT itself produce trained/SOTA embeddings and does NOT
close §6.7 (still no trained checkpoint, no labeled data, no eval harness).

Serialization: not greenfield — EmbeddingExtractor already had
flatten_weights()/unflatten_weights() (flat Vec<f32>). wifi-densepose-aether
is a deliberately dependency-free std-only leaf crate (ADR-185 §13), so
rather than add safetensors/serde/bincode (which would undo the zero-dep
property), the on-disk format is raw little-endian f32 with a 12-byte header
(magic "AETHERW1" + u32 param count) — zero new deps.

Native (v2/crates/wifi-densepose-aether/src/embedding.rs):
- EmbeddingExtractor::save_weights(path) / load_weights(path). load_weights
  never panics: errors on unreadable file, short/oversized payload, bad
  magic, or a param-count mismatch (delegated to unflatten_weights).
- Default (no weights) construction is UNCHANGED — still random init,
  clearly labeled untrained. Purely additive.

Python binding (python/src/bindings/aether.rs, .pyi):
- EmbeddingExtractor.load_weights(path) / save_weights(path) / param_count,
  GIL-released, ValueError on bad input.

Tests (both Rust and Python, per the task):
- Rust unit (embedding.rs): load_weights_actually_replaces_weights_and_
  round_trips proves the loaded weights MOVE the embedding away from the
  random-init baseline (not a silent no-op), match the source extractor
  (round-trip), and are bit-identical after the file round-trip; plus a
  bad-magic/wrong-count rejection test.
- Cross-language golden (aether_weights_parity.rs + test_aether.py): a
  shared deterministic weight formula (w[i]=k/65536-0.5, exact in f32+f64)
  written to the AETHER format; both native Rust and the Python binding load
  it and must produce the byte-identical embedding SHA-256
  (tests/golden/aether_loaded_embedding.sha256) — proving the binding's
  load path is bit-identical to native, and (vs the random baseline) that
  the loaded weights are actually used.

Verified:
  cargo test -p wifi-densepose-aether                       98 passed, 0 failed
  cargo test --features aether --test aether_parity
    --test aether_weights_parity                            3 passed, 0 failed
  maturin develop --features aether + pytest test_aether.py 13/13 pass
  default cargo build (no aether feature)                   clean
2026-07-21 18:20:23 -07:00
..

wifi-densepose

PyPI version Python License: MIT

Detect human presence, count people, read breathing and heart rate, and estimate skeletal pose — using only the WiFi signal already in your home.

No cameras. No wearables. Works through walls and in the dark.

wifi-densepose is the Python binding for the RuView sensing stack: a Rust core that turns the Channel State Information (CSI) emitted by ordinary WiFi chips into ambient-intelligence signals. The wheel ships compiled DSP for fast offline analysis, plus an opt-in Python client for talking to a live RuView sensing-server over WebSocket or MQTT.

Features

  • 17-keypoint pose — full-body skeletal estimate from WiFi CSI, no camera
  • Vital signs — respiratory rate (630 BPM) and heart rate (40120 BPM) with a confidence score and clinical-grade / degraded / unreliable status
  • Presence, person count, fall detection, motion — fused outputs from the same CSI stream
  • 10 semantic primitives (HA-MIND) — someone-sleeping, possible-distress, room-active, bathroom-occupied, fall-risk-elevated, bed-exit, … — ready to wire into Home Assistant or Apple Home automations
  • Beamforming Feedback (BFLD) support — 802.11ac/ax/be compressed feedback matrices on top of the receiver-side CSI path
  • GIL-releasing DSP — extract loops run with the GIL released, so a tokio-backed web server can call into the pipeline without stalling its event loop
  • Tiny wheel — ~240 KB compiled (one binary per OS/arch covers Python 3.10+ via the stable ABI)

Install

pip install wifi-densepose                 # core DSP only
pip install "wifi-densepose[client]"       # + WebSocket/MQTT clients

Wheels are published for Linux (x86_64, aarch64), macOS (x86_64, arm64), and Windows (amd64).

SOTA extras (ADR-185)

Three optional subsystems bind the Rust SOTA modules as compiled-feature wheels. Each raises a clear ImportError if you import it without the extra:

Extra Module What it adds
[aether] wifi_densepose.aether Contrastive CSI embeddings / re-identification (ADR-024) — EmbeddingExtractor, cosine_similarity, info_nce_loss
[meridian] wifi_densepose.meridian Cross-environment domain generalization (ADR-027) — HardwareNormalizer, GeometryEncoder, RapidAdaptation, CrossDomainEvaluator
[mat] wifi_densepose.mat Mass-Casualty Assessment disaster-survivor detection + START triage — DisasterResponse, Survivor, TriageStatus
[sota] all three Convenience superset
pip install "wifi-densepose[aether]"       # re-identification embeddings
pip install "wifi-densepose[meridian]"     # cross-room calibration
pip install "wifi-densepose[mat]"          # disaster triage
pip install "wifi-densepose[sota]"         # all three

Runnable examples: examples/reid_from_csi.py, examples/cross_room_calibrate.py, examples/mat_triage.py.

Usage

Extract breathing rate from a CSI stream

from wifi_densepose import BreathingExtractor

br = BreathingExtractor.esp32_default()     # 56 subcarriers @ 100 Hz, 30s window

for residuals, weights in your_csi_source:  # one frame at a time
    est = br.extract(residuals=residuals, weights=weights)
    if est is not None:
        print(f"{est.value_bpm:.1f} BPM  (confidence={est.confidence:.2f})")

Heart rate is the same shape — HeartRateExtractor.esp32_default() with a 0.82.0 Hz band-pass and a 15-second window.

Subscribe to a live sensing-server

import asyncio
from wifi_densepose.client import SensingClient, EdgeVitalsMessage

async def main():
    async with SensingClient("ws://your-ruview-node:8765/ws/sensing") as c:
        async for msg in c.stream():
            if isinstance(msg, EdgeVitalsMessage):
                print(msg.presence, msg.breathing_rate_bpm, msg.heartrate_bpm)

asyncio.run(main())

React to Home Assistant semantic primitives

from wifi_densepose.client import (
    RuViewMqttClient, SemanticPrimitive, SemanticPrimitiveListener,
)

listener = SemanticPrimitiveListener()
listener.on(SemanticPrimitive.BedExit, lambda e: print("bed exit:", e.node_id))
listener.on(SemanticPrimitive.PossibleDistress, lambda e: alert(e))

client = RuViewMqttClient(broker_host="homeassistant.local")
client.on_message(
    "homeassistant/+/wifi_densepose_+/+/state",
    listener.handle_mqtt_message,
)
client.start()
client.wait_connected()

Decode 802.11ax beamforming feedback

import numpy as np
from wifi_densepose import BfldFrame, BfldKind

# Parse compressed BFR from a Wireshark capture into a Complex64 ndarray ...
fb = np.zeros((2, 1, 996), dtype=np.complex64)  # Nr=2 Nc=1 Nsc=996 for HE80

frame = BfldFrame.from_compressed_feedback(
    timestamp_ms=ts,
    sounding_index=seq,
    sta_mac="aa:bb:cc:dd:ee:ff",
    kind=BfldKind.CompressedHE80,
    feedback_matrix=fb,
)
print(frame.n_subcarriers, frame.mean_amplitude)

Hardware

Works with any WiFi chip that exposes CSI. Reference setups (ESP-IDF firmware, build scripts, witness-verified test bundles) are in the RuView repo:

Device Cost Role
ESP32-S3 (8MB flash) ~$9 WiFi CSI sensing node
ESP32-S3 SuperMini (4MB) ~$6 WiFi CSI (compact)
ESP32-C6 + Seeed MR60BHA2 ~$15 mmWave HR/BR/presence add-on

The legacy v1 line (Wi-Pose-style FastAPI server) is end-of-life; wifi-densepose==1.99.0 is a tombstone that raises ImportError pointing to v2 with a migration URL.

License

MIT.