commit 1b89d08df121148fe294fde8e149e25ab1e9d2d5
parent 0dcde6cc427784b12d37964149cc6cadcea53628
Author: Debarghya Das <debarghya1108@gmail.com>
Date: Wed, 4 Mar 2026 11:40:32 +0000
Merge PR
Diffstat:
17 files changed, 1086 insertions(+), 240 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,8 +1,10 @@
-/venv
+/.venv
.env
node_modules/
__pycache__/
/web/node_modules
Knowledge_Base
-/.venv
-/agent/venv
-\ No newline at end of file
+/agent/model/
+/agent/training_data
+agent/Marl/model_bandit_greedy.pkl
+*.pt
diff --git a/Qdrant/Search.py b/Qdrant/Search.py
diff --git a/agent/Marl/bandit.py b/agent/Marl/bandit.py
@@ -5,7 +5,7 @@ import pickle
import os
class ContextualBandit:
- def __init__(self, n_actions=15, feature_dim=519):
+ def __init__(self, n_actions=15, feature_dim=515):
"""
LinGreedy Implementation (Pure Exploitation).
We removed 'alpha' because we do not want to explore.
@@ -19,14 +19,20 @@ class ContextualBandit:
# b: Reward Vector
self.b = [np.zeros(self.d) for _ in range(self.n_actions)]
- self.file_path = "model_bandit_greedy.pkl"
- self.load()
+ # theta: Weight vectors for each action (optional, computed on-the-fly)
+ self.theta = [np.zeros(self.d) for _ in range(self.n_actions)]
+
+ self.file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model_bandit_greedy.pkl")
+ self.load() # Now calls with no arguments
def select_action(self, context_vector):
"""
Returns: (action_index, debug_info)
Strictly picks the action with the highest PREDICTED reward.
"""
+ # Ensure context is flat (1D array)
+ context_vector = np.array(context_vector).reshape(-1)
+
predicted_rewards = np.zeros(self.n_actions)
confidences = np.zeros(self.n_actions)
@@ -47,8 +53,11 @@ class ContextualBandit:
# 3. Calculate Confidence (Optional, for UI only)
# We calculate variance just to show the user "How sure are we?"
# But we do NOT add this to the score.
- variance = context_vector.dot(np.linalg.solve(self.A[a], context_vector))
- confidences[a] = 1.0 / (1.0 + variance) # Simple confidence score (0-1)
+ try:
+ variance = context_vector.dot(np.linalg.solve(self.A[a], context_vector))
+ confidences[a] = 1.0 / (1.0 + variance) # Simple confidence score (0-1)
+ except np.linalg.LinAlgError:
+ confidences[a] = 0.0
# š¢ PURE EXPLOITATION: Pick max predicted reward
chosen_action = np.argmax(predicted_rewards)
@@ -58,27 +67,62 @@ class ContextualBandit:
"confidences": confidences.tolist()
}
- def update(self, action_idx, context_vector, reward):
+ def update(self, context_vector, action_idx, reward):
"""
Online Learning: The AI still gets smarter with every feedback.
+
+ Args:
+ context_vector: The feature vector (515-dim)
+ action_idx: Which action was taken
+ reward: The reward received
"""
+ # Ensure types are correct
+ action_idx = int(action_idx)
+ reward = float(reward)
+
+ # Ensure context is flat (1D array)
+ context_vector = np.array(context_vector).reshape(-1)
+
# Update the regression model for the chosen arm
self.A[action_idx] += np.outer(context_vector, context_vector)
self.b[action_idx] += reward * context_vector
- self.save()
- print(f"š Greedy Model Updated | Action: {action_idx} | Reward: {reward}")
+ # Update theta (optional, can be computed on-the-fly in select_action)
+ try:
+ self.theta[action_idx] = np.linalg.solve(self.A[action_idx], self.b[action_idx])
+ except np.linalg.LinAlgError:
+ # Use pseudoinverse if solve fails
+ self.theta[action_idx] = np.linalg.pinv(self.A[action_idx]).dot(self.b[action_idx])
def save(self):
- with open(self.file_path, 'wb') as f:
- pickle.dump({'A': self.A, 'b': self.b}, f)
+ """Save the model weights to disk"""
+ try:
+ with open(self.file_path, 'wb') as f:
+ pickle.dump({
+ 'A': self.A,
+ 'b': self.b,
+ 'theta': self.theta
+ }, f)
+ print(f"š¾ Model saved to {self.file_path}")
+ except Exception as e:
+ print(f"ā Error saving model: {e}")
- def load(self):
- if os.path.exists(self.file_path):
- try:
- with open(self.file_path, 'rb') as f:
- data = pickle.load(f)
- self.A = data['A']
- self.b = data['b']
- except Exception:
- print("ā ļø Could not load model, starting fresh.")
-\ No newline at end of file
+ def load(self): # <--- FIXED: No filepath argument, uses self.file_path
+ """Loads weights from disk if they exist."""
+ if not os.path.exists(self.file_path):
+ print(f"ā¹ļø No saved model found at {self.file_path}. Starting fresh.")
+ return False
+
+ try:
+ with open(self.file_path, 'rb') as f:
+ state = pickle.load(f)
+
+ # Restore state
+ self.A = state['A']
+ self.b = state['b']
+ self.theta = state['theta']
+ print(f"ā
Loaded bandit model from {self.file_path}")
+ return True
+ except Exception as e:
+ print(f"ā ļø Error loading model: {e}. Starting fresh.")
+ return False
+\ No newline at end of file
diff --git a/agent/Marl/train-bandit.py b/agent/Marl/train-bandit.py
@@ -0,0 +1,268 @@
+import sys
+import os
+import numpy as np
+import random
+import time
+
+# 1. Setup Path to import sibling modules
+current_dir = os.path.dirname(os.path.abspath(__file__))
+parent_dir = os.path.dirname(os.path.dirname(current_dir))
+sys.path.append(parent_dir)
+
+from agent.Marl.bandit import ContextualBandit
+from agent.Marl.strategies import STRATEGIES, NUM_ACTIONS
+
+def generate_scenario():
+ """
+ Generates DISTINCT scenarios with clear separations between strategies.
+ Returns:
+ context (np.array): 515-dim vector (512 visual + 3 sensors)
+ sensors (dict): Sensor readings
+ target_strategy (int): The index of the correct strategy
+ scenario_name (str): Description for logging
+ """
+ scenario_type = random.randint(0, NUM_ACTIONS - 1)
+
+ # Defaults - use float32 for consistency and speed
+ vis_vec = np.zeros(512, dtype=np.float32)
+ ph = random.uniform(5.8, 6.5)
+ ec = random.uniform(1.2, 1.8)
+ temp = random.uniform(22.0, 26.0)
+
+ target_strategy = 0
+ name = "Normal"
+
+ # --- 0. MAINTAIN_CURRENT (FIX: Very obvious optimal conditions) ---
+ if scenario_type == 0:
+ ph = random.uniform(5.9, 6.3) # Perfect pH
+ ec = random.uniform(1.3, 1.7) # Perfect EC
+ temp = random.uniform(23.0, 25.0) # Perfect temp
+ # Minimal visual noise (healthy plant)
+ vis_vec[0:5] = np.random.uniform(0.1, 0.3, 5)
+ name = "Optimal Conditions"
+ target_strategy = 0
+
+ # --- 1. CALIBRATE_SENSORS (FIX: Physically impossible values) ---
+ elif scenario_type == 1:
+ choice = random.randint(0, 2)
+ if choice == 0:
+ ph = random.choice([-10.0, -5.0, 0.0, 15.0, 20.0, 50.0])
+ elif choice == 1:
+ ec = random.choice([-10.0, 0.0, 50.0, 100.0, 500.0])
+ else:
+ temp = random.choice([-50.0, -20.0, 100.0, 150.0, 500.0])
+
+ # Strong visual anomaly signal
+ vis_vec[0:20] = np.random.uniform(0.9, 1.0, 20)
+ name = "Sensor Malfunction"
+ target_strategy = 1
+
+ # --- 2. AGGRESSIVE_PH_DOWN ---
+ elif scenario_type == 2:
+ ph = random.uniform(7.8, 12.0) # Very alkaline
+ name = "High pH (Alkaline)"
+ target_strategy = 2
+
+ # --- 3. AGGRESSIVE_PH_UP ---
+ elif scenario_type == 3:
+ ph = random.uniform(0.5, 4.2) # Very acidic
+ name = "Low pH (Acidic)"
+ target_strategy = 3
+
+ # --- 4. GENTLE_PH_BALANCING (FIX: Clear mild range) ---
+ elif scenario_type == 4:
+ if random.random() < 0.5:
+ ph = random.uniform(5.4, 5.8) # Slightly low
+ else:
+ ph = random.uniform(6.4, 6.9) # Slightly high
+ # Keep other sensors perfect
+ ec = random.uniform(1.4, 1.6)
+ temp = random.uniform(23.5, 24.5)
+ name = "Mild pH Drift"
+ target_strategy = 4
+
+ # --- 5. INCREASE_EC_VEG ---
+ elif scenario_type == 5:
+ ec = random.uniform(0.1, 0.9) # Low EC
+ # Strong vegetative signal (first block)
+ vis_vec[20:40] = np.random.uniform(0.6, 1.0, 20)
+ name = "Low EC (Veg Stage)"
+ target_strategy = 5
+
+ # --- 6. INCREASE_EC_BLOOM ---
+ elif scenario_type == 6:
+ ec = random.uniform(0.1, 0.9) # Low EC
+ # Strong flowering signal (second block)
+ vis_vec[40:60] = np.random.uniform(0.6, 1.0, 20)
+ name = "Low EC (Bloom Stage)"
+ target_strategy = 6
+
+ # --- 7. LOWER_EC_FLUSH ---
+ elif scenario_type == 7:
+ ec = random.uniform(2.8, 8.0) # Very high EC
+ # Nutrient burn visual (third block)
+ vis_vec[60:80] = np.random.uniform(0.7, 1.0, 20)
+ name = "Nutrient Burn / High EC"
+ target_strategy = 7
+
+ # --- 8. CALMAG_BOOST ---
+ elif scenario_type == 8:
+ # CalMag deficiency visual pattern (fourth block)
+ vis_vec[80:100] = np.random.uniform(0.8, 1.0, 20)
+ name = "CalMag Deficiency (Visual)"
+ target_strategy = 8
+
+ # --- 9. RAISE_TEMP_HUMIDITY ---
+ elif scenario_type == 9:
+ temp = random.uniform(8.0, 17.0) # Too cold
+ name = "Too Cold / Low VPD"
+ target_strategy = 9
+
+ # --- 10. LOWER_TEMP_HUMIDITY ---
+ elif scenario_type == 10:
+ temp = random.uniform(32.0, 50.0) # Too hot
+ name = "Too Hot / High VPD"
+ target_strategy = 10
+
+ # --- 11. MAX_AIR_CIRCULATION ---
+ elif scenario_type == 11:
+ # Stagnant air visual (fifth block)
+ vis_vec[100:120] = np.random.uniform(0.6, 1.0, 20)
+ name = "Stagnant Air / Weak Stems"
+ target_strategy = 11
+
+ # --- 12. FUNGAL_TREATMENT ---
+ elif scenario_type == 12:
+ # Fungal infection visual (sixth block)
+ vis_vec[120:140] = np.random.uniform(0.8, 1.0, 20)
+ temp = random.uniform(26.0, 32.0) # Warm and humid conditions
+ name = "Fungal Infection Detected"
+ target_strategy = 12
+
+ # --- 13. PEST_ISOLATION ---
+ elif scenario_type == 13:
+ # Pest visual (seventh block)
+ vis_vec[140:160] = np.random.uniform(0.8, 1.0, 20)
+ name = "Pest Infestation Detected"
+ target_strategy = 13
+
+ # --- 14. PRUNE_NECROTIC_LEAVES ---
+ elif scenario_type == 14:
+ # Necrosis visual (eighth block)
+ vis_vec[160:180] = np.random.uniform(0.8, 1.0, 20)
+ name = "Necrosis Detected"
+ target_strategy = 14
+
+ # Build Final Context Vector
+ sensor_vec = np.array([
+ (ph - 6.0) / 2.0,
+ (ec - 1.0) / 3.0,
+ (temp - 25.0) / 40.0
+ ], dtype=np.float32)
+
+ context = np.concatenate([vis_vec, sensor_vec])
+
+ return context, {'pH': ph, 'EC': ec, 'temp': temp}, target_strategy, name
+
+def train():
+ print("=" * 80)
+ print("š§ CONTEXTUAL BANDIT TRAINING - HYDROPONIC CONTROL SYSTEM")
+ print("=" * 80)
+ print(f" Strategy Count: {NUM_ACTIONS}")
+ print(f" Feature Dimensions: 515 (512 Vision + 3 Sensors)")
+ print(f" Training Episodes: 15,000")
+ print()
+
+ bandit = ContextualBandit(n_actions=NUM_ACTIONS, feature_dim=515)
+
+ n_epochs = 15000
+ correct_counts = np.zeros(NUM_ACTIONS, dtype=int)
+ total_counts = np.zeros(NUM_ACTIONS, dtype=int)
+
+ start_time = time.time()
+ last_print_time = start_time
+
+ for i in range(n_epochs):
+ # 1. Generate scenario
+ context, sensors, target_action, scenario_name = generate_scenario()
+
+ # 2. Bandit prediction
+ chosen_action_idx, debug_info = bandit.select_action(context)
+
+ # 3. Calculate reward
+ if chosen_action_idx == target_action:
+ reward = 1.0
+ correct_counts[target_action] += 1
+ else:
+ reward = -1.0
+
+ total_counts[target_action] += 1
+
+ # 4. Update bandit
+ bandit.update(context, chosen_action_idx, reward)
+
+ # Progress logging (every 2 seconds or every 1000 steps)
+ current_time = time.time()
+ if (i + 1) % 1000 == 0 or (current_time - last_print_time >= 2.0):
+ elapsed = current_time - start_time
+ speed = (i + 1) / elapsed
+ eta = (n_epochs - i - 1) / speed if speed > 0 else 0
+ current_acc = correct_counts.sum() / total_counts.sum() * 100 if total_counts.sum() > 0 else 0
+
+ print(f" [{i+1:5}/{n_epochs}] "
+ f"Accuracy: {current_acc:5.1f}% | "
+ f"Speed: {speed:4.0f} it/s | "
+ f"ETA: {eta:4.0f}s | "
+ f"Last: {scenario_name[:25]:<25}")
+ last_print_time = current_time
+
+ end_time = time.time()
+ training_time = end_time - start_time
+
+ print(f"\nā
Training Complete in {training_time:.1f}s ({training_time/60:.1f} min)")
+ print(f" Average Speed: {n_epochs/training_time:.0f} iterations/second")
+
+ # Detailed accuracy report
+ print("\n" + "=" * 80)
+ print("š FINAL ACCURACY REPORT BY STRATEGY")
+ print("=" * 80)
+ print(f"{'STRATEGY':<30} | {'ACCURACY':>10} | {'CORRECT':>8} / {'TOTAL':>8}")
+ print("-" * 80)
+
+ overall_correct = correct_counts.sum()
+ overall_total = total_counts.sum()
+
+ # Sort by accuracy (worst first) to highlight problems
+ strategy_performance = []
+ for idx in range(NUM_ACTIONS):
+ count = total_counts[idx]
+ correct = correct_counts[idx]
+ acc = (correct / count) * 100 if count > 0 else 0.0
+ strategy_performance.append((idx, acc, correct, count))
+
+ strategy_performance.sort(key=lambda x: x[1]) # Sort by accuracy
+
+ for idx, acc, correct, count in strategy_performance:
+ status = "ā
" if acc >= 90 else "ā ļø" if acc >= 70 else "ā"
+ print(f"{status} {STRATEGIES[idx]:<27} | {acc:>9.1f}% | {correct:>8} / {count:>8}")
+
+ print("-" * 80)
+ overall_acc = (overall_correct / overall_total) * 100
+ print(f"{'OVERALL ACCURACY':<30} | {overall_acc:>9.1f}% | {overall_correct:>8} / {overall_total:>8}")
+ print("=" * 80 + "\n")
+
+ # Save model
+ bandit.save()
+
+ # Final recommendations
+ if overall_acc >= 95:
+ print("š Excellent! Model ready for production.")
+ elif overall_acc >= 90:
+ print("ā
Good! Model is ready.")
+ elif overall_acc >= 80:
+ print("ā ļø Acceptable, but consider retraining with more epochs.")
+ else:
+ print("ā Low accuracy. Check scenario generation or retrain.")
+
+if __name__ == "__main__":
+ train()
+\ No newline at end of file
diff --git a/agent/memory.py b/agent/memory.py
@@ -0,0 +1,136 @@
+from mem0 import Memory
+import os
+from dotenv import load_dotenv
+from qdrant_client import QdrantClient
+from qdrant_client.http import models
+
+load_dotenv()
+
+class FarmMemory:
+ def __init__(self):
+ # First, ensure the collection exists with correct dimensions
+ self._setup_collection()
+
+ self.memory = Memory.from_config({
+ # š¢ 1. VECTOR STORE (Qdrant Cloud)
+ "vector_store": {
+ "provider": "qdrant",
+ "config": {
+ "url": os.getenv("QDRANT_URL"),
+ "api_key": os.getenv("QDRANT_API_KEY"),
+ "collection_name": "Plant_Biographies_HF", # Different collection for 384 dims
+ "port": 6333,
+ }
+ },
+ # š¢ 2. LLM (Groq)
+ "llm": {
+ "provider": "groq",
+ "config": {
+ "model": "llama-3.1-8b-instant",
+ "api_key": os.getenv("GROQ_API_KEY")
+ }
+ },
+ # š¢ 3. EMBEDDER (HuggingFace - 384 dimensions)
+ "embedder": {
+ "provider": "huggingface",
+ "config": {
+ "model": "all-MiniLM-L6-v2" # 384 dimensions
+ }
+ }
+ })
+
+ def _setup_collection(self):
+ """Create the collection with correct vector dimensions if it doesn't exist"""
+ client = QdrantClient(
+ url=os.getenv("QDRANT_URL"),
+ api_key=os.getenv("QDRANT_API_KEY"),
+ )
+
+ collection_name = "Plant_Biographies_HF"
+
+ try:
+ # Check if collection exists
+ client.get_collection(collection_name)
+ print(f"ā
Collection '{collection_name}' already exists")
+ except Exception:
+ # Create collection with 384 dimensions
+ print(f"š Creating collection '{collection_name}' with 384 dimensions...")
+ client.create_collection(
+ collection_name=collection_name,
+ vectors_config=models.VectorParams(
+ size=384, # HuggingFace all-MiniLM-L6-v2 dimension
+ distance=models.Distance.COSINE
+ )
+ )
+ print(f"ā
Collection created successfully")
+
+ def get_plant_history(self, crop_id):
+ """Retrieve the complete biographical history of a plant"""
+ history = self.memory.search(
+ query=f"What is the health history and past treatments for {crop_id}?",
+ user_id=crop_id
+ )
+
+ # Debug: Print the structure to see what we got
+ print(f"š Debug - History type: {type(history)}")
+ # print(f"š Debug - History content: {history}")
+
+ if not history:
+ return "No prior biographical records for this plant."
+
+ # Handle different possible response structures
+ try:
+ # If history is a dict with 'results' key
+ if isinstance(history, dict) and 'results' in history:
+ results = history['results']
+ formatted_history = "\n".join([f"- {item['memory']}" for item in results])
+ # If history is already a list
+ elif isinstance(history, list):
+ # Each item might be a dict or a string
+ formatted_lines = []
+ for item in history:
+ if isinstance(item, dict):
+ # Try different possible keys
+ text = item.get('memory') or item.get('text') or item.get('content') or str(item)
+ else:
+ text = str(item)
+ formatted_lines.append(f"- {text}")
+ formatted_history = "\n".join(formatted_lines)
+ # If it's a string (single result)
+ elif isinstance(history, str):
+ formatted_history = f"- {history}"
+ else:
+ formatted_history = str(history)
+
+ return formatted_history
+
+ except Exception as e:
+ print(f"ā ļø Error formatting history: {e}")
+ return f"Error retrieving history: {str(e)}\nRaw data: {history}"
+
+ def log_event(self, crop_id, event_text):
+ """Log a new event in the plant's biography"""
+ result = self.memory.add(event_text, user_id=crop_id)
+ # print(f"š§ Biography Updated for {crop_id}")
+ # print(f"š Add result: {result}")
+
+if __name__ == "__main__":
+ print("š Running Quick Memory Check...")
+
+ # 1. Initialize
+ mem = FarmMemory()
+ test_id = "Debug_Plant_001"
+
+ # 2. Write
+ print(f"\nš Writing memory for {test_id}...")
+ mem.log_event(test_id, "DIAGNOSIS: Plant shows signs of severe Nitrogen deficiency. Leaves are yellowing at the bottom.")
+
+ # 3. Read
+ print(f"\nš Reading back memory...")
+ history = mem.get_plant_history(test_id)
+
+ print("\n" + "="*50)
+ print("--- PLANT BIOGRAPHY ---")
+ print("="*50)
+ print(history)
+ print("="*50 + "\n")
+\ No newline at end of file
diff --git a/agent/sub_agents/Doctor.py b/agent/sub_agents/Doctor.py
@@ -0,0 +1,128 @@
+from ultralytics import YOLO
+import cv2
+import json
+import os
+import logging
+
+# Setup basic logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+class VisionAgent:
+ def __init__(self, model_path=None):
+ logger.info("šļø Initializing Vision Agent (Doctor)...")
+
+ # 1. Find the project root (directory containing "agent" folder)
+ if model_path:
+ default_model = model_path
+ else:
+ # Get the directory where THIS file (Doctor.py) is located
+ current_file = os.path.abspath(__file__)
+ # Navigate up to agent/sub_agents/Doctor.py -> agent/
+ agent_dir = os.path.dirname(os.path.dirname(current_file))
+ # Now go to agent/model/plant_disease_model.pt
+ default_model = os.path.join(agent_dir, "model", "plant_disease_model.pt")
+
+ self.model_name = default_model
+
+ # 2. Load Model with Fallback
+ try:
+ # Check if file exists
+ if os.path.exists(self.model_name):
+ logger.info(f"ā
Found plant disease model at: {self.model_name}")
+ self.model = YOLO(self.model_name)
+ logger.info(f"ā
Loaded Custom Plant Doctor")
+ else:
+ # Debug info
+ logger.warning(f"ā ļø Model not found at: {self.model_name}")
+ logger.warning(f" Looking in: {os.path.dirname(self.model_name)}")
+ logger.info("š„ Using generic YOLOv8n instead...")
+ self.model = YOLO("yolov8n.pt")
+ self.model_name = "yolov8n.pt"
+
+ # CPU Optimization for Laptop
+ self.model.to('cpu')
+ logger.info("ā
Vision Agent ready")
+
+ except Exception as e:
+ logger.error(f"ā Critical Error loading model: {e}")
+ self.model = None
+
+ def analyze_frame(self, image_path):
+ """
+ Scans an image for pests, diseases, or growth stages.
+ """
+ if not self.model:
+ return {"error": "Model not initialized"}
+
+ if not os.path.exists(image_path):
+ return {"error": f"Image file not found: {image_path}"}
+
+ try:
+ # 3. Run Inference
+ results = self.model.predict(image_path, conf=0.25, save=False, verbose=False)
+ result = results[0]
+
+ detections = []
+ summary_counts = {}
+
+ # 4. Process Detections
+ for box in result.boxes:
+ class_id = int(box.cls[0])
+ label = self.model.names[class_id]
+ confidence = float(box.conf[0])
+
+ detections.append({
+ "object": label,
+ "confidence": round(confidence, 2),
+ "box": [round(x, 2) for x in box.xywhn[0].tolist()]
+ })
+
+ summary_counts[label] = summary_counts.get(label, 0) + 1
+
+ # 5. Smart Health Logic
+ health_status = "HEALTHY"
+ visual_alert = False
+
+ if not detections:
+ health_status = "NO_PLANTS_DETECTED"
+ else:
+ for label in summary_counts:
+ label_lower = label.lower()
+ if "healthy" not in label_lower and any(x in label_lower for x in ['spot', 'rot', 'blight', 'mildew', 'rust', 'virus', 'miner', 'mite']):
+ health_status = "DISEASE_DETECTED"
+ visual_alert = True
+ break
+
+ report = {
+ "status": "Success",
+ "model_used": self.model_name,
+ "health_assessment": health_status,
+ "visual_alert": visual_alert,
+ "object_counts": summary_counts,
+ "detailed_detections": detections
+ }
+
+ return report
+
+ except Exception as e:
+ logger.error(f"Error during analysis: {e}")
+ return {"error": str(e)}
+
+# --- Quick Test Block ---
+if __name__ == "__main__":
+ agent = VisionAgent()
+
+ test_path = "test_plant.jpg"
+
+ if not os.path.exists(test_path):
+ import numpy as np
+ print("ā ļø Creating dummy test image...")
+ dummy_img = np.zeros((640, 640, 3), dtype=np.uint8)
+ dummy_img[:] = (0, 255, 0)
+ cv2.rectangle(dummy_img, (100, 100), (200, 200), (0, 0, 255), -1)
+ cv2.imwrite(test_path, dummy_img)
+
+ print("\n--- ANALYSIS REPORT ---")
+ report = agent.analyze_frame(test_path)
+ print(json.dumps(report, indent=2))
+\ No newline at end of file
diff --git a/agent/sub_agents/Supervisor.py b/agent/sub_agents/Supervisor.py
@@ -1,234 +1,243 @@
-import os
import json
import numpy as np
-from langchain_openai import ChatOpenAI
-from langchain_core.messages import SystemMessage, HumanMessage
-from langgraph.graph import StateGraph, END
-from agent.tools.actuation import convert_targets_to_actions
+import os
+# 1. Internal Engines
from agent.Marl.bandit import ContextualBandit
from agent.Marl.strategies import STRATEGIES, NUM_ACTIONS
-from agent.Qdrant.Store import store_fmu
-from agent.sub_agents.water_and_atmospheric_dependencies.physics_engine import predict_outcome
-
-# --- NEW TOOLS DEFINITION ---
-def check_cross_domain_conflicts(atmos, water):
- conflicts = []
-
- # 1. Thermal Shock Check
- air_t = atmos.get('air_temp', 25)
- water_t = water.get('water_temp', 20)
- if abs(air_t - water_t) > 10:
- conflicts.append(f"CRITICAL: Thermal Shock Risk. Air ({air_t}C) and Water ({water_t}C) delta > 10C.")
-
- # 2. Transpiration vs Uptake Check
- # High VPD (Dry) + High EC (Salty) = Burn Risk
- rh = atmos.get('humidity', 60)
- ec = water.get('ec', 1.0)
- if rh < 50 and ec > 2.0:
- conflicts.append(f"STRESS: Low Humidity ({rh}%) + High EC ({ec}) will cause Tip Burn.")
-
- return conflicts
-
-def validate_hard_limits(plan):
- violations = []
- # Hard limits for Lettuce/General Hydroponics
- if plan.get('ph', 6.0) < 5.0: violations.append("pH < 5.0 is toxic.")
- if plan.get('ph', 6.0) > 7.5: violations.append("pH > 7.5 causes lockout.")
- if plan.get('ec', 1.0) > 3.0: violations.append("EC > 3.0 is too high for lettuce.")
- if plan.get('humidity', 60) > 85: violations.append("Humidity > 85% guarantees mold.")
-
- return violations
-
-# --- STATE DEFINITION ---
-from typing import TypedDict, Optional, Dict, Any, List
-
-class SupervisorState(TypedDict):
- # Inputs
- atmos_plan: Dict[str, Any]
- water_plan: Dict[str, Any]
- strategy_advice: str # Kept as advice, not law
-
- # Processing
- merged_plan: Dict[str, Any]
- review_notes: List[str]
- simulation_health: float
-
- # Output
- final_decision: str # "APPROVE" or "REJECT"
- critique: str # Feedback for sub-agents if Rejected
-
-API_KEY = os.environ.get("GROQ_API_KEY")
-
-class SupervisorAgent:
- def __init__(self, researcher_agent=None):
- self.name = "Supervisor"
- # Bandit is now just an 'Advisor', not an enforcer
- self.bandit = ContextualBandit(n_actions=NUM_ACTIONS, feature_dim=519)
-
- if API_KEY:
- self.model = ChatOpenAI(
- base_url="https://api.groq.com/openai/v1",
- api_key=API_KEY,
- model="llama-3.3-70b-versatile",
- temperature=0.0 # Zero temp for strict judging
- )
-
- self.app = self._build_graph()
+from agent.memory import FarmMemory
- def _build_graph(self):
- workflow = StateGraph(SupervisorState)
+# š¢ NEW: Import the Doctor
+from agent.sub_agents.Doctor import VisionAgent
- # 1. Merge: Combine the two JSONs
- workflow.add_node("merge", self.node_merge)
-
- # 2. Review: Run the 3 Tools (Conflicts, Limits, Physics)
- workflow.add_node("review", self.node_review)
+class SupervisorAgent:
+ def __init__(self, llm_client):
+ self.llm = llm_client
- # 3. Judge: LLM decides if the issues are fatal
- workflow.add_node("judge", self.node_judge)
-
- # Flow
- workflow.set_entry_point("merge")
- workflow.add_edge("merge", "review")
- workflow.add_edge("review", "judge")
- workflow.add_edge("judge", END)
+ # Initialize the Team
+ self.bandit = ContextualBandit(n_actions=NUM_ACTIONS, feature_dim=515)
+ self.bio_memory = FarmMemory()
- return workflow.compile()
+ # š¢ NEW: Initialize the Doctor (Eyes)
+ self.doctor = VisionAgent()
- # --- NODE FUNCTIONS ---
+ # Load saved bandit brain if it exists
+ self.model_path = os.path.join(os.path.dirname(__file__), '../Marl/saved_bandit_state.pkl')
+ # self.bandit.load(self.model_path)
- def node_merge(self, state):
- print(" š Supervisor Merging Plans...")
- # Simple dictionary merge
- merged = {**state['atmos_plan'], **state['water_plan']}
- return {"merged_plan": merged}
-
- def node_review(self, state):
- print(" š Supervisor Running Unit Tests...")
- plan = state['merged_plan']
- notes = []
-
- # Tool 1: Conflict Check
- conflicts = check_cross_domain_conflicts(state['atmos_plan'], state['water_plan'])
- if conflicts:
- notes.extend(conflicts)
-
- # Tool 2: Limit Check
- limits = validate_hard_limits(plan)
- if limits:
- notes.extend(limits)
-
- # Tool 3: Physics Simulator
- # (We reuse your existing prediction engine)
- sim_result = predict_outcome(plan, plan) # Comparing plan vs itself as a snapshot for now
- health = sim_result.get('predicted_health', 100)
+ def _report_to_vector(self, doctor_report):
+ """
+ š¢ NEW: Converts the Doctor's JSON report into the 512-dim vector.
+ Uses semantic hashing to map specific diseases to specific neurons.
+ """
+ vis_vec = np.zeros(512)
+
+ if "detailed_detections" in doctor_report:
+ for detection in doctor_report["detailed_detections"]:
+ label = detection["object"]
+ confidence = detection["confidence"]
+
+ # Hash the label name to an index between 0-511
+ idx = hash(label) % 512
+ vis_vec[idx] += confidence
+
+ return np.clip(vis_vec, 0, 1.0)
+
+ def _build_context(self, visual_vector, sensors):
+ """
+ š¢ UPDATED: Fuses Vision (512) + 3
+ """
+ # Raw Sensor Values
+ ph = sensors.get('pH', 6.0)
+ ec = sensors.get('EC', 1.0)
+ temp = sensors.get('temp', 25.0)
+
+ # 1. Normalize Raw (Direction)
+ raw_ph = (ph - 6.0) / 2.0
+ raw_ec = (ec - 1.0) / 3.0
+ raw_temp = (temp - 25.0) / 40.0
+
+ sensor_features = np.array([
+ raw_ph, raw_ec, raw_temp,
+ ])
- if health < 90:
- notes.append(f"SIMULATION FAIL: Predicted health drops to {health}%. Risk: {sim_result.get('risk_warning')}")
-
- return {"review_notes": notes, "simulation_health": health}
+ return np.concatenate([visual_vector, sensor_features])
+
+ def _get_strategy_instruction(self, strategy_name):
+ """Translates Math Strategy -> Natural Language Orders"""
+ instructions = {
+ "MAINTAIN_CURRENT": "Do NOT recommend changes. System is stable.",
+ "CALIBRATE_SENSORS": "Sensor readings are anomalous. Recommend hardware calibration.",
+ "AGGRESSIVE_PH_DOWN": "Priority: LOWER pH rapidly. Recommend strong acid buffers.",
+ "AGGRESSIVE_PH_UP": "Priority: RAISE pH rapidly. Recommend strong base buffers.",
+ "GENTLE_PH_BALANCING": "pH is drifting. Recommend gentle adjustments only.",
+ "INCREASE_EC_VEG": "Plant needs NITROGEN for vegetative growth.",
+ "INCREASE_EC_BLOOM": "Plant needs PHOSPHORUS/POTASSIUM for flowering.",
+ "LOWER_EC_FLUSH": "Nutrient burn detected. Recommend flushing reservoir.",
+ "CALMAG_BOOST": "Calcium/Magnesium deficiency detected. Recommend CalMag supplement.",
+ "RAISE_TEMP_HUMIDITY": "Environment too cold/dry. Recommend heating/humidifying.",
+ "LOWER_TEMP_HUMIDITY": "Mold risk high. Recommend fans and dehumidifiers.",
+ "MAX_AIR_CIRCULATION": "Stagnant air. Recommend max fan speed.",
+ "FUNGAL_TREATMENT": "Fungal risk. Recommend fungicide and lower humidity.",
+ "PEST_ISOLATION": "Pests detected. Recommend isolation and organic pesticide.",
+ "PRUNE_NECROTIC_LEAVES": "Necrosis detected. Recommend pruning dead matter."
+ }
+ return instructions.get(strategy_name, "Follow standard procedures.")
- def node_judge(self, state):
+ def reason(self, current_fmu, similar_fmus, sub_agent_outputs):
"""
- The LLM looks at the automated test results and makes the final call.
+ The Core Logic: Synthesizes Bandit (Math), Specialists (Science),
+ Qdrant (History), mem0 (Biography), AND Doctor (Vision).
"""
- print(" āļø Supervisor Judging...")
-
- if not state['review_notes']:
- # No issues found by tools
- return {"final_decision": "APPROVE", "critique": "Plan looks solid."}
-
- # If issues exist, ask LLM if they are fatal or acceptable trade-offs
- prompt = f"""
- You are the Quality Assurance Supervisor.
-
- PROPOSED PLAN: {state['merged_plan']}
-
- AUTOMATED TEST FAILURES:
- {json.dumps(state['review_notes'], indent=2)}
-
- ADVISORY STRATEGY: {state['strategy_advice']}
-
- TASK:
- 1. If the failures are dangerous (Toxic pH, Thermal Shock, Low Health), REJECT the plan.
- 2. If the failures are minor or necessary for the Strategy (e.g., Low Humidity required for 'Fungal Treatment'), APPROVE it.
-
- OUTPUT JSON: {{ "verdict": "APPROVE" or "REJECT", "critique": "Explanation..." }}
+ payload = current_fmu['payload']
+ sensors = payload['sensors']
+
+ # š¢ NEW: Extract Image Path
+ image_path = payload.get('image_path', None)
+ crop_id = payload.get('crop_id', 'General_Zone_1')
+
+ # ---------------------------------------------------------
+ # 0. š¢ THE DOCTOR (Vision Analysis)
+ # ---------------------------------------------------------
+ visual_report = {"scan_summary": "No Image Provided", "detailed_detections": []}
+
+ if image_path and os.path.exists(image_path):
+ print(f"š Doctor Analyzing: {image_path}")
+ visual_report = self.doctor.analyze_frame(image_path)
+ print(f"š Visual Report: {visual_report.get('scan_summary')}")
+
+ # Convert report to vector for the Bandit
+ visual_vector = self._report_to_vector(visual_report)
+
+ # ---------------------------------------------------------
+ # 1. š¢ THE GENERAL (Bandit RL)
+ # ---------------------------------------------------------
+ # Build 515-dim context (Vision + Advanced Sensors)
+ context_vector = self._build_context(visual_vector, sensors)
+
+ action_idx, debug_info = self.bandit.select_action(context_vector)
+ strategic_intent = STRATEGIES[action_idx]
+ specific_order = self._get_strategy_instruction(strategic_intent)
+
+ print(f"š° Bandit Order: {strategic_intent} (Score: {debug_info['scores'][action_idx]:.2f})")
+
+ # ---------------------------------------------------------
+ # 2. š¢ THE EXPERTS (Mini-Agents)
+ # ---------------------------------------------------------
+ if "NUTRIENT" in strategic_intent or "PH" in strategic_intent or "EC" in strategic_intent:
+ highlighted_report = sub_agent_outputs.get("nutrient_report", "No Report")
+ focus_area = "NUTRIENT SPECIALIST"
+ elif "TEMP" in strategic_intent or "HUMIDITY" in strategic_intent:
+ highlighted_report = sub_agent_outputs.get("atmosphere_report", "No Report")
+ focus_area = "ATMOSPHERE SPECIALIST"
+ elif "PEST" in strategic_intent or "FUNGAL" in strategic_intent or "PRUNE" in strategic_intent:
+ # š¢ UPDATED: Use the Doctor's report for bio-threats
+ highlighted_report = f"Visual Diagnosis: {visual_report.get('scan_summary', 'None')}"
+ focus_area = "PLANT DOCTOR"
+ else:
+ highlighted_report = "Standard operational check."
+ focus_area = "ALL SECTORS"
+
+ # ---------------------------------------------------------
+ # 3. š¢ THE HISTORIAN (Qdrant / RAG)
+ # ---------------------------------------------------------
+ history_context = "No relevant global precedents found."
+ if similar_fmus and len(similar_fmus) > 0:
+ history_lines = []
+ for i, fmu in enumerate(similar_fmus):
+ past_action = fmu['payload'].get('action_taken', 'Unknown')
+ past_outcome = fmu['payload'].get('outcome', 'Unknown')
+ score = fmu.get('score', 0.0)
+ history_lines.append(f"- Global Case #{i+1} ({score:.0%} Match): Action '{past_action}' -> Result '{past_outcome}'")
+ history_context = "\n".join(history_lines)
+
+ # ---------------------------------------------------------
+ # 4. š¢ THE BIOGRAPHER (mem0 / Entity Memory)
+ # ---------------------------------------------------------
+ plant_biography = self.bio_memory.get_plant_history(crop_id)
+
+ # ---------------------------------------------------------
+ # 5. š¢ THE COMMANDER (Supervisor LLM)
+ # ---------------------------------------------------------
+ system_prompt = f"""
+ You are the Supervisor of a Hydroponic Farm.
+
+ --- šØ INPUTS FROM YOUR TEAM šØ ---
+
+ [1] INTELLIGENCE REPORT (From {focus_area}):
+ "{highlighted_report}"
+ *Use these facts to justify the decision.*
+
+ [2] VISUAL DIAGNOSIS (From The Doctor):
+ Summary: {json.dumps(visual_report.get('scan_summary'))}
+ Detections: {json.dumps(visual_report.get('detailed_detections'))}
+
+ [3] LIVE SENSORS:
+ {json.dumps(sensors)}
+
+ [4] GLOBAL PRECEDENT (Similar Past Situations):
+ {history_context}
+
+ [5] FULL CONTEXT:
+ All Specialist Reports: {json.dumps(sub_agent_outputs)}
+
+ [6] PATIENT BIOGRAPHY (Specific to {crop_id}):
+ {plant_biography}
+ *CRITICAL: If this specific plant has a history of sensitivity, adjust the plan.*
+
+ [7] STRATEGIC ORDER (From RL):
+ "{strategic_intent}" -> "{specific_order}"
+ *This is your just one metric*
+
+ --- šØ HIERARCHY OF TRUTH (CRITICAL) šØ ---
+ 1. **LIVE SENSORS**: Absolute truth.
+ 2. **VISUAL EVIDENCE**: Strong truth (The Doctor sees the plant and checks for sickness).
+ 3. **BIOGRAPHY**: History (Past truth).
+
+ --- YOUR TASK ---
+ Generate a detailed action plan. Synthesize all the inputs.
+
+ --- āļø STYLE GUIDELINES ---
+ - **Plain English Only.**
+ - **Tone:** Professional, decisive, and clear.
+
+ RESPONSE FORMAT (JSON):
+ {{
+ "decision": "Brief, actionable summary",
+ "reasoning": "Detailed explanation synthesizing Strategy + Visuals + History...",
+ "visual_alert": true/false,
+ "risk_matrix": {{ "nutrients": 0-10, "climate": 0-10, "visuals": 0-10, "history": 0-10 }}
+ }}
"""
try:
- response = self.model.invoke([HumanMessage(content=prompt)])
- content = response.content.replace("```json", "").replace("```", "").strip()
- result = json.loads(content)
+ response = self.llm.chat.completions.create(
+ model="llama-3.1-8b-instant",
+ messages=[
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": f"Current Sensors: {json.dumps(sensors)}"}
+ ],
+ response_format={"type": "json_object"}
+ )
+ decision_json = json.loads(response.choices[0].message.content)
+
+ # ---------------------------------------------------------
+ # 6. š¢ CLOSE THE LOOP (Log to mem0)
+ # ---------------------------------------------------------
+ log_entry = f"Condition: {strategic_intent}. Visuals: {visual_report.get('scan_summary')}. Action: {decision_json['decision']}."
+ self.bio_memory.log_event(crop_id, log_entry)
+
+ # Attach Metadata for RL Training later
+ decision_json["strategic_intent"] = strategic_intent
+ decision_json["bandit_action_idx"] = int(action_idx)
+ decision_json["visual_report"] = visual_report
+ return decision_json
+
+ except Exception as e:
return {
- "final_decision": result.get("verdict", "REJECT"),
- "critique": result.get("critique", "Automated tests failed.")
+ "decision": f"Execute Standard Protocol: {strategic_intent}",
+ "reasoning": f"LLM Generation Failed ({str(e)}). Defaulting to Bandit Strategy.",
+ "strategic_intent": strategic_intent,
+ "bandit_action_idx": int(action_idx)
}
- except:
- # Default to reject if unsafe
- return {"final_decision": "REJECT", "critique": "Plan failed automated safety checks."}
-
- # --- ENTRY POINT ---
-
- def synthesize_plan(self, atmos_plan, water_plan, fmu, history, strategy_info):
- strategy_name, _, action_idx = strategy_info
-
- initial_state = {
- "atmos_plan": atmos_plan,
- "water_plan": water_plan,
- "strategy_advice": strategy_name,
- "merged_plan": {},
- "review_notes": [],
- "simulation_health": 0.0,
- "final_decision": "",
- "critique": ""
- }
-
- result = self.app.invoke(initial_state)
- final_targets = result.get("merged_plan", {})
-
- # š¢ NEW STEP: CONVERT TARGETS TO PHYSICAL ACTIONS
- current_sensors = fmu.metadata.get('sensor_data', {})
-
- print(f"[{self.name}] āļø Converting Targets to Actuator Commands...")
-
- # Calculate physical actions
- physical_action_obj = convert_targets_to_actions(current_sensors, final_targets)
-
- # Convert Pydantic model to Dict for JSON serialization
- final_payload = physical_action_obj.dict()
-
- # Log it
- print(f"[{self.name}] š Activating Hardware: {final_payload}")
-
- # Store in FMU
- fmu.metadata["action_taken"] = str(final_payload)
- fmu.metadata["bandit_action_id"] = action_idx
- fmu.metadata["strategic_intent"] = strategy_name
-
- if "image_b64" in fmu.metadata: del fmu.metadata["image_b64"]
- store_fmu(fmu)
-
- return final_payload
-
- # --- ADVISORY ONLY (Not Enforced) ---
- def get_strategic_goal(self, fmu):
- # (Same as before, but treated as advice now)
- sensors = fmu.metadata.get('sensor_data', {})
- fmu_vector = fmu.vector
- vis_vec = np.array(fmu_vector) if isinstance(fmu_vector, list) else fmu_vector
- if vis_vec is None or len(vis_vec) == 0: vis_vec = np.zeros(516)
-
- s_vec = np.array([
- (float(sensors.get('pH', 6.0)) - 6.0) / 2.0,
- float(sensors.get('EC', 1.0)) / 3.0,
- float(sensors.get('temp', 25.0)) / 40.0
- ])
- context_vector = np.concatenate([vis_vec, s_vec])
-
- action_idx, _ = self.bandit.select_action(context_vector)
- strategy_name = STRATEGIES[action_idx]
-
- return strategy_name, "Advisory Only", int(action_idx)
-\ No newline at end of file
diff --git a/agent/tools/reset_memory.py b/agent/tools/reset_memory.py
@@ -0,0 +1,21 @@
+import os
+from dotenv import load_dotenv
+from qdrant_client import QdrantClient
+
+load_dotenv()
+
+# 1. Connect to Qdrant directly
+client = QdrantClient(
+ url=os.getenv("QDRANT_URL"),
+ api_key=os.getenv("QDRANT_API_KEY"),
+)
+
+collection_name = "plant_biographies"
+
+# 2. Check and Delete
+if client.collection_exists(collection_name):
+ print(f"šļø Deleting mismatched collection: {collection_name}...")
+ client.delete_collection(collection_name)
+ print("ā
Collection deleted. Restart your main script now!")
+else:
+ print(f"ā ļø Collection {collection_name} not found. You are good to go.")
+\ No newline at end of file
diff --git a/backend/server/functions.py b/backend/server/functions.py
@@ -16,7 +16,7 @@ from Qdrant.Client import client
# Initialize Agents ONCE (Global Scope) to save memory
print("š± Initializing Cognitive Stack...")
researcher = ResearcherAgent()
-supervisor = SupervisorAgent(researcher)
+supervisor = SupervisorAgent(researcher.llm)
explainer = ExplainerAgent(supervisor.llm)
print("ā
Agents Ready.")
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
@@ -16062,6 +16062,23 @@
}
}
},
+ "node_modules/tailwindcss/node_modules/yaml": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
+ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
+ "license": "ISC",
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
"node_modules/tapable": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
diff --git a/requirements.txt b/requirements.txt
@@ -7,7 +7,7 @@ python-multipart
# We force version 1.7.0+ to ensure .search() and .search_batch() exist
qdrant-client>=1.7.0
-# --- AI & Image Processing ---
+# --- AI ---
numpy
pillow
torch
@@ -19,6 +19,9 @@ fastembed
openai
pypdf
groq
+mem0ai
+sentence-transformers
+ultralytics
# --- OpenAI CLIP (Vision Encoder) ---
# This installs directly from GitHub because it's not on standard PyPI
diff --git a/runs/detect/train/args.yaml b/runs/detect/train/args.yaml
@@ -0,0 +1,108 @@
+task: detect
+mode: train
+model: yolov8n.pt
+data: agent/training_data/data.yaml
+epochs: 10
+time: null
+patience: 100
+batch: 16
+imgsz: 416
+save: true
+save_period: -1
+cache: false
+device: cpu
+workers: 8
+project: null
+name: train
+exist_ok: false
+pretrained: true
+optimizer: auto
+verbose: true
+seed: 0
+deterministic: true
+single_cls: false
+rect: false
+cos_lr: false
+close_mosaic: 10
+resume: false
+amp: true
+fraction: 1.0
+profile: false
+freeze: null
+multi_scale: 0.0
+compile: false
+overlap_mask: true
+mask_ratio: 4
+dropout: 0.0
+val: true
+split: val
+save_json: false
+conf: null
+iou: 0.7
+max_det: 300
+half: false
+dnn: false
+plots: true
+source: null
+vid_stride: 1
+stream_buffer: false
+visualize: false
+augment: false
+agnostic_nms: false
+classes: null
+retina_masks: false
+embed: null
+show: false
+save_frames: false
+save_txt: false
+save_conf: false
+save_crop: false
+show_labels: true
+show_conf: true
+show_boxes: true
+line_width: null
+format: torchscript
+keras: false
+optimize: false
+int8: false
+dynamic: false
+simplify: true
+opset: null
+workspace: null
+nms: false
+lr0: 0.01
+lrf: 0.01
+momentum: 0.937
+weight_decay: 0.0005
+warmup_epochs: 3.0
+warmup_momentum: 0.8
+warmup_bias_lr: 0.1
+box: 7.5
+cls: 0.5
+dfl: 1.5
+pose: 12.0
+kobj: 1.0
+rle: 1.0
+angle: 1.0
+nbs: 64
+hsv_h: 0.015
+hsv_s: 0.7
+hsv_v: 0.4
+degrees: 0.0
+translate: 0.1
+scale: 0.5
+shear: 0.0
+perspective: 0.0
+flipud: 0.0
+fliplr: 0.5
+bgr: 0.0
+mosaic: 1.0
+mixup: 0.0
+cutmix: 0.0
+copy_paste: 0.0
+copy_paste_mode: flip
+auto_augment: randaugment
+erasing: 0.4
+cfg: null
+tracker: botsort.yaml
+save_dir: C:\Debarghya\IIT Guwahati\Second Year\Sem 4\Convolve\Code\runs\detect\train
diff --git a/runs/detect/train2/args.yaml b/runs/detect/train2/args.yaml
@@ -0,0 +1,108 @@
+task: detect
+mode: train
+model: yolov8n.pt
+data: agent/training_data/data.yaml
+epochs: 10
+time: null
+patience: 100
+batch: 16
+imgsz: 416
+save: true
+save_period: -1
+cache: false
+device: cpu
+workers: 8
+project: null
+name: train2
+exist_ok: false
+pretrained: true
+optimizer: auto
+verbose: true
+seed: 0
+deterministic: true
+single_cls: false
+rect: false
+cos_lr: false
+close_mosaic: 10
+resume: false
+amp: true
+fraction: 1.0
+profile: false
+freeze: null
+multi_scale: 0.0
+compile: false
+overlap_mask: true
+mask_ratio: 4
+dropout: 0.0
+val: true
+split: val
+save_json: false
+conf: null
+iou: 0.7
+max_det: 300
+half: false
+dnn: false
+plots: true
+source: null
+vid_stride: 1
+stream_buffer: false
+visualize: false
+augment: false
+agnostic_nms: false
+classes: null
+retina_masks: false
+embed: null
+show: false
+save_frames: false
+save_txt: false
+save_conf: false
+save_crop: false
+show_labels: true
+show_conf: true
+show_boxes: true
+line_width: null
+format: torchscript
+keras: false
+optimize: false
+int8: false
+dynamic: false
+simplify: true
+opset: null
+workspace: null
+nms: false
+lr0: 0.01
+lrf: 0.01
+momentum: 0.937
+weight_decay: 0.0005
+warmup_epochs: 3.0
+warmup_momentum: 0.8
+warmup_bias_lr: 0.1
+box: 7.5
+cls: 0.5
+dfl: 1.5
+pose: 12.0
+kobj: 1.0
+rle: 1.0
+angle: 1.0
+nbs: 64
+hsv_h: 0.015
+hsv_s: 0.7
+hsv_v: 0.4
+degrees: 0.0
+translate: 0.1
+scale: 0.5
+shear: 0.0
+perspective: 0.0
+flipud: 0.0
+fliplr: 0.5
+bgr: 0.0
+mosaic: 1.0
+mixup: 0.0
+cutmix: 0.0
+copy_paste: 0.0
+copy_paste_mode: flip
+auto_augment: randaugment
+erasing: 0.4
+cfg: null
+tracker: botsort.yaml
+save_dir: C:\Debarghya\IIT Guwahati\Second Year\Sem 4\Convolve\Code\runs\detect\train2
diff --git a/runs/detect/train2/labels.jpg b/runs/detect/train2/labels.jpg
Binary files differ.
diff --git a/runs/detect/train2/train_batch0.jpg b/runs/detect/train2/train_batch0.jpg
Binary files differ.
diff --git a/runs/detect/train2/train_batch1.jpg b/runs/detect/train2/train_batch1.jpg
Binary files differ.
diff --git a/runs/detect/train2/train_batch2.jpg b/runs/detect/train2/train_batch2.jpg
Binary files differ.