wlatt commited on
Commit
d5e46bc
·
verified ·
1 Parent(s): 12b5dd9

Upload 81 files

Browse files
.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
- 1f7f03347a308182ed09b0259c5ebb0ffd934fdf4e513b0c7ac73a084b695237 ./CHANGELOG_REV5_ZEROGPU_CPU.md
3
- f27222a2551e48f94d45dafd181e76e17a2f371e49101b4be4349b0af784be56 ./CHANGELOG_REV6_9-11.md
4
- 2cf251e4db544ae2b6a5ca1e0a91dd1e4975e72cfa6119b5ffd366bb1f869c27 ./ISTRUZIONI_CARICAMENTO_REV5.md
5
  cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 ./LICENSE
6
  f248798ad0c9a4bfd5e55f0b9fe41208f69791241ac1e506f2ce8b3e8ffc934a ./NOTE_RUNTIME_ZEROGPU_CPU.md
7
- a406e2c6d2afc31cd9477104be6c0e9281fc49b0c859697c41fc22e498f28ce0 ./README.md
8
- 51fbe08f35cbc5bbf8a3ef94319d1eaf48b3d009db2e02d2c6d6c92318aaf9a2 ./TEST_REPORT_REV5_ZEROGPU_CPU.md
9
- 06c71db79a8d60ba085e24b013fcb0f0813fbc5c2b294ab9eb9d1b8f5a417a63 ./TEST_REPORT_REV6_9-11.md
10
  4180f6cce89dafd0665dcfd5b3f0c7be639773dba333c6afcb81324413bbe485 ./THIRD_PARTY_NOTICES.md
11
- 773b702f9a79cbd3945bf7a1605799fac008cec4cee6a5665025ddefbad307b2 ./app.py
 
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
- 0e4efe4ac5109accb5155f92c98cf2a814a91ace7be78f3ec6e51dd0d63a2783 ./dataset/README_dataset.md
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
- 24e57909eb3ddc7f1271256abff7065e61807ed9a50669ac13438b540e0d0965 ./requirements.txt
 
 
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-14 anni
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-14 anni
18
 
19
- Lo Space mantiene un unico indirizzo e organizza tre percorsi distinti:
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, eseguito esplicitamente su CPU anche quando lo Space usa il runtime ZeroGPU.
 
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
- ![Panoramica dei moduli](assets/screenshots/01_panoramica_annotata.png)
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
- Aprire **Prova a modificare le soglie nello spazio HSV**. Gli slider consentono di cambiare:
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
- Aprire **Prova l'indice ExG**. L'indice viene calcolato per ogni pixel:
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. Aprire **Adesso prova FastSAM**.
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/comuni/Tutorial_setup_sperimentale.pdf`;
219
- - `assets/comuni/Scheda_esperimento.pdf`;
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 lindice 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
+ ![Caricamento immagine](assets/14-16/Fig1_cariacamento.JPG)
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
+ ![Panoramica delle fasi](assets/14-16/Fig2_fasi.JPG)
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
+ ![Fase 1 automatica](assets/14-16/Fig3_fase1_auto.JPG)
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
+ ![Fase 1 manuale](assets/14-16/Fig4_fase1_manuale.JPG)
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
+ ![Fase 2 segmentazione colore](assets/14-16/Fig5_fase2.JPG)
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
+ ![FastSAM: esempio di segmentazione del vaso](assets/14-16/Fig7_IA_semantica.JPG)
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
+ ![Fase 3 AI / ibrido](assets/14-16/Fig6_fase3.JPG)
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 huggingface_hub import hf_hub_download
 
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-14 anni")
864
  gr.Markdown(
865
- "Lo Space raccoglie tre moduli distinti. Il dataset sperimentale completo rimane disponibile "
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.Accordion("1. Esplora un patch 5×5", open=True):
 
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.Accordion("2. Laboratorio del colore", open=False):
 
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.Accordion("Prova a modificare le soglie nello spazio HSV", open=False):
 
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.Accordion("Prova l'indice ExG", open=False):
 
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.Accordion("Adesso prova FastSAM", open=False):
 
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

  • SHA256: f774eee13a95dce40946ebbf51feeab6178cf988eb56dca33df6f85c89736cb8
  • Pointer size: 131 Bytes
  • Size of remote file: 118 kB
assets/14-16/Fig5_fase2.JPG ADDED

Git LFS Details

  • SHA256: 2b7b05246f771ee02f5dba684021d7dfc9f9aa4968241b29e0c3a1b8218358b5
  • Pointer size: 131 Bytes
  • Size of remote file: 137 kB
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/comuni/Scheda_esperimento.pdf`. Il protocollo fotografico è riportato in `assets/comuni/Tutorial_setup_sperimentale.pdf`.
 
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