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:
1 parent
8b01ae123f
commit
5ab808accd
2 files changed
+152
-2
No files matched your search
@@ -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", {})
|
||||
Reference in new issue
Block a user