demeter

Autonomous Hydroponic Intelligence

agent/sub_agents/Doctor.py (4292B)


  1from ultralytics import YOLO
  2import cv2
  3import json
  4import os
  5import logging
  6import numpy as np
  7import base64
  8from io import BytesIO
  9from PIL import Image
 10
 11# Setup basic logging
 12logging.basicConfig(level=logging.INFO)
 13logger = logging.getLogger(__name__)
 14
 15class VisionAgent:
 16    def __init__(self, model_path=None):
 17        logger.info("👁️ Initializing Vision Agent (Doctor)...")
 18        
 19        # 1. Find the project root
 20        if model_path:
 21            default_model = model_path
 22        else:
 23            current_file = os.path.abspath(__file__)
 24            agent_dir = os.path.dirname(os.path.dirname(current_file)) # agent/
 25            default_model = os.path.join(agent_dir, "model", "plant_disease_model.pt")
 26        
 27        self.model_name = default_model
 28        
 29        # 2. Load Model with Fallback
 30        try:
 31            if os.path.exists(self.model_name):
 32                logger.info(f"✅ Found plant disease model at: {self.model_name}")
 33                self.model = YOLO(self.model_name)
 34            else:
 35                logger.warning(f"⚠️ Custom model not found. Using generic YOLOv8n.")
 36                self.model = YOLO("yolov8n.pt")
 37                self.model_name = "yolov8n.pt"
 38            
 39            # Optimization
 40            self.model.to('cpu')
 41            
 42        except Exception as e:
 43            logger.error(f"❌ Critical Error loading model: {e}")
 44            self.model = None
 45
 46    def analyze_frame(self, image_b64):
 47        """
 48        Scans a base64 encoded image for pests, diseases, or growth stages.
 49        """
 50        if not self.model:
 51            return {"error": "Model not initialized"}
 52            
 53        if not image_b64:
 54            return {"error": "No image data provided"}
 55
 56        try:
 57            # 3. Decode Base64 to Image
 58            # Handle data URI scheme if present (e.g., "data:image/png;base64,...")
 59            if "," in image_b64:
 60                image_b64 = image_b64.split(",")[1]
 61            
 62            image_data = base64.b64decode(image_b64)
 63            image = Image.open(BytesIO(image_data))
 64
 65            # 4. Run Inference
 66            # YOLO can accept PIL Images directly
 67            results = self.model.predict(image, conf=0.25, save=False, verbose=False)
 68            result = results[0]
 69            
 70            detections = []
 71            summary_counts = {}
 72            
 73            for box in result.boxes:
 74                class_id = int(box.cls[0])
 75                label = self.model.names[class_id]
 76                confidence = float(box.conf[0])
 77                
 78                detections.append({
 79                    "object": label,
 80                    "confidence": round(confidence, 2),
 81                    "box": [round(x, 2) for x in box.xywhn[0].tolist()] 
 82                })
 83                summary_counts[label] = summary_counts.get(label, 0) + 1
 84
 85            # 5. Health Logic
 86            health_status = "HEALTHY"
 87            visual_alert = False
 88            
 89            if not detections:
 90                # If generic model, it might just see nothing. 
 91                # If disease model, empty usually means healthy.
 92                if "yolov8n" in self.model_name:
 93                    health_status = "NO_OBJECTS_DETECTED"
 94                else:
 95                    health_status = "HEALTHY"
 96            else:
 97                for label in summary_counts:
 98                    label_lower = label.lower()
 99                    # Keywords that imply sickness
100                    sick_keywords = ['spot', 'rot', 'blight', 'mildew', 'rust', 'virus', 'miner', 'mite', 'wilt']
101                    if any(x in label_lower for x in sick_keywords) and "healthy" not in label_lower:
102                        health_status = "DISEASE_DETECTED"
103                        visual_alert = True
104                        break
105
106            return {
107                "status": "Success",
108                "model_used": self.model_name,
109                "health_assessment": health_status,
110                "visual_alert": visual_alert,
111                "object_counts": summary_counts,
112                "detailed_detections": detections
113            }
114            
115        except Exception as e:
116           
117            logger.error(f"Error during analysis: {e}")
118            return {"error": str(e)}