Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import torch | |
| import torch.nn as nn | |
| from torchvision import transforms, models | |
| from PIL import Image | |
| DEVICE = torch.device("cpu") | |
| MODEL_PATH = "plant_disease_mvp.pth" | |
| TREATMENTS = { | |
| "Apple___Apple_scab": ["Appliquer un fongicide à base de captane ou myclobutanil", "Ramasser et détruire les feuilles tombées", "Tailler pour améliorer la circulation d'air"], | |
| "Apple___Black_rot": ["Tailler et brûler les branches infectées", "Appliquer un fongicide à base de cuivre au printemps", "Désinfecter les outils de taille"], | |
| "Apple___Cedar_apple_rust": ["Utiliser un fongicide à base de myclobutanil", "Éviter de planter près des cèdres", "Traiter dès l'apparition des premiers symptômes"], | |
| "Apple___healthy": ["Plante saine — aucun traitement nécessaire", "Maintenir les bonnes pratiques d'irrigation", "Surveiller régulièrement les feuilles"], | |
| "Blueberry___healthy": ["Plante saine — aucun traitement nécessaire", "Maintenir un bon drainage du sol"], | |
| "Cherry_(including_sour)___Powdery_mildew": ["Appliquer du soufre en poudre ou fongicide soufré", "Améliorer la circulation de l'air autour de l'arbre", "Éviter l'excès d'azote dans les engrais"], | |
| "Cherry_(including_sour)___healthy": ["Plante saine — aucun traitement nécessaire", "Continuer les bonnes pratiques agricoles"], | |
| "Corn_(maize)___Cercospora_leaf_spot Gray_leaf_spot": ["Utiliser des variétés résistantes à la maladie", "Appliquer un fongicide à base de strobilurine", "Pratiquer la rotation des cultures"], | |
| "Corn_(maize)___Common_rust_": ["Appliquer un fongicide préventif dès les premiers signes", "Utiliser des semences résistantes", "Détruire les résidus de récolte infectés"], | |
| "Corn_(maize)___Northern_Leaf_Blight": ["Appliquer un fongicide systémique (propiconazole)", "Pratiquer la rotation des cultures", "Utiliser des hybrides résistants"], | |
| "Corn_(maize)___healthy": ["Plante saine — aucun traitement nécessaire", "Continuer les bonnes pratiques agricoles"], | |
| "Grape___Black_rot": ["Tailler les parties infectées et les brûler", "Appliquer un fongicide à base de mancozèbe", "Éliminer les baies momifiées sur la vigne"], | |
| "Grape___Esca_(Black_Measles)": ["Tailler les bois infectés en période sèche", "Protéger les plaies avec un produit cicatrisant", "Éviter le stress hydrique de la vigne"], | |
| "Grape___Leaf_blight_(Isariopsis_Leaf_Spot)": ["Appliquer un fongicide cuivrique", "Améliorer la ventilation du vignoble", "Éliminer les feuilles tombées infectées"], | |
| "Grape___healthy": ["Plante saine — aucun traitement nécessaire", "Continuer les bonnes pratiques viticoles"], | |
| "Orange___Haunglongbing_(Citrus_greening)": ["Maladie grave sans traitement curatif", "Arracher et détruire les arbres infectés immédiatement", "Contrôler le psylle asiatique (insecticide)"], | |
| "Peach___Bacterial_spot": ["Appliquer un bactéricide à base de cuivre", "Éviter les blessures sur les fruits et rameaux", "Tailler en période sèche et désinfecter les outils"], | |
| "Peach___healthy": ["Plante saine — aucun traitement nécessaire", "Continuer les bonnes pratiques agricoles"], | |
| "Pepper,_bell___Bacterial_spot": ["Appliquer un bactéricide cuivrique dès les premiers symptômes", "Éviter l'irrigation par aspersion", "Éliminer et détruire les plants très infectés"], | |
| "Pepper,_bell___healthy": ["Plante saine — aucun traitement nécessaire", "Maintenir un bon drainage du sol"], | |
| "Potato___Early_blight": ["Appliquer un fongicide à base de chlorothalonil", "Pratiquer la rotation des cultures (3 ans minimum)", "Éliminer les feuilles infectées du champ"], | |
| "Potato___Late_blight": ["Traitement urgent avec fongicide systémique (métalaxyl)", "Détruire immédiatement les plants très infectés", "Ne pas composter les tubercules infectés"], | |
| "Potato___healthy": ["Plante saine — aucun traitement nécessaire", "Surveiller régulièrement pour détecter tôt"], | |
| "Raspberry___healthy": ["Plante saine — aucun traitement nécessaire", "Continuer les bonnes pratiques agricoles"], | |
| "Soybean___healthy": ["Plante saine — aucun traitement nécessaire", "Maintenir la rotation des cultures"], | |
| "Squash___Powdery_mildew": ["Appliquer du bicarbonate de soude dilué (1%) en spray", "Utiliser un fongicide soufré si sévère", "Améliorer la circulation d'air entre les plants"], | |
| "Strawberry___Leaf_scorch": ["Appliquer un fongicide à base de captan", "Éviter l'excès d'humidité sur les feuilles", "Éliminer les feuilles infectées du champ"], | |
| "Strawberry___healthy": ["Plante saine — aucun traitement nécessaire", "Continuer les bonnes pratiques agricoles"], | |
| "Tomato___Bacterial_spot": ["Appliquer un bactéricide à base de cuivre", "Éviter l'arrosage sur les feuilles", "Éliminer et brûler les plants très infectés"], | |
| "Tomato___Early_blight": ["Appliquer un fongicide à base de chlorothalonil", "Éliminer les feuilles infectées en bas de la plante", "Éviter l'excès d'humidité au niveau du sol"], | |
| "Tomato___Late_blight": ["Traitement urgent avec fongicide systémique", "Détruire les plants très infectés immédiatement", "Éviter l'irrigation par aspersion"], | |
| "Tomato___Leaf_Mold": ["Améliorer la ventilation de la serre ou du champ", "Appliquer un fongicide à base de mancozèbe", "Réduire l'humidité relative autour des plants"], | |
| "Tomato___Septoria_leaf_spot": ["Appliquer un fongicide (chlorothalonil ou cuivre)", "Éviter d'arroser le feuillage directement", "Éliminer les feuilles infectées régulièrement"], | |
| "Tomato___Spider_mites Two-spotted_spider_mite": ["Appliquer un acaricide homologué", "Augmenter l'humidité autour des plants", "Introduire des prédateurs naturels (acariens utiles)"], | |
| "Tomato___Target_Spot": ["Appliquer un fongicide systémique (azoxystrobine)", "Éliminer les débris végétaux du sol", "Pratiquer la rotation des cultures"], | |
| "Tomato___Tomato_Yellow_Leaf_Curl_Virus": ["Maladie virale sans traitement curatif", "Contrôler les aleurodes (mouches blanches) vecteurs", "Arracher et détruire les plants infectés"], | |
| "Tomato___Tomato_mosaic_virus": ["Pas de traitement curatif disponible", "Désinfecter tous les outils de travail (eau de javel)", "Arracher et détruire les plants infectés"], | |
| "Tomato___healthy": ["Plante saine — aucun traitement nécessaire", "Continuer les bonnes pratiques agricoles", "Surveiller régulièrement les feuilles"], | |
| } | |
| def load_model(model_path): | |
| checkpoint = torch.load(model_path, map_location=DEVICE) | |
| class_names = checkpoint["class_names"] | |
| num_classes = checkpoint["num_classes"] | |
| model = models.mobilenet_v2(pretrained=False) | |
| model.classifier = nn.Sequential( | |
| nn.Dropout(p=0.3), | |
| nn.Linear(model.last_channel, num_classes) | |
| ) | |
| model.load_state_dict(checkpoint["model_state_dict"]) | |
| model.eval() | |
| return model, class_names | |
| model, CLASS_NAMES = load_model(MODEL_PATH) | |
| transform = transforms.Compose([ | |
| transforms.Resize((224, 224)), | |
| transforms.ToTensor(), | |
| transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) | |
| ]) | |
| def predict(image): | |
| if image is None: | |
| return "", "", "" | |
| img_tensor = transform(image).unsqueeze(0).to(DEVICE) | |
| with torch.no_grad(): | |
| outputs = model(img_tensor) | |
| probs = torch.softmax(outputs, dim=1) | |
| conf, idx = probs.max(1) | |
| pred_class = CLASS_NAMES[idx.item()] | |
| confidence = conf.item() * 100 | |
| parts = pred_class.split("___") | |
| plant = parts[0].replace("_", " ") | |
| disease = parts[1].replace("_", " ") if len(parts) > 1 else "Inconnue" | |
| is_healthy = "healthy" in pred_class.lower() | |
| status = "SAINE" if is_healthy else "MALADE" | |
| color = "#2d6a4f" if is_healthy else "#c1121f" | |
| bg_color = "#e8f5e9" if is_healthy else "#fdecea" | |
| icon = "✅" if is_healthy else "⚠️" | |
| conf_bar = int(confidence) | |
| result_html = f""" | |
| <div style="font-family:'Inter',sans-serif; padding:20px; border-radius:14px; | |
| background:{bg_color}; border-left:5px solid {color}; margin-bottom:6px;"> | |
| <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:14px;"> | |
| <div style="font-size:1.5em; font-weight:800; color:{color};">{icon} Plante {status}</div> | |
| </div> | |
| <div style="display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-bottom:14px;"> | |
| <div style="background:white; border-radius:10px; padding:12px;"> | |
| <div style="font-size:0.75em; color:#888; text-transform:uppercase; letter-spacing:1px; margin-bottom:4px;">🌱 Plante</div> | |
| <div style="font-size:1em; font-weight:700; color:#1b4332;">{plant}</div> | |
| </div> | |
| <div style="background:white; border-radius:10px; padding:12px;"> | |
| <div style="font-size:0.75em; color:#888; text-transform:uppercase; letter-spacing:1px; margin-bottom:4px;">🦠 Maladie</div> | |
| <div style="font-size:1em; font-weight:700; color:{color};">{disease}</div> | |
| </div> | |
| </div> | |
| <div style="background:white; border-radius:10px; padding:12px;"> | |
| <div style="display:flex; justify-content:space-between; margin-bottom:6px;"> | |
| <span style="font-size:0.8em; color:#888;">📊 Niveau de confiance</span> | |
| <span style="font-size:0.9em; font-weight:700; color:{color};">{confidence:.1f}%</span> | |
| </div> | |
| <div style="background:#e0e0e0; border-radius:20px; height:10px;"> | |
| <div style="background:{color}; width:{conf_bar}%; height:10px; border-radius:20px; transition:width 0.5s;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| steps = TREATMENTS.get(pred_class, ["Consulter un agronome local."]) | |
| steps_html = "" | |
| for i, step in enumerate(steps): | |
| urgent = "urgent" in step.lower() or "grave" in step.lower() or "arracher" in step.lower() | |
| step_bg = "#fff3cd" if urgent else "white" | |
| steps_html += f""" | |
| <div style="display:flex; align-items:flex-start; gap:12px; padding:10px; | |
| background:{step_bg}; border-radius:8px; margin-bottom:8px;"> | |
| <div style="background:#2d6a4f; color:white; border-radius:50%; min-width:26px; height:26px; | |
| display:flex; align-items:center; justify-content:center; font-size:0.8em; font-weight:700;">{i+1}</div> | |
| <div style="color:#1b4332; font-size:0.92em; line-height:1.5;">{step}</div> | |
| </div>""" | |
| treatment_html = f""" | |
| <div style="font-family:'Inter',sans-serif; padding:20px; border-radius:14px; | |
| background:#f8fffe; border:1.5px solid #95d5b2;"> | |
| <div style="font-size:1em; font-weight:700; color:#1b4332; margin-bottom:14px;"> | |
| 💊 Protocole de Traitement | |
| </div> | |
| {steps_html} | |
| </div> | |
| """ | |
| top3_probs, top3_idx = probs.topk(3, dim=1) | |
| top3_rows = "" | |
| medals = ["🥇", "🥈", "🥉"] | |
| colors = ["#2d6a4f", "#74c69d", "#b7e4c7"] | |
| for i in range(3): | |
| cls = CLASS_NAMES[top3_idx[0][i].item()] | |
| prob = top3_probs[0][i].item() * 100 | |
| p, d = cls.split("___") if "___" in cls else (cls, "—") | |
| p = p.replace("_", " ") | |
| d = d.replace("_", " ") | |
| w = int(prob) | |
| bold = "700" if i == 0 else "400" | |
| top3_rows += f""" | |
| <div style="margin-bottom:14px;"> | |
| <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:5px;"> | |
| <span style="font-size:0.88em; color:#1b4332; font-weight:{bold};"> | |
| {medals[i]} {p} — {d} | |
| </span> | |
| <span style="font-size:0.88em; font-weight:700; color:{colors[i]};">{prob:.1f}%</span> | |
| </div> | |
| <div style="background:#e8f5e9; border-radius:20px; height:8px;"> | |
| <div style="background:{colors[i]}; width:{w}%; height:8px; border-radius:20px;"></div> | |
| </div> | |
| </div>""" | |
| top3_html = f""" | |
| <div style="font-family:'Inter',sans-serif; padding:20px; border-radius:14px; | |
| background:#f8fffe; border:1.5px solid #95d5b2;"> | |
| <div style="font-size:1em; font-weight:700; color:#1b4332; margin-bottom:14px;"> | |
| 📊 Top 3 Prédictions | |
| </div> | |
| {top3_rows} | |
| </div> | |
| """ | |
| return result_html, treatment_html, top3_html | |
| css = """ | |
| body, .gradio-container { background: #f0f7f4 !important; } | |
| .gradio-container { max-width: 1050px !important; margin: auto !important; } | |
| footer { display: none !important; } | |
| button.primary { background: #2d6a4f !important; border-radius: 10px !important; font-size: 1em !important; } | |
| """ | |
| with gr.Blocks(css=css, theme=gr.themes.Base( | |
| primary_hue="green", | |
| font=gr.themes.GoogleFont("Inter") | |
| )) as app: | |
| gr.HTML(""" | |
| <div style="text-align:center; padding:32px 20px 16px; font-family:'Inter',sans-serif;"> | |
| <div style="font-size:2.8em; margin-bottom:10px;">🌿</div> | |
| <h1 style="color:#1b4332; font-size:1.9em; font-weight:800; margin:0 0 8px;"> | |
| Détection des Maladies des Plantes | |
| </h1> | |
| <p style="color:#52b788; font-size:0.95em; margin:0;"> | |
| Diagnostic intelligent par IA · MobileNetV2 · 96.59% de précision sur 38 classes | |
| </p> | |
| <div style="width:50px; height:3px; background:#2d6a4f; margin:14px auto 0; border-radius:2px;"></div> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.HTML('<p style="font-family:Inter; font-size:0.82em; color:#666; text-transform:uppercase; letter-spacing:1px; margin-bottom:6px;">📸 Image de la plante</p>') | |
| image_input = gr.Image(type="pil", label="", height=300) | |
| predict_btn = gr.Button("🔍 Analyser la plante", variant="primary", size="lg") | |
| gr.HTML('<p style="font-family:Inter; font-size:0.75em; color:#aaa; text-align:center; margin-top:6px;">Formats acceptés : JPG · PNG · WEBP</p>') | |
| with gr.Column(scale=1): | |
| result_out = gr.HTML() | |
| treatment_out = gr.HTML() | |
| top3_out = gr.HTML() | |
| predict_btn.click(fn=predict, inputs=image_input, outputs=[result_out, treatment_out, top3_out]) | |
| gr.HTML(""" | |
| <div style="text-align:center; padding:18px; font-family:'Inter',sans-serif; | |
| font-size:0.76em; color:#aaa; border-top:1px solid #e0e0e0; margin-top:10px;"> | |
| 🌍 Projet IA · AIMS Sénégal · 2025 · MobileNetV2 · PyTorch · Gradio | |
| · Dataset : PlantVillage (54 000+ images · 38 classes) | |
| </div> | |
| """) | |
| app.launch() |