Files
Shape_Model_2D/tests/test_precision.py
Adriano 0420c4a863 test: suite pytest sintetica (GT pose note) + deps dev pytest/ruff
11 test senza dipendenza dalle immagini Test/ (non versionate):
- precisione/recall su 7 pose GT (soglie 0.2-0.5 deg, 0.3-1.0 px,
  margine 3-4x sulle misure Fase 2)
- unit: angle_list con estremi, clamp piramide, save/load roundtrip,
  no collisione cache scena, mask poligonale, find non addestrato
Config ruff in pyproject (E702/E402 idiomi del codebase esclusi).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:30:46 +00:00

57 lines
2.2 KiB
Python

"""Test di non-regressione su precisione e recall (GT sintetica).
Soglie derivate dalle misure di Fase 2 (errore mediano ~0.05 deg /
~0.08 px) con margine 3-4x per assorbire rumore tra run/macchine.
Una regressione del refine (es. score saturo, minMaxLoc sul plateau)
riporterebbe gli errori a 2-4 deg / 4 px e fa fallire i test con
margine enorme.
"""
from __future__ import annotations
import numpy as np
from pm2d import LineShapeMatcher
from tests.conftest import GT_POSES, match_errors
def _find(template, scene, step, **kw):
m = LineShapeMatcher(angle_step_deg=step, num_features=96)
m.train(template)
return m.find(scene, min_score=0.5, max_matches=10, **kw)
def test_recall_and_precision_step5(template, scene):
matches = _find(template, scene, 5.0)
errs_a, errs_p, miss = match_errors(matches, GT_POSES)
assert miss == 0, f"{miss} pose GT non trovate"
assert float(np.median(errs_a)) < 0.2, f"err angolo mediano {np.median(errs_a):.3f} deg"
assert float(np.max(errs_a)) < 0.5, f"err angolo max {np.max(errs_a):.3f} deg"
assert float(np.median(errs_p)) < 0.3, f"err posizione mediano {np.median(errs_p):.3f} px"
assert float(np.max(errs_p)) < 1.0, f"err posizione max {np.max(errs_p):.3f} px"
def test_recall_and_precision_step2(template, scene):
# Step fine: storicamente il caso peggiore (plateau con piu' varianti
# dentro la tolleranza spread → scelta variante arbitraria).
matches = _find(template, scene, 2.0)
errs_a, errs_p, miss = match_errors(matches, GT_POSES)
assert miss == 0, f"{miss} pose GT non trovate"
assert float(np.median(errs_a)) < 0.2
assert float(np.max(errs_a)) < 0.5
assert float(np.median(errs_p)) < 0.3
def test_no_false_positives(template, scene):
# max_matches alto: non devono comparire match spuri oltre le 7 pose.
matches = _find(template, scene, 5.0)
assert len(matches) <= len(GT_POSES) + 1, (
f"{len(matches)} match per {len(GT_POSES)} oggetti reali"
)
def test_full_scan_path_equivalent(template, scene):
# Il path full-scan (propagate off) deve trovare le stesse pose.
matches = _find(template, scene, 5.0, pyramid_propagate=False)
_, _, miss = match_errors(matches, GT_POSES)
assert miss == 0