feat(pose_3d): POST /align fit Umeyama cross-camera + persistenza

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AdrianoandClaude Sonnet 4.6 committed 2026-06-03 12:28:05 +02:00
1 parent 8b01ae123f
commit 5ab808accd
2 files changed
+152 -2

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
"""Test POST /api/pose_3d/align: fit Umeyama + persistenza config."""
from __future__ import annotations
from unittest.mock import patch, MagicMock
import numpy as np
from fastapi.testclient import TestClient
from presa_mano.main import app
from presa_mano.config import get_config
client = TestClient(app)
_CORNERS_PX = [[100, 100], [140, 100], [140, 140], [100, 140]]
def _cam_with_points(points_by_pixel):
cam = MagicMock()
cam.intrinsics.return_value = {"rgb": {"fx": 600.0, "fy": 600.0, "cx": 320.0, "cy": 240.0}}
cam.rgb_url.return_value = "http://cam/rgb.png"
def _measure(u, v, radius=2):
key = (int(u), int(v))
if key in points_by_pixel:
x, y, z = points_by_pixel[key]
else:
xs = [p[0] for p in points_by_pixel.values()]
ys = [p[1] for p in points_by_pixel.values()]
zs = [p[2] for p in points_by_pixel.values()]
x, y, z = sum(xs)/len(xs), sum(ys)/len(ys), sum(zs)/len(zs)
return {"point_m": {"x": x, "y": y, "z": z}, "depth_mm": int(z * 1000)}
cam.measure.side_effect = _measure
return cam
def _marker(found_ids):
mk = MagicMock()
mk.detect_url.return_value = {
"found": [{"id": i, "corners": _CORNERS_PX} for i in found_ids],
"not_found": [],
}
return mk
def test_align_fits_and_persists():
nuwa_pts = {
(100, 100): (0.10, 0.10, 0.30),
(140, 100): (0.14, 0.10, 0.30),
(140, 140): (0.14, 0.14, 0.30),
(100, 140): (0.10, 0.14, 0.30),
}
d435_pts = {k: (v[0]*2, v[1]*2, v[2]*2) for k, v in nuwa_pts.items()}
with patch("presa_mano.api.pose_3d_routes._deps.nuwa") as nuwa_f, \
patch("presa_mano.api.pose_3d_routes._deps.front_camera") as front_f, \
patch("presa_mano.api.pose_3d_routes._deps.marker") as mk_f:
nuwa_f.return_value = _cam_with_points(nuwa_pts)
front_f.return_value = _cam_with_points(d435_pts)
mk_f.return_value = _marker([7])
r = client.post("/api/pose_3d/align?ids=0-9&align_id=7")
assert r.status_code == 200
body = r.json()
assert body["found_in_both"] is True
assert abs(body["scale"] - 2.0) < 1e-3
assert body["align_id"] == 7
cc = get_config()["pose_3d"]["cross_camera"]
assert abs(cc["scale"] - 2.0) < 1e-3
assert cc["align_id"] == 7
assert len(cc["R"]) == 3 and len(cc["t"]) == 3
def test_align_tag_missing_in_one_camera_does_not_save():
pts = {(100,100):(0.1,0.1,0.3),(140,100):(0.14,0.1,0.3),
(140,140):(0.14,0.14,0.3),(100,140):(0.1,0.14,0.3)}
with patch("presa_mano.api.pose_3d_routes._deps.nuwa") as nuwa_f, \
patch("presa_mano.api.pose_3d_routes._deps.front_camera") as front_f, \
patch("presa_mano.api.pose_3d_routes._deps.marker") as mk_f:
nuwa_f.return_value = _cam_with_points(pts)
front_f.return_value = _cam_with_points(pts)
mk_f.return_value = _marker([1]) # nessun tag id=99
r = client.post("/api/pose_3d/align?ids=0-9&align_id=99")
assert r.status_code == 200
body = r.json()
assert body["found_in_both"] is False
assert body.get("error")
assert "cross_camera" not in get_config().get("pose_3d", {})