feat: 'Filtro falsi positivi' preset user-friendly (era verify_ncc)

Rinomina il parametro tecnico verify_threshold in un preset semantico
che operatore/cliente capisce senza leggere docs:

  off      -> 0.00 (tutti i match shape-based passano)
  leggero  -> 0.20 (tollera illuminazione/riflessi)
  medio    -> 0.35 (consigliato, default)
  forte    -> 0.50 (massima selettivita, scarta mismatch intensita)

UI: dropdown etichettato 'Filtro falsi positivi (verifica intensita colori)'
accanto a precisione angolare. Override tecnico (numerico) resta in
sezione Avanzate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 10:52:26 +02:00
parent 1671a151da
commit 2bca68d700
3 changed files with 27 additions and 5 deletions
+12 -4
View File
@@ -210,6 +210,15 @@ PRECISION_ANGLE_STEP = {
"preciso": 2.0, "preciso": 2.0,
} }
# "Filtro falsi positivi" = mapping semantico del verify NCC threshold.
# Un operatore sceglie il livello di rigore, non un numero astratto.
FILTRO_FP_MAP = {
"off": 0.0, # disabilitato: mantieni tutti i match shape-based
"leggero": 0.20, # tollera variazioni intensità/illuminazione forti
"medio": 0.35, # default bilanciato (consigliato)
"forte": 0.50, # scarta match con intensità molto diversa dal template
}
class SimpleMatchParams(BaseModel): class SimpleMatchParams(BaseModel):
model_id: str model_id: str
@@ -219,7 +228,8 @@ class SimpleMatchParams(BaseModel):
simmetria: str = "nessuna" # chiave SYMMETRY_TO_ANGLE_MAX simmetria: str = "nessuna" # chiave SYMMETRY_TO_ANGLE_MAX
scala: str = "fissa" # chiave SCALE_PRESETS scala: str = "fissa" # chiave SCALE_PRESETS
precisione: str = "normale" # chiave PRECISION_ANGLE_STEP precisione: str = "normale" # chiave PRECISION_ANGLE_STEP
min_score: float = 0.70 filtro_fp: str = "medio" # chiave FILTRO_FP_MAP
min_score: float = 0.65
max_matches: int = 25 max_matches: int = 25
@@ -270,9 +280,7 @@ def _simple_to_technical(
"min_score": p.min_score, "min_score": p.min_score,
"max_matches": p.max_matches, "max_matches": p.max_matches,
"nms_radius": 0, "nms_radius": 0,
# Verify NCC più permissivo per match con variazione intensità "verify_threshold": FILTRO_FP_MAP.get(p.filtro_fp, 0.35),
# (foro su sfondo variabile, parti di oggetto ecc.)
"verify_threshold": 0.25,
} }
+3 -1
View File
@@ -47,6 +47,7 @@ function readUserParams() {
simmetria: document.getElementById("p-simmetria").value, simmetria: document.getElementById("p-simmetria").value,
scala: document.getElementById("p-scala").value, scala: document.getElementById("p-scala").value,
precisione: document.getElementById("p-precisione").value, precisione: document.getElementById("p-precisione").value,
filtro_fp: document.getElementById("p-filtro-fp").value,
min_score: parseFloat(document.getElementById("p-min-score").value), min_score: parseFloat(document.getElementById("p-min-score").value),
max_matches: parseInt(document.getElementById("p-max-matches").value, 10), max_matches: parseInt(document.getElementById("p-max-matches").value, 10),
}; };
@@ -238,6 +239,7 @@ async function doMatch() {
const SCALE_MAP = {fissa:[1,1,0.1], mini:[0.9,1.1,0.05], const SCALE_MAP = {fissa:[1,1,0.1], mini:[0.9,1.1,0.05],
medio:[0.75,1.25,0.05], max:[0.5,1.5,0.05]}; medio:[0.75,1.25,0.05], max:[0.5,1.5,0.05]};
const PREC_MAP = {veloce:10, normale:5, preciso:2}; const PREC_MAP = {veloce:10, normale:5, preciso:2};
const FP_MAP = {off:0, leggero:0.20, medio:0.35, forte:0.50};
const [smin, smax, sstep] = SCALE_MAP[user.scala]; const [smin, smax, sstep] = SCALE_MAP[user.scala];
body = { body = {
model_id: state.model.id, scene_id: state.scene.id, roi: state.roi, model_id: state.model.id, scene_id: state.scene.id, roi: state.roi,
@@ -250,7 +252,7 @@ async function doMatch() {
strong_grad: adv.strong_grad ?? 60, strong_grad: adv.strong_grad ?? 60,
spread_radius: adv.spread_radius ?? 5, spread_radius: adv.spread_radius ?? 5,
pyramid_levels: adv.pyramid_levels ?? 3, pyramid_levels: adv.pyramid_levels ?? 3,
verify_threshold: adv.verify_threshold ?? 0.4, verify_threshold: adv.verify_threshold ?? (FP_MAP[user.filtro_fp] ?? 0.35),
nms_radius: adv.nms_radius ?? 0, nms_radius: adv.nms_radius ?? 0,
}; };
} else { } else {
+12
View File
@@ -81,6 +81,18 @@
</select> </select>
</div> </div>
<div class="field">
<label>Filtro falsi positivi
<span class="hint">(verifica intensità colori)</span>
</label>
<select id="p-filtro-fp">
<option value="off">Disattivato (massimo recall)</option>
<option value="leggero">Leggero (tollera illuminazione)</option>
<option value="medio" selected>Medio (consigliato)</option>
<option value="forte">Forte (massima selettività)</option>
</select>
</div>
<div class="field"> <div class="field">
<label>Score minimo <span id="v-score">0.65</span> <label>Score minimo <span id="v-score">0.65</span>
<span class="hint">(più basso = più match anche incerti)</span> <span class="hint">(più basso = più match anche incerti)</span>