demeter

Autonomous Hydroponic Intelligence
commit 1b89d08df121148fe294fde8e149e25ab1e9d2d5
parent 0dcde6cc427784b12d37964149cc6cadcea53628
Author: Debarghya Das <debarghya1108@gmail.com>
Date:   Wed,  4 Mar 2026 11:40:32 +0000

Merge PR

Diffstat:
M.gitignore | 9+++++----
DQdrant/Search.py | 0
Magent/Marl/bandit.py | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Aagent/Marl/train-bandit.py | 269+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aagent/memory.py | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aagent/sub_agents/Doctor.py | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Magent/sub_agents/Supervisor.py | 436++++++++++++++++++++++++++++++++++++++++---------------------------------------
Aagent/tools/reset_memory.py | 22++++++++++++++++++++++
Mbackend/server/functions.py | 2+-
Mfrontend/package-lock.json | 17+++++++++++++++++
Mrequirements.txt | 5++++-
Aruns/detect/train/args.yaml | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aruns/detect/train2/args.yaml | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aruns/detect/train2/labels.jpg | 0
Aruns/detect/train2/train_batch0.jpg | 0
Aruns/detect/train2/train_batch1.jpg | 0
Aruns/detect/train2/train_batch2.jpg | 0
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.