4 ottimizzazioni performance + visibilita':
GGG. find(profile=True) → timing per fase
- _checkpoint() registra ms tra: to_gray, spread_top, top_pruning,
full_kernel, refine_verify_nms
- get_last_profile() ritorna dict ms per identificare bottleneck
- Costo runtime trascurabile (~5 us per call)
HHH. pm2d.bench - benchmark suite eseguibile
- 3 scenarios (rect/L/circle x scene clean/cluttered)
- 5 configs (baseline, polarity, propagate, greedy, stride)
- Auto-aggiunge gpu_umat se opencl_available()
- Tabella ms/find + profile per ogni combo
- Entry-point pm2d-bench (--quick per smoke test 2 iter)
XX. Skip dilate per bin vuoti in _spread_bitmap
- Pre-calcolo bin presenti via np.unique sui pixel valid
- Su scene a bassa varianza orientation skip 50-70% delle dilate
- Misurato benchmark: spread_top da ~0.3ms a ~0.1ms in molti casi
VV. Variant pruning preliminare via histogramma orientation
- Per ogni variante calcolo overlap (feature bins ∩ scene bins) /
total feature bins
- Se overlap < 0.5 * min_score → skip variante (no kernel call)
- Counter n_variants_pruned_histogram nel diag
- Vantaggio: scene focalizzate (poche direzioni dominanti) skippano
varianti template con bin assenti dalla scena
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: per ogni match l'overlay edge del modello includeva anche il
PERIMETRO del template warpato (transizione bordo nero borderValue=0
→ scena = forte gradient artefatto). Con N match si vedevano N
rettangoli verdi attorno ai pezzi, simili a "ROI ripetute".
Fix:
- Warpa anche _train_mask alla pose
- Erode di (2*spread_radius+1) per scartare la fascia di transizione
bordo che produce gradient spurio
- Maschera edge_mask con warped_mask: solo edge interni al pezzo
vengono visualizzati
Risultato: overlay edge pulito che mostra solo i veri edge del
modello allineati al pezzo trovato, niente cornici fasulle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L'UCS del match precedentemente proiettava il baricentro feature
template alla pose, ma:
- Il baricentro veniva calcolato da una variante a 0° (v0) i cui dx/dy
sono offsets relativi al centro PADDED (non al centro template puro)
- _extract_features dipende dai parametri matcher che possono differire
da quelli del preview se la ricetta e' caricata
- Risultato: UCS appariva con offset costante errato rispetto al centro
visibile del pezzo
Fix: UCS sul centro POSE del match (m.cx, m.cy) = posizione del centro
template originale nella scena (questo e' esattamente cio' che
_subpixel_peak ritorna). Coerente, prevedibile, "fissato" sul centro
del pezzo.
Per coerenza visiva, anche preview_edges sposta UCS dal baricentro al
CENTRO ROI (rh/2, rw/2). Cosi' il modello mostra UCS nello stesso
identico punto relativo dove apparira' nel match dopo
traslazione+rotazione della pose.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 problemi visibili da screenshot:
1. UCS match troppo grande: usava 0.4 * lato bbox (~114 px su template
286). Anteprima modello usa 0.15 * max(lato_template) (~42 px).
Fix: stessa formula scalata per m.scale → coerenza dimensionale.
2. Asse Y match orientamento sbagliato: a m.angle_deg=0 puntava
in alto invece che in basso (errore segno trigonometrico:
sin(ax + pi/2) ≠ cos(ax) per il segno y-down).
Fix corretto:
- X axis = (cos(ax), -sin(ax)) # rotazione cv2 di (1, 0)
- Y axis = (sin(ax), cos(ax)) # rotazione cv2 di (0, 1)
Verificato: a ax=0 → X destra, Y giu' (matches modello).
3. Overlay edge modello orientato (richiesta utente): warpa template
alla pose (cx, cy, angle, scale), applica hysteresis identica al
matcher, disegna pixel edge come overlay verde brillante (60% alpha).
Permette di vedere visivamente l'allineamento del modello sul pezzo
rilevato.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Due richieste utente:
1. Param di pulizia rumore (weak/strong/num_features/spacing dal pannello
"Anteprima edge") devono essere usati anche in find e salvati nelle
ricette. Prima l'utente li regolava ma erano ignorati: il match usava
sempre i valori auto_tune.
Fix:
- SimpleMatchParams.edge_* (4 campi opzionali): None = usa auto_tune,
valore = override
- _simple_to_technical applica gli override se presenti, propagati
a min_feature_spacing nel matcher init
- Cache key matcher include min_feature_spacing
- SaveRecipeParams stessi 4 campi: la ricetta salva i param di
pulizia rumore identici a quelli del preview
- UI readEdgeOverrides() legge sempre i valori slider ed inietta
in body sia di /match_simple sia di POST /recipes
2. Match overlay sulla scena: solo UCS (X rosso, Y verde) ruotato
secondo m.angle_deg, posizionato sul baricentro feature del
modello (proiettato alla pose). Niente edge filtrati, niente
cerchietti feature, niente bbox, niente label/score sulla scena
reale: l'overlay deve essere pulito, gli edge si vedono solo
nell'anteprima modello.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug visibili da screenshot:
1. UCS match diverso da UCS anteprima modello (centro pose vs baricentro)
2. Numero feature disegnate < di quelle anteprima modello
Cause:
1. Match UCS era posto su (cx, cy) = centro template, mentre l'anteprima
modello mostra UCS sul baricentro feature (mean fx, fy).
2. _draw_matches estraeva feature dal template warpato → re-quantizza
gradient su immagine warp+interp, perdendo precisione vs feature
pre-computate del matcher.
Fix:
- Match.variant_idx: nuovo field con indice variante usata dal find()
- _draw_matches usa lvl0.dx/dy/bin pre-computati invece di re-estrarre:
* applica delta-rotation (m.angle_deg - var.angle_deg) per refine
sub-step
* proietta in scene coords intorno a (m.cx, m.cy)
* stesso identico set di feature dell'anteprima modello (modulo
rotazione+traslazione)
- UCS match calcolato sul baricentro delle feature warpate, non su
(cx, cy) → coerente con UCS anteprima
Fallback (variant_idx == -1, es. ricetta caricata da save_model
prima di questo commit): usa estrazione warpata legacy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_draw_matches ora coerente con anteprima modello:
- Edge filtrati con stessa pipeline matcher (hysteresis weak/strong_grad)
e selezione feature: l'overlay del match riflette esattamente quello
che l'utente ha visto nel preview "Anteprima edge"
- Background tinta scura su pixel hysteresis (40% colore match)
- Feature scelte come dot colorati per bin (palette 16 bin)
- UCS rosso/verde sul centro pose: asse X destra, Y giu' (image y-down),
ruotato secondo angle del match
- Origine UCS: cerchio bianco con bordo nero per visibilita'
Rimossi (richiesta utente "togli la ROI"):
- bbox poly perimetrale: ridondante, copriva il pezzo
- linea marker primo lato: sostituita da UCS rosso
Compatibilita': se matcher non passato (es. uso esterno), fallback
Canny legacy. Tutti e 3 endpoint match (/match, /match_simple,
/match_recipe) ora propagano il matcher a _draw_matches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pannello "🔬 Anteprima edge / pulizia rumore" sotto il canvas modello.
Permette tuning interattivo dei parametri di selezione edge per
togliere "sporcizie" (rumore di sfondo, edge spuri) prima di
trainare il matcher.
Server:
- POST /preview_edges: dato modello+ROI+param edge, ritorna immagine
ROI con overlay:
* heatmap magnitude gradient (sfondo)
* verde scuro: pixel sopra hysteresis edge
* cerchietti colorati per bin: feature scelte (palette 16 bin)
* UCS rosso/verde sul baricentro feature (richiesta utente):
asse X destra, Y giu' (image y-down)
Ritorna anche stats: n_features, n_edge_strong, percentili magnitude,
ucs_baricentro {cx, cy}
UI:
- Slider weak_grad/strong_grad/num_features/spacing + checkbox polarity
- Re-fetch debounced (200ms) ad ogni input → preview live
- Bottone "Applica ai parametri Avanzate": copia i valori scelti
nei campi Avanzate del matcher principale
- Auto-fetch quando il pannello viene aperto
Use case: operatore vede SUBITO quali edge il matcher userebbe,
regola soglie per escludere rumore, applica e poi MATCH.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MatchResp ora include diag dict (CC feature). UI rendering:
- Nuovo pannello pieghevole "🔍 Diagnostica" sotto i tempi
- Per ogni match mostra:
* pipeline pruning (vars total → top_eval → top_pass → full_eval)
* candidati (raw → pre_nms → final)
* drop reasons (NCC, score, recall, bbox, NMS) con counter
* soglie effettive applicate
* flag attivi (polarity, soft, subpix-LM)
- Quando 0 match → pannello si apre automaticamente + mostra hint
contestuale specifico:
* "0 candidati top" → suggerisce ↓ min_score / top_thresh
* "tutti dropped da NCC" → ↓ verify_threshold (filtro_fp)
* "score post-NCC sotto" → ↓ min_score
* "recall basso" → ↓ min_recall
* "bbox out-of-scene" → check pose / search_roi
Risolve il pattern "0 match perche'?" con guida actionable invece
del black-box. Tutti e 3 endpoint match (/match, /match_simple,
/match_recipe) propagano il diag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tool da CLI per misurare oggettivamente la qualita' del matcher
su dataset etichettato. Halcon ha questo solo nell'IDE (HDevelop),
qui esposto come modulo Python testabile in CI.
Format dataset JSON:
- template + mask
- params init matcher (override)
- find_params (override per find())
- scenes con ground_truth: lista pose attese (cx, cy, angle, scale,
tolerance_px, tolerance_deg)
Metriche per scena: TP/FP/FN, precision, recall, IoU medio bbox,
tempo find. Aggregato: precision globale, recall, F1.
Match-to-GT criterio: distanza centro <= tolerance_px AND
|angle| <= tolerance_deg, oppure IoU bbox >= 0.3.
Use case:
- regressione: confronto config A vs B oggettivo
- tuning: trovare param ottimi via grid-search guidato da F1
- validazione pre-deploy: report TP/FP/FN su dataset prod
Esposto come entry-point pm2d-eval (pyproject.toml).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manca il path "load" della V feature: utente poteva salvare ricetta
ma non caricarla dalla UI. Aggiunto:
Server:
- POST /recipes/{name}/load: carica .npz in cache _RECIPE_MATCHERS
- POST /match_recipe: usa matcher caricato senza re-train (zero
training time, solo find params propagati)
UI:
- Dropdown ricette disponibili (auto-refreshed da GET /recipes)
- Bottone "Carica" attiva ricetta + popola state.active_recipe
- Bottone "Stacca" torna al flow normale (training da ROI)
- Status indicator mostra ricetta attiva e dimensioni
doMatch dispatcha automaticamente:
- ricetta attiva → /match_recipe (no model/ROI necessari)
- altrimenti → /match o /match_simple come prima
Use case: ricetta tarata offline, deploy a runtime production senza
ricaricare modello+ROI ogni volta.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UI esponev gia' /auto_tune endpoint ma non c'era trigger user-facing.
Aggiunto bottone toolbar accanto a MATCH:
- Calcola tutti i parametri tecnici dalla ROI selezionata (gradient,
feature, piramide, angle_step, simmetria)
- Esegue self-validation training+find su template
- Applica i valori derivati ai campi della sezione Avanzate
- Mostra alert con riepilogo + meta diagnostica
(simmetria detected, self-validation result, ecc.)
Endpoint /auto_tune ora ritorna anche meta (_self_score, _validation,
_symmetry_order, _orient_entropy) per feedback UI invece di filtrarli.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nuovo helper _self_validate(): post-stima parametri, esegue dry-run
training+find sul template stesso e regola i parametri se subottimali.
Loop di auto-correzione (analogo a Halcon inspect_shape_model):
1. Se top-level piramide ha <8 feature → riduce pyramid_levels
2. Se train produce 0 varianti → dimezza weak/strong_grad
3. Se find sul template fallisce → riduce soglie + num_features
4. Se self-score < 0.7 → abbassa weak_grad
Costo: 1 train minimale (1 variante) + 1 find su canvas tpl + padding,
~50ms su template 100x100. Ne vale la pena per evitare match-time
errors su scene reali con parametri estimato male.
Esposto via auto_tune(self_validate=True) default; meta '_self_score'
e '_validation' nel dict risultato per logging UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UI espone tutti i nuovi flag tramite sezione pieghevole "Modalita Halcon"
nel pannello impostazioni. Default off = comportamento backward compat.
Flag esposti (checkbox + numerici):
- use_polarity (F): 16-bin orientation mod 2pi
- use_gpu (R): OpenCL UMat con silent fallback CPU
- use_soft_score (Y): score continuo cos(theta_t-theta_s)
- subpixel_lm (Z): refinement 0.05 px gradient field
- refine_pose_joint: Nelder-Mead 3D (cx,cy,theta)
- pyramid_propagate: top-K propagation a full-res
- min_recall (M): filtro feature-recall
- nms_iou_threshold (A): IoU bbox poligonale
- greediness: early-exit kernel
- coarse_stride: sub-sampling top-level
- search_roi: x,y,w,h area di ricerca
Persistenza ricette (V):
- Endpoint POST /recipes: training + save .npz in recipes/
- Endpoint GET /recipes: lista
- UI: campo nome + bottone "Salva" sotto i flag
Server SimpleMatchParams esteso con tutti i campi; pipeline match_simple
propaga init-flags al cache key (use_polarity/use_gpu = retrain) e
find-flags al m.find().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flag opzionale use_gpu=False/True su LineShapeMatcher e helper:
- opencl_available() per probe runtime
- set_gpu_enabled(bool) per attivare/disattivare globalmente
Quando attivo + cv2.ocl.haveOpenCL() True: Sobel + dilate +
warpAffine usano UMat con dispatch automatico kernel GPU
(Intel UHD, AMD, NVIDIA via OpenCL ICD). Speedup tipico 1.5-3x
sui filtri OpenCV (sec 1080p), gain finale ~10-15% sul total
find() perche' kernel JIT score-bitmap rimane CPU (Numba).
Path silently fallback CPU se OpenCL non disponibile (es. build
opencv-python senza ICD). Non rompe niente in ambienti non-GPU.
Per veri 20-50x speedup servirebbe kernel CUDA dedicato del
score-bitmap (out of scope, CPU + Numba e gia' molto buono).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggiunge una view extra al matcher gia addestrato. Le varianti
della nuova view vengono APPENDATE a self.variants col tag view_idx
e partecipano al pruning/matching come le altre.
NCC verify usa il template della view che ha matchato (via
_get_view_template + parametro view_idx propagato a _verify_ncc).
Halcon-equivalent: create_aniso_shape_model con fusione N viste.
Use case: pezzo che cambia aspetto (chiaro/scuro, prima/dopo
trattamento, illuminazioni diverse) → un solo matcher robusto
invece di N matcher distinti.
API:
m.train(template_chiaro)
m.add_template_view(template_scuro)
m.find(scene) # match su entrambi gli aspetti
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Halcon-equivalent write_shape_model / read_shape_model. Salva su
file .npz compresso:
- Tutti i parametri matcher (incluso use_polarity)
- Template gray + maschera training
- Tutte le varianti pre-computate (con piramide flat per scrittura
efficiente, ~12KB per template 80x80 con 28 varianti)
Caso d'uso: training offline su workstation, deploy a runtime
production senza re-train. load_model() istantaneo: skip training
(che e' il costo dominante per molte scale/angoli).
Format version 1, np.savez_compressed (zlib).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_subpixel_refine_lm: per ogni feature template, calcola normale
gradient nella scena (bilineare) e stima shift (dx, dy) globale
che minimizza errore direzionale gradient field. Iterazione damped
(max 1px/iter) per stabilita.
Halcon-equivalent SubPixel='least_squares_high'. Precisione attesa
0.05 px (vs 0.5 px del fit quadratico 2D plain). Costo: ~5ms per
match aggiuntivi (negligibile vs total find).
Default off (subpixel_lm=False, backward compat). Attivare per
applicazioni di alignment/dimensional inspection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_compute_soft_score: cos(theta_template - theta_scena) continuo
(non quantizzato a bin) pesato per magnitude. Polarity-aware se
use_polarity=True (mod 2pi) else |cos| (mod pi).
Quando use_soft_score=True (default off, backward compat), lo score
finale e' fuso con quello shape: piu discriminante per match a
piccola rotazione (penalita' graduale invece di binaria on/off).
Equivalente a Halcon Metric='use_polarity' / 'ignore_global_polarity'
in find_shape_model.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_compute_recall calcola hits/N feature template alla pose finale
(post sub-pixel refine). Equivalente Halcon MinScore originale:
quante feature shape effettivamente combaciano sul match accettato.
Param min_recall (default 0 = off, backward compat). Util quando
NCC e' alto ma poche feature reali matchano (es. match parziale
su zona di simil-tessitura). Soglia 0.7-0.9 raccomandata per
filtri stringenti.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug osservato: con precisione "veloce" (10 deg) il matching dava
risultati migliori che con "preciso" (2 deg). Causa: con step fine
ci sono molte varianti vicine, score top-level ravvicinati e:
- top_thresh = min_score * 0.5 troppo aggressivo: scartava varianti
valide che sarebbero state scelte al full-res
- coarse_angle_factor=2 (skip 1 ogni 2): col fine vicini sono quasi
identici, ma il pruning skippava la migliore
Fix: quando angle_step <= 3 deg, automatic:
- top_score_factor min 0.7 (vs default 0.5)
- coarse_angle_factor = 1 (no skip varianti)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: score finale = (shape + ncc) / 2 puo scendere sotto min_score
impostato dall'utente. La UI mostrava match con score < soglia
perche il filtro min_score era applicato solo allo shape-score
iniziale, non al risultato finale post-NCC.
Aggiunto re-check dopo averaging: scarta match con score finale
< min_score. Coerenza filtro user-facing ripristinata.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug JS: SYM_MAP[user.simmetria] || 360 trasforma il valore valido 0
(invariante = nessuna rotazione) in 360 = no simmetria. Risultato:
cambiare simmetria nel pannello avanzato non aveva effetto se
selezionato invariante; per le altre opzioni il valore passava
ma con potenziale altri valori 0 in futuro.
Sostituito con ?? per distinguere "chiave mancante" da "valore zero".
Stessa fix per PREC_MAP.
Inoltre allineato FP_MAP JS al server (medio 0.35 -> 0.50, ecc.)
per coerenza UI/backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 fix per match spuri ad alto score visti su scena reale:
1. NCC con guard varianza minima: se template-patch o scene-patch
hanno std quasi-zero (zone uniformi bianche/nere) NCC e instabile
e da false-correlation alta. Ora ritorna 0 sotto soglia varianza.
2. Reject post-bbox: se il bounding-box ruotato del match sfora
la scena per piu del 25%, scarto (centro derivato male o scala
incoerente). Tollera 25% out-of-bounds (bordi).
3. FILTRO_FP_MAP alzato: leggero 0.20→0.30, medio 0.35→0.50,
forte 0.50→0.70. Default piu conservativo per evitare match
spuri su zone con pochi edge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LineShapeMatcher.set_angle_range_around(center, tol): restringe
angle_range a (center-tol, center+tol). Use case: feeder/posizionamento
meccanico noto a priori. Esempio:
m.set_angle_range_around(0, 20) # cerca solo in [-20, +20]
auto_tune accetta angle_tolerance_deg + angle_center_deg: emette
angle_min/angle_max ristretti se hint fornito. Cache key include
hint per non collidere con tune default.
Beneficio misurato: angle_step=5 deg, template 80x80
- range 360°: 72 varianti
- range ±15°: 6 varianti (12x meno = matching ~12x piu veloce)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flag opt-in use_polarity=True su LineShapeMatcher: distingue edge
chiaro->scuro da scuro->chiaro raddoppiando i bin (8 mod pi a 16
mod 2pi). Riduce match accidentali quando il template e direzionale
ma scena ha bordo opposto (es. pezzo nero su bg chiaro vs pezzo
chiaro su bg nero).
Implementazione:
- _gradient calcola atan2 mod 2pi quando use_polarity
- _spread_bitmap usa uint16 (16 bit) invece di uint8 (8 bit)
- Nuovi kernel JIT _jit_score_bitmap_rescored_u16 e
_jit_popcount_density_u16
- Wrapper Python score_bitmap_rescored / popcount_density fanno
dispatch su dtype dello spread
Default off (use_polarity=False) = backward compat completo, 8 bin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_poly_iou via cv2.intersectConvexConvex: IoU esatto tra bbox
orientati. Sostituisce distanza-centro nel NMS post-refine.
Vantaggio: due pezzi adiacenti con centri vicini (entro nms_radius)
ma orientamenti diversi non vengono piu fusi se overlap reale e
basso. Stesso pezzo trovato da varianti angolari diverse (centri
uguali, IoU ~1) viene correttamente droppato.
Param nms_iou_threshold default 0.3. Fallback distanza centro
(r2/4) come safety per bbox degeneri.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 problemi visibili da test interattivo:
1. Match duplicati: stesso oggetto trovato da varianti angolari
diverse, NMS pre-refine non basta perche refine sposta i match.
Aggiunto NMS post-refine cross-variant.
2. Score sempre alto/saturato a 1.0: NCC era opzionale (skip>=0.85)
e non veniva mescolato nello score. Ora ncc_skip_above=1.01
(NCC sempre) e score finale = (shape + NCC) / 2: piu discriminante.
3. Angolo impreciso: _refine_angle aveva early-exit per shape-score
>= 0.99, ma quel valore satura facile (con pyramid_propagate o
spread ampio) senza garantire angolo preciso. Rimosso early-exit:
refine angolare e' sempre essenziale per orientamento sub-step.
Inoltre: pyramid_propagate default False (era True), riduce duplicati
da picchi propagati su angle-vicini. propagate_topk default 4 (era 8).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>