PrashanthB461 commited on
Commit
3506a7b
Β·
verified Β·
1 Parent(s): ba21352

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -133
app.py CHANGED
@@ -87,7 +87,7 @@ class AttendanceSystem:
87
  self.recognition_cooldown = 10 # Cooldown to prevent duplicates
88
  self.session_log: List[str] = []
89
  self.session_marked_present = set() # Track who's already marked present in this session
90
- self.session_auto_registered = set() # Track embeddings that were auto-registered in this session
91
  self.face_recognition_buffer = {} # Buffer for multiple detections before confirming
92
  self.buffer_threshold = 2 # Reduced threshold for faster recognition
93
  self.frame_skip_counter = 0 # Skip frames for better performance
@@ -138,8 +138,7 @@ 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:
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,14 +149,8 @@ class AttendanceSystem:
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
 
@@ -167,51 +160,40 @@ class AttendanceSystem:
167
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
168
  try:
169
  image_array = np.array(image)
170
- # Convert RGB to BGR for DeepFace
171
- if len(image_array.shape) == 3 and image_array.shape[2] == 3:
172
- image_array = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
173
-
174
- # Verify face detection first
175
  DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True)
176
- embedding = DeepFace.represent(img_path=image_array, model_name='Facenet', enforce_detection=True)[0]['embedding']
177
-
178
- # Check for duplicates with reasonable threshold for manual registration
179
- if self._is_duplicate_face(embedding, threshold=8.0):
180
  return f"❌ Face matches an existing worker!", self.get_registered_workers_info()
181
 
182
  worker_id = f"W{self.next_worker_id:04d}"
183
  name = name.strip().title()
184
  self._add_worker_to_system(worker_id, name, embedding, image_array)
185
  self.save_local_worker_data()
186
-
187
  return f"βœ… {name} registered with ID: {worker_id}!", self.get_registered_workers_info()
188
- except ValueError as e:
189
- return f"❌ No face detected in the image! Error: {str(e)}", self.get_registered_workers_info()
190
  except Exception as e:
191
- logger.error(f"Registration error: {e}")
192
  return f"❌ Registration error: {e}", self.get_registered_workers_info()
193
 
194
  def _register_worker_auto(self, face_image: np.ndarray, face_embedding: List[float]) -> Optional[Tuple[str, str]]:
195
  try:
196
- # Create a unique identifier for this embedding to track session registrations
197
- embedding_hash = hash(tuple(np.round(face_embedding, 3)))
198
-
199
- # Check if this specific face was already auto-registered in this session
200
- if embedding_hash in self.session_auto_registered:
201
- return None
202
-
203
  # Check for duplicates with more lenient threshold for auto-registration
204
- if self._is_duplicate_face(face_embedding, threshold=10.0):
205
  return None
206
 
207
  worker_id = f"W{self.next_worker_id:04d}"
208
- worker_name = f"Unknown Worker {self.next_worker_id}"
209
 
 
 
 
 
 
210
  self._add_worker_to_system(worker_id, worker_name, face_embedding, face_image)
211
  self.save_local_worker_data()
212
 
213
- # Mark this embedding as registered in this session
214
- self.session_auto_registered.add(embedding_hash)
215
 
216
  log_msg = f"πŸ†• [{datetime.now().strftime('%H:%M:%S')}] Auto-registered: {worker_name} ({worker_id})"
217
  self.session_log.append(log_msg)
@@ -226,31 +208,14 @@ class AttendanceSystem:
226
  self.known_face_names.append(name)
227
  self.known_face_ids.append(worker_id)
228
  self.next_worker_id += 1
229
-
230
- # Ensure image is in RGB format for saving
231
- if len(image_array.shape) == 3:
232
- if image_array.shape[2] == 3:
233
- # Check if BGR (OpenCV format) and convert to RGB
234
- face_pil = Image.fromarray(cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB))
235
- else:
236
- face_pil = Image.fromarray(image_array)
237
- else:
238
- face_pil = Image.fromarray(image_array)
239
-
240
  face_pil.save(f"data/faces/{worker_id}.jpg")
241
-
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}")
@@ -262,17 +227,12 @@ class AttendanceSystem:
262
 
263
  embedding_array = np.array(embedding)
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
277
 
278
  return False
@@ -325,17 +285,25 @@ class AttendanceSystem:
325
  return -1, float('inf')
326
 
327
  best_match_idx = -1
328
- best_distance = float('inf')
329
 
330
  for i, known_embedding in enumerate(self.known_face_embeddings):
331
- # Primary: Euclidean distance
332
  euclidean_dist = np.linalg.norm(target_embedding - known_embedding)
333
 
334
- if euclidean_dist < best_distance:
335
- best_distance = euclidean_dist
 
 
 
 
 
 
 
 
336
  best_match_idx = i
337
 
338
- return best_match_idx, best_distance
339
 
340
  # --- Video Processing ---
341
  def process_frame(self, frame: np.ndarray) -> np.ndarray:
@@ -366,7 +334,7 @@ class AttendanceSystem:
366
  print(f" Face #{i+1}: Confidence Score = {confidence:.2f}")
367
 
368
  # More lenient confidence threshold
369
- if confidence < 0.80:
370
  print(" -> Confidence too low, skipping.")
371
  continue
372
 
@@ -380,7 +348,7 @@ class AttendanceSystem:
380
  continue
381
 
382
  # More lenient minimum face size check
383
- if w < 40 or h < 40:
384
  print(" -> Face too small, skipping.")
385
  continue
386
 
@@ -394,28 +362,37 @@ class AttendanceSystem:
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)
404
 
405
- print(f" -> Comparing to DB... Best Match Distance: {match_distance:.4f}")
406
-
407
- # Use consistent threshold for recognition (10.0 works well)
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:
@@ -425,30 +402,24 @@ class AttendanceSystem:
425
  del self.face_recognition_buffer[buffer_key]
426
 
427
  else:
428
- # No match found - check if this should be auto-registered
429
- print(f" βœ— NO MATCH (Distance: {match_distance:.4f})")
430
-
431
- # Only auto-register if distance is significantly different (> 15.0)
432
- # and not a duplicate according to our duplicate detection
433
- if match_distance > 15.0 and not self._is_duplicate_face(embedding, threshold=12.0):
434
  color = (0, 165, 255) # Orange for potential new worker
435
- print(f" -> Attempting auto-registration...")
436
  new_worker = self._register_worker_auto(face_image, embedding)
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(" -> Distance too close to existing faces, skipping auto-registration.")
444
  else:
445
- # No known faces in database, auto-register the first face
446
  color = (0, 165, 255) # Orange for new worker
447
  print(" -> No known faces in database. Auto-registering...")
448
  new_worker = self._register_worker_auto(face_image, embedding)
449
  if new_worker:
450
  worker_id, worker_name = new_worker[0], new_worker[1]
451
- color = (0, 255, 0) # Change to green after successful registration
452
  if self.mark_attendance(worker_id, worker_name):
453
  self.last_recognition_time[worker_id] = time.time()
454
 
@@ -477,11 +448,9 @@ class AttendanceSystem:
477
  return
478
  while self.is_processing.is_set():
479
  ret, frame = video_capture.read()
480
- if not ret:
481
- break
482
  processed_frame = self.process_frame(frame)
483
- if not self.frame_queue.full():
484
- self.frame_queue.put(processed_frame)
485
  self.last_processed_frame = processed_frame # Continuously update last frame
486
  time.sleep(0.05)
487
  self.final_log = self.session_log.copy() # Save the final log
@@ -489,13 +458,12 @@ class AttendanceSystem:
489
  self.is_processing.clear()
490
 
491
  def start_processing(self, source) -> str:
492
- if self.is_processing.is_set():
493
- return "⚠️ Processing is already active."
494
  # Reset states for the new session
495
  self.session_log.clear()
496
  self.last_recognition_time.clear()
497
  self.session_marked_present.clear() # Reset session attendance tracking
498
- self.session_auto_registered.clear() # Reset session registration tracking
499
  self.face_recognition_buffer.clear() # Reset recognition buffer
500
  self.error_message = None
501
  self.last_processed_frame = None
@@ -518,8 +486,7 @@ class AttendanceSystem:
518
 
519
  # --- Helper & Reporting ---
520
  def _get_image_caption(self, image: Image.Image) -> str:
521
- if not HF_API_TOKEN:
522
- return "Hugging Face API token not configured."
523
  try:
524
  buffered = BytesIO()
525
  image.save(buffered, format="JPEG")
@@ -534,37 +501,27 @@ class AttendanceSystem:
534
  return "Caption generation failed."
535
 
536
  def _upload_image_to_salesforce(self, image: Image.Image, record_id: str, worker_id: str) -> Optional[str]:
537
- if not self.sf:
538
- return None
539
  try:
540
  buffered = BytesIO()
541
  image.save(buffered, format="JPEG")
542
  encoded_image = base64.b64encode(buffered.getvalue()).decode('utf-8')
543
- cv = self.sf.ContentVersion.create({
544
- 'Title': f'Image_{worker_id}',
545
- 'PathOnClient': f'{worker_id}.jpg',
546
- 'VersionData': encoded_image,
547
- 'FirstPublishLocationId': record_id
548
- })
549
  return f"/{cv['id']}" # Relative URL
550
  except Exception as e:
551
  logger.error(f"Salesforce image upload error: {e}")
552
  return None
553
 
554
  def get_registered_workers_info(self) -> str:
555
- if not self.sf:
556
- return "❌ Salesforce not connected."
557
  try:
558
  records = self.sf.query_all("SELECT Name, Worker_ID__c FROM Worker__c ORDER BY Name")['records']
559
- if not records:
560
- return "No workers registered."
561
  return f"**πŸ‘₯ Registered Workers ({len(records)})**\n" + "\n".join([f"- **{w['Name']}** (ID: {w['Worker_ID__c']})" for w in records])
562
- except Exception as e:
563
- return f"Error: {e}"
564
 
565
  # --- GRADIO UI ---
566
  attendance_system = AttendanceSystem()
567
-
568
  def create_interface():
569
  with gr.Blocks(theme=gr.themes.Soft(), title="Attendance System") as demo:
570
  gr.Markdown("# 🎯 Advanced Face Recognition Attendance System")
@@ -605,15 +562,11 @@ def create_interface():
605
  refresh_workers_btn = gr.Button("πŸ”„ Refresh List")
606
 
607
  # --- Event Handlers ---
608
- def on_tab_select(evt: gr.SelectData):
609
- return evt.index
610
-
611
  video_tabs.select(fn=on_tab_select, inputs=None, outputs=[selected_tab_index])
612
-
613
  def start_wrapper(tab_index, cam_src, vid_path):
614
  source = cam_src if tab_index == 0 else vid_path
615
  return "Please provide an input source." if source is None else attendance_system.start_processing(source)
616
-
617
  start_btn.click(fn=start_wrapper, inputs=[selected_tab_index, camera_source, video_file], outputs=[status_box])
618
  stop_btn.click(fn=attendance_system.stop_processing, inputs=None, outputs=[status_box])
619
  register_btn.click(fn=attendance_system.register_worker_manual, inputs=[register_image, register_name], outputs=[register_output, registered_workers_info])
@@ -623,18 +576,15 @@ def create_interface():
623
  while True:
624
  if attendance_system.error_message:
625
  yield None, attendance_system.error_message
626
- time.sleep(2)
627
- attendance_system.error_message = None
628
  continue
629
  if attendance_system.is_processing.is_set():
630
  frame, log_md = None, "\n".join(reversed(attendance_system.session_log)) or "Processing..."
631
  try:
632
  if not attendance_system.frame_queue.empty():
633
  frame = attendance_system.frame_queue.get_nowait()
634
- if frame is not None:
635
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
636
- except queue.Empty:
637
- pass
638
  yield frame, log_md
639
  else:
640
  if attendance_system.last_processed_frame is not None:
 
87
  self.recognition_cooldown = 10 # Cooldown to prevent duplicates
88
  self.session_log: List[str] = []
89
  self.session_marked_present = set() # Track who's already marked present in this session
90
+ self.session_registered = set() # Track who's already auto-registered in this session
91
  self.face_recognition_buffer = {} # Buffer for multiple detections before confirming
92
  self.buffer_threshold = 2 # Reduced threshold for faster recognition
93
  self.frame_skip_counter = 0 # Skip frames for better performance
 
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
 
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
 
 
160
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
161
  try:
162
  image_array = np.array(image)
 
 
 
 
 
163
  DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True)
164
+ embedding = DeepFace.represent(img_path=image_array, model_name='Facenet')[0]['embedding']
165
+ if self._is_duplicate_face(embedding):
 
 
166
  return f"❌ Face matches an existing worker!", self.get_registered_workers_info()
167
 
168
  worker_id = f"W{self.next_worker_id:04d}"
169
  name = name.strip().title()
170
  self._add_worker_to_system(worker_id, name, embedding, image_array)
171
  self.save_local_worker_data()
172
+ self.load_worker_data()
173
  return f"βœ… {name} registered with ID: {worker_id}!", self.get_registered_workers_info()
174
+ except ValueError:
175
+ return "❌ No face detected in the image!", self.get_registered_workers_info()
176
  except Exception as e:
 
177
  return f"❌ Registration error: {e}", self.get_registered_workers_info()
178
 
179
  def _register_worker_auto(self, face_image: np.ndarray, face_embedding: List[float]) -> Optional[Tuple[str, str]]:
180
  try:
 
 
 
 
 
 
 
181
  # Check for duplicates with more lenient threshold for auto-registration
182
+ if self._is_duplicate_face(face_embedding, threshold=12.0):
183
  return None
184
 
185
  worker_id = f"W{self.next_worker_id:04d}"
 
186
 
187
+ # Check if already auto-registered in this session
188
+ if worker_id in self.session_registered:
189
+ return None
190
+
191
+ worker_name = f"Unknown Worker {self.next_worker_id}"
192
  self._add_worker_to_system(worker_id, worker_name, face_embedding, face_image)
193
  self.save_local_worker_data()
194
 
195
+ # Mark as registered in this session
196
+ self.session_registered.add(worker_id)
197
 
198
  log_msg = f"πŸ†• [{datetime.now().strftime('%H:%M:%S')}] Auto-registered: {worker_name} ({worker_id})"
199
  self.session_log.append(log_msg)
 
208
  self.known_face_names.append(name)
209
  self.known_face_ids.append(worker_id)
210
  self.next_worker_id += 1
211
+ face_pil = Image.fromarray(cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB))
 
 
 
 
 
 
 
 
 
 
212
  face_pil.save(f"data/faces/{worker_id}.jpg")
 
213
  caption = self._get_image_caption(face_pil)
214
  if self.sf:
215
  try:
216
+ worker_record = self.sf.Worker__c.create({'Name': name, 'Worker_ID__c': worker_id, 'Face_Embedding__c': json.dumps(embedding), 'Image_Caption__c': caption})
 
 
 
 
 
217
  image_url = self._upload_image_to_salesforce(face_pil, worker_record['id'], worker_id)
218
+ if image_url: self.sf.Worker__c.update(worker_record['id'], {'Image_URL__c': image_url})
 
219
  logger.info(f"βœ… Worker {worker_id} synced to Salesforce.")
220
  except Exception as e:
221
  logger.error(f"❌ Salesforce sync error for {worker_id}: {e}")
 
227
 
228
  embedding_array = np.array(embedding)
229
  for known_embedding in self.known_face_embeddings:
230
+ # Use both euclidean distance and cosine similarity
231
  euclidean_dist = np.linalg.norm(embedding_array - known_embedding)
232
+ cosine_sim = np.dot(embedding_array, known_embedding) / (np.linalg.norm(embedding_array) * np.linalg.norm(known_embedding))
233
 
234
+ # If either similarity is high or distance is low, consider it duplicate
235
+ if cosine_sim > 0.80 or euclidean_dist < threshold:
 
 
 
 
 
 
236
  return True
237
 
238
  return False
 
285
  return -1, float('inf')
286
 
287
  best_match_idx = -1
288
+ best_score = float('inf')
289
 
290
  for i, known_embedding in enumerate(self.known_face_embeddings):
291
+ # Euclidean distance
292
  euclidean_dist = np.linalg.norm(target_embedding - known_embedding)
293
 
294
+ # Cosine similarity
295
+ cosine_sim = np.dot(target_embedding, known_embedding) / (
296
+ np.linalg.norm(target_embedding) * np.linalg.norm(known_embedding)
297
+ )
298
+
299
+ # Combined score (lower is better)
300
+ combined_score = euclidean_dist * (1 - cosine_sim)
301
+
302
+ if combined_score < best_score:
303
+ best_score = combined_score
304
  best_match_idx = i
305
 
306
+ return best_match_idx, best_score
307
 
308
  # --- Video Processing ---
309
  def process_frame(self, frame: np.ndarray) -> np.ndarray:
 
334
  print(f" Face #{i+1}: Confidence Score = {confidence:.2f}")
335
 
336
  # More lenient confidence threshold
337
+ if confidence < 0.85:
338
  print(" -> Confidence too low, skipping.")
339
  continue
340
 
 
348
  continue
349
 
350
  # More lenient minimum face size check
351
+ if w < 50 or h < 50:
352
  print(" -> Face too small, skipping.")
353
  continue
354
 
 
362
 
363
  color, worker_id, worker_name = (0, 0, 255), None, "Unknown"
364
 
 
 
 
 
365
  if self.known_face_embeddings:
366
+ # Enhanced matching
367
+ match_index, match_score = self._find_best_match(embedding_array)
368
 
369
+ # Also try simple euclidean distance for backup
370
+ distances = [np.linalg.norm(embedding_array - known) for known in self.known_face_embeddings]
371
+ min_dist = min(distances) if distances else float('inf')
372
+ simple_match_index = distances.index(min_dist) if min_dist < 12.0 else -1
373
+
374
+ print(f" -> Comparing to DB... Combined Score: {match_score:.4f}, Simple Distance: {min_dist:.4f}")
375
+
376
+ # Use more lenient thresholds for recognition
377
+ if (match_index != -1 and match_score < 15.0) or (simple_match_index != -1 and min_dist < 12.0):
378
+ # Use the better match
379
+ if match_index != -1 and match_score < 15.0:
380
+ final_match_index = match_index
381
+ else:
382
+ final_match_index = simple_match_index
383
+
384
+ worker_id = self.known_face_ids[final_match_index]
385
+ worker_name = self.known_face_names[final_match_index]
386
+ color = (0, 255, 0) # Green
387
+ print(f" βœ“ MATCH! Recognized as {worker_name}")
388
 
389
+ # Use buffering for consistent recognition
390
+ buffer_key = f"{worker_id}"
391
  if buffer_key not in self.face_recognition_buffer:
392
+ self.face_recognition_buffer[buffer_key] = {'count': 1, 'last_time': time.time()}
393
  else:
394
  self.face_recognition_buffer[buffer_key]['count'] += 1
395
+ self.face_recognition_buffer[buffer_key]['last_time'] = time.time()
396
 
397
  # Mark attendance after consistent detections
398
  if self.face_recognition_buffer[buffer_key]['count'] >= self.buffer_threshold:
 
402
  del self.face_recognition_buffer[buffer_key]
403
 
404
  else:
405
+ # Check if this should be auto-registered
406
+ if min_dist > 15.0: # Only register if very different from existing faces
 
 
 
 
407
  color = (0, 165, 255) # Orange for potential new worker
408
+ print(f" βœ— NO MATCH. Attempting to register as new worker...")
409
  new_worker = self._register_worker_auto(face_image, embedding)
410
  if new_worker:
411
  worker_id, worker_name = new_worker[0], new_worker[1]
 
412
  if self.mark_attendance(worker_id, worker_name):
413
  self.last_recognition_time[worker_id] = time.time()
414
  else:
415
+ print(" βœ— Uncertain match, skipping registration.")
416
  else:
417
+ # No known faces, auto-register
418
  color = (0, 165, 255) # Orange for new worker
419
  print(" -> No known faces in database. Auto-registering...")
420
  new_worker = self._register_worker_auto(face_image, embedding)
421
  if new_worker:
422
  worker_id, worker_name = new_worker[0], new_worker[1]
 
423
  if self.mark_attendance(worker_id, worker_name):
424
  self.last_recognition_time[worker_id] = time.time()
425
 
 
448
  return
449
  while self.is_processing.is_set():
450
  ret, frame = video_capture.read()
451
+ if not ret: break
 
452
  processed_frame = self.process_frame(frame)
453
+ if not self.frame_queue.full(): self.frame_queue.put(processed_frame)
 
454
  self.last_processed_frame = processed_frame # Continuously update last frame
455
  time.sleep(0.05)
456
  self.final_log = self.session_log.copy() # Save the final log
 
458
  self.is_processing.clear()
459
 
460
  def start_processing(self, source) -> str:
461
+ if self.is_processing.is_set(): return "⚠️ Processing is already active."
 
462
  # Reset states for the new session
463
  self.session_log.clear()
464
  self.last_recognition_time.clear()
465
  self.session_marked_present.clear() # Reset session attendance tracking
466
+ self.session_registered.clear() # Reset session registration tracking
467
  self.face_recognition_buffer.clear() # Reset recognition buffer
468
  self.error_message = None
469
  self.last_processed_frame = None
 
486
 
487
  # --- Helper & Reporting ---
488
  def _get_image_caption(self, image: Image.Image) -> str:
489
+ if not HF_API_TOKEN: return "Hugging Face API token not configured."
 
490
  try:
491
  buffered = BytesIO()
492
  image.save(buffered, format="JPEG")
 
501
  return "Caption generation failed."
502
 
503
  def _upload_image_to_salesforce(self, image: Image.Image, record_id: str, worker_id: str) -> Optional[str]:
504
+ if not self.sf: return None
 
505
  try:
506
  buffered = BytesIO()
507
  image.save(buffered, format="JPEG")
508
  encoded_image = base64.b64encode(buffered.getvalue()).decode('utf-8')
509
+ cv = self.sf.ContentVersion.create({'Title': f'Image_{worker_id}', 'PathOnClient': f'{worker_id}.jpg', 'VersionData': encoded_image, 'FirstPublishLocationId': record_id})
 
 
 
 
 
510
  return f"/{cv['id']}" # Relative URL
511
  except Exception as e:
512
  logger.error(f"Salesforce image upload error: {e}")
513
  return None
514
 
515
  def get_registered_workers_info(self) -> str:
516
+ if not self.sf: return "❌ Salesforce not connected."
 
517
  try:
518
  records = self.sf.query_all("SELECT Name, Worker_ID__c FROM Worker__c ORDER BY Name")['records']
519
+ if not records: return "No workers registered."
 
520
  return f"**πŸ‘₯ Registered Workers ({len(records)})**\n" + "\n".join([f"- **{w['Name']}** (ID: {w['Worker_ID__c']})" for w in records])
521
+ except Exception as e: return f"Error: {e}"
 
522
 
523
  # --- GRADIO UI ---
524
  attendance_system = AttendanceSystem()
 
525
  def create_interface():
526
  with gr.Blocks(theme=gr.themes.Soft(), title="Attendance System") as demo:
527
  gr.Markdown("# 🎯 Advanced Face Recognition Attendance System")
 
562
  refresh_workers_btn = gr.Button("πŸ”„ Refresh List")
563
 
564
  # --- Event Handlers ---
565
+ def on_tab_select(evt: gr.SelectData): return evt.index
 
 
566
  video_tabs.select(fn=on_tab_select, inputs=None, outputs=[selected_tab_index])
 
567
  def start_wrapper(tab_index, cam_src, vid_path):
568
  source = cam_src if tab_index == 0 else vid_path
569
  return "Please provide an input source." if source is None else attendance_system.start_processing(source)
 
570
  start_btn.click(fn=start_wrapper, inputs=[selected_tab_index, camera_source, video_file], outputs=[status_box])
571
  stop_btn.click(fn=attendance_system.stop_processing, inputs=None, outputs=[status_box])
572
  register_btn.click(fn=attendance_system.register_worker_manual, inputs=[register_image, register_name], outputs=[register_output, registered_workers_info])
 
576
  while True:
577
  if attendance_system.error_message:
578
  yield None, attendance_system.error_message
579
+ time.sleep(2); attendance_system.error_message = None
 
580
  continue
581
  if attendance_system.is_processing.is_set():
582
  frame, log_md = None, "\n".join(reversed(attendance_system.session_log)) or "Processing..."
583
  try:
584
  if not attendance_system.frame_queue.empty():
585
  frame = attendance_system.frame_queue.get_nowait()
586
+ if frame is not None: frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
587
+ except queue.Empty: pass
 
 
588
  yield frame, log_md
589
  else:
590
  if attendance_system.last_processed_frame is not None: