Spaces:
Sleeping
Sleeping
Update src/streamlit_app_stable.py
Browse files- src/streamlit_app_stable.py +297 -176
src/streamlit_app_stable.py
CHANGED
|
@@ -5,8 +5,9 @@ import time
|
|
| 5 |
import psutil
|
| 6 |
from transformers import AutoProcessor, Gemma3nForConditionalGeneration
|
| 7 |
from PIL import Image
|
|
|
|
| 8 |
|
| 9 |
-
# Configuration de la
|
| 10 |
st.set_page_config(
|
| 11 |
page_title="AgriLens AI - Analyse de Plantes",
|
| 12 |
page_icon="🌱",
|
|
@@ -14,7 +15,7 @@ st.set_page_config(
|
|
| 14 |
initial_sidebar_state="expanded"
|
| 15 |
)
|
| 16 |
|
| 17 |
-
# Initialisation des
|
| 18 |
if 'model_loaded' not in st.session_state:
|
| 19 |
st.session_state.model_loaded = False
|
| 20 |
if 'model' not in st.session_state:
|
|
@@ -26,13 +27,14 @@ if 'model_status' not in st.session_state:
|
|
| 26 |
if 'model_load_time' not in st.session_state:
|
| 27 |
st.session_state.model_load_time = None
|
| 28 |
if 'language' not in st.session_state:
|
| 29 |
-
st.session_state.language = "fr"
|
| 30 |
if 'load_attempt_count' not in st.session_state:
|
| 31 |
st.session_state.load_attempt_count = 0
|
| 32 |
if 'device' not in st.session_state:
|
| 33 |
st.session_state.device = "cpu"
|
| 34 |
|
| 35 |
-
# Fonctions d'
|
|
|
|
| 36 |
def check_model_health():
|
| 37 |
"""Vérifie si le modèle et le processeur sont chargés et semblent opérationnels."""
|
| 38 |
try:
|
|
@@ -49,7 +51,7 @@ def diagnose_loading_issues():
|
|
| 49 |
try:
|
| 50 |
ram = psutil.virtual_memory()
|
| 51 |
ram_gb = ram.total / (1024**3)
|
| 52 |
-
if ram_gb < 8:
|
| 53 |
issues.append(f"⚠️ RAM faible: {ram_gb:.1f}GB (recommandé: 8GB+ pour ce modèle)")
|
| 54 |
except Exception as e:
|
| 55 |
issues.append(f"⚠️ Impossible de vérifier la RAM : {e}")
|
|
@@ -65,7 +67,7 @@ def diagnose_loading_issues():
|
|
| 65 |
if torch.cuda.is_available():
|
| 66 |
try:
|
| 67 |
gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
|
| 68 |
-
if gpu_memory < 6:
|
| 69 |
issues.append(f"⚠️ GPU mémoire faible: {gpu_memory:.1f}GB (recommandé: 6GB+)")
|
| 70 |
except Exception as e:
|
| 71 |
issues.append(f"⚠️ Erreur lors de la vérification de la mémoire GPU : {e}")
|
|
@@ -89,11 +91,11 @@ def afficher_ram_disponible(context=""):
|
|
| 89 |
ram_used_gb = ram.used / (1024**3)
|
| 90 |
ram_total_gb = ram.total / (1024**3)
|
| 91 |
ram_percent = ram.percent
|
| 92 |
-
st.write(f"💾 RAM
|
| 93 |
except Exception as e:
|
| 94 |
-
st.write(f"💾 Impossible d'afficher l'utilisation de la RAM
|
| 95 |
|
| 96 |
-
# Gestion des
|
| 97 |
def t(key):
|
| 98 |
"""Fonction pour gérer les traductions."""
|
| 99 |
translations = {
|
|
@@ -110,7 +112,48 @@ def t(key):
|
|
| 110 |
"config_title": "⚙️ Configuration & Informations",
|
| 111 |
"about_title": "ℹ️ À Propos de l'Application",
|
| 112 |
"load_model": "Charger le Modèle IA",
|
| 113 |
-
"model_status": "Statut du Modèle IA"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
},
|
| 115 |
"en": {
|
| 116 |
"title": "🌱 AgriLens AI - Plant Analysis Assistant",
|
|
@@ -125,76 +168,116 @@ def t(key):
|
|
| 125 |
"config_title": "⚙️ Configuration & Information",
|
| 126 |
"about_title": "ℹ️ About the Application",
|
| 127 |
"load_model": "Load AI Model",
|
| 128 |
-
"model_status": "AI Model Status"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
}
|
| 130 |
}
|
| 131 |
return translations[st.session_state.language].get(key, key)
|
| 132 |
|
| 133 |
-
# Fonctions de chargement et d'analyse du modèle
|
| 134 |
MODEL_ID_HF = "google/gemma-3n-E4B-it" # ID du modèle sur Hugging Face Hub
|
| 135 |
|
| 136 |
def get_device_map():
|
| 137 |
"""Détermine si le modèle doit être chargé sur GPU ou CPU."""
|
| 138 |
if torch.cuda.is_available():
|
| 139 |
st.session_state.device = "cuda"
|
| 140 |
-
return "auto"
|
| 141 |
else:
|
| 142 |
st.session_state.device = "cpu"
|
| 143 |
return "cpu"
|
| 144 |
|
| 145 |
-
|
|
|
|
| 146 |
"""
|
| 147 |
Charge le modèle Gemma 3n et son processeur associé depuis Hugging Face Hub.
|
| 148 |
Gère les erreurs et les tentatives de chargement.
|
| 149 |
"""
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
return None, None
|
| 154 |
-
|
| 155 |
-
st.session_state.load_attempt_count += 1
|
| 156 |
-
st.info("🔍 Diagnostic de l'environnement avant chargement...")
|
| 157 |
-
issues = diagnose_loading_issues()
|
| 158 |
-
if issues:
|
| 159 |
-
with st.expander("📊 Diagnostic système", expanded=False):
|
| 160 |
-
for issue in issues:
|
| 161 |
-
st.write(issue)
|
| 162 |
-
|
| 163 |
-
gc.collect()
|
| 164 |
-
if torch.cuda.is_available():
|
| 165 |
-
torch.cuda.empty_cache()
|
| 166 |
-
device_map = get_device_map()
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
low_cpu_mem_usage=True,
|
| 176 |
-
device_map=device_map
|
| 177 |
-
)
|
| 178 |
-
st.success(f"✅ Modèle chargé avec succès depuis Hugging Face Hub ({MODEL_ID_HF}).")
|
| 179 |
-
st.session_state.model_status = "Chargé (Hub)"
|
| 180 |
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
st.session_state.model_load_time = time.time()
|
| 185 |
-
st.session_state.load_attempt_count = 0
|
| 186 |
|
| 187 |
-
|
| 188 |
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
-
except ImportError:
|
| 194 |
-
st.error("❌ Erreur : Les bibliothèques `transformers` ou `torch` ne sont pas installées.")
|
| 195 |
return None, None
|
| 196 |
except Exception as e:
|
| 197 |
-
st.error(f"
|
| 198 |
return None, None
|
| 199 |
|
| 200 |
def analyze_image_multilingual(image, prompt_text=""):
|
|
@@ -203,15 +286,16 @@ def analyze_image_multilingual(image, prompt_text=""):
|
|
| 203 |
Retourne le résultat de l'analyse.
|
| 204 |
"""
|
| 205 |
if not st.session_state.model_loaded or not check_model_health():
|
| 206 |
-
st.error("
|
| 207 |
return None
|
| 208 |
|
| 209 |
try:
|
| 210 |
if image.mode != 'RGB':
|
| 211 |
image = image.convert('RGB')
|
| 212 |
|
| 213 |
-
if not prompt_text:
|
| 214 |
-
|
|
|
|
| 215 |
Analyse cette image de plante et fournis un diagnostic complet :
|
| 216 |
1. **État général de la plante :** Décris son apparence globale et sa vitalité.
|
| 217 |
2. **Identification des problèmes :** Liste les maladies, parasites ou carences visibles.
|
|
@@ -220,7 +304,18 @@ Analyse cette image de plante et fournis un diagnostic complet :
|
|
| 220 |
5. **Recommandations de traitement :** Propose des solutions concrètes et adaptées.
|
| 221 |
6. **Conseils préventifs :** Donne des astuces pour éviter que le problème ne revienne.
|
| 222 |
Réponds de manière structurée et claire en français."""
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
if "<image>" not in prompt_text:
|
| 225 |
prompt_text = "<image>\n" + prompt_text
|
| 226 |
|
|
@@ -230,10 +325,12 @@ Réponds de manière structurée et claire en français."""
|
|
| 230 |
return_tensors="pt"
|
| 231 |
)
|
| 232 |
|
|
|
|
| 233 |
inputs = {key: val.to(st.session_state.model.device) for key, val in inputs.items()}
|
| 234 |
|
| 235 |
-
with st.spinner("
|
| 236 |
input_len = inputs["input_ids"].shape[-1]
|
|
|
|
| 237 |
outputs = st.session_state.model.generate(
|
| 238 |
**inputs,
|
| 239 |
max_new_tokens=512,
|
|
@@ -241,13 +338,13 @@ Réponds de manière structurée et claire en français."""
|
|
| 241 |
temperature=0.7,
|
| 242 |
top_p=0.9
|
| 243 |
)
|
| 244 |
-
generation = outputs[0][input_len:]
|
| 245 |
response = st.session_state.processor.decode(generation, skip_special_tokens=True)
|
| 246 |
|
| 247 |
return response.strip()
|
| 248 |
|
| 249 |
except Exception as e:
|
| 250 |
-
st.error(f"
|
| 251 |
return None
|
| 252 |
|
| 253 |
def analyze_text_multilingual(text_description):
|
|
@@ -256,28 +353,44 @@ def analyze_text_multilingual(text_description):
|
|
| 256 |
Retourne le diagnostic et les recommandations.
|
| 257 |
"""
|
| 258 |
if not st.session_state.model_loaded or not check_model_health():
|
| 259 |
-
st.error("
|
| 260 |
return None
|
| 261 |
|
| 262 |
try:
|
| 263 |
-
|
|
|
|
|
|
|
| 264 |
**Description des symptômes :**
|
| 265 |
-
|
| 266 |
**Instructions :**
|
| 267 |
1. **Diagnostic probable :** Quel est le problème principal ?
|
| 268 |
2. **Causes possibles :** Pourquoi ce problème survient-il ?
|
| 269 |
3. **Traitement recommandé :** Comment le résoudre ?
|
| 270 |
4. **Conseils préventifs :** Comment l'éviter à l'avenir ?
|
| 271 |
Réponds en français de manière claire et structurée."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
|
| 273 |
inputs = st.session_state.processor(
|
| 274 |
-
text=
|
| 275 |
return_tensors="pt"
|
| 276 |
)
|
| 277 |
|
|
|
|
| 278 |
inputs = {key: val.to(st.session_state.model.device) for key, val in inputs.items()}
|
| 279 |
|
| 280 |
-
with st.spinner("
|
| 281 |
outputs = st.session_state.model.generate(
|
| 282 |
**inputs,
|
| 283 |
max_new_tokens=512,
|
|
@@ -285,28 +398,29 @@ Réponds en français de manière claire et structurée."""
|
|
| 285 |
temperature=0.7,
|
| 286 |
top_p=0.9
|
| 287 |
)
|
|
|
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
response_only = response.split(prompt.strip())[-1].strip()
|
| 293 |
else:
|
| 294 |
response_only = response.strip()
|
| 295 |
|
| 296 |
return response_only
|
| 297 |
|
| 298 |
except Exception as e:
|
| 299 |
-
st.error(f"
|
| 300 |
return None
|
| 301 |
|
| 302 |
-
# Interface Utilisateur Streamlit
|
| 303 |
st.title(t("title"))
|
| 304 |
st.markdown(t("subtitle"))
|
| 305 |
|
| 306 |
-
# Barre Latérale (Sidebar) pour la Configuration
|
| 307 |
with st.sidebar:
|
| 308 |
st.header(t("config_title"))
|
| 309 |
|
|
|
|
| 310 |
lang_selector_options = ["Français", "English"]
|
| 311 |
current_lang_index = 0 if st.session_state.language == "fr" else 1
|
| 312 |
language_selected = st.selectbox(
|
|
@@ -319,47 +433,54 @@ with st.sidebar:
|
|
| 319 |
|
| 320 |
st.divider()
|
| 321 |
|
|
|
|
| 322 |
st.header(t("model_status"))
|
| 323 |
-
|
| 324 |
if st.session_state.model_loaded and check_model_health():
|
| 325 |
-
st.success("
|
| 326 |
-
st.write(f"**
|
| 327 |
if st.session_state.model_load_time:
|
| 328 |
load_time_str = time.strftime('%H:%M:%S', time.localtime(st.session_state.model_load_time))
|
| 329 |
-
st.write(f"**
|
| 330 |
|
| 331 |
-
if st.button("
|
| 332 |
st.session_state.model_loaded = False
|
| 333 |
st.session_state.model = None
|
| 334 |
st.session_state.processor = None
|
| 335 |
st.session_state.model_status = "Non chargé"
|
| 336 |
st.session_state.load_attempt_count = 0
|
| 337 |
-
st.info("
|
|
|
|
| 338 |
else:
|
| 339 |
-
st.warning("
|
| 340 |
|
| 341 |
if st.button(t("load_model"), type="primary"):
|
| 342 |
-
with st.spinner("
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
|
|
|
|
|
|
|
|
|
| 346 |
else:
|
| 347 |
-
st.error("❌ Échec du chargement du modèle IA.")
|
|
|
|
| 348 |
|
| 349 |
st.divider()
|
| 350 |
-
|
|
|
|
| 351 |
afficher_ram_disponible()
|
| 352 |
if torch.cuda.is_available():
|
| 353 |
try:
|
| 354 |
gpu_memory = torch.cuda.memory_allocated(0) / (1024**3)
|
| 355 |
gpu_total = torch.cuda.get_device_properties(0).total_memory / (1024**3)
|
| 356 |
-
st.write(f"🚀
|
|
|
|
| 357 |
except Exception as e:
|
| 358 |
-
st.write(f"🚀 Erreur lors de la récupération des informations GPU : {e}")
|
| 359 |
else:
|
| 360 |
-
st.write("🚀
|
| 361 |
|
| 362 |
-
# Onglets Principaux
|
| 363 |
tab1, tab2, tab3, tab4 = st.tabs(t("tabs"))
|
| 364 |
|
| 365 |
with tab1:
|
|
@@ -367,9 +488,8 @@ with tab1:
|
|
| 367 |
st.markdown(t("image_analysis_desc"))
|
| 368 |
|
| 369 |
capture_option = st.radio(
|
| 370 |
-
"
|
| 371 |
-
["
|
| 372 |
-
"📷 Capture par webcam" if st.session_state.language == "fr" else "📷 Webcam Capture"],
|
| 373 |
horizontal=True,
|
| 374 |
key="image_capture_method"
|
| 375 |
)
|
|
@@ -377,98 +497,102 @@ with tab1:
|
|
| 377 |
uploaded_file = None
|
| 378 |
captured_image = None
|
| 379 |
|
| 380 |
-
if capture_option == ("
|
| 381 |
uploaded_file = st.file_uploader(
|
| 382 |
t("choose_image"),
|
| 383 |
type=['png', 'jpg', 'jpeg'],
|
| 384 |
-
help="Formats acceptés : PNG, JPG, JPEG (taille max
|
|
|
|
| 385 |
)
|
| 386 |
if uploaded_file is not None and uploaded_file.size > 10 * 1024 * 1024:
|
| 387 |
-
st.warning("
|
| 388 |
-
else:
|
| 389 |
-
st.markdown("
|
| 390 |
-
st.
|
| 391 |
-
captured_image = st.camera_input("Prendre une photo de la plante")
|
| 392 |
|
| 393 |
image_to_analyze = None
|
| 394 |
if uploaded_file is not None:
|
| 395 |
try:
|
| 396 |
image_to_analyze = Image.open(uploaded_file)
|
| 397 |
except Exception as e:
|
| 398 |
-
st.error(f"
|
| 399 |
elif captured_image is not None:
|
| 400 |
try:
|
| 401 |
image_to_analyze = Image.open(captured_image)
|
| 402 |
except Exception as e:
|
| 403 |
-
st.error(f"
|
| 404 |
|
| 405 |
if image_to_analyze is not None:
|
| 406 |
-
original_size = image_to_analyze.size
|
| 407 |
resized_image, was_resized = resize_image_if_needed(image_to_analyze)
|
| 408 |
|
| 409 |
col1, col2 = st.columns([1, 1])
|
| 410 |
with col1:
|
| 411 |
-
st.image(resized_image, caption="
|
| 412 |
if was_resized:
|
| 413 |
-
st.info(
|
| 414 |
|
| 415 |
with col2:
|
| 416 |
if st.session_state.model_loaded and check_model_health():
|
| 417 |
-
st.subheader("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
analysis_type = st.selectbox(
|
| 419 |
-
"
|
| 420 |
-
|
| 421 |
-
"Diagnostic complet (maladie, soins, prévention)" if st.session_state.language == "fr" else "Full Diagnosis (disease, care, prevention)",
|
| 422 |
-
"Identification et diagnostic de maladies" if st.session_state.language == "fr" else "Disease Identification and Diagnosis",
|
| 423 |
-
"Conseils de soins et d'entretien" if st.session_state.language == "fr" else "Care and Maintenance Advice"
|
| 424 |
-
],
|
| 425 |
key="image_analysis_type_selector"
|
| 426 |
)
|
| 427 |
|
| 428 |
custom_prompt_input = st.text_area(
|
| 429 |
-
"
|
| 430 |
value="",
|
| 431 |
height=100,
|
| 432 |
-
placeholder="
|
| 433 |
)
|
| 434 |
|
| 435 |
-
if st.button("
|
| 436 |
final_prompt = custom_prompt_input.strip()
|
|
|
|
| 437 |
if not final_prompt:
|
| 438 |
-
if analysis_type
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
|
|
|
| 461 |
|
| 462 |
analysis_result = analyze_image_multilingual(resized_image, prompt_text=final_prompt)
|
| 463 |
|
| 464 |
if analysis_result:
|
| 465 |
-
st.success("
|
| 466 |
-
st.markdown("###
|
| 467 |
st.markdown(analysis_result)
|
| 468 |
else:
|
| 469 |
-
st.error("
|
| 470 |
else:
|
| 471 |
-
st.warning("
|
| 472 |
|
| 473 |
with tab2:
|
| 474 |
st.header(t("text_analysis_title"))
|
|
@@ -477,24 +601,24 @@ with tab2:
|
|
| 477 |
text_description_input = st.text_area(
|
| 478 |
t("enter_description"),
|
| 479 |
height=200,
|
| 480 |
-
placeholder="
|
| 481 |
)
|
| 482 |
|
| 483 |
-
if st.button("
|
| 484 |
if text_description_input.strip():
|
| 485 |
if st.session_state.model_loaded and check_model_health():
|
| 486 |
analysis_result = analyze_text_multilingual(text_description_input)
|
| 487 |
|
| 488 |
if analysis_result:
|
| 489 |
-
st.success("
|
| 490 |
-
st.markdown("###
|
| 491 |
st.markdown(analysis_result)
|
| 492 |
else:
|
| 493 |
-
st.error("
|
| 494 |
else:
|
| 495 |
-
st.warning("
|
| 496 |
else:
|
| 497 |
-
st.warning("
|
| 498 |
|
| 499 |
with tab3:
|
| 500 |
st.header(t("config_title"))
|
|
@@ -502,45 +626,42 @@ with tab3:
|
|
| 502 |
col1, col2 = st.columns(2)
|
| 503 |
|
| 504 |
with col1:
|
| 505 |
-
st.subheader("
|
| 506 |
-
|
| 507 |
try:
|
| 508 |
ram = psutil.virtual_memory()
|
| 509 |
-
st.write(f"**
|
| 510 |
-
st.write(f"**
|
| 511 |
|
| 512 |
disk = psutil.disk_usage('/')
|
| 513 |
-
st.write(f"**
|
| 514 |
|
| 515 |
if torch.cuda.is_available():
|
| 516 |
-
st.write(f"**
|
| 517 |
gpu_props = torch.cuda.get_device_properties(0)
|
| 518 |
-
st.write(f"**
|
| 519 |
else:
|
| 520 |
-
st.write("**
|
| 521 |
except Exception as e:
|
| 522 |
-
st.error(f"
|
| 523 |
|
| 524 |
with col2:
|
| 525 |
-
st.subheader("
|
| 526 |
-
|
| 527 |
if st.session_state.model_loaded and check_model_health():
|
| 528 |
-
st.write("**
|
| 529 |
if st.session_state.model is not None:
|
| 530 |
-
st.write(f"**
|
| 531 |
if hasattr(st.session_state.model, 'device'):
|
| 532 |
-
st.write(f"**
|
| 533 |
if st.session_state.model_load_time:
|
| 534 |
load_time_str = time.strftime('%H:%M:%S', time.localtime(st.session_state.model_load_time))
|
| 535 |
-
st.write(f"**
|
| 536 |
else:
|
| 537 |
-
st.write("**
|
| 538 |
-
st.write("**
|
| 539 |
-
st.write("**
|
| 540 |
|
| 541 |
with tab4:
|
| 542 |
st.header(t("about_title"))
|
| 543 |
-
|
| 544 |
st.markdown("""
|
| 545 |
## 🌱 AgriLens AI : Votre Assistant d'Analyse de Plantes
|
| 546 |
**AgriLens AI** est une application alimentée par l'intelligence artificielle qui vous aide à identifier les problèmes de vos plantes, à diagnostiquer les maladies et à obtenir des conseils de traitement personnalisés. Que vous soyez un jardinier débutant ou expérimenté, AgriLens AI est là pour vous accompagner.
|
|
@@ -550,10 +671,10 @@ with tab4:
|
|
| 550 |
- **Recommandations Précises :** Recevez des conseils de traitement, des suggestions de soins et des mesures préventives adaptées à chaque situation.
|
| 551 |
- **Support Multilingue :** L'interface et les réponses sont disponibles en français et en anglais pour une accessibilité maximale.
|
| 552 |
### 🤖 Technologie Utilisée :
|
| 553 |
-
- **Modèle IA :** Google Gemma 3n E4B IT, un modèle multimodal performant pour l'analyse de plantes.
|
| 554 |
- **Bibliothèques :** `transformers`, `torch`, `streamlit`, `Pillow`, `psutil`, `requests`, `huggingface-hub`.
|
| 555 |
### 📝 Comment Utiliser AgriLens AI :
|
| 556 |
-
1. **Chargez le Modèle IA :** Dans la barre latérale, cliquez sur "Charger le Modèle IA". Attendez que le statut passe à "Chargé et fonctionnel".
|
| 557 |
2. **Analysez votre Plante :**
|
| 558 |
- **Via Image :** Allez à l'onglet "📸 Analyse d'Image". Uploadez une photo de votre plante ou utilisez votre webcam. Choisissez le type d'analyse et cliquez sur "Analyser l'image".
|
| 559 |
- **Via Texte :** Allez à l'onglet "📝 Analyse de Texte". Décrivez les symptômes de votre plante dans la zone de texte et cliquez sur "Analyser la description".
|
|
@@ -565,13 +686,13 @@ with tab4:
|
|
| 565 |
*Développé avec passion pour aider les jardiniers et amoureux des plantes à prendre soin de leurs cultures grâce à la puissance de l'IA.*
|
| 566 |
""")
|
| 567 |
|
| 568 |
-
# Pied de page
|
| 569 |
st.divider()
|
| 570 |
st.markdown("""
|
| 571 |
<div style='text-align: center; color: #666;'>
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
</div>
|
| 577 |
""", unsafe_allow_html=True)
|
|
|
|
| 5 |
import psutil
|
| 6 |
from transformers import AutoProcessor, Gemma3nForConditionalGeneration
|
| 7 |
from PIL import Image
|
| 8 |
+
import os # Importé pour l'exemple d'utilisation de `max_upload_size`
|
| 9 |
|
| 10 |
+
# --- Configuration de la Page ---
|
| 11 |
st.set_page_config(
|
| 12 |
page_title="AgriLens AI - Analyse de Plantes",
|
| 13 |
page_icon="🌱",
|
|
|
|
| 15 |
initial_sidebar_state="expanded"
|
| 16 |
)
|
| 17 |
|
| 18 |
+
# --- Initialisation des Variables de Session ---
|
| 19 |
if 'model_loaded' not in st.session_state:
|
| 20 |
st.session_state.model_loaded = False
|
| 21 |
if 'model' not in st.session_state:
|
|
|
|
| 27 |
if 'model_load_time' not in st.session_state:
|
| 28 |
st.session_state.model_load_time = None
|
| 29 |
if 'language' not in st.session_state:
|
| 30 |
+
st.session_state.language = "fr" # Langue par défaut
|
| 31 |
if 'load_attempt_count' not in st.session_state:
|
| 32 |
st.session_state.load_attempt_count = 0
|
| 33 |
if 'device' not in st.session_state:
|
| 34 |
st.session_state.device = "cpu"
|
| 35 |
|
| 36 |
+
# --- Fonctions d'Aide Système ---
|
| 37 |
+
|
| 38 |
def check_model_health():
|
| 39 |
"""Vérifie si le modèle et le processeur sont chargés et semblent opérationnels."""
|
| 40 |
try:
|
|
|
|
| 51 |
try:
|
| 52 |
ram = psutil.virtual_memory()
|
| 53 |
ram_gb = ram.total / (1024**3)
|
| 54 |
+
if ram_gb < 8: # Recommandation générale, le modèle peut nécessiter plus
|
| 55 |
issues.append(f"⚠️ RAM faible: {ram_gb:.1f}GB (recommandé: 8GB+ pour ce modèle)")
|
| 56 |
except Exception as e:
|
| 57 |
issues.append(f"⚠️ Impossible de vérifier la RAM : {e}")
|
|
|
|
| 67 |
if torch.cuda.is_available():
|
| 68 |
try:
|
| 69 |
gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
|
| 70 |
+
if gpu_memory < 6: # Recommandation générale, le modèle peut nécessiter plus
|
| 71 |
issues.append(f"⚠️ GPU mémoire faible: {gpu_memory:.1f}GB (recommandé: 6GB+)")
|
| 72 |
except Exception as e:
|
| 73 |
issues.append(f"⚠️ Erreur lors de la vérification de la mémoire GPU : {e}")
|
|
|
|
| 91 |
ram_used_gb = ram.used / (1024**3)
|
| 92 |
ram_total_gb = ram.total / (1024**3)
|
| 93 |
ram_percent = ram.percent
|
| 94 |
+
st.write(f"💾 RAM : {ram_used_gb:.1f}GB / {ram_total_gb:.1f}GB ({ram_percent:.1f}%)")
|
| 95 |
except Exception as e:
|
| 96 |
+
st.write(f"💾 Impossible d'afficher l'utilisation de la RAM : {e}")
|
| 97 |
|
| 98 |
+
# --- Gestion des Traductions ---
|
| 99 |
def t(key):
|
| 100 |
"""Fonction pour gérer les traductions."""
|
| 101 |
translations = {
|
|
|
|
| 112 |
"config_title": "⚙️ Configuration & Informations",
|
| 113 |
"about_title": "ℹ️ À Propos de l'Application",
|
| 114 |
"load_model": "Charger le Modèle IA",
|
| 115 |
+
"model_status": "Statut du Modèle IA",
|
| 116 |
+
"upload_image_label": "📁 Upload d'image",
|
| 117 |
+
"webcam_capture_label": "📷 Capture par webcam",
|
| 118 |
+
"image_analysis_options_title": "Options d'analyse",
|
| 119 |
+
"analysis_type_full_diagnosis": "Diagnostic complet (maladie, soins, prévention)",
|
| 120 |
+
"analysis_type_disease_id": "Identification et diagnostic de maladies",
|
| 121 |
+
"analysis_type_care_advice": "Conseils de soins et d'entretien",
|
| 122 |
+
"custom_prompt_label": "Prompt personnalisé (optionnel) :",
|
| 123 |
+
"analyze_image_button": "🔍 Analyser l'image",
|
| 124 |
+
"analyze_text_button": "🔍 Analyser la description",
|
| 125 |
+
"model_not_loaded_warning": "⚠️ Modèle IA non chargé. Veuillez d'abord charger le modèle depuis la barre latérale.",
|
| 126 |
+
"system_info_title": "🔧 Informations Système",
|
| 127 |
+
"model_stats_title": "📊 Statistiques du Modèle IA",
|
| 128 |
+
"model_status_loaded": "✅ Modèle chargé et fonctionnel",
|
| 129 |
+
"model_status_not_loaded": "❌ Modèle IA non chargé",
|
| 130 |
+
"model_type_label": "Type de modèle :",
|
| 131 |
+
"device_used_label": "Device utilisé :",
|
| 132 |
+
"load_time_label": "Heure de chargement :",
|
| 133 |
+
"reload_model_button": "🔄 Recharger le modèle",
|
| 134 |
+
"model_unloaded_info": "Modèle déchargé. Cliquez sur 'Charger le modèle IA' pour le recharger.",
|
| 135 |
+
"loading_model_spinner": "🔄 Chargement du modèle IA en cours...",
|
| 136 |
+
"analysis_in_progress_image": "🔍 Analyse d'image en cours...",
|
| 137 |
+
"analysis_in_progress_text": "🔍 Analyse textuelle en cours...",
|
| 138 |
+
"analysis_complete_success": "✅ Analyse terminée !",
|
| 139 |
+
"analysis_failed_error": "❌ Échec de l'analyse de l'image.",
|
| 140 |
+
"text_analysis_failed_error": "❌ Échec de l'analyse textuelle.",
|
| 141 |
+
"results_title": "📋 Résultats de l'analyse",
|
| 142 |
+
"sys_ram_total": "RAM Totale :",
|
| 143 |
+
"sys_ram_used": "RAM Utilisée :",
|
| 144 |
+
"sys_disk_free": "Espace Disque Libre (/) :",
|
| 145 |
+
"sys_gpu_detected": "GPU Détecté :",
|
| 146 |
+
"sys_gpu_total_mem": "Mémoire GPU Totale :",
|
| 147 |
+
"sys_gpu_not_available": "GPU : Non disponible (fonctionnement sur CPU)",
|
| 148 |
+
"sys_info_error": "Erreur lors de la récupération des informations système :",
|
| 149 |
+
"model_loading_error": "❌ Trop de tentatives de chargement ont échoué. Veuillez vérifier votre configuration et redémarrer l'application.",
|
| 150 |
+
"model_loading_hub_error": "❌ Échec du chargement du modèle depuis Hugging Face Hub : ",
|
| 151 |
+
"image_processing_error": "❌ Erreur lors du traitement du fichier uploadé : ",
|
| 152 |
+
"image_capture_processing_error": "❌ Erreur lors du traitement de l'image capturée : ",
|
| 153 |
+
"image_resized_info": "ℹ️ Image redimensionnée de {} à {} pour l'analyse.",
|
| 154 |
+
"choose_capture_method_label": "Choisissez votre méthode de capture :",
|
| 155 |
+
"webcam_instructions": "Positionnez votre plante malade devant la webcam et cliquez sur 'Prendre une photo'.",
|
| 156 |
+
"file_size_warning": "Le fichier est très volumineux. Il est recommandé d'utiliser des images de taille raisonnable pour une analyse plus rapide."
|
| 157 |
},
|
| 158 |
"en": {
|
| 159 |
"title": "🌱 AgriLens AI - Plant Analysis Assistant",
|
|
|
|
| 168 |
"config_title": "⚙️ Configuration & Information",
|
| 169 |
"about_title": "ℹ️ About the Application",
|
| 170 |
"load_model": "Load AI Model",
|
| 171 |
+
"model_status": "AI Model Status",
|
| 172 |
+
"upload_image_label": "📁 Upload Image",
|
| 173 |
+
"webcam_capture_label": "📷 Webcam Capture",
|
| 174 |
+
"image_analysis_options_title": "Analysis Options",
|
| 175 |
+
"analysis_type_full_diagnosis": "Full Diagnosis (disease, care, prevention)",
|
| 176 |
+
"analysis_type_disease_id": "Disease Identification and Diagnosis",
|
| 177 |
+
"analysis_type_care_advice": "Care and Maintenance Advice",
|
| 178 |
+
"custom_prompt_label": "Custom prompt (optional) :",
|
| 179 |
+
"analyze_image_button": "🔍 Analyze Image",
|
| 180 |
+
"analyze_text_button": "🔍 Analyze Description",
|
| 181 |
+
"model_not_loaded_warning": "⚠️ AI Model not loaded. Please load the model from the sidebar first.",
|
| 182 |
+
"system_info_title": "🔧 System Information",
|
| 183 |
+
"model_stats_title": "📊 AI Model Statistics",
|
| 184 |
+
"model_status_loaded": "✅ Model Loaded and Functional",
|
| 185 |
+
"model_status_not_loaded": "❌ AI Model Not Loaded",
|
| 186 |
+
"model_type_label": "Model Type:",
|
| 187 |
+
"device_used_label": "Device Used:",
|
| 188 |
+
"load_time_label": "Load Time:",
|
| 189 |
+
"reload_model_button": "🔄 Reload Model",
|
| 190 |
+
"model_unloaded_info": "Model unloaded. Click 'Load AI Model' to reload it.",
|
| 191 |
+
"loading_model_spinner": "🔄 Loading AI Model...",
|
| 192 |
+
"analysis_in_progress_image": "🔍 Analyzing image...",
|
| 193 |
+
"analysis_in_progress_text": "🔍 Analyzing text...",
|
| 194 |
+
"analysis_complete_success": "✅ Analysis complete!",
|
| 195 |
+
"analysis_failed_error": "❌ Image analysis failed.",
|
| 196 |
+
"text_analysis_failed_error": "❌ Text analysis failed.",
|
| 197 |
+
"results_title": "📋 Analysis Results",
|
| 198 |
+
"sys_ram_total": "Total RAM:",
|
| 199 |
+
"sys_ram_used": "RAM Used:",
|
| 200 |
+
"sys_disk_free": "Free Disk Space (/):",
|
| 201 |
+
"sys_gpu_detected": "GPU Detected:",
|
| 202 |
+
"sys_gpu_total_mem": "Total GPU Memory:",
|
| 203 |
+
"sys_gpu_not_available": "GPU: Not Available (CPU operation)",
|
| 204 |
+
"sys_info_error": "Error retrieving system information:",
|
| 205 |
+
"model_loading_error": "❌ Too many failed loading attempts. Please check your configuration and restart the application.",
|
| 206 |
+
"model_loading_hub_error": "❌ Failed to load model from Hugging Face Hub: ",
|
| 207 |
+
"image_processing_error": "❌ Error processing uploaded file: ",
|
| 208 |
+
"image_capture_processing_error": "❌ Error processing captured image: ",
|
| 209 |
+
"image_resized_info": "ℹ️ Image resized from {} to {} for analysis.",
|
| 210 |
+
"choose_capture_method_label": "Choose your capture method:",
|
| 211 |
+
"webcam_instructions": "Position your sick plant in front of the webcam and click 'Take Photo'.",
|
| 212 |
+
"file_size_warning": "The file is very large. It's recommended to use reasonably sized images for faster analysis."
|
| 213 |
}
|
| 214 |
}
|
| 215 |
return translations[st.session_state.language].get(key, key)
|
| 216 |
|
| 217 |
+
# --- Fonctions de chargement et d'analyse du modèle ---
|
| 218 |
MODEL_ID_HF = "google/gemma-3n-E4B-it" # ID du modèle sur Hugging Face Hub
|
| 219 |
|
| 220 |
def get_device_map():
|
| 221 |
"""Détermine si le modèle doit être chargé sur GPU ou CPU."""
|
| 222 |
if torch.cuda.is_available():
|
| 223 |
st.session_state.device = "cuda"
|
| 224 |
+
return "auto" # Laisse transformers décider de la meilleure répartition
|
| 225 |
else:
|
| 226 |
st.session_state.device = "cpu"
|
| 227 |
return "cpu"
|
| 228 |
|
| 229 |
+
@st.cache_resource # Utilisation du cache Streamlit pour le modèle
|
| 230 |
+
def load_model_and_processor():
|
| 231 |
"""
|
| 232 |
Charge le modèle Gemma 3n et son processeur associé depuis Hugging Face Hub.
|
| 233 |
Gère les erreurs et les tentatives de chargement.
|
| 234 |
"""
|
| 235 |
+
if st.session_state.load_attempt_count >= 3:
|
| 236 |
+
st.error(t("model_loading_error"))
|
| 237 |
+
return None, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
+
st.session_state.load_attempt_count += 1
|
| 240 |
+
st.info("🔍 Diagnostic de l'environnement avant chargement...")
|
| 241 |
+
issues = diagnose_loading_issues()
|
| 242 |
+
if issues:
|
| 243 |
+
with st.expander("📊 Diagnostic système", expanded=False):
|
| 244 |
+
for issue in issues:
|
| 245 |
+
st.write(issue)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
+
gc.collect() # Nettoyage de la mémoire
|
| 248 |
+
if torch.cuda.is_available():
|
| 249 |
+
torch.cuda.empty_cache() # Vide le cache GPU
|
|
|
|
|
|
|
| 250 |
|
| 251 |
+
device_map = get_device_map()
|
| 252 |
|
| 253 |
+
try:
|
| 254 |
+
st.info(f"Chargement du modèle depuis Hugging Face Hub : `{MODEL_ID_HF}`...")
|
| 255 |
+
|
| 256 |
+
# Configuration du torch_dtype pour une meilleure efficacité sur GPU
|
| 257 |
+
torch_dtype = torch.bfloat16 if st.session_state.device == "cuda" else torch.float32
|
| 258 |
+
|
| 259 |
+
processor = AutoProcessor.from_pretrained(MODEL_ID_HF, trust_remote_code=True)
|
| 260 |
+
model = Gemma3nForConditionalGeneration.from_pretrained(
|
| 261 |
+
MODEL_ID_HF,
|
| 262 |
+
torch_dtype=torch_dtype,
|
| 263 |
+
trust_remote_code=True,
|
| 264 |
+
low_cpu_mem_usage=True, # Tente de réduire l'utilisation CPU pendant le chargement
|
| 265 |
+
device_map=device_map
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
st.success(f"✅ Modèle `{MODEL_ID_HF}` chargé avec succès depuis Hugging Face Hub.")
|
| 269 |
+
st.session_state.model_status = "Chargé (Hub)"
|
| 270 |
+
st.session_state.model_loaded = True
|
| 271 |
+
st.session_state.model_load_time = time.time()
|
| 272 |
+
st.session_state.load_attempt_count = 0 # Réinitialise le compteur si succès
|
| 273 |
+
|
| 274 |
+
return model, processor
|
| 275 |
|
| 276 |
+
except ImportError as e:
|
| 277 |
+
st.error(f"❌ Erreur : Les bibliothèques `transformers` ou `torch` ne sont pas installées. Détails: {e}")
|
| 278 |
return None, None
|
| 279 |
except Exception as e:
|
| 280 |
+
st.error(f"{t('model_loading_hub_error')} {e}")
|
| 281 |
return None, None
|
| 282 |
|
| 283 |
def analyze_image_multilingual(image, prompt_text=""):
|
|
|
|
| 286 |
Retourne le résultat de l'analyse.
|
| 287 |
"""
|
| 288 |
if not st.session_state.model_loaded or not check_model_health():
|
| 289 |
+
st.error(t("model_not_loaded_warning"))
|
| 290 |
return None
|
| 291 |
|
| 292 |
try:
|
| 293 |
if image.mode != 'RGB':
|
| 294 |
image = image.convert('RGB')
|
| 295 |
|
| 296 |
+
if not prompt_text: # Utilisation des prompts par défaut basés sur la langue
|
| 297 |
+
if st.session_state.language == "fr":
|
| 298 |
+
prompt_text = """<image>
|
| 299 |
Analyse cette image de plante et fournis un diagnostic complet :
|
| 300 |
1. **État général de la plante :** Décris son apparence globale et sa vitalité.
|
| 301 |
2. **Identification des problèmes :** Liste les maladies, parasites ou carences visibles.
|
|
|
|
| 304 |
5. **Recommandations de traitement :** Propose des solutions concrètes et adaptées.
|
| 305 |
6. **Conseils préventifs :** Donne des astuces pour éviter que le problème ne revienne.
|
| 306 |
Réponds de manière structurée et claire en français."""
|
| 307 |
+
else: # English
|
| 308 |
+
prompt_text = """<image>
|
| 309 |
+
Analyze this plant image and provide a comprehensive diagnosis:
|
| 310 |
+
1. **Overall Plant Health:** Describe its general appearance and vitality.
|
| 311 |
+
2. **Problem Identification:** List visible diseases, pests, or deficiencies.
|
| 312 |
+
3. **Probable Diagnosis:** Indicate the most likely disease or problem.
|
| 313 |
+
4. **Possible Causes:** Explain what might have caused this issue.
|
| 314 |
+
5. **Treatment Recommendations:** Propose concrete and appropriate solutions.
|
| 315 |
+
6. **Preventive Advice:** Give tips to avoid the problem from recurring.
|
| 316 |
+
Respond in a structured and clear manner in English."""
|
| 317 |
+
|
| 318 |
+
# Assure que le prompt commence par <image> si ce n'est pas déjà le cas
|
| 319 |
if "<image>" not in prompt_text:
|
| 320 |
prompt_text = "<image>\n" + prompt_text
|
| 321 |
|
|
|
|
| 325 |
return_tensors="pt"
|
| 326 |
)
|
| 327 |
|
| 328 |
+
# Déplace les tenseurs sur le bon device
|
| 329 |
inputs = {key: val.to(st.session_state.model.device) for key, val in inputs.items()}
|
| 330 |
|
| 331 |
+
with st.spinner(t("analysis_in_progress_image")):
|
| 332 |
input_len = inputs["input_ids"].shape[-1]
|
| 333 |
+
# Paramètres de génération ajustables si nécessaire
|
| 334 |
outputs = st.session_state.model.generate(
|
| 335 |
**inputs,
|
| 336 |
max_new_tokens=512,
|
|
|
|
| 338 |
temperature=0.7,
|
| 339 |
top_p=0.9
|
| 340 |
)
|
| 341 |
+
generation = outputs[0][input_len:] # Garde uniquement les tokens générés
|
| 342 |
response = st.session_state.processor.decode(generation, skip_special_tokens=True)
|
| 343 |
|
| 344 |
return response.strip()
|
| 345 |
|
| 346 |
except Exception as e:
|
| 347 |
+
st.error(f"{t('analysis_failed_error')} {e}")
|
| 348 |
return None
|
| 349 |
|
| 350 |
def analyze_text_multilingual(text_description):
|
|
|
|
| 353 |
Retourne le diagnostic et les recommandations.
|
| 354 |
"""
|
| 355 |
if not st.session_state.model_loaded or not check_model_health():
|
| 356 |
+
st.error(t("model_not_loaded_warning"))
|
| 357 |
return None
|
| 358 |
|
| 359 |
try:
|
| 360 |
+
# Définition du prompt en fonction de la langue sélectionnée
|
| 361 |
+
if st.session_state.language == "fr":
|
| 362 |
+
prompt = f"""Analyse la description des symptômes de cette plante et fournis un diagnostic détaillé :
|
| 363 |
**Description des symptômes :**
|
| 364 |
+
|
| 365 |
**Instructions :**
|
| 366 |
1. **Diagnostic probable :** Quel est le problème principal ?
|
| 367 |
2. **Causes possibles :** Pourquoi ce problème survient-il ?
|
| 368 |
3. **Traitement recommandé :** Comment le résoudre ?
|
| 369 |
4. **Conseils préventifs :** Comment l'éviter à l'avenir ?
|
| 370 |
Réponds en français de manière claire et structurée."""
|
| 371 |
+
else: # English
|
| 372 |
+
prompt = f"""Analyze the description of this plant's symptoms and provide a detailed diagnosis:
|
| 373 |
+
**Symptoms Description:**
|
| 374 |
+
|
| 375 |
+
**Instructions:**
|
| 376 |
+
1. **Probable Diagnosis:** What is the main issue?
|
| 377 |
+
2. **Possible Causes:** Why is this problem occurring?
|
| 378 |
+
3. **Recommended Treatment:** How to resolve it?
|
| 379 |
+
4. **Preventive Advice:** How to avoid it in the future?
|
| 380 |
+
Respond in English in a clear and structured manner."""
|
| 381 |
+
|
| 382 |
+
# Ajoute la description de l'utilisateur au prompt
|
| 383 |
+
full_prompt = prompt + "\n\n" + text_description
|
| 384 |
|
| 385 |
inputs = st.session_state.processor(
|
| 386 |
+
text=full_prompt,
|
| 387 |
return_tensors="pt"
|
| 388 |
)
|
| 389 |
|
| 390 |
+
# Déplace les tenseurs sur le bon device
|
| 391 |
inputs = {key: val.to(st.session_state.model.device) for key, val in inputs.items()}
|
| 392 |
|
| 393 |
+
with st.spinner(t("analysis_in_progress_text")):
|
| 394 |
outputs = st.session_state.model.generate(
|
| 395 |
**inputs,
|
| 396 |
max_new_tokens=512,
|
|
|
|
| 398 |
temperature=0.7,
|
| 399 |
top_p=0.9
|
| 400 |
)
|
| 401 |
+
response = st.session_state.processor.decode(outputs[0], skip_special_tokens=True)
|
| 402 |
|
| 403 |
+
# Nettoie le prompt du début de la réponse si le modèle l'inclut
|
| 404 |
+
if full_prompt.strip() in response:
|
| 405 |
+
response_only = response.split(full_prompt.strip())[-1].strip()
|
|
|
|
| 406 |
else:
|
| 407 |
response_only = response.strip()
|
| 408 |
|
| 409 |
return response_only
|
| 410 |
|
| 411 |
except Exception as e:
|
| 412 |
+
st.error(f"{t('text_analysis_failed_error')} {e}")
|
| 413 |
return None
|
| 414 |
|
| 415 |
+
# --- Interface Utilisateur Streamlit ---
|
| 416 |
st.title(t("title"))
|
| 417 |
st.markdown(t("subtitle"))
|
| 418 |
|
| 419 |
+
# --- Barre Latérale (Sidebar) pour la Configuration ---
|
| 420 |
with st.sidebar:
|
| 421 |
st.header(t("config_title"))
|
| 422 |
|
| 423 |
+
# Sélection de la langue
|
| 424 |
lang_selector_options = ["Français", "English"]
|
| 425 |
current_lang_index = 0 if st.session_state.language == "fr" else 1
|
| 426 |
language_selected = st.selectbox(
|
|
|
|
| 433 |
|
| 434 |
st.divider()
|
| 435 |
|
| 436 |
+
# Statut du Modèle IA et Bouton de chargement
|
| 437 |
st.header(t("model_status"))
|
|
|
|
| 438 |
if st.session_state.model_loaded and check_model_health():
|
| 439 |
+
st.success(t("model_status_loaded"))
|
| 440 |
+
st.write(f"**{t('model_type_label')}** `{st.session_state.model_status}`")
|
| 441 |
if st.session_state.model_load_time:
|
| 442 |
load_time_str = time.strftime('%H:%M:%S', time.localtime(st.session_state.model_load_time))
|
| 443 |
+
st.write(f"**{t('load_time_label')}** `{load_time_str}`")
|
| 444 |
|
| 445 |
+
if st.button(t("reload_model_button"), type="secondary"):
|
| 446 |
st.session_state.model_loaded = False
|
| 447 |
st.session_state.model = None
|
| 448 |
st.session_state.processor = None
|
| 449 |
st.session_state.model_status = "Non chargé"
|
| 450 |
st.session_state.load_attempt_count = 0
|
| 451 |
+
st.info(t("model_unloaded_info"))
|
| 452 |
+
st.rerun() # Recharge l'application pour appliquer le changement
|
| 453 |
else:
|
| 454 |
+
st.warning(t("model_status_not_loaded"))
|
| 455 |
|
| 456 |
if st.button(t("load_model"), type="primary"):
|
| 457 |
+
with st.spinner(t("loading_model_spinner")):
|
| 458 |
+
# Utilise la fonction cachée pour charger le modèle
|
| 459 |
+
model, processor = load_model_and_processor()
|
| 460 |
+
if model is not None and processor is not None:
|
| 461 |
+
st.session_state.model = model
|
| 462 |
+
st.session_state.processor = processor
|
| 463 |
+
st.success(t("✅ Modèle IA chargé avec succès !")) # Message plus clair
|
| 464 |
else:
|
| 465 |
+
st.error(t("❌ Échec du chargement du modèle IA."))
|
| 466 |
+
st.rerun() # Recharge pour mettre à jour l'UI
|
| 467 |
|
| 468 |
st.divider()
|
| 469 |
+
# Affichage des ressources système
|
| 470 |
+
st.subheader(t("system_info_title"))
|
| 471 |
afficher_ram_disponible()
|
| 472 |
if torch.cuda.is_available():
|
| 473 |
try:
|
| 474 |
gpu_memory = torch.cuda.memory_allocated(0) / (1024**3)
|
| 475 |
gpu_total = torch.cuda.get_device_properties(0).total_memory / (1024**3)
|
| 476 |
+
st.write(f"🚀 {t('sys_gpu_detected')}: {torch.cuda.get_device_name(0)}")
|
| 477 |
+
st.write(f"📊 {t('sys_gpu_total_mem')}: {gpu_total:.1f} GB")
|
| 478 |
except Exception as e:
|
| 479 |
+
st.write(f"🚀 {t('sys_gpu_detected')}: Erreur lors de la récupération des informations GPU : {e}")
|
| 480 |
else:
|
| 481 |
+
st.write(f"🚀 {t('sys_gpu_not_available')}")
|
| 482 |
|
| 483 |
+
# --- Onglets Principaux ---
|
| 484 |
tab1, tab2, tab3, tab4 = st.tabs(t("tabs"))
|
| 485 |
|
| 486 |
with tab1:
|
|
|
|
| 488 |
st.markdown(t("image_analysis_desc"))
|
| 489 |
|
| 490 |
capture_option = st.radio(
|
| 491 |
+
t("choose_capture_method_label"),
|
| 492 |
+
[t("upload_image_label"), t("webcam_capture_label")],
|
|
|
|
| 493 |
horizontal=True,
|
| 494 |
key="image_capture_method"
|
| 495 |
)
|
|
|
|
| 497 |
uploaded_file = None
|
| 498 |
captured_image = None
|
| 499 |
|
| 500 |
+
if capture_option == t("upload_image_label"):
|
| 501 |
uploaded_file = st.file_uploader(
|
| 502 |
t("choose_image"),
|
| 503 |
type=['png', 'jpg', 'jpeg'],
|
| 504 |
+
help="Formats acceptés : PNG, JPG, JPEG (taille max : 10MB).",
|
| 505 |
+
max_upload_size=10 # Limite à 10MB
|
| 506 |
)
|
| 507 |
if uploaded_file is not None and uploaded_file.size > 10 * 1024 * 1024:
|
| 508 |
+
st.warning(t("file_size_warning"))
|
| 509 |
+
else: # Webcam Capture
|
| 510 |
+
st.markdown(t("webcam_instructions"))
|
| 511 |
+
captured_image = st.camera_input(t("take_photo_label")) # Ajout d'une étiquette pour la caméra
|
|
|
|
| 512 |
|
| 513 |
image_to_analyze = None
|
| 514 |
if uploaded_file is not None:
|
| 515 |
try:
|
| 516 |
image_to_analyze = Image.open(uploaded_file)
|
| 517 |
except Exception as e:
|
| 518 |
+
st.error(f"{t('image_processing_error')} {e}")
|
| 519 |
elif captured_image is not None:
|
| 520 |
try:
|
| 521 |
image_to_analyze = Image.open(captured_image)
|
| 522 |
except Exception as e:
|
| 523 |
+
st.error(f"{t('image_capture_processing_error')} {e}")
|
| 524 |
|
| 525 |
if image_to_analyze is not None:
|
|
|
|
| 526 |
resized_image, was_resized = resize_image_if_needed(image_to_analyze)
|
| 527 |
|
| 528 |
col1, col2 = st.columns([1, 1])
|
| 529 |
with col1:
|
| 530 |
+
st.image(resized_image, caption=t("image_to_analyze_caption"), use_container_width=True) # Caption pour l'image
|
| 531 |
if was_resized:
|
| 532 |
+
st.info(t("image_resized_info").format(resized_image.size)) # Utilise la clé pour le message
|
| 533 |
|
| 534 |
with col2:
|
| 535 |
if st.session_state.model_loaded and check_model_health():
|
| 536 |
+
st.subheader(t("image_analysis_options_title"))
|
| 537 |
+
|
| 538 |
+
# Définition des options d'analyse basée sur la langue
|
| 539 |
+
analysis_options = [
|
| 540 |
+
t("analysis_type_full_diagnosis"),
|
| 541 |
+
t("analysis_type_disease_id"),
|
| 542 |
+
t("analysis_type_care_advice")
|
| 543 |
+
]
|
| 544 |
analysis_type = st.selectbox(
|
| 545 |
+
t("analysis_type_label"), # Ajout d'une clé pour le label du selectbox
|
| 546 |
+
analysis_options,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
key="image_analysis_type_selector"
|
| 548 |
)
|
| 549 |
|
| 550 |
custom_prompt_input = st.text_area(
|
| 551 |
+
t("custom_prompt_label"),
|
| 552 |
value="",
|
| 553 |
height=100,
|
| 554 |
+
placeholder=t("custom_prompt_placeholder") # Placeholder pour le prompt
|
| 555 |
)
|
| 556 |
|
| 557 |
+
if st.button(t("analyze_image_button"), type="primary", key="analyze_image_button"):
|
| 558 |
final_prompt = custom_prompt_input.strip()
|
| 559 |
+
# Utilise la fonction `t` pour obtenir le prompt par défaut basé sur la langue
|
| 560 |
if not final_prompt:
|
| 561 |
+
if analysis_type == t("analysis_type_full_diagnosis"):
|
| 562 |
+
default_prompt_key = "default_prompt_full_diagnosis"
|
| 563 |
+
elif analysis_type == t("analysis_type_disease_id"):
|
| 564 |
+
default_prompt_key = "default_prompt_disease_id"
|
| 565 |
+
else: # Care Advice
|
| 566 |
+
default_prompt_key = "default_prompt_care_advice"
|
| 567 |
+
|
| 568 |
+
# Fonction pour obtenir les prompts par défaut en fonction de la langue
|
| 569 |
+
def get_default_prompt(lang, key):
|
| 570 |
+
prompts = {
|
| 571 |
+
"fr": {
|
| 572 |
+
"default_prompt_full_diagnosis": "<image>\nAnalyse cette image de plante et fournis un diagnostic complet :\n1. **État général de la plante :** Décris son apparence globale et sa vitalité.\n2. **Identification des problèmes :** Liste les maladies, parasites ou carences visibles.\n3. **Diagnostic probable :** Indique la maladie ou le problème le plus probable.\n4. **Causes possibles :** Explique ce qui a pu causer ce problème.\n5. **Recommandations de traitement :** Propose des solutions concrètes et adaptées.\n6. **Conseils préventifs :** Donne des astuces pour éviter que le problème ne revienne.\nRéponds de manière structurée et claire en français.",
|
| 573 |
+
"default_prompt_disease_id": "<image>\nDiagnostique cette plante en te concentrant sur les maladies et parasites :\n1. Identifie les symptômes visuels spécifiques aux maladies ou parasites.\n2. Détermine la maladie ou le parasite le plus probable.\n3. Explique les conditions favorisant leur développement.\n4. Propose des traitements ciblés et des méthodes de lutte.\nRéponds en français de manière structurée.",
|
| 574 |
+
"default_prompt_care_advice": "<image>\nAnalyse cette plante et donne des conseils de soins détaillés :\n1. État général de la plante : Évalue sa santé actuelle.\n2. Besoins spécifiques : Précise ses besoins en eau, lumière, nutriments et substrat.\n3. Conseils d'entretien : Donne des instructions pour l'arrosage, la fertilisation et la taille.\n4. Améliorations recommandées : Suggère des actions pour optimiser sa croissance et sa santé.\nRéponds en français de manière structurée."
|
| 575 |
+
},
|
| 576 |
+
"en": {
|
| 577 |
+
"default_prompt_full_diagnosis": "<image>\nAnalyze this plant image and provide a comprehensive diagnosis:\n1. **Overall Plant Health:** Describe its general appearance and vitality.\n2. **Problem Identification:** List visible diseases, pests, or deficiencies.\n3. **Probable Diagnosis:** Indicate the most likely disease or problem.\n4. **Possible Causes:** Explain what might have caused this issue.\n5. **Treatment Recommendations:** Propose concrete and appropriate solutions.\n6. **Preventive Advice:** Give tips to avoid the problem from recurring.\nRespond in a structured and clear manner in English.",
|
| 578 |
+
"default_prompt_disease_id": "<image>\nDiagnose this plant focusing on diseases and pests:\n1. Identify visual symptoms specific to diseases or pests.\n2. Determine the most likely disease or pest.\n3. Explain the conditions that favor their development.\n4. Propose targeted treatments and control methods.\nRespond in a structured manner in English.",
|
| 579 |
+
"default_prompt_care_advice": "<image>\nAnalyze this plant and provide detailed care advice:\n1. Overall plant condition: Assess its current health.\n2. Specific needs: Specify its requirements for water, light, nutrients, and substrate.\n3. Maintenance tips: Give instructions for watering, fertilizing, and pruning.\n4. Recommended improvements: Suggest actions to optimize its growth and health.\nRespond in a structured manner in English."
|
| 580 |
+
}
|
| 581 |
+
}
|
| 582 |
+
return prompts[lang].get(key, key) # Retourne la clé si non trouvée
|
| 583 |
+
|
| 584 |
+
final_prompt = get_default_prompt(st.session_state.language, default_prompt_key)
|
| 585 |
|
| 586 |
analysis_result = analyze_image_multilingual(resized_image, prompt_text=final_prompt)
|
| 587 |
|
| 588 |
if analysis_result:
|
| 589 |
+
st.success(t("analysis_complete_success"))
|
| 590 |
+
st.markdown(f"### {t('results_title')}")
|
| 591 |
st.markdown(analysis_result)
|
| 592 |
else:
|
| 593 |
+
st.error(t("analysis_failed_error"))
|
| 594 |
else:
|
| 595 |
+
st.warning(t("model_not_loaded_warning"))
|
| 596 |
|
| 597 |
with tab2:
|
| 598 |
st.header(t("text_analysis_title"))
|
|
|
|
| 601 |
text_description_input = st.text_area(
|
| 602 |
t("enter_description"),
|
| 603 |
height=200,
|
| 604 |
+
placeholder=t("enter_description_placeholder") # Placeholder pour la description
|
| 605 |
)
|
| 606 |
|
| 607 |
+
if st.button(t("analyze_text_button"), type="primary", key="analyze_text_button"):
|
| 608 |
if text_description_input.strip():
|
| 609 |
if st.session_state.model_loaded and check_model_health():
|
| 610 |
analysis_result = analyze_text_multilingual(text_description_input)
|
| 611 |
|
| 612 |
if analysis_result:
|
| 613 |
+
st.success(t("analysis_complete_success"))
|
| 614 |
+
st.markdown(f"### {t('results_title')}")
|
| 615 |
st.markdown(analysis_result)
|
| 616 |
else:
|
| 617 |
+
st.error(t("text_analysis_failed_error"))
|
| 618 |
else:
|
| 619 |
+
st.warning(t("model_not_loaded_warning"))
|
| 620 |
else:
|
| 621 |
+
st.warning(t("please_enter_description_warning")) # Avertissement si pas de description
|
| 622 |
|
| 623 |
with tab3:
|
| 624 |
st.header(t("config_title"))
|
|
|
|
| 626 |
col1, col2 = st.columns(2)
|
| 627 |
|
| 628 |
with col1:
|
| 629 |
+
st.subheader(t("system_info_title"))
|
|
|
|
| 630 |
try:
|
| 631 |
ram = psutil.virtual_memory()
|
| 632 |
+
st.write(f"**{t('sys_ram_total')}** {ram.total / (1024**3):.1f} GB")
|
| 633 |
+
st.write(f"**{t('sys_ram_used')}** {ram.used / (1024**3):.1f} GB ({ram.percent:.1f}%)")
|
| 634 |
|
| 635 |
disk = psutil.disk_usage('/')
|
| 636 |
+
st.write(f"**{t('sys_disk_free')}** {disk.free / (1024**3):.1f} GB")
|
| 637 |
|
| 638 |
if torch.cuda.is_available():
|
| 639 |
+
st.write(f"**{t('sys_gpu_detected')}** {torch.cuda.get_device_name(0)}")
|
| 640 |
gpu_props = torch.cuda.get_device_properties(0)
|
| 641 |
+
st.write(f"**{t('sys_gpu_total_mem')}** {gpu_props.total_memory / (1024**3):.1f} GB")
|
| 642 |
else:
|
| 643 |
+
st.write(f"**{t('sys_gpu_not_available')}**")
|
| 644 |
except Exception as e:
|
| 645 |
+
st.error(f"{t('sys_info_error')} {e}")
|
| 646 |
|
| 647 |
with col2:
|
| 648 |
+
st.subheader(t("model_stats_title"))
|
|
|
|
| 649 |
if st.session_state.model_loaded and check_model_health():
|
| 650 |
+
st.write(f"**{t('model_status')}:** {t('model_status_loaded')}")
|
| 651 |
if st.session_state.model is not None:
|
| 652 |
+
st.write(f"**{t('model_type_label')}** `{type(st.session_state.model).__name__}`")
|
| 653 |
if hasattr(st.session_state.model, 'device'):
|
| 654 |
+
st.write(f"**{t('device_used_label')}** `{st.session_state.model.device}`")
|
| 655 |
if st.session_state.model_load_time:
|
| 656 |
load_time_str = time.strftime('%H:%M:%S', time.localtime(st.session_state.model_load_time))
|
| 657 |
+
st.write(f"**{t('load_time_label')}** `{load_time_str}`")
|
| 658 |
else:
|
| 659 |
+
st.write(f"**{t('model_status')}:** {t('model_status_not_loaded')}")
|
| 660 |
+
st.write(f"**{t('model_type_label')}** N/A")
|
| 661 |
+
st.write(f"**{t('device_used_label')}** N/A")
|
| 662 |
|
| 663 |
with tab4:
|
| 664 |
st.header(t("about_title"))
|
|
|
|
| 665 |
st.markdown("""
|
| 666 |
## 🌱 AgriLens AI : Votre Assistant d'Analyse de Plantes
|
| 667 |
**AgriLens AI** est une application alimentée par l'intelligence artificielle qui vous aide à identifier les problèmes de vos plantes, à diagnostiquer les maladies et à obtenir des conseils de traitement personnalisés. Que vous soyez un jardinier débutant ou expérimenté, AgriLens AI est là pour vous accompagner.
|
|
|
|
| 671 |
- **Recommandations Précises :** Recevez des conseils de traitement, des suggestions de soins et des mesures préventives adaptées à chaque situation.
|
| 672 |
- **Support Multilingue :** L'interface et les réponses sont disponibles en français et en anglais pour une accessibilité maximale.
|
| 673 |
### 🤖 Technologie Utilisée :
|
| 674 |
+
- **Modèle IA :** Google Gemma 3n E4B IT, un modèle multimodal performant pour l'analyse de plantes. ([Voir sur Hugging Face](https://huggingface.co/google/gemma-3n-E4B-it))
|
| 675 |
- **Bibliothèques :** `transformers`, `torch`, `streamlit`, `Pillow`, `psutil`, `requests`, `huggingface-hub`.
|
| 676 |
### 📝 Comment Utiliser AgriLens AI :
|
| 677 |
+
1. **Chargez le Modèle IA :** Dans la barre latérale, cliquez sur "Charger le Modèle IA". Attendez que le statut passe à "Chargé et fonctionnel". Cela peut prendre quelques minutes.
|
| 678 |
2. **Analysez votre Plante :**
|
| 679 |
- **Via Image :** Allez à l'onglet "📸 Analyse d'Image". Uploadez une photo de votre plante ou utilisez votre webcam. Choisissez le type d'analyse et cliquez sur "Analyser l'image".
|
| 680 |
- **Via Texte :** Allez à l'onglet "📝 Analyse de Texte". Décrivez les symptômes de votre plante dans la zone de texte et cliquez sur "Analyser la description".
|
|
|
|
| 686 |
*Développé avec passion pour aider les jardiniers et amoureux des plantes à prendre soin de leurs cultures grâce à la puissance de l'IA.*
|
| 687 |
""")
|
| 688 |
|
| 689 |
+
# --- Pied de page ---
|
| 690 |
st.divider()
|
| 691 |
st.markdown("""
|
| 692 |
<div style='text-align: center; color: #666;'>
|
| 693 |
+
🌱 AgriLens AI - Assistant d'Analyse de Plantes |
|
| 694 |
+
<a href='#' target='_blank'>Documentation</a> |
|
| 695 |
+
<a href='#' target='_blank'>Support</a> |
|
| 696 |
+
<a href='https://huggingface.co/google/gemma-3n-E4B-it' target='_blank'>Modèle Gemma 3n</a>
|
| 697 |
</div>
|
| 698 |
""", unsafe_allow_html=True)
|