Spaces:
Sleeping
Sleeping
Create utils.py
Browse files
utils.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
from reportlab.lib.pagesizes import letter
|
| 5 |
+
from reportlab.pdfgen import canvas
|
| 6 |
+
from reportlab.lib.units import inch
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
import base64
|
| 9 |
+
import asyncio
|
| 10 |
+
import logging
|
| 11 |
+
from simple_salesforce import Salesforce
|
| 12 |
+
import tenacity
|
| 13 |
+
from config import CONFIG
|
| 14 |
+
import dlib
|
| 15 |
+
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
async def preprocess_frame(frame):
|
| 19 |
+
frame = cv2.resize(frame, CONFIG["TARGET_RESOLUTION"], interpolation=cv2.INTER_LINEAR)
|
| 20 |
+
frame = cv2.convertScaleAbs(frame, alpha=1.2, beta=20)
|
| 21 |
+
return frame
|
| 22 |
+
|
| 23 |
+
def draw_detections(frame, detections):
|
| 24 |
+
result_frame = frame.copy()
|
| 25 |
+
for det in detections:
|
| 26 |
+
label = det.get("violation", "Unknown")
|
| 27 |
+
confidence = det.get("confidence", 0.0)
|
| 28 |
+
x, y, w, h = det.get("bounding_box", [0, 0, 0, 0])
|
| 29 |
+
worker_id = det.get("worker_id", "Unknown")
|
| 30 |
+
x1, y1 = int(x - w/2), int(y - h/2)
|
| 31 |
+
x2, y2 = int(x + w/2), int(y + h/2)
|
| 32 |
+
color = CONFIG["CLASS_COLORS"].get(label, (0, 0, 255))
|
| 33 |
+
cv2.rectangle(result_frame, (x1, y1), (x2, y2), color, 3)
|
| 34 |
+
display_text = f"{CONFIG['DISPLAY_NAMES'].get(label, label)} (Worker {worker_id})"
|
| 35 |
+
text_size = cv2.getTextSize(display_text, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
|
| 36 |
+
cv2.rectangle(result_frame, (x1, y1-text_size[1]-10), (x1+text_size[0]+10, y1), (0, 0, 0), -1)
|
| 37 |
+
cv2.putText(result_frame, display_text, (x1+5, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
| 38 |
+
cv2.putText(result_frame, f"Conf: {confidence:.2f}", (x1+5, y2+20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
|
| 39 |
+
return result_frame
|
| 40 |
+
|
| 41 |
+
def calculate_safety_score(violations):
|
| 42 |
+
penalties = {
|
| 43 |
+
"no_helmet": 25, "no_harness": 30, "unsafe_posture": 20,
|
| 44 |
+
"unsafe_zone": 35, "improper_tool_use": 25
|
| 45 |
+
}
|
| 46 |
+
worker_violations = {}
|
| 47 |
+
for v in violations:
|
| 48 |
+
worker_id = v.get("worker_id", "Unknown")
|
| 49 |
+
worker_violations.setdefault(worker_id, set()).add(v.get("violation", "Unknown"))
|
| 50 |
+
total_penalty = sum(sum(penalties.get(v, 0) for v in worker_violations[wid]) for wid in worker_violations)
|
| 51 |
+
return max(0, 100 - total_penalty)
|
| 52 |
+
|
| 53 |
+
async def generate_violation_pdf(violations, score, output_dir):
|
| 54 |
+
try:
|
| 55 |
+
pdf_filename = f"violations_{int(asyncio.get_event_loop().time())}.pdf"
|
| 56 |
+
pdf_path = os.path.join(output_dir, pdf_filename)
|
| 57 |
+
pdf_file = BytesIO()
|
| 58 |
+
c = canvas.Canvas(pdf_file, pagesize=letter)
|
| 59 |
+
c.setFont("Helvetica-Bold", 16)
|
| 60 |
+
c.drawString(1 * inch, 10 * inch, "Worksite Safety Violation Report")
|
| 61 |
+
c.setFont("Helvetica", 12)
|
| 62 |
+
c.drawString(1 * inch, 9.5 * inch, f"Date: {asyncio.get_event_loop().time():.0f}")
|
| 63 |
+
c.setFont("Helvetica-Bold", 14)
|
| 64 |
+
c.drawString(1 * inch, 8.7 * inch, f"Safety Compliance Score: {score}%")
|
| 65 |
+
c.setFont("Helvetica", 10)
|
| 66 |
+
y_position = 8.2 * inch
|
| 67 |
+
worker_violations = {}
|
| 68 |
+
for v in violations:
|
| 69 |
+
worker_id = v.get("worker_id", "Unknown")
|
| 70 |
+
worker_violations.setdefault(worker_id, []).append(v)
|
| 71 |
+
for worker_id, worker_vios in worker_violations.items():
|
| 72 |
+
c.drawString(1 * inch, y_position, f"Worker {worker_id}:")
|
| 73 |
+
y_position -= 0.2 * inch
|
| 74 |
+
for v in worker_vios:
|
| 75 |
+
display_name = CONFIG["DISPLAY_NAMES"].get(v.get("violation", "Unknown"), "Unknown")
|
| 76 |
+
c.drawString(1.2 * inch, y_position, f" - {display_name} at {v.get('timestamp', 0.0):.2f}s (Conf: {v.get('confidence', 0.0):.2f})")
|
| 77 |
+
y_position -= 0.2 * inch
|
| 78 |
+
if y_position < 1 * inch:
|
| 79 |
+
c.showPage()
|
| 80 |
+
y_position = 10 * inch
|
| 81 |
+
c.save()
|
| 82 |
+
pdf_file.seek(0)
|
| 83 |
+
with open(pdf_path, "wb") as f:
|
| 84 |
+
f.write(pdf_file.getvalue())
|
| 85 |
+
return pdf_path, f"{CONFIG['PUBLIC_URL_BASE']}{pdf_filename}", pdf_file
|
| 86 |
+
except Exception as e:
|
| 87 |
+
logger.error(f"Error generating PDF: {e}")
|
| 88 |
+
return "", "", None
|
| 89 |
+
|
| 90 |
+
@tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(2))
|
| 91 |
+
async def connect_to_salesforce():
|
| 92 |
+
sf = Salesforce(**CONFIG["SF_CREDENTIALS"])
|
| 93 |
+
logger.info("Connected to Salesforce")
|
| 94 |
+
return sf
|
| 95 |
+
|
| 96 |
+
async def upload_pdf_to_salesforce(sf, pdf_file, record_id):
|
| 97 |
+
try:
|
| 98 |
+
encoded_pdf = base64.b64encode(pdf_file.getvalue()).decode('utf-8')
|
| 99 |
+
content_version = sf.ContentVersion.create({
|
| 100 |
+
"Title": f"Safety_Violation_Report_{int(asyncio.get_event_loop().time())}",
|
| 101 |
+
"PathOnClient": f"safety_violation_{int(asyncio.get_event_loop().time())}.pdf",
|
| 102 |
+
"VersionData": encoded_pdf,
|
| 103 |
+
"FirstPublishLocationId": record_id
|
| 104 |
+
})
|
| 105 |
+
result = sf.query(f"SELECT Id, ContentDocumentId FROM ContentVersion WHERE Id = '{content_version['id']}'")
|
| 106 |
+
return f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{content_version['id']}"
|
| 107 |
+
except Exception as e:
|
| 108 |
+
logger.error(f"Error uploading PDF to Salesforce: {e}")
|
| 109 |
+
return ""
|
| 110 |
+
|
| 111 |
+
async def push_report_to_salesforce(violations, score, pdf_path, pdf_file):
|
| 112 |
+
try:
|
| 113 |
+
sf = await connect_to_salesforce()
|
| 114 |
+
violations_text = "".join(
|
| 115 |
+
f"Worker {v.get('worker_id', 'Unknown')}: {CONFIG['DISPLAY_NAMES'].get(v.get('violation', 'Unknown'), 'Unknown')} at {v.get('timestamp', 0.0):.2f}s (Conf: {v.get('confidence', 0.0):.2f})\n"
|
| 116 |
+
for v in violations
|
| 117 |
+
) or "No violations detected."
|
| 118 |
+
record_data = {
|
| 119 |
+
"Compliance_Score__c": score,
|
| 120 |
+
"Violations_Found__c": len(violations),
|
| 121 |
+
"Violations_Details__c": violations_text,
|
| 122 |
+
"Status__c": "Pending",
|
| 123 |
+
"PDF_Report_URL__c": f"{CONFIG['PUBLIC_URL_BASE']}{os.path.basename(pdf_path)}" if pdf_path else ""
|
| 124 |
+
}
|
| 125 |
+
record = sf.Safety_Video_Report__c.create(record_data)
|
| 126 |
+
if pdf_file:
|
| 127 |
+
uploaded_url = await upload_pdf_to_salesforce(sf, pdf_file, record["id"])
|
| 128 |
+
if uploaded_url:
|
| 129 |
+
sf.Safety_Video_Report__c.update(record["id"], {"PDF_Report_URL__c": uploaded_url})
|
| 130 |
+
return record["id"], uploaded_url or record_data["PDF_Report_URL__c"]
|
| 131 |
+
except Exception as e:
|
| 132 |
+
logger.error(f"Salesforce record creation failed: {e}")
|
| 133 |
+
return "N/A", "Salesforce integration failed."
|
| 134 |
+
|
| 135 |
+
@tenacity.retry(
|
| 136 |
+
stop=tenacity.stop_after_attempt(3),
|
| 137 |
+
wait=tenacity.wait_fixed(1),
|
| 138 |
+
retry=tenacity.retry_if_exception_type((IOError, OSError))
|
| 139 |
+
)
|
| 140 |
+
def verify_and_open_video(video_path):
|
| 141 |
+
if not os.path.exists(video_path) or os.path.getsize(video_path) == 0:
|
| 142 |
+
raise ValueError(f"Invalid video file: {video_path}")
|
| 143 |
+
cap = cv2.VideoCapture(video_path)
|
| 144 |
+
if not cap.isOpened():
|
| 145 |
+
raise ValueError("Could not open video file.")
|
| 146 |
+
return cap
|
| 147 |
+
|
| 148 |
+
def blur_faces(frame):
|
| 149 |
+
detector = dlib.get_frontal_face_detector()
|
| 150 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 151 |
+
faces = detector(gray)
|
| 152 |
+
for face in faces:
|
| 153 |
+
x, y, w, h = face.left(), face.top(), face.width(), face.height()
|
| 154 |
+
roi = frame[y:y+h, x:x+w]
|
| 155 |
+
roi = cv2.GaussianBlur(roi, (23, 23), 30)
|
| 156 |
+
frame[y:y+h, x:x+w] = roi
|
| 157 |
+
return frame
|