69c560dfe6
CI build oscope-of / build-check (push) Has been cancelled
Metal lines are 1px and the skeleton was barely visible in performance. Each ARKit bone is now drawn in concentric offset passes (SKEL_LINE_WIDTH, default 4 approx px; 1 restores the thin look).
336 lines
17 KiB
Python
336 lines
17 KiB
Python
"""Central configuration for data_only_viz — single source of truth for env vars.
|
|
|
|
All os.environ reads for data_only_viz belong here.
|
|
Consumers call ``VizConfig.from_env()`` inside their ``__init__`` or the
|
|
consuming method — never at module import time so that test monkeypatching
|
|
of os.environ is picked up correctly.
|
|
|
|
Env var reference table
|
|
-----------------------
|
|
Name Default Type Effect
|
|
MEDIAPIPE_DELEGATE "gpu" str "gpu" (Metal) or "cpu"
|
|
VIDEO_ROTATE "none" str none/ccw/cw/180 — rotate frame before MP+JPEG
|
|
POSE_GHOST_MIN_VISIBLE 10 int min visible kps to accept a body detection
|
|
POSE_GHOST_MIN_CONF 0.5 float min mean confidence to keep a detection
|
|
POSE_HAND_MIN_VISIBLE 15 int visibility gate for hand kps
|
|
POSE_FACE_MIN_VISIBLE 50 int visibility gate for face kps
|
|
POSE_NMS_IOU 0.7 float IoU threshold for body NMS
|
|
POSE_FILTER None str filter stage list (+/-sep); None=defaults
|
|
POSE_FILTER_FACE None str filter stages for face; None=defaults
|
|
POSE_FILTER_HAND None str filter stages for hands; None=defaults
|
|
FINGER_PIANO "0" bool enable air-piano finger-strike emission
|
|
FINGER_STRIKE_VEL 0.02 float downward relative-velocity threshold
|
|
FINGER_STRIKE_REFRACTORY_MS 120 float min ms between strikes per finger
|
|
FINGER_SOURCE "auto" str "auto"/"iphone"/"mediapipe"
|
|
FINGER_DEBUG "0" bool log each detected strike/pinch
|
|
PINCH_ENABLE "0" bool enable pinch gesture detection
|
|
PINCH_RATIO_ON 0.45 float thumb-to-finger dist/hand-size to engage
|
|
PINCH_RATIO_OFF 0.65 float dist/size above which pinch re-arms
|
|
PINCH_REFRACTORY_MS 250 float min ms between pinches per finger
|
|
PINCH_MARGIN 0.20 float extra margin added to pinch zone
|
|
PINCH_EXT_RATIO 1.35 float extended-pinch activation ratio
|
|
PINCH_EXT_MIN 2 int min extended fingers for a pinch to fire; 0 disables open-hand gate
|
|
PINCH_DEBOUNCE_FRAMES 3 int debounce frames for pinch state machine
|
|
HAND_CONF_MIN 0.45 float min hand landmark confidence to render
|
|
HAND_PERSIST_FRAMES 3 int consecutive frames before a hand track is drawn; 1=off
|
|
HAND_SWAP_LR "0" bool Safety knob: invert Vision chirality (validated correct live 2026-07-02; keep 0 unless a future iPhone app build flips it)
|
|
HAND_NEAR_MIN 0.10 float min hand size (norm) to consider "near"
|
|
HAND_FACE_MIN 0.5 float palm-spread ratio threshold: slot activates gestures only when facing >= this (side-on < 0.4, facing ~0.7-1.0)
|
|
HAND_PERSIST_GRACE 2 int grace frames before dropping a hand track on Vision miss (0 = strict)
|
|
HAND_NEAR_OFF 0.08 float hand size below which near gate deactivates (hysteresis off-threshold)
|
|
HAND_HOLD_FRAMES 2 int gesture slot hold: frames to carry last hand on Vision miss
|
|
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 + 0-9 source-bundle shortcuts (off: perf safety)
|
|
VIZ_AUDIO_KEYS "0" bool qsdfghjkl audio-scene shortcuts (off: perf safety; m stays)
|
|
VIZ_DEFAULT_MODE "auto" str fixed boot viz mode (e.g. voronoi); disables auto-openpos
|
|
SKEL_LINE_WIDTH "4" int body skeleton stroke width, approx px (1 = thin)
|
|
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)
|
|
ICP_LIDAR_HOST None str iPhone LiDAR stream host when ICP_FUSION=1
|
|
ICP_LIDAR_PORT 5500 int iPhone LiDAR stream port
|
|
ICP_LIDAR_EXTRINSIC None str path to extrinsic JSON for ICP
|
|
MULTIHMR_BACKEND "pytorch" str "pytorch"/"coreml"/"remote"
|
|
MULTIHMR_LOOP_FPS 30.0 float Multi-HMR worker loop target fps
|
|
MULTIHMR_AUTOCAST "0" bool MPS fp16 autocast for Multi-HMR (only "1")
|
|
MULTIHMR_REMOTE_HOST "192.168.0.175" str remote Multi-HMR server host
|
|
MULTIHMR_REMOTE_PORT 57140 int remote Multi-HMR server port
|
|
MULTIHMR_REMOTE_JPEG True bool send JPEG-compressed frames to remote
|
|
MULTIHMR_REMOTE_JPEG_QUALITY 80 int JPEG quality for remote frames
|
|
MULTIHMR_REMOTE_ASYNC True bool async pipeline for remote backend
|
|
MULTIHMR_REID "dino" str reid mode: "dino" or "iou"
|
|
MULTIHMR_REID_ALPHA 0.5 float DINOv2 weight in hybrid reid score
|
|
COREML_MLPACKAGE None str path to .mlpackage (None=default cache path)
|
|
COREML_COMPUTE_UNITS "" str "cpu_only"/"cpu_and_gpu"/"all"/"cpu_and_ne"
|
|
AVBODY_HOST "127.0.0.1" str host for AVLiveBody skeleton UDP/TCP
|
|
VDMX_OSC_HOST None str VDMX VJ OSC host (None=disabled)
|
|
VDMX_OSC_PORT 1234 int VDMX VJ OSC port
|
|
AV_LIVE_MEDIAPIPE True bool enable MediaPipe Multi worker (!=0 = True)
|
|
AV_LIVE_APPLE_VISION True bool enable Apple Vision fallback (!=0 = True)
|
|
AV_LIVE_COREML True bool enable CoreML pose fallback (!=0 = True)
|
|
AV_LIVE_DETRPOSE False bool enable DETRPose fallback (only "1" = True)
|
|
AV_LIVE_PARALLEL_POSE "both" str parallel pose backend in multi-hmr mode
|
|
MESH_RIG "1" bool enable SMPL-X mesh rigger (!=0 = True)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import os
|
|
from typing import Mapping
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class VizConfig:
|
|
"""Frozen snapshot of all data_only_viz environment configuration.
|
|
|
|
Construct with ``VizConfig.from_env()`` inside ``__init__`` or the
|
|
consuming method — never at module import time.
|
|
"""
|
|
|
|
# ---- MediaPipe / video -----------------------------------------------
|
|
mediapipe_delegate: str = "gpu"
|
|
video_rotate: str = "none"
|
|
|
|
# ---- Pose discrimination thresholds ----------------------------------
|
|
pose_ghost_min_visible: int = 10
|
|
pose_ghost_min_conf: float = 0.5
|
|
pose_hand_min_visible: int = 15
|
|
pose_face_min_visible: int = 50
|
|
pose_nms_iou: float = 0.7
|
|
|
|
# ---- Pose filter chain -----------------------------------------------
|
|
pose_filter: str | None = None
|
|
pose_filter_face: str | None = None
|
|
pose_filter_hand: str | None = None
|
|
|
|
# ---- Finger piano / strike -------------------------------------------
|
|
finger_piano: bool = False
|
|
finger_strike_vel: float = 0.02
|
|
finger_strike_refractory_ms: float = 120.0
|
|
finger_source: str = "auto"
|
|
finger_debug: bool = False
|
|
|
|
# ---- Pinch detection -------------------------------------------------
|
|
pinch_enable: bool = False
|
|
pinch_ratio_on: float = 0.50
|
|
pinch_ratio_off: float = 0.65
|
|
pinch_refractory_ms: float = 250.0
|
|
pinch_margin: float = 0.05
|
|
pinch_ext_ratio: float = 1.35
|
|
pinch_ext_min: int = 1
|
|
pinch_debounce_frames: int = 3
|
|
|
|
# ---- Hand display / gesture ------------------------------------------
|
|
hand_conf_min: float = 0.45
|
|
hand_persist_frames: int = 3
|
|
hand_persist_grace: int = 2
|
|
hand_swap_lr: bool = False
|
|
hand_near_min: float = 0.10
|
|
hand_near_off: float = 0.08
|
|
hand_hold_frames: int = 2
|
|
hand_face_min: float = 0.5
|
|
arkit_bone_max: float = 0.5
|
|
arkit_full_skeleton: bool = True
|
|
|
|
# ---- 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
|
|
# Audio-scene row (qsdfghjkl): same perf-safety gate, same date. The
|
|
# special m key (matrix mode: scene stop + openpos viz) stays active.
|
|
viz_audio_keys: bool = False
|
|
# Fixed viz mode at boot (a viz_mode_names entry, e.g. "voronoi").
|
|
# "auto" = historical behavior (mode 0 + auto-openpos when persons
|
|
# appear). A fixed mode also DISABLES the auto-openpos timer so it is
|
|
# truly always on (user request live 2026-07-02: t/voronoi permanent).
|
|
viz_default_mode: str = "auto"
|
|
# Approximate body-skeleton stroke width in pixels (multi-pass offsets;
|
|
# Metal lines are 1px). 1 = thin historical look.
|
|
skel_line_width: int = 4
|
|
|
|
# ---- iPhone OSC ------------------------------------------------------
|
|
iphone_osc_port: int = 57128
|
|
|
|
# ---- Concert / ARKit -------------------------------------------------
|
|
concert_mirror: bool = True
|
|
|
|
# ---- ICP LiDAR fusion ------------------------------------------------
|
|
icp_fusion: bool = False
|
|
icp_lidar_host: str | None = None
|
|
icp_lidar_port: int = 5500
|
|
icp_lidar_extrinsic: str | None = None
|
|
|
|
# ---- Multi-HMR -------------------------------------------------------
|
|
multihmr_backend: str = "pytorch"
|
|
multihmr_loop_fps: float = 30.0
|
|
multihmr_autocast: bool = False
|
|
multihmr_remote_host: str = "192.168.0.175"
|
|
multihmr_remote_port: int = 57140
|
|
multihmr_remote_jpeg: bool = True
|
|
multihmr_remote_jpeg_quality: int = 80
|
|
multihmr_remote_async: bool = True
|
|
multihmr_reid: str = "dino"
|
|
multihmr_reid_alpha: float = 0.5
|
|
|
|
# ---- CoreML ----------------------------------------------------------
|
|
coreml_mlpackage: str | None = None
|
|
coreml_compute_units: str = ""
|
|
|
|
# ---- OSC destinations ------------------------------------------------
|
|
avbody_host: str = "127.0.0.1"
|
|
vdmx_osc_host: str | None = None
|
|
vdmx_osc_port: int = 1234
|
|
|
|
# ---- main.py worker selection ----------------------------------------
|
|
av_live_mediapipe: bool = True
|
|
av_live_apple_vision: bool = True
|
|
av_live_coreml: bool = True
|
|
av_live_detrpose: bool = False
|
|
av_live_parallel_pose: str = "both"
|
|
mesh_rig: bool = True
|
|
|
|
# ------------------------------------------------------------------
|
|
# Factory
|
|
# ------------------------------------------------------------------
|
|
|
|
@classmethod
|
|
def from_env(cls, environ: Mapping[str, str] | None = None) -> "VizConfig":
|
|
"""Build a VizConfig by reading the given mapping (default: os.environ).
|
|
|
|
Numeric conversion errors fall back to the field default silently.
|
|
Bool conventions vary by field to match existing code exactly:
|
|
- _bool_std: not in ("0","","false","False") → True (standard)
|
|
- _bool_ne0: != "0" → True (empty string is True; None uses default)
|
|
- _bool_eq1: == "1" only (all other values → False)
|
|
- _bool_flag: in ("1","true","yes","on") → True (multihmr_remote style)
|
|
"""
|
|
env: Mapping[str, str] = environ if environ is not None else os.environ
|
|
|
|
_FALSE_STD = frozenset(("0", "", "false", "False"))
|
|
|
|
def _bool_std(name: str, default: bool) -> bool:
|
|
v = env.get(name)
|
|
if v is None:
|
|
return default
|
|
return v not in _FALSE_STD
|
|
|
|
def _bool_ne0(name: str, default: bool) -> bool:
|
|
"""Matches `env.get(name, "1") != "0"` style."""
|
|
v = env.get(name)
|
|
if v is None:
|
|
return default
|
|
return v != "0"
|
|
|
|
def _bool_eq1(name: str) -> bool:
|
|
"""Matches `env.get(name, "0") == "1"` style — only "1" is True."""
|
|
return env.get(name, "") == "1"
|
|
|
|
def _bool_flag(name: str, default: bool) -> bool:
|
|
"""Matches multihmr_remote _env_flag: in ("1","true","yes","on")."""
|
|
v = env.get(name)
|
|
if v is None:
|
|
return default
|
|
return v.strip().lower() in ("1", "true", "yes", "on")
|
|
|
|
def _int(name: str, default: int) -> int:
|
|
v = env.get(name)
|
|
if v is None:
|
|
return default
|
|
try:
|
|
return int(v)
|
|
except (ValueError, TypeError):
|
|
return default
|
|
|
|
def _float(name: str, default: float) -> float:
|
|
v = env.get(name)
|
|
if v is None:
|
|
return default
|
|
try:
|
|
return float(v)
|
|
except (ValueError, TypeError):
|
|
return default
|
|
|
|
def _str(name: str, default: str | None = None) -> str | None:
|
|
v = env.get(name)
|
|
return v if v is not None else default
|
|
|
|
def _str_lower(name: str, default: str) -> str:
|
|
v = env.get(name)
|
|
return v.lower() if v is not None else default
|
|
|
|
return cls(
|
|
mediapipe_delegate=_str_lower("MEDIAPIPE_DELEGATE", "gpu"),
|
|
video_rotate=_str_lower("VIDEO_ROTATE", "none"),
|
|
pose_ghost_min_visible=_int("POSE_GHOST_MIN_VISIBLE", 10),
|
|
pose_ghost_min_conf=_float("POSE_GHOST_MIN_CONF", 0.5),
|
|
pose_hand_min_visible=_int("POSE_HAND_MIN_VISIBLE", 15),
|
|
pose_face_min_visible=_int("POSE_FACE_MIN_VISIBLE", 50),
|
|
pose_nms_iou=_float("POSE_NMS_IOU", 0.7),
|
|
pose_filter=_str("POSE_FILTER"),
|
|
pose_filter_face=_str("POSE_FILTER_FACE"),
|
|
pose_filter_hand=_str("POSE_FILTER_HAND"),
|
|
finger_piano=_bool_std("FINGER_PIANO", False),
|
|
finger_strike_vel=_float("FINGER_STRIKE_VEL", 0.02),
|
|
finger_strike_refractory_ms=_float("FINGER_STRIKE_REFRACTORY_MS", 120.0),
|
|
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.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.05),
|
|
pinch_ext_ratio=_float("PINCH_EXT_RATIO", 1.35),
|
|
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),
|
|
hand_persist_grace=_int("HAND_PERSIST_GRACE", 2),
|
|
hand_swap_lr=_bool_std("HAND_SWAP_LR", False),
|
|
hand_near_min=_float("HAND_NEAR_MIN", 0.10),
|
|
hand_near_off=_float("HAND_NEAR_OFF", 0.08),
|
|
hand_hold_frames=_int("HAND_HOLD_FRAMES", 2),
|
|
hand_face_min=_float("HAND_FACE_MIN", 0.5),
|
|
arkit_bone_max=_float("ARKIT_BONE_MAX", 0.5),
|
|
# 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),
|
|
viz_audio_keys=_bool_std("VIZ_AUDIO_KEYS", False),
|
|
viz_default_mode=_str("VIZ_DEFAULT_MODE", "auto"),
|
|
skel_line_width=_int("SKEL_LINE_WIDTH", 4),
|
|
iphone_osc_port=_int("IPHONE_OSC_PORT", 57128),
|
|
concert_mirror=_bool_ne0("CONCERT_MIRROR", True),
|
|
# ICP_FUSION / MULTIHMR_AUTOCAST use strict "1" convention
|
|
icp_fusion=_bool_eq1("ICP_FUSION"),
|
|
icp_lidar_host=_str("ICP_LIDAR_HOST"),
|
|
icp_lidar_port=_int("ICP_LIDAR_PORT", 5500),
|
|
icp_lidar_extrinsic=_str("ICP_LIDAR_EXTRINSIC"),
|
|
multihmr_backend=(_str("MULTIHMR_BACKEND", "pytorch") or "pytorch").strip().lower(),
|
|
multihmr_loop_fps=_float("MULTIHMR_LOOP_FPS", 30.0),
|
|
multihmr_autocast=_bool_eq1("MULTIHMR_AUTOCAST"),
|
|
multihmr_remote_host=_str("MULTIHMR_REMOTE_HOST", "192.168.0.175") or "192.168.0.175",
|
|
multihmr_remote_port=_int("MULTIHMR_REMOTE_PORT", 57140),
|
|
# MULTIHMR_REMOTE_JPEG/ASYNC use _env_flag convention
|
|
multihmr_remote_jpeg=_bool_flag("MULTIHMR_REMOTE_JPEG", True),
|
|
multihmr_remote_jpeg_quality=_int("MULTIHMR_REMOTE_JPEG_QUALITY", 80),
|
|
multihmr_remote_async=_bool_flag("MULTIHMR_REMOTE_ASYNC", True),
|
|
multihmr_reid=_str_lower("MULTIHMR_REID", "dino"),
|
|
multihmr_reid_alpha=_float("MULTIHMR_REID_ALPHA", 0.5),
|
|
coreml_mlpackage=_str("COREML_MLPACKAGE"),
|
|
coreml_compute_units=(_str("COREML_COMPUTE_UNITS", "") or "").strip().lower(),
|
|
avbody_host=_str("AVBODY_HOST", "127.0.0.1") or "127.0.0.1",
|
|
vdmx_osc_host=_str("VDMX_OSC_HOST"),
|
|
vdmx_osc_port=_int("VDMX_OSC_PORT", 1234),
|
|
# AV_LIVE_* use != "0" convention (enabled by default when unset)
|
|
av_live_mediapipe=_bool_ne0("AV_LIVE_MEDIAPIPE", True),
|
|
av_live_apple_vision=_bool_ne0("AV_LIVE_APPLE_VISION", True),
|
|
av_live_coreml=_bool_ne0("AV_LIVE_COREML", True),
|
|
av_live_detrpose=_bool_eq1("AV_LIVE_DETRPOSE"),
|
|
av_live_parallel_pose=_str("AV_LIVE_PARALLEL_POSE", "both") or "both",
|
|
mesh_rig=_bool_ne0("MESH_RIG", True),
|
|
)
|