wlatt commited on
Commit
53209d3
·
verified ·
1 Parent(s): 8c8b808

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +24 -27
app.py CHANGED
@@ -119,24 +119,20 @@ def cattura_colore(image, evt: gr.SelectData, s_main, s_alt):
119
  return (max(0, v1-25), min(max_v1, v1+25), max(0, v2-25), min(255, v2+25), max(0, v3-25), min(255, v3+25))
120
 
121
  def disegna_etichetta_pianta(img, mask, quad_vertices, input_area_rif):
122
- """Funzione universale per disegnare contorni, freccia ed etichetta testuale."""
123
  contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
124
  if not contours: return img
125
 
126
- # Bordo Magenta
127
  cv2.drawContours(img, contours, -1, (255, 0, 255), 2)
128
 
129
  c = max(contours, key=cv2.contourArea)
130
  topmost = tuple(c[c[:, :, 1].argmin()][0])
131
 
132
- # Calcola coordinate coda della freccia (in alto a sinistra rispetto al top)
133
  tail = (max(20, topmost[0] - 120), max(40, topmost[1] - 80))
134
  cv2.arrowedLine(img, tail, topmost, (0, 255, 255), 3, tipLength=0.2)
135
 
136
  px_area = int(np.sum(mask == 255))
137
  text = f"Pianta: {px_area} px"
138
 
139
- # Se abbiamo il quadrilatero e l'area di riferimento, calcola in cm2
140
  if quad_vertices is not None and input_area_rif and input_area_rif > 0:
141
  valid_area = np.zeros(img.shape[:2], dtype=np.uint8)
142
  cv2.fillPoly(valid_area, [quad_vertices], 255)
@@ -147,7 +143,6 @@ def disegna_etichetta_pianta(img, mask, quad_vertices, input_area_rif):
147
 
148
  text_pos = (max(10, tail[0] - 50), max(20, tail[1] - 15))
149
 
150
- # Testo con Outline nero per massima leggibilità
151
  cv2.putText(img, text, text_pos, cv2.FONT_HERSHEY_SIMPLEX, 1.1, (0, 0, 0), 6)
152
  cv2.putText(img, text, text_pos, cv2.FONT_HERSHEY_SIMPLEX, 1.1, (0, 255, 255), 3)
153
 
@@ -158,7 +153,7 @@ def disegna_etichetta_pianta(img, mask, quad_vertices, input_area_rif):
158
  # ==========================================
159
  def ordina_min_max(v1, v2): return int(min(v1, v2)), int(max(v1, v2))
160
 
161
- def elabora_nastro(image, s_main, s_alt, c1_min, c1_max, c2_min, c2_max, c3_min, c3_max):
162
  if image is None: return None, None, "0", "In attesa...", None, gr.update(visible=False)
163
 
164
  spazio = get_spazio_attivo(s_main, s_alt)
@@ -261,7 +256,6 @@ def elabora_pianta(image, s_main, s_alt, c1_min, c1_max, c2_min, c2_max, c3_min,
261
 
262
  mask_pulita = applica_morfologia(mask, morfo_tipo, morfo_int)
263
 
264
- # Se abbiamo isolato correttamente un oggetto e abbiamo dati, mettiamo l'etichetta grossa
265
  segmented = cv2.bitwise_and(image, image, mask=mask_pulita)
266
  if np.sum(mask_pulita == 255) > 500:
267
  segmented = disegna_etichetta_pianta(segmented, mask_pulita, quad_state, input_area_rif)
@@ -296,33 +290,40 @@ def segmenta_ai_manuale(image, evt: gr.SelectData, quad_vertices, input_area_rif
296
 
297
  def segmenta_ai_automatico(image, quad_vertices, input_area_rif):
298
  if image is None or quad_vertices is None:
299
- return image, "Errore: Calibra il quadrilatero in Fase 1."
300
 
301
  h, w = image.shape[:2]
302
  valid_area = np.zeros((h, w), dtype=np.uint8)
303
  cv2.fillPoly(valid_area, [quad_vertices], 255)
 
304
 
305
- # exclude_mask tiene traccia di tutto ciò che NON deve essere cliccato
306
- exclude_mask = cv2.bitwise_not(valid_area)
307
 
 
308
  instances = []
309
  model = carica_fastsam()
310
 
311
  for step in range(5):
 
 
 
 
 
 
 
312
  if step == 0:
313
- # Step 1: Click esatto nel baricentro del nastro
314
  M = cv2.moments(valid_area)
315
  cx, cy = int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])
316
  else:
317
- # Step 2-5: Distance Transform per trovare il "Polo dell'inaccessibilità"
318
- allowed_area = cv2.bitwise_not(exclude_mask)
319
  dist = cv2.distanceTransform(allowed_area, cv2.DIST_L2, 5)
320
  _, max_val, _, max_loc = cv2.minMaxLoc(dist)
321
 
322
- if max_val < 5: break # Se il punto più lontano è a meno di 5 pixel dal bordo, abbiamo esplorato tutto
 
 
323
  cx, cy = max_loc
324
 
325
- # Inferenza
326
  results = model.predict(image, points=[[cx, cy]], labels=[1], device="cpu", verbose=False)
327
 
328
  if len(results) > 0 and results[0].masks is not None:
@@ -331,23 +332,21 @@ def segmenta_ai_automatico(image, quad_vertices, input_area_rif):
331
  mask = (mask * 255).astype(np.uint8)
332
  mask = cv2.bitwise_and(mask, valid_area)
333
 
334
- # Controllo anti-loop infinito
335
  new_pixels = cv2.bitwise_and(mask, cv2.bitwise_not(exclude_mask))
 
 
336
  if np.sum(new_pixels == 255) < 100:
337
- # Se la maschera non porta nulla di nuovo, marca il punto esplorato e riprova
338
- cv2.circle(exclude_mask, (cx, cy), 20, 255, -1)
339
  continue
340
 
341
  instances.append(mask)
342
- exclude_mask = cv2.bitwise_or(exclude_mask, mask) # Aggiungi la foglia alle zone da non cliccare
343
  else:
344
- # Se SAM fallisce su quel punto, non cliccarlo più
345
  cv2.circle(exclude_mask, (cx, cy), 20, 255, -1)
346
 
347
  if not instances:
348
  return cv2.bitwise_and(image, image, mask=valid_area), "Nessuna segmentazione AI riuscita."
349
 
350
- # Seleziona la vera pianta usando l'ExG medio
351
  img_float = image.astype(np.float32)
352
  exg_img = 2 * img_float[:,:,1] - img_float[:,:,0] - img_float[:,:,2]
353
  exg_img = np.clip(exg_img, 0, 255).astype(np.uint8)
@@ -358,13 +357,12 @@ def segmenta_ai_automatico(image, quad_vertices, input_area_rif):
358
  if mean_exg > max_exg:
359
  max_exg, plant_idx = mean_exg, i
360
 
361
- # Produci l'output ESCLUSIVO per la pianta vincitrice
362
  best_mask = instances[plant_idx]
363
  final_output = cv2.bitwise_and(image, image, mask=best_mask)
364
  final_output = disegna_etichetta_pianta(final_output, best_mask, quad_vertices, input_area_rif)
365
 
366
  final_px = int(np.sum(best_mask == 255))
367
- return final_output, f"Esplorazione AI completata. Pianta isolata ({final_px} px)."
368
 
369
  # ==========================================
370
  # 5. INTERFACCIA UTENTE (UI)
@@ -386,7 +384,7 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="green")) as app:
386
  with gr.Tabs():
387
  # --- TAB 1: GEOMETRIA ---
388
  with gr.Tab("📐 Fase 1: Calibrazione Riferimento"):
389
- gr.Markdown("**Isola il nastro**. L'algoritmo calcolerà il quadrilatero interno per contenere la pianta.")
390
  with gr.Group():
391
  with gr.Row():
392
  sp_main_r = gr.Radio(["HSV", "ExG"], value="HSV", label="Spazio Colore Consigliato")
@@ -424,7 +422,7 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="green")) as app:
424
 
425
  for s in sliders_r + [sp_main_r, sp_altri_r]:
426
  s.change(
427
- fn=elabora_nastro,
428
  inputs=[img_rif_in, sp_main_r, sp_altri_r] + sliders_r,
429
  outputs=[img_mask_out, img_geom_out, pixel_rif_out, log_geom, quad_state, box_area_rif]
430
  )
@@ -463,7 +461,6 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="green")) as app:
463
  sp.change(fn=aggiorna_sliders, inputs=[sp_main_l, sp_altri_l], outputs=sliders_l)
464
  img_lat_in.select(fn=cattura_colore, inputs=[img_lat_in, sp_main_l, sp_altri_l], outputs=sliders_l)
465
 
466
- # Aggiunti quad_state e input_area_rif per generare l'etichetta in cm2
467
  for s in sliders_l + [sp_main_l, sp_altri_l, morfo_tipo, morfo_int]:
468
  s.change(fn=elabora_pianta,
469
  inputs=[img_lat_in, sp_main_l, sp_altri_l] + sliders_l + [morfo_tipo, morfo_int, quad_state, input_area_rif],
@@ -473,7 +470,7 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="green")) as app:
473
  with gr.Tab("🧠 Fase 3: AI (FastSAM)"):
474
  gr.Markdown("**Clicca sulla pianta** per estrarla, oppure usa l'Auto-Segmentatore basato sulla cornice elaborata in Fase 1.")
475
 
476
- btn_auto_sam = gr.Button("🤖 Segmenta Automaticamente l'interno del Nastro", variant="primary", visible=False)
477
 
478
  with gr.Row():
479
  img_ai_in = gr.Image(label="Clicca sull'oggetto", interactive=True, height=350)
 
119
  return (max(0, v1-25), min(max_v1, v1+25), max(0, v2-25), min(255, v2+25), max(0, v3-25), min(255, v3+25))
120
 
121
  def disegna_etichetta_pianta(img, mask, quad_vertices, input_area_rif):
 
122
  contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
123
  if not contours: return img
124
 
 
125
  cv2.drawContours(img, contours, -1, (255, 0, 255), 2)
126
 
127
  c = max(contours, key=cv2.contourArea)
128
  topmost = tuple(c[c[:, :, 1].argmin()][0])
129
 
 
130
  tail = (max(20, topmost[0] - 120), max(40, topmost[1] - 80))
131
  cv2.arrowedLine(img, tail, topmost, (0, 255, 255), 3, tipLength=0.2)
132
 
133
  px_area = int(np.sum(mask == 255))
134
  text = f"Pianta: {px_area} px"
135
 
 
136
  if quad_vertices is not None and input_area_rif and input_area_rif > 0:
137
  valid_area = np.zeros(img.shape[:2], dtype=np.uint8)
138
  cv2.fillPoly(valid_area, [quad_vertices], 255)
 
143
 
144
  text_pos = (max(10, tail[0] - 50), max(20, tail[1] - 15))
145
 
 
146
  cv2.putText(img, text, text_pos, cv2.FONT_HERSHEY_SIMPLEX, 1.1, (0, 0, 0), 6)
147
  cv2.putText(img, text, text_pos, cv2.FONT_HERSHEY_SIMPLEX, 1.1, (0, 255, 255), 3)
148
 
 
153
  # ==========================================
154
  def ordina_min_max(v1, v2): return int(min(v1, v2)), int(max(v1, v2))
155
 
156
+ def elabora_riferimento(image, s_main, s_alt, c1_min, c1_max, c2_min, c2_max, c3_min, c3_max):
157
  if image is None: return None, None, "0", "In attesa...", None, gr.update(visible=False)
158
 
159
  spazio = get_spazio_attivo(s_main, s_alt)
 
256
 
257
  mask_pulita = applica_morfologia(mask, morfo_tipo, morfo_int)
258
 
 
259
  segmented = cv2.bitwise_and(image, image, mask=mask_pulita)
260
  if np.sum(mask_pulita == 255) > 500:
261
  segmented = disegna_etichetta_pianta(segmented, mask_pulita, quad_state, input_area_rif)
 
290
 
291
  def segmenta_ai_automatico(image, quad_vertices, input_area_rif):
292
  if image is None or quad_vertices is None:
293
+ return image, "Errore: Calibra l'area di riferimento in Fase 1."
294
 
295
  h, w = image.shape[:2]
296
  valid_area = np.zeros((h, w), dtype=np.uint8)
297
  cv2.fillPoly(valid_area, [quad_vertices], 255)
298
+ quad_area_px = np.sum(valid_area == 255)
299
 
300
+ if quad_area_px == 0:
301
+ return image, "Errore: Area di riferimento nulla."
302
 
303
+ exclude_mask = cv2.bitwise_not(valid_area)
304
  instances = []
305
  model = carica_fastsam()
306
 
307
  for step in range(5):
308
+ allowed_area = cv2.bitwise_not(exclude_mask)
309
+ remaining_px = np.sum(allowed_area == 255)
310
+
311
+ # SCUDO 1: Fallback se l'area rimanente è < 10% del totale (Richiesta Utente)
312
+ if (remaining_px / quad_area_px) < 0.10:
313
+ break
314
+
315
  if step == 0:
 
316
  M = cv2.moments(valid_area)
317
  cx, cy = int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])
318
  else:
 
 
319
  dist = cv2.distanceTransform(allowed_area, cv2.DIST_L2, 5)
320
  _, max_val, _, max_loc = cv2.minMaxLoc(dist)
321
 
322
+ # SCUDO 2: Fallback Topografico (spazi troppo stretti)
323
+ if max_val < 5:
324
+ break
325
  cx, cy = max_loc
326
 
 
327
  results = model.predict(image, points=[[cx, cy]], labels=[1], device="cpu", verbose=False)
328
 
329
  if len(results) > 0 and results[0].masks is not None:
 
332
  mask = (mask * 255).astype(np.uint8)
333
  mask = cv2.bitwise_and(mask, valid_area)
334
 
 
335
  new_pixels = cv2.bitwise_and(mask, cv2.bitwise_not(exclude_mask))
336
+
337
+ # SCUDO 3: Anti-Loop Infinito. Disegna zona morta se SAM ripete l'output.
338
  if np.sum(new_pixels == 255) < 100:
339
+ cv2.circle(exclude_mask, (cx, cy), max(15, int(max_val if step > 0 else 20)), 255, -1)
 
340
  continue
341
 
342
  instances.append(mask)
343
+ exclude_mask = cv2.bitwise_or(exclude_mask, mask)
344
  else:
 
345
  cv2.circle(exclude_mask, (cx, cy), 20, 255, -1)
346
 
347
  if not instances:
348
  return cv2.bitwise_and(image, image, mask=valid_area), "Nessuna segmentazione AI riuscita."
349
 
 
350
  img_float = image.astype(np.float32)
351
  exg_img = 2 * img_float[:,:,1] - img_float[:,:,0] - img_float[:,:,2]
352
  exg_img = np.clip(exg_img, 0, 255).astype(np.uint8)
 
357
  if mean_exg > max_exg:
358
  max_exg, plant_idx = mean_exg, i
359
 
 
360
  best_mask = instances[plant_idx]
361
  final_output = cv2.bitwise_and(image, image, mask=best_mask)
362
  final_output = disegna_etichetta_pianta(final_output, best_mask, quad_vertices, input_area_rif)
363
 
364
  final_px = int(np.sum(best_mask == 255))
365
+ return final_output, f"Esplorazione AI completata in {step+1} steps. Pianta isolata ({final_px} px)."
366
 
367
  # ==========================================
368
  # 5. INTERFACCIA UTENTE (UI)
 
384
  with gr.Tabs():
385
  # --- TAB 1: GEOMETRIA ---
386
  with gr.Tab("📐 Fase 1: Calibrazione Riferimento"):
387
+ gr.Markdown("**Isola l'area di riferimento**. L'algoritmo calcolerà il quadrilatero interno per contenere la pianta.")
388
  with gr.Group():
389
  with gr.Row():
390
  sp_main_r = gr.Radio(["HSV", "ExG"], value="HSV", label="Spazio Colore Consigliato")
 
422
 
423
  for s in sliders_r + [sp_main_r, sp_altri_r]:
424
  s.change(
425
+ fn=elabora_riferimento,
426
  inputs=[img_rif_in, sp_main_r, sp_altri_r] + sliders_r,
427
  outputs=[img_mask_out, img_geom_out, pixel_rif_out, log_geom, quad_state, box_area_rif]
428
  )
 
461
  sp.change(fn=aggiorna_sliders, inputs=[sp_main_l, sp_altri_l], outputs=sliders_l)
462
  img_lat_in.select(fn=cattura_colore, inputs=[img_lat_in, sp_main_l, sp_altri_l], outputs=sliders_l)
463
 
 
464
  for s in sliders_l + [sp_main_l, sp_altri_l, morfo_tipo, morfo_int]:
465
  s.change(fn=elabora_pianta,
466
  inputs=[img_lat_in, sp_main_l, sp_altri_l] + sliders_l + [morfo_tipo, morfo_int, quad_state, input_area_rif],
 
470
  with gr.Tab("🧠 Fase 3: AI (FastSAM)"):
471
  gr.Markdown("**Clicca sulla pianta** per estrarla, oppure usa l'Auto-Segmentatore basato sulla cornice elaborata in Fase 1.")
472
 
473
+ btn_auto_sam = gr.Button("🤖 Segmenta Automaticamente l'interno dell'Area", variant="primary", visible=False)
474
 
475
  with gr.Row():
476
  img_ai_in = gr.Image(label="Clicca sull'oggetto", interactive=True, height=350)