Files
Presa-Mano/tests/test_right_arm_move.py
T
Adriano Dal PastroandClaude Opus 4.7 1fa7d12ad7 feat(right_arm): force, camera↔polso offset, cluster filter sul centroide
Tre miglioramenti al move-toward dopo il primo uso live:

1. checkbox "force" accanto a "Muovi": bypassa max_step_m quando
   l'utente sa che il delta richiesto e' grande ma corretto (es. primo
   avvicinamento all'oggetto).

2. camera_offset_wrist_mm: il modello assumeva camera e TCP coincidenti
   (sole sign flips). La camera Nuwa e' invece montata con traslazione
   costante rispetto al polso. La formula diventa:
       delta_wrist = sign * (centroid_cam - offset_cam) + t_wrist_cam
   Esposto in config (default 0,0,0), GUI (3 input "Camera rispetto a
   polso (mm)") e in /state. Il termine compensa il fatto che muovere
   il polso porta la camera, non il TCP, sul punto di presa.

3. filter_nearest_cluster: il centroide era media di tutti i punti
   SAM21 della mask. In presenza di sfondo lontano visibile attraverso
   buchi della depth del primo piano la media veniva trascinata a
   metri di distanza. Ora si tiene il cluster piu' vicino:
       z_near = p5(Z)
       keep   = punti con Z <= z_near + cluster_depth_window_m
   Default window 0.20 m, configurabile. La risposta di /detect/wrist
   espone n_sam21_points, n_centroid_points e z_near/z_cutoff per
   diagnosi.

Aggiornata anche la persistenza: /offset accetta camera_offset_wrist_mm.
GUI: i campi Offset/Sign/CamOff rispettano "user touched" (refresh non
li cancella mentre l'utente digita).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:18:24 +02:00

120 lines
3.6 KiB
Python

"""Unit test della logica pura del move-toward (no I/O)."""
from __future__ import annotations
import numpy as np
import pytest
from presa_mano.right_arm.move import (
DeltaOversize,
apply_z_floor,
compute_delta_wrist,
)
def test_zero_offset_returns_centroid_with_signs():
delta = compute_delta_wrist(
centroid_camera_m=np.array([0.0, 0.0, 1.0]),
offset_camera_m=np.array([0.0, 0.0, 0.0]),
axis_signs=(1, 1, 1),
)
assert np.allclose(delta, [0.0, 0.0, 1.0])
def test_offset_subtracted_before_signs():
# centroid=(0,0,1) offset=(0,0,0.1) sign=(1,-1,-1) → (0, 0, -0.9)
delta = compute_delta_wrist(
centroid_camera_m=np.array([0.0, 0.0, 1.0]),
offset_camera_m=np.array([0.0, 0.0, 0.1]),
axis_signs=(1, -1, -1),
)
assert np.allclose(delta, [0.0, 0.0, -0.9])
def test_signs_applied_componentwise():
delta = compute_delta_wrist(
centroid_camera_m=np.array([0.05, 0.10, 0.20]),
offset_camera_m=np.array([0.00, 0.00, 0.00]),
axis_signs=(1, -1, -1),
)
assert np.allclose(delta, [0.05, -0.10, -0.20])
def test_camera_offset_wrist_added_after_sign_flip():
# centroid=(0,0,1) offset=(0,0,0) sign=(1,1,1) t_wc=(0.05,-0.02,0.08)
# → sign*(centroid-offset) = (0,0,1)
# → + t_wc = (0.05, -0.02, 1.08)
delta = compute_delta_wrist(
centroid_camera_m=np.array([0.0, 0.0, 1.0]),
offset_camera_m=np.array([0.0, 0.0, 0.0]),
axis_signs=(1, 1, 1),
camera_offset_wrist_m=np.array([0.05, -0.02, 0.08]),
)
assert np.allclose(delta, [0.05, -0.02, 1.08])
def test_camera_offset_wrist_default_zero_back_compat():
# Senza camera_offset_wrist_m la formula vecchia resta invariata.
delta = compute_delta_wrist(
centroid_camera_m=np.array([0.10, 0.20, 0.30]),
offset_camera_m=np.array([0.00, 0.00, 0.10]),
axis_signs=(1, -1, -1),
)
assert np.allclose(delta, [0.10, -0.20, -0.20])
def test_oversize_raises_when_not_forced():
with pytest.raises(DeltaOversize):
compute_delta_wrist(
centroid_camera_m=np.array([0.0, 0.0, 0.5]),
offset_camera_m=np.array([0.0, 0.0, 0.0]),
axis_signs=(1, 1, 1),
max_step_m=0.15,
force=False,
)
def test_oversize_passes_when_forced():
delta = compute_delta_wrist(
centroid_camera_m=np.array([0.0, 0.0, 0.5]),
offset_camera_m=np.array([0.0, 0.0, 0.0]),
axis_signs=(1, 1, 1),
max_step_m=0.15,
force=True,
)
assert np.allclose(delta, [0.0, 0.0, 0.5])
def test_z_floor_clips_when_target_below():
# wrist_before.z=0.10, delta.z=-0.30, target=-0.20, floor=0.05 → clip a 0.05
# → delta.z = 0.05 - 0.10 = -0.05
new_delta, clipped = apply_z_floor(
wrist_z_m=0.10,
delta=np.array([0.01, -0.02, -0.30]),
z_floor_m=0.05,
)
assert clipped is True
assert new_delta[2] == pytest.approx(-0.05)
# X/Y invariati
assert new_delta[0] == pytest.approx(0.01)
assert new_delta[1] == pytest.approx(-0.02)
def test_z_floor_noop_when_target_above():
new_delta, clipped = apply_z_floor(
wrist_z_m=0.30,
delta=np.array([0.0, 0.0, -0.10]),
z_floor_m=0.05,
)
assert clipped is False
assert np.allclose(new_delta, [0.0, 0.0, -0.10])
def test_z_floor_none_disables_clip():
new_delta, clipped = apply_z_floor(
wrist_z_m=0.10,
delta=np.array([0.0, 0.0, -0.50]),
z_floor_m=None,
)
assert clipped is False
assert np.allclose(new_delta, [0.0, 0.0, -0.50])