""" 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)