Spaces:
Running on Zero
Running on Zero
Upload 81 files
Browse files- .gitattributes +2 -0
- CHANGELOG_REV7_INTEGRATION.md +16 -0
- GUI_STRUCTURE_REV7.md +47 -0
- MANIFEST_SHA256.txt +19 -11
- README.md +30 -12
- README_14-16.md +198 -0
- TEST_REPORT_REV7.md +34 -0
- advanced_14_16.py +551 -0
- app.py +40 -37
- assets/14-16/Fig1_cariacamento.JPG +0 -0
- assets/14-16/Fig2_fasi.JPG +0 -0
- assets/14-16/Fig3_fase1_auto.JPG +0 -0
- assets/14-16/Fig4_fase1_manuale.JPG +3 -0
- assets/14-16/Fig5_fase2.JPG +3 -0
- assets/14-16/Fig6_fase3.JPG +0 -0
- assets/14-16/Fig7_IA_semantica.JPG +0 -0
- assets/14-16/Scheda_esperimento.pdf +3 -0
- assets/14-16/Tutorial_setup_sperimentale.pdf +3 -0
- dataset/README_dataset.md +1 -1
- fastsam_shared.py +30 -0
.gitattributes
CHANGED
|
@@ -1,3 +1,5 @@
|
|
| 1 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.pdf filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 1 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.pdf filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.png filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
assets/14-16/Fig4_fase1_manuale.JPG filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
assets/14-16/Fig5_fase2.JPG filter=lfs diff=lfs merge=lfs -text
|
CHANGELOG_REV7_INTEGRATION.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# REV7 - integrazione 14-16
|
| 2 |
+
|
| 3 |
+
- integrato il precedente `Plant_segmentation_edu` come quarta sezione dello Space;
|
| 4 |
+
- dataset condiviso, senza duplicazioni;
|
| 5 |
+
- FastSAM condiviso tramite `fastsam_shared.py`;
|
| 6 |
+
- mantenuta l'architettura ZeroGPU della REV6.1 con inferenza FastSAM esplicitamente su CPU;
|
| 7 |
+
- aggiunta calibrazione automatica e manuale del riferimento;
|
| 8 |
+
- aggiunta segmentazione automatica ExG e regolazione manuale ExG/HSV/RGB/Lab;
|
| 9 |
+
- aggiunto calcolo percentuale rispetto al riferimento e stima in cm² quando sono disponibili base, altezza, L1 e L2;
|
| 10 |
+
- aggiunto FastSAM puntuale 14-16;
|
| 11 |
+
- aggiunto metodo ibrido automatico del precedente Space;
|
| 12 |
+
- mantenuta a parte la guida tecnica dettagliata `README_14-16.md` con le schermate reali originali;
|
| 13 |
+
- copiati senza modifica i due PDF originali nella cartella `assets/14-16/`;
|
| 14 |
+
- convertiti in pannelli sempre visibili i passaggi fondamentali di 9-11 e 11-14;
|
| 15 |
+
- uniformata la gerarchia visiva dei pulsanti principali e dei pannelli manuali;
|
| 16 |
+
- eliminate dalle nuove etichette GUI annotazioni come “consigliato” e “avanzato”.
|
GUI_STRUCTURE_REV7.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Struttura GUI REV7
|
| 2 |
+
|
| 3 |
+
## Regole visive comuni
|
| 4 |
+
|
| 5 |
+
- I pannelli fondamentali sono sempre aperti e visibili.
|
| 6 |
+
- I pulsanti che avviano elaborazioni usano lo stesso stile verde `action-button`.
|
| 7 |
+
- Le regolazioni manuali sono visibili in pannelli secondari con bordo distinto.
|
| 8 |
+
- Le funzioni automatiche compaiono prima dei controlli manuali.
|
| 9 |
+
- Le etichette non usano annotazioni tra parentesi come “consigliato” o “avanzato”.
|
| 10 |
+
|
| 11 |
+
## 6-9
|
| 12 |
+
|
| 13 |
+
1. Caricamento fotografia o dataset.
|
| 14 |
+
2. Impostazione della griglia.
|
| 15 |
+
3. Generazione del PDF.
|
| 16 |
+
|
| 17 |
+
## 9-11
|
| 18 |
+
|
| 19 |
+
### A scuola
|
| 20 |
+
|
| 21 |
+
1. Un solo caricamento della fotografia.
|
| 22 |
+
2. Patch 5×5 con valori RGB o HSV sovrapposti direttamente ai pixel.
|
| 23 |
+
3. Laboratorio del colore sempre visibile sotto il patch.
|
| 24 |
+
|
| 25 |
+
### Con la famiglia
|
| 26 |
+
|
| 27 |
+
1. Fotografia A e fotografia B.
|
| 28 |
+
2. Confronto automatico del verde.
|
| 29 |
+
3. Risultati visuali e maschere.
|
| 30 |
+
|
| 31 |
+
## 11-14
|
| 32 |
+
|
| 33 |
+
1. Caricamento.
|
| 34 |
+
2. Metodo cromatico HSV con parametri predefiniti.
|
| 35 |
+
3. Regolazione manuale HSV già visibile.
|
| 36 |
+
4. ExG già visibile.
|
| 37 |
+
5. FastSAM già visibile con copia locale dell’immagine.
|
| 38 |
+
6. Confronto finale.
|
| 39 |
+
|
| 40 |
+
## 14-16
|
| 41 |
+
|
| 42 |
+
1. **Caricamento** — fotografia locale o dataset condiviso.
|
| 43 |
+
2. **Calibrazione** — rilevamento automatico della cornice; controlli manuali visibili sotto; campionamento del colore mediante click.
|
| 44 |
+
3. **Segmentazione e misure** — ExG automatico e pulizia automatica; regolazione ExG/HSV/RGB/Lab e morfologia visibile sotto; percentuale sul riferimento e area reale quando sono fornite le misure geometriche.
|
| 45 |
+
4. **FastSAM** — copia locale dell’immagine, selezione del punto e pulsante di inferenza.
|
| 46 |
+
5. **Metodo ibrido** — esplorazione automatica mediante più prompt FastSAM e scelta finale tramite ExG.
|
| 47 |
+
6. **Esportazione e documentazione** — file prodotti nei pannelli precedenti, README tecnico 14-16 e PDF originali.
|
MANIFEST_SHA256.txt
CHANGED
|
@@ -1,20 +1,27 @@
|
|
| 1 |
e0c1edaa83a3c419eb884b51c4b3e2de519de48b46420caa7c694ed802add437 ./.gitattributes
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
2cf251e4db544ae2b6a5ca1e0a91dd1e4975e72cfa6119b5ffd366bb1f869c27 ./ISTRUZIONI_CARICAMENTO_REV5.md
|
| 5 |
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 ./LICENSE
|
| 6 |
f248798ad0c9a4bfd5e55f0b9fe41208f69791241ac1e506f2ce8b3e8ffc934a ./NOTE_RUNTIME_ZEROGPU_CPU.md
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
4180f6cce89dafd0665dcfd5b3f0c7be639773dba333c6afcb81324413bbe485 ./THIRD_PARTY_NOTICES.md
|
| 11 |
-
|
|
|
|
| 12 |
2728fc9fcc6e2eec751fdd07979a85b78cd2143399216a6ee25f659d697ebdda ./assets/11-14/Approfondimenti_tecnici_docenti.pdf
|
| 13 |
1219c20c7528e57b3268d44ba4b58be8fbcfcaf886a0e8b6063eeb36224c54cc ./assets/11-14/Guida_confronto_segmentazioni.pdf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
d49cff20b915ce9abbda6f3d965943e5d7132ca17b78f6edf51a8a64d8a94c34 ./assets/6-9/Guida_piante_a_quadretti.pdf
|
| 15 |
c816c47c19694173765194c7c6579967cb2e5e8a9bbdaba83454053c9952867e ./assets/9-11/Guida_pixel_e_verde.pdf
|
| 16 |
-
3cbf7a3a59db1ed1178f04158ad60cd823c1313733991cbe2ee632566caf2c19 ./assets/comuni/Scheda_esperimento.pdf
|
| 17 |
-
cf68fe5a8d9832899f1fb5a54ec80a360de516412f3395bb541b95fd2081d549 ./assets/comuni/Tutorial_setup_sperimentale.pdf
|
| 18 |
053c47c13a9a5a1078eb8185ed241ec83ec88a0cb07b9d249604b0641390452b ./assets/screenshots/01_panoramica_annotata.png
|
| 19 |
3b7d3488f27001d7e28592b63ec8b7ab282fbeed7c462835e1c66b226ba68038 ./assets/screenshots/02_6-9_annotata.png
|
| 20 |
708f658bd5fe1ba96c08ae570c9581b73b49352835eda8dd4735aa0cb927ffab ./assets/screenshots/03_9-11_annotata.png
|
|
@@ -44,7 +51,7 @@ ad4db49b400a5f6c1b184d2d12fa9283b8e8f2c389169235de0c5e94262dbf49 ./dataset/NS_C
|
|
| 44 |
5aef103edd73e7cd2776b4202e7d20f53568315aff9aa09f08781e2eb0731632 ./dataset/NS_C_19.jpg
|
| 45 |
d821a6ecef12f5382e789000681dff2027da76956a7a3e319fa4ad7d285227c3 ./dataset/NS_C_23.jpg
|
| 46 |
f49b14148dd19211af4956c8e665232dfd7a5a51033dda5e8519b38f972260f1 ./dataset/NS_C_26.jpg
|
| 47 |
-
|
| 48 |
a9906bfe5f45baf6ddbb1916ef1c479e80f8e2e2c645a290b17e437c263a6eb5 ./dataset/S_A_06.jpg
|
| 49 |
8124a2a068f0e96a845ea96134638256e0ce6338588d98083377a77e158092f1 ./dataset/S_A_09.jpg
|
| 50 |
770d8581a4d389226c4cc0f8d14591e3ec5f9e7eb17c11310ad7e9cd876604b4 ./dataset/S_A_11.jpg
|
|
@@ -69,4 +76,5 @@ ec36860a1cac5eda3c10a745a7d8c8afd78c1caee16e05cd65a8b2b542c59b1f ./dataset/S_C_
|
|
| 69 |
ee9111fe634934cf0f2ee500efc3aea3288b7c6e793fe36e780acf205bccee55 ./dataset/S_C_19.jpg
|
| 70 |
4e7045ded7f4aca9fbb69c8bb5513c5ce3887669d32fe3aed28e6369e51360be ./dataset/S_C_23.jpg
|
| 71 |
5218323ac29cbe93e472edc2e45481d7e65983c5d236ad9a889653c5c42d3406 ./dataset/S_C_26.jpg
|
| 72 |
-
|
|
|
|
|
|
| 1 |
e0c1edaa83a3c419eb884b51c4b3e2de519de48b46420caa7c694ed802add437 ./.gitattributes
|
| 2 |
+
79f6b297b8815ef21703e0578b7331c33780a2329246443db059a86645d59e93 ./CHANGELOG_REV7_INTEGRATION.md
|
| 3 |
+
f3006c1dc301e47552b548c8cf11896425bea71492532a31aac6de5074e54553 ./GUI_STRUCTURE_REV7.md
|
|
|
|
| 4 |
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 ./LICENSE
|
| 5 |
f248798ad0c9a4bfd5e55f0b9fe41208f69791241ac1e506f2ce8b3e8ffc934a ./NOTE_RUNTIME_ZEROGPU_CPU.md
|
| 6 |
+
0b150a0ec188b2445da252fdf9964b5c271519edc5636cc5d7bf0a983ffceb02 ./README.md
|
| 7 |
+
fcb5ae34be041af364af67c5d61d7ae0efa741b7432b13b6255145d8e9009d74 ./README_14-16.md
|
| 8 |
+
dd17bfd2856951485515cea6625303b29041d29da4b610472a811b408d32aa9d ./TEST_REPORT_REV7.md
|
| 9 |
4180f6cce89dafd0665dcfd5b3f0c7be639773dba333c6afcb81324413bbe485 ./THIRD_PARTY_NOTICES.md
|
| 10 |
+
cb887ddb39c0b2e83e3ea6023222b8769aa6a0408bf01d904a8cfbdd55488e9d ./advanced_14_16.py
|
| 11 |
+
ef76cdec7c7a3369c5c8ad456784f6ae6b8182b023476777f1beb7ecefb1a329 ./app.py
|
| 12 |
2728fc9fcc6e2eec751fdd07979a85b78cd2143399216a6ee25f659d697ebdda ./assets/11-14/Approfondimenti_tecnici_docenti.pdf
|
| 13 |
1219c20c7528e57b3268d44ba4b58be8fbcfcaf886a0e8b6063eeb36224c54cc ./assets/11-14/Guida_confronto_segmentazioni.pdf
|
| 14 |
+
a671e38e6bd2ee0e844b056db1b22cfe9a1682512003a6da88309042eb0e5f98 ./assets/14-16/Fig1_cariacamento.JPG
|
| 15 |
+
f80bf46cae5fdd49af34a9ea2fab0a522fa6b517fe4627801d3627413a720f01 ./assets/14-16/Fig2_fasi.JPG
|
| 16 |
+
b850f0e171184b6fe110287d54b063be4da754f36a7a5bb39f628b15dcb437e4 ./assets/14-16/Fig3_fase1_auto.JPG
|
| 17 |
+
f774eee13a95dce40946ebbf51feeab6178cf988eb56dca33df6f85c89736cb8 ./assets/14-16/Fig4_fase1_manuale.JPG
|
| 18 |
+
2b7b05246f771ee02f5dba684021d7dfc9f9aa4968241b29e0c3a1b8218358b5 ./assets/14-16/Fig5_fase2.JPG
|
| 19 |
+
e03415bf911a186c64080ac62627ed914019d07a7f6d8636e190ee4c21606994 ./assets/14-16/Fig6_fase3.JPG
|
| 20 |
+
6cb27b09c3339ae9d702562e9ddece68b38faa9ac3bf42cbcfe356b3d2ab4774 ./assets/14-16/Fig7_IA_semantica.JPG
|
| 21 |
+
3cbf7a3a59db1ed1178f04158ad60cd823c1313733991cbe2ee632566caf2c19 ./assets/14-16/Scheda_esperimento.pdf
|
| 22 |
+
cf68fe5a8d9832899f1fb5a54ec80a360de516412f3395bb541b95fd2081d549 ./assets/14-16/Tutorial_setup_sperimentale.pdf
|
| 23 |
d49cff20b915ce9abbda6f3d965943e5d7132ca17b78f6edf51a8a64d8a94c34 ./assets/6-9/Guida_piante_a_quadretti.pdf
|
| 24 |
c816c47c19694173765194c7c6579967cb2e5e8a9bbdaba83454053c9952867e ./assets/9-11/Guida_pixel_e_verde.pdf
|
|
|
|
|
|
|
| 25 |
053c47c13a9a5a1078eb8185ed241ec83ec88a0cb07b9d249604b0641390452b ./assets/screenshots/01_panoramica_annotata.png
|
| 26 |
3b7d3488f27001d7e28592b63ec8b7ab282fbeed7c462835e1c66b226ba68038 ./assets/screenshots/02_6-9_annotata.png
|
| 27 |
708f658bd5fe1ba96c08ae570c9581b73b49352835eda8dd4735aa0cb927ffab ./assets/screenshots/03_9-11_annotata.png
|
|
|
|
| 51 |
5aef103edd73e7cd2776b4202e7d20f53568315aff9aa09f08781e2eb0731632 ./dataset/NS_C_19.jpg
|
| 52 |
d821a6ecef12f5382e789000681dff2027da76956a7a3e319fa4ad7d285227c3 ./dataset/NS_C_23.jpg
|
| 53 |
f49b14148dd19211af4956c8e665232dfd7a5a51033dda5e8519b38f972260f1 ./dataset/NS_C_26.jpg
|
| 54 |
+
be94c929fd3ffd4d73422b0346693dab25684c5781b8cbf168383b35b7ee514e ./dataset/README_dataset.md
|
| 55 |
a9906bfe5f45baf6ddbb1916ef1c479e80f8e2e2c645a290b17e437c263a6eb5 ./dataset/S_A_06.jpg
|
| 56 |
8124a2a068f0e96a845ea96134638256e0ce6338588d98083377a77e158092f1 ./dataset/S_A_09.jpg
|
| 57 |
770d8581a4d389226c4cc0f8d14591e3ec5f9e7eb17c11310ad7e9cd876604b4 ./dataset/S_A_11.jpg
|
|
|
|
| 76 |
ee9111fe634934cf0f2ee500efc3aea3288b7c6e793fe36e780acf205bccee55 ./dataset/S_C_19.jpg
|
| 77 |
4e7045ded7f4aca9fbb69c8bb5513c5ce3887669d32fe3aed28e6369e51360be ./dataset/S_C_23.jpg
|
| 78 |
5218323ac29cbe93e472edc2e45481d7e65983c5d236ad9a889653c5c42d3406 ./dataset/S_C_26.jpg
|
| 79 |
+
fc9d580ca89ab4c800be0ef40eaa821ac1cbfae50ee5f8fb1a4a7f3e207beef7 ./fastsam_shared.py
|
| 80 |
+
d6aef3a8e1836fc8027a6ef4d3dd6eb59ded5db75e9a35d14d66a1603e03cdc8 ./requirements.txt
|
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: Computer vision e piante - percorsi 6-
|
| 3 |
emoji: 🌱
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: gray
|
|
@@ -14,17 +14,15 @@ preload_from_hub:
|
|
| 14 |
- Uminosachi/FastSAM FastSAM-s.pt
|
| 15 |
---
|
| 16 |
|
| 17 |
-
# Computer vision e piante - percorsi 6-
|
| 18 |
|
| 19 |
-
Lo Space mantiene un unico indirizzo e organizza
|
| 20 |
|
| 21 |
- **6-9 anni:** preparazione di schede PDF con fotografie a quadretti;
|
| 22 |
- **9-11 anni:** esplorazione di matrici di pixel, trasformazioni additive del colore e stima automatica del verde;
|
| 23 |
-
- **11-14 anni:** confronto progressivo fra segmentazione HSV, indice ExG e FastSAM guidato da un punto
|
|
|
|
| 24 |
|
| 25 |
-
La fascia 14-16 non è inclusa in questa versione. Le relative attività verranno sviluppate separatamente a partire dal precedente Space sperimentale.
|
| 26 |
-
|
| 27 |
-

|
| 28 |
|
| 29 |
## Avvio rapido
|
| 30 |
|
|
@@ -145,7 +143,7 @@ Premere **Applica il metodo cromatico**. Lo Space usa soglie HSV iniziali e appl
|
|
| 145 |
|
| 146 |
### 2. Modifica delle soglie HSV
|
| 147 |
|
| 148 |
-
|
| 149 |
|
| 150 |
- intervallo della tonalità H;
|
| 151 |
- saturazione minima e massima;
|
|
@@ -155,7 +153,7 @@ Questa sezione serve a osservare come una trasformazione dei limiti numerici mod
|
|
| 155 |
|
| 156 |
### 3. Indice ExG
|
| 157 |
|
| 158 |
-
|
| 159 |
|
| 160 |
`ExG = 2G - R - B`
|
| 161 |
|
|
@@ -164,7 +162,7 @@ Il programma conserva i pixel per i quali ExG supera la soglia scelta e applica
|
|
| 164 |
### 4. FastSAM guidato da un punto
|
| 165 |
|
| 166 |
1. Caricare o selezionare la fotografia sorgente.
|
| 167 |
-
2.
|
| 168 |
3. Usare la copia sincronizzata dell'immagine presente nello stesso pannello: non è necessario tornare alla parte superiore della pagina.
|
| 169 |
4. Fare click sulla pianta e controllare il punto rosso.
|
| 170 |
5. Premere **Segmenta l'oggetto indicato**.
|
|
@@ -181,6 +179,26 @@ Guide:
|
|
| 181 |
|
| 182 |
---
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
## Runtime ZeroGPU con FastSAM forzato su CPU
|
| 185 |
|
| 186 |
Lo Space è predisposto per essere ospitato con hardware **ZeroGPU**, ma le funzioni visibili agli utenti restano sul processo CPU:
|
|
@@ -215,8 +233,8 @@ Per mantenere invariati i link già presenti nelle schede:
|
|
| 215 |
|
| 216 |
## Documenti allegati
|
| 217 |
|
| 218 |
-
- `assets/
|
| 219 |
-
- `assets/
|
| 220 |
- `assets/6-9/Guida_piante_a_quadretti.pdf`;
|
| 221 |
- `assets/9-11/Guida_pixel_e_verde.pdf`;
|
| 222 |
- `assets/11-14/Guida_confronto_segmentazioni.pdf`;
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Computer vision e piante - percorsi 6-16 anni
|
| 3 |
emoji: 🌱
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: gray
|
|
|
|
| 14 |
- Uminosachi/FastSAM FastSAM-s.pt
|
| 15 |
---
|
| 16 |
|
| 17 |
+
# Computer vision e piante - percorsi 6-16 anni
|
| 18 |
|
| 19 |
+
Lo Space mantiene un unico indirizzo e organizza quattro percorsi progressivi:
|
| 20 |
|
| 21 |
- **6-9 anni:** preparazione di schede PDF con fotografie a quadretti;
|
| 22 |
- **9-11 anni:** esplorazione di matrici di pixel, trasformazioni additive del colore e stima automatica del verde;
|
| 23 |
+
- **11-14 anni:** confronto progressivo fra segmentazione HSV, indice ExG e FastSAM guidato da un punto;
|
| 24 |
+
- **14-16 anni:** calibrazione del riferimento, segmentazione e misura, FastSAM e metodo ibrido. La documentazione tecnica dettagliata è mantenuta separata in `README_14-16.md`.
|
| 25 |
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
## Avvio rapido
|
| 28 |
|
|
|
|
| 143 |
|
| 144 |
### 2. Modifica delle soglie HSV
|
| 145 |
|
| 146 |
+
Nel pannello **Modifica le soglie nello spazio HSV**, già visibile, è possibile cambiare:
|
| 147 |
|
| 148 |
- intervallo della tonalità H;
|
| 149 |
- saturazione minima e massima;
|
|
|
|
| 153 |
|
| 154 |
### 3. Indice ExG
|
| 155 |
|
| 156 |
+
Nel pannello **Prova l’indice ExG**, già visibile, l’indice viene calcolato per ogni pixel:
|
| 157 |
|
| 158 |
`ExG = 2G - R - B`
|
| 159 |
|
|
|
|
| 162 |
### 4. FastSAM guidato da un punto
|
| 163 |
|
| 164 |
1. Caricare o selezionare la fotografia sorgente.
|
| 165 |
+
2. Raggiungere il pannello **Prova FastSAM**, già visibile nella sequenza.
|
| 166 |
3. Usare la copia sincronizzata dell'immagine presente nello stesso pannello: non è necessario tornare alla parte superiore della pagina.
|
| 167 |
4. Fare click sulla pianta e controllare il punto rosso.
|
| 168 |
5. Premere **Segmenta l'oggetto indicato**.
|
|
|
|
| 179 |
|
| 180 |
---
|
| 181 |
|
| 182 |
+
## 14-16 - Laboratorio di analisi
|
| 183 |
+
|
| 184 |
+
La sezione integra le funzioni del precedente `Plant_segmentation_edu` in sei pannelli sempre visibili:
|
| 185 |
+
|
| 186 |
+
1. caricamento unico della fotografia;
|
| 187 |
+
2. calibrazione automatica del riferimento, con regolazione manuale immediatamente disponibile;
|
| 188 |
+
3. segmentazione automatica ExG e misure, con confronto manuale fra ExG, HSV, RGB e Lab;
|
| 189 |
+
4. FastSAM guidato da un punto;
|
| 190 |
+
5. metodo ibrido automatico con più interrogazioni FastSAM e selezione finale tramite ExG;
|
| 191 |
+
6. esportazione e documentazione.
|
| 192 |
+
|
| 193 |
+
Il dataset è lo stesso usato nelle altre fasce e rimane presente una sola volta nella cartella `dataset/`. Il modello FastSAM è condiviso con la sezione 11-14 e viene caricato una sola volta in memoria.
|
| 194 |
+
|
| 195 |
+
La guida tecnica del precedente Space, completa delle schermate reali, è conservata separatamente: [`README_14-16.md`](README_14-16.md).
|
| 196 |
+
|
| 197 |
+
Documenti originali della sezione:
|
| 198 |
+
|
| 199 |
+
- `assets/14-16/Tutorial_setup_sperimentale.pdf`;
|
| 200 |
+
- `assets/14-16/Scheda_esperimento.pdf`.
|
| 201 |
+
|
| 202 |
## Runtime ZeroGPU con FastSAM forzato su CPU
|
| 203 |
|
| 204 |
Lo Space è predisposto per essere ospitato con hardware **ZeroGPU**, ma le funzioni visibili agli utenti restano sul processo CPU:
|
|
|
|
| 233 |
|
| 234 |
## Documenti allegati
|
| 235 |
|
| 236 |
+
- `assets/14-16/Tutorial_setup_sperimentale.pdf`;
|
| 237 |
+
- `assets/14-16/Scheda_esperimento.pdf`;
|
| 238 |
- `assets/6-9/Guida_piante_a_quadretti.pdf`;
|
| 239 |
- `assets/9-11/Guida_pixel_e_verde.pdf`;
|
| 240 |
- `assets/11-14/Guida_confronto_segmentazioni.pdf`;
|
README_14-16.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Guida tecnica dettagliata 14-16
|
| 2 |
+
|
| 3 |
+
> Questa guida conserva contenuti, spiegazioni e schermate reali del precedente Space `Plant_segmentation_edu`. Nello Space integrato le stesse funzioni sono disposte in pannelli sequenziali sempre visibili; nomi e posizione di alcuni controlli possono quindi essere diversi dalle schermate originali. Algoritmi, dataset e documenti sperimentali restano gli stessi.
|
| 4 |
+
|
| 5 |
+
# 🥬 Computer Vision in Agricoltura: Segmentazione
|
| 6 |
+
|
| 7 |
+
Questo Spazio mostra il funzionamento di una pipeline di segmentazione su fotografie di piante, con tre approcci confrontabili:
|
| 8 |
+
|
| 9 |
+
- **deterministico** (soglie su colore + geometria + morfologia),
|
| 10 |
+
- **AI** (FastSAM),
|
| 11 |
+
- **ibrido** (loop deterministico con chiamate AI).
|
| 12 |
+
|
| 13 |
+
L’obiettivo è rendere visibile il processo di segmentazione: non solo il risultato finale, ma anche i passaggi intermedi, i parametri, i casi limite e le differenze tra approcci.
|
| 14 |
+
In coda a questo documento sono allegati due pdf:
|
| 15 |
+
- Tutorial per la riproduzione del setup sperimentale, in classe o a casa.
|
| 16 |
+
- Descrizione e analisi dei risultati di un esperimento condotto da noi nelle stesse condizioni (il dataset comune, vedi sotto).
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## Caricamento immagine
|
| 21 |
+
L’interfaccia permette di iniziare da un’immagine del dataset incluso oppure da un file caricato manualmente. La demo è pensata per essere usata online: è sufficiente selezionare una foto e procedere.
|
| 22 |
+
|
| 23 |
+

|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## Struttura generale: tre fasi
|
| 28 |
+
L’analisi è organizzata in tre schede:
|
| 29 |
+
|
| 30 |
+
1. **Calibrazione del riferimento**
|
| 31 |
+
2. **Segmentazione colore (deterministica)**
|
| 32 |
+
3. **Segmentazione AI (FastSAM)**
|
| 33 |
+
|
| 34 |
+

|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## 📐 Fase 1 — Calibrazione del riferimento
|
| 39 |
+
|
| 40 |
+
In questa fase si calibra la **cornice di riferimento** presente nella fotografia. La calibrazione serve a standardizzare l’analisi e a rendere confrontabili fotografie di tempi diversi o di piante diverse, perché fornisce un’area di riferimento in pixel su cui rapportare le misure della pianta.
|
| 41 |
+
|
| 42 |
+
Sono disponibili due modalità:
|
| 43 |
+
|
| 44 |
+
### Modalità automatica (deterministica)
|
| 45 |
+
L’algoritmo utilizza l’informazione sul colore dello sfondo e della cornice per applicare una serie di soglie preimpostate e generare più maschere candidate. Su ciascun risultato cerca strutture compatibili con un quadrilatero (basandosi sulla presenza di linee rette), quindi confronta i risultati ottenuti nelle diverse iterazioni e propone una soluzione finale per consenso.
|
| 46 |
+
|
| 47 |
+
L’output è mostrato in tre pannelli:
|
| 48 |
+
- fotografia di partenza,
|
| 49 |
+
- maschera,
|
| 50 |
+
- geometria individuata (quadrilatero proposto).
|
| 51 |
+
|
| 52 |
+

|
| 53 |
+
|
| 54 |
+
### Modalità manuale
|
| 55 |
+
La modalità manuale espone direttamente i parametri di soglia nei vari spazi di colore. È quindi possibile controllare in modo esplicito l’isolamento della cornice, modificando minimi e massimi degli assi selezionati.
|
| 56 |
+
|
| 57 |
+
È anche possibile campionare un colore direttamente dall’immagine con un click: il sistema aggiorna automaticamente i valori soglia costruendo un piccolo intervallo di tolleranza attorno al colore selezionato, che può poi essere rifinito manualmente.
|
| 58 |
+
|
| 59 |
+
In questo caso il terzo pannello mostrerà anche tutte le linee rette compatibili con la maschera individuata.
|
| 60 |
+
|
| 61 |
+

|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## 🌱 Fase 2 — Segmentazione colore (thresholding deterministico)
|
| 66 |
+
|
| 67 |
+
Questa fase isola la pianta tramite **sogliatura** (thresholding) e successiva pulizia della maschera.
|
| 68 |
+
|
| 69 |
+
La demo permette di lavorare sia su **spazi di colore** (HSV, RGB, LAB) sia su **ExG** i valori di default corrispondono a quelli generalmente ottimali, è possibile variarli per osservare gli effetti risultanti.
|
| 70 |
+
|
| 71 |
+
Nota importante **ExG (Excess Green)** non è uno spazio di colore: è un **indice** calcolato a partire dai canali RGB, molto usato per evidenziare la vegetazione.
|
| 72 |
+
Nella forma usata qui:
|
| 73 |
+
**ExG = 2G - R - B**
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
Dopo la sogliatura, la maschera può contenere:
|
| 77 |
+
- piccoli pixel isolati (rumore),
|
| 78 |
+
- buchi all’interno della regione segmentata,
|
| 79 |
+
- bordi irregolari.
|
| 80 |
+
|
| 81 |
+
La **pulizia morfologica** è un post-processing della maschera binaria che itera processi di erosione e dilatazione:
|
| 82 |
+
- **Opening** → rimuovere piccoli elementi/rumore : https://it.wikipedia.org/wiki/Apertura_(morfologia)
|
| 83 |
+
- **Closing** → chiudere piccoli buchi o interruzioni : https://it.wikipedia.org/wiki/Chiusura_(morfologia)
|
| 84 |
+
- **Open + Close** → combinazione dei due effetti
|
| 85 |
+
|
| 86 |
+
Come nella Fase 1, il click sull’immagine può essere usato per campionare un colore e inizializzare i parametri intorno a quel valore; il risultato viene aggiornato mostrando la segmentazione e l’output esportabile.
|
| 87 |
+
|
| 88 |
+

|
| 89 |
+
|
| 90 |
+
---
|
| 91 |
+
## 🧠 Nota introduttiva alla fase 3
|
| 92 |
+
|
| 93 |
+
La terza fase introduce l'utilizzo dell'AI con FastSAM.
|
| 94 |
+
**FastSAM** è un piccolo e veloce “Segment Anything Model” (SAM), open source e orientato all’efficienza.
|
| 95 |
+
|
| 96 |
+
**SAM** è un modello di segmentazione “promptable”, cioè un modello che produce maschere a partire da un prompt (ad esempio un click, un box, ecc.).
|
| 97 |
+
Nella pratica, SAM è diventato un riferimento per le pipeline che usano prompt (click, box, regioni) per selezionare un oggetto, ma è molto pesante e richiede risorse computazionali importanti.
|
| 98 |
+
|
| 99 |
+
Questo progetto è pensato per essere **interattivo** e fruibile con risorse computazionali limitate.
|
| 100 |
+
|
| 101 |
+
FastSAM è scelta utile per la demo (veloce e gratuito), ma implica un compromesso: stiamo mostrando un modello **forte ma piccolo**, e con architettura diversa dai SAM più pesanti / più recenti.
|
| 102 |
+
|
| 103 |
+
### Nota di interpretazione dei risultati (importante)
|
| 104 |
+
I fallimenti o i bordi non perfetti osservabili nella pipeline **AI** e **ibrida** di questa demo sono utili per capire limiti reali dei metodi, ma non vanno generalizzati.
|
| 105 |
+
|
| 106 |
+
In particolare:
|
| 107 |
+
- una parte dei limiti osservati dipende dalla **classe di modello** scelta,
|
| 108 |
+
- una parte dipende dalla **taglia del modello** e dalle risorse disponibili,
|
| 109 |
+
- una parte dipende dalla **pipeline locale** (le regole deterministiche introdotte da noi).
|
| 110 |
+
|
| 111 |
+
Sistemi più sofisticati (o versioni più pesanti / più recenti) riducono notevolmente questi problemi.
|
| 112 |
+
|
| 113 |
+
## 🧠 Fase 3 — Segmentazione AI (FastSAM)
|
| 114 |
+
|
| 115 |
+
La terza fase permette di confrontare due modalità operative:
|
| 116 |
+
|
| 117 |
+
- **AI con click sull’immagine** (segmentazione dell’oggetto selezionato),
|
| 118 |
+
- **algoritmo ibrido automatico** (loop deterministico con chiamate FastSAM).
|
| 119 |
+
|
| 120 |
+
Nel caso AI con click, il comportamento dipende dal punto selezionato: il modello segmenta l’oggetto associato a quel click.
|
| 121 |
+
L’esempio seguente mostra un caso importante: cliccando sul vaso, FastSAM segmenta il vaso invece della pianta.
|
| 122 |
+
|
| 123 |
+

|
| 124 |
+
|
| 125 |
+
Nella modalità ibrida, la pipeline combina vincoli deterministici e segmentazione AI:
|
| 126 |
+
|
| 127 |
+
1. usa la cornice calibrata (Fase 1) per definire un’area valida di ricerca;
|
| 128 |
+
2. seleziona punti candidati all’interno dell’area (centroide e punti successivi ricavati da una logica geometrica sulla zona ancora disponibile);
|
| 129 |
+
3. interroga FastSAM più volte con point prompt;
|
| 130 |
+
4. filtra/accumula le istanze ottenute;
|
| 131 |
+
5. seleziona la maschera finale con una regola deterministica (ExG più alto).
|
| 132 |
+
|
| 133 |
+

|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## Casi particolari e limiti (presenti nel dataset)
|
| 138 |
+
Il dataset incluso contiene casi non banali, utili per osservare il comportamento reale della pipeline, ad esempio:
|
| 139 |
+
|
| 140 |
+
- casi in cui la segmentazione standard in **ExG** richiede ritocco dei parametri (campione stressato B 26 DAT),
|
| 141 |
+
- casi in cui la calibrazione automatica della **cornice** non è ottimale (campione non stressato B 26 DAT),
|
| 142 |
+
- casi in cui il metodo **ibrido** fallisce (campione stressato C 19 DAT, campione non stressato A 23 DAT ),
|
| 143 |
+
- casi in cui la componente **AI** seleziona o segmenta male l’oggetto (campione stressato A 9 DAT).
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
## Documenti allegati
|
| 148 |
+
|
| 149 |
+
📄 **Tutorial setup sperimentale (PDF)**
|
| 150 |
+
[Apri / scarica il documento](assets/14-16/Tutorial_setup_sperimentale.pdf)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
📄 **Risultati analitici e interpretazione (PDF)**
|
| 154 |
+
[Apri / scarica il documento](assets/14-16/Scheda_esperimento.pdf)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
---
|
| 158 |
+
|
| 159 |
+
## Approfondimenti — spazi colore, thresholding, morfologia, modelli SAM
|
| 160 |
+
<details>
|
| 161 |
+
<summary><b>Apri gli approfondimenti</b></summary>
|
| 162 |
+
|
| 163 |
+
### Video consigliato (spazi colore)
|
| 164 |
+
Panoramica molto chiara sugli spazi di colore (in inglese, con sottotitoli):
|
| 165 |
+
|
| 166 |
+
- https://www.youtube.com/watch?v=7KYwi2F5Ce4
|
| 167 |
+
|
| 168 |
+
---
|
| 169 |
+
|
| 170 |
+
### Segment Anything (SAM) / FastSAM / varianti efficienti
|
| 171 |
+
|
| 172 |
+
#### SAM (Segment Anything Model) — fonti ufficiali
|
| 173 |
+
- Paper (arXiv): https://arxiv.org/abs/2304.02643
|
| 174 |
+
- Repository ufficiale (Meta): https://github.com/facebookresearch/segment-anything
|
| 175 |
+
|
| 176 |
+
#### SAM 2 (immagini + video)
|
| 177 |
+
- Paper (arXiv): https://arxiv.org/abs/2408.00714
|
| 178 |
+
- Repository ufficiale (Meta): https://github.com/facebookresearch/sam2
|
| 179 |
+
- Pagina di ricerca Meta: https://ai.meta.com/research/sam2/
|
| 180 |
+
|
| 181 |
+
#### FastSAM (modello usato nello Space)
|
| 182 |
+
- Paper (arXiv): https://arxiv.org/abs/2306.12156
|
| 183 |
+
- Repository ufficiale: https://github.com/CASIA-IVA-Lab/FastSAM
|
| 184 |
+
|
| 185 |
+
#### Varianti/derivati efficienti o specializzati (per confronto)
|
| 186 |
+
- MobileSAM (lightweight SAM): https://github.com/ChaoningZhang/MobileSAM
|
| 187 |
+
- Paper MobileSAM / Towards Lightweight SAM for Mobile Applications: https://arxiv.org/abs/2306.14289
|
| 188 |
+
- HQ-SAM (high-quality masks): https://github.com/SysCV/sam-hq
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
### Spazi colore
|
| 193 |
+
- RGB: https://it.wikipedia.org/wiki/RGB
|
| 194 |
+
- HSL/HSV: https://it.wikipedia.org/wiki/HSL_e_HSV
|
| 195 |
+
- Spazio colore Lab: https://it.wikipedia.org/wiki/Spazio_colore_Lab
|
| 196 |
+
|
| 197 |
+
### Thresholding / sogliatura
|
| 198 |
+
- Metodo di Otsu: https://it.wikipedia.org/wiki/Metodo_Otsu
|
TEST_REPORT_REV7.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Report di verifica REV7
|
| 2 |
+
|
| 3 |
+
## Struttura
|
| 4 |
+
|
| 5 |
+
- Quattro tab principali: 6-9, 9-11, 11-14, 14-16.
|
| 6 |
+
- Nessun `gr.Accordion` nei file Python.
|
| 7 |
+
- Dataset condiviso: 48 immagini; nessuna seconda copia del dataset 14-16.
|
| 8 |
+
- Un solo loader FastSAM condiviso in `fastsam_shared.py`.
|
| 9 |
+
- Presente l'ancora `@spaces.GPU(duration=1)` richiesta dal runtime ZeroGPU.
|
| 10 |
+
- `spaces` non è fissato in `requirements.txt`, per evitare conflitti con la versione installata dal runtime Hugging Face.
|
| 11 |
+
|
| 12 |
+
## Test automatici eseguiti
|
| 13 |
+
|
| 14 |
+
- Compilazione Python di `app.py`: superata.
|
| 15 |
+
- Compilazione Python di `advanced_14_16.py`: superata.
|
| 16 |
+
- Compilazione Python di `fastsam_shared.py`: superata.
|
| 17 |
+
- Costruzione completa dell'interfaccia Gradio: superata nell'ambiente locale disponibile.
|
| 18 |
+
- Avvio server locale sulla porta 7860: superato; processo mantenuto attivo fino al timeout del test.
|
| 19 |
+
- Calibrazione automatica su `NS_A_14.jpg` con sfondo nero e cornice rossa: riferimento individuato in 5 tentativi su 5.
|
| 20 |
+
- Segmentazione automatica ExG sullo stesso campione: maschera, overlay, metriche e file PNG prodotti.
|
| 21 |
+
- Calibrazione manuale: percorso funzionale verificato.
|
| 22 |
+
- Metodo ibrido: flusso verificato con un modello FastSAM simulato, incluse cinque iterazioni, selezione tramite ExG e produzione di output.
|
| 23 |
+
- FastSAM puntuale condiviso: flusso verificato con un modello simulato.
|
| 24 |
+
|
| 25 |
+
## Documentazione 14-16
|
| 26 |
+
|
| 27 |
+
- `README_14-16.md` conserva il testo e le schermate reali del precedente Space; sono stati aggiornati soltanto i percorsi relativi degli asset e aggiunta una nota iniziale sulla nuova disposizione della GUI.
|
| 28 |
+
- `Tutorial_setup_sperimentale.pdf` è copiato senza modifica.
|
| 29 |
+
- `Scheda_esperimento.pdf` è copiata senza modifica.
|
| 30 |
+
- I due PDF non sono duplicati nella cartella degli asset comuni.
|
| 31 |
+
|
| 32 |
+
## Limiti del test locale
|
| 33 |
+
|
| 34 |
+
L'ambiente di preparazione dispone di Gradio 6.5.1, mentre lo Space usa Gradio 5.49.1 come già collaudato nella REV6.1. La costruzione della GUI e l'avvio sono stati verificati con la versione locale; il build definitivo con Gradio 5.49.1 e il runtime ZeroGPU devono essere verificati su Hugging Face mediante Factory rebuild. L'inferenza reale FastSAM non è stata eseguita localmente perché il peso viene precaricato nel build Hugging Face; il codice usa lo stesso loader CPU condiviso già adottato nella REV6.1.
|
advanced_14_16.py
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import tempfile
|
| 5 |
+
from typing import Any, Callable
|
| 6 |
+
|
| 7 |
+
import cv2
|
| 8 |
+
import gradio as gr
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
_FASTSAM_LOADER: Callable | None = None
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _rgb(image: Any) -> np.ndarray | None:
|
| 15 |
+
if image is None:
|
| 16 |
+
return None
|
| 17 |
+
arr = np.asarray(image)
|
| 18 |
+
if arr.ndim == 2:
|
| 19 |
+
arr = cv2.cvtColor(arr.astype(np.uint8), cv2.COLOR_GRAY2RGB)
|
| 20 |
+
if arr.ndim != 3:
|
| 21 |
+
raise ValueError("Formato immagine non supportato.")
|
| 22 |
+
if arr.shape[-1] == 4:
|
| 23 |
+
arr = cv2.cvtColor(arr.astype(np.uint8), cv2.COLOR_RGBA2RGB)
|
| 24 |
+
return arr.astype(np.uint8)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _save_png(image: np.ndarray, prefix: str) -> str:
|
| 28 |
+
fd, path = tempfile.mkstemp(prefix=prefix, suffix=".png")
|
| 29 |
+
os.close(fd)
|
| 30 |
+
cv2.imwrite(path, cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
|
| 31 |
+
return path
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _mask_rgb(mask: np.ndarray) -> np.ndarray:
|
| 35 |
+
return cv2.cvtColor(mask.astype(np.uint8), cv2.COLOR_GRAY2RGB)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _overlay(image: np.ndarray, mask: np.ndarray, tint=(30, 220, 80)) -> np.ndarray:
|
| 39 |
+
out = image.copy()
|
| 40 |
+
color = np.zeros_like(image)
|
| 41 |
+
color[:] = np.array(tint, dtype=np.uint8)
|
| 42 |
+
idx = mask > 0
|
| 43 |
+
if np.any(idx):
|
| 44 |
+
out[idx] = cv2.addWeighted(image[idx], 0.56, color[idx], 0.44, 0)
|
| 45 |
+
return out
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _order_vertices(pts: np.ndarray) -> np.ndarray:
|
| 49 |
+
pts = np.asarray(pts, dtype=np.float32).reshape((4, 2))
|
| 50 |
+
rect = np.zeros((4, 2), dtype=np.float32)
|
| 51 |
+
sums = pts.sum(axis=1)
|
| 52 |
+
rect[0] = pts[np.argmin(sums)]
|
| 53 |
+
rect[2] = pts[np.argmax(sums)]
|
| 54 |
+
diff = np.diff(pts, axis=1).ravel()
|
| 55 |
+
rect[1] = pts[np.argmin(diff)]
|
| 56 |
+
rect[3] = pts[np.argmax(diff)]
|
| 57 |
+
return rect
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _automatic_frame_mask(image: np.ndarray, frame: str, step: int) -> np.ndarray:
|
| 61 |
+
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
|
| 62 |
+
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
| 63 |
+
s_min = max(50, 150 - step * 25)
|
| 64 |
+
v_min = max(50, 150 - step * 25)
|
| 65 |
+
v_max_dark = min(200, 50 + step * 25)
|
| 66 |
+
v_min_light = max(100, 200 - step * 20)
|
| 67 |
+
if frame == "Rossa":
|
| 68 |
+
lower1, upper1 = np.array([0, s_min, v_min]), np.array([10, 255, 255])
|
| 69 |
+
lower2, upper2 = np.array([170, s_min, v_min]), np.array([179, 255, 255])
|
| 70 |
+
return cv2.bitwise_or(cv2.inRange(hsv, lower1, upper1), cv2.inRange(hsv, lower2, upper2))
|
| 71 |
+
if frame == "Blu":
|
| 72 |
+
return cv2.inRange(hsv, np.array([100, s_min, v_min]), np.array([140, 255, 255]))
|
| 73 |
+
if frame == "Viola":
|
| 74 |
+
return cv2.inRange(hsv, np.array([125, s_min, v_min]), np.array([165, 255, 255]))
|
| 75 |
+
if frame == "Bianca":
|
| 76 |
+
return cv2.inRange(gray, v_min_light, 255)
|
| 77 |
+
return cv2.inRange(gray, 0, v_max_dark)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def _quad_from_mask(mask: np.ndarray) -> np.ndarray | None:
|
| 81 |
+
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
|
| 82 |
+
closed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
|
| 83 |
+
edges = cv2.Canny(closed, 50, 150)
|
| 84 |
+
lines = cv2.HoughLines(edges, 1, np.pi / 180, 100)
|
| 85 |
+
if lines is None:
|
| 86 |
+
return None
|
| 87 |
+
horizontal, vertical = [], []
|
| 88 |
+
for line in lines:
|
| 89 |
+
rho, theta = line[0]
|
| 90 |
+
angle = theta * 180 / np.pi
|
| 91 |
+
if 45 < angle < 135:
|
| 92 |
+
horizontal.append((rho, theta))
|
| 93 |
+
else:
|
| 94 |
+
cos = np.cos(theta)
|
| 95 |
+
vertical.append((rho, theta, rho / cos if abs(cos) > 1e-8 else rho))
|
| 96 |
+
if len(horizontal) < 2 or len(vertical) < 2:
|
| 97 |
+
return None
|
| 98 |
+
horizontal.sort(key=lambda x: x[0])
|
| 99 |
+
vertical.sort(key=lambda x: x[2])
|
| 100 |
+
top, bottom = horizontal[0], horizontal[-1]
|
| 101 |
+
left, right = vertical[0][:2], vertical[-1][:2]
|
| 102 |
+
|
| 103 |
+
def intersection(l1, l2):
|
| 104 |
+
matrix = np.array([[np.cos(l1[1]), np.sin(l1[1])], [np.cos(l2[1]), np.sin(l2[1])]])
|
| 105 |
+
vector = np.array([l1[0], l2[0]])
|
| 106 |
+
try:
|
| 107 |
+
point = np.linalg.solve(matrix, vector)
|
| 108 |
+
return [int(round(point[0])), int(round(point[1]))]
|
| 109 |
+
except Exception:
|
| 110 |
+
return None
|
| 111 |
+
|
| 112 |
+
vertices = [intersection(top, left), intersection(top, right), intersection(bottom, right), intersection(bottom, left)]
|
| 113 |
+
if any(v is None for v in vertices):
|
| 114 |
+
return None
|
| 115 |
+
quad = np.asarray(vertices, dtype=np.float32)
|
| 116 |
+
h, w = mask.shape
|
| 117 |
+
area = cv2.contourArea(quad)
|
| 118 |
+
if not (w * h * 0.05 < area < w * h * 0.95):
|
| 119 |
+
return None
|
| 120 |
+
return _order_vertices(quad)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def calibrate_reference_auto(image: Any, background: str, frame: str):
|
| 124 |
+
rgb = _rgb(image)
|
| 125 |
+
if rgb is None:
|
| 126 |
+
return None, None, "0", "Caricare una fotografia.", None
|
| 127 |
+
masks, quads = [], []
|
| 128 |
+
for step in range(5):
|
| 129 |
+
mask = _automatic_frame_mask(rgb, frame, step)
|
| 130 |
+
masks.append(mask)
|
| 131 |
+
quad = _quad_from_mask(mask)
|
| 132 |
+
if quad is not None:
|
| 133 |
+
quads.append(quad)
|
| 134 |
+
if not quads:
|
| 135 |
+
return _mask_rgb(masks[2]), rgb.copy(), "0", "Riferimento non individuato. Usare la regolazione manuale visibile sotto.", None
|
| 136 |
+
median = np.median(np.asarray(quads), axis=0).astype(np.int32)
|
| 137 |
+
contour = median.reshape((-1, 1, 2))
|
| 138 |
+
geometry = rgb.copy()
|
| 139 |
+
cv2.polylines(geometry, [contour], True, (25, 220, 70), 5)
|
| 140 |
+
for vertex in median:
|
| 141 |
+
cv2.circle(geometry, tuple(vertex), 13, (255, 45, 45), -1)
|
| 142 |
+
area = int(abs(cv2.contourArea(median)))
|
| 143 |
+
return _mask_rgb(masks[2]), geometry, f"{area}", f"Riferimento rilevato: consenso in {len(quads)} tentativi su 5.", contour
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def _convert_space(image: np.ndarray, space: str) -> np.ndarray:
|
| 147 |
+
if space == "HSV":
|
| 148 |
+
return cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
|
| 149 |
+
if space == "LAB":
|
| 150 |
+
return cv2.cvtColor(image, cv2.COLOR_RGB2LAB)
|
| 151 |
+
return image.copy()
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _space_updates(space: str):
|
| 155 |
+
if space == "HSV":
|
| 156 |
+
values = [("H minimo", 30, 179, True), ("H massimo", 80, 179, True), ("S minimo", 40, 255, True), ("S massimo", 255, 255, True), ("V minimo", 40, 255, True), ("V massimo", 255, 255, True)]
|
| 157 |
+
elif space == "ExG":
|
| 158 |
+
values = [("Soglia minima ExG", 40, 255, True), ("", 255, 255, False), ("", 0, 255, False), ("", 255, 255, False), ("", 0, 255, False), ("", 255, 255, False)]
|
| 159 |
+
elif space == "LAB":
|
| 160 |
+
values = [("L minimo", 0, 255, True), ("L massimo", 255, 255, True), ("a minimo", 0, 255, True), ("a massimo", 110, 255, True), ("b minimo", 130, 255, True), ("b massimo", 255, 255, True)]
|
| 161 |
+
else:
|
| 162 |
+
values = [("R minimo", 0, 255, True), ("R massimo", 100, 255, True), ("G minimo", 100, 255, True), ("G massimo", 255, 255, True), ("B minimo", 0, 255, True), ("B massimo", 100, 255, True)]
|
| 163 |
+
return tuple(gr.update(label=label, value=value, maximum=maximum, visible=visible) for label, value, maximum, visible in values)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def _threshold_mask(image: np.ndarray, space: str, values: list[float]) -> np.ndarray:
|
| 167 |
+
c1_min, c1_max, c2_min, c2_max, c3_min, c3_max = values
|
| 168 |
+
if space == "ExG":
|
| 169 |
+
f = image.astype(np.float32)
|
| 170 |
+
exg = 2 * f[:, :, 1] - f[:, :, 0] - f[:, :, 2]
|
| 171 |
+
return np.where(exg >= float(c1_min), 255, 0).astype(np.uint8)
|
| 172 |
+
conv = _convert_space(image, space)
|
| 173 |
+
lo = np.array([min(c1_min, c1_max), min(c2_min, c2_max), min(c3_min, c3_max)], dtype=np.uint8)
|
| 174 |
+
hi = np.array([max(c1_min, c1_max), max(c2_min, c2_max), max(c3_min, c3_max)], dtype=np.uint8)
|
| 175 |
+
return cv2.inRange(conv, lo, hi)
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def _morph(mask: np.ndarray, mode: str, intensity: int) -> np.ndarray:
|
| 179 |
+
if mode == "Nessuna" or int(intensity) <= 0:
|
| 180 |
+
return mask
|
| 181 |
+
size = int(intensity) * 2 + 1
|
| 182 |
+
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (size, size))
|
| 183 |
+
if mode == "Apertura":
|
| 184 |
+
return cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
|
| 185 |
+
if mode == "Chiusura":
|
| 186 |
+
return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
|
| 187 |
+
return cv2.morphologyEx(cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel), cv2.MORPH_CLOSE, kernel)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def _auto_morph(mask: np.ndarray, image: np.ndarray) -> np.ndarray:
|
| 191 |
+
size = 3 if max(image.shape[:2]) < 1400 else 5
|
| 192 |
+
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (size, size))
|
| 193 |
+
return cv2.morphologyEx(cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel), cv2.MORPH_CLOSE, kernel)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def calibrate_reference_manual(image: Any, space: str, c1_min, c1_max, c2_min, c2_max, c3_min, c3_max):
|
| 197 |
+
rgb = _rgb(image)
|
| 198 |
+
if rgb is None:
|
| 199 |
+
return None, None, "0", "Caricare una fotografia.", None
|
| 200 |
+
mask = _threshold_mask(rgb, space, [c1_min, c1_max, c2_min, c2_max, c3_min, c3_max])
|
| 201 |
+
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
|
| 202 |
+
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
|
| 203 |
+
quad = _quad_from_mask(mask)
|
| 204 |
+
geometry = rgb.copy()
|
| 205 |
+
area = 0
|
| 206 |
+
contour = None
|
| 207 |
+
if quad is not None:
|
| 208 |
+
contour = quad.astype(np.int32).reshape((-1, 1, 2))
|
| 209 |
+
area = int(abs(cv2.contourArea(quad)))
|
| 210 |
+
cv2.polylines(geometry, [contour], True, (25, 220, 70), 5)
|
| 211 |
+
for v in quad.astype(np.int32):
|
| 212 |
+
cv2.circle(geometry, tuple(v), 13, (255, 45, 45), -1)
|
| 213 |
+
message = "Riferimento individuato con le soglie manuali."
|
| 214 |
+
else:
|
| 215 |
+
message = "Riferimento non individuato con queste soglie."
|
| 216 |
+
return _mask_rgb(mask), geometry, str(area), message, contour
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def sample_manual_thresholds(image: Any, space: str, evt: gr.SelectData):
|
| 220 |
+
rgb = _rgb(image)
|
| 221 |
+
if rgb is None:
|
| 222 |
+
return _space_updates(space)
|
| 223 |
+
try:
|
| 224 |
+
x, y = map(int, evt.index)
|
| 225 |
+
except Exception:
|
| 226 |
+
return _space_updates(space)
|
| 227 |
+
h, w = rgb.shape[:2]
|
| 228 |
+
if not (0 <= x < w and 0 <= y < h):
|
| 229 |
+
return _space_updates(space)
|
| 230 |
+
pixel = rgb[y, x]
|
| 231 |
+
if space == "ExG":
|
| 232 |
+
r, g, b = [float(v) for v in pixel]
|
| 233 |
+
exg = int(np.clip(2 * g - r - b, -255, 510))
|
| 234 |
+
return (gr.update(value=max(-255, exg - 20)), gr.update(), gr.update(), gr.update(), gr.update(), gr.update())
|
| 235 |
+
converted = _convert_space(np.uint8([[pixel]]), space)[0, 0]
|
| 236 |
+
max1 = 179 if space == "HSV" else 255
|
| 237 |
+
vals = [int(v) for v in converted]
|
| 238 |
+
return (
|
| 239 |
+
gr.update(value=max(0, vals[0] - 25)), gr.update(value=min(max1, vals[0] + 25)),
|
| 240 |
+
gr.update(value=max(0, vals[1] - 25)), gr.update(value=min(255, vals[1] + 25)),
|
| 241 |
+
gr.update(value=max(0, vals[2] - 25)), gr.update(value=min(255, vals[2] + 25)),
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def _valid_area_mask(image: np.ndarray, quad: Any) -> np.ndarray:
|
| 246 |
+
valid = np.zeros(image.shape[:2], dtype=np.uint8)
|
| 247 |
+
if quad is None:
|
| 248 |
+
valid.fill(255)
|
| 249 |
+
else:
|
| 250 |
+
contour = np.asarray(quad, dtype=np.int32).reshape((-1, 1, 2))
|
| 251 |
+
cv2.fillPoly(valid, [contour], 255)
|
| 252 |
+
return valid
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def _measurement_values(mask: np.ndarray, image: np.ndarray, quad: Any, width_cm: float, height_cm: float, l1_cm: float, l2_cm: float):
|
| 256 |
+
plant_px = int(np.count_nonzero(mask))
|
| 257 |
+
valid = _valid_area_mask(image, quad)
|
| 258 |
+
ref_px = int(np.count_nonzero(valid))
|
| 259 |
+
pct = 100.0 * plant_px / ref_px if ref_px else 0.0
|
| 260 |
+
area_cm2 = None
|
| 261 |
+
try:
|
| 262 |
+
width_cm, height_cm, l1_cm, l2_cm = map(float, [width_cm, height_cm, l1_cm, l2_cm])
|
| 263 |
+
if width_cm > 0 and height_cm > 0 and l1_cm > 0 and l2_cm > 0 and ref_px > 0:
|
| 264 |
+
area_cm2 = (plant_px / ref_px) * (width_cm * height_cm) * ((l2_cm / l1_cm) ** 2)
|
| 265 |
+
except Exception:
|
| 266 |
+
area_cm2 = None
|
| 267 |
+
return plant_px, ref_px, pct, area_cm2
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def _metrics_html(mask: np.ndarray, image: np.ndarray, quad: Any, width_cm: float, height_cm: float, l1_cm: float, l2_cm: float, title="Risultati") -> str:
|
| 271 |
+
plant_px, ref_px, pct, area_cm2 = _measurement_values(mask, image, quad, width_cm, height_cm, l1_cm, l2_cm)
|
| 272 |
+
real = f"{area_cm2:.2f} cm²" if area_cm2 is not None else "—"
|
| 273 |
+
plant_text = f"{plant_px:,}".replace(",", ".")
|
| 274 |
+
ref_text = f"{ref_px:,}".replace(",", ".")
|
| 275 |
+
return (
|
| 276 |
+
f'<div class="metrics-wrap"><div class="metrics-title">{title}</div><div class="metric-grid">'
|
| 277 |
+
f'<div class="metric-card"><span>Pixel pianta</span><strong>{plant_text}</strong></div>'
|
| 278 |
+
f'<div class="metric-card"><span>Pixel riferimento</span><strong>{ref_text}</strong></div>'
|
| 279 |
+
f'<div class="metric-card"><span>Area sul riferimento</span><strong>{pct:.2f}%</strong></div>'
|
| 280 |
+
f'<div class="metric-card"><span>Area reale stimata</span><strong>{real}</strong></div>'
|
| 281 |
+
'</div></div>'
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def automatic_plant_segmentation(image: Any, quad: Any, width_cm, height_cm, l1_cm, l2_cm):
|
| 286 |
+
rgb = _rgb(image)
|
| 287 |
+
if rgb is None:
|
| 288 |
+
return None, None, '<div class="warning">Caricare una fotografia.</div>', "Caricare una fotografia.", None, None
|
| 289 |
+
f = rgb.astype(np.float32)
|
| 290 |
+
exg = 2 * f[:, :, 1] - f[:, :, 0] - f[:, :, 2]
|
| 291 |
+
mask = np.where(exg >= 40, 255, 0).astype(np.uint8)
|
| 292 |
+
mask = _auto_morph(mask, rgb)
|
| 293 |
+
mask = cv2.bitwise_and(mask, _valid_area_mask(rgb, quad))
|
| 294 |
+
over = _overlay(rgb, mask)
|
| 295 |
+
clean = cv2.bitwise_and(rgb, rgb, mask=mask)
|
| 296 |
+
return _mask_rgb(mask), over, _metrics_html(mask, rgb, quad, width_cm, height_cm, l1_cm, l2_cm, "Segmentazione automatica"), "Eseguita segmentazione ExG con pulizia automatica.", _save_png(clean, "pianta_auto_"), mask
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def manual_plant_segmentation(image: Any, space: str, c1_min, c1_max, c2_min, c2_max, c3_min, c3_max, morph_mode: str, morph_int: int, quad: Any, width_cm, height_cm, l1_cm, l2_cm):
|
| 300 |
+
rgb = _rgb(image)
|
| 301 |
+
if rgb is None:
|
| 302 |
+
return None, None, '<div class="warning">Caricare una fotografia.</div>', "Caricare una fotografia.", None, None
|
| 303 |
+
mask = _threshold_mask(rgb, space, [c1_min, c1_max, c2_min, c2_max, c3_min, c3_max])
|
| 304 |
+
mask = _morph(mask, morph_mode, morph_int)
|
| 305 |
+
mask = cv2.bitwise_and(mask, _valid_area_mask(rgb, quad))
|
| 306 |
+
over = _overlay(rgb, mask)
|
| 307 |
+
clean = cv2.bitwise_and(rgb, rgb, mask=mask)
|
| 308 |
+
return _mask_rgb(mask), over, _metrics_html(mask, rgb, quad, width_cm, height_cm, l1_cm, l2_cm, "Segmentazione manuale"), f"Eseguita segmentazione in {space}.", _save_png(clean, "pianta_manuale_"), mask
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def metrics_from_mask_component(mask_image: Any, source_image: Any, quad: Any, width_cm, height_cm, l1_cm, l2_cm):
|
| 312 |
+
rgb = _rgb(source_image)
|
| 313 |
+
arr = _rgb(mask_image)
|
| 314 |
+
if rgb is None or arr is None:
|
| 315 |
+
return '<div class="warning">Nessuna maschera disponibile.</div>', None
|
| 316 |
+
gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
|
| 317 |
+
mask = np.where(gray > 127, 255, 0).astype(np.uint8)
|
| 318 |
+
clean = cv2.bitwise_and(rgb, rgb, mask=mask)
|
| 319 |
+
return _metrics_html(mask, rgb, quad, width_cm, height_cm, l1_cm, l2_cm, "FastSAM"), _save_png(clean, "pianta_fastsam_")
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def hybrid_automatic(image: Any, quad: Any, width_cm, height_cm, l1_cm, l2_cm, progress=gr.Progress()):
|
| 323 |
+
rgb = _rgb(image)
|
| 324 |
+
if rgb is None:
|
| 325 |
+
return None, None, '<div class="warning">Caricare una fotografia.</div>', "Caricare una fotografia.", None
|
| 326 |
+
if _FASTSAM_LOADER is None:
|
| 327 |
+
return None, None, '<div class="warning">FastSAM non configurato.</div>', "FastSAM non configurato.", None
|
| 328 |
+
h, w = rgb.shape[:2]
|
| 329 |
+
valid = _valid_area_mask(rgb, quad)
|
| 330 |
+
ref_px = max(1, int(np.count_nonzero(valid)))
|
| 331 |
+
exclude = cv2.bitwise_not(valid)
|
| 332 |
+
instances = []
|
| 333 |
+
model = _FASTSAM_LOADER()
|
| 334 |
+
max_side = 1024
|
| 335 |
+
scale = min(1.0, max_side / max(h, w))
|
| 336 |
+
infer = cv2.resize(rgb, (round(w * scale), round(h * scale)), interpolation=cv2.INTER_AREA) if scale < 1 else rgb
|
| 337 |
+
|
| 338 |
+
for step in range(5):
|
| 339 |
+
progress((step + 0.2) / 5.5, desc=f"FastSAM: tentativo {step + 1} di 5")
|
| 340 |
+
allowed = cv2.bitwise_not(exclude)
|
| 341 |
+
if np.count_nonzero(allowed) / ref_px < 0.10:
|
| 342 |
+
break
|
| 343 |
+
if step == 0:
|
| 344 |
+
m = cv2.moments(valid)
|
| 345 |
+
if m["m00"] == 0:
|
| 346 |
+
break
|
| 347 |
+
cx, cy = int(m["m10"] / m["m00"]), int(m["m01"] / m["m00"])
|
| 348 |
+
else:
|
| 349 |
+
dist = cv2.distanceTransform(allowed, cv2.DIST_L2, 5)
|
| 350 |
+
_, max_val, _, max_loc = cv2.minMaxLoc(dist)
|
| 351 |
+
if max_val < 5:
|
| 352 |
+
break
|
| 353 |
+
cx, cy = max_loc
|
| 354 |
+
ix, iy = round(cx * scale), round(cy * scale)
|
| 355 |
+
try:
|
| 356 |
+
results = model.predict(infer, points=[[ix, iy]], labels=[1], device="cpu", imgsz=1024, retina_masks=True, verbose=False)
|
| 357 |
+
except Exception as exc:
|
| 358 |
+
return None, None, '<div class="warning">Errore FastSAM.</div>', f"FastSAM non disponibile: {exc}", None
|
| 359 |
+
if not results or results[0].masks is None or len(results[0].masks.data) == 0:
|
| 360 |
+
cv2.circle(exclude, (cx, cy), 20, 255, -1)
|
| 361 |
+
continue
|
| 362 |
+
data = results[0].masks.data.detach().float().cpu().numpy()
|
| 363 |
+
candidates = []
|
| 364 |
+
for raw in data:
|
| 365 |
+
candidate = cv2.resize(raw, (w, h), interpolation=cv2.INTER_NEAREST) > 0.5
|
| 366 |
+
if candidate[cy, cx]:
|
| 367 |
+
candidates.append(candidate)
|
| 368 |
+
selected = min(candidates, key=lambda m: int(m.sum())) if candidates else (cv2.resize(data[0], (w, h), interpolation=cv2.INTER_NEAREST) > 0.5)
|
| 369 |
+
mask = selected.astype(np.uint8) * 255
|
| 370 |
+
mask = cv2.bitwise_and(mask, valid)
|
| 371 |
+
new_pixels = cv2.bitwise_and(mask, cv2.bitwise_not(exclude))
|
| 372 |
+
if np.count_nonzero(new_pixels) < 100:
|
| 373 |
+
cv2.circle(exclude, (cx, cy), 20, 255, -1)
|
| 374 |
+
continue
|
| 375 |
+
instances.append(mask)
|
| 376 |
+
exclude = cv2.bitwise_or(exclude, mask)
|
| 377 |
+
|
| 378 |
+
if not instances:
|
| 379 |
+
return None, None, '<div class="warning">Il metodo ibrido non ha isolato una regione valida.</div>', "Il metodo ibrido non ha isolato una regione valida.", None
|
| 380 |
+
|
| 381 |
+
f = rgb.astype(np.float32)
|
| 382 |
+
exg = np.clip(2 * f[:, :, 1] - f[:, :, 0] - f[:, :, 2], -255, 510)
|
| 383 |
+
scores = [cv2.mean(exg.astype(np.float32), mask=m)[0] for m in instances]
|
| 384 |
+
best = instances[int(np.argmax(scores))]
|
| 385 |
+
over = _overlay(rgb, best, (165, 55, 220))
|
| 386 |
+
clean = cv2.bitwise_and(rgb, rgb, mask=best)
|
| 387 |
+
return _mask_rgb(best), over, _metrics_html(best, rgb, quad, width_cm, height_cm, l1_cm, l2_cm, "Metodo ibrido"), f"Esplorazione completata: {len(instances)} regioni candidate, selezione finale tramite ExG.", _save_png(clean, "pianta_ibrida_")
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def _guide_html():
|
| 391 |
+
return '''<div class="guide-panel"><h3>Guida rapida</h3>
|
| 392 |
+
<p><b>1 · Caricamento</b><br>Usa il dataset oppure carica una fotografia.</p>
|
| 393 |
+
<p><b>2 · Calibrazione</b><br>Prova prima il rilevamento automatico della cornice. Se serve, usa i controlli manuali già visibili.</p>
|
| 394 |
+
<p><b>3 · Segmentazione</b><br>Esegui il metodo automatico e controlla maschera, overlay e misure. Poi sperimenta con gli altri spazi di colore.</p>
|
| 395 |
+
<p><b>4 · FastSAM</b><br>Clicca la pianta, controlla il punto e avvia la segmentazione.</p>
|
| 396 |
+
<p><b>5 · Metodo ibrido</b><br>Il programma esplora più regioni con FastSAM e seleziona quella con risposta ExG maggiore.</p>
|
| 397 |
+
<p><b>6 · Esportazione</b><br>Scarica le immagini pulite prodotte nei diversi passaggi.</p></div>'''
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def _steps_html():
|
| 401 |
+
labels = ["Caricamento", "Calibrazione", "Segmentazione e misure", "FastSAM", "Metodo ibrido", "Esportazione"]
|
| 402 |
+
boxes = "".join(f'<div class="step-box"><span>{i}</span><b>{label}</b></div>' for i, label in enumerate(labels, 1))
|
| 403 |
+
return f'<div class="step-strip">{boxes}</div>'
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def build_14_16_tab(load_dataset_image, sync_fastsam_source, select_fastsam_point, run_fastsam_cpu, load_fastsam_cpu):
|
| 407 |
+
global _FASTSAM_LOADER
|
| 408 |
+
_FASTSAM_LOADER = load_fastsam_cpu
|
| 409 |
+
|
| 410 |
+
with gr.Tab("14-16"):
|
| 411 |
+
gr.Markdown("## Laboratorio di analisi: calibrazione, segmentazione e misura")
|
| 412 |
+
gr.Markdown("Il percorso riunisce le funzioni del precedente Space sperimentale in una sequenza continua. Le azioni automatiche sono evidenziate; i controlli manuali rimangono sempre visibili sotto ogni passaggio.")
|
| 413 |
+
gr.HTML(_steps_html())
|
| 414 |
+
|
| 415 |
+
quad_state = gr.State(None)
|
| 416 |
+
fastsam_point = gr.State(None)
|
| 417 |
+
automatic_mask_state = gr.State(None)
|
| 418 |
+
|
| 419 |
+
with gr.Row():
|
| 420 |
+
with gr.Column(scale=4):
|
| 421 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 422 |
+
gr.HTML('<div class="panel-kicker">1 · CARICAMENTO</div>')
|
| 423 |
+
with gr.Row():
|
| 424 |
+
source = gr.Image(type="numpy", label="Fotografia del campione", interactive=True, height=430)
|
| 425 |
+
with gr.Column():
|
| 426 |
+
condition = gr.Radio(["NS", "S"], value="NS", label="Condizione dataset")
|
| 427 |
+
replicate = gr.Radio(["A", "B", "C"], value="A", label="Replica")
|
| 428 |
+
day = gr.Dropdown(["06", "09", "11", "14", "16", "19", "23", "26"], value="14", label="Giorno dal trapianto")
|
| 429 |
+
load_btn = gr.Button("Carica dal dataset", variant="primary", elem_classes=["action-button"])
|
| 430 |
+
load_status = gr.Textbox(label="Stato", interactive=False)
|
| 431 |
+
gr.HTML('<div class="note">La fotografia viene caricata una sola volta e alimenta tutti i passaggi successivi.</div>')
|
| 432 |
+
|
| 433 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 434 |
+
gr.HTML('<div class="panel-kicker">2 · CALIBRAZIONE DEL RIFERIMENTO</div>')
|
| 435 |
+
gr.Markdown("### Rilevamento automatico")
|
| 436 |
+
gr.Markdown("Usa per primo questo comando. I valori iniziali corrispondono al setup del dataset: sfondo nero e cornice rossa.")
|
| 437 |
+
with gr.Row():
|
| 438 |
+
bg = gr.Dropdown(["Nero", "Bianco"], value="Nero", label="Colore dello sfondo")
|
| 439 |
+
frame = gr.Dropdown(["Rossa", "Nera", "Bianca", "Viola", "Blu"], value="Rossa", label="Colore della cornice")
|
| 440 |
+
auto_cal_btn = gr.Button("Rileva automaticamente il riferimento", variant="primary", elem_classes=["action-button"])
|
| 441 |
+
with gr.Row():
|
| 442 |
+
cal_mask = gr.Image(label="Maschera del riferimento", interactive=False, height=300)
|
| 443 |
+
cal_geom = gr.Image(label="Geometria rilevata", interactive=False, height=300)
|
| 444 |
+
with gr.Row():
|
| 445 |
+
ref_px = gr.Textbox(label="Area del riferimento in pixel", interactive=False)
|
| 446 |
+
cal_status = gr.Textbox(label="Esito della calibrazione", interactive=False)
|
| 447 |
+
|
| 448 |
+
with gr.Group(elem_classes=["manual-panel"]):
|
| 449 |
+
gr.Markdown("### Regolazione manuale")
|
| 450 |
+
gr.Markdown("Questi controlli restano disponibili se il rilevamento automatico non è soddisfacente. Facendo click sulla fotografia sorgente si inizializzano le soglie intorno al colore selezionato.")
|
| 451 |
+
cal_space = gr.Radio(["HSV", "ExG", "RGB", "LAB"], value="HSV", label="Rappresentazione del colore")
|
| 452 |
+
with gr.Row():
|
| 453 |
+
r1min = gr.Slider(0, 179, 30, label="H minimo")
|
| 454 |
+
r1max = gr.Slider(0, 179, 80, label="H massimo")
|
| 455 |
+
r2min = gr.Slider(0, 255, 40, label="S minimo")
|
| 456 |
+
r2max = gr.Slider(0, 255, 255, label="S massimo")
|
| 457 |
+
r3min = gr.Slider(0, 255, 40, label="V minimo")
|
| 458 |
+
r3max = gr.Slider(0, 255, 255, label="V massimo")
|
| 459 |
+
manual_cal_btn = gr.Button("Calcola il riferimento con le soglie manuali", variant="primary", elem_classes=["action-button"])
|
| 460 |
+
|
| 461 |
+
gr.Markdown("### Misure geometriche per l’area reale")
|
| 462 |
+
gr.Markdown("La percentuale rispetto alla cornice è disponibile senza misure reali. Per stimare i cm² inserire base, altezza e le distanze L1 e L2 descritte nel tutorial del setup.")
|
| 463 |
+
with gr.Row():
|
| 464 |
+
width_cm = gr.Number(value=0, label="Base del riferimento · cm")
|
| 465 |
+
height_cm = gr.Number(value=0, label="Altezza del riferimento · cm")
|
| 466 |
+
l1_cm = gr.Number(value=0, label="Distanza L1 · cm")
|
| 467 |
+
l2_cm = gr.Number(value=0, label="Distanza L2 · cm")
|
| 468 |
+
|
| 469 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 470 |
+
gr.HTML('<div class="panel-kicker">3 · SEGMENTAZIONE E MISURE</div>')
|
| 471 |
+
gr.Markdown("### Segmentazione automatica")
|
| 472 |
+
gr.Markdown("Il primo tentativo usa ExG con soglia iniziale e pulizia morfologica automatica. Non richiede regolazioni.")
|
| 473 |
+
auto_seg_btn = gr.Button("Esegui la segmentazione automatica", variant="primary", elem_classes=["action-button"])
|
| 474 |
+
with gr.Row():
|
| 475 |
+
auto_mask = gr.Image(label="Maschera automatica", interactive=False, height=330)
|
| 476 |
+
auto_overlay = gr.Image(label="Overlay automatico", interactive=False, height=330)
|
| 477 |
+
auto_metrics = gr.HTML()
|
| 478 |
+
auto_status = gr.Textbox(label="Esito", interactive=False)
|
| 479 |
+
auto_file = gr.File(label="Scarica la pianta segmentata", interactive=False)
|
| 480 |
+
|
| 481 |
+
with gr.Group(elem_classes=["manual-panel"]):
|
| 482 |
+
gr.Markdown("### Regolazione manuale della segmentazione")
|
| 483 |
+
gr.Markdown("Usa questi parametri per confrontare ExG, HSV, RGB e Lab e per osservare l’effetto della pulizia morfologica.")
|
| 484 |
+
seg_space = gr.Radio(["ExG", "HSV", "RGB", "LAB"], value="ExG", label="Metodo di segmentazione")
|
| 485 |
+
with gr.Row():
|
| 486 |
+
s1min = gr.Slider(0, 255, 40, label="Soglia minima ExG")
|
| 487 |
+
s1max = gr.Slider(0, 255, 255, label="", visible=False)
|
| 488 |
+
s2min = gr.Slider(0, 255, 0, label="", visible=False)
|
| 489 |
+
s2max = gr.Slider(0, 255, 255, label="", visible=False)
|
| 490 |
+
s3min = gr.Slider(0, 255, 0, label="", visible=False)
|
| 491 |
+
s3max = gr.Slider(0, 255, 255, label="", visible=False)
|
| 492 |
+
with gr.Row():
|
| 493 |
+
morph_mode = gr.Radio(["Nessuna", "Apertura", "Chiusura", "Apertura + chiusura"], value="Apertura + chiusura", label="Pulizia morfologica")
|
| 494 |
+
morph_int = gr.Slider(1, 10, value=2, step=1, label="Intensità della pulizia")
|
| 495 |
+
manual_seg_btn = gr.Button("Esegui la segmentazione con i parametri manuali", variant="primary", elem_classes=["action-button"])
|
| 496 |
+
with gr.Row():
|
| 497 |
+
manual_mask = gr.Image(label="Maschera manuale", interactive=False, height=300)
|
| 498 |
+
manual_overlay = gr.Image(label="Overlay manuale", interactive=False, height=300)
|
| 499 |
+
manual_metrics = gr.HTML()
|
| 500 |
+
manual_status = gr.Textbox(label="Esito", interactive=False)
|
| 501 |
+
manual_file = gr.File(label="Scarica la pianta segmentata", interactive=False)
|
| 502 |
+
|
| 503 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 504 |
+
gr.HTML('<div class="panel-kicker">4 · FASTSAM</div>')
|
| 505 |
+
gr.HTML('<div class="fastsam-instruction"><b>Procedura</b><br>1. Clicca sulla pianta nell’immagine qui sotto.<br>2. Controlla il punto rosso.<br>3. Premi <b>Segmenta l’oggetto indicato</b>.</div>')
|
| 506 |
+
fastsam_image = gr.Image(type="numpy", label="Clicca sulla pianta", interactive=True, height=430)
|
| 507 |
+
fastsam_message = gr.Textbox(label="Istruzioni e risultato", value="Caricare una fotografia e scegliere un punto.", interactive=False)
|
| 508 |
+
fastsam_btn = gr.Button("Segmenta l’oggetto indicato", variant="primary", elem_classes=["action-button"])
|
| 509 |
+
with gr.Row():
|
| 510 |
+
fastsam_mask = gr.Image(label="Maschera FastSAM", interactive=False, visible=False, height=320)
|
| 511 |
+
fastsam_overlay = gr.Image(label="Overlay FastSAM", interactive=False, visible=False, height=320)
|
| 512 |
+
fastsam_metrics = gr.HTML()
|
| 513 |
+
fastsam_file = gr.File(label="Scarica la pianta segmentata", interactive=False)
|
| 514 |
+
fastsam_overlay_state = gr.State(None)
|
| 515 |
+
|
| 516 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 517 |
+
gr.HTML('<div class="panel-kicker">5 · METODO IBRIDO</div>')
|
| 518 |
+
gr.Markdown("Il metodo esplora automaticamente più regioni con FastSAM all’interno del riferimento e seleziona la regione finale confrontando l’indice ExG. È il passaggio più impegnativo sulla CPU.")
|
| 519 |
+
hybrid_btn = gr.Button("Esegui il metodo ibrido automatico", variant="primary", elem_classes=["action-button"])
|
| 520 |
+
with gr.Row():
|
| 521 |
+
hybrid_mask = gr.Image(label="Maschera ibrida", interactive=False, height=320)
|
| 522 |
+
hybrid_overlay = gr.Image(label="Overlay ibrido", interactive=False, height=320)
|
| 523 |
+
hybrid_metrics = gr.HTML()
|
| 524 |
+
hybrid_status = gr.Textbox(label="Esito", interactive=False)
|
| 525 |
+
hybrid_file = gr.File(label="Scarica la pianta segmentata", interactive=False)
|
| 526 |
+
|
| 527 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 528 |
+
gr.HTML('<div class="panel-kicker">6 · ESPORTAZIONE E DOCUMENTAZIONE</div>')
|
| 529 |
+
gr.Markdown("I file prodotti nei passaggi precedenti possono essere scaricati direttamente dai rispettivi pannelli.")
|
| 530 |
+
gr.Markdown("- [Guida tecnica dettagliata 14-16](README_14-16.md)\n- [Tutorial setup sperimentale](assets/14-16/Tutorial_setup_sperimentale.pdf)\n- [Scheda esperimento](assets/14-16/Scheda_esperimento.pdf)")
|
| 531 |
+
|
| 532 |
+
with gr.Column(scale=1, min_width=270):
|
| 533 |
+
gr.HTML(_guide_html())
|
| 534 |
+
gr.HTML('<div class="docs-panel"><h3>Documentazione</h3><p><a href="README_14-16.md" target="_blank">Guida tecnica 14-16</a></p><p><a href="assets/14-16/Tutorial_setup_sperimentale.pdf" target="_blank">Tutorial setup</a></p><p><a href="assets/14-16/Scheda_esperimento.pdf" target="_blank">Scheda esperimento</a></p></div>')
|
| 535 |
+
|
| 536 |
+
load_btn.click(load_dataset_image, [condition, replicate, day], [source, load_status])
|
| 537 |
+
auto_cal_btn.click(calibrate_reference_auto, [source, bg, frame], [cal_mask, cal_geom, ref_px, cal_status, quad_state])
|
| 538 |
+
manual_cal_btn.click(calibrate_reference_manual, [source, cal_space, r1min, r1max, r2min, r2max, r3min, r3max], [cal_mask, cal_geom, ref_px, cal_status, quad_state])
|
| 539 |
+
cal_space.change(_space_updates, cal_space, [r1min, r1max, r2min, r2max, r3min, r3max], show_progress="hidden")
|
| 540 |
+
source.select(sample_manual_thresholds, [source, cal_space], [r1min, r1max, r2min, r2max, r3min, r3max], show_progress="hidden")
|
| 541 |
+
|
| 542 |
+
auto_seg_btn.click(automatic_plant_segmentation, [source, quad_state, width_cm, height_cm, l1_cm, l2_cm], [auto_mask, auto_overlay, auto_metrics, auto_status, auto_file, automatic_mask_state])
|
| 543 |
+
seg_space.change(_space_updates, seg_space, [s1min, s1max, s2min, s2max, s3min, s3max], show_progress="hidden")
|
| 544 |
+
manual_seg_btn.click(manual_plant_segmentation, [source, seg_space, s1min, s1max, s2min, s2max, s3min, s3max, morph_mode, morph_int, quad_state, width_cm, height_cm, l1_cm, l2_cm], [manual_mask, manual_overlay, manual_metrics, manual_status, manual_file, automatic_mask_state])
|
| 545 |
+
|
| 546 |
+
source.change(sync_fastsam_source, source, [fastsam_image, fastsam_point, fastsam_mask, fastsam_overlay, fastsam_message, fastsam_overlay_state], show_progress="hidden")
|
| 547 |
+
fastsam_image.select(select_fastsam_point, [source], [fastsam_image, fastsam_point, fastsam_message], show_progress="hidden")
|
| 548 |
+
fastsam_btn.click(run_fastsam_cpu, [source, fastsam_point], [fastsam_mask, fastsam_overlay, fastsam_message, fastsam_overlay_state], concurrency_limit=1).then(
|
| 549 |
+
metrics_from_mask_component, [fastsam_mask, source, quad_state, width_cm, height_cm, l1_cm, l2_cm], [fastsam_metrics, fastsam_file]
|
| 550 |
+
)
|
| 551 |
+
hybrid_btn.click(hybrid_automatic, [source, quad_state, width_cm, height_cm, l1_cm, l2_cm], [hybrid_mask, hybrid_overlay, hybrid_metrics, hybrid_status, hybrid_file], concurrency_limit=1)
|
app.py
CHANGED
|
@@ -4,7 +4,6 @@ import os
|
|
| 4 |
import tempfile
|
| 5 |
from pathlib import Path
|
| 6 |
from typing import Any
|
| 7 |
-
from functools import lru_cache
|
| 8 |
|
| 9 |
os.environ.setdefault("YOLO_CONFIG_DIR", "/tmp/Ultralytics")
|
| 10 |
|
|
@@ -15,7 +14,8 @@ from PIL import Image, ImageDraw, ImageFont
|
|
| 15 |
from reportlab.lib.pagesizes import A4
|
| 16 |
from reportlab.lib.utils import ImageReader
|
| 17 |
from reportlab.pdfgen import canvas
|
| 18 |
-
from
|
|
|
|
| 19 |
|
| 20 |
try:
|
| 21 |
import spaces
|
|
@@ -33,8 +33,6 @@ DATASET_DIR = Path("dataset")
|
|
| 33 |
DAYS = ["06", "09", "11", "14", "16", "19", "23", "26"]
|
| 34 |
CONDITIONS = ["NS", "S"]
|
| 35 |
REPLICATES = ["A", "B", "C"]
|
| 36 |
-
FASTSAM_REPO = "Uminosachi/FastSAM"
|
| 37 |
-
FASTSAM_FILENAME = "FastSAM-s.pt"
|
| 38 |
|
| 39 |
|
| 40 |
@spaces.GPU(duration=1)
|
|
@@ -66,6 +64,17 @@ CSS = """
|
|
| 66 |
.action-button button:hover, button.action-button:hover {background:#155333 !important}
|
| 67 |
.action-button button:disabled, button.action-button:disabled {background:#8ca79a !important;color:#f6f6f6 !important}
|
| 68 |
.result-card img {border-radius:12px !important}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
"""
|
| 70 |
|
| 71 |
|
|
@@ -700,26 +709,6 @@ def compare_green_pair(image_a: Any, image_b: Any):
|
|
| 700 |
# -----------------------------------------------------------------------------
|
| 701 |
|
| 702 |
|
| 703 |
-
@lru_cache(maxsize=1)
|
| 704 |
-
def load_fastsam_cpu():
|
| 705 |
-
"""Carica FastSAM una sola volta e lo mantiene esplicitamente su CPU."""
|
| 706 |
-
from ultralytics import FastSAM
|
| 707 |
-
|
| 708 |
-
local_weight = Path(FASTSAM_FILENAME)
|
| 709 |
-
if local_weight.exists():
|
| 710 |
-
weight_path = local_weight
|
| 711 |
-
else:
|
| 712 |
-
weight_path = Path(
|
| 713 |
-
hf_hub_download(repo_id=FASTSAM_REPO, filename=FASTSAM_FILENAME)
|
| 714 |
-
)
|
| 715 |
-
model = FastSAM(str(weight_path))
|
| 716 |
-
# Il percorso visibile agli utenti deve restare CPU anche nello Space ZeroGPU.
|
| 717 |
-
try:
|
| 718 |
-
model.model.to("cpu")
|
| 719 |
-
except Exception:
|
| 720 |
-
pass
|
| 721 |
-
return model
|
| 722 |
-
|
| 723 |
|
| 724 |
def sync_fastsam_source(image: Any):
|
| 725 |
"""Sincronizza la copia cliccabile e azzera i risultati precedenti."""
|
|
@@ -860,9 +849,9 @@ def compare_overlays(color_overlay, ai_overlay):
|
|
| 860 |
|
| 861 |
|
| 862 |
with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hue="green"), css=CSS) as demo:
|
| 863 |
-
gr.Markdown("# Computer vision e piante - percorsi 6-
|
| 864 |
gr.Markdown(
|
| 865 |
-
"Lo Space raccoglie
|
| 866 |
"per le attività che richiedono fotografie già acquisite."
|
| 867 |
)
|
| 868 |
|
|
@@ -914,7 +903,8 @@ with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hu
|
|
| 914 |
height=440,
|
| 915 |
)
|
| 916 |
|
| 917 |
-
with gr.
|
|
|
|
| 918 |
gr.Markdown(
|
| 919 |
"Fare click sulla fotografia. Il programma ingrandisce esattamente 25 pixel e scrive "
|
| 920 |
"direttamente su ciascun pixel i valori del colore."
|
|
@@ -939,7 +929,8 @@ with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hu
|
|
| 939 |
show_progress="hidden",
|
| 940 |
)
|
| 941 |
|
| 942 |
-
with gr.
|
|
|
|
| 943 |
gr.HTML(
|
| 944 |
'<div class="info">La fotografia caricata sopra viene trasferita automaticamente nel pennello. '
|
| 945 |
"Dipingere una zona, scegliere un canale e applicare una variazione additiva. "
|
|
@@ -1015,6 +1006,7 @@ with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hu
|
|
| 1015 |
"Caricare la fotografia della caccia alla pianta segmentabile oppure scegliere un'immagine del dataset. "
|
| 1016 |
"La stessa immagine viene utilizzata in tutti i passaggi."
|
| 1017 |
)
|
|
|
|
| 1018 |
c_color_overlay_state = gr.State(None)
|
| 1019 |
c_ai_overlay_state = gr.State(None)
|
| 1020 |
with gr.Row():
|
|
@@ -1044,19 +1036,20 @@ with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hu
|
|
| 1044 |
[c_default_mask, c_default_overlay, c_default_message, c_default_raw],
|
| 1045 |
).then(lambda x: x, c_default_overlay, c_color_overlay_state)
|
| 1046 |
|
| 1047 |
-
with gr.
|
|
|
|
| 1048 |
gr.Markdown(
|
| 1049 |
"H descrive la tonalità lungo un cerchio da 0° a 360°. S descrive la saturazione e V la luminosità. "
|
| 1050 |
"Modificando i limiti cambia l'insieme dei pixel accettati."
|
| 1051 |
)
|
| 1052 |
with gr.Row():
|
| 1053 |
-
c_h_min = gr.Slider(0, 360, value=DEFAULT_HSV["h_min"], step=2, label="H minimo
|
| 1054 |
-
c_h_max = gr.Slider(0, 360, value=DEFAULT_HSV["h_max"], step=2, label="H massimo
|
| 1055 |
with gr.Row():
|
| 1056 |
-
c_s_min = gr.Slider(0, 100, value=DEFAULT_HSV["s_min"], step=1, label="S minimo
|
| 1057 |
-
c_s_max = gr.Slider(0, 100, value=DEFAULT_HSV["s_max"], step=1, label="S massimo
|
| 1058 |
-
c_v_min = gr.Slider(0, 100, value=DEFAULT_HSV["v_min"], step=1, label="V minimo
|
| 1059 |
-
c_v_max = gr.Slider(0, 100, value=DEFAULT_HSV["v_max"], step=1, label="V massimo
|
| 1060 |
c_custom_button = gr.Button("Applica le soglie HSV modificate", variant="primary", elem_classes=["action-button"])
|
| 1061 |
with gr.Row():
|
| 1062 |
c_custom_mask = gr.Image(label="Maschera HSV modificata", interactive=False)
|
|
@@ -1069,7 +1062,8 @@ with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hu
|
|
| 1069 |
[c_custom_mask, c_custom_overlay, c_custom_message, c_custom_raw],
|
| 1070 |
).then(lambda x: x, c_custom_overlay, c_color_overlay_state)
|
| 1071 |
|
| 1072 |
-
with gr.
|
|
|
|
| 1073 |
gr.HTML(
|
| 1074 |
'<div class="info"><b>ExG = 2G - R - B.</b> Il programma calcola questo valore per ogni pixel '
|
| 1075 |
"e conserva i pixel per i quali ExG supera la soglia scelta.</div>"
|
|
@@ -1088,7 +1082,8 @@ with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hu
|
|
| 1088 |
).then(lambda x: x, c_exg_overlay, c_color_overlay_state)
|
| 1089 |
|
| 1090 |
c_fastsam_point = gr.State(None)
|
| 1091 |
-
with gr.
|
|
|
|
| 1092 |
gr.HTML(
|
| 1093 |
'<div class="fastsam-instruction"><b>Procedura FastSAM</b><br>'
|
| 1094 |
'1. Cliccare sulla pianta nell’immagine immediatamente sotto.<br>'
|
|
@@ -1151,6 +1146,14 @@ with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hu
|
|
| 1151 |
c_compare = gr.Image(label="Metodo cromatico | FastSAM", interactive=False)
|
| 1152 |
c_compare_button.click(compare_overlays, [c_color_overlay_state, c_ai_overlay_state], c_compare)
|
| 1153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1154 |
|
| 1155 |
if __name__ == "__main__":
|
| 1156 |
demo.queue(default_concurrency_limit=4).launch(
|
|
|
|
| 4 |
import tempfile
|
| 5 |
from pathlib import Path
|
| 6 |
from typing import Any
|
|
|
|
| 7 |
|
| 8 |
os.environ.setdefault("YOLO_CONFIG_DIR", "/tmp/Ultralytics")
|
| 9 |
|
|
|
|
| 14 |
from reportlab.lib.pagesizes import A4
|
| 15 |
from reportlab.lib.utils import ImageReader
|
| 16 |
from reportlab.pdfgen import canvas
|
| 17 |
+
from fastsam_shared import load_fastsam_cpu
|
| 18 |
+
from advanced_14_16 import build_14_16_tab
|
| 19 |
|
| 20 |
try:
|
| 21 |
import spaces
|
|
|
|
| 33 |
DAYS = ["06", "09", "11", "14", "16", "19", "23", "26"]
|
| 34 |
CONDITIONS = ["NS", "S"]
|
| 35 |
REPLICATES = ["A", "B", "C"]
|
|
|
|
|
|
|
| 36 |
|
| 37 |
|
| 38 |
@spaces.GPU(duration=1)
|
|
|
|
| 64 |
.action-button button:hover, button.action-button:hover {background:#155333 !important}
|
| 65 |
.action-button button:disabled, button.action-button:disabled {background:#8ca79a !important;color:#f6f6f6 !important}
|
| 66 |
.result-card img {border-radius:12px !important}
|
| 67 |
+
.workflow-panel {border:1px solid #cbd8cf !important;border-radius:10px !important;padding:14px !important;margin:10px 0 18px 0 !important;background:#ffffff !important}
|
| 68 |
+
.manual-panel {border:1px solid #9fb3a7 !important;border-left:5px solid #6f8f7a !important;border-radius:8px !important;padding:14px !important;margin:14px 0 !important;background:#f8faf8 !important}
|
| 69 |
+
.panel-kicker {font-weight:800;color:#17653a;letter-spacing:.04em;font-size:1.02rem;margin-bottom:8px}
|
| 70 |
+
.step-strip {display:flex;gap:7px;flex-wrap:wrap;margin:10px 0 18px 0}
|
| 71 |
+
.step-box {display:flex;align-items:center;gap:8px;min-width:145px;flex:1;background:#edf6ef;border:1px solid #b9d2c0;border-radius:9px;padding:9px 12px;color:#194e31}
|
| 72 |
+
.step-box span {display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background:#1f6f43;color:white;font-weight:800}
|
| 73 |
+
.guide-panel,.docs-panel {background:#f7faf8;border:1px solid #d2ddd5;border-radius:10px;padding:14px;margin:10px 0;line-height:1.42}
|
| 74 |
+
.guide-panel h3,.docs-panel h3 {color:#17653a;margin-top:0}
|
| 75 |
+
.metrics-wrap {margin:8px 0 12px}.metrics-title{font-weight:800;color:#194e31;margin-bottom:7px}.metric-grid{display:grid;grid-template-columns:repeat(4,minmax(120px,1fr));gap:8px}.metric-card{border:1px solid #d4ded7;border-radius:8px;padding:10px;background:#f8fbf9}.metric-card span{display:block;font-size:.82rem;color:#596660}.metric-card strong{display:block;font-size:1.35rem;color:#153c28;margin-top:3px}
|
| 76 |
+
@media(max-width:900px){.metric-grid{grid-template-columns:repeat(2,minmax(120px,1fr))}.step-box{min-width:45%}}
|
| 77 |
+
|
| 78 |
"""
|
| 79 |
|
| 80 |
|
|
|
|
| 709 |
# -----------------------------------------------------------------------------
|
| 710 |
|
| 711 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
|
| 713 |
def sync_fastsam_source(image: Any):
|
| 714 |
"""Sincronizza la copia cliccabile e azzera i risultati precedenti."""
|
|
|
|
| 849 |
|
| 850 |
|
| 851 |
with gr.Blocks(title="Computer vision e piante", theme=gr.themes.Soft(primary_hue="green"), css=CSS) as demo:
|
| 852 |
+
gr.Markdown("# Computer vision e piante - percorsi 6-16 anni")
|
| 853 |
gr.Markdown(
|
| 854 |
+
"Lo Space raccoglie quattro percorsi progressivi. Il dataset sperimentale completo rimane condiviso "
|
| 855 |
"per le attività che richiedono fotografie già acquisite."
|
| 856 |
)
|
| 857 |
|
|
|
|
| 903 |
height=440,
|
| 904 |
)
|
| 905 |
|
| 906 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 907 |
+
gr.Markdown("### 1. Esplora un patch 5×5")
|
| 908 |
gr.Markdown(
|
| 909 |
"Fare click sulla fotografia. Il programma ingrandisce esattamente 25 pixel e scrive "
|
| 910 |
"direttamente su ciascun pixel i valori del colore."
|
|
|
|
| 929 |
show_progress="hidden",
|
| 930 |
)
|
| 931 |
|
| 932 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 933 |
+
gr.Markdown("### 2. Laboratorio del colore")
|
| 934 |
gr.HTML(
|
| 935 |
'<div class="info">La fotografia caricata sopra viene trasferita automaticamente nel pennello. '
|
| 936 |
"Dipingere una zona, scegliere un canale e applicare una variazione additiva. "
|
|
|
|
| 1006 |
"Caricare la fotografia della caccia alla pianta segmentabile oppure scegliere un'immagine del dataset. "
|
| 1007 |
"La stessa immagine viene utilizzata in tutti i passaggi."
|
| 1008 |
)
|
| 1009 |
+
gr.HTML('<div class="step-strip"><div class="step-box"><span>1</span><b>Caricamento</b></div><div class="step-box"><span>2</span><b>HSV</b></div><div class="step-box"><span>3</span><b>ExG</b></div><div class="step-box"><span>4</span><b>FastSAM</b></div><div class="step-box"><span>5</span><b>Confronto</b></div></div>')
|
| 1010 |
c_color_overlay_state = gr.State(None)
|
| 1011 |
c_ai_overlay_state = gr.State(None)
|
| 1012 |
with gr.Row():
|
|
|
|
| 1036 |
[c_default_mask, c_default_overlay, c_default_message, c_default_raw],
|
| 1037 |
).then(lambda x: x, c_default_overlay, c_color_overlay_state)
|
| 1038 |
|
| 1039 |
+
with gr.Group(elem_classes=["manual-panel"]):
|
| 1040 |
+
gr.Markdown("### 2. Modifica le soglie nello spazio HSV")
|
| 1041 |
gr.Markdown(
|
| 1042 |
"H descrive la tonalità lungo un cerchio da 0° a 360°. S descrive la saturazione e V la luminosità. "
|
| 1043 |
"Modificando i limiti cambia l'insieme dei pixel accettati."
|
| 1044 |
)
|
| 1045 |
with gr.Row():
|
| 1046 |
+
c_h_min = gr.Slider(0, 360, value=DEFAULT_HSV["h_min"], step=2, label="H minimo · gradi")
|
| 1047 |
+
c_h_max = gr.Slider(0, 360, value=DEFAULT_HSV["h_max"], step=2, label="H massimo · gradi")
|
| 1048 |
with gr.Row():
|
| 1049 |
+
c_s_min = gr.Slider(0, 100, value=DEFAULT_HSV["s_min"], step=1, label="S minimo · %")
|
| 1050 |
+
c_s_max = gr.Slider(0, 100, value=DEFAULT_HSV["s_max"], step=1, label="S massimo · %")
|
| 1051 |
+
c_v_min = gr.Slider(0, 100, value=DEFAULT_HSV["v_min"], step=1, label="V minimo · %")
|
| 1052 |
+
c_v_max = gr.Slider(0, 100, value=DEFAULT_HSV["v_max"], step=1, label="V massimo · %")
|
| 1053 |
c_custom_button = gr.Button("Applica le soglie HSV modificate", variant="primary", elem_classes=["action-button"])
|
| 1054 |
with gr.Row():
|
| 1055 |
c_custom_mask = gr.Image(label="Maschera HSV modificata", interactive=False)
|
|
|
|
| 1062 |
[c_custom_mask, c_custom_overlay, c_custom_message, c_custom_raw],
|
| 1063 |
).then(lambda x: x, c_custom_overlay, c_color_overlay_state)
|
| 1064 |
|
| 1065 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 1066 |
+
gr.Markdown("### 3. Prova l’indice ExG")
|
| 1067 |
gr.HTML(
|
| 1068 |
'<div class="info"><b>ExG = 2G - R - B.</b> Il programma calcola questo valore per ogni pixel '
|
| 1069 |
"e conserva i pixel per i quali ExG supera la soglia scelta.</div>"
|
|
|
|
| 1082 |
).then(lambda x: x, c_exg_overlay, c_color_overlay_state)
|
| 1083 |
|
| 1084 |
c_fastsam_point = gr.State(None)
|
| 1085 |
+
with gr.Group(elem_classes=["workflow-panel"]):
|
| 1086 |
+
gr.Markdown("### 4. Prova FastSAM")
|
| 1087 |
gr.HTML(
|
| 1088 |
'<div class="fastsam-instruction"><b>Procedura FastSAM</b><br>'
|
| 1089 |
'1. Cliccare sulla pianta nell’immagine immediatamente sotto.<br>'
|
|
|
|
| 1146 |
c_compare = gr.Image(label="Metodo cromatico | FastSAM", interactive=False)
|
| 1147 |
c_compare_button.click(compare_overlays, [c_color_overlay_state, c_ai_overlay_state], c_compare)
|
| 1148 |
|
| 1149 |
+
build_14_16_tab(
|
| 1150 |
+
load_dataset_image=load_dataset_image,
|
| 1151 |
+
sync_fastsam_source=sync_fastsam_source,
|
| 1152 |
+
select_fastsam_point=select_fastsam_point,
|
| 1153 |
+
run_fastsam_cpu=run_fastsam_cpu,
|
| 1154 |
+
load_fastsam_cpu=load_fastsam_cpu,
|
| 1155 |
+
)
|
| 1156 |
+
|
| 1157 |
|
| 1158 |
if __name__ == "__main__":
|
| 1159 |
demo.queue(default_concurrency_limit=4).launch(
|
assets/14-16/Fig1_cariacamento.JPG
ADDED
|
|
assets/14-16/Fig2_fasi.JPG
ADDED
|
|
assets/14-16/Fig3_fase1_auto.JPG
ADDED
|
|
assets/14-16/Fig4_fase1_manuale.JPG
ADDED
|
|
Git LFS Details
|
assets/14-16/Fig5_fase2.JPG
ADDED
|
|
Git LFS Details
|
assets/14-16/Fig6_fase3.JPG
ADDED
|
|
assets/14-16/Fig7_IA_semantica.JPG
ADDED
|
|
assets/14-16/Scheda_esperimento.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3cbf7a3a59db1ed1178f04158ad60cd823c1313733991cbe2ee632566caf2c19
|
| 3 |
+
size 983970
|
assets/14-16/Tutorial_setup_sperimentale.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cf68fe5a8d9832899f1fb5a54ec80a360de516412f3395bb541b95fd2081d549
|
| 3 |
+
size 337935
|
dataset/README_dataset.md
CHANGED
|
@@ -4,4 +4,4 @@ Il dataset contiene 48 fotografie: due condizioni (`NS` non stress, `S` stress i
|
|
| 4 |
|
| 5 |
Esempio: `S_B_19.jpg` indica condizione di stress idrico, replica B, 19 giorni dal trapianto.
|
| 6 |
|
| 7 |
-
Le immagini derivano dall’esperimento CREA-OF descritto in `assets/
|
|
|
|
| 4 |
|
| 5 |
Esempio: `S_B_19.jpg` indica condizione di stress idrico, replica B, 19 giorni dal trapianto.
|
| 6 |
|
| 7 |
+
Le immagini derivano dall’esperimento CREA-OF descritto in `assets/14-16/Scheda_esperimento.pdf`. Il protocollo fotografico è riportato in `assets/14-16/Tutorial_setup_sperimentale.pdf`.
|
fastsam_shared.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from functools import lru_cache
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from huggingface_hub import hf_hub_download
|
| 8 |
+
|
| 9 |
+
os.environ.setdefault("YOLO_CONFIG_DIR", "/tmp/Ultralytics")
|
| 10 |
+
|
| 11 |
+
FASTSAM_REPO = "Uminosachi/FastSAM"
|
| 12 |
+
FASTSAM_FILENAME = "FastSAM-s.pt"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@lru_cache(maxsize=1)
|
| 16 |
+
def load_fastsam_cpu():
|
| 17 |
+
"""Carica una sola istanza FastSAM e la mantiene esplicitamente su CPU."""
|
| 18 |
+
from ultralytics import FastSAM
|
| 19 |
+
|
| 20 |
+
local_weight = Path(FASTSAM_FILENAME)
|
| 21 |
+
if local_weight.exists():
|
| 22 |
+
weight_path = local_weight
|
| 23 |
+
else:
|
| 24 |
+
weight_path = Path(hf_hub_download(repo_id=FASTSAM_REPO, filename=FASTSAM_FILENAME))
|
| 25 |
+
model = FastSAM(str(weight_path))
|
| 26 |
+
try:
|
| 27 |
+
model.model.to("cpu")
|
| 28 |
+
except Exception:
|
| 29 |
+
pass
|
| 30 |
+
return model
|