Commit Graph

250 Commits

Author SHA1 Message Date
L'électron rare b6cd8cf4c7 docs: RC0.1+ architecture + env vars
README header pinned to v0.1.0-rc1. New section "RC0.1+
distributed Multi-HMR architecture" with ASCII diagram of the
M5 capture host <-> macm1 compute host pipeline (TCP :57140
pyobjc CoreML, JPEG q80, async double-buffer, MeshRigger 27 fps
perceived) and the unified 3D armature wireframe on macm1
AVLiveBody.

Env var reference table : MULTIHMR_BACKEND, MULTIHMR_REMOTE_*,
MULTIHMR_SERVER_BACKEND, MULTIHMR_LOOP_FPS, AVBODY_HOST,
MEDIAPIPE_DELEGATE, POSE_FILTER, MULTIHMR_REID*.

Root CLAUDE.md ; extended with the same env vars + file
locations for mesh_rigger, Skeleton3DRenderer, pose_filter,
dino_reid, multihmr_server, scene.metal.

Observed ceilings under MLX contention on macm1 : 25 fps loop,
9 fps fresh predict, 27 fps perceived via the rigger.
v0.1.0-rc1
2026-05-14 02:46:32 +02:00
L'électron rare bd46f6e052 perf(multi-hmr): lift self-throttle + fresh fps
Two changes that take loop fps from 9.8 to 20+ without touching
the predict cost :

1. MultiHMRWorker default target_fps 10 -> 30. The Python loop was
   self-capping at 10 fps via sleep(period - dt) regardless of the
   backend. With the remote async pipeline (queue maxsize=2) the
   actual fresh-predict rate stays at the macm1 server ceiling
   (~9 fps under MLX contention), but the loop now submits frames
   at 30 Hz and re-emits MeshRigger-interpolated state at the same
   cadence -- the mesh becomes visually fluid at the loop rate even
   when fresh predicts are slower. Tunable via MULTIHMR_LOOP_FPS.

2. Short-circuit reused-humans : when infer() returns None (queue
   empty), skip dedup/tracker/One-Euro reprocessing and just sleep
   to the next slot. Saves ~3-5 ms CPU per idle iteration and
   avoids advancing the smoother on identical state.

Heartbeat log extended with (fresh=Y) so we can distinguish loop
rate (visualiser feed) from real predict rate. Bench remote macm1
under MLX contention : hb[coreml]: 25.0 fps (fresh=9.0).

Root cause for the predict gap 53 vs 87 ms : MLX servers
mlx_lm.server :8502/8503 + LoRA on macm1 burn ~30 ms of unified
memory bandwidth per Multi-HMR predict. Not a software fix.
2026-05-14 02:43:31 +02:00
L'électron rare 1828d7ca3e feat(av-live): scenes react + finger joints
Two new capabilities on top of RC0.1 :

1. Pose drives Metal scenes via 12 new SceneUniforms (144 bytes total,
   36 floats): mouth_open, eye_open_l/r, head_tilt, head_yaw,
   finger_pinch_l/r, body_x/y/z, body_height, arm_spread, pose_velocity.
   BodyView.updateNSView derives them from PoseOSCListener.faces /
   hands / body3d with EMA smoothing (alpha=0.7). Five scenes wired :
   storm (velocity+tilt), plasma (mouth+yaw), kaleido (arm_spread
   segments), starfield (pinch -> star density), bars (height+eye).

2. Multi-HMR mlpackage extended with 6th output : 127 SMPL-X joints
   (4, 127, 3) incl. 30 finger joints (indices 25-54). TracedMHMR
   stacks the joints already computed in smpl_layer.py. Output
   names shifted (var_2420 etc.); constants updated in
   multihmr_coreml.py and multihmr_server.py. Propagation to
   client backends + Skeleton3DRenderer finger override remains TODO.
2026-05-14 02:31:10 +02:00
L'électron rare 67302e7ca2 perf(av-live): pyobjc + GPU MediaPipe + queues
Three perf optimizations stacked on top of the distributed pipeline:

1. multihmr_server.py ported to pyobjc CoreML.framework direct
   (drops the ~30 ms coremltools.MLModel.predict overhead). Compiles
   the mlpackage to .mlmodelc on load, then predicts via
   MLDictionaryFeatureProvider + MLMultiArray ctypes memcpy. Toggle
   MULTIHMR_SERVER_BACKEND=coremltools for fallback. setup script
   installs pyobjc-framework-CoreML on the macm1 venv.

2. MediaPipe Holistic now uses GPU Metal delegate on M5. Required
   wrapping camera frames as SRGBA (4-channel) instead of SRGB --
   the Metal CVPixelBuffer path rejects 3-channel formats. Bench
   M5 standalone : pose 6.7 -> 2.9 ms, face 4.0 -> 1.0 ms, hand
   6.1 -> 3.2 ms. Frees ~10 ms CPU per frame for OSC + rigger.

3. Remote client queues bumped maxsize 1 -> 2/3 (in/out) to absorb
   jitter without stalling capture.
2026-05-14 02:17:29 +02:00
L'électron rare 9838da3137 feat(av-live): distributed Multi-HMR + fused mesh
Multi-HMR inference offloaded to a remote macm1 (M1 Max, 32-core
GPU) via a custom TCP protocol on :57140. JPEG q=80 compression
(~80 KB vs 1.35 MB raw) + double-buffer async pipeline (3-thread
server reader/worker/writer, single-buffer client) overlaps net
I/O with predict. Live: 9.8 fps Multi-HMR keyframe (vs 6.8 fps
M5 local CoreML), 27 fps perceived via MeshRigger.

Distributed display option : AVBODY_HOST env routes the TCP mesh
and UDP OSC streams to a remote AVLiveBody. The Swift launcher
runs on macm1 in the user GUI session (via osascript do shell
script over SSH), receiving the full mesh+skeleton+face+hand
stream from M5 capture.

Skeleton3DRenderer now bundles body 33 joints + dlib 68 face
landmarks + 21x2 hand landmarks into a single procedural
LowLevelMesh (line topology) per person : 143 vertices, 288 line
indices, 4 draw calls. Anatomical connectors body nose -> face
nose bridge and body wrists -> hand wrists.

MediaPipe delegate is selectable via MEDIAPIPE_DELEGATE=gpu|cpu
(default cpu after observed Metal GpuBuffer crashes on macOS).

Multi-HMR ViT-L checkpoint also tested : 350 ms on macm1 GPU
(2.9 fps) -- too slow to ship, ViT-S remains default. Conversion
script generalized via MULTIHMR_CKPT_NAME env.
2026-05-14 02:02:33 +02:00
L'électron rare 91f4a46ceb feat(av-live): wireframe skel + face/hand filter
Skeleton3DRenderer now renders a wireframe: joint radius 1 mm
(quasi-invisible), bone radius 3 mm (line-like). Replaces the
chunky bead armature with a clean filaire silhouette covering
body 33 joints + face 68 dlib + hands 21x2, all 3D.

FaceHandOverlay 2D Canvas removed from ContentView -- face and
hand landmarks now live in the same 3D RealityKit armature as
the body skeleton (Skeleton3DRenderer.applyFace / applyHands,
anchored on nose joint 0 + wrist joints 15/16).

pose_filter.py extended with FaceFilterChain (alpha-beta + 30 ms
lookahead) and HandFilterChain. multi.py wires them after the
2D smoothers, plus ghost rejection (POSE_GHOST_MIN_VISIBLE),
bbox NMS (POSE_NMS_IOU), and pid hysteresis. 10 new tests, all
green.

CoreML perf audit (bench_multihmr_coreml.py): predict() = 99% of
wall-time on FP32. ANE catastrophic for DINOv2 (1300 ms),
INT8 weight quant = no live gain (GPU compute-bound).
6.4-6.8 fps live is the hardware ceiling on this model.
quantize_multihmr_int8.py left in scripts/ for future trials.
2026-05-14 01:06:27 +02:00
L'électron rare c4bc5c2e5a fix(av-live-body): restore face+hand+3D (f540158)
Three regressions after recent merges, all restored to match the
original f540158 design:

1. FaceHandOverlay was no longer instantiated in ContentView.
   Added back as a SwiftUI Canvas overlay (68 dlib face landmarks
   with mouth slots 48-67, plus 21x2 hand landmarks cyan/magenta).

2. Skeleton3DRenderer was not attached. BodyView now creates an
   AnchorEntity at (0,0,-2.5), instantiates Skeleton3DRenderer
   and ties its visibility to vizMode==9 or showSkeleton toggle.

3. Joint and bone radii bumped to 4.5 cm / 2.2 cm so the 3D
   skeleton actually reads as 3D instead of looking flat.

MeshRenderer exposes pelvisWorld map per pid for future
interconnect uses (not auto-applied -- design keeps mesh and
skeleton each in their own coord space per f540158).
2026-05-14 00:45:31 +02:00
L'électron rare 7ed2e2764a feat(av-live): openpos 3D + DINO reid + filter
Three improvements wired end-to-end:

1. Openpos 3D skeleton visible: Skeleton3DRenderer attached to a
   RealityKit AnchorEntity in BodyView, toggled by showSkeleton
   or vizMode==9. PoseOSCListener now parses /pose3d/count and
   /pose3d/kp (plus restored /face/* and /hand/* paths).

2. DINO re-id (dinov2_vits14, ~9 ms ANE forward):
   MeshRigger combines Hungarian IoU with cosine similarity over
   a per-pid embedding history (deque maxlen=10), weighted by
   MULTIHMR_REID_ALPHA (default 0.5). Falls back to pure IoU if
   DINO mlpackage absent or scipy missing. state.last_frame_rgb
   buffer added so the rigger can crop bbox regions for embedding.

3. PoseFilterChain on pose_world_landmarks:
   median (anti-spike) -> Kalman constant-velocity ->
   50 ms lookahead -> IK elbow/knee/ankle clamp. Configurable
   via POSE_FILTER env (default median+kalman+lookahead+ik).
   <2 ms per frame for typical 1-2 persons.

Tests: 5 new in test_dino_reid.py + 6 new in test_pose_filter.py,
all green. Live validated by user: skeleton spawns, mesh stays
stable.
2026-05-14 00:30:42 +02:00
L'électron rare aad56e5bf9 docs(plans): action-head v3 + branch sync notes
Update plan header :
- v2 (Task 18) + v3 (Task 19) extensions chronology
- Studio train validated, ckpt action_head_v3.pt landed
- Mesh NaN-guard debug trail (commit 4e7101c)
- Branch convergence main == feat/action-head
- Pointers to memories project_action_head_v3, etc.
2026-05-14 00:14:13 +02:00
L'électron rare 06f2a55f08 fix(coreml): FP32 mlpackage + Hungarian pid
CoreML mlpackage reverted to FLOAT32 compute_precision: the FP16
build, even with the roma branchless rotmat patch, visibly
degraded the mesh on extreme poses (atan2 near theta=pi + SMPL-X
decoder drift). FP32 stays 2.3x faster than PyTorch MPS (139 ms
vs 270 ms; 8 fps live with 3 workers).

MeshRigger pid matching now uses Hungarian assignment on bbox IoU
with sticky cache (new_iou >= 0.30 AND old_iou < 0.15 to switch).
Replaces fragile pelvis-distance heuristic. 5 new tests pass.
2026-05-14 00:01:03 +02:00
L'électron rare 4e7101c54e fix(multi-hmr): NaN/Inf guard on v3d output
Multi-HMR PyTorch MPS sort occasionnellement des v3d avec NaN/Inf
ou vertices a magnitudes extremes (>5 m). Ces frames glissaient
sans guard jusqu'a AVLiveBody et apparaissaient comme pics/trous
sur le mesh.

Approche : np.isfinite check sur v3d + transl_np, skip person si
invalide. Plus sanity clamp sur |v3d|.max() > 5.0 m : humains ~2 m,
au-dela = garbage du modele, skip aussi. Le receiver Swift garde
le dernier good mesh pendant les frames skip via retain_window
500 ms (dc7de90).

Tradeoff : on perd la frame entiere du pid au lieu de juste les
verts NaN. Acceptable car corruption est globale au tensor v3d
quand elle arrive (jamais juste 2-3 verts isoles).
2026-05-13 23:59:35 +02:00
L'électron rare 87b76a42d0 feat(data-only-viz): MESH_RIG=0 env toggle
Permet de desactiver le rigger 30 fps (mesh_rigger.py, ajoute en
2c8094c) via env var MESH_RIG=0 pour debugger les deformations
mesh sans changer le code.
2026-05-13 23:53:48 +02:00
L'électron rare 52588b9910 fix(coreml): roma branchless rotmat -> rotvec
Root cause of v3d/transl NaN identified: roma.rotmat_to_rotvec
uses torch.empty + 8 index_put_ on a buffer that CoreML mlprogram
translates as scatter_nd over a garbage-initialised tensor. Cells
that the scatter chain misses keep NaN; the subsequent quat /
norm propagates NaN to every vertex.

Patch: branchless atan2 formulation (stack/clamp/norm/atan2 only),
no torch.empty, no index_put_. Precision drift vs roma original:
2.26e-6 L_inf on random batches. Mlpackage now validates all
outputs finite (1.27e-4 L_inf vs PyTorch eager on v3d).

Bench standalone: 65 ms median FP16 (15.3 fps, target met).
Live with 3 parallel workers: 8 fps Multi-HMR keyframe rate
(2.3x speedup vs PyTorch MPS baseline 3.5 fps); rigging still
ships at 15-20 fps perceived.

Output names shifted post-patch (var_2541 -> var_2412 etc) so
multihmr_coreml.py constants updated.
2026-05-13 23:45:20 +02:00
L'électron rare 4717da385c feat(sound-algo): action-head live FX scene
Add scene_pose_action.scd to map real-time action-head pose metrics
to live effect parameters. Reads ~poseState and ~poseKin dicts
(populated by data_feeds.scd OSC handlers) and drives:
- speed -> drive amount (0..1)
- accel -> filter cutoff (200 Hz..6 kHz)
- symmetry -> stereo width (0..1)
- label argmax -> reverb/compressor presets (debout/assise/danse)

Manual load only (eval block-by-block in SC IDE). Validates P:0 B:0.
2026-05-13 23:45:05 +02:00
L'électron rare 6af220dd59 feat(data-only-viz): MediaPipe offline extract
Add standalone MP4 extractor for action-head v3 training data via
MediaPipe HolisticLandmarker (VIDEO mode). Unlike extract_j3d_offline.py
(SMPL-X path), writes real hands_kp (42, 3) and mouth_open from face
landmarks instead of zeros.

- scripts/extract_mediapipe_offline.py: full pipeline (open video,
  iterate frames, map body33/face/hands -> j3d32 + hands_kp42 +
  mouth_open, write jsonl rows matching dataset.py schema)
- tests/test_extract_mediapipe_offline.py: 8 pure-numpy unit tests;
  no mediapipe runtime required

Enables hand-aware training from recorded footage without SMPL-X
or GPU at extraction time.
2026-05-13 23:31:52 +02:00
L'électron rare 1f623fe331 feat(av-live-body): mirror webcam preview
CATransform3D scale x=-1 on the AVCaptureVideoPreviewLayer so the
user sees themselves like in a mirror (left in reality = left on
screen). Overlays (skeleton/face/hand/mesh) keep their raw camera
coordinates for now; align via X-flip if user requests it.
2026-05-13 23:27:30 +02:00
L'électron rare beb94d2a4c feat(data-only-viz): action-head v3 hands+lips
Extends the action-head feature pipeline from v2 (302-D) to v3 (428-D).

- Replace placeholder SMPLX_FINGERTIP_VERTS with canonical vertex IDs
  from smplx.vertex_ids (lthumb/lindex/lmiddle/lring/lpinky, mirrored R)
- Add HANDS_KP_* constants (21 kp/hand, 42 total, 126-D flat block)
- FEATURE_DIM: 302 -> 428; hands_kp block inserted at [288:414]
- FeatureExtractor.from_buffer gains hands_kp param (42, 3),
  zero-padded when absent
- ActionHead.step gains hands_kp param, threads to from_buffer
- _read_sources returns 5-tuples with hands_kp42x3 per person
- MediaPipe FaceMesh inner-lip (idx 13/14) used for mouth_open;
  fallback to SMPL-X v3d lip vertices when face not available
- _build_hands_map and _build_face_mouth_map helpers added
- dataset.py: RawFrame/WindowRow/DatasetRow gain hands_kp fields
- train_action_head.py: reads hands_kp_stack per step, zeros if absent
- extract_j3d_offline.py: writes zero-filled hands_kp in jsonl output
- Tests: FEATURE_DIM 302->428, param bound 80k->100k, +4 new tests
2026-05-13 23:26:14 +02:00
L'électron rare 623c47983d perf(multi-hmr): autocast opt-in
MULTIHMR_AUTOCAST=1 enables MPS mixed precision for the ViT-S
backbone. Tested 2026-05-13: slower than fp32 baseline (400ms vs
270ms) -- overhead cast within forward exceeds matmul savings on
M5. Off by default; FP16 .half() crashes MPS matmul accumulator,
left out entirely.

apple_vision_pose face parser short-circuits to return immediately
since pyobjc 11 cannot dereference VNFaceLandmarkRegion2D
pointsInImageOfSize_ result. Removes ObjCPointerWarning spam at
30 fps (9 regions per face).
2026-05-13 23:24:06 +02:00
L'électron rare 2c8094c06c feat(av-live): hybrid mesh rigging 30 fps
MeshRigger module : entre deux keyframes Multi-HMR (~3.5 fps mesh
dense PyTorch MPS), on translate rigidement le mesh keyframe via le
delta pelvis 2D Apple Vision (30 fps body ANE) projete a profondeur
constante. SMPLXTCPSender bumpe a 30 fps target et applique le rig
sur chaque tick. Verifie live : 27 fps TCP soutenu, 100% rigged,
keyframe Multi-HMR a 3.2 fps -> ~8x speedup perceptuel dans la
fenetre RealityKit AVLiveBody.

Limitations connues :
- Translation seule (pas de rotation ni de LBS articule)
- Pelvis 2D delta projete a Z constant du keyframe
- Pas de matching d'identite robuste Vision <-> Multi-HMR : on prend
  la personne Vision la plus proche du pelvis keyframe projete
2026-05-13 23:17:22 +02:00
L'électron rare aedcb0f01b feat(data-only-viz): action-head v2 fingers+face
Extend action-head to 32 joints (body22 + 10 fingertips),
10 SMPL-X expression PCA scalars, and mouth_open distance.
FEATURE_DIM 201→302. MIRROR_MAP extended to 32. Dataset,
augment, training, publisher, offline extractor all updated.
2026-05-13 23:15:12 +02:00
L'électron rare 28d562b11c feat(av-live): wire Apple Vision body pose on ANE
Add a parallel-pose worker selector (env AV_LIVE_PARALLEL_POSE)
defaulting to "both": runs Apple Vision body 2D on ANE alongside
MediaPipe Holistic on CPU XNNPACK, while Multi-HMR PyTorch streams
the dense mesh on MPS. Modes "apple_vision" or "mediapipe" to pick
one. Body keypoints land in state.persons_body either way.

Face landmark parser via pyobjc remains blocked: pointAtIndex_
selector arity confuses pyobjc 11 and pointsInImageOfSize_ returns
an opaque PyObjCPointer with no address handle. Keep the
ANE-detected count for logging, fall back to MediaPipe face/hand
fin landmarks until a Swift bridge is in place.
2026-05-13 23:07:48 +02:00
L'électron rare 31ba587a63 docs(action-head): post-impl deviations + README
- plans/2026-05-13-action-head.md : note SUPERSEDED sur
  Task 11 + Task 14 (pivot publisher thread, pas de worker
  refactor), header status + decisions.
- specs/2026-05-13-action-head-design.md : status implemente,
  deviations cataloguees en haut, note inline section worker.
- data_only_viz/CLAUDE.md : section action-head ajoutee avec
  pipeline complet capture->train->live + ref tests.
2026-05-13 23:02:54 +02:00
L'électron rare 2a732faffc fix(data-only-viz): action-head review fixes
Adresse final review du feature action-head :
- action_head_pub.py + extract_j3d_offline.py : CoreMLArray
  wraps numpy mais n'a pas __array__ ; unwrap via .numpy()
  avant np.asarray pour eviter object-array silencieux
  quand persons_smplx vient du backend CoreML. extract_j3d
  ramene depuis main (manquait sur feat suite au merge c52271e).
- train_on_studio.sh : TRAIN_ARGS quote defensivement via
  printf %q + reject single quotes pour eviter injection
  via le payload single-quoted sur bastion.
2026-05-13 23:02:29 +02:00
L'électron rare b5e9139317 feat(data-only-viz): ActionHead publisher thread
Standalone publisher polls state at 30 Hz, extracts 22-joint
positions from either persons_smplx (vertex anchors) or
persons_body3d (MediaPipe 33→22 map), runs ActionHead.step()
per pid, and emits /pose/action + /pose/kin + lifecycle OSC.

- action_head_pub.py: ActionHeadPublisher thread with dedup
  via smplx_last_t / pose_last_t; purges lost pids
- tests/test_action_head_pub.py: 4 unit tests (39 total pass)
- multi.py: import + instantiate + start publisher in __init__
2026-05-13 22:41:31 +02:00
L'électron rare 5013a1916d docs(coreml-probe): note NaN bug deferred
Add comment in TracedMHMR.forward documenting that CoreML conversion
produces all-NaN on v3d/transl while PyTorch eager works. Tested FP32,
K_inv closed-form, simplified subtract+divide projection, nan_to_num
masking. Root cause is an op-level mistranslation in the v3d/transl
path; needs sub-wrapper bisection. Workaround: PyTorch backend.
2026-05-13 22:37:31 +02:00
L'électron rare a5c793f39c feat(data-only-viz): action capture webcam script
Implement Task 10 of action-head plan. Records webcam frames +
timestamps for action-head training using OpenCV. Outputs MP4 video
+ timestamp text file to ~/.cache/av-live-action/raw/ with 672x672
square crop, configurable fps/session/camera, interactive q-quit.
2026-05-13 22:36:19 +02:00
L'électron rare f540158f45 feat(av-live): face+hand+3D pose to launcher
Stream MediaPipe Holistic face landmarks (68 dlib subset of 478),
hand landmarks (21 left + 21 right), and pose world landmarks (33
3D xyz meters) over OSC :57126 to AVLiveBody. Launcher renders
face/hand as SwiftUI Canvas overlay and the 3D skeleton as a
RealityKit armature (sphere joints + cylinder bones, color per
chain) toggled via p / mode openpos.

Multi-HMR worker now also starts MediaPipe Multi in parallel so
both the dense SMPL-X mesh (TCP 57130, PyTorch backend) and the
skeleton/face/hand streams (OSC 57126) feed the launcher from
one Python process.

Launcher AppDelegate forces .regular activation so SwiftPM
binaries actually show their WindowGroup without a bundle.

Tests: 9 new pytest cases (4 body3d + 5 face/hand), all green.

CoreML conversion still produces NaN on v3d/transl; PyTorch
backend is the working path for now.
2026-05-13 22:34:42 +02:00
L'électron rare b53c748704 feat(data-only-viz): action-head review TUI
Add interactive console TUI for manual label review of
auto-labeled action datasets. Displays ASCII skeleton,
kinetics, and proposed label with confidence. User can
accept proposed label, choose manual override (1/2/3),
skip, or quit. Reads auto-labeled JSONL and writes
validated rows to reviewed dataset.
2026-05-13 22:34:26 +02:00
L'électron rare 0ecb2c3d3b fix(data-only-viz): studio rsync excludes + extras
rsync excludes .venv/__pycache__/.pytest_cache + uv sync ajoute
extra multihmr (torch). End-to-end valide: smoke 160 windows
3 epochs MPS studio en ~4s, ckpt rsync back OK.
2026-05-13 22:32:14 +02:00
L'électron rare a1ea343ff0 fix(data-only-viz): studio ssh quoting + abs paths
Le precedent printf %q sur-quotait $HOME -> mkdir recevait 0 arg.
On utilise des chemins absolus /Users/clems/av-live-action/* cote
studio et single-quotes pour proteger des bastion expansions.
2026-05-13 22:24:24 +02:00
L'électron rare dfafd23d5a feat(sound-algo): action-head pose OSC handlers
Add OSCdef handlers for /pose/action, /pose/kin, /pose/enter,
/pose/leave routes emitted by data_only_viz pose_bridge. Store
person state and kinematics in ~poseState and ~poseKin dicts.
2026-05-13 22:23:17 +02:00
L'électron rare c381d0c4e7 feat(data-only-viz): pose_bridge action+kin OSC
Ajoute 4 methodes OSC pour action_head et localisation cinetique:

send_action(pid, label_idx, probs, t_now, force) envoie /pose/action
avec [pid, label_idx, prob_0, prob_1, prob_2] pour les 3 classes.

send_kin(pid, kin, t_now, force) envoie /pose/kin avec [pid, kin[0],
kin[1], kin[2]] pour angles de poignets/coudes.

send_enter/send_leave envoient /pose/enter et /pose/leave pour cycle
vie des personnes.

Throttle reuse _period/_last_t existants; force=True bypass throttle.
2026-05-13 22:18:08 +02:00
L'électron rare e292ed7ef3 feat(data-only-viz): studio train wrapper
Wrapper bash : rsync dataset+code grosmac->studio via bastion
electron-server, exec uv run train_action_head --device mps sur
M3 Ultra, rsync checkpoint back. SSH direct cassee depuis reboot
studio 2026-05-12 ; route via bastion documentee dans CLAUDE.md.
2026-05-13 22:07:34 +02:00
L'électron rare 9d67426b2c feat(data-only-viz): action-head eval script
Add evaluation script to compute test accuracy, confusion matrix, and
inference latency on a trained action-head checkpoint. Reuses existing
WindowDataset and model infrastructure from training pipeline.

Falls back to evaluating on full dataset if test split is empty
(edge case with <4 sessions).
2026-05-13 22:05:15 +02:00
L'électron rare 744bc4a8a4 feat(data-only-viz): action-head training loop
Add train_action_head.py with WindowDataset, class-weighted
CrossEntropy, AdamW optimizer, per-epoch train/val loop, and
best-val-acc checkpoint saving. Add smoke tests verifying
2-epoch run and checkpoint loadability via ActionHead.

- WindowDataset computes position/velocity/accel features inline
- _class_weights balances imbalanced label distribution
- train() returns history dict (train/val loss and acc)
- CLI entry point for --device mps/cuda/cpu production runs
2026-05-13 21:43:51 +02:00
L'électron rare 9e7a9f8fd4 feat(data-only-viz): Multi-HMR CoreML backend
Convert Multi-HMR ViT-S 672 to CoreML mlpackage and wire as optional
worker backend via MULTIHMR_BACKEND=coreml env var.

Inference path uses pyobjc + native CoreML framework (Python 3.14 has
no libcoremlpython binding). Conversion done in a separate Py 3.12
venv; einsum cascade patched (camera intrinsics broadcast + smplx
landmarks) via setup_multihmr.sh, idempotent on re-clone.

Bench: 28 ms mock, 100-170 ms live (~13 fps, 4x PyTorch MPS). ANE
compile fails on this model; CPU+GPU is the sweet spot.
2026-05-13 21:42:43 +02:00
L'électron rare 24d1e85b7d feat(data-only-viz): action-head augmentations
Implement on-the-fly spatial and temporal augmentations for multi-HMR
j3d windows: mirror_x (left/right joint swap + x-flip), add_noise,
time_stretch (linear resampling), rotate_y. Task 7 of action-head plan.
All 30 tests pass (26 prior + 4 new augment tests).
2026-05-13 21:40:22 +02:00
L'électron rare f0f79b3478 merge: main into feat/action-head
Brings 9ab82e4 (MediaPipe Multi default + rich pose OSC) +
96a326d (av-live-body scene Metal) + 214b154 (HUD + menus).
Conflicts on duplicate launcher Swift commits (667f63c vs
96a326d) resolved with -X theirs (prefer main version, since
214b154 builds on it).
2026-05-13 21:36:08 +02:00
L'électron rare 214b1545c4 feat(av-live-body): HUD + menus + pose -> scene
C - HUD coin haut-gauche + touches actives :
- HUDOverlay.swift : statut mode actif, count personnes, list
  touches actives (S/0-9/M/W/C/V), positions head/wrists de la
  1ere personne. allowsHitTesting(false).

Menus + raccourcis SwiftUI complets :
- App > Toggle Settings   (S)
- Calques > Toggle Webcam (C) / Scene Metal (V) /
            Maillage SMPL-X (M) / Fil de fer (W)
- Modes visuels > 0..9 -> storm/.../openpos
Tous propages via NotificationCenter (.toggleSettings,
.toggleLayer, .setVizMode).

B - Pose drive scene uniforms :
- BodyView observe PoseOSCListener en plus de RenderSettings.
- updateNSView pousse pose_count, pose_alive, hand_l_x/y,
  hand_r_x/y dans SceneRenderer.uniforms.
- Mode hands3d (#8) reagit a la position des poignets de la
  1ere personne detectee. Mode openpos (#9) idem.

A - Dashboard data-only : 0 fix code, le probleme etait
bridge.py orphelins multiples + cache navigateur.
2026-05-13 21:34:26 +02:00
L'électron rare 96a326d3c6 feat(av-live-body): scene Metal + pose OSC
Phase 1 fusion : AV-Live-Body absorbe la couche Metal viz et
ecoute les data pose via OSC.

Metal scene (10 viz modes :  storm, tunnel, plasma, kaleido,
voronoi, metaballs, starfield, bars, hands3d, openpos) :
- Resources/scene.metal copie depuis data_only_viz, compile au
  runtime via MTLLibrary.makeLibrary(source).
- SceneRenderer.swift : MTKViewDelegate qui rebuild
  SceneUniforms (20 floats, miroir du struct Metal) et drive
  bg_pipeline (full-screen triangle).
- BodyView : nouveau MTKView entre la cam preview et l'ARView,
  zPosition intermediaire, alpha pour laisser passer la cam.
- RenderSettings : showScene + vizMode (0..9), picker 10
  boutons numerotes dans SettingsPanel + libelle du mode actif
  affiche dans la row 'Scene Metal (<name>)'.

Pose OSC :
- PoseOSCListener.swift : UDP listener :57126, parser OSC
  minimal (i, f, s args), @MainActor dispatch des Published.
  Stocke un PoseFrame par pid (center, head, wrists, sho_span,
  yaw, pitch) avec GC 2 s.
- data_only_viz/pose_bridge.py : 2e SimpleUDPClient broadcast
  vers 127.0.0.1:57126 (try/except OSError pour silencer si
  AVLiveBody pas la). Throttle 30 Hz partage.

Phase 2 (futur) : rendu skeleton entities RealityKit (spheres +
cylindres) consommant PoseOSCListener.persons.

Package.swift : ajout Resources/scene.metal en .copy.
2026-05-13 21:23:48 +02:00
L'électron rare 667f63c385 feat(av-live-body): scene Metal + pose OSC
Phase 1 fusion : AV-Live-Body absorbe la couche Metal viz et
ecoute les data pose via OSC.

Metal scene (10 viz modes :  storm, tunnel, plasma, kaleido,
voronoi, metaballs, starfield, bars, hands3d, openpos) :
- Resources/scene.metal copie depuis data_only_viz, compile au
  runtime via MTLLibrary.makeLibrary(source).
- SceneRenderer.swift : MTKViewDelegate qui rebuild
  SceneUniforms (20 floats, miroir du struct Metal) et drive
  bg_pipeline (full-screen triangle).
- BodyView : nouveau MTKView entre la cam preview et l'ARView,
  zPosition intermediaire, alpha pour laisser passer la cam.
- RenderSettings : showScene + vizMode (0..9), picker 10
  boutons numerotes dans SettingsPanel + libelle du mode actif
  affiche dans la row 'Scene Metal (<name>)'.

Pose OSC :
- PoseOSCListener.swift : UDP listener :57126, parser OSC
  minimal (i, f, s args), @MainActor dispatch des Published.
  Stocke un PoseFrame par pid (center, head, wrists, sho_span,
  yaw, pitch) avec GC 2 s.
- data_only_viz/pose_bridge.py : 2e SimpleUDPClient broadcast
  vers 127.0.0.1:57126 (try/except OSError pour silencer si
  AVLiveBody pas la). Throttle 30 Hz partage.

Phase 2 (futur) : rendu skeleton entities RealityKit (spheres +
cylindres) consommant PoseOSCListener.persons.

Package.swift : ajout Resources/scene.metal en .copy.
2026-05-13 21:22:57 +02:00
L'électron rare 7f0ac97a21 fix(data-only-viz): restore action-head module 2026-05-13 21:21:06 +02:00
L'électron rare 9ab82e47aa feat(pose): MediaPipe Multi default + rich OSC
User demande detection mesh visage + main + interaction
sonore plus riche + openpos toujours actif. Pipeline :

main.py priorite pose worker reordonnee :
  0. Multi-HMR (flag opt-in)
  1. MediaPipe Multi (DEFAUT) : 33 body + 478 face + 21x2 hands
     x 4 personnes. Active la couche mesh_pipe pour rendre face
     triangulee + hands triangulees + body skeleton ensemble.
  2. Apple Vision (fallback body-only ANE).

main.py auto-engage openpos #9 quand persons_body non vide via
un NSTimer 2 Hz autoOpenpos. Lock manuel 8s quand l'utilisateur
appuie sur azertyuiop. Revient en storm (#0) quand plus
aucune personne detectee.

pose_bridge.py enrichi :
  /pose/wrist_speed pid l|r vx vy   (velocity normalisee)
  /pose/torso_yaw pid yaw            (rotation epaules)
  /pose/body_pitch pid pitch         (inclinaison verticale)
  /pose/face pid mouth_open eye_open (FACE_MESH 478)
  /pose/hand pid l|r openness px py  (HAND 21)
  /pose/active pid activity_0_1      (mean conf)

multi.py passe persons_face / persons_hands au PoseSoundBridge.
2026-05-13 21:02:27 +02:00
L'électron rare a199c50297 feat(data-only-viz): action auto-labeler rules
Problem: Action classification (debout/assise/danse) requires rule-based
labeling before neural training. Task 5 of action-head plan.

Approach: Heuristic rules on j3d posture + kinetics (speed/accel):
- Hip height + knee angle → seated vs. standing
- Joint velocity → static vs. dancing
- Confidence scoring for ambiguous windows

Changes:
- action_head.py: scaffold with FeatureExtractor (kinetics, knee angle)
- autolabel.py: AutoLabelConfig, autolabel_window(), autolabel_dataset()
  CLI glue (raw frames jsonl → windowed labeled dataset jsonl)
- test_autolabel.py: 4 TDD tests (debout, assise, danse, ambiguous)

Impact: Enables dataset creation pipeline (extract_j3d → auto-label →
manual review → train ActionHead GRU).
2026-05-13 20:59:47 +02:00
L'électron rare b410023d03 feat(data-only-viz): dataset jsonl+windows
Implements core dataset infrastructure for action-head training:
- RawFrame: JSONL frame parsing (ts, session, pid, j3d)
- sliding_windows: temporal windows by (session, pid)
- DatasetRow: labeled windows with confidence/validation
- write/load_dataset_jsonl: JSONL numpy array serialization
- split_by_session: stratified train/val/test by session

Tests verify load→windows→write→load→split data flows.
2026-05-13 20:53:31 +02:00
L'électron rare 2d92cceef9 feat(data-only-viz): dataset jsonl+windows
Implements core dataset infrastructure for action-head training:
- RawFrame: JSONL frame parsing (ts, session, pid, j3d)
- sliding_windows: temporal windows by (session, pid)
- DatasetRow: labeled windows with confidence/validation
- write/load_dataset_jsonl: JSONL numpy array serialization
- split_by_session: stratified train/val/test by session

Tests verify load→windows→write→load→split data flows.
2026-05-13 20:50:13 +02:00
L'électron rare bd3dd89511 fix(feeds): 3 warnings externes
3 fixes pour reduire le bruit de logs des sources publiques :

wikimedia.py : ajoute User-Agent descriptif. WMF EventStreams
refuse l'UA httpx par defaut avec 403 Forbidden. Le 1.0+URL
descriptif est obligatoire pour le rate-limiting et les abuse
reports.

netzfrequenz.py : backoff exponentiel cap a 5 min sur les ws
disconnect. La source (mainsfrequenz.de) est NXDOMAIN depuis
mai 2026 ; on logguait toutes les 3 s soit ~28 800/jour. Avec
backoff x1.6 et log uniquement attempt==1 ou attempt%10, on
descend a ~30 log/jour.

config.data-only.toml : opensky poll_seconds 20 -> 60. L'API
anonyme est credit-based (10 req/min budget). 60s reste
raisonnable pour le viz tout en restaurant le credit.
2026-05-13 20:49:23 +02:00
L'électron rare 50343be6ec docs(plans): bench CoreML M5 vs M1 Max
Comparatif backbone DINOv2 ViT-S 672 sur deux Apple Silicon :

Hardware    | CoreML GPU | CoreML ALL | PyTorch MPS
M5 (16 GB)  | 25.1 ms    | 157.3 ms   | 274.7 ms
M1 Max(32G) | 63.3 ms    | 154.1 ms   | 110.6 ms

M5 GPU 2.5x plus rapide en CoreML (per-core efficiency vs
32 cores M1 Max). M1 Max 2.5x plus rapide en PyTorch MPS
(M5 souffrait probablement de throttle accumule).

ANE handicape les deux (~155 ms vs 25-63 ms GPU) — DINOv2
ViT-S non ANE-friendly.

macM1 workers MLX pauses via SIGSTOP/SIGCONT pour bench
isole.
2026-05-13 20:31:33 +02:00
L'électron rare 17d93fd351 docs(plans): action-head implementation plan
16 bite-sized tasks covering ActionHead model, training, dataset
pipeline (capture -> extract -> autolabel -> review), eval, OSC
wiring, sound_algo handlers, and end-to-end gate.
2026-05-13 20:31:12 +02:00
L'électron rare ee86990195 feat(pose): enrichir card dashboard
data_feeds/feeds/pose.py emet maintenant /data/pose/stats
avg_conf avg_size cx_bar cy_bar apres chaque batch :
- avg_conf : moyenne des confidences keypoints (0..1)
- avg_size : moyenne des bbox surface w*h normalisee
  (proxy distance camera ; 1.0 = cadre plein)
- cx_bar, cy_bar : barycentre des centres de bboxes
  (utile pour piloter des FX qui suivent l'occupation)

Dashboard handler 'pose' enrichi (wide card) :
- count en valeur principale
- subline : conf % + occup. % cadre + centre (cx, cy)
- bordure warn si avg_conf < 0.4
- bboxes stockees dans c._persons pour overlay futur
2026-05-13 20:29:26 +02:00