Spaces:
Sleeping
Sleeping
File size: 8,102 Bytes
d0624ee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | """
FarmGuard Nigeria - Plant Disease Detection API
Using Google Gemini Vision for accurate real photo analysis
"""
import json
import logging
import os
import base64
from google import genai
from google.genai import types
from dotenv import load_dotenv
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.middleware.cors import CORSMiddleware
# Load .env file
load_dotenv()
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(
title="FarmGuard Nigeria API",
description="AI-powered plant disease detection for Nigerian farmers",
version="2.0.0"
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# Load disease config
config_path = os.path.join(os.path.dirname(__file__), "disease_config.json")
with open(config_path) as f:
DISEASE_CONFIG = json.load(f)
# Setup Gemini
GEMINI_KEY = os.getenv("GEMINI_API_KEY")
gemini_client = None
if GEMINI_KEY:
gemini_client = genai.Client(api_key=GEMINI_KEY)
logger.info("Gemini Vision API ready")
else:
logger.warning("No GEMINI_API_KEY found in .env file")
@app.get("/")
def root():
return {
"message": "FarmGuard Nigeria API is running!",
"version": "2.0.0",
"gemini_ready": gemini_client is not None,
"docs": "/docs"
}
@app.get("/api/health")
def health():
return {
"status": "healthy",
"gemini_ready": gemini_client is not None,
}
@app.post("/api/predict")
async def predict(file: UploadFile = File(...)):
"""Detect plant disease from uploaded leaf image using Gemini Vision."""
if file.content_type not in ["image/jpeg", "image/jpg", "image/png"]:
raise HTTPException(
status_code=400,
detail="Only JPEG and PNG images are supported"
)
image_bytes = await file.read()
if len(image_bytes) > 8 * 1024 * 1024:
raise HTTPException(
status_code=400,
detail="Image too large. Maximum size is 8MB"
)
if gemini_client is None:
raise HTTPException(
status_code=503,
detail="Gemini API not configured. Add GEMINI_API_KEY to your .env file"
)
try:
mime_type = file.content_type
prompt = """You are an expert Nigerian agricultural plant pathologist.
Analyse this plant leaf image and respond ONLY with a JSON object in this exact format with no extra text:
{
"crop_identified": true,
"plant": "crop name e.g. Tomato, Maize, Cassava, Potato, Pepper, Yam, Plantain",
"disease": "disease name or Healthy",
"is_healthy": false,
"confidence": 0.85,
"severity": "none or low or moderate or high or very_high",
"symptoms": ["symptom 1", "symptom 2", "symptom 3"],
"causes": "what causes this disease",
"treatment": {
"chemical": "specific chemical treatment with Nigerian product names e.g. Dithane M-45, Cobox, Ridomil Gold, Confidor",
"cultural": "cultural and physical management practices",
"preventive": "prevention measures for Nigerian farmers"
},
"urgency": "treatment urgency description",
"economic_impact": "potential yield and economic impact on Nigerian farmer",
"message": ""
}
Rules:
- If you cannot see a plant leaf clearly, set crop_identified to false and explain in message
- Focus on crops grown in Nigeria: Tomato, Maize, Cassava, Potato, Pepper, Yam, Plantain, Rice, Cowpea, Soybean, Orange, Groundnut, Banana
- Always include locally available Nigerian product names in chemical treatment
- Be specific and practical for Nigerian smallholder farmers
- Respond with ONLY the JSON object, nothing else"""
# Call Gemini with new SDK
response = gemini_client.models.generate_content(
model="gemini-2.5-flash",
contents=[
types.Part.from_bytes(data=image_bytes, mime_type=mime_type),
prompt
]
)
text = response.text.strip()
# Remove markdown code blocks if present
if "```json" in text:
text = text.split("```json")[1].split("```")[0].strip()
elif "```" in text:
text = text.split("```")[1].split("```")[0].strip()
result = json.loads(text)
# Handle unidentified image
if not result.get("crop_identified", False):
return {
"success": False,
"crop_identified": False,
"message": result.get("message", "Could not identify a plant leaf. Please upload a clear close-up photo of a single leaf."),
"top_prediction": None,
"all_predictions": [],
"is_healthy": False
}
severity = result.get("severity", "moderate")
severity_colors = {
"none": "#16a34a", "low": "#ca8a04",
"moderate": "#ea580c", "high": "#dc2626", "very_high": "#7f1d1d"
}
top_prediction = {
"plant": result.get("plant", "Unknown"),
"disease": result.get("disease", "Unknown"),
"confidence": float(result.get("confidence", 0.8)),
"severity": severity,
"severity_color": severity_colors.get(severity, "#ea580c"),
"symptoms": result.get("symptoms", []),
"causes": result.get("causes", ""),
"treatment": result.get("treatment", {}),
"urgency": result.get("urgency", ""),
"economic_impact": result.get("economic_impact", "")
}
conf = float(result.get("confidence", 0.8))
confidence_level = "high" if conf >= 0.7 else "medium" if conf >= 0.4 else "low"
return {
"success": True,
"crop_identified": True,
"is_healthy": result.get("is_healthy", False),
"top_prediction": top_prediction,
"all_predictions": [top_prediction],
"confidence_level": confidence_level,
"message": None
}
except json.JSONDecodeError as e:
logger.error(f"JSON parse error: {e}")
raise HTTPException(
status_code=500,
detail="Failed to parse AI response. Please try again."
)
except Exception as e:
logger.error(f"Gemini error: {e}")
raise HTTPException(
status_code=500,
detail=f"AI analysis failed: {str(e)}"
)
@app.get("/api/recommend")
def recommend(state: str = "Rivers", soil: str = "loamy", season: str = "rainy"):
"""Get crop recommendations based on location, soil and season."""
recommendations = {
"rainy": {
"crops": ["Cassava", "Yam", "Maize", "Plantain", "Cocoyam", "Waterleaf", "Fluted Pumpkin (Ugu)"],
"tip": "Rainy season is ideal for root crops and leafy vegetables. Ensure good drainage to prevent waterlogging."
},
"dry": {
"crops": ["Tomato", "Pepper", "Onion", "Carrot", "Cabbage", "Cowpea", "Soybean", "Groundnut"],
"tip": "Dry season farming requires irrigation. Vegetables thrive well with consistent water supply."
}
}
soil_tips = {
"loamy": "Loamy soil is excellent and suitable for almost all Nigerian crops. Maintain organic matter with compost.",
"sandy": "Sandy soil drains fast — best for cassava, groundnut and sweet potato. Add organic matter to improve water retention.",
"clay": "Clay soil retains water well — suitable for rice and yam. Ensure adequate drainage ridges to avoid waterlogging."
}
season_data = recommendations.get(season.lower(), recommendations["rainy"])
soil_tip = soil_tips.get(soil.lower(), "Prepare soil well with compost before planting.")
return {
"state": state,
"season": season,
"soil_type": soil,
"recommended_crops": season_data["crops"],
"season_tip": season_data["tip"],
"soil_tip": soil_tip
}
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|