Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
"""Tests for the segment_with_sam21/points_via_sam21 decomposition.
|
|
|
|
We exercise the path that the find_model endpoint will take:
|
|
segment_with_sam21(nuwa, sam21, bbox, sw) -> Sam21Segmentation
|
|
and the legacy wrapper still works.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import cv2
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from presa_mano.perception.sam21_bridge import (
|
|
Sam21Segmentation,
|
|
segment_with_sam21,
|
|
points_via_sam21,
|
|
Sam21Fallback,
|
|
)
|
|
from presa_mano.timing import Stopwatch
|
|
|
|
|
|
def _fake_nuwa():
|
|
"""Returns a MagicMock Nuwa that yields a 4x4 black RGB jpeg and a 4x4
|
|
uint16 depth PNG (all 1000 mm) + a minimal Nuwa intrinsics dict."""
|
|
rgb = np.zeros((4, 4, 3), dtype=np.uint8)
|
|
ok, jpg = cv2.imencode(".jpg", rgb)
|
|
assert ok
|
|
depth = np.full((4, 4), 1000, dtype=np.uint16)
|
|
ok, dpng = cv2.imencode(".png", depth)
|
|
assert ok
|
|
intr = {
|
|
"rgb": {"fx": 600.0, "fy": 600.0, "cx": 2.0, "cy": 2.0},
|
|
"ir": {"fx": 600.0, "fy": 600.0, "cx": 2.0, "cy": 2.0,
|
|
"extrinsics": {"R": [[1,0,0],[0,1,0],[0,0,1]],
|
|
"t": [0.0, 0.0, 0.0]}},
|
|
}
|
|
n = MagicMock()
|
|
n.rgb_jpeg.return_value = bytes(jpg)
|
|
n.depth_png.return_value = bytes(dpng)
|
|
n.intrinsics.return_value = intr
|
|
return n, bytes(jpg)
|
|
|
|
|
|
def _fake_sam21(points_m: np.ndarray):
|
|
s = MagicMock()
|
|
s.segment_pointcloud.return_value = points_m
|
|
return s
|
|
|
|
|
|
def test_segment_with_sam21_returns_segmentation(monkeypatch):
|
|
nuwa, rgb_jpeg = _fake_nuwa()
|
|
pts_m = np.array([[0.01, 0.02, 0.5], [0.011, 0.022, 0.49]])
|
|
sam21 = _fake_sam21(pts_m)
|
|
# Niente plane filter qui — segment_with_sam21 NON deve applicarlo
|
|
monkeypatch.setattr(
|
|
"presa_mano.perception.sam21_bridge.get_work_plane", lambda: None
|
|
)
|
|
sw = Stopwatch()
|
|
seg = segment_with_sam21(nuwa, sam21, bbox=(0, 0, 4, 4), sw=sw)
|
|
assert isinstance(seg, Sam21Segmentation)
|
|
np.testing.assert_array_equal(seg.points_cv_m, pts_m)
|
|
assert seg.rgb_jpeg == rgb_jpeg
|
|
assert "width" in seg.intrinsics_rgb and "height" in seg.intrinsics_rgb
|
|
|
|
|
|
def test_points_via_sam21_still_applies_plane_filter(monkeypatch):
|
|
"""L'API legacy non deve cambiare: ritorna (pts, plane_info) e applica
|
|
apply_below_plane_filter come prima."""
|
|
nuwa, _rgb = _fake_nuwa()
|
|
pts_m = np.array([[0.0, 0.0, 0.5]] * 20)
|
|
sam21 = _fake_sam21(pts_m)
|
|
monkeypatch.setattr(
|
|
"presa_mano.perception.sam21_bridge.get_work_plane", lambda: None
|
|
)
|
|
sw = Stopwatch()
|
|
pts, info = points_via_sam21(nuwa, sam21, bbox=(0, 0, 4, 4), sw=sw)
|
|
assert pts.shape == (20, 3)
|
|
assert info["plane_applied"] is False
|
|
assert info["n_points_before_plane"] == 20
|
|
|
|
|
|
def test_points_via_sam21_too_few_points_raises_fallback(monkeypatch):
|
|
nuwa, _rgb = _fake_nuwa()
|
|
pts_m = np.array([[0.0, 0.0, 0.5]] * 5) # <10
|
|
sam21 = _fake_sam21(pts_m)
|
|
monkeypatch.setattr(
|
|
"presa_mano.perception.sam21_bridge.get_work_plane", lambda: None
|
|
)
|
|
sw = Stopwatch()
|
|
with pytest.raises(Sam21Fallback):
|
|
points_via_sam21(nuwa, sam21, bbox=(0, 0, 4, 4), sw=sw)
|