feat(pose): fist kill-switch, default off
CI build oscope-of / build-check (push) Has been cancelled
CI build oscope-of / build-check (push) Has been cancelled
User request live 2026-07-02: disable the fist gesture completely (false held-fist actions during play). fist is forced to 0.0 at the extractor unless FIST_ENABLE=1; continuous features unchanged. SC held-fist actions (reload/next) go silent without touching SC.
This commit is contained in:
@@ -43,11 +43,21 @@ def _clamp(v: float, lo: float, hi: float) -> float:
|
||||
|
||||
|
||||
class HandFeatureExtractor:
|
||||
def __init__(self, history: int = 5):
|
||||
def __init__(self, history: int = 5, fist_enable: bool | None = None):
|
||||
self._history = max(2, history)
|
||||
# two routed slots (hand_slots.route_hands): 0 = user's left (L),
|
||||
# 1 = user's right (R); cx fallback when chirality is absent.
|
||||
self._buf = [deque(maxlen=self._history), deque(maxlen=self._history)]
|
||||
# Master fist kill-switch: user-disabled live 2026-07-02 (too many
|
||||
# false held-fist actions). fist stays 0.0 for BOTH hands whatever
|
||||
# the per-slot fist_enabled kwarg says. FIST_ENABLE=1 re-enables.
|
||||
# TODO: migrate the env read to VizConfig once config.py is free.
|
||||
if fist_enable is None:
|
||||
import os
|
||||
fist_enable = os.environ.get("FIST_ENABLE", "0") not in (
|
||||
"0", "", "false", "False",
|
||||
)
|
||||
self._fist_enable = fist_enable
|
||||
|
||||
def _features(self, lm: list) -> dict:
|
||||
xs = [_finite(_coord(p, "x", 0), 0.5) for p in lm[:21]]
|
||||
@@ -98,7 +108,7 @@ class HandFeatureExtractor:
|
||||
|
||||
if left_lm is not None:
|
||||
lf = self._features(left_lm)
|
||||
if not fist_enabled[0]:
|
||||
if not (self._fist_enable and fist_enabled[0]):
|
||||
lf["fist"] = 0.0
|
||||
lf["speed"] = self._speed(0, lf["cx"], lf["cy"])
|
||||
out_l = lf
|
||||
@@ -107,7 +117,7 @@ class HandFeatureExtractor:
|
||||
|
||||
if right_lm is not None:
|
||||
rf = self._features(right_lm)
|
||||
if not fist_enabled[1]:
|
||||
if not (self._fist_enable and fist_enabled[1]):
|
||||
rf["fist"] = 0.0
|
||||
rf["speed"] = self._speed(1, rf["cx"], rf["cy"])
|
||||
out_r = rf
|
||||
|
||||
@@ -117,19 +117,19 @@ def _fist_hand(tip_ext):
|
||||
|
||||
|
||||
def test_fist_all_fingers_curled():
|
||||
out = HandFeatureExtractor().step([_fist_hand(0.20)]) # tips at knuckles
|
||||
out = HandFeatureExtractor(fist_enable=True).step([_fist_hand(0.20)]) # tips at knuckles
|
||||
assert (out["L"] or out["R"])["fist"] > 0.9
|
||||
|
||||
|
||||
def test_open_hand_is_not_a_fist():
|
||||
out = HandFeatureExtractor().step([_fist_hand(0.80)]) # fingers extended
|
||||
out = HandFeatureExtractor(fist_enable=True).step([_fist_hand(0.80)]) # fingers extended
|
||||
assert (out["L"] or out["R"])["fist"] < 0.1
|
||||
|
||||
|
||||
def test_one_extended_finger_breaks_the_fist():
|
||||
h = _fist_hand(0.20) # all curled...
|
||||
h[8] = LM(0.5 + 0.80 * 0.10, 0.5) # ...except the index
|
||||
out = HandFeatureExtractor().step([h])
|
||||
out = HandFeatureExtractor(fist_enable=True).step([h])
|
||||
assert (out["L"] or out["R"])["fist"] < 0.1 # min over fingers
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ def test_fist_enabled_false_zeroes_fist_preserves_openness():
|
||||
|
||||
def test_fist_enabled_true_computes_fist():
|
||||
"""fist_enabled=(True, True) leaves fist computation unchanged."""
|
||||
ext = HandFeatureExtractor()
|
||||
ext = HandFeatureExtractor(fist_enable=True)
|
||||
h = _fist_hand(0.20)
|
||||
out = ext.step([h], fist_enabled=(True, True))
|
||||
hand = out["L"] or out["R"]
|
||||
@@ -225,3 +225,19 @@ def test_fist_enabled_per_slot_independence():
|
||||
assert out["L"]["fist"] == 0.0
|
||||
# R slot (cx~0.8) should compute normally (open -> fist < 0.1)
|
||||
assert out["R"]["fist"] < 0.1
|
||||
|
||||
|
||||
def test_fist_kill_switch_default_off(monkeypatch):
|
||||
"""FIST_ENABLE unset -> fist forced 0.0 even on a perfect fist
|
||||
(user-disabled live 2026-07-02); continuous features unaffected."""
|
||||
monkeypatch.delenv("FIST_ENABLE", raising=False)
|
||||
out = HandFeatureExtractor().step([_fist_hand(0.20)])
|
||||
hand = out["L"] or out["R"]
|
||||
assert hand["fist"] == 0.0
|
||||
assert hand["openness"] < 0.2
|
||||
|
||||
|
||||
def test_fist_kill_switch_env_reenable(monkeypatch):
|
||||
monkeypatch.setenv("FIST_ENABLE", "1")
|
||||
out = HandFeatureExtractor().step([_fist_hand(0.20)])
|
||||
assert (out["L"] or out["R"])["fist"] > 0.9
|
||||
|
||||
Reference in New Issue
Block a user