Causa principale: erode di (2*spread_radius+1) sulla maschera warpata
toglieva troppo bordo. Per spread_radius=8 → kernel 17x17 = -8px da
ogni lato. L'edge map applicata sopra mostrava i bordi spostati di ~8px
verso l'interno del pezzo, creando apparente "traslazione fissa".
Soluzione: erode 3x3 solo per rimuovere ~1px di bordo nero residuo
da warpAffine borderValue=0 (artefatto di padding). Bordi del pezzo
ora visualizzati nelle posizioni corrette.
Bonus fix: cx_t calcolato come w/2 invece di (w-1)/2, coerente con
center=diag/2.0 usato in training (era 0.5px di shift residuo per
template di lato pari).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug visibili dallo screenshot:
1. Rallentamento sostanziale: il fix precedente aggiungeva 16 iter golden
(era 8) + 3 chiamate parabolic fit = ~19 _score_at_angle vs 11 prima.
2. Asse Y dell'UCS invisibile sul match: edge overlay era verde brillante
(0,220,0) e si sovrapponeva esattamente al verde dell'asse Y dell'UCS.
3. Angolo non corretto: il parabolic fit finale era instabile su template
simmetrici (multiple local max ravvicinati lo facevano divergere fuori
dal vero picco trovato dal golden).
Fix:
- _refine_angle: 10 iter golden con tol 0.05 (compromesso tra precisione
e velocita'). Rimosso parabolic fit finale instabile. search_radius
resta a step pieno (utile per recuperare estremi del bin).
- Edge overlay color: ciano (BGR 255,200,0) invece di verde brillante.
L'asse Y verde dell'UCS ora ben visibile sopra l'overlay.
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>
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>
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>
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>
POST /upload_to_folder: sanitizza nome, valida estensione e contenuto
via cv2.imdecode, auto-rename su collisione.
Toolbar UI: bottone 'Carica file', dopo upload ricarica picker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
V1 Coarse-to-fine angolare:
- Al top-level valuta solo 1 variante ogni coarse_angle_factor (default 2)
- Espande ai vicini nel full-res per preservare accuracy
- Safe anche per template allungati (factor=2 non perde match)
V11 Cache matcher in-memory (LRU, capacita 8):
- Key = md5(ROI bytes + params tecnici che influenzano il training)
- Re-match con stessi parametri: train_time = 0s (era 0.5-1.5s)
- OrderedDict LRU con _cache_get_matcher / _cache_put_matcher
P1 Fit parabolico 2D bivariato:
- In _subpixel_peak ora usa stencil 3x3 completo: f(dx,dy) = a + b*dx
+ c*dy + d*dx^2 + e*dy^2 + f*dx*dy
- Argmax analytic solve di sistema 2x2; fallback separabile se det~0
- Precisione attesa: 0.1-0.3 px (era 0.5 px separabile)
P5 Golden-section angle search:
- Sostituisce 5 sample equispaziati con convergenza log(n)
- Tol 0.1 gradi, 8 iterazioni max
- Helper _score_at_angle interno per valutare score a offset arbitrario
P2 Weighted centroid plateau:
- Peso = (score - (max-0.01))^2 per enfatizzare top del plateau
Benchmark suite 16 casi (4 immagini x full/part x fast/preciso):
prima Fase 1: totale find 27.3s
dopo Fase 1: totale find 25.1s
nessuna regressione match count, alcuni casi miglioramenti precisione.
ROADMAP.md aggiornato con checklist Fase 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- GET /folder_image/{filename}?w=N: PNG ridotto cache 1h
- Frontend: 2 thumb-picker al posto dei select (thumb + nome + caret)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- .env con IMAGES_DIR=Test
- server: _load_env legge .env senza dip extra
- GET /images lista file, POST /load_from_folder carica per nome
- frontend: file picker sostituiti con 2 select popolati all avvio
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Test tooth_rim foro grande: 12x piu veloce (0.14s vs 1.77s) perche
angle_max=0 genera 1 sola variante angolare invece di 72.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: _IMAGES era dict in-memory, restart server → browser con id cached
riceveva 404 'Immagini non trovate'.
Fix: scrittura PNG in /tmp/pm2d_cache/{id}.png al upload, _load_image()
prova cache memory prima di leggere disco.
Rimossa funzione _store_image duplicata.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sostituisce GUI cv2/tkinter con webapp standalone:
Server (pm2d/web/server.py):
- FastAPI + uvicorn
- Endpoint: GET /, POST /upload, POST /match, POST /auto_tune,
GET /image/{id}/raw
- In-memory image store (uuid-based)
- Rendering annotated server-side via opencv (overlay bbox + edges
template warpati)
Frontend (pm2d/web/static/):
- index.html: layout 3 colonne (MODELLO | SCENA | PARAMETRI) + footer
legenda
- style.css: tema dark, CSS grid responsive
- app.js: canvas HTML5 per visualizzazione scalata fit,
ROI selection con drag mouse, form parametri live,
MATCH button, Auto-tune button
Parametri modificabili INLINE (niente dialog separata).
Enter su qualsiasi campo triggera MATCH.
Legenda match in fondo con pallino colorato + dati.
main.py ora lancia il server webapp. Deprecato ingresso GUI cv2
(pm2d/gui.py resta importable per backward compat).
Test: /match su rings_and_nuts: 3/3 ruote in 1.14s (train 0.36s + find 0.77s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>