Files
Shape_Model_2D/benchmarks/test_suite.py
T
Adriano 9458173ad0 fix: robustezza web/gui/legacy (lock matcher, LRU cache, clamp ROI, overlay)
- server: lock globale matcher (race nel threadpool FastAPI), LRU su
  _IMG_CACHE e _RECIPE_MATCHERS (leak), clamp ROI in tutti gli endpoint
  (400/422 invece di crash 500, check train senza varianti),
  filtro_fp=off disabilita davvero il verify NCC, fallback FILTRO_FP_MAP
  = medio, verify_threshold ricetta allineato a 0.4, _draw_matches su
  crop locale (era warp+Sobel full-frame per ogni match), spread_radius
  default 5->4
- gui: centro overlay edge (W-1)/2 -> W/2 (coerenza col train),
  spread_radius 5->4
- matcher legacy: _angle_list include estremo, cap candidati top-level,
  save/load persiste template_gray
- auto_tune: ref centrato fuori dal loop angoli
- test_suite: check imread con errore chiaro

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

102 lines
3.5 KiB
Python

"""Test suite esaustivo su Test/*.png con varie configurazioni.
Esegue matrix (immagine, ROI completa/parziale, config) e stampa tempi/match.
"""
from __future__ import annotations
import time
from pathlib import Path
import cv2
import numpy as np
from pm2d import LineShapeMatcher
from pm2d.gui import draw_matches
TEST_DIR = Path(__file__).parent.parent / "Test"
OUT_DIR = Path("/tmp/pm2d_suite"); OUT_DIR.mkdir(exist_ok=True)
# Casi: (nome, immagine, (y0,y1,x0,x1) roi completa, (y0,y1,x0,x1) roi parziale)
CASES = [
("clip", "clip.png", ( 60, 200, 90, 290), ( 60, 135, 90, 290)),
("ruota", "rings_and_nuts.png", ( 55, 175, 90, 215), ( 55, 115, 90, 215)),
("dado", "rings_and_nuts.png", (255, 375, 40, 170), (255, 315, 40, 170)),
("lama", "razors2.png", ( 90, 370, 120, 160), ( 90, 230, 120, 160)),
]
CONFIGS = [
("fast", dict(angle_step_deg=10.0, scale_range=(1.0, 1.0),
pyramid_levels=3, num_features=64)),
("preciso", dict(angle_step_deg=5.0, scale_range=(0.5, 1.1), scale_step=0.05,
pyramid_levels=3, num_features=96)),
]
def bench(case_name: str, img_path: str, roi_box: tuple, roi_kind: str,
cfg_name: str, cfg: dict) -> dict:
scene = cv2.imread(str(TEST_DIR / img_path))
if scene is None:
# cv2.imread ritorna None silenzioso: senza check il crash arriva
# dopo, sullo slice, con un errore criptico.
raise FileNotFoundError(
f"Immagine di test non trovata o non leggibile: {TEST_DIR / img_path}")
y0, y1, x0, x1 = roi_box
roi = scene[y0:y1, x0:x1].copy()
m = LineShapeMatcher(
angle_range_deg=(0.0, 360.0),
weak_grad=30, strong_grad=60,
spread_radius=5, n_threads=4, **cfg,
)
t0 = time.time()
n_var = m.train(roi)
t_train = time.time() - t0
# warmup (prima call è JIT compile)
m.find(scene, min_score=0.55, max_matches=3, refine_angle=False)
t0 = time.time()
matches = m.find(
scene, min_score=0.55, max_matches=25, nms_radius=None,
refine_angle=True, subpixel=True, verify_threshold=0.4,
)
t_find = time.time() - t0
tag = f"{case_name}_{roi_kind}_{cfg_name}"
overlay = draw_matches(scene, matches,
template_gray=cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY))
cv2.imwrite(str(OUT_DIR / f"{tag}.png"), overlay)
return {
"case": tag,
"roi": f"{roi.shape[1]}x{roi.shape[0]}",
"variants": n_var,
"train_s": t_train,
"find_s": t_find,
"n_match": len(matches),
"score_range": (
f"{min(x.score for x in matches):.2f}..{max(x.score for x in matches):.2f}"
if matches else "-"
),
}
def main():
print(f"{'case':30s} {'roi':>9s} {'var':>4s} "
f"{'train':>6s} {'find':>6s} {'n':>3s} score")
print("-" * 85)
total_find = 0.0
for case_name, img, roi_full, roi_part in CASES:
for roi_kind, roi_box in [("full", roi_full), ("part", roi_part)]:
for cfg_name, cfg in CONFIGS:
r = bench(case_name, img, roi_box, roi_kind, cfg_name, cfg)
print(f"{r['case']:30s} {r['roi']:>9s} {r['variants']:>4d} "
f"{r['train_s']:>5.2f}s {r['find_s']:>5.2f}s "
f"{r['n_match']:>3d} {r['score_range']}")
total_find += r["find_s"]
print("-" * 85)
print(f"totale find: {total_find:.1f}s overlay salvati in {OUT_DIR}/")
if __name__ == "__main__":
main()