Commit Graph

174 Commits

Author SHA1 Message Date
L'électron rare 5bfc63f86d feat(viz): hand persistence gate + conf knobs
HandPersistenceGate (min_frames=3, radius=0.15) suppresses ghost
hands that flash for fewer than min_frames consecutive frames.
Gate is called once per frame in the renderer on persons_hands;
result filters both the video overlay loop and the side panels.

hand_plausible now rejects wrists outside [-0.1, 1.1] (out-of-frame
ghost anchor check).  panel_segments gains a conf_min param.

Env knobs read at renderer init (single parse, no per-frame cost):
  HAND_CONF_MIN (default 0.45, raised from 0.3)
  HAND_PERSIST_FRAMES (default 3)
  ARKIT_BONE_MAX (default 0.5, passed to arkit_segments)

New tests: 12 gate tests + 4 wrist-bounds tests.
2026-07-02 10:27:58 +02:00
L'électron rare ac6768d8a8 fix(viz): arkit bone bounds + length filter
arkit_segments now accepts max_len and bounds params.
Bones whose endpoints fall outside [-bounds, 1+bounds] or whose
length exceeds max_len are skipped (tracking-loss garbage).
Defaults: max_len=0.5, bounds=0.1.  Renderer will pass
max_len=ARKIT_BONE_MAX explicitly (Part 3).

Tests updated to use realistic bone lengths; new tests added
for the out-of-bounds and overlong-bone cases.
2026-07-02 10:27:49 +02:00
L'électron rare 287c4977a9 fix(viz): install key monitors at app launch
NSEvent keyboard monitors were placed at the END of
_start_pose_worker, unreachable: every backend path returns
early (lines 369/384/399/413/440) so monitors were never
installed. ESC, space (concertNext) and keymaps were silently
dead in all real concert paths.

Move the local + global monitor installation into
applicationDidFinishLaunching_, after window focus setup,
non-headless path only. The --multi-hmr headless early-return
(line 95) is unaffected.
2026-07-02 10:15:19 +02:00
L'électron rare b8ae1a668c fix(pose): read real smplx person dataclass
_read_sources used .get("pid")/.get("v3d") (dict API) but
multi_hmr_worker emits SMPLXPerson dataclasses (field: vertices_3d).
AttributeError was swallowed by LOG.exception at 30 Hz.

- Dual-read via isinstance check: dataclass path uses p.pid /
  p.vertices_3d / p.expression; dict path keeps p.get() unchanged
- 3 new tests: real SMPLXPerson emits OSC, expression field accepted,
  legacy dict format still works
2026-07-02 10:14:17 +02:00
L'électron rare 48b04084fa fix(viz): gate arkit overlay on 2d freshness
persons_arkit_2d_t was written by IphoneUSBSource but never
read. A USB drop left the frozen skeleton on screen indefinitely.

- Add arkit_2d_fresh(ts_by_pid, now, max_age=1.0) pure helper
  in hand_display.py (8 TDD tests, all green)
- renderer._update_skeleton: use_arkit now requires at least one
  pid younger than 1 s (replaces bool check)
- Per-pid freshness gate in draw loop skips stale pids
2026-07-02 10:13:05 +02:00
L'électron rare 0a2865e136 feat(viz): left/right hand front-view panels
CI build oscope-of / build-check (push) Has been cancelled
Add panel_rect/panel_segments/panel_frame helpers to hand_display.py
(PANEL_SIDE=0.30, PANEL_MARGIN=0.02, PANEL_INNER=0.10). Segments use
uniform pixel-space scale so the hand aspect ratio is preserved
regardless of view aspect. mirror=True flips X within the panel.

In renderer._update_skeleton add push_panel (no mirror flip) and a
panels block before the segs==0 epilogue: routes persons_hands to
left/right panel via chirality list when aligned, screen-cx fallback
otherwise. Draws frame (pid 7) + wireframe (pid 5 left / 6 right).
Runs on every path including use_arkit. Guards: _mp_bones None, empty
hands, misaligned chirality, height==0.

26 tests pass (10 new for panel helpers).
2026-07-02 09:56:08 +02:00
L'électron rare 3a3e6b113f feat(viz): hud debug panel off by default
The AV-Live data-only text HUD hid the lower-left corner of the
performance display and would collide with the new left-hand panel.
VIZ_HUD=1 re-enables it; the H key toggle guards the absent view.
2026-07-02 09:47:54 +02:00
L'électron rare ef719e76f3 feat(viz): filter hand overlay artifacts
Add hand_display.py with hand_plausible / segment_ok / hand_size pure
helpers; wire into both skeleton hands loops (plausibility gate +
per-segment confidence+spike filter + real min(A.c,B.c) push); remove
filled mesh triangles for hands from _update_mesh (HAND_TRIANGLES block
deleted).
2026-07-02 09:42:51 +02:00
L'électron rare 60640a881e feat(pose): decode hand confidence + chirality
_decode_hands now returns (hands, chirality) tuple instead of a bare
list. Landmark .z keeps the raw wire value; .c = clamp(z, 0, 1).
Chirality byte (1=right, else 0=left) is decoded per hand and stored
in State.persons_hands_chirality under the same lock as
persons_hands_iphone.
2026-07-02 09:37:24 +02:00
L'électron rare 1f19a5db96 feat(pose): pinch gate env vars + docs
CI build oscope-of / build-check (push) Has been cancelled
2026-07-02 09:18:14 +02:00
L'électron rare ea42ba4bb3 feat(pose): pinch engage debounce 2026-07-02 09:14:18 +02:00
L'électron rare 021c53f078 feat(pose): pinch open-hand extension gate 2026-07-02 09:10:06 +02:00
clement 24ce960992 fix(iphone): mirror skeleton overlay to video 2026-06-30 22:40:31 +02:00
clement 08b7c72e49 feat(iphone): render full arkit body skeleton 2026-06-30 22:14:35 +02:00
clement 86c071d0c9 feat(iphone): arkit skeleton segment builder 2026-06-30 22:09:10 +02:00
clement 04eb182fab feat(iphone): store topology + 2d validity
Add arkit_joint_names, arkit_parents, persons_arkit_2d_valid
to State. Wire TAG_TOPOLOGY=7 dispatch in IphoneUSBSource:
decode_topology stored under lock; TAG_SKELETON2D branch now
also stores per-joint validity bool array (91,) alongside pts.
2026-06-30 22:05:32 +02:00
clement b00b6dfbda fix(iphone): topology decode return type + test 2026-06-30 22:02:35 +02:00
clement fbc8b55044 feat(iphone): python topology codec 2026-06-30 21:58:42 +02:00
L'électron rare 2b7b4a81c6 docs(data-only-viz): arkit body source mode
CI build oscope-of / build-check (push) Has been cancelled
2026-06-30 20:14:17 +02:00
L'électron rare 5a9a6338a0 feat(pose): arkit body, skip mediapipe pose+face
Under --iphone-usb, skip all three MediaPipe detectors
(pose, face, hand) and rebuild the MP-33 body from
state.persons_arkit_{2d,joints} via new arkit_body_2d /
arkit_body_3d builders. Add head anchor (ARKIT_HEAD_IDX=51,
needs live verify) as MP33 slot 0. Webcam path unchanged.
2026-06-30 19:29:20 +02:00
L'électron rare c49de5c500 feat(pose): decode iphone 2D skeleton
Add TAG_SKELETON2D=6, SKEL2D_BYTES=819, decode_skeleton2D() to
iphone_usb_bridge.py; handler in IphoneUSBSource writing
persons_arkit_2d / persons_arkit_2d_t to state; new state fields
in State dataclass. TDD: test_arkit_body.py (round-trip + wrong
length → None). Import check verified.
2026-06-30 19:19:54 +02:00
L'électron rare 02f7ad42b2 feat(pose): all-fingers-curled fist metric 2026-06-30 18:22:38 +02:00
L'électron rare 16cd6a3655 fix(pose): pinch closest-finger-wins + margin
CI build oscope-of / build-check (push) Has been cancelled
2026-06-30 18:06:34 +02:00
L'électron rare 4c0794a3db fix(pose): iphone-only hands, cut mac mediapipe
CI build oscope-of / build-check (push) Has been cancelled
2026-06-30 17:32:55 +02:00
L'électron rare b3ec348378 feat(pose): pinch engage/release edges + launcher
PinchDetector now emits state=1 on engage and state=0 on release
(and on hand-disappear). send_pinch appends the state as the 4th
OSC arg so SC can distinguish hold from lift. Launcher switches
from FINGER_PIANO to PINCH_ENABLE to gate only the pinch detector.
2026-06-30 15:46:13 +02:00
L'électron rare 4bccbc1580 feat(pose): decouple pinch from air-piano gate 2026-06-30 15:40:33 +02:00
L'électron rare fc268405d2 fix(iphone): retry initial USB connect
CI build oscope-of / build-check (push) Has been cancelled
The iPhone USB source gave up permanently if the first connect_device()
failed (app not streaming on :7000 at startup -- phone locked, app not
launched/crashed, USB hiccup): start() returned False and the read thread
(which holds the reconnect loop) never ran. Auto-reconnect only covered a
mid-session drop, never a startup race.

Now start() launches the thread regardless and _run reconnects from the
top of its loop, so the initial connect retries with backoff exactly like
a mid-session drop -- the source connects whenever the iPhone app appears,
no data_only_viz restart needed.

Test: tests/test_iphone_usb_reconnect.py (start() keeps retrying when no
device). The 2 unrelated pre-existing collection errors are unchanged.
2026-06-30 09:34:23 +02:00
L'électron rare 3ecef87f4b feat(pose): emit /pose/mouth from bridge
Add PoseSoundBridge.send_mouth(pid, mouth_open) mirroring send_kin
pattern; call it in ActionHeadPublisher._tick after send_kin using
the already-in-scope `mouth` scalar; add route to module docstring.
3 unit tests (address, VJ mirror, boundary values) all green.
2026-06-29 15:07:54 +02:00
clement b8f5cc0b2e feat: thumb-finger pinch detection
PinchDetector (hysteresis + refractory, normalized by hand
size) emits /pose/pinch. Publisher picks iPhone Vision hands
when fresh (FINGER_SOURCE). 4 pinch tests.
2026-06-28 14:50:05 +02:00
clement 6a286b4f0e feat: prefer iphone vision hands for air-piano
iPhone Vision hands (on-device, .right upright, y already
top-left, rotation-invariant) are stored in a dedicated state
field and used by the strike detector when fresh (FINGER_SOURCE
auto/iphone/mediapipe). More stable than MediaPipe on the
rotated downscaled video. Adds FINGER_DEBUG strike logging.
2026-06-28 14:12:34 +02:00
clement c1b567f116 feat: VIDEO_ROTATE to rotate source frame
Rotate the iPhone/webcam frame before MediaPipe and display
so detection, skeleton overlay and the shown video stay
aligned after the phone is physically rotated. ARKit 3D
world joints are orientation-invariant and untouched.
2026-06-28 13:44:09 +02:00
clement e1cf2af339 fix: guard _vj against missing _vdmx attr
Pre-existing failure: test_pose_bridge_hands builds the
bridge via __new__, so _vj raised AttributeError on _vdmx.
Defensive getattr makes the full suite green.
2026-06-28 13:21:45 +02:00
clement 6b7cae277e feat: wire finger strikes into pose publisher 2026-06-28 13:05:21 +02:00
clement 85379be922 test: assert all send_finger osc args
Strengthen test_send_finger_emits_expected_osc to verify all seven OSC
arguments by value (z, tipx, tipy) and their types. Ensures OSC tags and
SC ~fpResolve reader get correct int/float types.
2026-06-28 13:03:10 +02:00
clement 211ee2b323 feat: emit /pose/finger over osc bridge 2026-06-28 13:00:12 +02:00
clement 2b41516a20 feat: add finger strike detector 2026-06-28 12:56:00 +02:00
L'électron rare c7f98c24af feat: spacebar advances concert morceau 2026-06-27 21:14:03 +02:00
L'électron rare a88fc5e643 feat(viz): mirror iphone video for gestures
CI build oscope-of / build-check (push) Has been cancelled
2026-06-27 21:09:23 +02:00
L'électron rare 6b6be7bff2 feat(viz): skeleton joints to SC via pose/skel
Add /pose/skel OSC route emitting nose, shoulders, hips,
knees and ankles (18 floats) from _emit_person. Add
\poseSkel OSCdef in data_feeds.scd storing the 9 joint
positions per pid; cleanup on \poseLeave.
2026-06-27 18:59:49 +02:00
L'électron rare cc0d5270cb feat(viz): mirror pose OSC to VDMX (VJ)
CI build oscope-of / build-check (push) Has been cancelled
Env-gated VDMX_OSC_HOST (+ VDMX_OSC_PORT, default 1234) forwards the
VJ-useful pose signals (/pose/hands, /pose/kin, /pose/center,
/pose/action, /pose/count) to VDMX OSC input so the body drives any
VDMX parameter. Off by default (no env = no mirror). Verified on macm1:
hands/kin/action received on the VDMX port; None without the env.
2026-06-27 16:43:42 +02:00
L'électron rare 04764b6d6f fix(viz): auto-reconnect iphone USB stream
CI build oscope-of / build-check (push) Has been cancelled
When the iPhone USB stream drops (app backgrounds, USB hiccup, decode
end), the reader thread now reconnects with capped backoff instead of
spinning dead on read()=(False,None) forever. The stale frame is cleared
on drop so read() reports no-frame during the gap (no frozen pose).
Verified on macm1: app kill -> "reconnecting" -> relaunch -> detection
resumes (body=1). Unit tests still 10/10.
2026-06-27 16:07:44 +02:00
L'électron rare 3314b58b69 fix(viz): action-head body3d kp conversion
The body3d / MediaPipe fallback feeding action-head silently failed:
getattr(kp, "x", kp[0]) eagerly evaluated the kp[0] fallback, which
raises TypeError on the non-subscriptable Kp3D dataclass, so
_kp_list_to_array returned None and every body was skipped. This
blocked /pose/kin and /pose/action for any MediaPipe-only mode (webcam
and iphone-usb), so SC body-play never saw a body (hasBody=false ->
no notes). Use attribute access for Kp3D, index fallback only for
indexable rows. Verified: /pose/kin + /pose/action now flow (97 vs 0).
2026-06-27 16:03:04 +02:00
L'électron rare 57e3a200bc fix(viz): harden iphone-usb source
CI build oscope-of / build-check (push) Has been cancelled
Fix 8 defects from whole-branch review (a79bc14..8c4d9f4):

- write_hands param: MediaPipe owns persons_hands in iphone-usb mode;
  IphoneUSBSource gets write_hands=False in multi.py
- Guard cap.start() failure: return early if USB source unavailable
- Stale frame on disconnect: read() returns (False,None) when
  _opened=False after thread exits
- ARKit validity: update only valid joints, matching OSC listener
- Decode exception: except Exception replaces removal-prone av.AVError
- release() unblock: socket.shutdown() before join wakes recv-blocked
  reader immediately
- Log stutter: remove redundant "multi —" prefix from USB log line
- Unit tests: 10 pure no-device tests for _to_annexb + _decode_hands;
  add iphone-usb optional extra (av>=12.0) to pyproject.toml
2026-06-27 15:27:06 +02:00
L'électron rare 8c4d9f4d7b feat(viz): --iphone-usb mode wiring
Add --iphone-usb flag so the pose worker reads frames from
IphoneUSBSource instead of the Mac webcam. Skip Mac-camera TCC
when iphone_usb is set; IphoneUSBSource handles its own usbmuxd
connect. MediaPipe still runs on iPhone frames; arkit_fuse refines
body joints as before.
2026-06-27 14:58:37 +02:00
L'électron rare 50772f3b9b feat(viz): ARKit skeleton + hands into State
Handle TAG_SKELETON and TAG_HANDS in IphoneUSBSource._run.
Skeleton: decode_skeleton() -> (91,3) float32 array into
persons_arkit_joints[pid] + persons_arkit_last_t[pid].
Hands: _decode_hands() parses count:u8 + per-hand chirality:u8
+ 21x(x,y,z) BE f32 into list[list[PoseKp]] for persons_hands,
compatible with HandFeatureExtractor.step().
2026-06-27 14:50:35 +02:00
L'électron rare a79bc14dc3 feat(viz): iphone USB HEVC frame source
cv2-VideoCapture-shaped IphoneUSBSource backed by the iPhone
ARBodyTracker USB stream (usbmuxd). Decodes AVLiveWire HEVC
video to 640x480 BGR frames for MediaPipe. Thread-safe read().
Skeleton and hands (Task 2) dispatched via state but not yet
wired.
2026-06-27 14:40:49 +02:00
L'électron rare 54d07aca27 feat(viz): iphone USB skeleton to OSC bridge
CI build oscope-of / build-check (push) Has been cancelled
The ARBodyTracker iOS app serves its ARKit 91-joint skeleton over the
device TCP :7000 in AVLiveWire framing, not OSC, so data_only_viz never
received it. This bridge tunnels to :7000 through usbmuxd (ListDevices +
Connect, port byte-swapped), demuxes the AVLiveWire stream (magic AVL1,
19-byte BE header), decodes SkeletonPayload (91 interleaved BE xyz floats
+ 91 validity bytes) and republishes each valid joint as OSC /body3d/kp
pid joint_idx x y z to :57128 for the arkit_fuse POSE_FILTER stage.
Verified on macm1: usbmux connect to device :7000, frames forwarded.
2026-06-27 13:57:17 +02:00
L'électron rare 32a4ef2232 fix(viz): load DETRPose config from src.core
CI build oscope-of / build-check (push) Has been cancelled
DETRPoseWorker._load_py_config imported LazyConfig from
src.misc.lazy_config, which the DETRPose repo does not expose (it lives
in src.core, as tools/inference/torch_inf.py uses it). The import raised
ImportError with no fallback, dropping into a hand-rolled exec() that
cannot resolve the lazy config's relative imports ("from .include..."),
so model loading died with KeyError "'__name__' not in globals". Try
src.core first, then the old path, before the exec fallback. DETRPose
now loads on macm1 (HGNetV2 backbone auto-downloads, no exception).
2026-06-26 16:44:35 +02:00
L'électron rare f504ad4f41 fix(viz): repair multihmr remote protocol tests
CI build oscope-of / build-check (push) Has been cancelled
The whole data_only_viz pytest run was aborted at collection because
test_multihmr_remote imported REQ_PAYLOAD_LEN, a constant that had been
dropped from multihmr_remote while RSP_PAYLOAD_LEN stayed — restore it
as the RAW-format request payload size. The test had also drifted from
the protocol: decode_request now returns a third timing field, so the
two unpack sites take (img, K, _); and the backend defaults to async
double-buffering (MULTIHMR_REMOTE_ASYNC), under which infer() returns
None when the out-queue is empty — the two loopback stub tests now pin
MULTIHMR_REMOTE_ASYNC=0 for a deterministic sync round-trip. Suite goes
from aborted-at-collection to 151 passed, 14 skipped.
2026-06-26 15:53:44 +02:00
L'électron rare cf6eb9c47e fix(viz): narrow uniform test except + lock write 2026-06-26 10:35:52 +02:00