PrashanthB461 commited on
Commit
54ff363
·
verified ·
1 Parent(s): 068739f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -58
app.py CHANGED
@@ -21,16 +21,9 @@ from multiprocessing import Pool, cpu_count
21
  from functools import partial
22
  import tempfile
23
  import shutil
 
24
 
25
  # ========================== # Configuration and Setup # ==========================
26
- # Use a temporary directory for storage to avoid file system issues on Hugging Face Spaces
27
- TEMP_DIR = tempfile.mkdtemp(prefix="Ultralytics_")
28
- os.environ['YOLO_CONFIG_DIR'] = TEMP_DIR
29
-
30
- # Ensure output directory exists within temp directory
31
- OUTPUT_DIR = os.path.join(TEMP_DIR, "output")
32
- os.makedirs(OUTPUT_DIR, exist_ok=True)
33
-
34
  # Configure logging for better debugging
35
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
36
  logger = logging.getLogger(__name__)
@@ -193,7 +186,6 @@ class BYTETracker:
193
  CONFIG = {
194
  "MODEL_PATH": "yolov8_safety.pt",
195
  "FALLBACK_MODEL": "yolov8n.pt",
196
- "OUTPUT_DIR": OUTPUT_DIR,
197
  "VIOLATION_LABELS": {
198
  0: "no_helmet",
199
  1: "no_harness",
@@ -329,11 +321,11 @@ def calculate_safety_score(violations):
329
  score = max(0, 100 - total_penalty)
330
  return score
331
 
332
- def generate_violation_pdf(violations, score):
333
  """Generate a PDF report for the detected violations"""
334
  try:
335
  pdf_filename = f"violations_{int(time.time())}.pdf"
336
- pdf_path = os.path.join(CONFIG["OUTPUT_DIR"], pdf_filename)
337
  pdf_file = BytesIO()
338
  c = canvas.Canvas(pdf_file, pagesize=letter)
339
 
@@ -503,23 +495,50 @@ def push_report_to_salesforce(violations, score, pdf_path, pdf_file):
503
  logger.error(f"Salesforce record creation failed: {e}")
504
  return "N/A", "Salesforce integration failed."
505
 
506
- def process_video(video_data):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  """Process video to detect safety violations"""
508
- video_path = None # Initialize video_path for cleanup
 
 
 
 
509
  try:
510
  # Validate video data
511
  if not video_data:
512
  raise ValueError("Empty video data provided.")
513
 
514
- # Log the size of the video data
515
  logger.info(f"Received video data size: {len(video_data)} bytes")
516
  if len(video_data) == 0:
517
  raise ValueError("Video data is empty.")
518
 
519
- # Save video to a temporary file using NamedTemporaryFile
520
- with tempfile.NamedTemporaryFile(suffix=".mp4", dir=TEMP_DIR, delete=False) as temp_file:
521
  temp_file.write(video_data)
522
- temp_file.flush() # Ensure all data is written to disk
523
  video_path = temp_file.name
524
  logger.info(f"Video saved to temporary file: {video_path}")
525
 
@@ -531,18 +550,9 @@ def process_video(video_data):
531
  raise ValueError(f"Temporary video file is empty: {video_path}")
532
  logger.info(f"Temporary video file size: {file_size} bytes")
533
 
534
- # Ensure the file is readable
535
- try:
536
- with open(video_path, "rb") as f:
537
- f.read(1) # Test read access
538
- logger.info(f"Temporary video file is readable: {video_path}")
539
- except Exception as e:
540
- raise IOError(f"Cannot read temporary video file {video_path}: {str(e)}")
541
-
542
- # Open video with OpenCV
543
- cap = cv2.VideoCapture(video_path)
544
- if not cap.isOpened():
545
- raise ValueError("Could not open video file. Ensure the video format is supported (e.g., MP4) and FFmpeg is installed.")
546
 
547
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
548
  fps = cap.get(cv2.CAP_PROP_FPS) or 30
@@ -551,7 +561,6 @@ def process_video(video_data):
551
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
552
  logger.info(f"Video properties: {duration:.2f}s, {total_frames} frames, {fps:.1f} FPS, {width}x{height}")
553
 
554
- # Check if video is empty
555
  if total_frames <= 0:
556
  raise ValueError("Video has no frames.")
557
 
@@ -596,7 +605,6 @@ def process_video(video_data):
596
  logger.info("No more frames to process.")
597
  break
598
 
599
- # Process batch with YOLO model
600
  try:
601
  results = model(batch_frames, device=device, conf=0.1, verbose=False)
602
  except Exception as e:
@@ -659,7 +667,7 @@ def process_video(video_data):
659
  detection = {
660
  "worker_id": worker_id,
661
  "violation": label,
662
- "confidence": round(float(conf), 2), # Ensure confidence is a float
663
  "bounding_box": bbox,
664
  "timestamp": current_time
665
  }
@@ -678,7 +686,7 @@ def process_video(video_data):
678
  )
679
 
680
  snapshot_filename = f"violation_{label}_worker{worker_id}_{int(current_time*100)}.jpg"
681
- snapshot_path = os.path.join(CONFIG["OUTPUT_DIR"], snapshot_filename)
682
 
683
  cv2.imwrite(
684
  snapshot_path,
@@ -692,24 +700,20 @@ def process_video(video_data):
692
  "timestamp": current_time,
693
  "snapshot_path": snapshot_path,
694
  "snapshot_url": f"{CONFIG['PUBLIC_URL_BASE']}{snapshot_filename}",
695
- "confidence": round(float(conf), 2) # Ensure confidence is stored as float
696
  })
697
 
698
  logger.info(f"Captured snapshot for {label} violation by worker {worker_id} at {current_time:.2f}s")
699
 
700
- # Ensure resources are released
701
  cap.release()
702
-
703
  processing_time = time.time() - start_time
704
  logger.info(f"Processing complete in {processing_time:.2f}s")
705
 
706
- # Log the snapshots for debugging
707
  logger.info(f"Snapshots: {snapshots}")
708
 
709
  violations = []
710
  for worker_id, worker_violations in unique_violations.items():
711
  for label, detection_time in worker_violations.items():
712
- # Find the confidence from snapshots, ensuring it's a float
713
  confidence = next(
714
  (float(s["confidence"]) for s in snapshots if s["worker_id"] == worker_id and s["violation"] == label),
715
  0.0
@@ -722,7 +726,6 @@ def process_video(video_data):
722
  }
723
  violations.append(violation)
724
 
725
- # Log the violations for debugging
726
  logger.info(f"Violations: {violations}")
727
 
728
  if not violations:
@@ -731,12 +734,10 @@ def process_video(video_data):
731
  return
732
 
733
  score = calculate_safety_score(violations)
734
- pdf_path, pdf_url, pdf_file = generate_violation_pdf(violations, score)
735
 
736
- # Push to Salesforce with fallback
737
  record_id, final_pdf_url = push_report_to_salesforce(violations, score, pdf_path, pdf_file)
738
 
739
- # Generate violation table with robust error handling
740
  violation_table = "| Violation | Worker ID | Time (s) | Confidence |\n"
741
  violation_table += "|-----------|-----------|----------|------------|\n"
742
 
@@ -744,7 +745,6 @@ def process_video(video_data):
744
  display_name = CONFIG["DISPLAY_NAMES"].get(v.get("violation", "Unknown"), "Unknown")
745
  worker_id = v.get("worker_id", "Unknown")
746
  timestamp = v.get("timestamp", 0.0)
747
- # Ensure confidence is a valid float
748
  try:
749
  confidence = float(v.get("confidence", 0.0))
750
  except (ValueError, TypeError) as e:
@@ -777,44 +777,62 @@ def process_video(video_data):
777
  logger.error(f"Error processing video: {str(e)}", exc_info=True)
778
  yield f"Error processing video: {str(e)}", "", "", "", ""
779
  finally:
780
- # Clean up the temporary video file
781
  if video_path and os.path.exists(video_path):
782
  try:
783
  os.remove(video_path)
784
  logger.info(f"Cleaned up temporary video file: {video_path}")
785
  except Exception as e:
786
  logger.error(f"Failed to clean up temporary video file {video_path}: {e}")
787
- # Clean up temporary directory
788
- if os.path.exists(TEMP_DIR):
789
- shutil.rmtree(TEMP_DIR, ignore_errors=True)
790
- logger.info(f"Cleaned up temporary directory: {TEMP_DIR}")
791
 
792
  def gradio_interface(video_file):
793
  """Gradio interface for the video processing"""
794
- if not video_file:
795
- return "No file uploaded.", "", "No file uploaded.", "", ""
796
-
797
  try:
798
- # Read the video file
 
 
 
 
 
 
 
799
  with open(video_file, "rb") as f:
800
  video_data = f.read()
801
-
802
- # Log the size of the uploaded video file
803
- logger.info(f"Uploaded video file size: {len(video_data)} bytes")
804
  if len(video_data) == 0:
805
  return "Uploaded video file is empty.", "", "", "", ""
806
 
807
- # Validate FFmpeg availability
 
 
 
 
 
 
808
  if not FFMPEG_AVAILABLE:
809
  return "FFmpeg is not available in the environment. Please install FFmpeg to process videos.", "", "", "", ""
810
 
811
- # Process the video
812
- for status, score, snapshots_text, record_id, details_url in process_video(video_data):
813
  yield status, score, snapshots_text, record_id, details_url
814
 
815
  except Exception as e:
816
  logger.error(f"Error in Gradio interface: {e}", exc_info=True)
817
  yield f"Error: {str(e)}", "", "Error in processing.", "", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
818
 
819
  # ========================== # Gradio Interface # ==========================
820
  interface = gr.Interface(
 
21
  from functools import partial
22
  import tempfile
23
  import shutil
24
+ import tenacity
25
 
26
  # ========================== # Configuration and Setup # ==========================
 
 
 
 
 
 
 
 
27
  # Configure logging for better debugging
28
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
29
  logger = logging.getLogger(__name__)
 
186
  CONFIG = {
187
  "MODEL_PATH": "yolov8_safety.pt",
188
  "FALLBACK_MODEL": "yolov8n.pt",
 
189
  "VIOLATION_LABELS": {
190
  0: "no_helmet",
191
  1: "no_harness",
 
321
  score = max(0, 100 - total_penalty)
322
  return score
323
 
324
+ def generate_violation_pdf(violations, score, output_dir):
325
  """Generate a PDF report for the detected violations"""
326
  try:
327
  pdf_filename = f"violations_{int(time.time())}.pdf"
328
+ pdf_path = os.path.join(output_dir, pdf_filename)
329
  pdf_file = BytesIO()
330
  c = canvas.Canvas(pdf_file, pagesize=letter)
331
 
 
495
  logger.error(f"Salesforce record creation failed: {e}")
496
  return "N/A", "Salesforce integration failed."
497
 
498
+ @tenacity.retry(
499
+ stop=tenacity.stop_after_attempt(3),
500
+ wait=tenacity.wait_fixed(1),
501
+ retry=tenacity.retry_if_exception_type((IOError, OSError)),
502
+ before_sleep=lambda retry_state: logger.info(f"Retrying file access (attempt {retry_state.attempt_number}/3)...")
503
+ )
504
+ def verify_and_open_video(video_path):
505
+ """Verify file existence and readability, then open with cv2.VideoCapture"""
506
+ if not os.path.exists(video_path):
507
+ raise FileNotFoundError(f"Temporary video file not found: {video_path}")
508
+
509
+ file_size = os.path.getsize(video_path)
510
+ if file_size == 0:
511
+ raise ValueError(f"Temporary video file is empty: {video_path}")
512
+
513
+ with open(video_path, "rb") as f:
514
+ f.read(1) # Test read access
515
+
516
+ cap = cv2.VideoCapture(video_path)
517
+ if not cap.isOpened():
518
+ raise ValueError("Could not open video file. Ensure the video format is supported (e.g., MP4) and FFmpeg is installed.")
519
+
520
+ return cap
521
+
522
+ def process_video(video_data, temp_dir):
523
  """Process video to detect safety violations"""
524
+ video_path = None
525
+ output_dir = os.path.join(temp_dir, "output")
526
+ os.makedirs(output_dir, exist_ok=True)
527
+ os.environ['YOLO_CONFIG_DIR'] = temp_dir # Set YOLO config dir for this process
528
+
529
  try:
530
  # Validate video data
531
  if not video_data:
532
  raise ValueError("Empty video data provided.")
533
 
 
534
  logger.info(f"Received video data size: {len(video_data)} bytes")
535
  if len(video_data) == 0:
536
  raise ValueError("Video data is empty.")
537
 
538
+ # Save video to a temporary file
539
+ with tempfile.NamedTemporaryFile(suffix=".mp4", dir=temp_dir, delete=False) as temp_file:
540
  temp_file.write(video_data)
541
+ temp_file.flush()
542
  video_path = temp_file.name
543
  logger.info(f"Video saved to temporary file: {video_path}")
544
 
 
550
  raise ValueError(f"Temporary video file is empty: {video_path}")
551
  logger.info(f"Temporary video file size: {file_size} bytes")
552
 
553
+ # Open video with OpenCV (with retries)
554
+ cap = verify_and_open_video(video_path)
555
+ logger.info(f"Successfully opened video file: {video_path}")
 
 
 
 
 
 
 
 
 
556
 
557
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
558
  fps = cap.get(cv2.CAP_PROP_FPS) or 30
 
561
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
562
  logger.info(f"Video properties: {duration:.2f}s, {total_frames} frames, {fps:.1f} FPS, {width}x{height}")
563
 
 
564
  if total_frames <= 0:
565
  raise ValueError("Video has no frames.")
566
 
 
605
  logger.info("No more frames to process.")
606
  break
607
 
 
608
  try:
609
  results = model(batch_frames, device=device, conf=0.1, verbose=False)
610
  except Exception as e:
 
667
  detection = {
668
  "worker_id": worker_id,
669
  "violation": label,
670
+ "confidence": round(float(conf), 2),
671
  "bounding_box": bbox,
672
  "timestamp": current_time
673
  }
 
686
  )
687
 
688
  snapshot_filename = f"violation_{label}_worker{worker_id}_{int(current_time*100)}.jpg"
689
+ snapshot_path = os.path.join(output_dir, snapshot_filename)
690
 
691
  cv2.imwrite(
692
  snapshot_path,
 
700
  "timestamp": current_time,
701
  "snapshot_path": snapshot_path,
702
  "snapshot_url": f"{CONFIG['PUBLIC_URL_BASE']}{snapshot_filename}",
703
+ "confidence": round(float(conf), 2)
704
  })
705
 
706
  logger.info(f"Captured snapshot for {label} violation by worker {worker_id} at {current_time:.2f}s")
707
 
 
708
  cap.release()
 
709
  processing_time = time.time() - start_time
710
  logger.info(f"Processing complete in {processing_time:.2f}s")
711
 
 
712
  logger.info(f"Snapshots: {snapshots}")
713
 
714
  violations = []
715
  for worker_id, worker_violations in unique_violations.items():
716
  for label, detection_time in worker_violations.items():
 
717
  confidence = next(
718
  (float(s["confidence"]) for s in snapshots if s["worker_id"] == worker_id and s["violation"] == label),
719
  0.0
 
726
  }
727
  violations.append(violation)
728
 
 
729
  logger.info(f"Violations: {violations}")
730
 
731
  if not violations:
 
734
  return
735
 
736
  score = calculate_safety_score(violations)
737
+ pdf_path, pdf_url, pdf_file = generate_violation_pdf(violations, score, output_dir)
738
 
 
739
  record_id, final_pdf_url = push_report_to_salesforce(violations, score, pdf_path, pdf_file)
740
 
 
741
  violation_table = "| Violation | Worker ID | Time (s) | Confidence |\n"
742
  violation_table += "|-----------|-----------|----------|------------|\n"
743
 
 
745
  display_name = CONFIG["DISPLAY_NAMES"].get(v.get("violation", "Unknown"), "Unknown")
746
  worker_id = v.get("worker_id", "Unknown")
747
  timestamp = v.get("timestamp", 0.0)
 
748
  try:
749
  confidence = float(v.get("confidence", 0.0))
750
  except (ValueError, TypeError) as e:
 
777
  logger.error(f"Error processing video: {str(e)}", exc_info=True)
778
  yield f"Error processing video: {str(e)}", "", "", "", ""
779
  finally:
 
780
  if video_path and os.path.exists(video_path):
781
  try:
782
  os.remove(video_path)
783
  logger.info(f"Cleaned up temporary video file: {video_path}")
784
  except Exception as e:
785
  logger.error(f"Failed to clean up temporary video file {video_path}: {e}")
 
 
 
 
786
 
787
  def gradio_interface(video_file):
788
  """Gradio interface for the video processing"""
789
+ temp_dir = None
790
+ local_video_path = None
 
791
  try:
792
+ if not video_file:
793
+ return "No file uploaded.", "", "No file uploaded.", "", ""
794
+
795
+ # Create a unique temporary directory for this video
796
+ temp_dir = tempfile.mkdtemp(prefix="Ultralytics_")
797
+ logger.info(f"Created temporary directory for video processing: {temp_dir}")
798
+
799
+ # Copy Gradio's video file to a local temporary file
800
  with open(video_file, "rb") as f:
801
  video_data = f.read()
802
+ logger.info(f"Read Gradio video file: {video_file}, size: {len(video_data)} bytes")
803
+
 
804
  if len(video_data) == 0:
805
  return "Uploaded video file is empty.", "", "", "", ""
806
 
807
+ # Save to a local temporary file to avoid Gradio file deletion
808
+ with tempfile.NamedTemporaryFile(suffix=".mp4", dir=temp_dir, delete=False) as temp_file:
809
+ temp_file.write(video_data)
810
+ temp_file.flush()
811
+ local_video_path = temp_file.name
812
+ logger.info(f"Copied Gradio video to local temporary file: {local_video_path}")
813
+
814
  if not FFMPEG_AVAILABLE:
815
  return "FFmpeg is not available in the environment. Please install FFmpeg to process videos.", "", "", "", ""
816
 
817
+ for status, score, snapshots_text, record_id, details_url in process_video(video_data, temp_dir):
 
818
  yield status, score, snapshots_text, record_id, details_url
819
 
820
  except Exception as e:
821
  logger.error(f"Error in Gradio interface: {e}", exc_info=True)
822
  yield f"Error: {str(e)}", "", "Error in processing.", "", ""
823
+ finally:
824
+ # Clean up the local temporary video file
825
+ if local_video_path and os.path.exists(local_video_path):
826
+ try:
827
+ os.remove(local_video_path)
828
+ logger.info(f"Cleaned up local temporary video file: {local_video_path}")
829
+ except Exception as e:
830
+ logger.error(f"Failed to clean up local temporary video file {local_video_path}: {e}")
831
+
832
+ # Clean up the temporary directory
833
+ if temp_dir and os.path.exists(temp_dir):
834
+ shutil.rmtree(temp_dir, ignore_errors=True)
835
+ logger.info(f"Cleaned up temporary directory: {temp_dir}")
836
 
837
  # ========================== # Gradio Interface # ==========================
838
  interface = gr.Interface(