From 1c07211dd8624f9f69b3ab63a5cc9dfb5fe096bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:44:28 +0200 Subject: [PATCH] feat(viz): perf-safe keys, frozen pinch tuning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live session outcomes: (1) pinch thresholds validated for inner fingers frozen as defaults (ratio_on 0.50, margin 0.05, ext_min 1); (2) panel frames + X/Y gauges now permanent (no early-return when nothing is detected); (3) wxcvbn source-bundle shortcuts gated off by default (VIZ_SOURCE_KEYS=1 re-enables) — a stray key mid-performance switched the audio scene. --- CLAUDE.md | 5 +++-- data_only_viz/config.py | 18 ++++++++++++------ data_only_viz/main.py | 11 +++++++++-- data_only_viz/renderer.py | 6 ++++-- data_only_viz/tests/test_config.py | 12 +++++++++--- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f1912f9..6d4d83c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,11 +74,12 @@ Toujours répondre en français à l'utilisateur. Code, commentaires de code, co | `FINGER_STRIKE_REFRACTORY_MS` | `120` | min ms between strikes per finger | | `FINGER_SOURCE` | `auto` | hand source for finger gestures: `auto` (iPhone Vision hands if fresh, else MediaPipe), `iphone`, `mediapipe` | | `FINGER_DEBUG` | `0` | `1` logs each detected strike/pinch (hand, finger) — for threshold tuning | -| `PINCH_RATIO_ON` | `0.45` | thumb-to-finger distance / hand size below which a pinch engages (clip toggle) | +| `PINCH_RATIO_ON` | `0.50` | thumb-to-finger distance / hand size below which a pinch engages (0.50 validated live 2026-07-02 for ring/pinky reach) | | `PINCH_RATIO_OFF` | `0.65` | distance/size above which the pinch re-arms (hysteresis) | | `PINCH_REFRACTORY_MS` | `250` | min ms between pinches per finger | +| `PINCH_MARGIN` | `0.05` | closest-wins margin between nearest and runner-up fingertip (0.05 validated live: 0.20 rejected middle/ring/pinky pinches) | | `PINCH_EXT_RATIO` | `1.35` | tip-to-wrist distance / hand size above which a finger counts as extended (open-hand pinch gate) | -| `PINCH_EXT_MIN` | `2` | min extended non-pinching fingers required for a pinch to engage; `0` disables the open-hand gate | +| `PINCH_EXT_MIN` | `1` | min extended non-pinching fingers required for a pinch to engage; `0` disables the open-hand gate (1 validated live: inner-finger pinches curl neighbours) | | `PINCH_DEBOUNCE_FRAMES` | `3` | consecutive qualifying frames before a pinch engage fires (release stays immediate); `1` disables | | `HAND_CONF_MIN` | `0.45` | min Vision hand confidence to draw (raised from 0.3; applies to both overlay and side panels) | | `HAND_PERSIST_FRAMES` | `3` | consecutive frames a hand track must match before being drawn (1 = off, all hands drawn immediately) | diff --git a/data_only_viz/config.py b/data_only_viz/config.py index d5983fd..66ea59c 100644 --- a/data_only_viz/config.py +++ b/data_only_viz/config.py @@ -42,6 +42,7 @@ HAND_HOLD_FRAMES 2 int gesture slot hold: frames to carry ARKIT_BONE_MAX 0.5 float max bone length (norm) for ARKit skeleton ARKIT_FULL_SKELETON "1" bool draw full 91-joint ARKit body (!=0 = True) VIZ_HUD "0" bool show debug HUD overlay +VIZ_SOURCE_KEYS "0" bool wxcvbn source-bundle shortcuts (off: perf safety) IPHONE_OSC_PORT 57128 int UDP port for iPhone ARBodyTracker /body3d/kp CONCERT_MIRROR "1" bool mirror video horizontally (!=0 = True) ICP_FUSION "0" bool enable ICP LiDAR fusion (only "1" = True) @@ -110,12 +111,12 @@ class VizConfig: # ---- Pinch detection ------------------------------------------------- pinch_enable: bool = False - pinch_ratio_on: float = 0.45 + pinch_ratio_on: float = 0.50 pinch_ratio_off: float = 0.65 pinch_refractory_ms: float = 250.0 - pinch_margin: float = 0.20 + pinch_margin: float = 0.05 pinch_ext_ratio: float = 1.35 - pinch_ext_min: int = 2 + pinch_ext_min: int = 1 pinch_debounce_frames: int = 3 # ---- Hand display / gesture ------------------------------------------ @@ -132,6 +133,10 @@ class VizConfig: # ---- HUD ------------------------------------------------------------- viz_hud: bool = False + # Source-bundle keyboard shortcuts (wxcvbn): OFF by default — a stray + # keypress during a matrix performance switches the audio scene + # (user-disabled live 2026-07-02). VIZ_SOURCE_KEYS=1 re-enables. + viz_source_keys: bool = False # ---- iPhone OSC ------------------------------------------------------ iphone_osc_port: int = 57128 @@ -260,12 +265,12 @@ class VizConfig: finger_source=_str_lower("FINGER_SOURCE", "auto"), finger_debug=_bool_std("FINGER_DEBUG", False), pinch_enable=_bool_std("PINCH_ENABLE", False), - pinch_ratio_on=_float("PINCH_RATIO_ON", 0.45), + pinch_ratio_on=_float("PINCH_RATIO_ON", 0.50), pinch_ratio_off=_float("PINCH_RATIO_OFF", 0.65), pinch_refractory_ms=_float("PINCH_REFRACTORY_MS", 250.0), - pinch_margin=_float("PINCH_MARGIN", 0.20), + pinch_margin=_float("PINCH_MARGIN", 0.05), pinch_ext_ratio=_float("PINCH_EXT_RATIO", 1.35), - pinch_ext_min=_int("PINCH_EXT_MIN", 2), + pinch_ext_min=_int("PINCH_EXT_MIN", 1), pinch_debounce_frames=_int("PINCH_DEBOUNCE_FRAMES", 3), hand_conf_min=_float("HAND_CONF_MIN", 0.45), hand_persist_frames=_int("HAND_PERSIST_FRAMES", 3), @@ -279,6 +284,7 @@ class VizConfig: # ARKIT_FULL_SKELETON uses != "0" convention (empty = True) arkit_full_skeleton=_bool_ne0("ARKIT_FULL_SKELETON", True), viz_hud=_bool_std("VIZ_HUD", False), + viz_source_keys=_bool_std("VIZ_SOURCE_KEYS", False), iphone_osc_port=_int("IPHONE_OSC_PORT", 57128), concert_mirror=_bool_ne0("CONCERT_MIRROR", True), # ICP_FUSION / MULTIHMR_AUTOCAST use strict "1" convention diff --git a/data_only_viz/main.py b/data_only_viz/main.py index b6516ca..50d11a7 100644 --- a/data_only_viz/main.py +++ b/data_only_viz/main.py @@ -71,6 +71,8 @@ class AppDelegate(NSObject): # Sender vers sclang pour les changements de scene depuis le clavier self._scClient = SimpleUDPClient("127.0.0.1", opts.sclang_port) self._pose_worker = None + from .config import VizConfig as _VizConfig + self._source_keys = _VizConfig.from_env().viz_source_keys return self def applicationDidFinishLaunching_(self, notification): # noqa: N802 @@ -627,8 +629,13 @@ class AppDelegate(NSObject): self._state.active_scene = scene LOG.info("[audio] scene -> %s", scene) return None - # wxcvbn + 0-9 -> preset bundle (source + scene audio + viz video) - for kk, source, scene, viz in (*KEYMAP_SOURCE, *KEYMAP_SOURCE_NUM): + # wxcvbn -> preset bundle: gated OFF by default (VIZ_SOURCE_KEYS=1 + # re-enables) — a stray keypress mid-performance switches the audio + # scene. The 0-9 numeric bundles stay active. + _src_maps = ((*KEYMAP_SOURCE, *KEYMAP_SOURCE_NUM) + if getattr(self, "_source_keys", False) + else KEYMAP_SOURCE_NUM) + for kk, source, scene, viz in _src_maps: if key == kk or k == kk: # Audio : envoie a sclang self._scClient.send_message("/control/doScene", [scene]) diff --git a/data_only_viz/renderer.py b/data_only_viz/renderer.py index 86235f4..b46aa03 100644 --- a/data_only_viz/renderer.py +++ b/data_only_viz/renderer.py @@ -343,8 +343,10 @@ class MetalRenderer(NSObject): _arkit_now = time.perf_counter() use_arkit = (getattr(self, '_arkit_full', ARKIT_FULL) and bool(s.arkit_parents) and arkit_2d_fresh(s.persons_arkit_2d_t, _arkit_now)) - if not use_arkit and not s.pose_alive(): - return 0 + # NO early-return when nothing is detected: the panel frames and the + # X/Y gauges are permanent screen furniture (user request: indicators + # must stay visible with no hands). The overlay loops below iterate + # empty collections at negligible cost. buf = self._skel_cpu_buf segs = 0 diff --git a/data_only_viz/tests/test_config.py b/data_only_viz/tests/test_config.py index 02e21a3..9e07f35 100644 --- a/data_only_viz/tests/test_config.py +++ b/data_only_viz/tests/test_config.py @@ -48,12 +48,12 @@ def test_defaults_finger_piano(): def test_defaults_pinch(): cfg = VizConfig.from_env({}) assert cfg.pinch_enable is False - assert cfg.pinch_ratio_on == pytest.approx(0.45) + assert cfg.pinch_ratio_on == pytest.approx(0.50) assert cfg.pinch_ratio_off == pytest.approx(0.65) assert cfg.pinch_refractory_ms == pytest.approx(250.0) - assert cfg.pinch_margin == pytest.approx(0.20) + assert cfg.pinch_margin == pytest.approx(0.05) assert cfg.pinch_ext_ratio == pytest.approx(1.35) - assert cfg.pinch_ext_min == 2 + assert cfg.pinch_ext_min == 1 assert cfg.pinch_debounce_frames == 3 @@ -407,3 +407,9 @@ def test_hand_face_min_env_override(): """HAND_FACE_MIN env var is parsed as float and stored in hand_face_min.""" cfg = VizConfig.from_env({"HAND_FACE_MIN": "0.3"}) assert cfg.hand_face_min == pytest.approx(0.3) + + +def test_viz_source_keys_default_off(): + cfg = VizConfig.from_env({}) + assert cfg.viz_source_keys is False + assert VizConfig.from_env({"VIZ_SOURCE_KEYS": "1"}).viz_source_keys is True