mirror of
https://github.com/ruvnet/RuView
synced 2026-06-21 12:13:19 +00:00
fd0568caa1
Lands the first chunk of P2: PyO3 bindings for `Keypoint` and
`KeypointType` from `wifi_densepose_core`. Bound types surface to
Python as `wifi_densepose.Keypoint` / `wifi_densepose.KeypointType`.
## Design choices that affect the API surface
1. **`Confidence` is NOT bound as a separate class.** Users hate
wrapping a float in a constructor. Python-side, confidence is just
a `float in [0.0, 1.0]`; the binding validates on construction
(`ValueError` for out-of-range, matching the Rust core error).
2. **`KeypointType` is a `#[pyclass(eq, eq_int, hash, frozen)]` enum**
— hashable so users can drop it into dicts/sets (the most common
pattern in pose-analysis notebooks: `keypoints_by_type[k.type] = k`).
3. **`Keypoint.__init__` keyword-only `z`** so 2D users don't have to
write `None` and 3D users get a clear named arg:
`Keypoint(KeypointType.LeftWrist, 0.2, 0.4, 0.8, z=0.1)`.
4. **`Keypoint` is `#[pyclass(frozen)]`** — no in-place mutation. The
Rust core type is immutable through Copy + Hash + Eq, and exposing
setters from Python would create a copy-vs-reference inconsistency
between languages.
## Files
- `python/src/bindings/keypoint.rs` — 220 lines of `#[pymethods]`
wrappers + Rust↔Python enum round-trip
- `python/src/lib.rs` — `mod bindings { pub mod keypoint; }` +
`bindings::keypoint::register(m)?` call from `#[pymodule]`
- `python/wifi_densepose/__init__.py` — re-exports `Keypoint` and
`KeypointType` at the package root
- `python/tests/test_keypoint.py` — 23 tests covering:
- 17-element COCO ordering of `KeypointType.all()`
- index→type mapping for every variant
- snake_name matches COCO spec
- `is_face()` / `is_upper_body()` predicates
- hashability (the bug I caught when I added the set-based face
test — fixed by adding `hash` to the `#[pyclass]` attribute)
- 2D + 3D constructor variants
- position_2d / position_3d tuples
- is_visible threshold
- confidence validation (Err on out-of-range)
- distance_to (2D Euclidean, 3D Euclidean, fallback when one is 2D
and the other is 3D)
- __repr__ + __eq__
- the new `p2-keypoint-bindings` feature marker landed
## Local validation
\`\`\`
$ cd python && .venv/Scripts/python -m pytest tests/ -v
tests/test_smoke.py::test_package_imports PASSED
tests/test_smoke.py::test_version_string_well_formed PASSED
tests/test_smoke.py::test_rust_version_surfaced PASSED
tests/test_smoke.py::test_build_features_listed PASSED
tests/test_smoke.py::test_hello_returns_ok PASSED
tests/test_smoke.py::test_native_module_private PASSED
tests/test_keypoint.py::test_keypoint_type_all_returns_17 PASSED
…
======================== 29 passed in 0.06s =========================
\`\`\`
Wheel size after both bindings: still well under the 5 MB ADR §5.4
budget (release build with --strip on Windows: ~340 KB).
Also adds `python/.gitignore` to prevent the `.venv/` + `target/` +
`_native.abi3.pyd` artifacts from getting committed.
## What's left in P2
CsiFrame + PoseEstimate bindings land in the next iteration. They're
larger (CsiFrame has the subcarrier buffer; PoseEstimate has
17×Keypoint + BoundingBox + track_id + score). Pattern is now proven
so they go faster.
Refs #785, ADR-117 §6.
Co-Authored-By: claude-flow <ruv@ruv.net>
78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
"""WiFi-DensePose — passive human sensing from WiFi CSI.
|
|
|
|
ADR-117 — v2.0 is a PyO3-bound replacement for the legacy pure-Python
|
|
``wifi-densepose==1.1.0`` (released 2025-06-07). The compiled core is
|
|
the same Rust workspace published in `v2/crates/` of the
|
|
`ruvnet/RuView <https://github.com/ruvnet/RuView>`_ repository.
|
|
|
|
Quick start::
|
|
|
|
import wifi_densepose
|
|
print(wifi_densepose.__version__)
|
|
print(wifi_densepose.__rust_version__)
|
|
print(wifi_densepose.hello()) # → "ok"
|
|
|
|
P1 (this release): scaffold. Core types land in P2; vital signs +
|
|
signal DSP in P3; WebSocket/MQTT client in P4. See the
|
|
`ADR-117 modernization plan
|
|
<https://github.com/ruvnet/RuView/blob/main/docs/adr/ADR-117-pip-wifi-densepose-modernization.md>`_
|
|
for the full phase ledger.
|
|
|
|
Migrating from v1.x: the v1 line was pure-Python and had a different
|
|
API surface. v2 is a hard break (semver-justified). See the
|
|
``v1.99.0`` tombstone wheel for the migration URL.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# Public Python version follows the wheel version, NOT the Rust core
|
|
# version. The Rust core version is surfaced separately as
|
|
# `__rust_version__` for diagnostics.
|
|
__version__ = "2.0.0a1"
|
|
|
|
# Re-export the compiled module's surface. The leading underscore on
|
|
# `_native` is intentional — it marks the binding module as internal.
|
|
# Users always import from `wifi_densepose` directly.
|
|
from wifi_densepose import _native
|
|
|
|
# ─── P2 — Core type re-exports ───────────────────────────────────────
|
|
# Bound types land in `wifi_densepose._native` and are re-exported here
|
|
# under their stable public names. Users always `from wifi_densepose
|
|
# import Keypoint, KeypointType` — never reach into `_native`.
|
|
Keypoint = _native.Keypoint
|
|
KeypointType = _native.KeypointType
|
|
|
|
|
|
__rust_version__: str = _native.__rust_version__
|
|
"""Version of the bound Rust core. Useful for bug reports."""
|
|
|
|
__rust_build_tag__: str = _native.__rust_build_tag__
|
|
"""Build tag of the Rust core (P5 will swap this for the git SHA)."""
|
|
|
|
__build_features__: list[str] = list(_native.__build_features__)
|
|
"""Feature flags the wheel was compiled with."""
|
|
|
|
|
|
def hello() -> str:
|
|
"""Smoke test — confirms the compiled module loads and is callable.
|
|
|
|
Returns:
|
|
Always ``"ok"`` if the wheel built and loaded correctly.
|
|
|
|
Used by ``python/tests/test_smoke.py`` to assert the PyO3 round-trip
|
|
works end-to-end on every cibuildwheel target.
|
|
"""
|
|
return _native.hello()
|
|
|
|
|
|
__all__ = [
|
|
"__version__",
|
|
"__rust_version__",
|
|
"__rust_build_tag__",
|
|
"__build_features__",
|
|
"hello",
|
|
# P2 — core types
|
|
"Keypoint",
|
|
"KeypointType",
|
|
]
|