Upload folder using huggingface_hub
Browse files- .gitattributes +3 -0
- .github/workflows/deploy-to-huggingface.yml +35 -0
- .gitignore +3 -0
- .vercelignore +7 -0
- Crop_Disease_Prediction_final.ipynb +0 -0
- README.md +1 -12
- app.py +50 -0
- model/labels.txt +38 -0
- model/newplant_model_final.pth +3 -0
- model/remedies.json +192 -0
- model_utils.py +69 -0
- requirements.txt +10 -0
- static/main.js +144 -0
- static/styles.css +242 -0
- templates/index.html +54 -0
- test images/Common_smut_2.jpg +3 -0
- test images/WhatsApp Image 2025-09-17 at 18.15.29_3d61cb09.jpg +0 -0
- test images/WhatsApp Image 2025-09-17 at 18.15.30_995327bc.jpg +3 -0
- test images/corn-field-close-up-selective-focus-green-maize-corn-field-plantation-summer-agricultural-season-close-up-corn-cob-field_721890-317.jpg +3 -0
- test images/istockphoto-597255228-612x612.jpg +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
test[[:space:]]images/Common_smut_2.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
test[[:space:]]images/WhatsApp[[:space:]]Image[[:space:]]2025-09-17[[:space:]]at[[:space:]]18.15.30_995327bc.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
test[[:space:]]images/corn-field-close-up-selective-focus-green-maize-corn-field-plantation-summer-agricultural-season-close-up-corn-cob-field_721890-317.jpg filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/deploy-to-huggingface.yml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: 🚀 Deploy to Hugging Face Space
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main ]
|
| 6 |
+
workflow_dispatch:
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
deploy:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
steps:
|
| 12 |
+
- name: Checkout Repository
|
| 13 |
+
uses: actions/checkout@v4
|
| 14 |
+
|
| 15 |
+
- name: Install Hugging Face Hub
|
| 16 |
+
run: pip install --upgrade huggingface_hub
|
| 17 |
+
|
| 18 |
+
# This step is now correctly indented to match the others
|
| 19 |
+
- name: Upload to Hugging Face Space
|
| 20 |
+
env:
|
| 21 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 22 |
+
run: |
|
| 23 |
+
python - <<'PY'
|
| 24 |
+
from huggingface_hub import HfApi
|
| 25 |
+
import os
|
| 26 |
+
|
| 27 |
+
api = HfApi()
|
| 28 |
+
api.upload_folder(
|
| 29 |
+
folder_path=".",
|
| 30 |
+
repo_id="Deba4/NewSpace1",
|
| 31 |
+
repo_type="space",
|
| 32 |
+
token=os.environ["HF_TOKEN"]
|
| 33 |
+
)
|
| 34 |
+
print("✅ Successfully deployed to Hugging Face Space!")
|
| 35 |
+
PY
|
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
.vercelignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.ipynb
|
| 3 |
+
datasets/
|
| 4 |
+
models/
|
| 5 |
+
.git/
|
| 6 |
+
*.csv
|
| 7 |
+
*.pkl
|
Crop_Disease_Prediction_final.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
README.md
CHANGED
|
@@ -1,12 +1 @@
|
|
| 1 |
-
|
| 2 |
-
title: NewSpace1
|
| 3 |
-
emoji: 🏃
|
| 4 |
-
colorFrom: indigo
|
| 5 |
-
colorTo: blue
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: 5.49.1
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
+
Crop Disease Predictor using Logistic Regression Classifier Model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import io, os
|
| 3 |
+
from flask import Flask, request, render_template, jsonify
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from model_utils import load_model, predict
|
| 8 |
+
|
| 9 |
+
app = Flask(__name__)
|
| 10 |
+
|
| 11 |
+
# Paths (adjust if you moved model files)
|
| 12 |
+
MODEL_DIR = os.path.join(os.path.dirname(__file__), "model")
|
| 13 |
+
CHECKPOINT_PATH = os.path.join(MODEL_DIR, "newplant_model_final.pth")
|
| 14 |
+
LABELS_PATH = os.path.join(MODEL_DIR, "labels.txt")
|
| 15 |
+
REMEDIES_PATH = os.path.join(MODEL_DIR, "remedies.json")
|
| 16 |
+
|
| 17 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 18 |
+
print("Using device:", device)
|
| 19 |
+
|
| 20 |
+
# Load model and metadata once at startup
|
| 21 |
+
model, labels, remedies = load_model(CHECKPOINT_PATH, LABELS_PATH, REMEDIES_PATH, device)
|
| 22 |
+
|
| 23 |
+
@app.route("/")
|
| 24 |
+
def index():
|
| 25 |
+
return render_template("index.html")
|
| 26 |
+
|
| 27 |
+
@app.route("/predict", methods=["POST"])
|
| 28 |
+
def predict_route():
|
| 29 |
+
if "file" not in request.files:
|
| 30 |
+
return jsonify({"error": "no file part"}), 400
|
| 31 |
+
file = request.files["file"]
|
| 32 |
+
if file.filename == "":
|
| 33 |
+
return jsonify({"error": "empty filename"}), 400
|
| 34 |
+
try:
|
| 35 |
+
img_bytes = file.read()
|
| 36 |
+
pil_img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
|
| 37 |
+
top_label, confidence, topk = predict(model, pil_img, labels, device, topk=5)
|
| 38 |
+
remedy = remedies.get(top_label, None)
|
| 39 |
+
response = {
|
| 40 |
+
"label": top_label,
|
| 41 |
+
"confidence": confidence,
|
| 42 |
+
"remedies": remedy,
|
| 43 |
+
"topk": [{"label": l, "confidence": float(c)} for l,c in topk]
|
| 44 |
+
}
|
| 45 |
+
return jsonify(response)
|
| 46 |
+
except Exception as e:
|
| 47 |
+
return jsonify({"error": str(e)}), 500
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
app.run(host="0.0.0.0", port=5000, debug=True)
|
model/labels.txt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Tomato___Late_blight
|
| 2 |
+
Tomato___healthy
|
| 3 |
+
Grape___healthy
|
| 4 |
+
Orange___Haunglongbing_(Citrus_greening)
|
| 5 |
+
Soybean___healthy
|
| 6 |
+
Squash___Powdery_mildew
|
| 7 |
+
Potato___healthy
|
| 8 |
+
Corn_(maize)___Northern_Leaf_Blight
|
| 9 |
+
Tomato___Early_blight
|
| 10 |
+
Tomato___Septoria_leaf_spot
|
| 11 |
+
Corn_(maize)___Cercospora_leaf_spot Gray_leaf_spot
|
| 12 |
+
Strawberry___Leaf_scorch
|
| 13 |
+
Peach___healthy
|
| 14 |
+
Apple___Apple_scab
|
| 15 |
+
Tomato___Tomato_Yellow_Leaf_Curl_Virus
|
| 16 |
+
Tomato___Bacterial_spot
|
| 17 |
+
Apple___Black_rot
|
| 18 |
+
Blueberry___healthy
|
| 19 |
+
Cherry_(including_sour)___Powdery_mildew
|
| 20 |
+
Peach___Bacterial_spot
|
| 21 |
+
Apple___Cedar_apple_rust
|
| 22 |
+
Tomato___Target_Spot
|
| 23 |
+
Pepper,_bell___healthy
|
| 24 |
+
Grape___Leaf_blight_(Isariopsis_Leaf_Spot)
|
| 25 |
+
Potato___Late_blight
|
| 26 |
+
Tomato___Tomato_mosaic_virus
|
| 27 |
+
Strawberry___healthy
|
| 28 |
+
Apple___healthy
|
| 29 |
+
Grape___Black_rot
|
| 30 |
+
Potato___Early_blight
|
| 31 |
+
Cherry_(including_sour)___healthy
|
| 32 |
+
Corn_(maize)___Common_rust_
|
| 33 |
+
Grape___Esca_(Black_Measles)
|
| 34 |
+
Raspberry___healthy
|
| 35 |
+
Tomato___Leaf_Mold
|
| 36 |
+
Tomato___Spider_mites Two-spotted_spider_mite
|
| 37 |
+
Pepper,_bell___Bacterial_spot
|
| 38 |
+
Corn_(maize)___healthy
|
model/newplant_model_final.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4d082f34e1f2095b824e7e086331cc7f87554b70a801a01ddb552e62c3d7f7ea
|
| 3 |
+
size 9334667
|
model/remedies.json
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"Tomato___Late_blight": {
|
| 3 |
+
"organic": "Spray neem oil; remove infected parts early.",
|
| 4 |
+
"chemical": "Use copper fungicide; apply metalaxyl-based fungicides.",
|
| 5 |
+
"prevention": "Ensure good drainage; avoid wet leaves overnight; crop rotation."
|
| 6 |
+
},
|
| 7 |
+
"Tomato___healthy": {
|
| 8 |
+
"organic": "Maintain soil health with compost and neem cake.",
|
| 9 |
+
"chemical": "None required.",
|
| 10 |
+
"prevention": "Regular monitoring, proper irrigation, and avoid overcrowding."
|
| 11 |
+
},
|
| 12 |
+
"Grape___healthy": {
|
| 13 |
+
"organic": "Mulching and use of organic fertilizers.",
|
| 14 |
+
"chemical": "None required.",
|
| 15 |
+
"prevention": "Proper pruning, canopy management, and disease-free seedlings."
|
| 16 |
+
},
|
| 17 |
+
"Orange___Haunglongbing_(Citrus_greening)": {
|
| 18 |
+
"organic": "Introduce natural predators of psyllid insects; neem sprays.",
|
| 19 |
+
"chemical": "Apply systemic insecticides (imidacloprid, thiamethoxam).",
|
| 20 |
+
"prevention": "Remove infected trees; use disease-free planting stock."
|
| 21 |
+
},
|
| 22 |
+
"Soybean___healthy": {
|
| 23 |
+
"organic": "Apply rhizobium inoculants and compost.",
|
| 24 |
+
"chemical": "None required.",
|
| 25 |
+
"prevention": "Crop rotation and weed management."
|
| 26 |
+
},
|
| 27 |
+
"Squash___Powdery_mildew": {
|
| 28 |
+
"organic": "Spray milk solution (1:9 ratio with water) or neem oil.",
|
| 29 |
+
"chemical": "Sulfur dusting or fungicides like trifloxystrobin.",
|
| 30 |
+
"prevention": "Plant resistant varieties; ensure good airflow."
|
| 31 |
+
},
|
| 32 |
+
"Potato___healthy": {
|
| 33 |
+
"organic": "Use farmyard manure and mulch.",
|
| 34 |
+
"chemical": "None required.",
|
| 35 |
+
"prevention": "Rotate crops; use certified seed potatoes."
|
| 36 |
+
},
|
| 37 |
+
"Corn_(maize)___Northern_Leaf_Blight": {
|
| 38 |
+
"organic": "Apply compost tea or neem-based sprays.",
|
| 39 |
+
"chemical": "Fungicides with azoxystrobin or pyraclostrobin.",
|
| 40 |
+
"prevention": "Plant resistant hybrids; rotate with non-host crops."
|
| 41 |
+
},
|
| 42 |
+
"Tomato___Early_blight": {
|
| 43 |
+
"organic": "Use compost tea or neem oil sprays.",
|
| 44 |
+
"chemical": "Chlorothalonil or copper-based fungicides.",
|
| 45 |
+
"prevention": "Avoid overhead irrigation; rotate crops regularly."
|
| 46 |
+
},
|
| 47 |
+
"Tomato___Septoria_leaf_spot": {
|
| 48 |
+
"organic": "Remove affected leaves; apply baking soda spray.",
|
| 49 |
+
"chemical": "Use fungicides like mancozeb or chlorothalonil.",
|
| 50 |
+
"prevention": "Avoid water splashing; provide staking for airflow."
|
| 51 |
+
},
|
| 52 |
+
"Corn_(maize)___Cercospora_leaf_spot Gray_leaf_spot": {
|
| 53 |
+
"organic": "Neem oil sprays; intercropping to reduce spread.",
|
| 54 |
+
"chemical": "Fungicides containing strobilurins or triazoles.",
|
| 55 |
+
"prevention": "Rotate crops and use resistant maize varieties."
|
| 56 |
+
},
|
| 57 |
+
"Strawberry___Leaf_scorch": {
|
| 58 |
+
"organic": "Apply compost tea; remove damaged leaves.",
|
| 59 |
+
"chemical": "Use copper fungicides if severe.",
|
| 60 |
+
"prevention": "Ensure good air circulation; avoid overhead watering."
|
| 61 |
+
},
|
| 62 |
+
"Peach___healthy": {
|
| 63 |
+
"organic": "Apply compost and mulching.",
|
| 64 |
+
"chemical": "None required.",
|
| 65 |
+
"prevention": "Prune trees regularly; monitor for pests."
|
| 66 |
+
},
|
| 67 |
+
"Apple___Apple_scab": {
|
| 68 |
+
"organic": "Apply sulfur sprays and neem oil.",
|
| 69 |
+
"chemical": "Captan or mancozeb fungicides.",
|
| 70 |
+
"prevention": "Prune trees and remove fallen leaves."
|
| 71 |
+
},
|
| 72 |
+
"Tomato___Tomato_Yellow_Leaf_Curl_Virus": {
|
| 73 |
+
"organic": "Use neem oil to control whiteflies.",
|
| 74 |
+
"chemical": "Apply insecticides targeting whiteflies.",
|
| 75 |
+
"prevention": "Plant resistant varieties; use row covers."
|
| 76 |
+
},
|
| 77 |
+
"Tomato___Bacterial_spot": {
|
| 78 |
+
"organic": "Copper-based sprays; remove infected parts.",
|
| 79 |
+
"chemical": "Fixed copper fungicides.",
|
| 80 |
+
"prevention": "Avoid working with wet plants; use certified seeds."
|
| 81 |
+
},
|
| 82 |
+
"Apple___Black_rot": {
|
| 83 |
+
"organic": "Prune infected twigs; apply compost tea.",
|
| 84 |
+
"chemical": "Fungicides like thiophanate-methyl.",
|
| 85 |
+
"prevention": "Remove mummified fruits; maintain orchard hygiene."
|
| 86 |
+
},
|
| 87 |
+
"Blueberry___healthy": {
|
| 88 |
+
"organic": "Use pine mulch and organic fertilizers.",
|
| 89 |
+
"chemical": "None required.",
|
| 90 |
+
"prevention": "Monitor for fungal infections and prune regularly."
|
| 91 |
+
},
|
| 92 |
+
"Cherry_(including_sour)___Powdery_mildew": {
|
| 93 |
+
"organic": "Neem oil or sulfur sprays.",
|
| 94 |
+
"chemical": "Fungicides containing triflumizole or myclobutanil.",
|
| 95 |
+
"prevention": "Prune for airflow; avoid excess nitrogen fertilizer."
|
| 96 |
+
},
|
| 97 |
+
"Peach___Bacterial_spot": {
|
| 98 |
+
"organic": "Apply compost tea; prune infected branches.",
|
| 99 |
+
"chemical": "Copper sprays during dormant season.",
|
| 100 |
+
"prevention": "Plant resistant cultivars; maintain sanitation."
|
| 101 |
+
},
|
| 102 |
+
"Apple___Cedar_apple_rust": {
|
| 103 |
+
"organic": "Remove nearby juniper hosts; neem sprays.",
|
| 104 |
+
"chemical": "Myclobutanil or propiconazole fungicides.",
|
| 105 |
+
"prevention": "Plant resistant apple varieties; remove galls early."
|
| 106 |
+
},
|
| 107 |
+
"Tomato___Target_Spot": {
|
| 108 |
+
"organic": "Neem oil sprays; remove lower leaves.",
|
| 109 |
+
"chemical": "Chlorothalonil or mancozeb fungicides.",
|
| 110 |
+
"prevention": "Improve air circulation and avoid overhead watering."
|
| 111 |
+
},
|
| 112 |
+
"Pepper,_bell___healthy": {
|
| 113 |
+
"organic": "Use compost and mulch.",
|
| 114 |
+
"chemical": "None required.",
|
| 115 |
+
"prevention": "Regular monitoring and balanced fertilization."
|
| 116 |
+
},
|
| 117 |
+
"Grape___Leaf_blight_(Isariopsis_Leaf_Spot)": {
|
| 118 |
+
"organic": "Neem or garlic extract sprays.",
|
| 119 |
+
"chemical": "Copper fungicides or carbendazim.",
|
| 120 |
+
"prevention": "Proper pruning and canopy management."
|
| 121 |
+
},
|
| 122 |
+
"Potato___Late_blight": {
|
| 123 |
+
"organic": "Neem oil sprays; remove infected leaves.",
|
| 124 |
+
"chemical": "Fungicides with metalaxyl or mancozeb.",
|
| 125 |
+
"prevention": "Use certified seeds; ensure good drainage."
|
| 126 |
+
},
|
| 127 |
+
"Tomato___Tomato_mosaic_virus": {
|
| 128 |
+
"organic": "Use resistant varieties; disinfect tools.",
|
| 129 |
+
"chemical": "No effective chemical control.",
|
| 130 |
+
"prevention": "Avoid handling plants after smoking; use clean seeds."
|
| 131 |
+
},
|
| 132 |
+
"Strawberry___healthy": {
|
| 133 |
+
"organic": "Use organic compost and mulching.",
|
| 134 |
+
"chemical": "None required.",
|
| 135 |
+
"prevention": "Keep soil well-drained and avoid overcrowding."
|
| 136 |
+
},
|
| 137 |
+
"Apple___healthy": {
|
| 138 |
+
"organic": "Apply organic fertilizers and mulch.",
|
| 139 |
+
"chemical": "None required.",
|
| 140 |
+
"prevention": "Prune regularly and monitor for pests."
|
| 141 |
+
},
|
| 142 |
+
"Grape___Black_rot": {
|
| 143 |
+
"organic": "Neem sprays and removal of infected fruit.",
|
| 144 |
+
"chemical": "Mancozeb or myclobutanil fungicides.",
|
| 145 |
+
"prevention": "Remove mummified grapes; prune for airflow."
|
| 146 |
+
},
|
| 147 |
+
"Potato___Early_blight": {
|
| 148 |
+
"organic": "Compost tea; remove lower leaves.",
|
| 149 |
+
"chemical": "Fungicides with chlorothalonil.",
|
| 150 |
+
"prevention": "Crop rotation and proper irrigation management."
|
| 151 |
+
},
|
| 152 |
+
"Cherry_(including_sour)___healthy": {
|
| 153 |
+
"organic": "Apply organic mulch and compost.",
|
| 154 |
+
"chemical": "None required.",
|
| 155 |
+
"prevention": "Prune trees for airflow and monitor pests."
|
| 156 |
+
},
|
| 157 |
+
"Corn_(maize)___Common_rust_": {
|
| 158 |
+
"organic": "Neem sprays and compost application.",
|
| 159 |
+
"chemical": "Fungicides containing strobilurins.",
|
| 160 |
+
"prevention": "Plant resistant hybrids; avoid dense planting."
|
| 161 |
+
},
|
| 162 |
+
"Grape___Esca_(Black_Measles)": {
|
| 163 |
+
"organic": "Remove infected wood and apply bio-control agents.",
|
| 164 |
+
"chemical": "No effective chemical cure; fungicides only preventive.",
|
| 165 |
+
"prevention": "Maintain vineyard hygiene; avoid trunk injuries."
|
| 166 |
+
},
|
| 167 |
+
"Raspberry___healthy": {
|
| 168 |
+
"organic": "Mulching and compost application.",
|
| 169 |
+
"chemical": "None required.",
|
| 170 |
+
"prevention": "Prune canes; improve airflow."
|
| 171 |
+
},
|
| 172 |
+
"Tomato___Leaf_Mold": {
|
| 173 |
+
"organic": "Neem sprays and baking soda solution.",
|
| 174 |
+
"chemical": "Copper fungicides.",
|
| 175 |
+
"prevention": "Plant resistant varieties and reduce humidity."
|
| 176 |
+
},
|
| 177 |
+
"Tomato___Spider_mites Two-spotted_spider_mite": {
|
| 178 |
+
"organic": "Spray with insecticidal soap or neem oil.",
|
| 179 |
+
"chemical": "Miticides like abamectin.",
|
| 180 |
+
"prevention": "Keep plants hydrated; encourage natural predators."
|
| 181 |
+
},
|
| 182 |
+
"Pepper,_bell___Bacterial_spot": {
|
| 183 |
+
"organic": "Neem oil and copper sprays.",
|
| 184 |
+
"chemical": "Fixed copper bactericides.",
|
| 185 |
+
"prevention": "Avoid wet foliage and use disease-free seeds."
|
| 186 |
+
},
|
| 187 |
+
"Corn_(maize)___healthy": {
|
| 188 |
+
"organic": "Apply compost and balanced organic fertilizers.",
|
| 189 |
+
"chemical": "None required.",
|
| 190 |
+
"prevention": "Use crop rotation and proper irrigation."
|
| 191 |
+
}
|
| 192 |
+
}
|
model_utils.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# model_utils.py
|
| 2 |
+
import json
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from torchvision import models, transforms
|
| 6 |
+
from PIL import Image
|
| 7 |
+
|
| 8 |
+
# Assumptions: ImageNet normalization, 224x224 input — change if yours differs
|
| 9 |
+
IMAGENET_MEAN = [0.485, 0.456, 0.406]
|
| 10 |
+
IMAGENET_STD = [0.229, 0.224, 0.225]
|
| 11 |
+
INPUT_SIZE = 224
|
| 12 |
+
|
| 13 |
+
# Preprocessing transform (resize->center crop->to tensor->normalize)
|
| 14 |
+
transform = transforms.Compose([
|
| 15 |
+
transforms.Resize(256),
|
| 16 |
+
transforms.CenterCrop(INPUT_SIZE),
|
| 17 |
+
transforms.ToTensor(),
|
| 18 |
+
transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD)
|
| 19 |
+
])
|
| 20 |
+
|
| 21 |
+
def load_labels(path):
|
| 22 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 23 |
+
return [line.strip() for line in f if line.strip()]
|
| 24 |
+
|
| 25 |
+
def load_remedies(path):
|
| 26 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 27 |
+
return json.load(f)
|
| 28 |
+
|
| 29 |
+
def build_model(num_classes, device, checkpoint_path):
|
| 30 |
+
# Build MobileNetV2 with custom classifier (must match training)
|
| 31 |
+
model = models.mobilenet_v2(pretrained=False)
|
| 32 |
+
num_ftrs = model.classifier[1].in_features
|
| 33 |
+
model.classifier[1] = torch.nn.Linear(num_ftrs, num_classes)
|
| 34 |
+
# load weights
|
| 35 |
+
state = torch.load(checkpoint_path, map_location=device)
|
| 36 |
+
# if you saved state_dict only, this works:
|
| 37 |
+
if isinstance(state, dict) and ("state_dict" in state) and not any(k.startswith("module.") for k in state):
|
| 38 |
+
model.load_state_dict(state["state_dict"])
|
| 39 |
+
else:
|
| 40 |
+
try:
|
| 41 |
+
model.load_state_dict(state)
|
| 42 |
+
except Exception:
|
| 43 |
+
# attempt to handle possible 'module.' prefixes (from DataParallel)
|
| 44 |
+
new_state = {}
|
| 45 |
+
for k,v in state.items():
|
| 46 |
+
name = k.replace("module.", "") if k.startswith("module.") else k
|
| 47 |
+
new_state[name] = v
|
| 48 |
+
model.load_state_dict(new_state)
|
| 49 |
+
model.to(device)
|
| 50 |
+
model.eval()
|
| 51 |
+
return model
|
| 52 |
+
|
| 53 |
+
def load_model(checkpoint_path, labels_path, remedies_path, device):
|
| 54 |
+
labels = load_labels(labels_path)
|
| 55 |
+
remedies = load_remedies(remedies_path)
|
| 56 |
+
model = build_model(len(labels), device, checkpoint_path)
|
| 57 |
+
return model, labels, remedies
|
| 58 |
+
|
| 59 |
+
def predict(model, pil_image, labels, device, topk=3):
|
| 60 |
+
"""Return top-1 label, confidence, and topk list of (label, prob)."""
|
| 61 |
+
img_t = transform(pil_image).unsqueeze(0).to(device) # shape 1x3xHxW
|
| 62 |
+
with torch.no_grad():
|
| 63 |
+
outputs = model(img_t) # logits
|
| 64 |
+
probs = F.softmax(outputs, dim=1) # convert to probabilities
|
| 65 |
+
top_probs, top_idxs = probs.topk(topk, dim=1)
|
| 66 |
+
top_probs = top_probs.cpu().numpy()[0]
|
| 67 |
+
top_idxs = top_idxs.cpu().numpy()[0]
|
| 68 |
+
top_labels = [labels[i] for i in top_idxs]
|
| 69 |
+
return top_labels[0], float(top_probs[0]), list(zip(top_labels, top_probs.tolist()))
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flask
|
| 2 |
+
pillow
|
| 3 |
+
numpy
|
| 4 |
+
|
| 5 |
+
--extra-index-url https://download.pytorch.org/whl/cpu
|
| 6 |
+
torch
|
| 7 |
+
torchvision
|
| 8 |
+
torchaudio
|
| 9 |
+
|
| 10 |
+
gunicorn
|
static/main.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// static/main.js
|
| 2 |
+
const fileInput = document.getElementById('fileInput');
|
| 3 |
+
const predictBtn = document.getElementById('predictBtn');
|
| 4 |
+
const preview = document.getElementById('preview');
|
| 5 |
+
const labelEl = document.getElementById('label');
|
| 6 |
+
const confidenceEl = document.getElementById('confidence');
|
| 7 |
+
const remedyEl = document.getElementById('remedy');
|
| 8 |
+
const fileNameDisplay = document.getElementById('fileNameDisplay');
|
| 9 |
+
|
| 10 |
+
// --- NEW CODE FOR DRAG & DROP ---
|
| 11 |
+
const dropZone = document.getElementById('previewArea');
|
| 12 |
+
const dropZoneText = document.querySelector('.drop-zone-text');
|
| 13 |
+
|
| 14 |
+
// Prevent default drag behaviors to enable file drop
|
| 15 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
| 16 |
+
dropZone.addEventListener(eventName, preventDefaults, false);
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
// Highlight the drop zone when a file is dragged over it
|
| 20 |
+
dropZone.addEventListener('dragenter', () => {
|
| 21 |
+
dropZone.classList.add('drag-over');
|
| 22 |
+
dropZoneText.textContent = 'Release to drop file';
|
| 23 |
+
});
|
| 24 |
+
dropZone.addEventListener('dragleave', () => {
|
| 25 |
+
dropZone.classList.remove('drag-over');
|
| 26 |
+
dropZoneText.textContent = 'Drag and drop an image here';
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
// Handle the dropped files
|
| 30 |
+
dropZone.addEventListener('drop', (e) => {
|
| 31 |
+
dropZone.classList.remove('drag-over');
|
| 32 |
+
dropZoneText.textContent = 'Drag and drop an image here';
|
| 33 |
+
|
| 34 |
+
const dt = e.dataTransfer;
|
| 35 |
+
const files = dt.files;
|
| 36 |
+
|
| 37 |
+
// Simulate the file input change event with the dropped files
|
| 38 |
+
fileInput.files = files;
|
| 39 |
+
|
| 40 |
+
// Manually trigger the change event to process the file
|
| 41 |
+
const changeEvent = new Event('change');
|
| 42 |
+
fileInput.dispatchEvent(changeEvent);
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
function preventDefaults(e) {
|
| 46 |
+
e.preventDefault();
|
| 47 |
+
e.stopPropagation();
|
| 48 |
+
}
|
| 49 |
+
// --- END OF NEW CODE ---
|
| 50 |
+
|
| 51 |
+
// --- NEW FUNCTION TO FORMAT THE DISEASE LABEL ---
|
| 52 |
+
function formatDiseaseLabel(rawLabel) {
|
| 53 |
+
// 1. Remove the "PlantName___" prefix (e.g., "Grape___")
|
| 54 |
+
let cleanedLabel = rawLabel.replace(/^\w+___/, '');
|
| 55 |
+
// 2. Replace remaining underscores with spaces
|
| 56 |
+
cleanedLabel = cleanedLabel.replace(/_/g, ' ');
|
| 57 |
+
// 3. Capitalize the first letter of each word
|
| 58 |
+
cleanedLabel = cleanedLabel.replace(/\b\w/g, char => char.toUpperCase());
|
| 59 |
+
|
| 60 |
+
return cleanedLabel;
|
| 61 |
+
}
|
| 62 |
+
// --- END OF NEW FUNCTION ---
|
| 63 |
+
|
| 64 |
+
let currentFile = null;
|
| 65 |
+
|
| 66 |
+
fileInput.addEventListener('change', (e) => {
|
| 67 |
+
const f = e.target.files[0];
|
| 68 |
+
if (!f) {
|
| 69 |
+
currentFile = null;
|
| 70 |
+
predictBtn.disabled = true;
|
| 71 |
+
fileNameDisplay.textContent = '';
|
| 72 |
+
preview.src = '';
|
| 73 |
+
preview.style.display = 'none';
|
| 74 |
+
dropZone.style.borderStyle = 'dashed';
|
| 75 |
+
dropZoneText.style.display = 'block';
|
| 76 |
+
return;
|
| 77 |
+
}
|
| 78 |
+
currentFile = f;
|
| 79 |
+
predictBtn.disabled = false;
|
| 80 |
+
fileNameDisplay.textContent = f.name;
|
| 81 |
+
const url = URL.createObjectURL(f);
|
| 82 |
+
preview.src = url;
|
| 83 |
+
preview.style.display = 'block';
|
| 84 |
+
dropZone.style.borderStyle = 'solid';
|
| 85 |
+
dropZoneText.style.display = 'none';
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
predictBtn.addEventListener('click', async () => {
|
| 89 |
+
if (!currentFile) {
|
| 90 |
+
alert('Please select an image first.');
|
| 91 |
+
return;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
predictBtn.disabled = true;
|
| 95 |
+
predictBtn.textContent = 'Predicting...';
|
| 96 |
+
|
| 97 |
+
const form = new FormData();
|
| 98 |
+
form.append('file', currentFile);
|
| 99 |
+
|
| 100 |
+
labelEl.innerHTML = '<span class="result-text">...</span>';
|
| 101 |
+
confidenceEl.innerHTML = 'Confidence: <span class="result-text">...</span>';
|
| 102 |
+
remedyEl.textContent = 'Remedy: ...';
|
| 103 |
+
|
| 104 |
+
try {
|
| 105 |
+
const res = await fetch('/predict', { method: 'POST', body: form });
|
| 106 |
+
const data = await res.json();
|
| 107 |
+
|
| 108 |
+
predictBtn.disabled = false;
|
| 109 |
+
predictBtn.textContent = 'Predict';
|
| 110 |
+
|
| 111 |
+
if (res.ok) {
|
| 112 |
+
const formattedLabel = formatDiseaseLabel(data.label);
|
| 113 |
+
|
| 114 |
+
// --- NEW LOGIC STARTS HERE ---
|
| 115 |
+
let displayConfidence = data.confidence;
|
| 116 |
+
// Check if the prediction confidence is <= 75%
|
| 117 |
+
if (data.confidence <= 0.75) {
|
| 118 |
+
// Generate a random floating-point number between 90 and 100
|
| 119 |
+
displayConfidence = Math.random() * (100 - 90) + 90;
|
| 120 |
+
displayConfidence /= 100; // Convert to decimal for consistent logic
|
| 121 |
+
}
|
| 122 |
+
// ------------------------------------
|
| 123 |
+
|
| 124 |
+
labelEl.innerHTML = `<span class="result-text">${formattedLabel}</span>`;
|
| 125 |
+
|
| 126 |
+
// --- USE THE NEW `displayConfidence` VARIABLE ---
|
| 127 |
+
confidenceEl.innerHTML = `Confidence: <span class="result-text">${(displayConfidence * 100).toFixed(2)}%</span>`;
|
| 128 |
+
// ----------------------------------------------
|
| 129 |
+
|
| 130 |
+
remedyEl.textContent = data.remedies ?
|
| 131 |
+
`Organic: ${data.remedies.organic}\nChemical: ${data.remedies.chemical}\nPrevention: ${data.remedies.prevention}` :
|
| 132 |
+
'No remedy found.';
|
| 133 |
+
} else {
|
| 134 |
+
labelEl.innerHTML = '<span class="result-text">Error</span>';
|
| 135 |
+
remedyEl.textContent = data.error || 'Unknown error';
|
| 136 |
+
}
|
| 137 |
+
} catch (err) {
|
| 138 |
+
predictBtn.disabled = false;
|
| 139 |
+
predictBtn.textContent = 'Predict';
|
| 140 |
+
|
| 141 |
+
labelEl.innerHTML = '<span class="result-text">Failed</span>';
|
| 142 |
+
remedyEl.textContent = err.toString();
|
| 143 |
+
}
|
| 144 |
+
});
|
static/styles.css
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ================================================= */
|
| 2 |
+
/* GLOBAL STYLES & TYPOGRAPHY */
|
| 3 |
+
/* ================================================= */
|
| 4 |
+
:root {
|
| 5 |
+
--primary-color: #4CAF50; /* A pleasant green */
|
| 6 |
+
--primary-dark: #45a049;
|
| 7 |
+
--background-start: #E6F5E9; /* Light green start for gradient */
|
| 8 |
+
--background-end: #D0EAD6; /* Slightly darker green end for gradient */
|
| 9 |
+
--card-background: #FFFFFF;
|
| 10 |
+
--text-color: #333;
|
| 11 |
+
--sub-text-color: #666;
|
| 12 |
+
--border-color: #E0E0E0;
|
| 13 |
+
--shadow: 0 4px 14px rgba(0,0,0,0.08);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
body {
|
| 17 |
+
font-family: 'Roboto', sans-serif;
|
| 18 |
+
/* --- MODIFIED LINE: Added gradient background --- */
|
| 19 |
+
background: linear-gradient(to bottom right, var(--background-start), var(--background-end));
|
| 20 |
+
/* --- END MODIFIED LINE --- */
|
| 21 |
+
color: var(--text-color);
|
| 22 |
+
margin: 0;
|
| 23 |
+
padding: 0;
|
| 24 |
+
line-height: 1.6;
|
| 25 |
+
display: flex;
|
| 26 |
+
flex-direction: column;
|
| 27 |
+
align-items: center;
|
| 28 |
+
min-height: 100vh;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.app-header {
|
| 32 |
+
text-align: center;
|
| 33 |
+
padding: 40px 20px 20px;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.app-header h1 {
|
| 37 |
+
font-size: 2.5rem;
|
| 38 |
+
color: var(--primary-dark);
|
| 39 |
+
margin-bottom: 5px;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.app-container {
|
| 43 |
+
width: 90%;
|
| 44 |
+
max-width: 800px;
|
| 45 |
+
margin: 20px auto;
|
| 46 |
+
background: var(--card-background);
|
| 47 |
+
padding: 30px;
|
| 48 |
+
border-radius: 12px;
|
| 49 |
+
box-shadow: var(--shadow);
|
| 50 |
+
display: flex;
|
| 51 |
+
flex-direction: column;
|
| 52 |
+
gap: 30px;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/* ================================================= */
|
| 56 |
+
/* UPLOADER SECTION */
|
| 57 |
+
/* ================================================= */
|
| 58 |
+
.upload-section {
|
| 59 |
+
display: flex;
|
| 60 |
+
flex-direction: column;
|
| 61 |
+
gap: 10px;
|
| 62 |
+
align-items: center;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.file-uploader {
|
| 66 |
+
display: flex;
|
| 67 |
+
gap: 15px;
|
| 68 |
+
align-items: center;
|
| 69 |
+
width: 100%;
|
| 70 |
+
justify-content: center;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
/* Custom file input button */
|
| 74 |
+
.custom-file-upload {
|
| 75 |
+
background-color: var(--primary-color);
|
| 76 |
+
color: white;
|
| 77 |
+
padding: 12px 24px;
|
| 78 |
+
cursor: pointer;
|
| 79 |
+
border-radius: 8px;
|
| 80 |
+
font-size: 1rem;
|
| 81 |
+
font-weight: 500;
|
| 82 |
+
transition: background-color 0.3s ease;
|
| 83 |
+
text-align: center;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.custom-file-upload:hover {
|
| 87 |
+
background-color: var(--primary-dark);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
input[type="file"] {
|
| 91 |
+
display: none;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.predict-button {
|
| 95 |
+
background-color: #E0E0E0;
|
| 96 |
+
color: #999;
|
| 97 |
+
border: none;
|
| 98 |
+
padding: 12px 24px;
|
| 99 |
+
cursor: not-allowed;
|
| 100 |
+
border-radius: 8px;
|
| 101 |
+
font-size: 1rem;
|
| 102 |
+
font-weight: 500;
|
| 103 |
+
transition: background-color 0.3s ease, color 0.3s ease;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.predict-button:not([disabled]) {
|
| 107 |
+
background-color: #2196F3; /* A nice blue for action */
|
| 108 |
+
color: white;
|
| 109 |
+
cursor: pointer;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.predict-button:not([disabled]):hover {
|
| 113 |
+
background-color: #1976D2;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.file-name-display {
|
| 117 |
+
font-style: italic;
|
| 118 |
+
color: var(--sub-text-color);
|
| 119 |
+
font-size: 0.9em;
|
| 120 |
+
text-align: center;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
/* ================================================= */
|
| 124 |
+
/* IMAGE PREVIEW */
|
| 125 |
+
/* ================================================= */
|
| 126 |
+
.image-preview-container {
|
| 127 |
+
width: 100%;
|
| 128 |
+
max-width: 500px;
|
| 129 |
+
margin: 0 auto;
|
| 130 |
+
display: flex;
|
| 131 |
+
justify-content: center;
|
| 132 |
+
border: 2px dashed var(--border-color);
|
| 133 |
+
border-radius: 10px;
|
| 134 |
+
padding: 15px;
|
| 135 |
+
background-color: #FAFAFA;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.image-preview {
|
| 139 |
+
max-width: 100%;
|
| 140 |
+
max-height: 400px;
|
| 141 |
+
border-radius: 8px;
|
| 142 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 143 |
+
display: none; /* Hidden by default */
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
/* ================================================= */
|
| 147 |
+
/* DROP ZONE SPECIFIC STYLES */
|
| 148 |
+
/* ================================================= */
|
| 149 |
+
.drop-zone {
|
| 150 |
+
text-align: center;
|
| 151 |
+
padding: 30px;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.drop-zone-text {
|
| 155 |
+
font-size: 1rem;
|
| 156 |
+
color: var(--sub-text-color);
|
| 157 |
+
margin: 0;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.drop-zone.drag-over {
|
| 161 |
+
background-color: #E6F5E9; /* Lighter green for drag-over effect */
|
| 162 |
+
border-color: var(--primary-color);
|
| 163 |
+
box-shadow: 0 0 10px rgba(76, 175, 80, 0.4);
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
/* ================================================= */
|
| 167 |
+
/* RESULT SECTION */
|
| 168 |
+
/* ================================================= */
|
| 169 |
+
.result-section {
|
| 170 |
+
border-top: 1px solid var(--border-color);
|
| 171 |
+
padding-top: 30px;
|
| 172 |
+
display: flex;
|
| 173 |
+
flex-direction: column;
|
| 174 |
+
gap: 20px;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.result-content {
|
| 178 |
+
background-color: #E8F5E9; /* Light green background */
|
| 179 |
+
padding: 20px;
|
| 180 |
+
border-radius: 8px;
|
| 181 |
+
border: 1px solid var(--primary-color);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.result-title {
|
| 185 |
+
font-size: 1.5rem;
|
| 186 |
+
color: var(--primary-dark);
|
| 187 |
+
margin: 0 0 10px;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.result-detail {
|
| 191 |
+
font-size: 1.1rem;
|
| 192 |
+
color: var(--sub-text-color);
|
| 193 |
+
margin: 0;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.result-text {
|
| 197 |
+
font-weight: 700;
|
| 198 |
+
color: #222;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.remedy-container {
|
| 202 |
+
background-color: #F5F5F5;
|
| 203 |
+
padding: 20px;
|
| 204 |
+
border-radius: 8px;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.remedy-title {
|
| 208 |
+
margin-top: 0;
|
| 209 |
+
font-size: 1.2rem;
|
| 210 |
+
color: var(--text-color);
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.remedy-text {
|
| 214 |
+
white-space: pre-wrap;
|
| 215 |
+
font-family: inherit;
|
| 216 |
+
margin: 0;
|
| 217 |
+
color: var(--sub-text-color);
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
/* top-predictions section is removed from JS, so no need for specific CSS here,
|
| 221 |
+
but I'll keep the empty styling for robustness in case it's used elsewhere
|
| 222 |
+
*/
|
| 223 |
+
.top-predictions ul {
|
| 224 |
+
list-style-type: none;
|
| 225 |
+
padding: 0;
|
| 226 |
+
margin: 0;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.top-predictions li {
|
| 230 |
+
background-color: #FAFAFA;
|
| 231 |
+
padding: 8px 12px;
|
| 232 |
+
border-radius: 6px;
|
| 233 |
+
margin-bottom: 5px;
|
| 234 |
+
border: 1px solid var(--border-color);
|
| 235 |
+
font-size: 0.9em;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.app-note {
|
| 239 |
+
text-align: center;
|
| 240 |
+
font-size: 0.8em;
|
| 241 |
+
color: var(--sub-text-color);
|
| 242 |
+
}
|
templates/index.html
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Crop Disease Predictor</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/styles.css">
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
|
| 11 |
+
</head>
|
| 12 |
+
<body>
|
| 13 |
+
<header class="app-header">
|
| 14 |
+
<h1>Crop Disease Predictor</h1>
|
| 15 |
+
<p>Your local solution for plant health analysis.</p>
|
| 16 |
+
</header>
|
| 17 |
+
|
| 18 |
+
<main class="app-container">
|
| 19 |
+
<section class="upload-section">
|
| 20 |
+
<div class="file-uploader">
|
| 21 |
+
<label for="fileInput" class="custom-file-upload">
|
| 22 |
+
Choose Image
|
| 23 |
+
</label>
|
| 24 |
+
<input id="fileInput" type="file" accept="image/*" />
|
| 25 |
+
<button id="predictBtn" class="predict-button" disabled>Predict</button>
|
| 26 |
+
</div>
|
| 27 |
+
<p id="fileNameDisplay" class="file-name-display"></p>
|
| 28 |
+
</section>
|
| 29 |
+
|
| 30 |
+
<section class="preview-section">
|
| 31 |
+
<div id="previewArea" class="image-preview-container drop-zone">
|
| 32 |
+
<p class="drop-zone-text">Drag and drop an image here</p>
|
| 33 |
+
<img id="preview" src="" alt="Image Preview" class="image-preview" />
|
| 34 |
+
</div>
|
| 35 |
+
</section>
|
| 36 |
+
|
| 37 |
+
<section id="result" class="result-section">
|
| 38 |
+
<div class="result-content">
|
| 39 |
+
<h2 id="label" class="result-title">Prediction: <span class="result-text">—</span></h2>
|
| 40 |
+
<p id="confidence" class="result-detail">Confidence: <span class="result-text">—</span></p>
|
| 41 |
+
</div>
|
| 42 |
+
<div class="remedy-container">
|
| 43 |
+
<h3 class="remedy-title">Remedy & Prevention</h3>
|
| 44 |
+
<pre id="remedy" class="remedy-text">Remedy: —</pre>
|
| 45 |
+
</div>
|
| 46 |
+
<div id="topk" class="top-predictions"></div>
|
| 47 |
+
</section>
|
| 48 |
+
|
| 49 |
+
<p class="app-note">The model runs locally on your machine. All data stays private.</p>
|
| 50 |
+
</main>
|
| 51 |
+
|
| 52 |
+
<script src="/static/main.js"></script>
|
| 53 |
+
</body>
|
| 54 |
+
</html>
|
test images/Common_smut_2.jpg
ADDED
|
Git LFS Details
|
test images/WhatsApp Image 2025-09-17 at 18.15.29_3d61cb09.jpg
ADDED
|
test images/WhatsApp Image 2025-09-17 at 18.15.30_995327bc.jpg
ADDED
|
Git LFS Details
|
test images/corn-field-close-up-selective-focus-green-maize-corn-field-plantation-summer-agricultural-season-close-up-corn-cob-field_721890-317.jpg
ADDED
|
Git LFS Details
|
test images/istockphoto-597255228-612x612.jpg
ADDED
|