Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -46,12 +46,25 @@ SF_CREDENTIALS = {
|
|
| 46 |
@retry(stop_max_attempt_number=3, wait_fixed=2000)
|
| 47 |
def connect_to_salesforce():
|
| 48 |
try:
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
sf.describe()
|
| 52 |
return sf
|
| 53 |
except Exception as e:
|
| 54 |
-
logger.error(f"Salesforce connection failed: {e}")
|
| 55 |
raise
|
| 56 |
|
| 57 |
class AttendanceSystem:
|
|
@@ -69,12 +82,13 @@ class AttendanceSystem:
|
|
| 69 |
self.recognition_cooldown = 5 # seconds
|
| 70 |
self.video_file_path = None
|
| 71 |
self.video_processing = False
|
|
|
|
| 72 |
|
| 73 |
# Initialize Salesforce
|
| 74 |
try:
|
| 75 |
self.sf = connect_to_salesforce()
|
| 76 |
except Exception as e:
|
| 77 |
-
logger.error(f"
|
| 78 |
self.sf = None
|
| 79 |
|
| 80 |
# Create directories
|
|
@@ -178,10 +192,22 @@ class AttendanceSystem:
|
|
| 178 |
image_array = np.array(image)
|
| 179 |
|
| 180 |
try:
|
|
|
|
| 181 |
face_analysis = DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True, detector_backend='opencv')
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
embedding = DeepFace.represent(img_path=image_array, model_name='Facenet')[0]['embedding']
|
| 184 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
name = name.strip().title()
|
| 186 |
if name in self.known_face_names:
|
| 187 |
return f"β {name} is already registered!", self.get_registered_workers_info()
|
|
@@ -215,6 +241,7 @@ class AttendanceSystem:
|
|
| 215 |
logger.warning("Image URL not set due to upload failure")
|
| 216 |
except Exception as e:
|
| 217 |
logger.error(f"Error saving to Salesforce: {e}")
|
|
|
|
| 218 |
|
| 219 |
self.save_data()
|
| 220 |
|
|
@@ -227,14 +254,28 @@ class AttendanceSystem:
|
|
| 227 |
except Exception as e:
|
| 228 |
return f"β Error during registration: {str(e)}", self.get_registered_workers_info()
|
| 229 |
|
| 230 |
-
def register_worker_auto(self, face_image: np.ndarray) -> Tuple[Optional[str], Optional[str]]:
|
| 231 |
"""Automatic worker registration for unrecognized faces"""
|
| 232 |
try:
|
| 233 |
-
|
| 234 |
-
|
|
|
|
|
|
|
| 235 |
|
| 236 |
embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
|
| 237 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
face_pil = Image.fromarray(cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB))
|
| 239 |
caption = self.get_image_caption(face_pil)
|
| 240 |
|
|
@@ -326,6 +367,10 @@ class AttendanceSystem:
|
|
| 326 |
face_area = face_obj['facial_area']
|
| 327 |
x, y, w, h = face_area['x'], face_area['y'], face_area['w'], face_area['h']
|
| 328 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
face_image = frame[y:y+h, x:x+w]
|
| 330 |
|
| 331 |
try:
|
|
@@ -353,13 +398,14 @@ class AttendanceSystem:
|
|
| 353 |
self.last_recognition_time[worker_id] = current_time
|
| 354 |
else:
|
| 355 |
if face_image.size > 0:
|
| 356 |
-
new_id, new_name = self.register_worker_auto(face_image)
|
| 357 |
if new_id:
|
| 358 |
worker_id = new_id
|
| 359 |
worker_name = new_name
|
| 360 |
color = (255, 165, 0)
|
| 361 |
logger.info(f"New worker registered: {new_name} ({new_id})")
|
| 362 |
-
self.mark_attendance(worker_id, worker_name)
|
|
|
|
| 363 |
|
| 364 |
cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
|
| 365 |
cv2.rectangle(frame, (x, y+h - 35), (x+w, y+h), color, cv2.FILLED)
|
|
@@ -481,7 +527,7 @@ class AttendanceSystem:
|
|
| 481 |
|
| 482 |
def get_registered_workers_info(self) -> str:
|
| 483 |
if not self.sf:
|
| 484 |
-
return "β Salesforce connection not established."
|
| 485 |
|
| 486 |
try:
|
| 487 |
workers = self.sf.query_all("SELECT Name, Worker_ID__c, Image_Caption__c, Image_URL__c FROM Worker__c")['records']
|
|
@@ -509,7 +555,7 @@ class AttendanceSystem:
|
|
| 509 |
|
| 510 |
def get_today_attendance(self) -> str:
|
| 511 |
if not self.sf:
|
| 512 |
-
return "β Salesforce connection not established."
|
| 513 |
|
| 514 |
today = date.today().isoformat()
|
| 515 |
try:
|
|
@@ -553,7 +599,7 @@ class AttendanceSystem:
|
|
| 553 |
return "Invalid date format. Please use YYYY-MM-DD."
|
| 554 |
|
| 555 |
if not self.sf:
|
| 556 |
-
return "β Salesforce connection not established."
|
| 557 |
|
| 558 |
try:
|
| 559 |
records = self.sf.query_all(
|
|
@@ -659,7 +705,7 @@ def create_interface():
|
|
| 659 |
## π **Key Features:**
|
| 660 |
- **π₯ Live Camera Recognition** - Real-time face detection from camera/CCTV
|
| 661 |
- **πΉ Video File Processing** - Process pre-recorded videos for attendance
|
| 662 |
-
- **π€ Automatic Worker Registration** - Auto-register unknown faces with unique IDs
|
| 663 |
- **π€ Manual Registration** - Register workers manually with photos and AI-generated captions
|
| 664 |
- **π
24-Hour Attendance Rule** - One attendance mark per worker per day
|
| 665 |
- **π Advanced Analytics** - Detailed reports and data export
|
|
|
|
| 46 |
@retry(stop_max_attempt_number=3, wait_fixed=2000)
|
| 47 |
def connect_to_salesforce():
|
| 48 |
try:
|
| 49 |
+
if SF_CREDENTIALS["instance"]:
|
| 50 |
+
sf = Salesforce(
|
| 51 |
+
username=SF_CREDENTIALS["username"],
|
| 52 |
+
password=SF_CREDENTIALS["password"],
|
| 53 |
+
security_token=SF_CREDENTIALS["security_token"],
|
| 54 |
+
instance=SF_CREDENTIALS["instance"]
|
| 55 |
+
)
|
| 56 |
+
else:
|
| 57 |
+
sf = Salesforce(
|
| 58 |
+
username=SF_CREDENTIALS["username"],
|
| 59 |
+
password=SF_CREDENTIALS["password"],
|
| 60 |
+
security_token=SF_CREDENTIALS["security_token"],
|
| 61 |
+
domain=SF_CREDENTIALS["domain"]
|
| 62 |
+
)
|
| 63 |
+
logger.info("Connected to Salesforce successfully")
|
| 64 |
sf.describe()
|
| 65 |
return sf
|
| 66 |
except Exception as e:
|
| 67 |
+
logger.error(f"Salesforce connection failed: {e}. Check SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN, and SF_DOMAIN in .env file.")
|
| 68 |
raise
|
| 69 |
|
| 70 |
class AttendanceSystem:
|
|
|
|
| 82 |
self.recognition_cooldown = 5 # seconds
|
| 83 |
self.video_file_path = None
|
| 84 |
self.video_processing = False
|
| 85 |
+
self.min_face_size = (100, 100) # Minimum face size for registration/recognition
|
| 86 |
|
| 87 |
# Initialize Salesforce
|
| 88 |
try:
|
| 89 |
self.sf = connect_to_salesforce()
|
| 90 |
except Exception as e:
|
| 91 |
+
logger.error(f"Failed to initialize Salesforce connection: {e}")
|
| 92 |
self.sf = None
|
| 93 |
|
| 94 |
# Create directories
|
|
|
|
| 192 |
image_array = np.array(image)
|
| 193 |
|
| 194 |
try:
|
| 195 |
+
# Verify face and size
|
| 196 |
face_analysis = DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True, detector_backend='opencv')
|
| 197 |
+
faces = DeepFace.extract_faces(img_path=image_array, target_size=(160, 160), enforce_detection=False, detector_backend='opencv')
|
| 198 |
+
if not faces or faces[0]['facial_area']['w'] < self.min_face_size[0] or faces[0]['facial_area']['h'] < self.min_face_size[1]:
|
| 199 |
+
return "β Face too small or not clear enough (must be at least 100x100 pixels)!", self.get_registered_workers_info()
|
| 200 |
|
| 201 |
embedding = DeepFace.represent(img_path=image_array, model_name='Facenet')[0]['embedding']
|
| 202 |
|
| 203 |
+
# Check for duplicate face
|
| 204 |
+
if len(self.known_face_embeddings) > 0:
|
| 205 |
+
distances = [np.linalg.norm(np.array(embedding) - np.array(known_embedding))
|
| 206 |
+
for known_embedding in self.known_face_embeddings]
|
| 207 |
+
if min(distances) < 10:
|
| 208 |
+
best_match_index = distances.index(min(distances))
|
| 209 |
+
return f"β Face already registered as {self.known_face_names[best_match_index]} (ID: {self.known_face_ids[best_match_index]})!", self.get_registered_workers_info()
|
| 210 |
+
|
| 211 |
name = name.strip().title()
|
| 212 |
if name in self.known_face_names:
|
| 213 |
return f"β {name} is already registered!", self.get_registered_workers_info()
|
|
|
|
| 241 |
logger.warning("Image URL not set due to upload failure")
|
| 242 |
except Exception as e:
|
| 243 |
logger.error(f"Error saving to Salesforce: {e}")
|
| 244 |
+
return f"β Error saving to Salesforce: {e}. Check connection and permissions.", self.get_registered_workers_info()
|
| 245 |
|
| 246 |
self.save_data()
|
| 247 |
|
|
|
|
| 254 |
except Exception as e:
|
| 255 |
return f"β Error during registration: {str(e)}", self.get_registered_workers_info()
|
| 256 |
|
| 257 |
+
def register_worker_auto(self, face_image: np.ndarray, face_area: dict) -> Tuple[Optional[str], Optional[str]]:
|
| 258 |
"""Automatic worker registration for unrecognized faces"""
|
| 259 |
try:
|
| 260 |
+
# Verify face size
|
| 261 |
+
if face_area['w'] < self.min_face_size[0] or face_area['h'] < self.min_face_size[1]:
|
| 262 |
+
logger.info("Face too small for registration (must be at least 100x100 pixels)")
|
| 263 |
+
return None, None
|
| 264 |
|
| 265 |
embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
|
| 266 |
|
| 267 |
+
# Check for duplicate face
|
| 268 |
+
if len(self.known_face_embeddings) > 0:
|
| 269 |
+
distances = [np.linalg.norm(np.array(embedding) - np.array(known_embedding))
|
| 270 |
+
for known_embedding in self.known_face_embeddings]
|
| 271 |
+
if min(distances) < 10:
|
| 272 |
+
best_match_index = distances.index(min(distances))
|
| 273 |
+
logger.info(f"Face already registered as {self.known_face_names[best_match_index]} (ID: {self.known_face_ids[best_match_index]})")
|
| 274 |
+
return self.known_face_ids[best_match_index], self.known_face_names[best_match_index]
|
| 275 |
+
|
| 276 |
+
worker_id = f"W{self.next_worker_id:04d}"
|
| 277 |
+
worker_name = f"Unknown_Worker_{self.next_worker_id}"
|
| 278 |
+
|
| 279 |
face_pil = Image.fromarray(cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB))
|
| 280 |
caption = self.get_image_caption(face_pil)
|
| 281 |
|
|
|
|
| 367 |
face_area = face_obj['facial_area']
|
| 368 |
x, y, w, h = face_area['x'], face_area['y'], face_area['w'], face_area['h']
|
| 369 |
|
| 370 |
+
# Check face size
|
| 371 |
+
if w < self.min_face_size[0] or h < self.min_face_size[1]:
|
| 372 |
+
continue
|
| 373 |
+
|
| 374 |
face_image = frame[y:y+h, x:x+w]
|
| 375 |
|
| 376 |
try:
|
|
|
|
| 398 |
self.last_recognition_time[worker_id] = current_time
|
| 399 |
else:
|
| 400 |
if face_image.size > 0:
|
| 401 |
+
new_id, new_name = self.register_worker_auto(face_image, face_area)
|
| 402 |
if new_id:
|
| 403 |
worker_id = new_id
|
| 404 |
worker_name = new_name
|
| 405 |
color = (255, 165, 0)
|
| 406 |
logger.info(f"New worker registered: {new_name} ({new_id})")
|
| 407 |
+
if self.mark_attendance(worker_id, worker_name):
|
| 408 |
+
logger.info(f"Attendance marked for new worker {worker_name} ({worker_id})")
|
| 409 |
|
| 410 |
cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
|
| 411 |
cv2.rectangle(frame, (x, y+h - 35), (x+w, y+h), color, cv2.FILLED)
|
|
|
|
| 527 |
|
| 528 |
def get_registered_workers_info(self) -> str:
|
| 529 |
if not self.sf:
|
| 530 |
+
return "β Salesforce connection not established. Check credentials in .env file and ensure user has access to Worker__c and ContentVersion objects."
|
| 531 |
|
| 532 |
try:
|
| 533 |
workers = self.sf.query_all("SELECT Name, Worker_ID__c, Image_Caption__c, Image_URL__c FROM Worker__c")['records']
|
|
|
|
| 555 |
|
| 556 |
def get_today_attendance(self) -> str:
|
| 557 |
if not self.sf:
|
| 558 |
+
return "β Salesforce connection not established. Check credentials in .env file and ensure user has access to Attendance__c object."
|
| 559 |
|
| 560 |
today = date.today().isoformat()
|
| 561 |
try:
|
|
|
|
| 599 |
return "Invalid date format. Please use YYYY-MM-DD."
|
| 600 |
|
| 601 |
if not self.sf:
|
| 602 |
+
return "β Salesforce connection not established. Check credentials in .env file and ensure user has access to Attendance__c object."
|
| 603 |
|
| 604 |
try:
|
| 605 |
records = self.sf.query_all(
|
|
|
|
| 705 |
## π **Key Features:**
|
| 706 |
- **π₯ Live Camera Recognition** - Real-time face detection from camera/CCTV
|
| 707 |
- **πΉ Video File Processing** - Process pre-recorded videos for attendance
|
| 708 |
+
- **π€ Automatic Worker Registration** - Auto-register unknown faces with unique IDs in Salesforce
|
| 709 |
- **π€ Manual Registration** - Register workers manually with photos and AI-generated captions
|
| 710 |
- **π
24-Hour Attendance Rule** - One attendance mark per worker per day
|
| 711 |
- **π Advanced Analytics** - Detailed reports and data export
|