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.
This commit is contained in:
@@ -139,6 +139,7 @@ class ActionHeadPublisher(threading.Thread):
|
||||
idx = LABELS.index(label)
|
||||
self.bridge.send_action(pid, idx, probs, t_now, force=True)
|
||||
self.bridge.send_kin(pid, kin, t_now, force=True)
|
||||
self.bridge.send_mouth(pid, mouth)
|
||||
if pid not in self._last_pids:
|
||||
self.bridge.send_enter(pid=pid)
|
||||
for gone in self._last_pids - current_pids:
|
||||
|
||||
@@ -10,6 +10,7 @@ Routes emises :
|
||||
/pose/head <pid> <x> <y> <c> position du nez (visage)
|
||||
/pose/sho_span <pid> <dx> ecart epaules (estime distance camera)
|
||||
/pose/limb_span <pid> <span> envergure brassse (poignet a poignet)
|
||||
/pose/mouth <pid> <open> mouth openness 0..1
|
||||
/face/count <n> nombre de visages detectes
|
||||
/face/kp <pid> <idx> <x> <y> <z> <c> 68-pt subset (dlib mapping)
|
||||
/hand/count <n_left> <n_right> nombre de mains gauche / droite
|
||||
@@ -350,6 +351,15 @@ class PoseSoundBridge:
|
||||
self._client.send_message("/pose/kin", kin_args)
|
||||
self._vj("/pose/kin", kin_args)
|
||||
|
||||
def send_mouth(self, pid: int, mouth_open: float) -> None:
|
||||
"""Send mouth openness via /pose/mouth OSC route.
|
||||
|
||||
Sends: [pid (int), mouth_open (float)] — mouth_open is 0..1.
|
||||
"""
|
||||
args = [int(pid), float(mouth_open)]
|
||||
self._client.send_message("/pose/mouth", args)
|
||||
self._vj("/pose/mouth", args)
|
||||
|
||||
@staticmethod
|
||||
def _hand_slot(h):
|
||||
if not h:
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Tests for /pose/mouth OSC route."""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
def test_send_mouth_address_and_args() -> None:
|
||||
from data_only_viz.pose_bridge import PoseSoundBridge
|
||||
b = PoseSoundBridge()
|
||||
b._client = MagicMock()
|
||||
b._vdmx = None
|
||||
b.send_mouth(pid=5, mouth_open=0.42)
|
||||
b._client.send_message.assert_called_once()
|
||||
address, args = b._client.send_message.call_args.args
|
||||
assert address == "/pose/mouth"
|
||||
assert args[0] == 5
|
||||
assert isinstance(args[0], int)
|
||||
assert abs(args[1] - 0.42) < 1e-6
|
||||
assert isinstance(args[1], float)
|
||||
|
||||
|
||||
def test_send_mouth_vj_mirror() -> None:
|
||||
"""VJ mirror receives the same address and args."""
|
||||
from data_only_viz.pose_bridge import PoseSoundBridge
|
||||
b = PoseSoundBridge()
|
||||
b._client = MagicMock()
|
||||
b._vdmx = MagicMock()
|
||||
b.send_mouth(pid=1, mouth_open=0.75)
|
||||
vj_call = b._vdmx.send_message.call_args.args
|
||||
assert vj_call[0] == "/pose/mouth"
|
||||
assert vj_call[1][0] == 1
|
||||
assert abs(vj_call[1][1] - 0.75) < 1e-6
|
||||
|
||||
|
||||
def test_send_mouth_zero_and_one_clamp() -> None:
|
||||
"""Boundary values: 0.0 and 1.0 are forwarded without change."""
|
||||
from data_only_viz.pose_bridge import PoseSoundBridge
|
||||
b = PoseSoundBridge()
|
||||
b._client = MagicMock()
|
||||
b._vdmx = None
|
||||
b.send_mouth(pid=0, mouth_open=0.0)
|
||||
_, args0 = b._client.send_message.call_args.args
|
||||
assert args0[1] == 0.0
|
||||
|
||||
b._client.reset_mock()
|
||||
b.send_mouth(pid=0, mouth_open=1.0)
|
||||
_, args1 = b._client.send_message.call_args.args
|
||||
assert args1[1] == 1.0
|
||||
Reference in New Issue
Block a user