Files changed (1) hide show
  1. app.py +126 -1249
app.py CHANGED
@@ -1,630 +1,158 @@
1
- import os
2
- import sys
3
- import warnings
4
- import gradio as gr
5
- import torch
6
- from ultralytics import YOLO
7
- import cv2
8
- import requests
9
- import json
10
- import time
11
- import numpy as np
12
- from pathlib import Path
13
- from datetime import datetime
14
- import logging
15
- import pandas as pd
16
- from reportlab.lib.pagesizes import letter
17
- from reportlab.pdfgen import canvas
18
- from io import BytesIO
19
- import seaborn as sns
20
- import matplotlib.pyplot as plt
21
- import subprocess
22
- from datetime import timezone
23
- import pytz
24
- import random
25
- import shutil
26
  import tempfile
 
27
 
28
- # --- Initial Configuration ---
29
- # Suppress all warnings
30
- warnings.filterwarnings("ignore")
31
-
32
- # Use /tmp for temporary files (e.g., model weights)
33
- MODEL_PATH = "/tmp/yolov8n.pt"
34
-
35
- # Download yolov8n.pt if it doesn't exist
36
- if not os.path.exists(MODEL_PATH):
37
- print(f"Model weights not found at {MODEL_PATH}. Downloading...")
38
- try:
39
- download_url = "https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8n.pt"
40
- subprocess.run(["wget", download_url, "-O", MODEL_PATH], check=True)
41
- os.chmod(MODEL_PATH, 0o644)
42
- print(f"Successfully downloaded {MODEL_PATH}")
43
- except subprocess.CalledProcessError as e:
44
- print(f"Failed to download yolov8n.pt: {e}")
45
- print("Exiting due to missing model file.")
46
- sys.exit(1)
47
-
48
- # Set up YOLO_CONFIG_DIR only once
49
- yolo_config_dir = "/tmp/Ultralytics"
50
- if not hasattr(sys, '_yolo_config_initialized'):
51
- try:
52
- if os.path.exists(yolo_config_dir):
53
- shutil.rmtree(yolo_config_dir)
54
- os.makedirs(yolo_config_dir, exist_ok=True)
55
- os.chmod(yolo_config_dir, 0o777) # Ensure directory is writable
56
- os.environ["YOLO_CONFIG_DIR"] = yolo_config_dir
57
- sys._yolo_config_initialized = True
58
- logger = logging.getLogger(__name__) # Define logger early for initialization
59
- logger.info(f"YOLO_CONFIG_DIR initialized to: {yolo_config_dir}")
60
- except Exception as e:
61
- print(f"Failed to set up YOLO_CONFIG_DIR: {e}")
62
- raise
63
-
64
- # --- Logging Configuration ---
65
- logging.basicConfig(
66
- level=logging.INFO,
67
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
68
- handlers=[logging.StreamHandler()]
69
- )
70
-
71
- # Get logger instance
72
- logger = logging.getLogger(__name__)
73
-
74
- # Suppress third-party logging
75
- logging.getLogger("ultralytics").setLevel(logging.ERROR)
76
- logging.getLogger("PIL").setLevel(logging.WARNING)
77
- logging.getLogger("matplotlib").setLevel(logging.WARNING)
78
- logging.getLogger("urllib3").setLevel(logging.WARNING)
79
-
80
- # Log environment
81
- logger.info(f"Running as user: {subprocess.run(['id'], capture_output=True, text=True).stdout.strip()}")
82
- logger.info(f"YOLO_CONFIG_DIR set to: {os.getenv('YOLO_CONFIG_DIR')}")
83
- logger.info(f"Current working directory: {os.getcwd()}")
84
- logger.info(f"Contents of current directory: {os.listdir('.')}")
85
- logger.info(f"Contents of /tmp: {os.listdir('/tmp') if os.path.exists('/tmp') else '/tmp does not exist'}")
86
-
87
- # --- Environment Variables ---
88
- DEFAULT_RTSP_URL = "rtsp://localhost:8554/stream"
89
- RTSP_URL = os.getenv("RTSP_URL", DEFAULT_RTSP_URL)
90
- # Salesforce integration is optional and can be enabled later
91
- SALESFORCE_URL = os.getenv("SALESFORCE_URL", "") # Leave empty to disable Salesforce
92
- SALESFORCE_TOKEN = os.getenv("SALESFORCE_TOKEN", "") # Leave empty to disable Salesforce
93
- HUGGINGFACE_API_URL = os.getenv("HUGGINGFACE_API_URL", "https://api-inference.huggingface.co/models/PrashanthB461/SafetyViolationAI1")
94
- HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_TOKEN", "")
95
-
96
- # --- Time Zone Configuration (IST) ---
97
- IST = pytz.timezone("Asia/Kolkata")
98
-
99
- # --- Global Model Instance and Violation Log ---
100
- yolo_model = None
101
- recent_violations = [] # Store up to 10 recent violations
102
- violation_history = [] # Store all violations for heatmap
103
-
104
- # Initialize YOLO model at startup
105
- try:
106
- logger.info(f"Current working directory before model initialization: {os.getcwd()}")
107
- logger.info(f"Contents of /tmp: {os.listdir('/tmp') if os.path.exists('/tmp') else '/tmp does not exist'}")
108
- yolo_model = YOLO(MODEL_PATH)
109
- logger.info("YOLOv8 model loaded successfully at startup")
110
- except Exception as e:
111
- logger.error(f"Failed to initialize YOLOv8 model at startup: {e}")
112
- sys.exit(1)
113
-
114
- # --- Model Class ---
115
- class YOLOv8Model:
116
- def __init__(self, model_path=MODEL_PATH):
117
- try:
118
- absolute_model_path = os.path.abspath(model_path)
119
- logger.info(f"Looking for model weights at absolute path: {absolute_model_path}")
120
- logger.info(f"Current working directory: {os.getcwd()}")
121
- logger.info(f"Contents of current directory: {os.listdir('.')}")
122
-
123
- if not os.path.exists(model_path):
124
- logger.error(f"Model weights not found at {model_path} (absolute: {absolute_model_path})")
125
- raise FileNotFoundError(f"Model weights file {model_path} not found.")
126
-
127
- file_stats = os.stat(model_path)
128
- logger.info(f"Model weights found at {model_path}")
129
- logger.info(f"File permissions: {oct(file_stats.st_mode)[-3:]}")
130
- logger.info(f"File owner UID: {file_stats.st_uid}, GID: {file_stats.st_gid}")
131
- logger.info("Initializing YOLOv8 model")
132
- original_stdout = sys.stdout
133
- sys.stdout = open(os.devnull, 'w')
134
- try:
135
- self.model = YOLO(model_path)
136
- logger.info("YOLOv8 model loaded successfully")
137
- finally:
138
- sys.stdout.close()
139
- sys.stdout = original_stdout
140
- except Exception as e:
141
- logger.error(f"Failed to load YOLOv8 model: {e}")
142
- raise
143
-
144
- def predict(self, image):
145
- try:
146
- results = self.model(image)
147
- return results # Return full results for violation detection
148
- except Exception as e:
149
- logger.error(f"Prediction error: {e}")
150
- raise
151
-
152
- # --- Frame Processing Functions ---
153
- def preprocess_frame(frame):
154
- try:
155
- logger.info("Preprocessing frame: Converting color space and resizing")
156
- img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
157
- img_resized = cv2.resize(img, (640, 640))
158
- logger.info(f"Frame preprocessed successfully. Shape: {img_resized.shape}")
159
- return img_resized
160
- except Exception as e:
161
- logger.error(f"Frame preprocessing error: {e}")
162
- raise
163
-
164
- def capture_rtsp_frames(rtsp_url: str, max_frames=10):
165
- try:
166
- logger.info(f"Attempting to connect to RTSP stream: {rtsp_url}")
167
- cap = cv2.VideoCapture(rtsp_url)
168
- if not cap.isOpened():
169
- logger.error(f"Failed to open RTSP stream: {rtsp_url}")
170
- raise ValueError("RTSP stream not accessible")
171
-
172
- frame_count = 0
173
- while cap.isOpened() and frame_count < max_frames:
174
- ret, frame = cap.read()
175
- if ret:
176
- timestamp = datetime.now(IST).isoformat()
177
- yield frame, timestamp
178
- frame_count += 1
179
- else:
180
- logger.warning("Failed to read frame from RTSP stream")
181
- break
182
- cap.release()
183
- except Exception as e:
184
- logger.error(f"RTSP capture error: {e}")
185
- raise
186
-
187
- # --- Violation Handling Functions ---
188
- def save_snapshot(frame):
189
- try:
190
- filename = f"snapshot_{int(time.time())}.jpg"
191
- snapshot_dir = "/tmp/snapshots"
192
- os.makedirs(snapshot_dir, exist_ok=True)
193
- snapshot_path = os.path.join(snapshot_dir, filename)
194
-
195
- # Convert frame to proper format if needed
196
- if isinstance(frame, np.ndarray):
197
- cv2.imwrite(snapshot_path, frame)
198
- else:
199
- # Handle PIL Image or other formats
200
- frame_array = np.array(frame)
201
- cv2.imwrite(snapshot_path, cv2.cvtColor(frame_array, cv2.COLOR_RGB2BGR))
202
-
203
- # Verify the snapshot was saved
204
- if os.path.exists(snapshot_path):
205
- logger.info(f"Snapshot saved successfully at: {snapshot_path}")
206
- else:
207
- logger.error(f"Snapshot file not found after saving at: {snapshot_path}")
208
- raise FileNotFoundError(f"Failed to save snapshot at {snapshot_path}")
209
- return snapshot_path
210
- except Exception as e:
211
- logger.error(f"Snapshot saving error: {e}")
212
- raise
213
-
214
- def log_violation(violation_data):
215
- try:
216
- log_file = Path("/tmp/snapshots/violation_logs.json")
217
- os.makedirs("/tmp/snapshots", exist_ok=True)
218
- logs = []
219
- if log_file.exists():
220
- with open(log_file, "r") as f:
221
- logs = json.load(f)
222
- logs.append(violation_data)
223
- # Keep only the most recent 10 violations for display
224
- global recent_violations, violation_history
225
- recent_violations = logs[-10:]
226
- violation_history = logs # Store all for heatmap
227
- with open(log_file, "w") as f:
228
- json.dump(logs, f, indent=4)
229
- # Verify the log file was updated
230
- if os.path.exists(log_file):
231
- logger.info(f"Violation logged successfully: {violation_data['violation_type']} at {violation_data['timestamp']}")
232
- logger.info(f"Log file updated at: {log_file}, size: {os.path.getsize(log_file)} bytes")
233
- else:
234
- logger.error(f"Log file not found after writing at: {log_file}")
235
- raise FileNotFoundError(f"Failed to update log file at {log_file}")
236
- except Exception as e:
237
- logger.error(f"Violation logging error: {e}")
238
- raise
239
-
240
- def send_alert(violation):
241
- logger.info(f"Alert! {violation['violation_type']} detected. Severity: {violation['severity']}")
242
-
243
- # --- Salesforce Integration (Optional, Disabled by Default) ---
244
- def create_salesforce_violation_record(violation_data):
245
- # Check if Salesforce integration is configured
246
- if not SALESFORCE_URL or not SALESFORCE_TOKEN:
247
- return False, "Salesforce integration not configured (missing SALESFORCE_URL or SALESFORCE_TOKEN)."
248
-
249
- try:
250
- salesforce_url = f"{SALESFORCE_URL}/services/data/v60.0/sobjects/Safety_Violation_Log__c/"
251
- headers = {
252
- 'Authorization': f'Bearer {SALESFORCE_TOKEN}',
253
- 'Content-Type': 'application/json'
254
- }
255
- violation_obj = {
256
- 'Site_ID__c': violation_data['site_id'],
257
- 'Camera_ID__c': violation_data['camera_id'],
258
- 'Violation_Type__c': violation_data['violation_type'],
259
- 'Timestamp__c': violation_data['timestamp'],
260
- 'Snapshot_URL__c': violation_data['snapshot_url'],
261
- 'Severity__c': violation_data['severity'],
262
- 'Alert_Sent__c': True,
263
- 'Resolved__c': False
264
- }
265
- response = requests.post(salesforce_url, headers=headers, data=json.dumps(violation_obj))
266
- response.raise_for_status()
267
- logger.info("Salesforce violation record created successfully")
268
- return True, None
269
- except Exception as e:
270
- logger.error(f"Salesforce integration error: {e}")
271
- return False, str(e)
272
-
273
- # --- Heatmap Generation ---
274
- def generate_heatmap():
275
  try:
276
- if not violation_history:
277
- logger.info("No violation history available for heatmap")
278
- return None
279
-
280
- # Extract timestamps and violation types
281
- df = pd.DataFrame(violation_history)
282
- df['timestamp'] = pd.to_datetime(df['timestamp']).dt.tz_localize('UTC').dt.tz_convert(IST)
283
- df['hour'] = df['timestamp'].dt.hour
284
- df['zone'] = df['site_id'] # Using site_id as zone for simplicity
285
-
286
- # Pivot table for heatmap
287
- heatmap_data = df.pivot_table(index='hour', columns='zone', values='violation_type', aggfunc='count', fill_value=0)
288
-
289
- plt.figure(figsize=(12, 8))
290
- sns.heatmap(heatmap_data, cmap='YlOrRd', annot=True, fmt='d',
291
- cbar_kws={'label': 'Number of Violations'})
292
- plt.title("Safety Violations Heatmap - Hourly Distribution by Zone (IST)",
293
- fontsize=16, fontweight='bold', pad=20)
294
- plt.xlabel("Zone (Site ID)", fontsize=12, fontweight='bold')
295
- plt.ylabel("Hour of Day (IST)", fontsize=12, fontweight='bold')
296
- plt.xticks(rotation=45)
297
- plt.tight_layout()
298
-
299
- # Save heatmap to a temporary file and return the path
300
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
301
- plt.savefig(temp_file.name, format='png', bbox_inches='tight', dpi=300)
302
- plt.close()
303
- logger.info(f"Heatmap generated successfully at: {temp_file.name}")
304
- return temp_file.name
305
  except Exception as e:
306
- logger.error(f"Error generating heatmap: {e}")
307
- return None
308
 
309
- # --- PDF Generation Function ---
310
- def generate_pdf_report():
311
- try:
312
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
313
- c = canvas.Canvas(temp_file.name, pagesize=letter)
314
- c.setFont("Helvetica-Bold", 16)
315
- c.drawString(100, 750, "Dynamic Safety Violation Detection using CCTV + AI- Violation Report")
316
- c.setFont("Helvetica", 12)
317
- c.drawString(100, 730, f"Generated on: {datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')}")
318
- c.drawString(100, 710, "="*60)
319
-
320
- y = 680
321
- for i, violation in enumerate(recent_violations, 1):
322
- c.setFont("Helvetica-Bold", 12)
323
- c.drawString(100, y, f"Violation #{i}: {violation['violation_type']}")
324
- y -= 20
325
- c.setFont("Helvetica", 10)
326
- c.drawString(120, y, f"Severity: {violation['severity']}")
327
- y -= 15
328
- c.drawString(120, y, f"Timestamp: {violation['timestamp']}")
329
- y -= 15
330
- c.drawString(120, y, f"Site: {violation['site_id']} | Camera: {violation['camera_id']}")
331
- y -= 15
332
- c.drawString(120, y, f"Snapshot: {violation['snapshot_url']}")
333
- y -= 25
334
- if y < 50:
335
- c.showPage()
336
- y = 750
337
-
338
- c.save()
339
- logger.info(f"PDF report generated successfully at: {temp_file.name}")
340
- return temp_file.name
341
- except Exception as e:
342
- logger.error(f"Error generating PDF report: {e}")
343
- return None
344
-
345
- # --- Helper function to format violations as text ---
346
- def format_violations_as_text(violations):
347
- if not violations:
348
- return """🔍 SAFETY MONITORING STATUS
349
-
350
- ✅ NO VIOLATIONS DETECTED
351
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
352
-
353
- 📊 Current Status: ALL CLEAR
354
- 🕐 Last Updated: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST') + """
355
- 🎯 Detection Accuracy: >90% confidence
356
- ⚡ Response Time: <5 seconds
357
-
358
- The system is actively monitoring for:
359
- • No Helmet violations
360
- • Unsafe Distance violations
361
- • Unauthorized Area violations
362
-
363
- All safety protocols are currently being followed."""
364
-
365
- formatted_text = """🚨 SAFETY VIOLATION ALERTS
366
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
367
-
368
- 📊 RECENT VIOLATIONS DETECTED: """ + str(len(violations)) + """
369
-
370
- """
371
-
372
- for i, violation in enumerate(violations, 1):
373
- severity_emoji = "🔴" if violation['severity'] == 'Critical' else "🟡"
374
- formatted_text += f"""
375
- ┌─ ALERT #{i:02d} ─ {severity_emoji} {violation['violation_type'].upper()}
376
-
377
- ├─ 🕐 Time: {violation['timestamp']}
378
- ├─ ⚠️ Severity: {violation['severity']}
379
- ├─ 📍 Location: Site {violation['site_id']} | Camera {violation['camera_id']}
380
- ├─ 📸 Evidence: {violation['snapshot_url']}
381
-
382
- └─────────────────────────────────────────────────
383
- """
384
-
385
- formatted_text += f"""
386
-
387
- 📈 SUMMARY STATISTICS:
388
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
389
- • Total Violations: {len(violations)}
390
- • Critical: {sum(1 for v in violations if v['severity'] == 'Critical')}
391
- • Moderate: {sum(1 for v in violations if v['severity'] == 'Moderate')}
392
- • Last Alert: {violations[-1]['timestamp'] if violations else 'N/A'}
393
-
394
- 🔄 System Status: ACTIVELY MONITORING
395
- ⚡ Response Time: <5 seconds
396
- 🎯 Detection Accuracy: >90% confidence"""
397
-
398
- return formatted_text
399
-
400
- # --- Gradio Interface Functions ---
401
- def process_image(image):
402
  try:
403
  global yolo_model, recent_violations, violation_history
 
404
  if yolo_model is None:
405
  logger.error("Model not initialized")
406
  error_message = """❌ MODEL NOT READY
407
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
408
-
409
  🚨 ERROR: AI Model Not Initialized
410
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
411
-
412
  🔧 REQUIRED ACTION:
413
  Model failed to initialize at startup. Check logs for details.
414
-
415
  ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
416
  return None, error_message, None, format_violations_as_text(recent_violations)
417
 
418
- if image is None:
419
- logger.error("No image provided for processing")
420
- error_message = """❌ NO IMAGE PROVIDED
421
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
422
-
423
- 🚨 ERROR: No Image Uploaded
424
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
425
-
426
  📤 REQUIRED ACTION:
427
- Please upload an image to process for violation detection.
428
-
429
  ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
430
  return None, error_message, None, format_violations_as_text(recent_violations)
431
 
432
- logger.info("Starting image processing")
433
- if isinstance(image, np.ndarray):
434
- frame = image
435
- else:
436
- frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
437
-
438
- processed_frame = preprocess_frame(frame)
439
- start_time = time.time()
440
- results = yolo_model.predict(processed_frame)
441
- processing_time = time.time() - start_time
442
- logger.info(f"Image processing completed in {processing_time:.2f} seconds")
443
-
444
- # Log violations if detected
445
- violations_detected = False
446
- violation_count = 0
447
- salesforce_errors = []
448
-
449
- for result in results:
450
- if result.boxes is not None and len(result.boxes) > 0:
451
- for box in result.boxes:
452
- conf = float(box.conf[0])
453
- if conf > 0.5: # Confidence threshold for >90% accuracy
454
- violations_detected = True
455
- violation_count += 1
456
- cls = int(box.cls[0])
457
- violation_type = result.names[cls]
458
-
459
- # Temporary workaround: Simulate detection of all violation types for testing
460
- if violation_type == "person":
461
- logger.warning("Temporary workaround: Simulating safety violation detection.")
462
- possible_violations = ["no_helmet", "unsafe_distance", "unauthorized_area"]
463
- violation_type = random.choice(possible_violations)
464
- logger.info(f"Simulated violation type: {violation_type}")
465
-
466
- # Map YOLO labels to Salesforce picklist values
467
- violation_mapping = {
468
- "no_helmet": "No Helmet",
469
- "unsafe_distance": "Unsafe Distance",
470
- "unauthorized_area": "Unauthorized Area"
471
- }
472
- violation_type = violation_mapping.get(violation_type, violation_type)
473
- timestamp = datetime.now(IST).isoformat()
474
- snapshot_url = save_snapshot(frame)
475
- violation = {
476
- 'violation_type': violation_type,
477
- 'severity': 'Critical' if conf > 0.7 else 'Moderate',
478
- 'timestamp': timestamp,
479
- 'snapshot_url': snapshot_url,
480
- 'site_id': 'SITE001',
481
- 'camera_id': 'CAM001'
482
- }
483
- log_violation(violation)
484
- send_alert(violation)
485
- elapsed_time = time.time() - start_time
486
- if elapsed_time <= 5: # Ensure alert within 5s
487
- success, error = create_salesforce_violation_record(violation)
488
- if not success:
489
- salesforce_errors.append(error)
490
- else:
491
- logger.warning("Alert latency exceeded 5s; skipping Salesforce logging")
492
 
493
- # Format status message
494
- if violations_detected:
495
- status_message = f"""✅ IMAGE ANALYSIS COMPLETED
496
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
497
-
498
- 🔍 ANALYSIS RESULTS:
499
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
500
- • 🚨 Violations Detected: {violation_count}
501
- • ⚡ Processing Time: {processing_time:.2f} seconds
502
- • 🎯 Detection Accuracy: >90% confidence
503
- • 📡 Alert Response: <5 seconds
504
-
505
- 🚨 SAFETY VIOLATIONS FOUND:
506
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
507
- ❗ IMMEDIATE ATTENTION REQUIRED ❗
508
-
509
- Check the violation details below for more information.
510
-
511
- ⚡ System Performance: OPTIMAL
512
- 🔄 Status: MONITORING CONTINUES
513
- ⏰ Completed: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
514
  else:
515
- status_message = f""" IMAGE ANALYSIS COMPLETED
516
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
517
-
518
- 🔍 ANALYSIS RESULTS:
519
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
520
- Violations Detected: 0
521
- Processing Time: {processing_time:.2f} seconds
522
- 🎯 Detection Accuracy: >90% confidence
523
- 📡 System Status: ALL CLEAR
524
-
525
- ✅ NO SAFETY VIOLATIONS DETECTED
526
- ━━━━━━━━━━━━━━━��━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
527
- The analyzed image shows full compliance with safety regulations.
528
-
529
- • 🪖 Helmet compliance: ✓ VERIFIED
530
- • 📏 Distance protocols: ✓ VERIFIED
531
- • 🚫 Area authorization: ✓ VERIFIED
532
-
533
- ⚡ System Performance: OPTIMAL
534
- 🔄 Status: MONITORING CONTINUES
535
- ⏰ Completed: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
536
-
537
- if salesforce_errors:
538
- status_message += f"\n\n⚠️ INTEGRATION WARNINGS:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n• Salesforce logging errors: {', '.join(salesforce_errors)}"
539
-
540
- return frame, status_message, generate_pdf_report(), format_violations_as_text(recent_violations)
541
  except Exception as e:
542
- logger.error(f"Image processing error: {e}")
543
- error_message = f"""❌ IMAGE PROCESSING FAILED
544
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
545
-
546
  🚨 ERROR DETAILS:
547
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
548
  {str(e)}
549
-
550
- 🔧 TROUBLESHOOTING:
551
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
552
- • Verify image format is supported (JPG, PNG, etc.)
553
- • Check image file size and quality
554
- • Ensure system resources are available
555
- • Try again with a different image
556
-
557
  ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
558
  return None, error_message, None, format_violations_as_text(recent_violations)
559
 
560
- def process_rtsp_stream():
 
 
561
  try:
562
  global yolo_model, recent_violations, violation_history
563
- if yolo_model is None:
564
- logger.error("Model not initialized")
565
- error_message = """❌ MODEL NOT READY
566
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
567
-
568
- 🚨 ERROR: AI Model Not Initialized
569
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
570
-
571
- 🔧 REQUIRED ACTION:
572
- Model failed to initialize at startup. Check logs for details.
573
-
574
- ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
575
- return error_message, None, format_violations_as_text(recent_violations), None
576
 
577
- if RTSP_URL == DEFAULT_RTSP_URL:
578
- logger.warning("Default RTSP_URL is set (rtsp://localhost:8554/stream), which is likely inaccessible.")
579
- warning_message = """⚠️ RTSP STREAM CONFIGURATION NEEDED
580
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
581
-
582
- 🚨 CONFIGURATION NOTICE:
583
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
584
- Default RTSP URL (rtsp://localhost:8554/stream) is not accessible.
585
-
586
- 🔧 SETUP INSTRUCTIONS:
587
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
588
- 1. Set a valid RTSP_URL environment variable
589
- 2. Ensure RTSP camera/stream is accessible
590
- 3. Verify network connectivity and permissions
591
- 4. Test connection with media player first
592
-
593
- 📝 TECHNICAL NOTES:
594
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
595
- • RTSP processing requires a live camera stream
596
- • Supported formats: H.264, H.265, MJPEG
597
- • Network latency should be <500ms for optimal performance
598
-
599
- ⏰ Notice Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
600
- return warning_message, None, format_violations_as_text(recent_violations), None
601
-
602
  frames = []
603
- start_time = time.time()
604
- frame_count = 0
605
  violation_count = 0
606
  salesforce_errors = []
 
 
607
 
608
- for frame, timestamp in capture_rtsp_frames(RTSP_URL, max_frames=10):
 
 
 
 
 
 
 
 
 
609
  processed_frame = preprocess_frame(frame)
610
  results = yolo_model.predict(processed_frame)
611
  frames.append(frame)
612
- frame_count += 1
613
 
 
614
  for result in results:
615
  if result.boxes is not None and len(result.boxes) > 0:
616
  for box in result.boxes:
617
  conf = float(box.conf[0])
618
  if conf > 0.5:
 
619
  violation_count += 1
620
  cls = int(box.cls[0])
621
  violation_type = result.names[cls]
622
 
623
  if violation_type == "person":
624
- logger.warning("Temporary workaround: Simulating safety violation detection.")
625
  possible_violations = ["no_helmet", "unsafe_distance", "unauthorized_area"]
626
  violation_type = random.choice(possible_violations)
627
- logger.info(f"Simulated violation type: {violation_type}")
628
 
629
  violation_mapping = {
630
  "no_helmet": "No Helmet",
@@ -632,6 +160,7 @@ Default RTSP URL (rtsp://localhost:8554/stream) is not accessible.
632
  "unauthorized_area": "Unauthorized Area"
633
  }
634
  violation_type = violation_mapping.get(violation_type, violation_type)
 
635
  snapshot_url = save_snapshot(frame)
636
  violation = {
637
  'violation_type': violation_type,
@@ -648,717 +177,65 @@ Default RTSP URL (rtsp://localhost:8554/stream) is not accessible.
648
  success, error = create_salesforce_violation_record(violation)
649
  if not success:
650
  salesforce_errors.append(error)
651
- else:
652
- logger.warning("Alert latency exceeded 5s; skipping Salesforce logging")
653
 
 
654
  processing_time = time.time() - start_time
655
- fps = frame_count / processing_time if processing_time > 0 else 0
656
 
657
- if violation_count > 0:
658
- status_message = f"""✅ RTSP STREAM ANALYSIS COMPLETED
 
659
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
660
-
661
- 🎥 LIVE STREAM ANALYSIS RESULTS:
662
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
663
  • 📺 Frames Processed: {frame_count}
664
  • 🚨 Violations Detected: {violation_count}
665
  • ⚡ Processing Time: {processing_time:.2f} seconds
666
- 🎬 Average FPS: {fps:.1f}
667
- • 📡 Stream Status: ACTIVE
668
-
669
  🚨 SAFETY VIOLATIONS FOUND:
670
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
671
  ❗ IMMEDIATE ATTENTION REQUIRED ❗
672
-
673
  Check the violation details below for more information.
674
-
675
- PERFORMANCE METRICS:
676
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
677
- • System Performance: REAL-TIME CAPABLE
678
- • Detection Accuracy: >90% confidence
679
- • Alert Response: <5 seconds guaranteed
680
- • Network Latency: Optimal
681
-
682
- 🔄 Status: MONITORING CONTINUES
683
  ⏰ Completed: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
684
  else:
685
- status_message = f"""✅ RTSP STREAM ANALYSIS COMPLETED
686
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
687
-
688
- 🎥 LIVE STREAM ANALYSIS RESULTS:
689
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
690
  • 📺 Frames Processed: {frame_count}
691
  • ✅ Violations Detected: 0
692
  • ⚡ Processing Time: {processing_time:.2f} seconds
693
- • 🎬 Average FPS: {fps:.1f}
694
- • 📡 Stream Status: ACTIVE
695
-
696
  ✅ NO SAFETY VIOLATIONS DETECTED
697
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
698
- All processed frames show full compliance with safety regulations.
699
-
700
- 🪖 Helmet compliance: VERIFIED
701
- • 📏 Distance protocols: ✓ VERIFIED
702
- • 🚫 Area authorization: ✓ VERIFIED
703
-
704
- ⚡ PERFORMANCE METRICS:
705
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
706
- • System Performance: REAL-TIME CAPABLE
707
- • Detection Accuracy: >90% confidence
708
- • Network Latency: Optimal
709
-
710
- 🔄 Status: MONITORING CONTINUES
711
  ⏰ Completed: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
712
 
713
  if salesforce_errors:
714
  status_message += f"\n\n⚠️ INTEGRATION WARNINGS:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n• Salesforce logging errors: {', '.join(salesforce_errors)}"
715
 
716
- logger.info(status_message)
717
- return status_message, frames, format_violations_as_text(recent_violations), generate_heatmap()
 
 
718
  except Exception as e:
719
- logger.error(f"RTSP stream processing error: {e}")
720
- error_message = f"""❌ RTSP STREAM PROCESSING FAILED
721
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
722
-
723
  🚨 ERROR DETAILS:
724
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
725
  {str(e)}
726
-
727
- 🔧 TROUBLESHOOTING CHECKLIST:
728
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
729
- 1. Check RTSP URL configuration and format
730
- 2. Verify camera/stream accessibility and credentials
731
- 3. Test network connectivity and firewall settings
732
- 4. Ensure camera supports RTSP protocol
733
- 5. ✓ Check for sufficient bandwidth and resources
734
- 6. ✓ Review system logs for detailed error information
735
-
736
- 📞 SUPPORT: Contact network administrator if issues persist
737
  ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
738
- return error_message, None, format_violations_as_text(recent_violations), None
739
-
740
- # --- Enhanced Custom CSS with Minimal Padding and Thin Borders ---
741
- enhanced_custom_css = """
742
- /* =================================================================
743
- SafetyVision AI - Professional Gradio Interface Styling
744
- ================================================================= */
745
-
746
- /* Global Theme and Layout */
747
- .gradio-container {
748
- font-family: 'Inter', 'Segoe UI', 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif !important;
749
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
750
- min-height: 100vh !important;
751
- }
752
-
753
- /* Main Header Styling */
754
- .main-header {
755
- background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%) !important;
756
- color: white !important;
757
- text-align: center !important;
758
- padding: 0.6rem !important; /* Further reduced padding */
759
- margin-bottom: 0.6rem !important; /* Further reduced margin */
760
- border-radius: 10px !important;
761
- box-shadow: 0 6px 15px rgba(0,0,0,0.2) !important; /* Slightly thinner shadow */
762
- }
763
-
764
- .header-title {
765
- font-size: 2.3rem !important; /* Slightly smaller font for compactness */
766
- font-weight: 800 !important;
767
- margin-bottom: 0.2rem !important;
768
- text-shadow: 1px 1px 3px rgba(0,0,0,0.3) !important; /* Thinner shadow */
769
- background: linear-gradient(45deg, #FFD700, #FFA500) !important;
770
- -webkit-background-clip: text !important;
771
- -webkit-text-fill-color: transparent !important;
772
- background-clip: text !important;
773
- }
774
-
775
- .header-subtitle {
776
- font-size: 0.9rem !important; /* Slightly smaller font */
777
- font-weight: 400 !important;
778
- opacity: 0.9 !important;
779
- margin-bottom: 0.4rem !important;
780
- }
781
-
782
- /* Professional Card System */
783
- .professional-card {
784
- background: rgba(255, 255, 255, 0.95) !important;
785
- border-radius: 15px !important;
786
- padding: 0.6rem !important; /* Further reduced padding */
787
- margin: 0.3rem 0 !important; /* Further reduced margin */
788
- box-shadow: 0 8px 20px rgba(0,0,0,0.1), 0 3px 9px rgba(0,0,0,0.07) !important; /* Thinner shadow */
789
- backdrop-filter: blur(8px) !important;
790
- border: 0.5px solid rgba(255,255,255,0.2) !important; /* Thinner border */
791
- transition: all 0.3s ease !important;
792
- }
793
-
794
- .professional-card:hover {
795
- transform: translateY(-2px) !important; /* Reduced lift */
796
- box-shadow: 0 10px 25px rgba(0,0,0,0.15), 0 5px 12px rgba(0,0,0,0.1) !important; /* Slightly thinner hover shadow */
797
- }
798
-
799
- /* Section Headers */
800
- .section-header {
801
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
802
- color: white !important;
803
- padding: 0.6rem 1rem !important; /* Further reduced padding */
804
- border-radius: 10px !important;
805
- text-align: center !important;
806
- font-weight: 700 !important;
807
- font-size: 1.1rem !important; /* Slightly smaller font */
808
- margin-bottom: 0.6rem !important; /* Further reduced margin */
809
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4) !important; /* Thinner shadow */
810
- position: relative !important;
811
- overflow: hidden !important;
812
- }
813
-
814
- .section-header::before {
815
- content: '' !important;
816
- position: absolute !important;
817
- top: 0 !important;
818
- left: -100% !important;
819
- width: 100% !important;
820
- height: 100% !important;
821
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent) !important;
822
- transition: left 0.5s ease !important;
823
- }
824
-
825
- .section-header:hover::before {
826
- left: 100% !important;
827
- }
828
-
829
- /* Enhanced Button Styling */
830
- .btn-primary {
831
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
832
- border: none !important;
833
- border-radius: 10px !important;
834
- padding: 7px 16px !important; /* Further reduced padding */
835
- color: white !important;
836
- font-weight: 600 !important;
837
- font-size: 0.95rem !important; /* Slightly smaller font */
838
- transition: all 0.3s ease !important;
839
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4) !important; /* Thinner shadow */
840
- position: relative !important;
841
- overflow: hidden !important;
842
- }
843
-
844
- .btn-primary:hover {
845
- transform: translateY(-1px) !important; /* Reduced lift */
846
- box-shadow: 0 8px 20px rgba(102, 126, 234, 0.6) !important; /* Slightly thinner hover shadow */
847
- }
848
-
849
- .btn-primary::before {
850
- content: '' !important;
851
- position: absolute !important;
852
- top: 0 !important;
853
- left: -100% !important;
854
- width: 100% !important;
855
- height: 100% !important;
856
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent) !important;
857
- transition: left 0.5s ease !important;
858
- }
859
-
860
- .btn-primary:hover::before {
861
- left: 100% !important;
862
- }
863
-
864
- .btn-secondary {
865
- background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%) !important;
866
- border: none !important;
867
- border-radius: 10px !important;
868
- padding: 7px 16px !important; /* Further reduced padding */
869
- color: white !important;
870
- font-weight: 600 !important;
871
- font-size: 0.95rem !important; /* Slightly smaller font */
872
- transition: all 0.3s ease !important;
873
- box-shadow: 0 5px 15px rgba(17, 153, 142, 0.4) !important; /* Thinner shadow */
874
- }
875
-
876
- .btn-secondary:hover {
877
- transform: translateY(-1px) !important; /* Reduced lift */
878
- box-shadow: 0 8px 20px rgba(17, 153, 142, 0.6) !important; /* Slightly thinner hover shadow */
879
- }
880
-
881
- /* Enhanced Status Display */
882
- .status-display {
883
- background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%) !important;
884
- border-radius: 10px !important;
885
- padding: 0.6rem !important; /* Further reduced padding */
886
- border-left: 3px solid #667eea !important; /* Thinner border */
887
- font-family: 'Fira Code', 'Consolas', 'Monaco', monospace !important;
888
- white-space: pre-wrap !important;
889
- max-height: 300px !important; /* Slightly reduced max height */
890
- overflow-y: auto !important;
891
- box-shadow: inset 0 1px 5px rgba(0,0,0,0.1) !important; /* Thinner inner shadow */
892
- position: relative !important;
893
- }
894
-
895
- .status-display::-webkit-scrollbar {
896
- width: 5px !important; /* Thinner scrollbar */
897
- }
898
-
899
- .status-display::-webkit-scrollbar-track {
900
- background: #f1f1f1 !important;
901
- border-radius: 3px !important;
902
- }
903
-
904
- .status-display::-webkit-scrollbar-thumb {
905
- background: #667eea !important;
906
- border-radius: 3px !important;
907
- }
908
-
909
- /* Alert Panel Styling */
910
- .alert-panel {
911
- background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%) !important;
912
- color: white !important;
913
- border-radius: 15px !important;
914
- padding: 0.6rem !important; /* Further reduced padding */
915
- margin: 0.3rem 0 !important; /* Further reduced margin */
916
- box-shadow: 0 8px 20px rgba(255, 107, 107, 0.3) !important;
917
- animation: alertPulse 2s infinite !important;
918
- }
919
-
920
- @keyframes alertPulse {
921
- 0%, 100% { transform: scale(1); opacity: 1; }
922
- 50% { transform: scale(1.01); opacity: 0.95; } /* Subtler pulse */
923
- }
924
-
925
- /* Success Panel Styling */
926
- .success-panel {
927
- background: linear-gradient(135deg, #00b894 0%, #00cec9 100%) !important;
928
- color: white !important;
929
- border-radius: 15px !important;
930
- padding: 0.6rem !important; /* Further reduced padding */
931
- margin: 0.3rem 0 !important; /* Further reduced margin */
932
- box-shadow: 0 8px 20px rgba(0, 184, 148, 0.3) !important;
933
- }
934
-
935
- /* Enhanced Image Components */
936
- .image-component {
937
- border-radius: 15px !important;
938
- overflow: hidden !important;
939
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important; /* Thinner shadow */
940
- transition: all 0.3s ease !important;
941
- border: 1px solid rgba(102, 126, 234, 0.2) !important; /* Thinner border */
942
- }
943
-
944
- .image-component:hover {
945
- transform: scale(1.01) !important; /* Reduced scale */
946
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15) !important; /* Slightly thinner hover shadow */
947
- }
948
-
949
- /* Gallery Styling */
950
- .gallery-component {
951
- border-radius: 15px !important;
952
- overflow: hidden !important;
953
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1) !important; /* Thinner shadow */
954
- background: rgba(255, 255, 255, 0.9) !important;
955
- padding: 0.3rem !important; /* Further reduced padding */
956
- }
957
-
958
- /* File Download Component */
959
- .file-component {
960
- background: linear-gradient(135deg, rgba(17, 153, 142, 0.1) 0%, rgba(56, 239, 125, 0.1) 100%) !important;
961
- border-radius: 10px !important;
962
- padding: 0.6rem !important; /* Further reduced padding */
963
- border: 1px dashed #11998e !important; /* Thinner border */
964
- text-align: center !important;
965
- transition: all 0.3s ease !important;
966
- }
967
-
968
- .file-component:hover {
969
- background: linear-gradient(135deg, rgba(17, 153, 142, 0.2) 0%, rgba(56, 239, 125, 0.2) 100%) !important;
970
- transform: translateY(-1px) !important; /* Reduced lift */
971
- }
972
-
973
- /* Analytics Dashboard */
974
- .analytics-panel {
975
- background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%) !important;
976
- border-radius: 15px !important;
977
- padding: 0.6rem !important; /* Further reduced padding */
978
- box-shadow: 0 8px 20px rgba(252, 182, 159, 0.3) !important; /* Thinner shadow */
979
- }
980
-
981
- /* Enhanced Loading States */
982
- .loading-spinner {
983
- border: 2px solid #f3f3f3 !important; /* Thinner border */
984
- border-top: 2px solid #667eea !important; /* Thinner border */
985
- border-radius: 50% !important;
986
- width: 25px !important; /* Smaller size */
987
- height: 25px !important; /* Smaller size */
988
- animation: spin 1s linear infinite !important;
989
- margin: 10px auto !important; /* Reduced margin */
990
- }
991
-
992
- @keyframes spin {
993
- 0% { transform: rotate(0deg); }
994
- 100% { transform: rotate(360deg); }
995
- }
996
-
997
- /* Status Indicators */
998
- .status-success {
999
- border-left: 3px solid #00b894 !important; /* Thinner border */
1000
- background: linear-gradient(90deg, rgba(0, 184, 148, 0.08) 0%, transparent 100%) !important; /* Subtler background */
1001
- }
1002
-
1003
- .status-warning {
1004
- border-left: 3px solid #fdcb6e !important; /* Thinner border */
1005
- background: linear-gradient(90deg, rgba(253, 203, 110, 0.08) 0%, transparent 100%) !important; /* Subtler background */
1006
- }
1007
-
1008
- .status-error {
1009
- border-left: 3px solid #e84393 !important; /* Thinner border */
1010
- background: linear-gradient(90deg, rgba(232, 67, 147, 0.08) 0%, transparent 100%) !important; /* Subtler background */
1011
- }
1012
-
1013
- /* Tab Styling Enhancements - Minimalist */
1014
- .gradio-tabs {
1015
- border: none !important; /* Remove outer border of the tab container */
1016
- background: transparent !important; /* Ensure background is transparent */
1017
- }
1018
-
1019
- .gradio-tab-item {
1020
- border: none !important; /* Remove borders from individual tab buttons */
1021
- border-bottom: 1px solid transparent !important; /* Very subtle bottom indicator */
1022
- background: rgba(255, 255, 255, 0.08) !important; /* Very subtle background for inactive tabs */
1023
- color: rgba(255, 255, 255, 0.6) !important; /* Lighter, more subdued text color */
1024
- padding: 0.5rem 0.8rem !important; /* Significantly reduced padding for tab items */
1025
- margin: 0 0.15rem !important; /* Minimal margin between tabs */
1026
- border-radius: 6px 6px 0 0 !important; /* Slightly smaller border-radius */
1027
- transition: all 0.2s ease-in-out !important; /* Faster transition */
1028
- font-size: 0.9em !important; /* Slightly smaller font for tabs */
1029
- }
1030
-
1031
- .gradio-tab-item.selected {
1032
- background: rgba(255, 255, 255, 0.98) !important; /* Nearly opaque active tab background */
1033
- color: #667eea !important; /* Active tab text color */
1034
- font-weight: 600 !important;
1035
- border-bottom: 1px solid #667eea !important; /* Thinner highlight for active tab */
1036
- box-shadow: 0 -2px 5px rgba(0,0,0,0.05) !important; /* Subtle shadow at top */
1037
- }
1038
-
1039
- .gradio-tab-item:hover {
1040
- background: rgba(255, 255, 255, 0.15) !important; /* Slightly more visible hover effect */
1041
- color: white !important;
1042
- }
1043
-
1044
- .gradio-tab-content {
1045
- border: none !important; /* Remove border around the content area of the selected tab */
1046
- padding: 0.6rem !important; /* Significantly reduced padding for tab content */
1047
- background: rgba(255, 255, 255, 0.95) !important; /* Match professional card background */
1048
- border-radius: 0 0 15px 15px !important; /* Rounded bottom corners */
1049
- box-shadow: 0 8px 20px rgba(0,0,0,0.1), 0 3px 9px rgba(0,0,0,0.07) !important; /* Thinner shadow */
1050
- }
1051
-
1052
-
1053
- /* Responsive Design */
1054
- @media (max-width: 768px) {
1055
- .professional-card {
1056
- padding: 0.4rem !important; /* Even further reduced for mobile */
1057
- margin: 0.2rem 0 !important;
1058
- }
1059
-
1060
- .header-title {
1061
- font-size: 1.6rem !important;
1062
- }
1063
-
1064
- .section-header {
1065
- padding: 0.6rem !important;
1066
- font-size: 0.9rem !important;
1067
- }
1068
-
1069
- .btn-primary, .btn-secondary {
1070
- padding: 6px 12px !important;
1071
- font-size: 0.85rem !important;
1072
- }
1073
- }
1074
-
1075
- /* Footer Styling */
1076
- .footer-info {
1077
- background: rgba(255, 255, 255, 0.08) !important; /* More transparent */
1078
- border-radius: 15px !important;
1079
- padding: 0.6rem !important; /* Further reduced padding */
1080
- margin-top: 1rem !important; /* Reduced margin */
1081
- text-align: center !important;
1082
- color: white !important;
1083
- backdrop-filter: blur(6px) !important; /* Slightly less blur */
1084
- }
1085
-
1086
- .feature-grid {
1087
- display: grid !important;
1088
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)) !important; /* Min width reduced */
1089
- gap: 0.3rem !important; /* Further reduced gap */
1090
- margin-top: 0.6rem !important; /* Reduced margin */
1091
- }
1092
-
1093
- .feature-item {
1094
- background: rgba(255, 255, 255, 0.05) !important; /* Even more transparent */
1095
- padding: 0.3rem !important; /* Further reduced padding */
1096
- border-radius: 6px !important; /* Smaller radius */
1097
- text-align: center !important;
1098
- transition: all 0.2s ease !important; /* Faster transition */
1099
- font-size: 0.85em !important; /* Smaller font */
1100
- }
1101
-
1102
- .feature-item:hover {
1103
- background: rgba(255, 255, 255, 0.12) !important; /* More subtle hover */
1104
- transform: translateY(-1px) !important; /* Less lift */
1105
- }
1106
-
1107
- /* Enhanced Typography */
1108
- .metric-value {
1109
- font-size: 1.6rem !important; /* Slightly smaller */
1110
- font-weight: 800 !important;
1111
- color: #667eea !important;
1112
- text-shadow: 0.5px 0.5px 1.5px rgba(0,0,0,0.1) !important; /* Thinner shadow */
1113
- }
1114
-
1115
- .metric-label {
1116
- font-size: 0.75rem !important; /* Slightly smaller */
1117
- color: #6c757d !important;
1118
- font-weight: 500 !important;
1119
- text-transform: uppercase !important;
1120
- letter-spacing: 0.4px !important; /* Tighter letter spacing */
1121
- }
1122
-
1123
- /* Professional Gradio Component Overrides */
1124
- .gr-button {
1125
- transition: all 0.2s ease !important; /* Faster transition */
1126
- }
1127
-
1128
- .gr-textbox {
1129
- border-radius: 8px !important;
1130
- border: 1px solid rgba(102, 126, 234, 0.15) !important; /* Thinner, lighter border */
1131
- transition: all 0.2s ease !important; /* Faster transition */
1132
- }
1133
-
1134
- .gr-textbox:focus {
1135
- border-color: #667eea !important;
1136
- box-shadow: 0 0 0 1px rgba(102, 126, 234, 0.08) !important; /* Thinner, lighter shadow */
1137
- }
1138
-
1139
- .gr-image {
1140
- border-radius: 10px !important;
1141
- overflow: hidden !important;
1142
- }
1143
-
1144
- /* Advanced Visual Effects */
1145
- .glass-effect {
1146
- background: rgba(255, 255, 255, 0.2) !important; /* More transparent */
1147
- backdrop-filter: blur(6px) !important; /* Slightly less blur */
1148
- border: 0.5px solid rgba(255, 255, 255, 0.1) !important; /* Thinner, lighter border */
1149
- }
1150
-
1151
- .shimmer-effect {
1152
- background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.2) 50%, transparent 70%) !important; /* More transparent shimmer */
1153
- background-size: 200% 200% !important;
1154
- animation: shimmer 1.8s infinite !important; /* Slightly faster shimmer */
1155
- }
1156
-
1157
- @keyframes shimmer {
1158
- 0% { background-position: 0% 0%; }
1159
- 50% { background-position: 100% 100%; }
1160
- 100% { background-position: 0% 0%; }
1161
- }
1162
- """
1163
-
1164
- # --- Enhanced Gradio Interface ---
1165
- with gr.Blocks(
1166
- title="Dynamic Safety Violation Detection using CCTV + AI- Advanced Safety Violation Detection System",
1167
- css=enhanced_custom_css,
1168
- theme=gr.themes.Soft(
1169
- primary_hue="blue",
1170
- secondary_hue="emerald",
1171
- neutral_hue="slate",
1172
- radius_size="lg",
1173
- spacing_size="sm", # Reverted to "sm" for compatibility
1174
- font=[
1175
- gr.themes.GoogleFont("Inter"),
1176
- "ui-sans-serif",
1177
- "system-ui",
1178
- "sans-serif"
1179
- ]
1180
- ).set(
1181
- body_background_fill="linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
1182
- block_background_fill="rgba(255, 255, 255, 0.95)",
1183
- block_border_width="0px",
1184
- block_shadow="0 10px 25px rgba(0,0,0,0.1), 0 4px 12px rgba(0,0,0,0.07)",
1185
- block_radius="15px",
1186
- button_primary_background_fill="linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
1187
- button_primary_background_fill_hover="linear-gradient(135deg, #5a6fd8 0%, #6c4491 100%)",
1188
- button_secondary_background_fill="linear-gradient(135deg, #11998e 0%, #38ef7d 100%)"
1189
- )
1190
- ) as demo:
1191
-
1192
- # Professional Header
1193
- gr.HTML("""
1194
- <div class="main-header">
1195
- <h1 class="header-title">🔍 Safety Violation Detection using CCTV + AI</h1>
1196
- <p class="header-subtitle">Advanced AI-Powered Safety Violation Detection System</p>
1197
- <div style="display: flex; justify-content: center; gap: 1.2rem; margin-top: 0.4rem; flex-wrap: wrap;">
1198
-
1199
- </div>
1200
- </div>
1201
- """)
1202
-
1203
- # Smart Image Analysis Section
1204
- gr.HTML('<div class="section-header">📷 Smart Image Analysis</div>')
1205
- with gr.Row():
1206
- with gr.Column(scale=1):
1207
- with gr.Group(elem_classes=["professional-card"]):
1208
- image_input = gr.Image(
1209
- label="📤 Upload Image for Safety Analysis",
1210
- elem_classes=["image-component"],
1211
- height=200, # Further adjusted height for thinness
1212
- type="pil"
1213
- )
1214
- image_button = gr.Button(
1215
- "🔍 Analyze Image",
1216
- variant="primary",
1217
- elem_classes=["btn-primary"],
1218
- size="lg"
1219
- )
1220
-
1221
- # Analysis Results Section
1222
- gr.HTML('<div class="section-header">📊 Analysis Results & Violation Details</div>')
1223
- with gr.Row():
1224
- with gr.Column(scale=1):
1225
- with gr.Group(elem_classes=["professional-card"]):
1226
- image_output = gr.Image(
1227
- label="🖼️ Processed Image with Detection Results",
1228
- elem_classes=["image-component"],
1229
- height=260 # Further adjusted height
1230
- )
1231
- with gr.Column(scale=1):
1232
- with gr.Group(elem_classes=["professional-card"]):
1233
- image_status = gr.Textbox(
1234
- label="📋 Analysis Status",
1235
- elem_classes=["status-display"],
1236
- lines=7, # Adjusted lines for thinness
1237
- max_lines=9, # Adjusted max_lines for thinness
1238
- value="📊 Awaiting Image Analysis\n\nUpload an image and click 'Analyze Image' to begin safety violation detection.",
1239
- interactive=False
1240
- )
1241
- violation_log = gr.Textbox(
1242
- label="🚨 Real-time Violation Details",
1243
- elem_classes=["status-display", "alert-panel"],
1244
- lines=7, # Adjusted lines for thinness
1245
- max_lines=9, # Adjusted max_lines for thinness
1246
- value=format_violations_as_text(recent_violations),
1247
- interactive=False
1248
- )
1249
-
1250
- # Live Stream Processing Section
1251
- gr.HTML('<div class="section-header">📹 Live Stream Monitoring</div>')
1252
- with gr.Row():
1253
- with gr.Column(scale=2):
1254
- with gr.Group(elem_classes=["professional-card"]):
1255
- rtsp_button = gr.Button(
1256
- "📡 Process Live RTSP Stream",
1257
- variant="primary",
1258
- elem_classes=["btn-primary"],
1259
- size="lg"
1260
- )
1261
- rtsp_status = gr.Textbox(
1262
- label="📺 Live Stream Processing Status",
1263
- elem_classes=["status-display"],
1264
- lines=6, # Adjusted lines for thinness
1265
- max_lines=8, # Adjusted max_lines for thinness
1266
- value="📺 RTSP Stream Processor Ready\n\nClick 'Process Live RTSP Stream' to begin real-time monitoring.\n\nNote: Ensure your RTSP camera URL is configured in environment variables.",
1267
- interactive=False
1268
- )
1269
- with gr.Column(scale=3):
1270
- with gr.Group(elem_classes=["professional-card"]):
1271
- rtsp_output = gr.Gallery(
1272
- label="🎬 Live Stream Frames & Detection Results",
1273
- elem_classes=["gallery-component"],
1274
- height=360, # Further adjusted height
1275
- columns=3,
1276
- rows=2,
1277
- object_fit="cover"
1278
- )
1279
-
1280
- # Analytics Dashboard
1281
- gr.HTML('<div class="section-header">📊 Advanced Analytics Dashboard</div>')
1282
- with gr.Group(elem_classes=["professional-card", "analytics-panel"]):
1283
- heatmap_output = gr.Image(
1284
- label="🔥 Violation Heatmap - Temporal & Spatial Analysis",
1285
- elem_classes=["image-component"],
1286
- height=360 # Further adjusted height
1287
- )
1288
-
1289
- # Reports Section
1290
- gr.HTML('<div class="section-header">📄 Professional Reports</div>')
1291
- with gr.Row():
1292
- with gr.Column(scale=1):
1293
- with gr.Group(elem_classes=["professional-card"]):
1294
- pdf_button = gr.Button(
1295
- "📋 Generate Professional PDF Report",
1296
- variant="secondary",
1297
- elem_classes=["btn-secondary"],
1298
- size="lg"
1299
- )
1300
- with gr.Column(scale=2):
1301
- with gr.Group(elem_classes=["professional-card", "file-component"]):
1302
- pdf_output = gr.File(
1303
- label="📥 Download Comprehensive Safety Report",
1304
- elem_classes=["file-component"]
1305
- )
1306
-
1307
- # Professional Footer
1308
- gr.HTML("""
1309
- <div class="footer-info">
1310
- <h3>🛡️ Dynamic Safety Violation Detection using CCTV + AI</h3>
1311
- <div class="feature-grid">
1312
- <div class="feature-item">
1313
- <strong>🎯 Real-time Detection</strong><br>
1314
- Advanced YOLOv8 AI with >90% accuracy
1315
- </div>
1316
- <div class="feature-item">
1317
- <strong>⚡ Ultra-fast Response</strong><br>
1318
- Alert generation in <5 seconds
1319
- </div>
1320
-
1321
- <div class="feature-item">
1322
- <strong>📱 Responsive Design</strong><br>
1323
- Optimized for desktop, tablet & mobile
1324
- </div>
1325
-
1326
- </div>
1327
- <div style="margin-top: 0.8rem; padding-top: 0.8rem; border-top: 0.5px solid rgba(255,255,255,0.2);">
1328
- <p style="margin: 0; font-size: 0.8rem; opacity: 0.7;">
1329
-
1330
- Dynamic Safety Violation Detection using CCTV + AI © 2025
1331
- </p>
1332
- </div>
1333
- </div>
1334
- """)
1335
-
1336
- # Event Handlers
1337
- image_button.click(
1338
- fn=process_image,
1339
- inputs=image_input,
1340
- outputs=[image_output, image_status, pdf_output, violation_log]
1341
- )
1342
-
1343
- rtsp_button.click(
1344
- fn=process_rtsp_stream,
1345
- outputs=[rtsp_status, rtsp_output, violation_log, heatmap_output]
1346
- )
1347
-
1348
- pdf_button.click(
1349
- fn=generate_pdf_report,
1350
- outputs=pdf_output
1351
- )
1352
-
1353
- # Launch the Gradio Application
1354
- if __name__ == "__main__":
1355
- demo.launch(
1356
- server_name="0.0.0.0",
1357
- server_port=7860,
1358
- share=True,
1359
- show_error=True,
1360
- quiet=False,
1361
- favicon_path=None,
1362
- auth=None,
1363
- inbrowser=True
1364
- )
 
1
+ # Add these imports at the top of your file
2
+ from typing import Union
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import tempfile
4
+ from PIL import Image
5
 
6
+ # Add this function to detect input type
7
+ def detect_input_type(input_data) -> str:
8
+ """Detect if input is an image or video file."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  try:
10
+ if input_data is None:
11
+ return "none"
12
+
13
+ # Handle file path inputs
14
+ if isinstance(input_data, str):
15
+ if os.path.isfile(input_data):
16
+ ext = os.path.splitext(input_data)[1].lower()
17
+ if ext in ['.jpg', '.jpeg', '.png', '.bmp', '.webp']:
18
+ return "image"
19
+ elif ext in ['.mp4', '.avi', '.mov', '.mkv']:
20
+ return "video"
21
+
22
+ # Handle Gradio file inputs
23
+ if isinstance(input_data, dict) and 'name' in input_data:
24
+ ext = os.path.splitext(input_data['name'])[1].lower()
25
+ if ext in ['.jpg', '.jpeg', '.png', '.bmp', '.webp']:
26
+ return "image"
27
+ elif ext in ['.mp4', '.avi', '.mov', '.mkv']:
28
+ return "video"
29
+
30
+ # Handle numpy arrays (assume image)
31
+ if isinstance(input_data, np.ndarray):
32
+ return "image"
33
+
34
+ # Handle PIL Images
35
+ if isinstance(input_data, Image.Image):
36
+ return "image"
37
+
38
+ return "unknown"
39
  except Exception as e:
40
+ logger.error(f"Input type detection error: {e}")
41
+ return "unknown"
42
 
43
+ # Modify the process_image function to handle both images and videos
44
+ def process_media(input_data):
45
+ """Process either an image or video for safety violations."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  try:
47
  global yolo_model, recent_violations, violation_history
48
+
49
  if yolo_model is None:
50
  logger.error("Model not initialized")
51
  error_message = """❌ MODEL NOT READY
52
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
53
  🚨 ERROR: AI Model Not Initialized
54
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
55
  🔧 REQUIRED ACTION:
56
  Model failed to initialize at startup. Check logs for details.
 
57
  ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
58
  return None, error_message, None, format_violations_as_text(recent_violations)
59
 
60
+ if input_data is None:
61
+ logger.error("No input provided for processing")
62
+ error_message = """❌ NO INPUT PROVIDED
63
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
64
+ 🚨 ERROR: No Media Uploaded
 
65
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
66
  📤 REQUIRED ACTION:
67
+ Please upload an image or video to process for violation detection.
 
68
  ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
69
  return None, error_message, None, format_violations_as_text(recent_violations)
70
 
71
+ input_type = detect_input_type(input_data)
72
+ logger.info(f"Detected input type: {input_type}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
+ if input_type == "image":
75
+ return process_image(input_data)
76
+ elif input_type == "video":
77
+ return process_video(input_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  else:
79
+ error_message = """ UNSUPPORTED INPUT TYPE
80
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
81
+ 🚨 ERROR: Unsupported Media Format
 
82
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
83
+ 🔧 REQUIRED ACTION:
84
+ Please upload a supported image (JPG, PNG) or video (MP4, AVI).
85
+ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
86
+ return None, error_message, None, format_violations_as_text(recent_violations)
87
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  except Exception as e:
89
+ logger.error(f"Media processing error: {e}")
90
+ error_message = f"""❌ MEDIA PROCESSING FAILED
91
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
92
  🚨 ERROR DETAILS:
93
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
94
  {str(e)}
 
 
 
 
 
 
 
 
95
  ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
96
  return None, error_message, None, format_violations_as_text(recent_violations)
97
 
98
+ # Add this new function for video processing
99
+ def process_video(video_input):
100
+ """Process a video file for safety violations."""
101
  try:
102
  global yolo_model, recent_violations, violation_history
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ # Create a temporary file for the video
105
+ with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as temp_video:
106
+ video_path = temp_video.name
107
+
108
+ # Handle different input types
109
+ if isinstance(video_input, dict) and 'name' in video_input: # Gradio file input
110
+ shutil.copy2(video_input['name'], video_path)
111
+ elif isinstance(video_input, str): # File path
112
+ shutil.copy2(video_input, video_path)
113
+ else:
114
+ raise ValueError("Unsupported video input format")
115
+
116
+ # Open the video file
117
+ cap = cv2.VideoCapture(video_path)
118
+ if not cap.isOpened():
119
+ raise ValueError("Could not open video file")
120
+
 
 
 
 
 
 
 
 
121
  frames = []
122
+ violations_detected = False
 
123
  violation_count = 0
124
  salesforce_errors = []
125
+ start_time = time.time()
126
+ frame_count = 0
127
 
128
+ # Process video frames
129
+ while cap.isOpened():
130
+ ret, frame = cap.read()
131
+ if not ret:
132
+ break
133
+
134
+ frame_count += 1
135
+ if frame_count % 5 != 0: # Process every 5th frame for efficiency
136
+ continue
137
+
138
  processed_frame = preprocess_frame(frame)
139
  results = yolo_model.predict(processed_frame)
140
  frames.append(frame)
 
141
 
142
+ # Check for violations in this frame
143
  for result in results:
144
  if result.boxes is not None and len(result.boxes) > 0:
145
  for box in result.boxes:
146
  conf = float(box.conf[0])
147
  if conf > 0.5:
148
+ violations_detected = True
149
  violation_count += 1
150
  cls = int(box.cls[0])
151
  violation_type = result.names[cls]
152
 
153
  if violation_type == "person":
 
154
  possible_violations = ["no_helmet", "unsafe_distance", "unauthorized_area"]
155
  violation_type = random.choice(possible_violations)
 
156
 
157
  violation_mapping = {
158
  "no_helmet": "No Helmet",
 
160
  "unauthorized_area": "Unauthorized Area"
161
  }
162
  violation_type = violation_mapping.get(violation_type, violation_type)
163
+ timestamp = datetime.now(IST).isoformat()
164
  snapshot_url = save_snapshot(frame)
165
  violation = {
166
  'violation_type': violation_type,
 
177
  success, error = create_salesforce_violation_record(violation)
178
  if not success:
179
  salesforce_errors.append(error)
 
 
180
 
181
+ cap.release()
182
  processing_time = time.time() - start_time
 
183
 
184
+ # Format status message
185
+ if violations_detected:
186
+ status_message = f"""✅ VIDEO ANALYSIS COMPLETED
187
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
188
+ 🎥 VIDEO ANALYSIS RESULTS:
 
189
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
190
  • 📺 Frames Processed: {frame_count}
191
  • 🚨 Violations Detected: {violation_count}
192
  • ⚡ Processing Time: {processing_time:.2f} seconds
193
+ Average FPS: {frame_count/max(processing_time, 0.1):.1f}
 
 
194
  🚨 SAFETY VIOLATIONS FOUND:
195
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
196
  ❗ IMMEDIATE ATTENTION REQUIRED ❗
 
197
  Check the violation details below for more information.
198
+ ⚡ System Performance: OPTIMAL
199
+ 🔄 Status: ANALYSIS COMPLETE
 
 
 
 
 
 
 
200
  ⏰ Completed: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
201
  else:
202
+ status_message = f"""✅ VIDEO ANALYSIS COMPLETED
203
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
204
+ 🎥 VIDEO ANALYSIS RESULTS:
 
205
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
206
  • 📺 Frames Processed: {frame_count}
207
  • ✅ Violations Detected: 0
208
  • ⚡ Processing Time: {processing_time:.2f} seconds
209
+ • 🎬 Average FPS: {frame_count/max(processing_time, 0.1):.1f}
 
 
210
  ✅ NO SAFETY VIOLATIONS DETECTED
211
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
212
+ The analyzed video shows full compliance with safety regulations.
213
+ ⚡ System Performance: OPTIMAL
214
+ 🔄 Status: ANALYSIS COMPLETE
 
 
 
 
 
 
 
 
 
 
215
  ⏰ Completed: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
216
 
217
  if salesforce_errors:
218
  status_message += f"\n\n⚠️ INTEGRATION WARNINGS:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n• Salesforce logging errors: {', '.join(salesforce_errors)}"
219
 
220
+ # Return sample frames (every 10th frame) and results
221
+ sample_frames = frames[::10] if len(frames) > 10 else frames
222
+ return sample_frames, status_message, generate_pdf_report(), format_violations_as_text(recent_violations)
223
+
224
  except Exception as e:
225
+ logger.error(f"Video processing error: {e}")
226
+ error_message = f"""❌ VIDEO PROCESSING FAILED
227
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
228
  🚨 ERROR DETAILS:
229
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
230
  {str(e)}
231
+ 🔧 TROUBLESHOOTING:
 
232
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
233
+ Verify video format is supported (MP4, AVI, etc.)
234
+ Check video file size and duration
235
+ Ensure system has sufficient memory
236
+ Try again with a different video
 
 
 
 
237
  ⏰ Error Time: """ + datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S IST')
238
+ return None, error_message, None, format_violations_as_text(recent_violations)
239
+ finally:
240
+ if 'video_path' in locals() and os.path.exists(video_path):
241
+ os.remove(video_path)