Spaces:
Runtime error
Runtime error
Update src/streamlit_app_stable.py
Browse files- src/streamlit_app_stable.py +68 -124
src/streamlit_app_stable.py
CHANGED
|
@@ -4,11 +4,13 @@ import io
|
|
| 4 |
from PIL import Image
|
| 5 |
import requests
|
| 6 |
import torch
|
| 7 |
-
import google.generativeai as genai # Importé mais non utilisé dans ce code
|
| 8 |
import gc
|
| 9 |
import time
|
| 10 |
import sys
|
| 11 |
import psutil
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
# --- Configuration de la page ---
|
| 14 |
st.set_page_config(
|
|
@@ -19,6 +21,7 @@ st.set_page_config(
|
|
| 19 |
)
|
| 20 |
|
| 21 |
# --- Initialisation des variables de session ---
|
|
|
|
| 22 |
if 'model_loaded' not in st.session_state:
|
| 23 |
st.session_state.model_loaded = False
|
| 24 |
if 'model' not in st.session_state:
|
|
@@ -33,54 +36,50 @@ if 'language' not in st.session_state:
|
|
| 33 |
st.session_state.language = "fr"
|
| 34 |
if 'load_attempt_count' not in st.session_state:
|
| 35 |
st.session_state.load_attempt_count = 0
|
|
|
|
|
|
|
| 36 |
|
| 37 |
# --- Fonctions d'aide système ---
|
| 38 |
|
| 39 |
def check_model_health():
|
| 40 |
"""Vérifie si le modèle et le processeur sont chargés et semblent opérationnels."""
|
| 41 |
try:
|
| 42 |
-
# Vérifie si les objets existent et si le modèle a un attribut 'device' (ce qui suggère une initialisation)
|
| 43 |
return (st.session_state.model is not None and
|
| 44 |
st.session_state.processor is not None and
|
| 45 |
hasattr(st.session_state.model, 'device'))
|
| 46 |
except Exception:
|
| 47 |
-
# Si une exception survient (ex: AttributeError si model ou processor est None)
|
| 48 |
return False
|
| 49 |
|
| 50 |
def diagnose_loading_issues():
|
| 51 |
"""Diagnostique les problèmes potentiels avant le chargement du modèle."""
|
| 52 |
issues = []
|
| 53 |
|
| 54 |
-
# Vérification RAM
|
| 55 |
try:
|
| 56 |
ram = psutil.virtual_memory()
|
| 57 |
ram_gb = ram.total / (1024**3)
|
| 58 |
-
if ram_gb < 8:
|
| 59 |
issues.append(f"⚠️ RAM faible: {ram_gb:.1f}GB (recommandé: 8GB+ pour ce modèle)")
|
| 60 |
except Exception as e:
|
| 61 |
issues.append(f"⚠️ Impossible de vérifier la RAM : {e}")
|
| 62 |
|
| 63 |
-
# Vérification Espace Disque (utile si le modèle est téléchargé localement)
|
| 64 |
try:
|
| 65 |
disk_usage = psutil.disk_usage('/')
|
| 66 |
disk_gb = disk_usage.free / (1024**3)
|
| 67 |
-
if disk_gb < 10:
|
| 68 |
issues.append(f"⚠️ Espace disque faible: {disk_gb:.1f}GB libre sur '/'")
|
| 69 |
except Exception as e:
|
| 70 |
issues.append(f"⚠️ Impossible de vérifier l'espace disque : {e}")
|
| 71 |
|
| 72 |
-
# Vérification connexion Hugging Face
|
| 73 |
try:
|
| 74 |
requests.get("https://huggingface.co", timeout=5)
|
| 75 |
except requests.exceptions.RequestException:
|
| 76 |
issues.append("⚠️ Problème de connexion à Hugging Face Hub")
|
| 77 |
|
| 78 |
-
# Vérification CUDA
|
| 79 |
if torch.cuda.is_available():
|
| 80 |
try:
|
| 81 |
gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
|
| 82 |
-
if gpu_memory <
|
| 83 |
-
issues.append(f"⚠️ GPU mémoire faible: {gpu_memory:.1f}GB (recommandé: 6GB+
|
| 84 |
except Exception as e:
|
| 85 |
issues.append(f"⚠️ Erreur lors de la vérification de la mémoire GPU : {e}")
|
| 86 |
else:
|
|
@@ -88,14 +87,13 @@ def diagnose_loading_issues():
|
|
| 88 |
|
| 89 |
return issues
|
| 90 |
|
| 91 |
-
def resize_image_if_needed(image, max_size=(
|
| 92 |
"""Redimensionne l'image si ses dimensions dépassent max_size."""
|
| 93 |
original_size = image.size
|
| 94 |
if image.size[0] > max_size[0] or image.size[1] > max_size[1]:
|
| 95 |
-
# Utiliser Image.Resampling.LANCZOS pour une meilleure qualité de redimensionnement
|
| 96 |
image.thumbnail(max_size, Image.Resampling.LANCZOS)
|
| 97 |
-
return image, True
|
| 98 |
-
return image, False
|
| 99 |
|
| 100 |
def afficher_ram_disponible(context=""):
|
| 101 |
"""Affiche l'utilisation de la RAM de manière lisible."""
|
|
@@ -109,7 +107,6 @@ def afficher_ram_disponible(context=""):
|
|
| 109 |
st.write(f"💾 Impossible d'afficher l'utilisation de la RAM {context}: {e}")
|
| 110 |
|
| 111 |
# --- Gestion des traductions ---
|
| 112 |
-
|
| 113 |
def t(key):
|
| 114 |
"""Fonction pour gérer les traductions."""
|
| 115 |
translations = {
|
|
@@ -144,18 +141,22 @@ def t(key):
|
|
| 144 |
"model_status": "AI Model Status"
|
| 145 |
}
|
| 146 |
}
|
| 147 |
-
# Retourne la traduction si elle existe, sinon retourne la clé elle-même
|
| 148 |
return translations[st.session_state.language].get(key, key)
|
| 149 |
|
| 150 |
# --- Fonctions de chargement et d'analyse du modèle ---
|
| 151 |
|
| 152 |
-
# IMPORTANT: Sur Hugging Face Spaces, le chemin local D:/Dev/model_gemma n'existera PAS.
|
| 153 |
-
# Le code se rabattra automatiquement sur le chargement depuis le Hub Hugging Face.
|
| 154 |
-
# Si vous exécutez ceci localement et que vous avez téléchargé le modèle,
|
| 155 |
-
# assurez-vous que le chemin est correct et accessible.
|
| 156 |
MODEL_ID_LOCAL = "D:/Dev/model_gemma" # Path local (pour votre machine)
|
| 157 |
MODEL_ID_HF = "google/gemma-3n-E4B-it" # ID du modèle sur Hugging Face Hub
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
def load_model():
|
| 160 |
"""
|
| 161 |
Charge le modèle Gemma 3n et son processeur associé.
|
|
@@ -163,11 +164,9 @@ def load_model():
|
|
| 163 |
Gère les erreurs et les tentatives de chargement.
|
| 164 |
"""
|
| 165 |
try:
|
| 166 |
-
#
|
| 167 |
-
# pour éviter des erreurs si elles ne sont pas installées dans l'environnement de base
|
| 168 |
from transformers import AutoProcessor, Gemma3nForConditionalGeneration
|
| 169 |
|
| 170 |
-
# Limiter les tentatives de chargement pour éviter les boucles infinies en cas de problème persistant
|
| 171 |
if st.session_state.load_attempt_count >= 3:
|
| 172 |
st.error("❌ Trop de tentatives de chargement ont échoué. Veuillez vérifier votre configuration et redémarrer l'application.")
|
| 173 |
return None, None
|
|
@@ -180,19 +179,15 @@ def load_model():
|
|
| 180 |
for issue in issues:
|
| 181 |
st.write(issue)
|
| 182 |
|
| 183 |
-
# Libérer la mémoire GPU si disponible et si nécessaire
|
| 184 |
gc.collect()
|
| 185 |
if torch.cuda.is_available():
|
| 186 |
torch.cuda.empty_cache()
|
| 187 |
|
| 188 |
processor = None
|
| 189 |
model = None
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
# Vérifier si le chemin local existe et s'il contient des fichiers de modèle
|
| 194 |
-
local_model_found = os.path.exists(MODEL_ID_LOCAL) and \
|
| 195 |
-
os.path.exists(os.path.join(MODEL_ID_LOCAL, "config.json"))
|
| 196 |
|
| 197 |
if local_model_found:
|
| 198 |
try:
|
|
@@ -200,42 +195,38 @@ def load_model():
|
|
| 200 |
processor = AutoProcessor.from_pretrained(MODEL_ID_LOCAL, trust_remote_code=True)
|
| 201 |
model = Gemma3nForConditionalGeneration.from_pretrained(
|
| 202 |
MODEL_ID_LOCAL,
|
| 203 |
-
torch_dtype=torch.bfloat16
|
| 204 |
trust_remote_code=True,
|
| 205 |
-
low_cpu_mem_usage=True,
|
| 206 |
-
device_map=
|
| 207 |
)
|
| 208 |
st.success("✅ Modèle chargé avec succès depuis le dossier local.")
|
| 209 |
st.session_state.model_status = "Chargé (Local)"
|
| 210 |
except Exception as e:
|
| 211 |
st.warning(f"⚠️ Échec du chargement depuis le local ({e}). Tentative depuis Hugging Face Hub...")
|
| 212 |
-
# Si le chargement local échoue malgré l'existence du dossier, on tente le Hub.
|
| 213 |
-
# On ne met pas à jour model et processor ici pour permettre la tentative Hub.
|
| 214 |
|
| 215 |
-
# Si le
|
| 216 |
-
if model is None:
|
| 217 |
try:
|
| 218 |
st.info(f"Chargement du modèle depuis Hugging Face Hub : {MODEL_ID_HF}...")
|
| 219 |
processor = AutoProcessor.from_pretrained(MODEL_ID_HF, trust_remote_code=True)
|
| 220 |
model = Gemma3nForConditionalGeneration.from_pretrained(
|
| 221 |
MODEL_ID_HF,
|
| 222 |
-
torch_dtype=torch.bfloat16,
|
| 223 |
trust_remote_code=True,
|
| 224 |
low_cpu_mem_usage=True,
|
| 225 |
-
device_map=
|
| 226 |
)
|
| 227 |
st.success(f"✅ Modèle chargé avec succès depuis Hugging Face Hub ({MODEL_ID_HF}).")
|
| 228 |
st.session_state.model_status = "Chargé (Hub)"
|
| 229 |
except Exception as e:
|
| 230 |
st.error(f"❌ Échec du chargement du modèle depuis Hugging Face Hub : {e}")
|
| 231 |
-
return None, None
|
| 232 |
|
| 233 |
-
# Mettre à jour l'état de la session si le chargement a réussi
|
| 234 |
st.session_state.model = model
|
| 235 |
st.session_state.processor = processor
|
| 236 |
st.session_state.model_loaded = True
|
| 237 |
st.session_state.model_load_time = time.time()
|
| 238 |
-
st.session_state.load_attempt_count = 0
|
| 239 |
|
| 240 |
return model, processor
|
| 241 |
|
|
@@ -256,11 +247,9 @@ def analyze_image_multilingual(image, prompt_text=""):
|
|
| 256 |
return None
|
| 257 |
|
| 258 |
try:
|
| 259 |
-
# S'assurer que l'image est en mode RGB
|
| 260 |
if image.mode != 'RGB':
|
| 261 |
image = image.convert('RGB')
|
| 262 |
|
| 263 |
-
# Préparation du prompt par défaut s'il n'est pas fourni
|
| 264 |
if not prompt_text:
|
| 265 |
prompt_text = """Analyse cette image de plante et fournis un diagnostic complet :
|
| 266 |
1. **État général de la plante :** Décris son apparence globale et sa vitalité.
|
|
@@ -270,39 +259,31 @@ def analyze_image_multilingual(image, prompt_text=""):
|
|
| 270 |
5. **Recommandations de traitement :** Propose des solutions concrètes et adaptées.
|
| 271 |
6. **Conseils préventifs :** Donne des astuces pour éviter que le problème ne revienne.
|
| 272 |
|
| 273 |
-
Réponds de manière structurée et claire en français.
|
| 274 |
|
| 275 |
-
# Préparation des entrées pour le modèle
|
| 276 |
-
# Le processeur s'attend à une liste d'images si plusieurs sont passées, même s'il n'y en a qu'une.
|
| 277 |
inputs = st.session_state.processor(
|
| 278 |
images=[image],
|
| 279 |
text=prompt_text,
|
| 280 |
return_tensors="pt"
|
| 281 |
)
|
| 282 |
|
| 283 |
-
# Déplacer les tensors sur le bon device (CPU dans notre cas, ou GPU si `device_map="auto"` était utilisé)
|
| 284 |
inputs = {key: val.to(st.session_state.model.device) for key, val in inputs.items()}
|
| 285 |
|
| 286 |
-
# Générer la réponse avec le modèle
|
| 287 |
with st.spinner("🔍 Analyse d'image en cours..."):
|
| 288 |
outputs = st.session_state.model.generate(
|
| 289 |
**inputs,
|
| 290 |
-
max_new_tokens=512,
|
| 291 |
-
do_sample=True,
|
| 292 |
-
temperature=0.7,
|
| 293 |
-
top_p=0.9
|
| 294 |
)
|
| 295 |
|
| 296 |
-
# Décoder la réponse générée
|
| 297 |
response = st.session_state.processor.decode(outputs[0], skip_special_tokens=True)
|
| 298 |
|
| 299 |
-
# Extraire uniquement la partie générée par le modèle, en retirant le prompt initial
|
| 300 |
-
# Ceci peut nécessiter un ajustement si le modèle répète le prompt ou s'y réfère différemment
|
| 301 |
if prompt_text.strip() in response:
|
| 302 |
response_only = response.split(prompt_text.strip())[-1].strip()
|
| 303 |
else:
|
| 304 |
-
|
| 305 |
-
response_only = response.strip() # Utiliser la réponse brute si split échoue
|
| 306 |
|
| 307 |
return response_only
|
| 308 |
|
|
@@ -320,30 +301,27 @@ def analyze_text_multilingual(text_description):
|
|
| 320 |
return None
|
| 321 |
|
| 322 |
try:
|
| 323 |
-
#
|
| 324 |
prompt = f"""Analyse la description des symptômes de cette plante et fournis un diagnostic détaillé :
|
| 325 |
|
| 326 |
**Description des symptômes :**
|
| 327 |
{text_description}
|
| 328 |
|
| 329 |
-
**Instructions
|
| 330 |
-
1. **Diagnostic probable :** Quel est le problème principal
|
| 331 |
-
2. **Causes possibles :**
|
| 332 |
-
3. **
|
| 333 |
-
4. **Conseils
|
| 334 |
|
| 335 |
-
Réponds de manière
|
| 336 |
|
| 337 |
-
# Préparation des entrées pour le modèle (sans image ici)
|
| 338 |
inputs = st.session_state.processor(
|
| 339 |
text=prompt,
|
| 340 |
return_tensors="pt"
|
| 341 |
)
|
| 342 |
|
| 343 |
-
# Déplacer les tensors sur le bon device
|
| 344 |
inputs = {key: val.to(st.session_state.model.device) for key, val in inputs.items()}
|
| 345 |
|
| 346 |
-
# Générer la réponse
|
| 347 |
with st.spinner("🔍 Analyse textuelle en cours..."):
|
| 348 |
outputs = st.session_state.model.generate(
|
| 349 |
**inputs,
|
|
@@ -353,10 +331,8 @@ Réponds de manière structurée, claire et en français. Ne répète pas la des
|
|
| 353 |
top_p=0.9
|
| 354 |
)
|
| 355 |
|
| 356 |
-
# Décoder la réponse
|
| 357 |
response = st.session_state.processor.decode(outputs[0], skip_special_tokens=True)
|
| 358 |
|
| 359 |
-
# Extraire uniquement la partie générée par le modèle
|
| 360 |
if prompt.strip() in response:
|
| 361 |
response_only = response.split(prompt.strip())[-1].strip()
|
| 362 |
else:
|
|
@@ -370,7 +346,6 @@ Réponds de manière structurée, claire et en français. Ne répète pas la des
|
|
| 370 |
|
| 371 |
# --- Interface Utilisateur Streamlit ---
|
| 372 |
|
| 373 |
-
# Titre principal et sous-titre
|
| 374 |
st.title(t("title"))
|
| 375 |
st.markdown(t("subtitle"))
|
| 376 |
|
|
@@ -378,7 +353,6 @@ st.markdown(t("subtitle"))
|
|
| 378 |
with st.sidebar:
|
| 379 |
st.header(t("config_title"))
|
| 380 |
|
| 381 |
-
# Option de sélection de langue
|
| 382 |
lang_selector_options = ["Français", "English"]
|
| 383 |
current_lang_index = 0 if st.session_state.language == "fr" else 1
|
| 384 |
language_selected = st.selectbox(
|
|
@@ -387,52 +361,41 @@ with st.sidebar:
|
|
| 387 |
index=current_lang_index,
|
| 388 |
help="Sélectionnez la langue de l'interface et des réponses."
|
| 389 |
)
|
| 390 |
-
# Mise à jour de la langue de la session si elle change
|
| 391 |
st.session_state.language = "fr" if language_selected == "Français" else "en"
|
| 392 |
|
| 393 |
st.divider()
|
| 394 |
|
| 395 |
-
# Section pour la gestion du modèle IA
|
| 396 |
st.header(t("model_status"))
|
| 397 |
|
| 398 |
if st.session_state.model_loaded and check_model_health():
|
| 399 |
st.success("✅ Modèle chargé et fonctionnel")
|
| 400 |
st.write(f"**Statut :** `{st.session_state.model_status}`")
|
| 401 |
if st.session_state.model_load_time:
|
| 402 |
-
# Formatte le temps de chargement pour une meilleure lisibilité
|
| 403 |
load_time_str = time.strftime('%H:%M:%S', time.localtime(st.session_state.model_load_time))
|
| 404 |
st.write(f"**Heure de chargement :** {load_time_str}")
|
| 405 |
|
| 406 |
-
# Bouton pour décharger et recharger le modèle (utile pour tester les chargements)
|
| 407 |
if st.button("🔄 Recharger le modèle", type="secondary"):
|
| 408 |
st.session_state.model_loaded = False
|
| 409 |
st.session_state.model = None
|
| 410 |
st.session_state.processor = None
|
| 411 |
st.session_state.model_status = "Non chargé"
|
| 412 |
-
st.session_state.load_attempt_count = 0
|
| 413 |
st.info("Modèle déchargé. Cliquez sur 'Charger le modèle IA' pour le recharger.")
|
| 414 |
-
# Sur Hugging Face Spaces, il n'est pas nécessaire de faire `st.rerun()` après ce bouton,
|
| 415 |
-
# car le redémarrage se fait via l'interface du Space si nécessaire.
|
| 416 |
else:
|
| 417 |
st.warning("⚠️ Modèle IA non chargé")
|
| 418 |
|
| 419 |
-
# Bouton pour lancer le chargement du modèle
|
| 420 |
if st.button(t("load_model"), type="primary"):
|
| 421 |
with st.spinner("🔄 Chargement du modèle IA en cours..."):
|
| 422 |
model_loaded_success = load_model()
|
| 423 |
if model_loaded_success[0] is not None and model_loaded_success[1] is not None:
|
| 424 |
st.success("✅ Modèle IA chargé avec succès !")
|
| 425 |
-
# `st.rerun()`
|
| 426 |
-
# mais il peut causer des boucles si mal géré. Souvent, un redémarrage du Space est plus propre.
|
| 427 |
-
# st.rerun()
|
| 428 |
else:
|
| 429 |
st.error("❌ Échec du chargement du modèle IA.")
|
| 430 |
|
| 431 |
-
# Informations sur l'utilisation des ressources
|
| 432 |
st.divider()
|
| 433 |
st.subheader("📊 Ressources Système")
|
| 434 |
afficher_ram_disponible()
|
| 435 |
-
# Afficher l'utilisation GPU si disponible
|
| 436 |
if torch.cuda.is_available():
|
| 437 |
try:
|
| 438 |
gpu_memory = torch.cuda.memory_allocated(0) / (1024**3)
|
|
@@ -450,13 +413,12 @@ with tab1: # Onglet Analyse d'Image
|
|
| 450 |
st.header(t("image_analysis_title"))
|
| 451 |
st.markdown(t("image_analysis_desc"))
|
| 452 |
|
| 453 |
-
# Choix entre upload et capture webcam
|
| 454 |
capture_option = st.radio(
|
| 455 |
"Choisissez votre méthode de capture :",
|
| 456 |
["📁 Upload d'image" if st.session_state.language == "fr" else "📁 Upload Image",
|
| 457 |
"📷 Capture par webcam" if st.session_state.language == "fr" else "📷 Webcam Capture"],
|
| 458 |
horizontal=True,
|
| 459 |
-
key="image_capture_method"
|
| 460 |
)
|
| 461 |
|
| 462 |
uploaded_file = None
|
|
@@ -466,20 +428,18 @@ with tab1: # Onglet Analyse d'Image
|
|
| 466 |
uploaded_file = st.file_uploader(
|
| 467 |
t("choose_image"),
|
| 468 |
type=['png', 'jpg', 'jpeg'],
|
| 469 |
-
help="Formats acceptés : PNG, JPG, JPEG (taille max recommandée : 10MB
|
| 470 |
)
|
| 471 |
-
|
| 472 |
-
if uploaded_file is not None and uploaded_file.size > 10 * 1024 * 1024: # 10MB
|
| 473 |
st.warning("Le fichier est très volumineux. Il est recommandé d'utiliser des images de taille raisonnable pour une analyse plus rapide.")
|
| 474 |
else:
|
| 475 |
st.markdown("**📷 Capture d'image par webcam**")
|
| 476 |
st.info("Positionnez votre plante malade devant la webcam et cliquez sur 'Prendre une photo'.")
|
| 477 |
captured_image = st.camera_input(
|
| 478 |
"Prendre une photo de la plante",
|
| 479 |
-
key="webcam_photo"
|
| 480 |
)
|
| 481 |
|
| 482 |
-
# Traitement de l'image si un fichier est uploadé ou capturé
|
| 483 |
image_to_analyze = None
|
| 484 |
if uploaded_file is not None:
|
| 485 |
try:
|
|
@@ -492,11 +452,9 @@ with tab1: # Onglet Analyse d'Image
|
|
| 492 |
except Exception as e:
|
| 493 |
st.error(f"❌ Erreur lors du traitement de l'image capturée : {e}")
|
| 494 |
|
| 495 |
-
# Affichage de l'image et du bouton d'analyse si une image est prête
|
| 496 |
if image_to_analyze is not None:
|
| 497 |
-
# Redimensionner l'image si nécessaire pour l'affichage et l'analyse
|
| 498 |
original_size = image_to_analyze.size
|
| 499 |
-
resized_image, was_resized = resize_image_if_needed(image_to_analyze
|
| 500 |
|
| 501 |
col1, col2 = st.columns([1, 1])
|
| 502 |
with col1:
|
|
@@ -505,7 +463,6 @@ with tab1: # Onglet Analyse d'Image
|
|
| 505 |
st.info(f"ℹ️ Image redimensionnée de {original_size} à {resized_image.size} pour l'analyse.")
|
| 506 |
|
| 507 |
with col2:
|
| 508 |
-
# Options d'analyse seulement si le modèle est chargé
|
| 509 |
if st.session_state.model_loaded and check_model_health():
|
| 510 |
st.subheader("Options d'analyse")
|
| 511 |
analysis_type = st.selectbox(
|
|
@@ -526,9 +483,8 @@ with tab1: # Onglet Analyse d'Image
|
|
| 526 |
)
|
| 527 |
|
| 528 |
if st.button("🔍 Analyser l'image", type="primary", key="analyze_image_button"):
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
if not final_prompt: # Sinon, utiliser un prompt par défaut basé sur le type d'analyse
|
| 532 |
if analysis_type.startswith("Diagnostic complet"):
|
| 533 |
final_prompt = """Analyse cette image de plante et fournis un diagnostic complet :
|
| 534 |
1. **État général de la plante :** Décris son apparence globale et sa vitalité.
|
|
@@ -538,7 +494,7 @@ with tab1: # Onglet Analyse d'Image
|
|
| 538 |
5. **Recommandations de traitement :** Propose des solutions concrètes et adaptées.
|
| 539 |
6. **Conseils préventifs :** Donne des astuces pour éviter que le problème ne revienne.
|
| 540 |
|
| 541 |
-
Réponds de manière structurée et claire en français.
|
| 542 |
elif analysis_type.startswith("Identification et diagnostic de maladies"):
|
| 543 |
final_prompt = """Diagnostique cette plante en te concentrant sur les maladies et parasites :
|
| 544 |
1. Identifie les symptômes visuels spécifiques aux maladies ou parasites.
|
|
@@ -547,7 +503,7 @@ Réponds de manière structurée et claire en français. Utilise des termes simp
|
|
| 547 |
4. Propose des traitements ciblés et des méthodes de lutte.
|
| 548 |
|
| 549 |
Réponds en français de manière structurée."""
|
| 550 |
-
else:
|
| 551 |
final_prompt = """Analyse cette plante et donne des conseils de soins détaillés :
|
| 552 |
1. État général de la plante : Évalue sa santé actuelle.
|
| 553 |
2. Besoins spécifiques : Précise ses besoins en eau, lumière, nutriments et substrat.
|
|
@@ -556,13 +512,12 @@ Réponds en français de manière structurée."""
|
|
| 556 |
|
| 557 |
Réponds en français de manière structurée."""
|
| 558 |
|
| 559 |
-
# Appel de la fonction d'analyse
|
| 560 |
analysis_result = analyze_image_multilingual(resized_image, prompt_text=final_prompt)
|
| 561 |
|
| 562 |
if analysis_result:
|
| 563 |
st.success("✅ Analyse terminée !")
|
| 564 |
st.markdown("### 📋 Résultats de l'analyse")
|
| 565 |
-
st.markdown(analysis_result)
|
| 566 |
else:
|
| 567 |
st.error("❌ Échec de l'analyse de l'image.")
|
| 568 |
else:
|
|
@@ -572,18 +527,15 @@ with tab2: # Onglet Analyse de Texte
|
|
| 572 |
st.header(t("text_analysis_title"))
|
| 573 |
st.markdown(t("text_analysis_desc"))
|
| 574 |
|
| 575 |
-
# Zone de texte pour que l'utilisateur décrive les symptômes
|
| 576 |
text_description_input = st.text_area(
|
| 577 |
t("enter_description"),
|
| 578 |
height=200,
|
| 579 |
placeholder="Décrivez ici les symptômes observés sur votre plante : feuilles jaunes, taches, flétrissement, présence d'insectes, etc."
|
| 580 |
)
|
| 581 |
|
| 582 |
-
# Bouton d'analyse du texte
|
| 583 |
if st.button("🔍 Analyser la description", type="primary", key="analyze_text_button"):
|
| 584 |
-
if text_description_input.strip():
|
| 585 |
if st.session_state.model_loaded and check_model_health():
|
| 586 |
-
# Appel de la fonction d'analyse textuelle
|
| 587 |
analysis_result = analyze_text_multilingual(text_description_input)
|
| 588 |
|
| 589 |
if analysis_result:
|
|
@@ -600,12 +552,10 @@ with tab2: # Onglet Analyse de Texte
|
|
| 600 |
with tab3: # Onglet Configuration & Informations
|
| 601 |
st.header(t("config_title"))
|
| 602 |
|
| 603 |
-
col1, col2 = st.columns(2)
|
| 604 |
|
| 605 |
-
with col1:
|
| 606 |
st.subheader("🔧 Informations Système")
|
| 607 |
-
|
| 608 |
-
# Affichage des informations système générales
|
| 609 |
try:
|
| 610 |
ram = psutil.virtual_memory()
|
| 611 |
st.write(f"**RAM Totale :** {ram.total / (1024**3):.1f} GB")
|
|
@@ -623,7 +573,7 @@ with tab3: # Onglet Configuration & Informations
|
|
| 623 |
except Exception as e:
|
| 624 |
st.error(f"Erreur lors de la récupération des informations système : {e}")
|
| 625 |
|
| 626 |
-
with col2:
|
| 627 |
st.subheader("📊 Statistiques du Modèle IA")
|
| 628 |
|
| 629 |
if st.session_state.model_loaded and check_model_health():
|
|
@@ -643,7 +593,6 @@ with tab3: # Onglet Configuration & Informations
|
|
| 643 |
with tab4: # Onglet À Propos
|
| 644 |
st.header(t("about_title"))
|
| 645 |
|
| 646 |
-
# Utilisation de Markdown pour une mise en page plus riche
|
| 647 |
st.markdown("""
|
| 648 |
## 🌱 AgriLens AI : Votre Assistant d'Analyse de Plantes
|
| 649 |
|
|
@@ -658,12 +607,8 @@ with tab4: # Onglet À Propos
|
|
| 658 |
|
| 659 |
### 🤖 Technologie Utilisée :
|
| 660 |
|
| 661 |
-
* **Modèle IA :**
|
| 662 |
-
* **Bibliothèques :**
|
| 663 |
-
* `transformers` et `torch` pour le fonctionnement des modèles IA.
|
| 664 |
-
* `Streamlit` pour une interface utilisateur web interactive et facile à utiliser.
|
| 665 |
-
* `Pillow` pour le traitement d'images.
|
| 666 |
-
* `psutil` pour l'affichage des ressources système.
|
| 667 |
|
| 668 |
### 📝 Comment Utiliser AgriLens AI :
|
| 669 |
|
|
@@ -675,9 +620,8 @@ with tab4: # Onglet À Propos
|
|
| 675 |
|
| 676 |
### 🔧 Support et Optimisation :
|
| 677 |
|
| 678 |
-
* **
|
| 679 |
-
* **
|
| 680 |
-
* **Performance :** Les performances peuvent varier en fonction de votre matériel et de la configuration de votre environnement (CPU vs GPU). Pour une expérience optimale avec ce type de modèle, un GPU est fortement recommandé.
|
| 681 |
|
| 682 |
---
|
| 683 |
|
|
@@ -685,7 +629,7 @@ with tab4: # Onglet À Propos
|
|
| 685 |
""")
|
| 686 |
|
| 687 |
# --- Pied de page ---
|
| 688 |
-
st.divider()
|
| 689 |
st.markdown("""
|
| 690 |
<div style='text-align: center; color: #666;'>
|
| 691 |
🌱 AgriLens AI - Assistant d'Analyse de Plantes |
|
|
|
|
| 4 |
from PIL import Image
|
| 5 |
import requests
|
| 6 |
import torch
|
|
|
|
| 7 |
import gc
|
| 8 |
import time
|
| 9 |
import sys
|
| 10 |
import psutil
|
| 11 |
+
# from transformers import AutoProcessor, Gemma3nForConditionalGeneration # Importés plus bas pour éviter de charger inutilement
|
| 12 |
+
# Importations pour le chargement du modèle seulement si nécessaire
|
| 13 |
+
# from transformers import AutoProcessor, Gemma3nForConditionalGeneration
|
| 14 |
|
| 15 |
# --- Configuration de la page ---
|
| 16 |
st.set_page_config(
|
|
|
|
| 21 |
)
|
| 22 |
|
| 23 |
# --- Initialisation des variables de session ---
|
| 24 |
+
# Initialise les variables de session pour gérer l'état de l'application
|
| 25 |
if 'model_loaded' not in st.session_state:
|
| 26 |
st.session_state.model_loaded = False
|
| 27 |
if 'model' not in st.session_state:
|
|
|
|
| 36 |
st.session_state.language = "fr"
|
| 37 |
if 'load_attempt_count' not in st.session_state:
|
| 38 |
st.session_state.load_attempt_count = 0
|
| 39 |
+
if 'device' not in st.session_state:
|
| 40 |
+
st.session_state.device = "cpu" # Valeur par défaut
|
| 41 |
|
| 42 |
# --- Fonctions d'aide système ---
|
| 43 |
|
| 44 |
def check_model_health():
|
| 45 |
"""Vérifie si le modèle et le processeur sont chargés et semblent opérationnels."""
|
| 46 |
try:
|
|
|
|
| 47 |
return (st.session_state.model is not None and
|
| 48 |
st.session_state.processor is not None and
|
| 49 |
hasattr(st.session_state.model, 'device'))
|
| 50 |
except Exception:
|
|
|
|
| 51 |
return False
|
| 52 |
|
| 53 |
def diagnose_loading_issues():
|
| 54 |
"""Diagnostique les problèmes potentiels avant le chargement du modèle."""
|
| 55 |
issues = []
|
| 56 |
|
|
|
|
| 57 |
try:
|
| 58 |
ram = psutil.virtual_memory()
|
| 59 |
ram_gb = ram.total / (1024**3)
|
| 60 |
+
if ram_gb < 8:
|
| 61 |
issues.append(f"⚠️ RAM faible: {ram_gb:.1f}GB (recommandé: 8GB+ pour ce modèle)")
|
| 62 |
except Exception as e:
|
| 63 |
issues.append(f"⚠️ Impossible de vérifier la RAM : {e}")
|
| 64 |
|
|
|
|
| 65 |
try:
|
| 66 |
disk_usage = psutil.disk_usage('/')
|
| 67 |
disk_gb = disk_usage.free / (1024**3)
|
| 68 |
+
if disk_gb < 10:
|
| 69 |
issues.append(f"⚠️ Espace disque faible: {disk_gb:.1f}GB libre sur '/'")
|
| 70 |
except Exception as e:
|
| 71 |
issues.append(f"⚠️ Impossible de vérifier l'espace disque : {e}")
|
| 72 |
|
|
|
|
| 73 |
try:
|
| 74 |
requests.get("https://huggingface.co", timeout=5)
|
| 75 |
except requests.exceptions.RequestException:
|
| 76 |
issues.append("⚠️ Problème de connexion à Hugging Face Hub")
|
| 77 |
|
|
|
|
| 78 |
if torch.cuda.is_available():
|
| 79 |
try:
|
| 80 |
gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
|
| 81 |
+
if gpu_memory < 6: # Recommandation pour ce type de modèle multimodal
|
| 82 |
+
issues.append(f"⚠️ GPU mémoire faible: {gpu_memory:.1f}GB (recommandé: 6GB+)")
|
| 83 |
except Exception as e:
|
| 84 |
issues.append(f"⚠️ Erreur lors de la vérification de la mémoire GPU : {e}")
|
| 85 |
else:
|
|
|
|
| 87 |
|
| 88 |
return issues
|
| 89 |
|
| 90 |
+
def resize_image_if_needed(image, max_size=(1024, 1024)): # Augmenté pour une meilleure analyse si possible
|
| 91 |
"""Redimensionne l'image si ses dimensions dépassent max_size."""
|
| 92 |
original_size = image.size
|
| 93 |
if image.size[0] > max_size[0] or image.size[1] > max_size[1]:
|
|
|
|
| 94 |
image.thumbnail(max_size, Image.Resampling.LANCZOS)
|
| 95 |
+
return image, True
|
| 96 |
+
return image, False
|
| 97 |
|
| 98 |
def afficher_ram_disponible(context=""):
|
| 99 |
"""Affiche l'utilisation de la RAM de manière lisible."""
|
|
|
|
| 107 |
st.write(f"💾 Impossible d'afficher l'utilisation de la RAM {context}: {e}")
|
| 108 |
|
| 109 |
# --- Gestion des traductions ---
|
|
|
|
| 110 |
def t(key):
|
| 111 |
"""Fonction pour gérer les traductions."""
|
| 112 |
translations = {
|
|
|
|
| 141 |
"model_status": "AI Model Status"
|
| 142 |
}
|
| 143 |
}
|
|
|
|
| 144 |
return translations[st.session_state.language].get(key, key)
|
| 145 |
|
| 146 |
# --- Fonctions de chargement et d'analyse du modèle ---
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
MODEL_ID_LOCAL = "D:/Dev/model_gemma" # Path local (pour votre machine)
|
| 149 |
MODEL_ID_HF = "google/gemma-3n-E4B-it" # ID du modèle sur Hugging Face Hub
|
| 150 |
|
| 151 |
+
def get_device_map():
|
| 152 |
+
"""Détermine si le modèle doit être chargé sur GPU ou CPU."""
|
| 153 |
+
if torch.cuda.is_available():
|
| 154 |
+
st.session_state.device = "cuda"
|
| 155 |
+
return "auto" # Hugging Face gérera l'allocation sur GPU
|
| 156 |
+
else:
|
| 157 |
+
st.session_state.device = "cpu"
|
| 158 |
+
return "cpu" # Forcer l'utilisation du CPU
|
| 159 |
+
|
| 160 |
def load_model():
|
| 161 |
"""
|
| 162 |
Charge le modèle Gemma 3n et son processeur associé.
|
|
|
|
| 164 |
Gère les erreurs et les tentatives de chargement.
|
| 165 |
"""
|
| 166 |
try:
|
| 167 |
+
# Importer seulement lorsque c'est nécessaire
|
|
|
|
| 168 |
from transformers import AutoProcessor, Gemma3nForConditionalGeneration
|
| 169 |
|
|
|
|
| 170 |
if st.session_state.load_attempt_count >= 3:
|
| 171 |
st.error("❌ Trop de tentatives de chargement ont échoué. Veuillez vérifier votre configuration et redémarrer l'application.")
|
| 172 |
return None, None
|
|
|
|
| 179 |
for issue in issues:
|
| 180 |
st.write(issue)
|
| 181 |
|
|
|
|
| 182 |
gc.collect()
|
| 183 |
if torch.cuda.is_available():
|
| 184 |
torch.cuda.empty_cache()
|
| 185 |
|
| 186 |
processor = None
|
| 187 |
model = None
|
| 188 |
+
device_map = get_device_map() # Déterminer le device_map
|
| 189 |
+
|
| 190 |
+
local_model_found = os.path.exists(MODEL_ID_LOCAL) and os.path.exists(os.path.join(MODEL_ID_LOCAL, "config.json"))
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
if local_model_found:
|
| 193 |
try:
|
|
|
|
| 195 |
processor = AutoProcessor.from_pretrained(MODEL_ID_LOCAL, trust_remote_code=True)
|
| 196 |
model = Gemma3nForConditionalGeneration.from_pretrained(
|
| 197 |
MODEL_ID_LOCAL,
|
| 198 |
+
torch_dtype=torch.bfloat16 if device_map == "auto" else torch.float32, # Utilise bfloat16 si GPU, sinon float32 sur CPU
|
| 199 |
trust_remote_code=True,
|
| 200 |
+
low_cpu_mem_usage=True,
|
| 201 |
+
device_map=device_map
|
| 202 |
)
|
| 203 |
st.success("✅ Modèle chargé avec succès depuis le dossier local.")
|
| 204 |
st.session_state.model_status = "Chargé (Local)"
|
| 205 |
except Exception as e:
|
| 206 |
st.warning(f"⚠️ Échec du chargement depuis le local ({e}). Tentative depuis Hugging Face Hub...")
|
|
|
|
|
|
|
| 207 |
|
| 208 |
+
if model is None: # Si le chargement local a échoué ou n'a pas été tenté
|
|
|
|
| 209 |
try:
|
| 210 |
st.info(f"Chargement du modèle depuis Hugging Face Hub : {MODEL_ID_HF}...")
|
| 211 |
processor = AutoProcessor.from_pretrained(MODEL_ID_HF, trust_remote_code=True)
|
| 212 |
model = Gemma3nForConditionalGeneration.from_pretrained(
|
| 213 |
MODEL_ID_HF,
|
| 214 |
+
torch_dtype=torch.bfloat16 if device_map == "auto" else torch.float32,
|
| 215 |
trust_remote_code=True,
|
| 216 |
low_cpu_mem_usage=True,
|
| 217 |
+
device_map=device_map
|
| 218 |
)
|
| 219 |
st.success(f"✅ Modèle chargé avec succès depuis Hugging Face Hub ({MODEL_ID_HF}).")
|
| 220 |
st.session_state.model_status = "Chargé (Hub)"
|
| 221 |
except Exception as e:
|
| 222 |
st.error(f"❌ Échec du chargement du modèle depuis Hugging Face Hub : {e}")
|
| 223 |
+
return None, None
|
| 224 |
|
|
|
|
| 225 |
st.session_state.model = model
|
| 226 |
st.session_state.processor = processor
|
| 227 |
st.session_state.model_loaded = True
|
| 228 |
st.session_state.model_load_time = time.time()
|
| 229 |
+
st.session_state.load_attempt_count = 0
|
| 230 |
|
| 231 |
return model, processor
|
| 232 |
|
|
|
|
| 247 |
return None
|
| 248 |
|
| 249 |
try:
|
|
|
|
| 250 |
if image.mode != 'RGB':
|
| 251 |
image = image.convert('RGB')
|
| 252 |
|
|
|
|
| 253 |
if not prompt_text:
|
| 254 |
prompt_text = """Analyse cette image de plante et fournis un diagnostic complet :
|
| 255 |
1. **État général de la plante :** Décris son apparence globale et sa vitalité.
|
|
|
|
| 259 |
5. **Recommandations de traitement :** Propose des solutions concrètes et adaptées.
|
| 260 |
6. **Conseils préventifs :** Donne des astuces pour éviter que le problème ne revienne.
|
| 261 |
|
| 262 |
+
Réponds de manière structurée et claire en français.""" # Prompt légèrement simplifié
|
| 263 |
|
|
|
|
|
|
|
| 264 |
inputs = st.session_state.processor(
|
| 265 |
images=[image],
|
| 266 |
text=prompt_text,
|
| 267 |
return_tensors="pt"
|
| 268 |
)
|
| 269 |
|
|
|
|
| 270 |
inputs = {key: val.to(st.session_state.model.device) for key, val in inputs.items()}
|
| 271 |
|
|
|
|
| 272 |
with st.spinner("🔍 Analyse d'image en cours..."):
|
| 273 |
outputs = st.session_state.model.generate(
|
| 274 |
**inputs,
|
| 275 |
+
max_new_tokens=512,
|
| 276 |
+
do_sample=True,
|
| 277 |
+
temperature=0.7,
|
| 278 |
+
top_p=0.9
|
| 279 |
)
|
| 280 |
|
|
|
|
| 281 |
response = st.session_state.processor.decode(outputs[0], skip_special_tokens=True)
|
| 282 |
|
|
|
|
|
|
|
| 283 |
if prompt_text.strip() in response:
|
| 284 |
response_only = response.split(prompt_text.strip())[-1].strip()
|
| 285 |
else:
|
| 286 |
+
response_only = response.strip()
|
|
|
|
| 287 |
|
| 288 |
return response_only
|
| 289 |
|
|
|
|
| 301 |
return None
|
| 302 |
|
| 303 |
try:
|
| 304 |
+
# Prompt légèrement simplifié pour réduire la complexité et les potentiels problèmes de cache sur CPU
|
| 305 |
prompt = f"""Analyse la description des symptômes de cette plante et fournis un diagnostic détaillé :
|
| 306 |
|
| 307 |
**Description des symptômes :**
|
| 308 |
{text_description}
|
| 309 |
|
| 310 |
+
**Instructions :**
|
| 311 |
+
1. **Diagnostic probable :** Quel est le problème principal ?
|
| 312 |
+
2. **Causes possibles :** Pourquoi ce problème survient-il ?
|
| 313 |
+
3. **Traitement recommandé :** Comment le résoudre ?
|
| 314 |
+
4. **Conseils préventifs :** Comment l'éviter à l'avenir ?
|
| 315 |
|
| 316 |
+
Réponds en français de manière claire et structurée."""
|
| 317 |
|
|
|
|
| 318 |
inputs = st.session_state.processor(
|
| 319 |
text=prompt,
|
| 320 |
return_tensors="pt"
|
| 321 |
)
|
| 322 |
|
|
|
|
| 323 |
inputs = {key: val.to(st.session_state.model.device) for key, val in inputs.items()}
|
| 324 |
|
|
|
|
| 325 |
with st.spinner("🔍 Analyse textuelle en cours..."):
|
| 326 |
outputs = st.session_state.model.generate(
|
| 327 |
**inputs,
|
|
|
|
| 331 |
top_p=0.9
|
| 332 |
)
|
| 333 |
|
|
|
|
| 334 |
response = st.session_state.processor.decode(outputs[0], skip_special_tokens=True)
|
| 335 |
|
|
|
|
| 336 |
if prompt.strip() in response:
|
| 337 |
response_only = response.split(prompt.strip())[-1].strip()
|
| 338 |
else:
|
|
|
|
| 346 |
|
| 347 |
# --- Interface Utilisateur Streamlit ---
|
| 348 |
|
|
|
|
| 349 |
st.title(t("title"))
|
| 350 |
st.markdown(t("subtitle"))
|
| 351 |
|
|
|
|
| 353 |
with st.sidebar:
|
| 354 |
st.header(t("config_title"))
|
| 355 |
|
|
|
|
| 356 |
lang_selector_options = ["Français", "English"]
|
| 357 |
current_lang_index = 0 if st.session_state.language == "fr" else 1
|
| 358 |
language_selected = st.selectbox(
|
|
|
|
| 361 |
index=current_lang_index,
|
| 362 |
help="Sélectionnez la langue de l'interface et des réponses."
|
| 363 |
)
|
|
|
|
| 364 |
st.session_state.language = "fr" if language_selected == "Français" else "en"
|
| 365 |
|
| 366 |
st.divider()
|
| 367 |
|
|
|
|
| 368 |
st.header(t("model_status"))
|
| 369 |
|
| 370 |
if st.session_state.model_loaded and check_model_health():
|
| 371 |
st.success("✅ Modèle chargé et fonctionnel")
|
| 372 |
st.write(f"**Statut :** `{st.session_state.model_status}`")
|
| 373 |
if st.session_state.model_load_time:
|
|
|
|
| 374 |
load_time_str = time.strftime('%H:%M:%S', time.localtime(st.session_state.model_load_time))
|
| 375 |
st.write(f"**Heure de chargement :** {load_time_str}")
|
| 376 |
|
|
|
|
| 377 |
if st.button("🔄 Recharger le modèle", type="secondary"):
|
| 378 |
st.session_state.model_loaded = False
|
| 379 |
st.session_state.model = None
|
| 380 |
st.session_state.processor = None
|
| 381 |
st.session_state.model_status = "Non chargé"
|
| 382 |
+
st.session_state.load_attempt_count = 0
|
| 383 |
st.info("Modèle déchargé. Cliquez sur 'Charger le modèle IA' pour le recharger.")
|
|
|
|
|
|
|
| 384 |
else:
|
| 385 |
st.warning("⚠️ Modèle IA non chargé")
|
| 386 |
|
|
|
|
| 387 |
if st.button(t("load_model"), type="primary"):
|
| 388 |
with st.spinner("🔄 Chargement du modèle IA en cours..."):
|
| 389 |
model_loaded_success = load_model()
|
| 390 |
if model_loaded_success[0] is not None and model_loaded_success[1] is not None:
|
| 391 |
st.success("✅ Modèle IA chargé avec succès !")
|
| 392 |
+
# `st.rerun()` peut être tenté, mais un redémarrage du Space est souvent plus sûr.
|
|
|
|
|
|
|
| 393 |
else:
|
| 394 |
st.error("❌ Échec du chargement du modèle IA.")
|
| 395 |
|
|
|
|
| 396 |
st.divider()
|
| 397 |
st.subheader("📊 Ressources Système")
|
| 398 |
afficher_ram_disponible()
|
|
|
|
| 399 |
if torch.cuda.is_available():
|
| 400 |
try:
|
| 401 |
gpu_memory = torch.cuda.memory_allocated(0) / (1024**3)
|
|
|
|
| 413 |
st.header(t("image_analysis_title"))
|
| 414 |
st.markdown(t("image_analysis_desc"))
|
| 415 |
|
|
|
|
| 416 |
capture_option = st.radio(
|
| 417 |
"Choisissez votre méthode de capture :",
|
| 418 |
["📁 Upload d'image" if st.session_state.language == "fr" else "📁 Upload Image",
|
| 419 |
"📷 Capture par webcam" if st.session_state.language == "fr" else "📷 Webcam Capture"],
|
| 420 |
horizontal=True,
|
| 421 |
+
key="image_capture_method"
|
| 422 |
)
|
| 423 |
|
| 424 |
uploaded_file = None
|
|
|
|
| 428 |
uploaded_file = st.file_uploader(
|
| 429 |
t("choose_image"),
|
| 430 |
type=['png', 'jpg', 'jpeg'],
|
| 431 |
+
help="Formats acceptés : PNG, JPG, JPEG (taille max recommandée : 10MB)."
|
| 432 |
)
|
| 433 |
+
if uploaded_file is not None and uploaded_file.size > 10 * 1024 * 1024:
|
|
|
|
| 434 |
st.warning("Le fichier est très volumineux. Il est recommandé d'utiliser des images de taille raisonnable pour une analyse plus rapide.")
|
| 435 |
else:
|
| 436 |
st.markdown("**📷 Capture d'image par webcam**")
|
| 437 |
st.info("Positionnez votre plante malade devant la webcam et cliquez sur 'Prendre une photo'.")
|
| 438 |
captured_image = st.camera_input(
|
| 439 |
"Prendre une photo de la plante",
|
| 440 |
+
key="webcam_photo"
|
| 441 |
)
|
| 442 |
|
|
|
|
| 443 |
image_to_analyze = None
|
| 444 |
if uploaded_file is not None:
|
| 445 |
try:
|
|
|
|
| 452 |
except Exception as e:
|
| 453 |
st.error(f"❌ Erreur lors du traitement de l'image capturée : {e}")
|
| 454 |
|
|
|
|
| 455 |
if image_to_analyze is not None:
|
|
|
|
| 456 |
original_size = image_to_analyze.size
|
| 457 |
+
resized_image, was_resized = resize_image_if_needed(image_to_analyze)
|
| 458 |
|
| 459 |
col1, col2 = st.columns([1, 1])
|
| 460 |
with col1:
|
|
|
|
| 463 |
st.info(f"ℹ️ Image redimensionnée de {original_size} à {resized_image.size} pour l'analyse.")
|
| 464 |
|
| 465 |
with col2:
|
|
|
|
| 466 |
if st.session_state.model_loaded and check_model_health():
|
| 467 |
st.subheader("Options d'analyse")
|
| 468 |
analysis_type = st.selectbox(
|
|
|
|
| 483 |
)
|
| 484 |
|
| 485 |
if st.button("🔍 Analyser l'image", type="primary", key="analyze_image_button"):
|
| 486 |
+
final_prompt = custom_prompt_input.strip()
|
| 487 |
+
if not final_prompt:
|
|
|
|
| 488 |
if analysis_type.startswith("Diagnostic complet"):
|
| 489 |
final_prompt = """Analyse cette image de plante et fournis un diagnostic complet :
|
| 490 |
1. **État général de la plante :** Décris son apparence globale et sa vitalité.
|
|
|
|
| 494 |
5. **Recommandations de traitement :** Propose des solutions concrètes et adaptées.
|
| 495 |
6. **Conseils préventifs :** Donne des astuces pour éviter que le problème ne revienne.
|
| 496 |
|
| 497 |
+
Réponds de manière structurée et claire en français."""
|
| 498 |
elif analysis_type.startswith("Identification et diagnostic de maladies"):
|
| 499 |
final_prompt = """Diagnostique cette plante en te concentrant sur les maladies et parasites :
|
| 500 |
1. Identifie les symptômes visuels spécifiques aux maladies ou parasites.
|
|
|
|
| 503 |
4. Propose des traitements ciblés et des méthodes de lutte.
|
| 504 |
|
| 505 |
Réponds en français de manière structurée."""
|
| 506 |
+
else:
|
| 507 |
final_prompt = """Analyse cette plante et donne des conseils de soins détaillés :
|
| 508 |
1. État général de la plante : Évalue sa santé actuelle.
|
| 509 |
2. Besoins spécifiques : Précise ses besoins en eau, lumière, nutriments et substrat.
|
|
|
|
| 512 |
|
| 513 |
Réponds en français de manière structurée."""
|
| 514 |
|
|
|
|
| 515 |
analysis_result = analyze_image_multilingual(resized_image, prompt_text=final_prompt)
|
| 516 |
|
| 517 |
if analysis_result:
|
| 518 |
st.success("✅ Analyse terminée !")
|
| 519 |
st.markdown("### 📋 Résultats de l'analyse")
|
| 520 |
+
st.markdown(analysis_result)
|
| 521 |
else:
|
| 522 |
st.error("❌ Échec de l'analyse de l'image.")
|
| 523 |
else:
|
|
|
|
| 527 |
st.header(t("text_analysis_title"))
|
| 528 |
st.markdown(t("text_analysis_desc"))
|
| 529 |
|
|
|
|
| 530 |
text_description_input = st.text_area(
|
| 531 |
t("enter_description"),
|
| 532 |
height=200,
|
| 533 |
placeholder="Décrivez ici les symptômes observés sur votre plante : feuilles jaunes, taches, flétrissement, présence d'insectes, etc."
|
| 534 |
)
|
| 535 |
|
|
|
|
| 536 |
if st.button("🔍 Analyser la description", type="primary", key="analyze_text_button"):
|
| 537 |
+
if text_description_input.strip():
|
| 538 |
if st.session_state.model_loaded and check_model_health():
|
|
|
|
| 539 |
analysis_result = analyze_text_multilingual(text_description_input)
|
| 540 |
|
| 541 |
if analysis_result:
|
|
|
|
| 552 |
with tab3: # Onglet Configuration & Informations
|
| 553 |
st.header(t("config_title"))
|
| 554 |
|
| 555 |
+
col1, col2 = st.columns(2)
|
| 556 |
|
| 557 |
+
with col1:
|
| 558 |
st.subheader("🔧 Informations Système")
|
|
|
|
|
|
|
| 559 |
try:
|
| 560 |
ram = psutil.virtual_memory()
|
| 561 |
st.write(f"**RAM Totale :** {ram.total / (1024**3):.1f} GB")
|
|
|
|
| 573 |
except Exception as e:
|
| 574 |
st.error(f"Erreur lors de la récupération des informations système : {e}")
|
| 575 |
|
| 576 |
+
with col2:
|
| 577 |
st.subheader("📊 Statistiques du Modèle IA")
|
| 578 |
|
| 579 |
if st.session_state.model_loaded and check_model_health():
|
|
|
|
| 593 |
with tab4: # Onglet À Propos
|
| 594 |
st.header(t("about_title"))
|
| 595 |
|
|
|
|
| 596 |
st.markdown("""
|
| 597 |
## 🌱 AgriLens AI : Votre Assistant d'Analyse de Plantes
|
| 598 |
|
|
|
|
| 607 |
|
| 608 |
### 🤖 Technologie Utilisée :
|
| 609 |
|
| 610 |
+
* **Modèle IA :** Google Gemma 3n E4B IT, un modèle multimodal performant pour l'analyse de plantes.
|
| 611 |
+
* **Bibliothèques :** `transformers`, `torch`, `streamlit`, `Pillow`, `psutil`, `requests`, `huggingface-hub`.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
|
| 613 |
### 📝 Comment Utiliser AgriLens AI :
|
| 614 |
|
|
|
|
| 620 |
|
| 621 |
### 🔧 Support et Optimisation :
|
| 622 |
|
| 623 |
+
* **Gestion des Ressources :** Le modèle nécessite une quantité significative de RAM et idéalement un GPU pour des performances optimales. L'application s'adapte à votre environnement (CPU/GPU).
|
| 624 |
+
* **Performance :** Les temps de réponse dépendent de votre matériel. Un GPU est fortement recommandé pour une expérience fluide.
|
|
|
|
| 625 |
|
| 626 |
---
|
| 627 |
|
|
|
|
| 629 |
""")
|
| 630 |
|
| 631 |
# --- Pied de page ---
|
| 632 |
+
st.divider()
|
| 633 |
st.markdown("""
|
| 634 |
<div style='text-align: center; color: #666;'>
|
| 635 |
🌱 AgriLens AI - Assistant d'Analyse de Plantes |
|