PrashanthB461 commited on
Commit
e547b5a
Β·
verified Β·
1 Parent(s): 8f2e659

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -27
app.py CHANGED
@@ -138,7 +138,8 @@ class AttendanceSystem:
138
  def _load_local_worker_data(self):
139
  try:
140
  if os.path.exists("data/workers.pkl"):
141
- with open("data/workers.pkl", "rb") as f: data = pickle.load(f)
 
142
  self.known_face_embeddings = data.get("embeddings", [])
143
  self.known_face_names = data.get("names", [])
144
  self.known_face_ids = data.get("ids", [])
@@ -149,8 +150,14 @@ class AttendanceSystem:
149
 
150
  def save_local_worker_data(self):
151
  try:
152
- worker_data = {"embeddings": self.known_face_embeddings, "names": self.known_face_names, "ids": self.known_face_ids, "next_id": self.next_worker_id}
153
- with open("data/workers.pkl", "wb") as f: pickle.dump(worker_data, f)
 
 
 
 
 
 
154
  except Exception as e:
155
  logger.error(f"❌ Error saving local worker data: {e}")
156
 
@@ -235,9 +242,15 @@ class AttendanceSystem:
235
  caption = self._get_image_caption(face_pil)
236
  if self.sf:
237
  try:
238
- worker_record = self.sf.Worker__c.create({'Name': name, 'Worker_ID__c': worker_id, 'Face_Embedding__c': json.dumps(embedding), 'Image_Caption__c': caption})
 
 
 
 
 
239
  image_url = self._upload_image_to_salesforce(face_pil, worker_record['id'], worker_id)
240
- if image_url: self.sf.Worker__c.update(worker_record['id'], {'Image_URL__c': image_url})
 
241
  logger.info(f"βœ… Worker {worker_id} synced to Salesforce.")
242
  except Exception as e:
243
  logger.error(f"❌ Salesforce sync error for {worker_id}: {e}")
@@ -251,9 +264,13 @@ class AttendanceSystem:
251
  for known_embedding in self.known_face_embeddings:
252
  # Use euclidean distance as primary method
253
  euclidean_dist = np.linalg.norm(embedding_array - known_embedding)
254
- # Also check cosine similarity for additional validation
255
- # If distance is low, consider it duplicate
256
- if euclidean_dist < threshold:
 
 
 
 
257
  # Consider it duplicate if distance is small OR similarity is very high
258
  if euclidean_dist < threshold or cosine_sim > 0.85:
259
  return True
@@ -377,6 +394,10 @@ class AttendanceSystem:
377
 
378
  color, worker_id, worker_name = (0, 0, 255), None, "Unknown"
379
 
 
 
 
 
380
  if self.known_face_embeddings:
381
  # Find best match using euclidean distance
382
  match_index, match_distance = self._find_best_match(embedding_array)
@@ -387,6 +408,15 @@ class AttendanceSystem:
387
  if match_index != -1 and match_distance < 10.0:
388
  worker_id = self.known_face_ids[match_index]
389
  worker_name = self.known_face_names[match_index]
 
 
 
 
 
 
 
 
 
390
  # Mark attendance after consistent detections
391
  if self.face_recognition_buffer[buffer_key]['count'] >= self.buffer_threshold:
392
  if self.mark_attendance(worker_id, worker_name):
@@ -407,13 +437,10 @@ class AttendanceSystem:
407
  if new_worker:
408
  worker_id, worker_name = new_worker[0], new_worker[1]
409
  color = (0, 255, 0) # Change to green after successful registration
410
- color = (0, 255, 0) # Change to green after successful registration
411
  if self.mark_attendance(worker_id, worker_name):
412
  self.last_recognition_time[worker_id] = time.time()
413
  else:
414
  print(" -> Auto-registration skipped (duplicate or session limit)")
415
- else:
416
- print(" -> Auto-registration skipped (duplicate or session limit)")
417
  else:
418
  print(" -> Distance too close to existing faces, skipping auto-registration.")
419
  else:
@@ -424,13 +451,10 @@ class AttendanceSystem:
424
  if new_worker:
425
  worker_id, worker_name = new_worker[0], new_worker[1]
426
  color = (0, 255, 0) # Change to green after successful registration
427
- color = (0, 255, 0) # Change to green after successful registration
428
  if self.mark_attendance(worker_id, worker_name):
429
  self.last_recognition_time[worker_id] = time.time()
430
  else:
431
  print(" -> Auto-registration failed")
432
- else:
433
- print(" -> Auto-registration failed")
434
 
435
  # Clean old buffer entries
436
  current_time = time.time()
@@ -457,9 +481,11 @@ class AttendanceSystem:
457
  return
458
  while self.is_processing.is_set():
459
  ret, frame = video_capture.read()
460
- if not ret: break
 
461
  processed_frame = self.process_frame(frame)
462
- if not self.frame_queue.full(): self.frame_queue.put(processed_frame)
 
463
  self.last_processed_frame = processed_frame # Continuously update last frame
464
  time.sleep(0.05)
465
  self.final_log = self.session_log.copy() # Save the final log
@@ -467,7 +493,8 @@ class AttendanceSystem:
467
  self.is_processing.clear()
468
 
469
  def start_processing(self, source) -> str:
470
- if self.is_processing.is_set(): return "⚠️ Processing is already active."
 
471
  # Reset states for the new session
472
  self.session_log.clear()
473
  self.last_recognition_time.clear()
@@ -495,7 +522,8 @@ class AttendanceSystem:
495
 
496
  # --- Helper & Reporting ---
497
  def _get_image_caption(self, image: Image.Image) -> str:
498
- if not HF_API_TOKEN: return "Hugging Face API token not configured."
 
499
  try:
500
  buffered = BytesIO()
501
  image.save(buffered, format="JPEG")
@@ -510,27 +538,37 @@ class AttendanceSystem:
510
  return "Caption generation failed."
511
 
512
  def _upload_image_to_salesforce(self, image: Image.Image, record_id: str, worker_id: str) -> Optional[str]:
513
- if not self.sf: return None
 
514
  try:
515
  buffered = BytesIO()
516
  image.save(buffered, format="JPEG")
517
  encoded_image = base64.b64encode(buffered.getvalue()).decode('utf-8')
518
- cv = self.sf.ContentVersion.create({'Title': f'Image_{worker_id}', 'PathOnClient': f'{worker_id}.jpg', 'VersionData': encoded_image, 'FirstPublishLocationId': record_id})
 
 
 
 
 
519
  return f"/{cv['id']}" # Relative URL
520
  except Exception as e:
521
  logger.error(f"Salesforce image upload error: {e}")
522
  return None
523
 
524
  def get_registered_workers_info(self) -> str:
525
- if not self.sf: return "❌ Salesforce not connected."
 
526
  try:
527
  records = self.sf.query_all("SELECT Name, Worker_ID__c FROM Worker__c ORDER BY Name")['records']
528
- if not records: return "No workers registered."
 
529
  return f"**πŸ‘₯ Registered Workers ({len(records)})**\n" + "\n".join([f"- **{w['Name']}** (ID: {w['Worker_ID__c']})" for w in records])
530
- except Exception as e: return f"Error: {e}"
 
531
 
532
  # --- GRADIO UI ---
533
  attendance_system = AttendanceSystem()
 
534
  def create_interface():
535
  with gr.Blocks(theme=gr.themes.Soft(), title="Attendance System") as demo:
536
  gr.Markdown("# 🎯 Advanced Face Recognition Attendance System")
@@ -571,11 +609,15 @@ def create_interface():
571
  refresh_workers_btn = gr.Button("πŸ”„ Refresh List")
572
 
573
  # --- Event Handlers ---
574
- def on_tab_select(evt: gr.SelectData): return evt.index
 
 
575
  video_tabs.select(fn=on_tab_select, inputs=None, outputs=[selected_tab_index])
 
576
  def start_wrapper(tab_index, cam_src, vid_path):
577
  source = cam_src if tab_index == 0 else vid_path
578
  return "Please provide an input source." if source is None else attendance_system.start_processing(source)
 
579
  start_btn.click(fn=start_wrapper, inputs=[selected_tab_index, camera_source, video_file], outputs=[status_box])
580
  stop_btn.click(fn=attendance_system.stop_processing, inputs=None, outputs=[status_box])
581
  register_btn.click(fn=attendance_system.register_worker_manual, inputs=[register_image, register_name], outputs=[register_output, registered_workers_info])
@@ -585,15 +627,18 @@ def create_interface():
585
  while True:
586
  if attendance_system.error_message:
587
  yield None, attendance_system.error_message
588
- time.sleep(2); attendance_system.error_message = None
 
589
  continue
590
  if attendance_system.is_processing.is_set():
591
  frame, log_md = None, "\n".join(reversed(attendance_system.session_log)) or "Processing..."
592
  try:
593
  if not attendance_system.frame_queue.empty():
594
  frame = attendance_system.frame_queue.get_nowait()
595
- if frame is not None: frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
596
- except queue.Empty: pass
 
 
597
  yield frame, log_md
598
  else:
599
  if attendance_system.last_processed_frame is not None:
 
138
  def _load_local_worker_data(self):
139
  try:
140
  if os.path.exists("data/workers.pkl"):
141
+ with open("data/workers.pkl", "rb") as f:
142
+ data = pickle.load(f)
143
  self.known_face_embeddings = data.get("embeddings", [])
144
  self.known_face_names = data.get("names", [])
145
  self.known_face_ids = data.get("ids", [])
 
150
 
151
  def save_local_worker_data(self):
152
  try:
153
+ worker_data = {
154
+ "embeddings": self.known_face_embeddings,
155
+ "names": self.known_face_names,
156
+ "ids": self.known_face_ids,
157
+ "next_id": self.next_worker_id
158
+ }
159
+ with open("data/workers.pkl", "wb") as f:
160
+ pickle.dump(worker_data, f)
161
  except Exception as e:
162
  logger.error(f"❌ Error saving local worker data: {e}")
163
 
 
242
  caption = self._get_image_caption(face_pil)
243
  if self.sf:
244
  try:
245
+ worker_record = self.sf.Worker__c.create({
246
+ 'Name': name,
247
+ 'Worker_ID__c': worker_id,
248
+ 'Face_Embedding__c': json.dumps(embedding),
249
+ 'Image_Caption__c': caption
250
+ })
251
  image_url = self._upload_image_to_salesforce(face_pil, worker_record['id'], worker_id)
252
+ if image_url:
253
+ self.sf.Worker__c.update(worker_record['id'], {'Image_URL__c': image_url})
254
  logger.info(f"βœ… Worker {worker_id} synced to Salesforce.")
255
  except Exception as e:
256
  logger.error(f"❌ Salesforce sync error for {worker_id}: {e}")
 
264
  for known_embedding in self.known_face_embeddings:
265
  # Use euclidean distance as primary method
266
  euclidean_dist = np.linalg.norm(embedding_array - known_embedding)
267
+
268
+ # Calculate cosine similarity for additional validation
269
+ dot_product = np.dot(embedding_array, known_embedding)
270
+ norm_a = np.linalg.norm(embedding_array)
271
+ norm_b = np.linalg.norm(known_embedding)
272
+ cosine_sim = dot_product / (norm_a * norm_b) if (norm_a * norm_b) != 0 else 0
273
+
274
  # Consider it duplicate if distance is small OR similarity is very high
275
  if euclidean_dist < threshold or cosine_sim > 0.85:
276
  return True
 
394
 
395
  color, worker_id, worker_name = (0, 0, 255), None, "Unknown"
396
 
397
+ # Create a buffer key for this face location
398
+ buffer_key = f"{x}_{y}_{w}_{h}"
399
+ current_time = time.time()
400
+
401
  if self.known_face_embeddings:
402
  # Find best match using euclidean distance
403
  match_index, match_distance = self._find_best_match(embedding_array)
 
408
  if match_index != -1 and match_distance < 10.0:
409
  worker_id = self.known_face_ids[match_index]
410
  worker_name = self.known_face_names[match_index]
411
+ color = (0, 255, 0) # Green for recognized
412
+
413
+ # Use buffer for consistent detections
414
+ if buffer_key not in self.face_recognition_buffer:
415
+ self.face_recognition_buffer[buffer_key] = {'count': 1, 'last_time': current_time, 'worker_id': worker_id}
416
+ else:
417
+ self.face_recognition_buffer[buffer_key]['count'] += 1
418
+ self.face_recognition_buffer[buffer_key]['last_time'] = current_time
419
+
420
  # Mark attendance after consistent detections
421
  if self.face_recognition_buffer[buffer_key]['count'] >= self.buffer_threshold:
422
  if self.mark_attendance(worker_id, worker_name):
 
437
  if new_worker:
438
  worker_id, worker_name = new_worker[0], new_worker[1]
439
  color = (0, 255, 0) # Change to green after successful registration
 
440
  if self.mark_attendance(worker_id, worker_name):
441
  self.last_recognition_time[worker_id] = time.time()
442
  else:
443
  print(" -> Auto-registration skipped (duplicate or session limit)")
 
 
444
  else:
445
  print(" -> Distance too close to existing faces, skipping auto-registration.")
446
  else:
 
451
  if new_worker:
452
  worker_id, worker_name = new_worker[0], new_worker[1]
453
  color = (0, 255, 0) # Change to green after successful registration
 
454
  if self.mark_attendance(worker_id, worker_name):
455
  self.last_recognition_time[worker_id] = time.time()
456
  else:
457
  print(" -> Auto-registration failed")
 
 
458
 
459
  # Clean old buffer entries
460
  current_time = time.time()
 
481
  return
482
  while self.is_processing.is_set():
483
  ret, frame = video_capture.read()
484
+ if not ret:
485
+ break
486
  processed_frame = self.process_frame(frame)
487
+ if not self.frame_queue.full():
488
+ self.frame_queue.put(processed_frame)
489
  self.last_processed_frame = processed_frame # Continuously update last frame
490
  time.sleep(0.05)
491
  self.final_log = self.session_log.copy() # Save the final log
 
493
  self.is_processing.clear()
494
 
495
  def start_processing(self, source) -> str:
496
+ if self.is_processing.is_set():
497
+ return "⚠️ Processing is already active."
498
  # Reset states for the new session
499
  self.session_log.clear()
500
  self.last_recognition_time.clear()
 
522
 
523
  # --- Helper & Reporting ---
524
  def _get_image_caption(self, image: Image.Image) -> str:
525
+ if not HF_API_TOKEN:
526
+ return "Hugging Face API token not configured."
527
  try:
528
  buffered = BytesIO()
529
  image.save(buffered, format="JPEG")
 
538
  return "Caption generation failed."
539
 
540
  def _upload_image_to_salesforce(self, image: Image.Image, record_id: str, worker_id: str) -> Optional[str]:
541
+ if not self.sf:
542
+ return None
543
  try:
544
  buffered = BytesIO()
545
  image.save(buffered, format="JPEG")
546
  encoded_image = base64.b64encode(buffered.getvalue()).decode('utf-8')
547
+ cv = self.sf.ContentVersion.create({
548
+ 'Title': f'Image_{worker_id}',
549
+ 'PathOnClient': f'{worker_id}.jpg',
550
+ 'VersionData': encoded_image,
551
+ 'FirstPublishLocationId': record_id
552
+ })
553
  return f"/{cv['id']}" # Relative URL
554
  except Exception as e:
555
  logger.error(f"Salesforce image upload error: {e}")
556
  return None
557
 
558
  def get_registered_workers_info(self) -> str:
559
+ if not self.sf:
560
+ return "❌ Salesforce not connected."
561
  try:
562
  records = self.sf.query_all("SELECT Name, Worker_ID__c FROM Worker__c ORDER BY Name")['records']
563
+ if not records:
564
+ return "No workers registered."
565
  return f"**πŸ‘₯ Registered Workers ({len(records)})**\n" + "\n".join([f"- **{w['Name']}** (ID: {w['Worker_ID__c']})" for w in records])
566
+ except Exception as e:
567
+ return f"Error: {e}"
568
 
569
  # --- GRADIO UI ---
570
  attendance_system = AttendanceSystem()
571
+
572
  def create_interface():
573
  with gr.Blocks(theme=gr.themes.Soft(), title="Attendance System") as demo:
574
  gr.Markdown("# 🎯 Advanced Face Recognition Attendance System")
 
609
  refresh_workers_btn = gr.Button("πŸ”„ Refresh List")
610
 
611
  # --- Event Handlers ---
612
+ def on_tab_select(evt: gr.SelectData):
613
+ return evt.index
614
+
615
  video_tabs.select(fn=on_tab_select, inputs=None, outputs=[selected_tab_index])
616
+
617
  def start_wrapper(tab_index, cam_src, vid_path):
618
  source = cam_src if tab_index == 0 else vid_path
619
  return "Please provide an input source." if source is None else attendance_system.start_processing(source)
620
+
621
  start_btn.click(fn=start_wrapper, inputs=[selected_tab_index, camera_source, video_file], outputs=[status_box])
622
  stop_btn.click(fn=attendance_system.stop_processing, inputs=None, outputs=[status_box])
623
  register_btn.click(fn=attendance_system.register_worker_manual, inputs=[register_image, register_name], outputs=[register_output, registered_workers_info])
 
627
  while True:
628
  if attendance_system.error_message:
629
  yield None, attendance_system.error_message
630
+ time.sleep(2)
631
+ attendance_system.error_message = None
632
  continue
633
  if attendance_system.is_processing.is_set():
634
  frame, log_md = None, "\n".join(reversed(attendance_system.session_log)) or "Processing..."
635
  try:
636
  if not attendance_system.frame_queue.empty():
637
  frame = attendance_system.frame_queue.get_nowait()
638
+ if frame is not None:
639
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
640
+ except queue.Empty:
641
+ pass
642
  yield frame, log_md
643
  else:
644
  if attendance_system.last_processed_frame is not None: