farmguard-api / main.py
0xdivin3's picture
Upload 7 files
d0624ee verified
Raw
History Blame Contribute Delete
8.1 kB
"""
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)