Spaces:
Sleeping
Sleeping
import os | |
import json | |
from datetime import datetime, timedelta | |
from typing import Dict, List, Optional | |
import logging | |
try: | |
from PIL import Image | |
except ImportError: | |
Image = None | |
try: | |
import google.generativeai as genai | |
except ImportError: | |
genai = None | |
from models import DiseaseDetection, Farm, db | |
# Configure logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
class DiseaseDetectionService: | |
"""Service for AI-powered crop disease detection and treatment recommendations""" | |
def __init__(self, gemini_api_key: str): | |
self.gemini_api_key = gemini_api_key | |
genai.configure(api_key=gemini_api_key) | |
self.model = genai.GenerativeModel('gemini-2.0-flash') | |
# Common diseases database for quick reference | |
self.disease_database = { | |
'rice': { | |
'blast': { | |
'symptoms': 'Diamond-shaped lesions on leaves, brown borders with gray centers', | |
'treatment': 'Apply fungicides like Tricyclazole, improve drainage', | |
'prevention': 'Use resistant varieties, avoid excessive nitrogen' | |
}, | |
'bacterial_blight': { | |
'symptoms': 'Water-soaked lesions that turn yellow then brown', | |
'treatment': 'Copper-based bactericides, remove infected plants', | |
'prevention': 'Use certified seeds, avoid overhead irrigation' | |
} | |
}, | |
'wheat': { | |
'rust': { | |
'symptoms': 'Orange-red pustules on leaves and stems', | |
'treatment': 'Fungicides like Propiconazole, early application', | |
'prevention': 'Resistant varieties, proper spacing' | |
}, | |
'powdery_mildew': { | |
'symptoms': 'White powdery growth on leaves', | |
'treatment': 'Sulfur-based fungicides, improve air circulation', | |
'prevention': 'Avoid dense planting, reduce humidity' | |
} | |
}, | |
'tomato': { | |
'late_blight': { | |
'symptoms': 'Dark green water-soaked spots, white mold on leaf undersides', | |
'treatment': 'Copper fungicides, remove infected parts', | |
'prevention': 'Improve ventilation, avoid overhead watering' | |
}, | |
'early_blight': { | |
'symptoms': 'Concentric rings on leaves, starts from bottom', | |
'treatment': 'Fungicides, remove lower leaves', | |
'prevention': 'Crop rotation, proper spacing' | |
} | |
} | |
} | |
def analyze_disease_from_text(self, farm_id: int, crop_name: str, symptoms: str) -> Dict: | |
"""Analyze disease based on text symptoms description""" | |
try: | |
farm = Farm.query.get(farm_id) | |
if not farm: | |
return {'error': 'Farm not found'} | |
# Create prompt for Gemini AI | |
prompt = self._create_disease_analysis_prompt(crop_name, symptoms) | |
# Get AI analysis | |
response = self.model.generate_content(prompt) | |
ai_analysis = self._parse_disease_response(response.text) | |
# Save to database | |
detection = DiseaseDetection( | |
farm_id=farm_id, | |
crop_name=crop_name, | |
disease_name=ai_analysis.get('disease_name', 'Unknown'), | |
confidence_score=ai_analysis.get('confidence', 0.0), | |
symptoms=symptoms, | |
treatment=ai_analysis.get('treatment', ''), | |
prevention=ai_analysis.get('prevention', ''), | |
ai_analysis=json.dumps(ai_analysis), | |
severity=ai_analysis.get('severity', 'unknown') | |
) | |
db.session.add(detection) | |
db.session.commit() | |
return { | |
'success': True, | |
'detection_id': detection.id, | |
'analysis': ai_analysis | |
} | |
except Exception as e: | |
logger.error(f"Error analyzing disease: {str(e)}") | |
return {'error': f'Analysis failed: {str(e)}'} | |
def analyze_disease_from_image(self, farm_id: int, crop_name: str, image_path: str, symptoms: str = "") -> Dict: | |
"""Analyze disease from uploaded image""" | |
try: | |
farm = Farm.query.get(farm_id) | |
if not farm: | |
return {'error': 'Farm not found'} | |
# Verify image exists | |
if not os.path.exists(image_path): | |
return {'error': 'Image file not found'} | |
# Create prompt for image analysis | |
prompt = self._create_image_analysis_prompt(crop_name, symptoms) | |
# Load and process image | |
image = Image.open(image_path) | |
# Get AI analysis with image | |
response = self.model.generate_content([prompt, image]) | |
ai_analysis = self._parse_disease_response(response.text) | |
# Save to database | |
detection = DiseaseDetection( | |
farm_id=farm_id, | |
crop_name=crop_name, | |
disease_name=ai_analysis.get('disease_name', 'Unknown'), | |
confidence_score=ai_analysis.get('confidence', 0.0), | |
symptoms=symptoms, | |
treatment=ai_analysis.get('treatment', ''), | |
prevention=ai_analysis.get('prevention', ''), | |
image_path=image_path, | |
ai_analysis=json.dumps(ai_analysis), | |
severity=ai_analysis.get('severity', 'unknown') | |
) | |
db.session.add(detection) | |
db.session.commit() | |
return { | |
'success': True, | |
'detection_id': detection.id, | |
'analysis': ai_analysis | |
} | |
except Exception as e: | |
logger.error(f"Error analyzing disease from image: {str(e)}") | |
return {'error': f'Image analysis failed: {str(e)}'} | |
def _create_disease_analysis_prompt(self, crop_name: str, symptoms: str) -> str: | |
"""Create prompt for disease analysis""" | |
return f""" | |
You are an expert plant pathologist. Analyze the following crop disease symptoms and provide a detailed diagnosis. | |
CROP: {crop_name} | |
SYMPTOMS: {symptoms} | |
Please provide your analysis in the following JSON format: | |
{{ | |
"disease_name": "Most likely disease name", | |
"confidence": 0.85, | |
"severity": "mild/moderate/severe", | |
"symptoms_analysis": "Detailed analysis of symptoms", | |
"treatment": "Immediate treatment recommendations", | |
"prevention": "Future prevention measures", | |
"additional_info": "Any additional relevant information", | |
"urgency": "low/medium/high" | |
}} | |
Consider: | |
1. Common diseases for this crop | |
2. Seasonal factors | |
3. Environmental conditions | |
4. Treatment urgency | |
5. Cost-effective solutions for farmers | |
Provide practical, actionable advice that farmers can easily implement. | |
""" | |
def _create_image_analysis_prompt(self, crop_name: str, symptoms: str = "") -> str: | |
"""Create prompt for image-based disease analysis""" | |
base_prompt = f""" | |
You are an expert plant pathologist. Analyze this image of {crop_name} crop for signs of disease or pest damage. | |
CROP: {crop_name} | |
""" | |
if symptoms: | |
base_prompt += f"REPORTED SYMPTOMS: {symptoms}\n" | |
base_prompt += """ | |
Please examine the image carefully and provide your analysis in the following JSON format: | |
{ | |
"disease_name": "Identified disease or pest issue", | |
"confidence": 0.85, | |
"severity": "mild/moderate/severe", | |
"visual_symptoms": "What you observe in the image", | |
"treatment": "Immediate treatment recommendations", | |
"prevention": "Future prevention measures", | |
"affected_area": "Percentage of plant affected", | |
"urgency": "low/medium/high", | |
"additional_observations": "Any other relevant findings" | |
} | |
Look for: | |
1. Leaf discoloration or spots | |
2. Wilting or deformation | |
3. Pest damage | |
4. Fungal growth | |
5. Nutrient deficiencies | |
6. Environmental stress signs | |
Provide practical, cost-effective treatment recommendations suitable for farmers. | |
""" | |
return base_prompt | |
def _parse_disease_response(self, response_text: str) -> Dict: | |
"""Parse AI response and extract disease information""" | |
try: | |
# Try to extract JSON from response | |
start_idx = response_text.find('{') | |
end_idx = response_text.rfind('}') + 1 | |
if start_idx != -1 and end_idx != -1: | |
json_text = response_text[start_idx:end_idx] | |
analysis = json.loads(json_text) | |
# Ensure required fields | |
analysis.setdefault('disease_name', 'Unknown Disease') | |
analysis.setdefault('confidence', 0.5) | |
analysis.setdefault('severity', 'moderate') | |
analysis.setdefault('treatment', 'Consult agricultural expert') | |
analysis.setdefault('prevention', 'Maintain good crop hygiene') | |
analysis.setdefault('urgency', 'medium') | |
return analysis | |
else: | |
# Manual parsing if JSON not found | |
return self._manual_parse_response(response_text) | |
except json.JSONDecodeError: | |
return self._manual_parse_response(response_text) | |
except Exception as e: | |
logger.error(f"Error parsing disease response: {str(e)}") | |
return self._get_fallback_analysis() | |
def _manual_parse_response(self, response_text: str) -> Dict: | |
"""Manually parse response if JSON parsing fails""" | |
analysis = { | |
'disease_name': 'Unknown Disease', | |
'confidence': 0.5, | |
'severity': 'moderate', | |
'treatment': 'Consult agricultural expert', | |
'prevention': 'Maintain good crop hygiene', | |
'urgency': 'medium' | |
} | |
lines = response_text.lower().split('\n') | |
for line in lines: | |
if 'disease' in line and 'name' in line: | |
analysis['disease_name'] = line.split(':')[-1].strip() | |
elif 'treatment' in line: | |
analysis['treatment'] = line.split(':')[-1].strip() | |
elif 'prevention' in line: | |
analysis['prevention'] = line.split(':')[-1].strip() | |
elif 'severe' in line: | |
analysis['severity'] = 'severe' | |
elif 'mild' in line: | |
analysis['severity'] = 'mild' | |
return analysis | |
def _get_fallback_analysis(self) -> Dict: | |
"""Provide fallback analysis when AI fails""" | |
return { | |
'disease_name': 'Analysis Failed', | |
'confidence': 0.0, | |
'severity': 'unknown', | |
'treatment': 'Please consult with a local agricultural expert or extension officer', | |
'prevention': 'Maintain good crop hygiene and monitoring practices', | |
'urgency': 'medium', | |
'error': 'AI analysis unavailable' | |
} | |
def get_disease_history(self, farm_id: int, days: int = 30) -> List[DiseaseDetection]: | |
"""Get disease detection history for a farm""" | |
from_date = datetime.now() - timedelta(days=days) | |
return DiseaseDetection.query.filter( | |
DiseaseDetection.farm_id == farm_id, | |
DiseaseDetection.detected_at >= from_date | |
).order_by(DiseaseDetection.detected_at.desc()).all() | |
def update_treatment_status(self, detection_id: int, status: str, notes: str = "") -> bool: | |
"""Update treatment status for a disease detection""" | |
try: | |
detection = DiseaseDetection.query.get(detection_id) | |
if not detection: | |
return False | |
detection.status = status | |
if status == 'resolved': | |
detection.resolved_at = datetime.now() | |
db.session.commit() | |
return True | |
except Exception as e: | |
logger.error(f"Error updating treatment status: {str(e)}") | |
return False | |
def get_preventive_recommendations(self, crop_name: str, season: str = "") -> Dict: | |
"""Get preventive recommendations for common diseases""" | |
crop_lower = crop_name.lower() | |
if crop_lower in self.disease_database: | |
diseases = self.disease_database[crop_lower] | |
recommendations = [] | |
for disease, info in diseases.items(): | |
recommendations.append({ | |
'disease': disease.replace('_', ' ').title(), | |
'prevention': info['prevention'], | |
'early_symptoms': info['symptoms'] | |
}) | |
return { | |
'crop': crop_name, | |
'recommendations': recommendations, | |
'general_tips': [ | |
'Regular field monitoring', | |
'Proper crop rotation', | |
'Maintain field hygiene', | |
'Use disease-resistant varieties', | |
'Proper water management' | |
] | |
} | |
return { | |
'crop': crop_name, | |
'recommendations': [], | |
'general_tips': [ | |
'Regular field monitoring is essential', | |
'Maintain proper spacing between plants', | |
'Ensure good drainage', | |
'Use certified seeds', | |
'Follow integrated pest management' | |
] | |
} | |