Spaces:
Sleeping
Sleeping
| import requests | |
| import cv2 | |
| import numpy as np | |
| from datetime import datetime | |
| from pathlib import Path | |
| import logging | |
| import base64 | |
| import io | |
| import json | |
| logger = logging.getLogger(__name__) | |
| class DiscordAlertManager: | |
| """Manages Discord webhook alerts for hygiene violations.""" | |
| def __init__(self, discord_config: dict): | |
| """ | |
| discord_config: { | |
| 'webhook_url': 'your_webhook_url' | |
| } | |
| """ | |
| self.webhook_url = discord_config['webhook_url'] | |
| self.alert_cooldown = 300 | |
| self.last_alert_time = None | |
| self.dirty_start_time = None | |
| self.dirty_threshold_seconds = 10 | |
| self.dirty_coverage_threshold = 0.06 | |
| def should_send_alert(self, dirty_coverage: float, current_time: datetime) -> bool: | |
| """Same logic as before""" | |
| if dirty_coverage < self.dirty_coverage_threshold: | |
| self.dirty_start_time = None | |
| return False | |
| if self.dirty_start_time is None: | |
| self.dirty_start_time = current_time | |
| return False | |
| dirty_duration = (current_time - self.dirty_start_time).total_seconds() | |
| if dirty_duration < self.dirty_threshold_seconds: | |
| return False | |
| if self.last_alert_time is not None: | |
| time_since_last = (current_time - self.last_alert_time).total_seconds() | |
| if time_since_last < self.alert_cooldown: | |
| return False | |
| return True | |
| def generate_heatmap_image(self, frame: np.ndarray, gmm_heatmap: np.ndarray, | |
| output_path: str) -> str: | |
| """Generate heatmap visualization""" | |
| result = frame.copy() | |
| height, width = result.shape[:2] | |
| if gmm_heatmap.shape != (height, width): | |
| heatmap_resized = cv2.resize(gmm_heatmap, (width, height)) | |
| else: | |
| heatmap_resized = gmm_heatmap | |
| heatmap_colored = cv2.applyColorMap( | |
| (heatmap_resized * 255).astype(np.uint8), | |
| cv2.COLORMAP_JET | |
| ) | |
| alpha = 0.5 | |
| result = cv2.addWeighted(frame, 1 - alpha, heatmap_colored, alpha, 0) | |
| # Add info panel | |
| avg_dirt = np.mean(heatmap_resized) | |
| max_dirt = np.max(heatmap_resized) | |
| dirty_pixels = np.sum(heatmap_resized > 0.60) | |
| coverage_percent = (dirty_pixels / heatmap_resized.size) * 100 | |
| cv2.rectangle(result, (10, 10), (400, 120), (0, 0, 0), -1) | |
| cv2.rectangle(result, (10, 10), (400, 120), (255, 255, 255), 2) | |
| info_text = [ | |
| f"Average Dirt: {avg_dirt:.2f}", | |
| f"Maximum Dirt: {max_dirt:.2f}", | |
| f"Red Zone: {coverage_percent:.1f}%", | |
| f"Time: {datetime.now().strftime('%H:%M:%S')}" | |
| ] | |
| for i, text in enumerate(info_text): | |
| cv2.putText(result, text, (20, 35 + i * 25), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1) | |
| cv2.imwrite(output_path, result) | |
| return output_path | |
| def send_alert(self, camera_name: str, dirty_coverage: float, | |
| dirty_duration: int, frame: np.ndarray, | |
| gmm_heatmap: np.ndarray) -> bool: | |
| """Send Discord webhook alert with embedded image""" | |
| try: | |
| # Generate image | |
| temp_image_path = f"tmp/heatmap_{datetime.now().timestamp()}.png" | |
| self.generate_heatmap_image(frame, gmm_heatmap, temp_image_path) | |
| # Calculate duration | |
| duration_mins = dirty_duration // 60 | |
| duration_secs = dirty_duration % 60 | |
| # Create rich embed | |
| embed = { | |
| "title": "π¨ CLEANING ALERT", | |
| "description": f"**{camera_name}** requires immediate attention!", | |
| "color": 15158332, # Red color (#E74C3C) | |
| "fields": [ | |
| { | |
| "name": "π Location", | |
| "value": camera_name, | |
| "inline": True | |
| }, | |
| { | |
| "name": "π΄ Coverage", | |
| "value": f"{dirty_coverage*100:.1f}%", | |
| "inline": True | |
| }, | |
| { | |
| "name": "β± Duration", | |
| "value": f"{duration_mins}m {duration_secs}s", | |
| "inline": True | |
| }, | |
| { | |
| "name": "β οΈ Action Required", | |
| "value": "Table has exceeded cleanliness threshold and needs cleaning.", | |
| "inline": False | |
| } | |
| ], | |
| "footer": { | |
| "text": "Kitchen Hygiene Monitoring System" | |
| }, | |
| "timestamp": datetime.utcnow().isoformat() | |
| } | |
| # Prepare webhook payload with embeds | |
| payload = { | |
| "username": "Hygiene Monitor Bot", | |
| "avatar_url": "https://cdn-icons-png.flaticon.com/512/3699/3699516.png", | |
| "embeds": [embed] | |
| } | |
| # Read the image file | |
| with open(temp_image_path, 'rb') as f: | |
| image_data = f.read() | |
| # Prepare the multipart form data | |
| files = { | |
| 'payload_json': (None, json.dumps(payload), 'application/json'), | |
| 'file': ('heatmap.png', image_data, 'image/png') | |
| } | |
| # Send the request | |
| response = requests.post(self.webhook_url, files=files) | |
| if response.status_code in [200, 204]: | |
| self.last_alert_time = datetime.now() | |
| logger.info(f"β Discord alert sent for {camera_name}") | |
| Path(temp_image_path).unlink(missing_ok=True) | |
| return True | |
| else: | |
| logger.error(f"Discord webhook error: {response.status_code} - {response.text}") | |
| return False | |
| except Exception as e: | |
| logger.error(f"Failed to send Discord alert: {str(e)}") | |
| import traceback | |
| logger.error(traceback.format_exc()) | |
| return False |