FraRiccio/pattern-recognition-ai
0
1import math2import numpy as np3import cv24import gradio as gr5 6# ---------------------- Feature extraction ----------------------7 8def _safe_resize(img, max_side=256):9 h, w = img.shape[:2]10 scale = max_side / max(h, w)11 if scale < 1.0:12 img = cv2.resize(img, (int(w*scale), int(h*scale)), interpolation=cv2.INTER_AREA)13 return img14 15def to_gray_bin(img):16 if img.ndim == 3:17 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)18 else:19 gray = img.copy()20 gray = cv2.GaussianBlur(gray, (3,3), 0)21 bin_im = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,22 cv2.THRESH_BINARY, 11, 2)23 if np.mean(bin_im) > 127:24 bin_im = 255 - bin_im25 return gray, bin_im26 27def edge_density(gray):28 edges = cv2.Canny(gray, 50, 150)29 return float(np.mean(edges > 0)), edges30 31def connected_components(bin_im):32 # cv2.connectedComponents richiede uint8 (0/1). Evitiamo bool.33 mask = (bin_im > 0).astype(np.uint8)34 num_labels, labels = cv2.connectedComponents(mask)35 return max(0, num_labels - 1)36 37 38def largest_contour_features(bin_im):39 cnts, _ = cv2.findContours(bin_im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)40 if not cnts:41 return dict(area=0., ar=0., circ=0., extent=0.)42 c = max(cnts, key=cv2.contourArea)43 area = float(cv2.contourArea(c))44 x,y,w,h = cv2.boundingRect(c)45 rect_area = float(w*h) if w*h>0 else 1.046 ar = float(w)/float(h) if h>0 else 0.047 per = cv2.arcLength(c, True)48 circ = (4*math.pi*area/(per*per)) if per>0 else 0.049 extent = area/rect_area if rect_area>0 else 0.050 return dict(area=area, ar=ar, circ=circ, extent=extent)51 52def hu_moments(bin_im):53 m = cv2.moments(bin_im)54 hu = cv2.HuMoments(m).flatten()55 hu_log = np.sign(hu) * np.log1p(np.abs(hu))56 return hu_log.tolist()57 58def symmetry_score(bin_im):59 im = bin_im.astype(np.float32)/255.060 fh = cv2.flip(im, 1)61 fv = cv2.flip(im, 0)62 def cos_sim(a,b):63 denom = (np.linalg.norm(a)*np.linalg.norm(b) + 1e-8)64 return float(np.dot(a.ravel(), b.ravel())/denom)65 return dict(sym_h=cos_sim(im, fh), sym_v=cos_sim(im, fv))66 67def dominant_orientation(gray):68 gx = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3)69 gy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3)70 mag, ang = cv2.cartToPolar(gx, gy, angleInDegrees=True)71 bins = 1872 hist, edges = np.histogram(ang[mag>0], bins=bins, range=(0,180), weights=mag[mag>0])73 if hist.sum() == 0:74 return 0.075 angle_center = (edges[:-1] + edges[1:]) / 2.076 return float(angle_center[np.argmax(hist)])77 78def corner_count(gray):79 corners = cv2.goodFeaturesToTrack(gray, maxCorners=128, qualityLevel=0.01, minDistance=5)80 return int(0 if corners is None else len(corners))81 82FEATURE_KEYS = [83 "avg_gray","edge_density","components","largest_area","largest_ar","largest_circ","largest_extent",84 "hu1","hu2","hu3","hu4","hu5","hu6","hu7","sym_h","sym_v","dom_angle","corner_count","area_ratio"85]86 87def extract_features(img):88 img = _safe_resize(img)89 gray, bin_im = to_gray_bin(img)90 ed, _ = edge_density(gray)91 comps = connected_components(bin_im)92 lcf = largest_contour_features(bin_im)93 hu = hu_moments(bin_im)94 sym = symmetry_score(bin_im)95 ang = dominant_orientation(gray)96 cor = corner_count(gray)97 area_ratio = float(np.sum(bin_im>0)) / float(bin_im.size)98 feats = {99 "avg_gray": float(np.mean(gray)/255.0),100 "edge_density": ed,101 "components": float(comps),102 "largest_area": float(lcf["area"]),103 "largest_ar": float(lcf["ar"]),104 "largest_circ": float(lcf["circ"]),105 "largest_extent": float(lcf["extent"]),106 "hu1": float(hu[0]), "hu2": float(hu[1]), "hu3": float(hu[2]),107 "hu4": float(hu[3]), "hu5": float(hu[4]), "hu6": float(hu[5]), "hu7": float(hu[6]),108 "sym_h": float(sym["sym_h"]),109 "sym_v": float(sym["sym_v"]),110 "dom_angle": float(ang/180.0),111 "corner_count": float(cor),112 "area_ratio": float(area_ratio),113 }114 return feats115 116def vectorize(feats):117 return np.array([feats[k] for k in FEATURE_KEYS], dtype=np.float32)118 119def analyze_sequence(seq_imgs, option_imgs):120 seq_vecs = [vectorize(extract_features(img)) for img in seq_imgs]121 delta = np.median([seq_vecs[i+1]-seq_vecs[i] for i in range(3)], axis=0)122 v_pred = seq_vecs[-1] + delta123 results = []124 for idx, img in enumerate(option_imgs):125 v = vectorize(extract_features(img))126 score = float(np.linalg.norm(v_pred - v))127 results.append((idx+1, score))128 return sorted(results, key=lambda x: x[1])129 130def run_ui(a1,a2,a3,a4,b1,b2,b3,b4,b5=None,b6=None):131 seq = [a1,a2,a3,a4]132 opts = [x for x in [b1,b2,b3,b4,b5,b6] if x is not None]133 if any(x is None for x in seq):134 return "⚠️ Carica tutte e 4 le immagini A1..A4."135 if len(opts) < 4:136 return "⚠️ Carica almeno 4 opzioni (B1..B4), fino a 6."137 results = analyze_sequence(seq, opts)138 text = "### 🔮 Risultato\nLa scelta consigliata è **B{}** ✅\n\n".format(results[0][0])139 text += "### Classifica (score: più basso è meglio)\n"140 for r in results:141 text += f"- B{r[0]} → {r[1]:.4f}\n"142 return text143 144# ---------------------- PASSWORD PROTECTION ----------------------145PASSWORD = "123456789" # puoi cambiarla quando vuoi146 147def check_password(password: str) -> bool:148 return (password or "").strip() == PASSWORD149 150# ====================== UI ======================151with gr.Blocks() as demo:152 gr.Markdown("## 🔒 Accesso")153 with gr.Row():154 password_box = gr.Textbox(label="Inserisci password per accedere", type="password")155 access_button = gr.Button("🔓 Entra")156 157 access_msg = gr.Markdown(visible=False)158 159 # App vera e propria (nascosta finché non c'è accesso)160 with gr.Group(visible=False) as app_group:161 gr.Markdown("# 🧠 Pattern Recognition – Trova la figura successiva")162 gr.Markdown("Carica 4 immagini sequenziali e scegli tra 4–6 opzioni quella corretta 🔍")163 with gr.Row():164 with gr.Column():165 a1 = gr.Image(label="A1", type="numpy")166 a2 = gr.Image(label="A2", type="numpy")167 a3 = gr.Image(label="A3", type="numpy")168 a4 = gr.Image(label="A4", type="numpy")169 with gr.Column():170 b1 = gr.Image(label="B1", type="numpy")171 b2 = gr.Image(label="B2", type="numpy")172 b3 = gr.Image(label="B3", type="numpy")173 b4 = gr.Image(label="B4", type="numpy")174 b5 = gr.Image(label="B5 (opzionale)", type="numpy")175 b6 = gr.Image(label="B6 (opzionale)", type="numpy")176 btn = gr.Button("🔎 Analizza Pattern")177 output = gr.Markdown()178 btn.click(run_ui, inputs=[a1,a2,a3,a4,b1,b2,b3,b4,b5,b6], outputs=output)179 180 def grant_access(pwd):181 if check_password(pwd):182 return gr.update(visible=False, value=""), gr.update(visible=True)183 else:184 return gr.update(visible=True, value="❌ **Password errata. Riprova.**"), gr.update(visible=False)185 186 access_button.click(fn=grant_access, inputs=password_box, outputs=[access_msg, app_group])187 188demo.launch()189 190 