Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dae49eb4a3 | |||
| 9218cb2741 | |||
| 159f9089a5 |
@@ -8,3 +8,5 @@ __pycache__/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.log
|
*.log
|
||||||
models/
|
models/
|
||||||
|
# Ricette pre-trained (generate da utente, non versionare)
|
||||||
|
recipes/*.npz
|
||||||
|
|||||||
@@ -1309,6 +1309,7 @@ class LineShapeMatcher:
|
|||||||
min_recall: float = 0.0,
|
min_recall: float = 0.0,
|
||||||
use_soft_score: bool = False,
|
use_soft_score: bool = False,
|
||||||
subpixel_lm: bool = False,
|
subpixel_lm: bool = False,
|
||||||
|
debug: bool = False,
|
||||||
) -> list[Match]:
|
) -> list[Match]:
|
||||||
"""
|
"""
|
||||||
scale_penalty: se > 0, riduce lo score per match a scala diversa da 1.0:
|
scale_penalty: se > 0, riduce lo score per match a scala diversa da 1.0:
|
||||||
@@ -1326,6 +1327,32 @@ class LineShapeMatcher:
|
|||||||
if not self.variants:
|
if not self.variants:
|
||||||
raise RuntimeError("Matcher non addestrato: chiamare train() prima.")
|
raise RuntimeError("Matcher non addestrato: chiamare train() prima.")
|
||||||
|
|
||||||
|
# Diagnostic counter: traccia perche' candidati sono droppati lungo
|
||||||
|
# la pipeline. Esposto via get_last_diag() o ritornato implicitamente
|
||||||
|
# se debug=True (vedi sotto).
|
||||||
|
diag = {
|
||||||
|
"n_variants_total": len(self.variants),
|
||||||
|
"n_variants_top_evaluated": 0,
|
||||||
|
"n_variants_top_passed": 0,
|
||||||
|
"n_variants_full_evaluated": 0,
|
||||||
|
"n_raw_candidates": 0,
|
||||||
|
"n_after_pre_nms": 0,
|
||||||
|
"drop_ncc_low": 0,
|
||||||
|
"drop_min_score_post_avg": 0,
|
||||||
|
"drop_recall_low": 0,
|
||||||
|
"drop_bbox_out_of_scene": 0,
|
||||||
|
"drop_nms_iou": 0,
|
||||||
|
"n_final": 0,
|
||||||
|
"top_thresh_used": 0.0,
|
||||||
|
"verify_threshold_used": float(verify_threshold),
|
||||||
|
"min_score_used": float(min_score),
|
||||||
|
"min_recall_used": float(min_recall),
|
||||||
|
"use_polarity": bool(self.use_polarity),
|
||||||
|
"use_soft_score": bool(use_soft_score),
|
||||||
|
"subpixel_lm": bool(subpixel_lm),
|
||||||
|
}
|
||||||
|
self._last_diag = diag
|
||||||
|
|
||||||
gray_full = self._to_gray(scene_bgr)
|
gray_full = self._to_gray(scene_bgr)
|
||||||
# Applica ROI di ricerca: restringe scena a crop, ricorda offset per
|
# Applica ROI di ricerca: restringe scena a crop, ricorda offset per
|
||||||
# ri-traslare le coordinate dei match a fine pipeline.
|
# ri-traslare le coordinate dei match a fine pipeline.
|
||||||
@@ -1368,6 +1395,7 @@ class LineShapeMatcher:
|
|||||||
top_factor = max(top_factor, 0.7)
|
top_factor = max(top_factor, 0.7)
|
||||||
cf_eff = 1
|
cf_eff = 1
|
||||||
top_thresh = min_score * top_factor
|
top_thresh = min_score * top_factor
|
||||||
|
diag["top_thresh_used"] = float(top_thresh)
|
||||||
|
|
||||||
tw, th = self.template_size
|
tw, th = self.template_size
|
||||||
density_top = _jit_popcount(spread_top)
|
density_top = _jit_popcount(spread_top)
|
||||||
@@ -1453,6 +1481,7 @@ class LineShapeMatcher:
|
|||||||
|
|
||||||
kept_coarse: list[tuple[int, float]] = []
|
kept_coarse: list[tuple[int, float]] = []
|
||||||
all_top_scores: list[tuple[int, float]] = []
|
all_top_scores: list[tuple[int, float]] = []
|
||||||
|
diag["n_variants_top_evaluated"] = len(coarse_idx_list)
|
||||||
# batch_top: usa kernel batch single-call con prange-esterno su
|
# batch_top: usa kernel batch single-call con prange-esterno su
|
||||||
# varianti. Vince su threadpool quando n_vars >> n_threads e quando
|
# varianti. Vince su threadpool quando n_vars >> n_threads e quando
|
||||||
# H*W top e' piccolo (overhead chiamate JIT > costo kernel).
|
# H*W top e' piccolo (overhead chiamate JIT > costo kernel).
|
||||||
@@ -1516,6 +1545,8 @@ class LineShapeMatcher:
|
|||||||
kept_variants.sort(key=lambda t: -t[1])
|
kept_variants.sort(key=lambda t: -t[1])
|
||||||
max_vars_full = max(max_matches * 8, len(self.variants) // 2)
|
max_vars_full = max(max_matches * 8, len(self.variants) // 2)
|
||||||
kept_variants = kept_variants[:max_vars_full]
|
kept_variants = kept_variants[:max_vars_full]
|
||||||
|
diag["n_variants_top_passed"] = len(kept_coarse)
|
||||||
|
diag["n_variants_full_evaluated"] = len(kept_variants)
|
||||||
|
|
||||||
# Full-res (parallelizzato) con bitmap
|
# Full-res (parallelizzato) con bitmap
|
||||||
spread0 = self._spread_bitmap(gray0)
|
spread0 = self._spread_bitmap(gray0)
|
||||||
@@ -1601,6 +1632,7 @@ class LineShapeMatcher:
|
|||||||
raw.append((float(vals[i]), int(xs[i]), int(ys[i]), vi))
|
raw.append((float(vals[i]), int(xs[i]), int(ys[i]), vi))
|
||||||
|
|
||||||
raw.sort(key=lambda c: -c[0])
|
raw.sort(key=lambda c: -c[0])
|
||||||
|
diag["n_raw_candidates"] = len(raw)
|
||||||
|
|
||||||
# Mappa vi → score_map per subpixel/refinement
|
# Mappa vi → score_map per subpixel/refinement
|
||||||
score_maps = dict(candidates_per_var)
|
score_maps = dict(candidates_per_var)
|
||||||
@@ -1632,6 +1664,7 @@ class LineShapeMatcher:
|
|||||||
preliminary_int.append((score, xi, yi, vi))
|
preliminary_int.append((score, xi, yi, vi))
|
||||||
if len(preliminary_int) >= pre_cap:
|
if len(preliminary_int) >= pre_cap:
|
||||||
break
|
break
|
||||||
|
diag["n_after_pre_nms"] = len(preliminary_int)
|
||||||
|
|
||||||
# Subpixel + refine + verify solo sui candidati pre-NMS (max pre_cap)
|
# Subpixel + refine + verify solo sui candidati pre-NMS (max pre_cap)
|
||||||
kept: list[Match] = []
|
kept: list[Match] = []
|
||||||
@@ -1678,6 +1711,7 @@ class LineShapeMatcher:
|
|||||||
view_idx=getattr(var, "view_idx", 0),
|
view_idx=getattr(var, "view_idx", 0),
|
||||||
)
|
)
|
||||||
if ncc < verify_threshold:
|
if ncc < verify_threshold:
|
||||||
|
diag["drop_ncc_low"] += 1
|
||||||
continue
|
continue
|
||||||
score_f = (float(score_f) + max(0.0, ncc)) * 0.5
|
score_f = (float(score_f) + max(0.0, ncc)) * 0.5
|
||||||
# Soft-margin gradient similarity: sostituisce o integra lo
|
# Soft-margin gradient similarity: sostituisce o integra lo
|
||||||
@@ -1692,6 +1726,7 @@ class LineShapeMatcher:
|
|||||||
# abbattere lo shape-score sotto la soglia user. Senza questo
|
# abbattere lo shape-score sotto la soglia user. Senza questo
|
||||||
# check apparivano match con score < min_score (UI confusing).
|
# check apparivano match con score < min_score (UI confusing).
|
||||||
if float(score_f) < min_score:
|
if float(score_f) < min_score:
|
||||||
|
diag["drop_min_score_post_avg"] += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Feature recall (Halcon MinScore-style): conta quante feature
|
# Feature recall (Halcon MinScore-style): conta quante feature
|
||||||
@@ -1703,6 +1738,7 @@ class LineShapeMatcher:
|
|||||||
spread0, var, cx_f, cy_f, ang_f,
|
spread0, var, cx_f, cy_f, ang_f,
|
||||||
)
|
)
|
||||||
if recall < min_recall:
|
if recall < min_recall:
|
||||||
|
diag["drop_recall_low"] += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Ri-traslo coord da spazio crop ROI a spazio scena originale.
|
# Ri-traslo coord da spazio crop ROI a spazio scena originale.
|
||||||
@@ -1726,6 +1762,7 @@ class LineShapeMatcher:
|
|||||||
)
|
)
|
||||||
inside_ratio = float(inter) / poly_area
|
inside_ratio = float(inter) / poly_area
|
||||||
if inside_ratio < 0.75:
|
if inside_ratio < 0.75:
|
||||||
|
diag["drop_bbox_out_of_scene"] += 1
|
||||||
continue
|
continue
|
||||||
# Penalità scala opzionale: score degrada con distanza da 1.0
|
# Penalità scala opzionale: score degrada con distanza da 1.0
|
||||||
if scale_penalty > 0.0 and var.scale != 1.0:
|
if scale_penalty > 0.0 and var.scale != 1.0:
|
||||||
@@ -1750,6 +1787,7 @@ class LineShapeMatcher:
|
|||||||
dup = True
|
dup = True
|
||||||
break
|
break
|
||||||
if dup:
|
if dup:
|
||||||
|
diag["drop_nms_iou"] += 1
|
||||||
continue
|
continue
|
||||||
kept.append(Match(
|
kept.append(Match(
|
||||||
cx=cx_out, cy=cy_out,
|
cx=cx_out, cy=cy_out,
|
||||||
@@ -1760,4 +1798,35 @@ class LineShapeMatcher:
|
|||||||
))
|
))
|
||||||
if len(kept) >= max_matches:
|
if len(kept) >= max_matches:
|
||||||
break
|
break
|
||||||
|
diag["n_final"] = len(kept)
|
||||||
|
if debug:
|
||||||
|
# Debug mode: stampa diagnostica su stderr per visibilita' immediata.
|
||||||
|
import sys as _sys
|
||||||
|
_sys.stderr.write(f"[pm2d.find debug] {self._format_diag(diag)}\n")
|
||||||
return kept
|
return kept
|
||||||
|
|
||||||
|
def _format_diag(self, diag: dict) -> str:
|
||||||
|
"""Formatta dict diagnostica in una linea leggibile."""
|
||||||
|
return (
|
||||||
|
f"vars: {diag['n_variants_total']} -> "
|
||||||
|
f"top_eval={diag['n_variants_top_evaluated']} "
|
||||||
|
f"top_pass={diag['n_variants_top_passed']} "
|
||||||
|
f"full_eval={diag['n_variants_full_evaluated']} | "
|
||||||
|
f"raw={diag['n_raw_candidates']} "
|
||||||
|
f"pre_nms={diag['n_after_pre_nms']} -> "
|
||||||
|
f"drop[ncc={diag['drop_ncc_low']}, "
|
||||||
|
f"score={diag['drop_min_score_post_avg']}, "
|
||||||
|
f"recall={diag['drop_recall_low']}, "
|
||||||
|
f"bbox={diag['drop_bbox_out_of_scene']}, "
|
||||||
|
f"nms={diag['drop_nms_iou']}] = "
|
||||||
|
f"final={diag['n_final']} (top_thresh={diag['top_thresh_used']:.2f})"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_last_diag(self) -> dict | None:
|
||||||
|
"""Ritorna dict diagnostica dell'ultima chiamata find().
|
||||||
|
|
||||||
|
Halcon-equivalent: oggi inspect_shape_model espone parziali contatori.
|
||||||
|
Util per debug 'perche' 0 match', tuning interattivo, validation.
|
||||||
|
Vedi diag keys per significato (n_variants_top_evaluated, drop_*, ...).
|
||||||
|
"""
|
||||||
|
return getattr(self, "_last_diag", None)
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user