Files
hermes-bot/ros/hermes_face/test/test_gaze.py
T
kyleandClaude Sonnet 5.5 f8525c5526 Make the eyes follow detected faces; start the face at power-up
hermes_face now subscribes to /vision/faces and glances toward the best face (a
recognised one first, then the biggest), smoothed, holding 2.5 s and drifting
back to centre when none is seen. Sleeping ignores gaze. Image x flips on the
display: the camera looks the way the robot faces, so a person on the image's
right is on the robot's right, the viewer's left.

Fix the pan sign carried over from the prototype: positive pan is the robot's
left (the MCP contract), which is the viewer's right on the display.

robot-face.service starts the face at boot and restarts it on failure, with a
private working directory because lgpio drops a notification pipe in its cwd and
systemd's default / is not writable. robot-cpu-performance.service holds the CPU
at full clock, which halves the face's CPU cost.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFN8nRRCiDiMN7ChBpHJhs
2026-10-04 23:11:19 -07:00

28 lines
964 B
Python

from hermes_face.gaze import Target, gaze_from_pixel, pick_target
def test_centre_is_straight_ahead():
assert gaze_from_pixel(648, 486, 1296, 972) == (0.0, 0.0)
def test_person_on_the_image_right_is_the_viewers_left_on_the_display():
gx, gy = gaze_from_pixel(1296, 486, 1296, 972)
assert gx == -1.0 and gy == 0.0
def test_person_high_in_the_image_is_up_on_the_display():
assert gaze_from_pixel(648, 0, 1296, 972) == (0.0, -1.0)
def test_off_image_positions_are_clamped():
assert gaze_from_pixel(5000, -5000, 1296, 972) == (-1.0, -1.0)
def test_pick_target_prefers_a_recognised_face_then_the_biggest():
small_known = Target(100, 100, 50, 60, 0.9, 'owner')
big_unknown = Target(500, 400, 300, 400, 0.9, '')
mid_unknown = Target(300, 300, 200, 250, 0.9, '')
assert pick_target([big_unknown, small_known]) is small_known
assert pick_target([mid_unknown, big_unknown]) is big_unknown
assert pick_target([]) is None