marian522's picture
Update app.py
97ed0e0 verified
Raw
History Blame Contribute Delete
7.83 kB
from flask import Flask, request, jsonify
from flask_cors import CORS
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image
import io
import os
# ══════════════════════════════════════════════════════════════
# Flask App Setup
# ══════════════════════════════════════════════════════════════
app = Flask(__name__)
CORS(app) # Allow mobile app to connect
# ══════════════════════════════════════════════════════════════
# Configuration
# ══════════════════════════════════════════════════════════════
MODEL_PATH = "resnet18_rice_v2.pth" # ← your saved model
CLASS_NAMES = ["ClassA-Drought", "ClassB-PestInfestation", "ClassC-Healthy"]
THRESHOLD = 0.80 # below this = uncertain
IMG_SIZE = 224
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# ══════════════════════════════════════════════════════════════
# Load Model Once at Startup
# ══════════════════════════════════════════════════════════════
def load_model():
model = models.resnet18(weights=None)
model.fc = nn.Linear(model.fc.in_features, len(CLASS_NAMES))
checkpoint = torch.load(MODEL_PATH, map_location=device)
model.load_state_dict(checkpoint["model_state_dict"])
model = model.to(device)
model.eval()
print(f"βœ… Model loaded from {MODEL_PATH}")
print(f"βœ… Classes: {CLASS_NAMES}")
return model
model = load_model()
# ══════════════════════════════════════════════════════════════
# Image Transform
# ══════════════════════════════════════════════════════════════
transform = transforms.Compose([
transforms.Resize((IMG_SIZE, IMG_SIZE)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
# ══════════════════════════════════════════════════════════════
# Routes
# ══════════════════════════════════════════════════════════════
# Health check β€” test if API is running
@app.route("/", methods=["GET"])
def home():
return jsonify({
"status" : "running",
"message" : "Rice Stress Detection API",
"classes" : CLASS_NAMES,
"model" : "ResNet18 β€” 99.66% accuracy"
})
# Main prediction endpoint
@app.route("/predict", methods=["POST"])
def predict():
# ── Check if image was sent ─────────────────────────────
if "image" not in request.files:
return jsonify({
"error": "No image provided. Send image as form-data with key 'image'"
}), 400
file = request.files["image"]
# ── Check file is valid ─────────────────────────────────
if file.filename == "":
return jsonify({"error": "Empty filename"}), 400
allowed = {"jpg", "jpeg", "png", "bmp", "webp"}
ext = file.filename.rsplit(".", 1)[-1].lower()
if ext not in allowed:
return jsonify({"error": f"File type .{ext} not allowed. Use jpg/png"}), 400
try:
# ── Read and process image ──────────────────────────
image_bytes = file.read()
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
tensor = transform(image).unsqueeze(0).to(device)
# ── Run prediction ──────────────────────────────────
with torch.no_grad():
outputs = model(tensor)
probs = torch.softmax(outputs, dim=1)[0]
confidence, predicted = torch.max(probs, 0)
predicted_class = CLASS_NAMES[predicted.item()]
conf_value = confidence.item()
is_uncertain = conf_value < THRESHOLD
# ── Build response ──────────────────────────────────
all_probs = {
CLASS_NAMES[i]: round(probs[i].item() * 100, 2)
for i in range(len(CLASS_NAMES))
}
# Clean class name for display
display_name = predicted_class.replace("ClassA-", "")\
.replace("ClassB-", "")\
.replace("ClassC-", "")
response = {
"predicted_class" : predicted_class,
"display_name" : display_name,
"confidence" : round(conf_value * 100, 2),
"is_uncertain" : is_uncertain,
"all_probabilities": all_probs,
"recommendation" : get_recommendation(display_name, is_uncertain)
}
return jsonify(response), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
# ══════════════════════════════════════════════════════════════
# Recommendation Logic
# ══════════════════════════════════════════════════════════════
def get_recommendation(class_name, is_uncertain):
if is_uncertain:
return "Low confidence β€” recommend manual inspection by agronomist"
recommendations = {
"Drought": (
"Rice plant shows drought stress. "
"Recommended actions: increase irrigation frequency, "
"check soil moisture levels, consider mulching."
),
"PestInfestation": (
"Rice plant shows pest infestation signs. "
"Recommended actions: inspect leaves for insects, "
"apply appropriate pesticide, monitor surrounding plants."
),
"Healthy": (
"Rice plant appears healthy. "
"Continue regular monitoring and maintenance."
)
}
return recommendations.get(class_name, "No recommendation available")
# ══════════════════════════════════════════════════════════════
# Run Server
# ══════════════════════════════════════════════════════════════
if __name__ == "__main__":
port = int(os.environ.get("PORT", 7860))
print("🌾 Rice Stress Detection API starting...")
app.run(debug=False, host="0.0.0.0", port=port)