From cc7d035f663a0d5f0e0f3132b52fcf72dd223678 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Fri, 24 Apr 2026 14:37:36 +0200 Subject: [PATCH] feat: scale_penalty - score riflette dimensione oltre a forma Shape matching e invariante scala per design: 3 ruote dentate di dim diverse avevano tutte score 1.00 confondendo l operatore. Parametro scale_penalty [0..1]: score_final = score * max(0, 1 - penalty * |scale - 1|) UI dropdown 'Peso dimensione nel score' con preset 0 / 0.3 / 0.5 / 0.8. Test rings con penalty 0.5: 1.00 -> 1.00, 0.95 -> 0.97, 0.80 -> 0.90. Co-Authored-By: Claude Opus 4.7 (1M context) --- pm2d/line_matcher.py | 13 +++++++++++++ pm2d/web/server.py | 3 +++ pm2d/web/static/app.js | 2 ++ pm2d/web/static/index.html | 12 ++++++++++++ 4 files changed, 30 insertions(+) diff --git a/pm2d/line_matcher.py b/pm2d/line_matcher.py index 6f5a669..7520a12 100644 --- a/pm2d/line_matcher.py +++ b/pm2d/line_matcher.py @@ -548,7 +548,15 @@ class LineShapeMatcher: verify_ncc: bool = True, verify_threshold: float = 0.4, coarse_angle_factor: int = 2, + scale_penalty: float = 0.0, ) -> list[Match]: + """ + scale_penalty: se > 0, riduce lo score per match a scala diversa da 1.0: + score_final = score_shape * max(0, 1 - scale_penalty * |scale - 1|) + Utile se l'operatore vuole che match "identico al template anche per + dimensione" abbia score più alto di match "stessa forma, dimensione + diversa". scale_penalty=0 (default) = comportamento shape puro. + """ if not self.variants: raise RuntimeError("Matcher non addestrato: chiamare train() prima.") @@ -746,6 +754,11 @@ class LineShapeMatcher: poly = _oriented_bbox_polygon( cx_f, cy_f, tw * var.scale, th * var.scale, ang_f, ) + # Penalità scala opzionale: score degrada con distanza da 1.0 + if scale_penalty > 0.0 and var.scale != 1.0: + score_f = float(score_f) * max( + 0.0, 1.0 - scale_penalty * abs(var.scale - 1.0) + ) kept.append(Match( cx=cx_f, cy=cy_f, angle_deg=ang_f, diff --git a/pm2d/web/server.py b/pm2d/web/server.py index 517583a..2534697 100644 --- a/pm2d/web/server.py +++ b/pm2d/web/server.py @@ -264,6 +264,7 @@ class SimpleMatchParams(BaseModel): scala: str = "fissa" # chiave SCALE_PRESETS precisione: str = "normale" # chiave PRECISION_ANGLE_STEP filtro_fp: str = "medio" # chiave FILTRO_FP_MAP + penalita_scala: float = 0.0 # 0 = score shape invariante, >0 = penalizza scala != 1 min_score: float = 0.65 max_matches: int = 25 @@ -316,6 +317,7 @@ def _simple_to_technical( "max_matches": p.max_matches, "nms_radius": 0, "verify_threshold": FILTRO_FP_MAP.get(p.filtro_fp, 0.35), + "scale_penalty": p.penalita_scala, } @@ -502,6 +504,7 @@ def match_simple(p: SimpleMatchParams): matches = m.find( scene, min_score=tech["min_score"], max_matches=tech["max_matches"], nms_radius=nms, verify_threshold=tech["verify_threshold"], + scale_penalty=tech.get("scale_penalty", 0.0), ) t_find = time.time() - t0 diff --git a/pm2d/web/static/app.js b/pm2d/web/static/app.js index 186053d..b719abd 100644 --- a/pm2d/web/static/app.js +++ b/pm2d/web/static/app.js @@ -48,6 +48,8 @@ function readUserParams() { scala: document.getElementById("p-scala").value, precisione: document.getElementById("p-precisione").value, filtro_fp: document.getElementById("p-filtro-fp").value, + penalita_scala: parseFloat( + document.getElementById("p-penalita-scala").value), min_score: parseFloat(document.getElementById("p-min-score").value), max_matches: parseInt(document.getElementById("p-max-matches").value, 10), }; diff --git a/pm2d/web/static/index.html b/pm2d/web/static/index.html index eb2f963..b47555a 100644 --- a/pm2d/web/static/index.html +++ b/pm2d/web/static/index.html @@ -101,6 +101,18 @@ +
+ + +
+