Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -280,6 +280,14 @@ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
| 280 |
logger.info(f"Using device: {device}")
|
| 281 |
|
| 282 |
def load_model():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
try:
|
| 284 |
processor = DetrImageProcessor.from_pretrained(CONFIG["MODEL_NAME"])
|
| 285 |
model = DetrForObjectDetection.from_pretrained(CONFIG["MODEL_NAME"]).to(device)
|
|
@@ -289,7 +297,7 @@ def load_model():
|
|
| 289 |
logger.info(f"Model classes: {model.config.id2label}")
|
| 290 |
return processor, model
|
| 291 |
except Exception as e:
|
| 292 |
-
logger.error(f"Failed to load model: {e}")
|
| 293 |
raise
|
| 294 |
|
| 295 |
processor, model = load_model()
|
|
@@ -309,21 +317,21 @@ def is_unsafe_posture(box, frame_shape):
|
|
| 309 |
height = y2 - y1
|
| 310 |
width = x2 - x1
|
| 311 |
aspect_ratio = height / max(width, 1)
|
| 312 |
-
return aspect_ratio > 2.0
|
| 313 |
|
| 314 |
def is_improper_tool_use(person_box, tool_box):
|
| 315 |
"""Placeholder for improper tool use. Fine-tune DETR for specific tools."""
|
| 316 |
person_center = ((person_box[0] + person_box[2]) / 2, (person_box[1] + person_box[3]) / 2)
|
| 317 |
tool_center = ((tool_box[0] + tool_box[2]) / 2, (tool_box[1] + tool_box[3]) / 2)
|
| 318 |
dist = distance.euclidean(person_center, tool_center)
|
| 319 |
-
return dist > 100
|
| 320 |
|
| 321 |
def is_unsafe_zone(person_box, frame_shape):
|
| 322 |
"""Check if person is in restricted area (e.g., top-left quadrant)."""
|
| 323 |
px, py, pw, ph = person_box
|
| 324 |
frame_h, frame_w = frame_shape
|
| 325 |
person_center = (px + pw / 2, py + ph / 2)
|
| 326 |
-
unsafe_zone = (0, 0, 0.5, 0.5)
|
| 327 |
return (unsafe_zone[0] * frame_w < person_center[0] < unsafe_zone[2] * frame_w and
|
| 328 |
unsafe_zone[1] * frame_h < person_center[1] < unsafe_zone[3] * frame_h)
|
| 329 |
|
|
@@ -487,7 +495,7 @@ def push_report_to_salesforce(violations, score, pdf_path, pdf_file):
|
|
| 487 |
logger.info(f"Creating Salesforce record with data: {record_data}")
|
| 488 |
try:
|
| 489 |
record = sf.Safety_Video_Report__c.create(record_data)
|
| 490 |
-
logger.info(f"Created
|
| 491 |
except Exception as e:
|
| 492 |
logger.error(f"Failed to create Safety_Video_Report__c: {e}")
|
| 493 |
record = sf.Account.create({"Name": f"Safety_Report_{int(time.time())}"})
|
|
@@ -497,12 +505,12 @@ def push_report_to_salesforce(violations, score, pdf_path, pdf_file):
|
|
| 497 |
uploaded_url = upload_pdf_to_salesforce(sf, pdf_file, record_id)
|
| 498 |
if uploaded_url:
|
| 499 |
try:
|
| 500 |
-
sf.Safety_Video_Report__c.update(record_id, {"
|
| 501 |
logger.info(f"Updated record {record_id} with PDF URL: {uploaded_url}")
|
| 502 |
except Exception as e:
|
| 503 |
logger.error(f"Failed to update Safety_Video_Report__c: {e}")
|
| 504 |
sf.Account.update(record_id, {"Description": uploaded_url})
|
| 505 |
-
logger.info(f"Updated
|
| 506 |
pdf_url = uploaded_url
|
| 507 |
return record_id, pdf_url
|
| 508 |
except Exception as e:
|
|
@@ -626,7 +634,7 @@ def process_video(video_data, temp_dir):
|
|
| 626 |
break
|
| 627 |
ret, frame = cap.read()
|
| 628 |
if not ret:
|
| 629 |
-
logger.warning(f"Failed to read frame {frame_idx}. Skipping.")
|
| 630 |
break
|
| 631 |
original_frame = frame.copy()
|
| 632 |
frame = preprocess_frame(frame)
|
|
@@ -636,7 +644,7 @@ def process_video(video_data, temp_dir):
|
|
| 636 |
batch_frames.append(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
|
| 637 |
batch_indices.append(frame_idx)
|
| 638 |
batch_originals.append(original_frame)
|
| 639 |
-
processed_frames += 1
|
| 640 |
|
| 641 |
if not batch_frames:
|
| 642 |
logger.info("No more frames to process.")
|
|
@@ -663,7 +671,7 @@ def process_video(video_data, temp_dir):
|
|
| 663 |
progress = (processed_frames / total_frames) * 100
|
| 664 |
elapsed_time = current_time - start_time
|
| 665 |
fps_processed = processed_frames / elapsed_time if elapsed_time > 0 else 0
|
| 666 |
-
yield f"Processing video... {progress:.1f}% complete (Frame {processed_frames}/{total_frames}, {fps_processed:.1f} FPS)", "", "", "", ""
|
| 667 |
last_yield_time = current_time
|
| 668 |
|
| 669 |
for i, (result, frame_idx, original_frame) in enumerate(zip(results, batch_indices, batch_originals)):
|
|
@@ -681,8 +689,8 @@ def process_video(video_data, temp_dir):
|
|
| 681 |
bbox_xywh = [x + w/2, y + h/2, w, h]
|
| 682 |
|
| 683 |
if label_name in ["no_helmet", "no_harness"] and conf >= CONFIG["CONFIDENCE_THRESHOLDS"].get(label_name, 0.25):
|
| 684 |
-
if label_name == "no_helmet" and not
|
| 685 |
-
logger.info(f"Frame {frame_idx}:
|
| 686 |
continue
|
| 687 |
track_inputs.append({"bbox": bbox_xywh, "conf": conf, "cls": label_name})
|
| 688 |
elif label_name == "person":
|
|
@@ -690,7 +698,7 @@ def process_video(video_data, temp_dir):
|
|
| 690 |
elif label_name in ["hammer", "wrench"]: # Example tools; update with your dataset
|
| 691 |
tool_boxes.append(bbox_xywh)
|
| 692 |
|
| 693 |
-
# Handle Unsafe
|
| 694 |
for pbox in person_boxes:
|
| 695 |
if is_unsafe_posture(pbox, original_frame.shape[:2]):
|
| 696 |
track_inputs.append({"bbox": pbox, "conf": 0.9, "cls": "unsafe_posture"})
|
|
@@ -714,7 +722,6 @@ def process_video(video_data, temp_dir):
|
|
| 714 |
tracker_id = obj['id']
|
| 715 |
label = obj['cls']
|
| 716 |
conf = obj['score']
|
| 717 |
-
bbox = obj['bbox']
|
| 718 |
|
| 719 |
if label not in CONFIG["VIOLATION_LABELS"]:
|
| 720 |
continue
|
|
@@ -764,7 +771,7 @@ def process_video(video_data, temp_dir):
|
|
| 764 |
|
| 765 |
if not violations:
|
| 766 |
logger.info("No violations detected after processing")
|
| 767 |
-
yield "No violations detected in the video.", "Safety Score: 100%", "No snapshots captured.", "N/A", "N/A"
|
| 768 |
return
|
| 769 |
|
| 770 |
snapshots = []
|
|
@@ -865,12 +872,13 @@ def process_video(video_data, temp_dir):
|
|
| 865 |
f"Safety Score: {score}%",
|
| 866 |
snapshots_text,
|
| 867 |
f"Salesforce Record ID: {record_id}",
|
| 868 |
-
final_pdf_url
|
|
|
|
| 869 |
)
|
| 870 |
|
| 871 |
except Exception as e:
|
| 872 |
logger.error(f"Error processing video: {str(e)}", exc_info=True)
|
| 873 |
-
yield f"Error processing video: {str(e)}", "", "", "", ""
|
| 874 |
finally:
|
| 875 |
if video_path and os.path.exists(video_path):
|
| 876 |
try:
|
|
@@ -886,7 +894,7 @@ def gradio_interface(video_file):
|
|
| 886 |
local_video_path = None
|
| 887 |
try:
|
| 888 |
if not video_file:
|
| 889 |
-
return "No file uploaded.", "", "No file uploaded.", "", ""
|
| 890 |
|
| 891 |
temp_dir = tempfile.mkdtemp(prefix="DETR_")
|
| 892 |
logger.info(f"Created temporary directory for video processing: {temp_dir}")
|
|
@@ -896,7 +904,7 @@ def gradio_interface(video_file):
|
|
| 896 |
logger.info(f"Read Gradio video file: {video_file}, size: {len(video_data)} bytes")
|
| 897 |
|
| 898 |
if len(video_data) == 0:
|
| 899 |
-
return "Uploaded video file is empty.", "", "", "", ""
|
| 900 |
|
| 901 |
with tempfile.NamedTemporaryFile(suffix=".mp4", dir=temp_dir, delete=False) as temp_file:
|
| 902 |
temp_file.write(video_data)
|
|
@@ -905,14 +913,14 @@ def gradio_interface(video_file):
|
|
| 905 |
logger.info(f"Copied Gradio video to local temporary file: {local_video_path}")
|
| 906 |
|
| 907 |
if not FFMPEG_AVAILABLE:
|
| 908 |
-
return "FFmpeg is not available in the environment. Please install FFmpeg to process videos.", "", "", "", ""
|
| 909 |
|
| 910 |
-
for status, score, snapshots_text, record_id, details_url in process_video(video_data, temp_dir):
|
| 911 |
-
yield status, score, snapshots_text, record_id, details_url
|
| 912 |
|
| 913 |
except Exception as e:
|
| 914 |
logger.error(f"Error in Gradio interface: {e}", exc_info=True)
|
| 915 |
-
yield f"Error: {str(e)}", "", "Error in processing.", "", ""
|
| 916 |
finally:
|
| 917 |
if local_video_path and os.path.exists(local_video_path):
|
| 918 |
try:
|
|
@@ -936,7 +944,8 @@ interface = gr.Interface(
|
|
| 936 |
gr.Textbox(label="Compliance Score"),
|
| 937 |
gr.Markdown(label="Snapshots"),
|
| 938 |
gr.Textbox(label="Salesforce Record ID"),
|
| 939 |
-
gr.Textbox(label="Violation Details URL")
|
|
|
|
| 940 |
],
|
| 941 |
title="Worksite Safety Violation Analyzer",
|
| 942 |
description="Upload site videos to detect safety violations (No Helmet, No Harness, Unsafe Posture, Unsafe Zone, Improper Tool Use). Each unique violation is detected only once per worker.",
|
|
|
|
| 280 |
logger.info(f"Using device: {device}")
|
| 281 |
|
| 282 |
def load_model():
|
| 283 |
+
try:
|
| 284 |
+
# Check for timm dependency
|
| 285 |
+
import timm
|
| 286 |
+
logger.info("timm library is available.")
|
| 287 |
+
except ImportError as e:
|
| 288 |
+
logger.error("timm library is not installed. Install it with: pip install timm")
|
| 289 |
+
raise ImportError("timm is required for DetrConvEncoder. Run `pip install timm` and restart your runtime.") from e
|
| 290 |
+
|
| 291 |
try:
|
| 292 |
processor = DetrImageProcessor.from_pretrained(CONFIG["MODEL_NAME"])
|
| 293 |
model = DetrForObjectDetection.from_pretrained(CONFIG["MODEL_NAME"]).to(device)
|
|
|
|
| 297 |
logger.info(f"Model classes: {model.config.id2label}")
|
| 298 |
return processor, model
|
| 299 |
except Exception as e:
|
| 300 |
+
logger.error(f"Failed to load model: {str(e)}")
|
| 301 |
raise
|
| 302 |
|
| 303 |
processor, model = load_model()
|
|
|
|
| 317 |
height = y2 - y1
|
| 318 |
width = x2 - x1
|
| 319 |
aspect_ratio = height / max(width, 1)
|
| 320 |
+
return aspect_ratio > 2.0
|
| 321 |
|
| 322 |
def is_improper_tool_use(person_box, tool_box):
|
| 323 |
"""Placeholder for improper tool use. Fine-tune DETR for specific tools."""
|
| 324 |
person_center = ((person_box[0] + person_box[2]) / 2, (person_box[1] + person_box[3]) / 2)
|
| 325 |
tool_center = ((tool_box[0] + tool_box[2]) / 2, (tool_box[1] + tool_box[3]) / 2)
|
| 326 |
dist = distance.euclidean(person_center, tool_center)
|
| 327 |
+
return dist > 100
|
| 328 |
|
| 329 |
def is_unsafe_zone(person_box, frame_shape):
|
| 330 |
"""Check if person is in restricted area (e.g., top-left quadrant)."""
|
| 331 |
px, py, pw, ph = person_box
|
| 332 |
frame_h, frame_w = frame_shape
|
| 333 |
person_center = (px + pw / 2, py + ph / 2)
|
| 334 |
+
unsafe_zone = (0, 0, 0.5, 0.5)
|
| 335 |
return (unsafe_zone[0] * frame_w < person_center[0] < unsafe_zone[2] * frame_w and
|
| 336 |
unsafe_zone[1] * frame_h < person_center[1] < unsafe_zone[3] * frame_h)
|
| 337 |
|
|
|
|
| 495 |
logger.info(f"Creating Salesforce record with data: {record_data}")
|
| 496 |
try:
|
| 497 |
record = sf.Safety_Video_Report__c.create(record_data)
|
| 498 |
+
logger.info(f"Created record: {record['id']}")
|
| 499 |
except Exception as e:
|
| 500 |
logger.error(f"Failed to create Safety_Video_Report__c: {e}")
|
| 501 |
record = sf.Account.create({"Name": f"Safety_Report_{int(time.time())}"})
|
|
|
|
| 505 |
uploaded_url = upload_pdf_to_salesforce(sf, pdf_file, record_id)
|
| 506 |
if uploaded_url:
|
| 507 |
try:
|
| 508 |
+
sf.Safety_Video_Report__c.update(record_id, {"PDF_Report_URL": uploaded_url})
|
| 509 |
logger.info(f"Updated record {record_id} with PDF URL: {uploaded_url}")
|
| 510 |
except Exception as e:
|
| 511 |
logger.error(f"Failed to update Safety_Video_Report__c: {e}")
|
| 512 |
sf.Account.update(record_id, {"Description": uploaded_url})
|
| 513 |
+
logger.info(f"Updated account record {record_id} with PDF URL")
|
| 514 |
pdf_url = uploaded_url
|
| 515 |
return record_id, pdf_url
|
| 516 |
except Exception as e:
|
|
|
|
| 634 |
break
|
| 635 |
ret, frame = cap.read()
|
| 636 |
if not ret:
|
| 637 |
+
logger.warning(f"Failed to read frame {frame_idx}. Skipping...")
|
| 638 |
break
|
| 639 |
original_frame = frame.copy()
|
| 640 |
frame = preprocess_frame(frame)
|
|
|
|
| 644 |
batch_frames.append(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
|
| 645 |
batch_indices.append(frame_idx)
|
| 646 |
batch_originals.append(original_frame)
|
| 647 |
+
processed_frames += frame_skip + 1
|
| 648 |
|
| 649 |
if not batch_frames:
|
| 650 |
logger.info("No more frames to process.")
|
|
|
|
| 671 |
progress = (processed_frames / total_frames) * 100
|
| 672 |
elapsed_time = current_time - start_time
|
| 673 |
fps_processed = processed_frames / elapsed_time if elapsed_time > 0 else 0
|
| 674 |
+
yield f"Processing video... {progress:.1f}% complete (Frame {processed_frames}/{total_frames}, {fps_processed:.1f} FPS)", "", "", "", "", ""
|
| 675 |
last_yield_time = current_time
|
| 676 |
|
| 677 |
for i, (result, frame_idx, original_frame) in enumerate(zip(results, batch_indices, batch_originals)):
|
|
|
|
| 689 |
bbox_xywh = [x + w/2, y + h/2, w, h]
|
| 690 |
|
| 691 |
if label_name in ["no_helmet", "no_harness"] and conf >= CONFIG["CONFIDENCE_THRESHOLDS"].get(label_name, 0.25):
|
| 692 |
+
if label_name == "no_helmet" and not validate_helmet(original_frame, bbox_xywh, conf):
|
| 693 |
+
logger.info(f"Frame {frame_idx}: Height false positive violation filtered out at {conf:.2f} confidence")
|
| 694 |
continue
|
| 695 |
track_inputs.append({"bbox": bbox_xywh, "conf": conf, "cls": label_name})
|
| 696 |
elif label_name == "person":
|
|
|
|
| 698 |
elif label_name in ["hammer", "wrench"]: # Example tools; update with your dataset
|
| 699 |
tool_boxes.append(bbox_xywh)
|
| 700 |
|
| 701 |
+
# Handle Unsafe violations
|
| 702 |
for pbox in person_boxes:
|
| 703 |
if is_unsafe_posture(pbox, original_frame.shape[:2]):
|
| 704 |
track_inputs.append({"bbox": pbox, "conf": 0.9, "cls": "unsafe_posture"})
|
|
|
|
| 722 |
tracker_id = obj['id']
|
| 723 |
label = obj['cls']
|
| 724 |
conf = obj['score']
|
|
|
|
| 725 |
|
| 726 |
if label not in CONFIG["VIOLATION_LABELS"]:
|
| 727 |
continue
|
|
|
|
| 771 |
|
| 772 |
if not violations:
|
| 773 |
logger.info("No violations detected after processing")
|
| 774 |
+
yield "No violations detected in the video.", "Safety Score: 100%", "No snapshots captured.", "N/A", "N/A", ""
|
| 775 |
return
|
| 776 |
|
| 777 |
snapshots = []
|
|
|
|
| 872 |
f"Safety Score: {score}%",
|
| 873 |
snapshots_text,
|
| 874 |
f"Salesforce Record ID: {record_id}",
|
| 875 |
+
final_pdf_url,
|
| 876 |
+
""
|
| 877 |
)
|
| 878 |
|
| 879 |
except Exception as e:
|
| 880 |
logger.error(f"Error processing video: {str(e)}", exc_info=True)
|
| 881 |
+
yield f"Error processing video: {str(e)}", "", "", "", "", ""
|
| 882 |
finally:
|
| 883 |
if video_path and os.path.exists(video_path):
|
| 884 |
try:
|
|
|
|
| 894 |
local_video_path = None
|
| 895 |
try:
|
| 896 |
if not video_file:
|
| 897 |
+
return "No file uploaded.", "", "No file uploaded.", "", "", ""
|
| 898 |
|
| 899 |
temp_dir = tempfile.mkdtemp(prefix="DETR_")
|
| 900 |
logger.info(f"Created temporary directory for video processing: {temp_dir}")
|
|
|
|
| 904 |
logger.info(f"Read Gradio video file: {video_file}, size: {len(video_data)} bytes")
|
| 905 |
|
| 906 |
if len(video_data) == 0:
|
| 907 |
+
return "Uploaded video file is empty.", "", "", "", "", ""
|
| 908 |
|
| 909 |
with tempfile.NamedTemporaryFile(suffix=".mp4", dir=temp_dir, delete=False) as temp_file:
|
| 910 |
temp_file.write(video_data)
|
|
|
|
| 913 |
logger.info(f"Copied Gradio video to local temporary file: {local_video_path}")
|
| 914 |
|
| 915 |
if not FFMPEG_AVAILABLE:
|
| 916 |
+
return "FFmpeg is not available in the environment. Please install FFmpeg to process videos.", "", "", "", "", ""
|
| 917 |
|
| 918 |
+
for status, score, snapshots_text, record_id, details_url, _ in process_video(video_data, temp_dir):
|
| 919 |
+
yield status, score, snapshots_text, record_id, details_url, ""
|
| 920 |
|
| 921 |
except Exception as e:
|
| 922 |
logger.error(f"Error in Gradio interface: {e}", exc_info=True)
|
| 923 |
+
yield f"Error: {str(e)}", "", "Error in processing.", "", "", ""
|
| 924 |
finally:
|
| 925 |
if local_video_path and os.path.exists(local_video_path):
|
| 926 |
try:
|
|
|
|
| 944 |
gr.Textbox(label="Compliance Score"),
|
| 945 |
gr.Markdown(label="Snapshots"),
|
| 946 |
gr.Textbox(label="Salesforce Record ID"),
|
| 947 |
+
gr.Textbox(label="Violation Details URL"),
|
| 948 |
+
gr.Textbox(label="Error Log", visible=False)
|
| 949 |
],
|
| 950 |
title="Worksite Safety Violation Analyzer",
|
| 951 |
description="Upload site videos to detect safety violations (No Helmet, No Harness, Unsafe Posture, Unsafe Zone, Improper Tool Use). Each unique violation is detected only once per worker.",
|