wifi-densepose
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
In the compiled wheel (runs offline, no server needed):
- Vital signs — the Rust 3-stage pipeline:
CsiVitalPreprocessor(static-component suppression),BreathingExtractor(6–30 BPM) andHeartRateExtractor(40–120 BPM), each estimate carrying a confidence score and a valid / degraded / unreliable status - Pose types —
Keypoint,PoseEstimate,PersonPose,BoundingBoxfor the 17-keypoint skeletons a RuView sensing-server produces - Beamforming Feedback (BFLD) support — 802.11ac/ax/be compressed feedback matrices on top of the receiver-side CSI path
- Witness check —
wifi_densepose.verify_witness()re-runs the published deterministic pipeline proof (ADR-117 §11.3) - 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
- Small wheel — under 400 KB compiled (one binary per OS/arch covers Python 3.10+ via the stable ABI)
With the [client] extra, from a live RuView sensing-server:
- Pose, presence, person count, fall detection, motion — streamed over
WebSocket (
SensingClient) or MQTT (RuViewMqttClient) - 10 semantic primitives (HA-MIND) — someone-sleeping, possible-distress, room-active, bathroom-occupied, fall-risk-elevated, bed-exit, … — ready to wire into Home Assistant automations
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.
Quickstart
Runs as-is after pip install wifi-densepose. It feeds 30 seconds of
synthetic CSI through the Rust pipeline and prints the recovered rates
(about 15 breaths/min and 72 beats/min):
import math
from wifi_densepose import BreathingExtractor, CsiVitalPreprocessor, HeartRateExtractor
pre = CsiVitalPreprocessor.esp32_default() # 56 subcarriers, EMA static suppression
br = BreathingExtractor.esp32_default() # 0.1-0.5 Hz band, 30 s window
hr = HeartRateExtractor.esp32_default() # 0.8-2.0 Hz band, 15 s window
# 30 s of synthetic CSI at 100 Hz: 15 breaths/min (0.25 Hz) + 72 beats/min (1.2 Hz).
for i in range(3000):
t = i / 100.0
a = 10.0 + 0.5 * math.sin(2 * math.pi * 0.25 * t) + 0.05 * math.sin(2 * math.pi * 1.2 * t)
residuals = pre.process([a] * 56)
breathing = br.extract(residuals, [])
heart = hr.extract(residuals, [])
print("breathing:", breathing)
print("heart: ", heart)
The signal is synthetic; this shows the API, not accuracy on real CSI.
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})")
For heart rate, use HeartRateExtractor.esp32_default() with a 0.8–2.0 Hz
bandpass and a 15-second window. Its second argument is phases, not the
breathing extractor's weights:
hr = HeartRateExtractor.esp32_default()
est = hr.extract(residuals=residuals, phases=phases)
# If phase measurements are unavailable, phases=[] uses equal weighting.
The current binding rejects the old weights keyword with TypeError.
Heart-rate estimation requires a local autocorrelation peak; a declining
breathing-only correlation is not treated as a pulse at the band edge.
Additional filtering reduces respiratory leakage, while confidence remains
capped by evidence from the original filter stage. Synthetic regression tests
cover these cases; they do not establish accuracy on real CSI or medical use.
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(
# MQTT `+` must fill a whole topic level; the listener skips non-RuView topics.
"homeassistant/+/+/+/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 complex128 ndarray ...
fb = np.zeros((2, 1, 996), dtype=np.complex128) # 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.
Links
- Repository — https://github.com/ruvnet/RuView
- Modernization plan — ADR-117
- Home Assistant integration — ADR-115
- Issues — https://github.com/ruvnet/RuView/issues
License
MIT.
Metadata
Release files for wifi-densepose 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| wifi_densepose-2.0.0.tar.gz | 1.2 MB | Without provenance | Details for wifi_densepose-2.0.0.tar.gz |
Built distributions (wheels)
Total release size: 3.8 MB
Release files / wifi_densepose-2.0.0.tar.gz
| Download URL | wifi_densepose-2.0.0.tar.gz |
|---|---|
| Size | 1.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e9a0ac8edd859d4367354859f02ea02eca1a278ed67269dd94f7b7e7dfaf8fe0
|
|
BLAKE2b-256 checksum How to use checksums |
a094ce613b2478a2f9ea3f19c9937a5e14bbefed374cff92fb89c1f4e42afd2f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / wifi_densepose-2.0.0-cp310-abi3-win_amd64.whl
| Download URL | wifi_densepose-2.0.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 284.9 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
ab976f12bde15b9675fbc0e086347f6dff7ef849ffa53780c76ac545fd80651b
|
|
BLAKE2b-256 checksum How to use checksums |
9f7433b10245fc570e68b56966e5259e36c3ac901f212e265cbc7191b0189c93
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / wifi_densepose-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | wifi_densepose-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 439.8 kB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
bd08595380d670ac746aa17ad9c5809e28d4feea6d3b2ba0d3484833066b47ef
|
|
BLAKE2b-256 checksum How to use checksums |
df8b90f8b2c478bb510eea858bd56a858a26b184a5b56a98d0bf8065e9e3c5bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / wifi_densepose-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl
| Download URL | wifi_densepose-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 408.6 kB |
| Tags | CPython 3.10 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
6d6ffe75391865004b0ed315b5c6002368023a58d29b52b8e01927b9f0685955
|
|
BLAKE2b-256 checksum How to use checksums |
1d2939719dd4a18ba5c48275e37de84bf918dca829c106b4e9dbe6a91b474f26
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / wifi_densepose-2.0.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | wifi_densepose-2.0.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 377.9 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
6f5f30fdc4f88dd2427a7b1d5f7ece7cf0d36cf0bfa353fe28c6cdd571b9716a
|
|
BLAKE2b-256 checksum How to use checksums |
7a27dd4b5bb26999cb92e8bef5595184fc92a37d4bf552e6ff1f1f9de6c23626
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / wifi_densepose-2.0.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | wifi_densepose-2.0.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 360.3 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
92b4fc6b694d902e2165109ac1f12e29801576862dcbab821b484b153cfdab1e
|
|
BLAKE2b-256 checksum How to use checksums |
b1f5c81effe9153c19d35b00ae6fa9c28d9e43eddaf00eca5d9de22ca17826af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / wifi_densepose-2.0.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | wifi_densepose-2.0.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 342.6 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9510139b67643be8e32f8aeb2fcb1f9050bfe66e270f6141ef3885bbad15729b
|
|
BLAKE2b-256 checksum How to use checksums |
4739a72f8135450e4e466fe6ce0dcda51ba41cfa32c3e91c0e732fef19e64a77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / wifi_densepose-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | wifi_densepose-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 352.8 kB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
188654a5ce22d666800bf73867e504587f275323ad51af6692288320fcbe3e21
|
|
BLAKE2b-256 checksum How to use checksums |
9589b5ec9d08b28b71e065bb4605b68d1ee75ba4e0040c07ecc882b95a612774
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for wifi-densepose 2.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.