Goubaa's picture
Update app.py
9abbffb verified
Raw
History Blame Contribute Delete
8.2 kB
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import transforms
from PIL import Image
import gradio as gr
# ==========================
# 1) Device
# ==========================
device = torch.device("cpu") # Hugging Face = CPU par défaut
# ==========================
# 2) Définition du modèle (PlantCNN) EXACTEMENT comme à l'entraînement
# ==========================
class PlantCNN(nn.Module):
def __init__(self, num_classes):
super(PlantCNN, self).__init__()
# 🧩 Bloc 1
self.conv_block1 = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=0),
nn.ELU(),
nn.Conv2d(32, 32, kernel_size=3, padding=0),
nn.ELU(),
nn.MaxPool2d(2)
)
# 🧩 Bloc 2
self.conv_block2 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, padding=0),
nn.ELU(),
nn.Conv2d(64, 64, kernel_size=3, padding=0),
nn.ELU(),
nn.MaxPool2d(2)
)
# 🧩 Bloc 3
self.conv_block3 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, padding=0),
nn.ELU(),
nn.Conv2d(128, 128, kernel_size=3, padding=0),
nn.ELU(),
nn.MaxPool2d(2)
)
# 🔁 Global Average Pooling
self.gap = nn.AdaptiveAvgPool2d((1, 1))
# 🧱 Dense layers
self.fc1 = nn.Sequential(
nn.Linear(128, 256),
nn.ELU(),
nn.BatchNorm1d(256),
nn.Dropout(0.2)
)
self.fc2 = nn.Sequential(
nn.Linear(256, 128),
nn.ELU(),
nn.BatchNorm1d(128),
nn.Dropout(0.2)
)
self.fc3 = nn.Sequential(
nn.Linear(128, 64),
nn.ELU(),
nn.BatchNorm1d(64),
nn.Dropout(0.2)
)
self.out = nn.Linear(64, num_classes) # CrossEntropyLoss fait softmax
def forward(self, x):
x = self.conv_block1(x)
x = self.conv_block2(x)
x = self.conv_block3(x)
x = self.gap(x) # [B, 128, 1, 1]
x = torch.flatten(x, 1) # [B, 128]
x = self.fc1(x)
x = self.fc2(x)
x = self.fc3(x)
x = self.out(x)
return x
# ==========================
# 3) Classes (dans le même ordre que train_dataset.classes)
# ==========================
class_names = [
"Apple___Apple_scab",
"Apple___Black_rot",
"Apple___Cedar_apple_rust",
"Apple___healthy",
"Blueberry___healthy",
"Cherry_(including_sour)___Powdery_mildew",
"Cherry_(including_sour)___healthy",
"Corn_(maize)___Cercospora_leaf_spot Gray_leaf_spot",
"Corn_(maize)___Common_rust_",
"Corn_(maize)___Northern_Leaf_Blight",
"Corn_(maize)___healthy",
"Grape___Black_rot",
"Grape___Esca_(Black_Measles)",
"Grape___Leaf_blight_(Isariopsis_Leaf_Spot)",
"Grape___healthy",
"Orange___Haunglongbing_(Citrus_greening)",
"Peach___Bacterial_spot",
"Peach___healthy",
"Pepper,_bell___Bacterial_spot",
"Pepper,_bell___healthy",
"Potato___Early_blight",
"Potato___Late_blight",
"Potato___healthy",
"Raspberry___healthy",
"Soybean___healthy",
"Squash___Powdery_mildew",
"Strawberry___Leaf_scorch",
"Strawberry___healthy",
"Tomato___Bacterial_spot",
"Tomato___Early_blight",
"Tomato___Late_blight",
"Tomato___Leaf_Mold",
"Tomato___Septoria_leaf_spot",
"Tomato___Spider_mites Two-spotted_spider_mite",
"Tomato___Target_Spot",
"Tomato___Tomato_Yellow_Leaf_Curl_Virus",
"Tomato___Tomato_mosaic_virus",
"Tomato___healthy",
]
num_classes = len(class_names)
# ==========================
# 4) Chargement du modèle
# ==========================
MODEL_PATH = "best_model.pth" # ⚠️ mets exactement le même nom que le fichier uploadé
model = PlantCNN(num_classes=num_classes)
state_dict = torch.load(MODEL_PATH, map_location=device)
model.load_state_dict(state_dict)
model.to(device)
model.eval()
print("✅ Modèle PyTorch chargé sur CPU, prêt pour la prédiction.")
# ==========================
# 5) Transformations des images
# ==========================
image_size = 224
transform = transforms.Compose([
transforms.Resize((image_size, image_size)),
transforms.ToTensor(),
])
def prettify_class_name(raw_name: str) -> str:
# Transforme "Tomato___Early_blight" -> "Tomato – Early blight"
name = raw_name.replace("___", " – ")
name = name.replace("_", " ")
return name
# ==========================
# 6) Fonction de prédiction
# ==========================
def predict(image: Image.Image):
if image is None:
return "Merci d'uploader une image 🌸"
img = transform(image).unsqueeze(0).to(device) # [1, 3, H, W]
with torch.no_grad():
outputs = model(img)
probs = torch.softmax(outputs, dim=1)[0]
top_prob, top_idx = torch.max(probs, dim=0)
top_prob = float(top_prob.item())
top_idx = int(top_idx.item())
pred_class_raw = class_names[top_idx]
pred_class = prettify_class_name(pred_class_raw)
# Top 3 prédictions
topk = 3
top_probs, top_indices = torch.topk(probs, k=topk)
top_probs = top_probs.cpu().numpy()
top_indices = top_indices.cpu().numpy()
md = f"### 🌿 Résultat de la prédiction\n"
md += f"**Classe prédite :** `{pred_class}`\n\n"
md += f"**Confiance :** `{top_prob*100:.2f}%` 💖\n\n"
md += "---\n"
md += "### 🌈 Top 3 des classes probables\n"
for i in range(topk):
cls_raw = class_names[int(top_indices[i])]
cls_name = prettify_class_name(cls_raw)
cls_prob = float(top_probs[i])
md += f"- `{cls_name}` → **{cls_prob*100:.2f}%**\n"
md += "\n> 🍃 *Modèle entraîné sur PlantVillage (38 classes). À utiliser pour l’exploration, pas comme outil médical.*\n"
return md
# ==========================
# 7) CSS girly (injecté via HTML, compatible ancienne version Gradio)
# ==========================
custom_css = """
body {
background: #ffeef7;
}
#root, .gradio-container {
background: linear-gradient(135deg, #ffeef7 0%, #e4f9ff 100%) !important;
}
.gradio-container {
font-family: 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
}
h1, h2, h3 {
color: #d6478f !important;
}
button {
border-radius: 999px !important;
}
"""
# ==========================
# 8) Interface Gradio
# ==========================
with gr.Blocks() as demo:
# Injecter le CSS custom
gr.HTML(f"<style>{custom_css}</style>")
gr.Markdown(
"""
<div style="text-align:center; margin-bottom: 10px;">
<h1>🌸 Plant Disease Detector by Hanen 🌸</h1>
<p style="font-size:16px; color:#555;">
Upload une feuille de plante 🍃 et laisse ton modèle PyTorch super entraîné
deviner la maladie (ou si elle est healthy 💚).
</p>
</div>
"""
)
with gr.Row():
with gr.Column(scale=1):
image_input = gr.Image(
type="pil",
label="🌿 Upload une image de feuille",
height=320
)
gr.Markdown(
"""
<span style="font-size:14px; color:#666;">
👉 Utilise une image du dataset PlantVillage ou une photo claire d'une feuille sur fond neutre.<br>
Formats supportés : JPG, PNG.
</span>
"""
)
with gr.Column(scale=1):
output_md = gr.Markdown(
value="Le résultat apparaîtra ici 💖",
)
btn = gr.Button("✨ Analyser la feuille ✨")
btn.click(fn=predict, inputs=image_input, outputs=output_md)
gr.Markdown(
"""
<div style="text-align:center; font-size:13px; color:#777; margin-top:20px;">
Modèle : <b>PlantCNN (PyTorch)</b> – Accuracy test ≈ <b>98%</b> 🌟<br>
Déployé avec 💕 sur Hugging Face & Gradio.
</div>
"""
)
if __name__ == "__main__":
demo.launch()