PrashanthB461 commited on
Commit
96f5fd1
Β·
verified Β·
1 Parent(s): f16032b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -182
app.py CHANGED
@@ -19,6 +19,12 @@ import queue
19
  import requests
20
  from simple_salesforce import Salesforce
21
  from dotenv import load_dotenv
 
 
 
 
 
 
22
 
23
  # Load environment variables from .env file
24
  load_dotenv()
@@ -28,9 +34,23 @@ HF_API_URL = "https://api-inference.huggingface.co/models/Salesforce/blip-image-
28
  HF_API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN")
29
 
30
  # Salesforce configuration
31
- SF_USERNAME = os.getenv("smartlabour@attendance.system")
32
- SF_PASSWORD = os.getenv("#Prashanth@123")
33
- SF_SECURITY_TOKEN = os.getenv("pasQDqmWApzD0skgbv76gVgIs")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  class AttendanceSystem:
36
  def __init__(self):
@@ -47,24 +67,20 @@ class AttendanceSystem:
47
  self.recognition_cooldown = 5 # seconds between recognitions for same person
48
  self.video_file_path = None
49
  self.video_processing = False
50
-
51
  # Initialize Salesforce connection
52
  try:
53
- self.sf = Salesforce(
54
- username=SF_USERNAME,
55
- password=SF_PASSWORD,
56
- security_token=SF_SECURITY_TOKEN
57
- )
58
  except Exception as e:
59
- print(f"Error connecting to Salesforce: {e}")
60
  self.sf = None
61
-
62
  # Create directories for data storage
63
  os.makedirs("data", exist_ok=True)
64
  os.makedirs("data/faces", exist_ok=True)
65
-
66
- self.load_data()
67
 
 
 
68
  def load_data(self):
69
  """Load all stored data"""
70
  try:
@@ -76,20 +92,20 @@ class AttendanceSystem:
76
  self.known_face_names = data.get("names", [])
77
  self.known_face_ids = data.get("ids", [])
78
  self.next_worker_id = data.get("next_id", 1)
79
-
80
  # Load attendance records
81
  if os.path.exists("data/attendance.json"):
82
  with open("data/attendance.json", "r") as f:
83
  self.attendance_records = json.load(f)
84
-
85
  except Exception as e:
86
- print(f"Error loading data: {e}")
87
  self.known_face_embeddings = []
88
  self.known_face_names = []
89
  self.known_face_ids = []
90
  self.attendance_records = []
91
  self.next_worker_id = 1
92
-
93
  def save_data(self):
94
  """Save all data to files"""
95
  try:
@@ -102,14 +118,14 @@ class AttendanceSystem:
102
  }
103
  with open("data/workers.pkl", "wb") as f:
104
  pickle.dump(worker_data, f)
105
-
106
  # Save attendance records
107
  with open("data/attendance.json", "w") as f:
108
  json.dump(self.attendance_records, f, indent=2)
109
-
110
  except Exception as e:
111
- print(f"Error saving data: {e}")
112
-
113
  def get_image_caption(self, image):
114
  """Generate image caption using Hugging Face API"""
115
  try:
@@ -118,60 +134,60 @@ class AttendanceSystem:
118
  img_byte_arr = BytesIO()
119
  image.save(img_byte_arr, format='JPEG')
120
  img_data = img_byte_arr.getvalue()
121
-
122
  # Make API request to Hugging Face
123
  headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
124
  response = requests.post(HF_API_URL, headers=headers, data=img_data)
125
-
126
  if response.status_code == 200:
127
  result = response.json()
128
  if isinstance(result, list) and len(result) > 0:
129
  return result[0].get("generated_text", "No caption generated")
130
  return "No caption generated"
131
  else:
132
- print(f"Hugging Face API error: {response.status_code} - {response.text}")
133
  return "Error generating caption"
134
  except Exception as e:
135
- print(f"Error in Hugging Face API call: {e}")
136
  return "Error generating caption"
137
-
138
  def register_worker_manual(self, image, name):
139
  """Manual worker registration with Hugging Face and Salesforce integration"""
140
  if image is None or not name.strip():
141
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
142
-
143
  # Convert PIL image to RGB array
144
  if isinstance(image, Image.Image):
145
  image_array = np.array(image)
146
-
147
  try:
148
  # Verify the image contains a face
149
  face_analysis = DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True, detector_backend='opencv')
150
-
151
  # Get face embedding
152
  embedding = DeepFace.represent(img_path=image_array, model_name='Facenet')[0]['embedding']
153
-
154
  # Check if person already exists
155
  name = name.strip().title()
156
  if name in self.known_face_names:
157
  return f"❌ {name} is already registered!", self.get_registered_workers_info()
158
-
159
  # Generate new worker ID
160
  worker_id = f"W{self.next_worker_id:04d}"
161
-
162
  # Generate image caption using Hugging Face
163
  caption = self.get_image_caption(image)
164
-
165
  # Add the face embedding, name, and ID
166
  self.known_face_embeddings.append(embedding)
167
  self.known_face_names.append(name)
168
  self.known_face_ids.append(worker_id)
169
  self.next_worker_id += 1
170
-
171
  # Save face image
172
  face_image = Image.fromarray(image_array)
173
  face_image.save(f"data/faces/{worker_id}_{name.replace(' ', '_')}.jpg")
174
-
175
  # Save to Salesforce
176
  if self.sf:
177
  try:
@@ -181,44 +197,44 @@ class AttendanceSystem:
181
  'Face_Embedding__c': json.dumps(embedding),
182
  'Image_Caption__c': caption
183
  })
184
- print(f"βœ… Worker {name} ({worker_id}) saved to Salesforce with caption: {caption}")
185
  except Exception as e:
186
- print(f"Error saving to Salesforce: {e}")
187
-
188
  self.save_data()
189
-
190
- return f"βœ… {name} has been successfully registered with ID: {worker_id}! Caption: {caption}", self.get_registered_workers_info()
191
 
 
 
192
  except ValueError as e:
193
  if "Face could not be detected" in str(e):
194
  return "❌ No face detected in the image! Please try again with a clear face image.", self.get_registered_workers_info()
195
  return f"❌ Error processing image: {str(e)}", self.get_registered_workers_info()
196
  except Exception as e:
197
  return f"❌ Error during registration: {str(e)}", self.get_registered_workers_info()
198
-
199
  def register_worker_auto(self, face_image):
200
  """Automatic worker registration for unrecognized faces"""
201
  try:
202
  # Generate new worker ID and name
203
  worker_id = f"W{self.next_worker_id:04d}"
204
  worker_name = f"Unknown_Worker_{self.next_worker_id}"
205
-
206
  # Get face embedding
207
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
208
-
209
  # Generate image caption
210
  face_pil = Image.fromarray(cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB))
211
  caption = self.get_image_caption(face_pil)
212
-
213
  # Add to database
214
  self.known_face_embeddings.append(embedding)
215
  self.known_face_names.append(worker_name)
216
  self.known_face_ids.append(worker_id)
217
  self.next_worker_id += 1
218
-
219
  # Save face image
220
  face_pil.save(f"data/faces/{worker_id}_{worker_name}.jpg")
221
-
222
  # Save to Salesforce
223
  if self.sf:
224
  try:
@@ -228,30 +244,30 @@ class AttendanceSystem:
228
  'Face_Embedding__c': json.dumps(embedding),
229
  'Image_Caption__c': caption
230
  })
231
- print(f"βœ… Worker {worker_name} ({worker_id}) saved to Salesforce with caption: {caption}")
232
  except Exception as e:
233
- print(f"Error saving to Salesforce: {e}")
234
-
235
  self.save_data()
236
-
237
  return worker_id, worker_name
238
-
239
  except Exception as e:
240
- print(f"Error in auto registration: {e}")
241
  return None, None
242
-
243
  def mark_attendance(self, worker_id, worker_name):
244
  """Mark attendance for a worker and save to Salesforce"""
245
  try:
246
  today = date.today().isoformat()
247
  current_time = datetime.now()
248
-
249
  # Check if already marked today
250
  already_marked = any(
251
  record["worker_id"] == worker_id and record["date"] == today
252
  for record in self.attendance_records
253
  )
254
-
255
  if not already_marked:
256
  # Create attendance record
257
  attendance_record = {
@@ -264,7 +280,7 @@ class AttendanceSystem:
264
  "method": "Auto"
265
  }
266
  self.attendance_records.append(attendance_record)
267
-
268
  # Save to Salesforce
269
  if self.sf:
270
  try:
@@ -277,44 +293,44 @@ class AttendanceSystem:
277
  'Status__c': "Present",
278
  'Method__c': "Auto"
279
  })
280
- print(f"βœ… Attendance for {worker_name} ({worker_id}) saved to Salesforce")
281
  except Exception as e:
282
- print(f"Error saving attendance to Salesforce: {e}")
283
-
284
  self.save_data()
285
  return True
286
  return False
287
-
288
  except Exception as e:
289
- print(f"Error marking attendance: {e}")
290
  return False
291
-
292
  def process_video_frame(self, frame):
293
  """Process a single video frame for face recognition"""
294
  try:
295
  rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
296
-
297
  # Find faces in the frame
298
  face_objs = DeepFace.extract_faces(img_path=rgb_frame, target_size=(160, 160), enforce_detection=False, detector_backend='opencv')
299
-
300
  current_time = time.time()
301
-
302
  for face_obj in face_objs:
303
  if face_obj['confidence'] > 0.9: # Only consider confident detections
304
  face_area = face_obj['facial_area']
305
  x, y, w, h = face_area['x'], face_area['y'], face_area['w'], face_area['h']
306
-
307
  # Extract face image
308
  face_image = frame[y:y+h, x:x+w]
309
-
310
  try:
311
  # Get face embedding
312
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
313
-
314
  worker_id = None
315
  worker_name = "Unknown"
316
  color = (0, 0, 255) # Red for unknown
317
-
318
  # Compare with known faces
319
  if len(self.known_face_embeddings) > 0:
320
  # Calculate distances to known faces
@@ -322,23 +338,23 @@ class AttendanceSystem:
322
  for known_embedding in self.known_face_embeddings:
323
  distance = np.linalg.norm(np.array(embedding) - np.array(known_embedding))
324
  distances.append(distance)
325
-
326
  min_distance = min(distances)
327
  best_match_index = distances.index(min_distance)
328
-
329
  if min_distance < 10: # Threshold for recognition
330
  worker_id = self.known_face_ids[best_match_index]
331
  worker_name = self.known_face_names[best_match_index]
332
  color = (0, 255, 0) # Green for known
333
-
334
  # Check cooldown period
335
  if worker_id not in self.last_recognition_time or \
336
  current_time - self.last_recognition_time[worker_id] > self.recognition_cooldown:
337
-
338
  # Mark attendance
339
  if self.mark_attendance(worker_id, worker_name):
340
- print(f"βœ… Attendance marked for {worker_name} ({worker_id})")
341
-
342
  self.last_recognition_time[worker_id] = current_time
343
  else:
344
  # Unknown face - auto register
@@ -348,145 +364,145 @@ class AttendanceSystem:
348
  worker_id = new_id
349
  worker_name = new_name
350
  color = (255, 165, 0) # Orange for newly registered
351
- print(f"πŸ†• New worker registered: {new_name} ({new_id})")
352
-
353
  # Mark attendance for new worker
354
  self.mark_attendance(worker_id, worker_name)
355
-
356
  # Draw rectangle and label
357
  cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
358
  cv2.rectangle(frame, (x, y+h - 35), (x+w, y+h), color, cv2.FILLED)
359
-
360
  label = f"{worker_name}"
361
  if worker_id:
362
  label += f" ({worker_id})"
363
-
364
  cv2.putText(frame, label, (x + 6, y+h - 6),
365
  cv2.FONT_HERSHEY_DUPLEX, 0.6, (255, 255, 255), 1)
366
-
367
  except Exception as e:
368
- print(f"Error processing face: {e}")
369
  continue
370
-
371
  return frame
372
-
373
  except Exception as e:
374
- print(f"Error processing frame: {e}")
375
  return frame
376
-
377
  def start_video_stream(self, camera_source=0):
378
  """Start video streaming and recognition"""
379
  try:
380
  if self.is_streaming:
381
  return "⚠️ Video stream is already running!"
382
-
383
  # Clear previous video file if switching from file to camera
384
  self.video_file_path = None
385
-
386
  self.video_capture = cv2.VideoCapture(camera_source)
387
  if not self.video_capture.isOpened():
388
  return "❌ Could not open camera/video source!"
389
-
390
  self.is_streaming = True
391
-
392
  def video_loop():
393
  while self.is_streaming:
394
  ret, frame = self.video_capture.read()
395
  if not ret:
396
  break
397
-
398
  # Process frame for face recognition
399
  processed_frame = self.process_video_frame(frame)
400
-
401
  # Add to queue for display
402
  if not self.frame_queue.full():
403
  try:
404
  self.frame_queue.put_nowait(processed_frame)
405
  except queue.Full:
406
  pass
407
-
408
  time.sleep(0.1) # Limit processing rate
409
-
410
  self.recognition_thread = threading.Thread(target=video_loop)
411
  self.recognition_thread.daemon = True
412
  self.recognition_thread.start()
413
-
414
  return "βœ… Live camera stream started successfully!"
415
-
416
  except Exception as e:
417
  return f"❌ Error starting video stream: {e}"
418
-
419
  def process_uploaded_video(self, video_path):
420
  """Process an uploaded video file for face recognition"""
421
  try:
422
  if self.is_streaming:
423
  return "⚠️ Please stop current stream before processing a video file!"
424
-
425
  if not os.path.exists(video_path):
426
  return "❌ Video file not found!"
427
-
428
  self.video_file_path = video_path
429
  self.video_processing = True
430
-
431
  def video_processing_loop():
432
  cap = cv2.VideoCapture(video_path)
433
  fps = cap.get(cv2.CAP_PROP_FPS)
434
  frame_delay = 1.0 / fps if fps > 0 else 0.03
435
-
436
  while self.video_processing and cap.isOpened():
437
  ret, frame = cap.read()
438
  if not ret:
439
  break
440
-
441
  # Process frame for face recognition
442
  processed_frame = self.process_video_frame(frame)
443
-
444
  # Add to queue for display
445
  if not self.frame_queue.full():
446
  try:
447
  self.frame_queue.put_nowait(processed_frame)
448
  except queue.Full:
449
  pass
450
-
451
- time.sleep(frame_delay)
452
 
 
 
453
  cap.release()
454
  self.video_processing = False
455
-
456
  self.recognition_thread = threading.Thread(target=video_processing_loop)
457
  self.recognition_thread.daemon = True
458
  self.recognition_thread.start()
459
-
460
  return f"βœ… Video processing started successfully! ({os.path.basename(video_path)})"
461
-
462
  except Exception as e:
463
  return f"❌ Error processing video: {e}"
464
-
465
  def stop_video_stream(self):
466
  """Stop video streaming or processing"""
467
  try:
468
  self.is_streaming = False
469
  self.video_processing = False
470
-
471
  if self.video_capture:
472
  self.video_capture.release()
473
  self.video_capture = None
474
-
475
  if self.recognition_thread:
476
  self.recognition_thread.join(timeout=2)
477
-
478
  # Clear frame queue
479
  while not self.frame_queue.empty():
480
  try:
481
  self.frame_queue.get_nowait()
482
  except queue.Empty:
483
  break
484
-
485
  return "βœ… Video stream/processing stopped successfully!"
486
-
487
  except Exception as e:
488
  return f"❌ Error stopping video: {e}"
489
-
490
  def get_current_frame(self):
491
  """Get current frame for display"""
492
  try:
@@ -496,113 +512,113 @@ class AttendanceSystem:
496
  return None
497
  except queue.Empty:
498
  return None
499
-
500
  def get_registered_workers_info(self):
501
  """Get information about registered workers from Salesforce"""
502
  if not self.sf:
503
  return "❌ Salesforce connection not established."
504
-
505
  try:
506
  workers = self.sf.query_all("SELECT Name, Worker_ID__c, Image_Caption__c FROM Worker__c")['records']
507
  if not workers:
508
  return "No workers registered yet."
509
-
510
  info = f"**Registered Workers ({len(workers)}):**\n\n"
511
  for i, worker in enumerate(workers, 1):
512
  info += f"{i}. **{worker['Name']}** (ID: {worker['Worker_ID__c']}) - Caption: {worker['Image_Caption__c'] or 'N/A'}\n"
513
  return info
514
  except Exception as e:
515
- print(f"Error fetching workers from Salesforce: {e}")
516
  return self._get_local_workers_info()
517
-
518
  def _get_local_workers_info(self):
519
  """Fallback to local worker info if Salesforce query fails"""
520
  if not self.known_face_names:
521
  return "No workers registered yet."
522
-
523
  info = f"**Registered Workers ({len(self.known_face_names)}):**\n\n"
524
  for i, (worker_id, name) in enumerate(zip(self.known_face_ids, self.known_face_names), 1):
525
  info += f"{i}. **{name}** (ID: {worker_id})\n"
526
  return info
527
-
528
  def get_today_attendance(self):
529
  """Get today's attendance records from Salesforce"""
530
  if not self.sf:
531
  return "❌ Salesforce connection not established."
532
-
533
  today = date.today().isoformat()
534
  try:
535
  records = self.sf.query_all(
536
  f"SELECT Name__c, Worker_ID__c, Time__c, Method__c FROM Attendance__c WHERE Date__c = '{today}'"
537
  )['records']
538
-
539
  if not records:
540
  return f"**Today's Attendance ({today}):**\n\nNo attendance marked yet."
541
-
542
  info = f"**Today's Attendance ({today}):**\n\n"
543
  for record in records:
544
  method_icon = "πŸ€–" if record['Method__c'] == "Auto" else "πŸ‘€"
545
  info += f"{method_icon} **{record['Name__c']}** (ID: {record['Worker_ID__c']}) - {record['Time__c']}\n"
546
  return info
547
  except Exception as e:
548
- print(f"Error fetching attendance from Salesforce: {e}")
549
  return self._get_local_today_attendance()
550
-
551
  def _get_local_today_attendance(self):
552
  """Fallback to local attendance records if Salesforce query fails"""
553
  today = date.today().isoformat()
554
  today_records = [r for r in self.attendance_records if r["date"] == today]
555
-
556
  if not today_records:
557
  return f"**Today's Attendance ({today}):**\n\nNo attendance marked yet."
558
-
559
  info = f"**Today's Attendance ({today}):**\n\n"
560
  for record in today_records:
561
  method_icon = "πŸ€–" if record.get("method") == "Auto" else "πŸ‘€"
562
  info += f"{method_icon} **{record['name']}** (ID: {record['worker_id']}) - {record['time']}\n"
563
  return info
564
-
565
  def get_attendance_report(self, start_date, end_date):
566
  """Generate attendance report for date range from Salesforce"""
567
  if not start_date or not end_date:
568
  return "Please select both start and end dates."
569
-
570
  try:
571
  # Validate date format
572
  datetime.strptime(start_date, '%Y-%m-%d')
573
  datetime.strptime(end_date, '%Y-%m-%d')
574
  except ValueError:
575
  return "Invalid date format. Please use YYYY-MM-DD."
576
-
577
  if not self.sf:
578
  return "❌ Salesforce connection not established."
579
-
580
  try:
581
  # Query Salesforce for attendance records
582
  records = self.sf.query_all(
583
  f"SELECT Worker_ID__c, Name__c, Date__c, Time__c, Method__c FROM Attendance__c "
584
  f"WHERE Date__c >= '{start_date}' AND Date__c <= '{end_date}'"
585
  )['records']
586
-
587
  if not records:
588
  return f"No attendance records found between {start_date} and {end_date}."
589
-
590
  # Create DataFrame for analysis
591
  df = pd.DataFrame(records)
592
-
593
  # Summary statistics
594
  total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
595
  unique_workers = df['Worker_ID__c'].nunique()
596
  total_attendances = len(df)
597
  auto_registrations = len(df[df['Method__c'] == 'Auto'])
598
-
599
  report = f"**πŸ“Š Attendance Report ({start_date} to {end_date})**\n\n"
600
  report += f"**Summary:**\n"
601
  report += f"β€’ Total Days: {total_days}\n"
602
  report += f"β€’ Unique Workers: {unique_workers}\n"
603
- report += f"β€’ Total Attendances: {total_attendances}\n"
604
  report += f"β€’ Auto Detections: {auto_registrations}\n\n"
605
-
606
  # Individual attendance counts
607
  if not df.empty:
608
  attendance_counts = df.groupby(['Worker_ID__c', 'Name__c']).size().reset_index(name='count')
@@ -610,12 +626,12 @@ class AttendanceSystem:
610
  for _, row in attendance_counts.iterrows():
611
  percentage = (row['count'] / total_days) * 100
612
  report += f"β€’ **{row['Name__c']}** ({row['Worker_ID__c']}): {row['count']} days ({percentage:.1f}%)\n"
613
-
614
  return report
615
  except Exception as e:
616
- print(f"Error generating report from Salesforce: {e}")
617
  return self._get_local_attendance_report(start_date, end_date)
618
-
619
  def _get_local_attendance_report(self, start_date, end_date):
620
  """Fallback to local attendance report if Salesforce query fails"""
621
  # Filter records by date range
@@ -623,26 +639,26 @@ class AttendanceSystem:
623
  r for r in self.attendance_records
624
  if start_date <= r["date"] <= end_date
625
  ]
626
-
627
  if not filtered_records:
628
  return f"No attendance records found between {start_date} and {end_date}."
629
-
630
  # Create DataFrame for analysis
631
  df = pd.DataFrame(filtered_records)
632
-
633
  # Summary statistics
634
  total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
635
  unique_workers = df['worker_id'].nunique()
636
  total_attendances = len(df)
637
  auto_registrations = len(df[df['method'] == 'Auto'])
638
-
639
  report = f"**πŸ“Š Attendance Report ({start_date} to {end_date})**\n\n"
640
  report += f"**Summary:**\n"
641
  report += f"β€’ Total Days: {total_days}\n"
642
  report += f"β€’ Unique Workers: {unique_workers}\n"
643
  report += f"β€’ Total Attendances: {total_attendances}\n"
644
  report += f"β€’ Auto Detections: {auto_registrations}\n\n"
645
-
646
  # Individual attendance counts
647
  if not df.empty:
648
  attendance_counts = df.groupby(['worker_id', 'name']).size().reset_index(name='count')
@@ -650,22 +666,22 @@ class AttendanceSystem:
650
  for _, row in attendance_counts.iterrows():
651
  percentage = (row['count'] / total_days) * 100
652
  report += f"β€’ **{row['name']}** ({row['worker_id']}): {row['count']} days ({percentage:.1f}%)\n"
653
-
654
- return report
655
 
 
 
656
  def export_attendance_csv(self):
657
  """Export attendance records to CSV"""
658
  try:
659
  if not self.attendance_records:
660
  return None, "No attendance records to export."
661
-
662
  df = pd.DataFrame(self.attendance_records)
663
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
664
  csv_file = f"attendance_report_{timestamp}.csv"
665
  df.to_csv(csv_file, index=False)
666
-
667
  return csv_file, f"βœ… Attendance exported to {csv_file}"
668
-
669
  except Exception as e:
670
  return None, f"❌ Error exporting data: {e}"
671
 
@@ -693,7 +709,6 @@ def create_interface():
693
  }
694
  """
695
  ) as demo:
696
-
697
  gr.Markdown(
698
  """
699
  # 🎯 Advanced Attendance System with Face Recognition
@@ -711,12 +726,12 @@ def create_interface():
711
  - **☁️ Salesforce Integration** - Store worker and attendance data in Salesforce
712
  """
713
  )
714
-
715
  with gr.Tabs():
716
  # Video Recognition Tab
717
  with gr.Tab("πŸŽ₯ Video Recognition", elem_classes="tab-nav"):
718
  gr.Markdown("### Face Recognition from Live Camera or Video File")
719
-
720
  with gr.Row():
721
  with gr.Column(scale=1):
722
  with gr.Tabs(selected="live", elem_classes="video-option-tabs") as video_tabs:
@@ -726,41 +741,41 @@ def create_interface():
726
  value=0,
727
  precision=0
728
  )
729
-
730
  with gr.Row():
731
  start_stream_btn = gr.Button(
732
  "πŸŽ₯ Start Live Recognition",
733
  variant="primary",
734
  size="lg"
735
  )
736
-
737
  with gr.Tab("Upload Video", id="upload"):
738
  video_file = gr.Video(
739
  label="Upload Video File",
740
  sources=["upload"],
741
  format="mp4"
742
  )
743
-
744
  with gr.Row():
745
  process_video_btn = gr.Button(
746
  "πŸ“Ή Process Video File",
747
  variant="primary",
748
  size="lg"
749
  )
750
-
751
  stop_stream_btn = gr.Button(
752
  "⏹️ Stop Processing",
753
  variant="stop",
754
  size="lg"
755
  )
756
-
757
  stream_status = gr.Textbox(
758
  label="Processing Status",
759
  value="Ready to start...",
760
  interactive=False,
761
  lines=2
762
  )
763
-
764
  gr.Markdown(
765
  """
766
  **πŸ“‹ Instructions:**
@@ -774,28 +789,28 @@ def create_interface():
774
  - πŸ”΄ **Red:** Face detected but processing
775
  """
776
  )
777
-
778
  with gr.Column(scale=1):
779
  video_output = gr.Image(
780
  label="Recognition Output",
781
  streaming=True,
782
  interactive=False
783
  )
784
-
785
  live_attendance_display = gr.Markdown(
786
  value=attendance_system.get_today_attendance(),
787
  label="Live Attendance Updates"
788
  )
789
-
790
  refresh_attendance_btn = gr.Button(
791
  "πŸ”„ Refresh Attendance",
792
  variant="secondary"
793
  )
794
-
795
  # Manual Registration Tab
796
  with gr.Tab("πŸ‘€ Manual Registration", elem_classes="tab-nav"):
797
  gr.Markdown("### Register Workers Manually")
798
-
799
  with gr.Row():
800
  with gr.Column(scale=1):
801
  register_image = gr.Image(
@@ -813,7 +828,7 @@ def create_interface():
813
  variant="primary",
814
  size="lg"
815
  )
816
-
817
  with gr.Column(scale=1):
818
  register_output = gr.Textbox(
819
  label="Registration Status",
@@ -824,11 +839,11 @@ def create_interface():
824
  value=attendance_system.get_registered_workers_info(),
825
  label="Registered Workers Database"
826
  )
827
-
828
  # Reports & Analytics Tab
829
  with gr.Tab("πŸ“Š Reports & Analytics", elem_classes="tab-nav"):
830
  gr.Markdown("### Attendance Reports and Data Export")
831
-
832
  with gr.Row():
833
  with gr.Column():
834
  gr.Markdown("#### πŸ“… Generate Report")
@@ -844,7 +859,7 @@ def create_interface():
844
  "πŸ“Š Generate Report",
845
  variant="primary"
846
  )
847
-
848
  gr.Markdown("#### πŸ’Ύ Export Data")
849
  export_btn = gr.Button(
850
  "πŸ“₯ Export to CSV",
@@ -859,60 +874,60 @@ def create_interface():
859
  label="Download File",
860
  visible=False
861
  )
862
-
863
  with gr.Column():
864
  report_output = gr.Markdown(
865
  value="Select date range and click 'Generate Report' to view attendance analytics.",
866
  label="Attendance Report"
867
  )
868
-
869
  # Event handlers
870
  start_stream_btn.click(
871
  fn=attendance_system.start_video_stream,
872
  inputs=[camera_source],
873
  outputs=[stream_status]
874
  )
875
-
876
  process_video_btn.click(
877
  fn=attendance_system.process_uploaded_video,
878
  inputs=[video_file],
879
  outputs=[stream_status]
880
  )
881
-
882
  stop_stream_btn.click(
883
  fn=attendance_system.stop_video_stream,
884
  outputs=[stream_status]
885
  )
886
-
887
  refresh_attendance_btn.click(
888
  fn=attendance_system.get_today_attendance,
889
  outputs=[live_attendance_display]
890
  )
891
-
892
  register_btn.click(
893
  fn=attendance_system.register_worker_manual,
894
  inputs=[register_image, register_name],
895
  outputs=[register_output, registered_workers_info]
896
  )
897
-
898
  generate_report_btn.click(
899
  fn=attendance_system.get_attendance_report,
900
  inputs=[start_date, end_date],
901
  outputs=[report_output]
902
  )
903
-
904
  def export_and_show():
905
  file_path, status = attendance_system.export_attendance_csv()
906
  if file_path:
907
  return status, gr.update(visible=True, value=file_path)
908
  else:
909
  return status, gr.update(visible=False)
910
-
911
  export_btn.click(
912
  fn=export_and_show,
913
  outputs=[export_status, export_file]
914
  )
915
-
916
  # Video frame update
917
  def update_video_frame():
918
  start_time = time.time()
@@ -926,12 +941,12 @@ def create_interface():
926
  return frame
927
  start_time = current_time
928
  time.sleep(0.01) # Small sleep to prevent busy-waiting
929
-
930
  # Start the video frame update as a background thread
931
  video_thread = threading.Thread(target=lambda: demo.queue()(update_video_frame)())
932
  video_thread.daemon = True
933
  video_thread.start()
934
-
935
  return demo
936
 
937
  # Create and launch the interface
 
19
  import requests
20
  from simple_salesforce import Salesforce
21
  from dotenv import load_dotenv
22
+ from retrying import retry
23
+ import logging
24
+
25
+ # Setup logging
26
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
27
+ logger = logging.getLogger(__name__)
28
 
29
  # Load environment variables from .env file
30
  load_dotenv()
 
34
  HF_API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN")
35
 
36
  # Salesforce configuration
37
+ SF_CREDENTIALS = {
38
+ "username": os.getenv("SF_USERNAME", "smartlabour@attendance.system"),
39
+ "password": os.getenv("SF_PASSWORD", "#Prashanth@123"),
40
+ "security_token": os.getenv("SF_SECURITY_TOKEN", "pasQDqmWApzD0skgbv76gVgIs"),
41
+ "domain": "login"
42
+ }
43
+
44
+ @retry(stop_max_attempt_number=3, wait_fixed=2000)
45
+ def connect_to_salesforce():
46
+ try:
47
+ sf = Salesforce(**SF_CREDENTIALS)
48
+ logger.info("Connected to Salesforce")
49
+ sf.describe()
50
+ return sf
51
+ except Exception as e:
52
+ logger.error(f"Salesforce connection failed: {e}")
53
+ raise
54
 
55
  class AttendanceSystem:
56
  def __init__(self):
 
67
  self.recognition_cooldown = 5 # seconds between recognitions for same person
68
  self.video_file_path = None
69
  self.video_processing = False
70
+
71
  # Initialize Salesforce connection
72
  try:
73
+ self.sf = connect_to_salesforce()
 
 
 
 
74
  except Exception as e:
75
+ logger.error(f"Error connecting to Salesforce: {e}")
76
  self.sf = None
77
+
78
  # Create directories for data storage
79
  os.makedirs("data", exist_ok=True)
80
  os.makedirs("data/faces", exist_ok=True)
 
 
81
 
82
+ self.load_data()
83
+
84
  def load_data(self):
85
  """Load all stored data"""
86
  try:
 
92
  self.known_face_names = data.get("names", [])
93
  self.known_face_ids = data.get("ids", [])
94
  self.next_worker_id = data.get("next_id", 1)
95
+
96
  # Load attendance records
97
  if os.path.exists("data/attendance.json"):
98
  with open("data/attendance.json", "r") as f:
99
  self.attendance_records = json.load(f)
100
+
101
  except Exception as e:
102
+ logger.error(f"Error loading data: {e}")
103
  self.known_face_embeddings = []
104
  self.known_face_names = []
105
  self.known_face_ids = []
106
  self.attendance_records = []
107
  self.next_worker_id = 1
108
+
109
  def save_data(self):
110
  """Save all data to files"""
111
  try:
 
118
  }
119
  with open("data/workers.pkl", "wb") as f:
120
  pickle.dump(worker_data, f)
121
+
122
  # Save attendance records
123
  with open("data/attendance.json", "w") as f:
124
  json.dump(self.attendance_records, f, indent=2)
125
+
126
  except Exception as e:
127
+ logger.error(f"Error saving data: {e}")
128
+
129
  def get_image_caption(self, image):
130
  """Generate image caption using Hugging Face API"""
131
  try:
 
134
  img_byte_arr = BytesIO()
135
  image.save(img_byte_arr, format='JPEG')
136
  img_data = img_byte_arr.getvalue()
137
+
138
  # Make API request to Hugging Face
139
  headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
140
  response = requests.post(HF_API_URL, headers=headers, data=img_data)
141
+
142
  if response.status_code == 200:
143
  result = response.json()
144
  if isinstance(result, list) and len(result) > 0:
145
  return result[0].get("generated_text", "No caption generated")
146
  return "No caption generated"
147
  else:
148
+ logger.error(f"Hugging Face API error: {response.status_code} - {response.text}")
149
  return "Error generating caption"
150
  except Exception as e:
151
+ logger.error(f"Error in Hugging Face API call: {e}")
152
  return "Error generating caption"
153
+
154
  def register_worker_manual(self, image, name):
155
  """Manual worker registration with Hugging Face and Salesforce integration"""
156
  if image is None or not name.strip():
157
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
158
+
159
  # Convert PIL image to RGB array
160
  if isinstance(image, Image.Image):
161
  image_array = np.array(image)
162
+
163
  try:
164
  # Verify the image contains a face
165
  face_analysis = DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True, detector_backend='opencv')
166
+
167
  # Get face embedding
168
  embedding = DeepFace.represent(img_path=image_array, model_name='Facenet')[0]['embedding']
169
+
170
  # Check if person already exists
171
  name = name.strip().title()
172
  if name in self.known_face_names:
173
  return f"❌ {name} is already registered!", self.get_registered_workers_info()
174
+
175
  # Generate new worker ID
176
  worker_id = f"W{self.next_worker_id:04d}"
177
+
178
  # Generate image caption using Hugging Face
179
  caption = self.get_image_caption(image)
180
+
181
  # Add the face embedding, name, and ID
182
  self.known_face_embeddings.append(embedding)
183
  self.known_face_names.append(name)
184
  self.known_face_ids.append(worker_id)
185
  self.next_worker_id += 1
186
+
187
  # Save face image
188
  face_image = Image.fromarray(image_array)
189
  face_image.save(f"data/faces/{worker_id}_{name.replace(' ', '_')}.jpg")
190
+
191
  # Save to Salesforce
192
  if self.sf:
193
  try:
 
197
  'Face_Embedding__c': json.dumps(embedding),
198
  'Image_Caption__c': caption
199
  })
200
+ logger.info(f"Worker {name} ({worker_id}) saved to Salesforce with caption: {caption}")
201
  except Exception as e:
202
+ logger.error(f"Error saving to Salesforce: {e}")
203
+
204
  self.save_data()
 
 
205
 
206
+ return f"βœ… {name} has been successfully registered with ID: {worker_id}! Caption: {caption}", self.get_registered_workers_info()
207
+
208
  except ValueError as e:
209
  if "Face could not be detected" in str(e):
210
  return "❌ No face detected in the image! Please try again with a clear face image.", self.get_registered_workers_info()
211
  return f"❌ Error processing image: {str(e)}", self.get_registered_workers_info()
212
  except Exception as e:
213
  return f"❌ Error during registration: {str(e)}", self.get_registered_workers_info()
214
+
215
  def register_worker_auto(self, face_image):
216
  """Automatic worker registration for unrecognized faces"""
217
  try:
218
  # Generate new worker ID and name
219
  worker_id = f"W{self.next_worker_id:04d}"
220
  worker_name = f"Unknown_Worker_{self.next_worker_id}"
221
+
222
  # Get face embedding
223
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
224
+
225
  # Generate image caption
226
  face_pil = Image.fromarray(cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB))
227
  caption = self.get_image_caption(face_pil)
228
+
229
  # Add to database
230
  self.known_face_embeddings.append(embedding)
231
  self.known_face_names.append(worker_name)
232
  self.known_face_ids.append(worker_id)
233
  self.next_worker_id += 1
234
+
235
  # Save face image
236
  face_pil.save(f"data/faces/{worker_id}_{worker_name}.jpg")
237
+
238
  # Save to Salesforce
239
  if self.sf:
240
  try:
 
244
  'Face_Embedding__c': json.dumps(embedding),
245
  'Image_Caption__c': caption
246
  })
247
+ logger.info(f"Worker {worker_name} ({worker_id}) saved to Salesforce with caption: {caption}")
248
  except Exception as e:
249
+ logger.error(f"Error saving to Salesforce: {e}")
250
+
251
  self.save_data()
252
+
253
  return worker_id, worker_name
254
+
255
  except Exception as e:
256
+ logger.error(f"Error in auto registration: {e}")
257
  return None, None
258
+
259
  def mark_attendance(self, worker_id, worker_name):
260
  """Mark attendance for a worker and save to Salesforce"""
261
  try:
262
  today = date.today().isoformat()
263
  current_time = datetime.now()
264
+
265
  # Check if already marked today
266
  already_marked = any(
267
  record["worker_id"] == worker_id and record["date"] == today
268
  for record in self.attendance_records
269
  )
270
+
271
  if not already_marked:
272
  # Create attendance record
273
  attendance_record = {
 
280
  "method": "Auto"
281
  }
282
  self.attendance_records.append(attendance_record)
283
+
284
  # Save to Salesforce
285
  if self.sf:
286
  try:
 
293
  'Status__c': "Present",
294
  'Method__c': "Auto"
295
  })
296
+ logger.info(f"Attendance for {worker_name} ({worker_id}) saved to Salesforce")
297
  except Exception as e:
298
+ logger.error(f"Error saving attendance to Salesforce: {e}")
299
+
300
  self.save_data()
301
  return True
302
  return False
303
+
304
  except Exception as e:
305
+ logger.error(f"Error marking attendance: {e}")
306
  return False
307
+
308
  def process_video_frame(self, frame):
309
  """Process a single video frame for face recognition"""
310
  try:
311
  rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
312
+
313
  # Find faces in the frame
314
  face_objs = DeepFace.extract_faces(img_path=rgb_frame, target_size=(160, 160), enforce_detection=False, detector_backend='opencv')
315
+
316
  current_time = time.time()
317
+
318
  for face_obj in face_objs:
319
  if face_obj['confidence'] > 0.9: # Only consider confident detections
320
  face_area = face_obj['facial_area']
321
  x, y, w, h = face_area['x'], face_area['y'], face_area['w'], face_area['h']
322
+
323
  # Extract face image
324
  face_image = frame[y:y+h, x:x+w]
325
+
326
  try:
327
  # Get face embedding
328
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet')[0]['embedding']
329
+
330
  worker_id = None
331
  worker_name = "Unknown"
332
  color = (0, 0, 255) # Red for unknown
333
+
334
  # Compare with known faces
335
  if len(self.known_face_embeddings) > 0:
336
  # Calculate distances to known faces
 
338
  for known_embedding in self.known_face_embeddings:
339
  distance = np.linalg.norm(np.array(embedding) - np.array(known_embedding))
340
  distances.append(distance)
341
+
342
  min_distance = min(distances)
343
  best_match_index = distances.index(min_distance)
344
+
345
  if min_distance < 10: # Threshold for recognition
346
  worker_id = self.known_face_ids[best_match_index]
347
  worker_name = self.known_face_names[best_match_index]
348
  color = (0, 255, 0) # Green for known
349
+
350
  # Check cooldown period
351
  if worker_id not in self.last_recognition_time or \
352
  current_time - self.last_recognition_time[worker_id] > self.recognition_cooldown:
353
+
354
  # Mark attendance
355
  if self.mark_attendance(worker_id, worker_name):
356
+ logger.info(f"Attendance marked for {worker_name} ({worker_id})")
357
+
358
  self.last_recognition_time[worker_id] = current_time
359
  else:
360
  # Unknown face - auto register
 
364
  worker_id = new_id
365
  worker_name = new_name
366
  color = (255, 165, 0) # Orange for newly registered
367
+ logger.info(f"New worker registered: {new_name} ({new_id})")
368
+
369
  # Mark attendance for new worker
370
  self.mark_attendance(worker_id, worker_name)
371
+
372
  # Draw rectangle and label
373
  cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
374
  cv2.rectangle(frame, (x, y+h - 35), (x+w, y+h), color, cv2.FILLED)
375
+
376
  label = f"{worker_name}"
377
  if worker_id:
378
  label += f" ({worker_id})"
379
+
380
  cv2.putText(frame, label, (x + 6, y+h - 6),
381
  cv2.FONT_HERSHEY_DUPLEX, 0.6, (255, 255, 255), 1)
382
+
383
  except Exception as e:
384
+ logger.error(f"Error processing face: {e}")
385
  continue
386
+
387
  return frame
388
+
389
  except Exception as e:
390
+ logger.error(f"Error processing frame: {e}")
391
  return frame
392
+
393
  def start_video_stream(self, camera_source=0):
394
  """Start video streaming and recognition"""
395
  try:
396
  if self.is_streaming:
397
  return "⚠️ Video stream is already running!"
398
+
399
  # Clear previous video file if switching from file to camera
400
  self.video_file_path = None
401
+
402
  self.video_capture = cv2.VideoCapture(camera_source)
403
  if not self.video_capture.isOpened():
404
  return "❌ Could not open camera/video source!"
405
+
406
  self.is_streaming = True
407
+
408
  def video_loop():
409
  while self.is_streaming:
410
  ret, frame = self.video_capture.read()
411
  if not ret:
412
  break
413
+
414
  # Process frame for face recognition
415
  processed_frame = self.process_video_frame(frame)
416
+
417
  # Add to queue for display
418
  if not self.frame_queue.full():
419
  try:
420
  self.frame_queue.put_nowait(processed_frame)
421
  except queue.Full:
422
  pass
423
+
424
  time.sleep(0.1) # Limit processing rate
425
+
426
  self.recognition_thread = threading.Thread(target=video_loop)
427
  self.recognition_thread.daemon = True
428
  self.recognition_thread.start()
429
+
430
  return "βœ… Live camera stream started successfully!"
431
+
432
  except Exception as e:
433
  return f"❌ Error starting video stream: {e}"
434
+
435
  def process_uploaded_video(self, video_path):
436
  """Process an uploaded video file for face recognition"""
437
  try:
438
  if self.is_streaming:
439
  return "⚠️ Please stop current stream before processing a video file!"
440
+
441
  if not os.path.exists(video_path):
442
  return "❌ Video file not found!"
443
+
444
  self.video_file_path = video_path
445
  self.video_processing = True
446
+
447
  def video_processing_loop():
448
  cap = cv2.VideoCapture(video_path)
449
  fps = cap.get(cv2.CAP_PROP_FPS)
450
  frame_delay = 1.0 / fps if fps > 0 else 0.03
451
+
452
  while self.video_processing and cap.isOpened():
453
  ret, frame = cap.read()
454
  if not ret:
455
  break
456
+
457
  # Process frame for face recognition
458
  processed_frame = self.process_video_frame(frame)
459
+
460
  # Add to queue for display
461
  if not self.frame_queue.full():
462
  try:
463
  self.frame_queue.put_nowait(processed_frame)
464
  except queue.Full:
465
  pass
 
 
466
 
467
+ time.sleep(frame_delay)
468
+
469
  cap.release()
470
  self.video_processing = False
471
+
472
  self.recognition_thread = threading.Thread(target=video_processing_loop)
473
  self.recognition_thread.daemon = True
474
  self.recognition_thread.start()
475
+
476
  return f"βœ… Video processing started successfully! ({os.path.basename(video_path)})"
477
+
478
  except Exception as e:
479
  return f"❌ Error processing video: {e}"
480
+
481
  def stop_video_stream(self):
482
  """Stop video streaming or processing"""
483
  try:
484
  self.is_streaming = False
485
  self.video_processing = False
486
+
487
  if self.video_capture:
488
  self.video_capture.release()
489
  self.video_capture = None
490
+
491
  if self.recognition_thread:
492
  self.recognition_thread.join(timeout=2)
493
+
494
  # Clear frame queue
495
  while not self.frame_queue.empty():
496
  try:
497
  self.frame_queue.get_nowait()
498
  except queue.Empty:
499
  break
500
+
501
  return "βœ… Video stream/processing stopped successfully!"
502
+
503
  except Exception as e:
504
  return f"❌ Error stopping video: {e}"
505
+
506
  def get_current_frame(self):
507
  """Get current frame for display"""
508
  try:
 
512
  return None
513
  except queue.Empty:
514
  return None
515
+
516
  def get_registered_workers_info(self):
517
  """Get information about registered workers from Salesforce"""
518
  if not self.sf:
519
  return "❌ Salesforce connection not established."
520
+
521
  try:
522
  workers = self.sf.query_all("SELECT Name, Worker_ID__c, Image_Caption__c FROM Worker__c")['records']
523
  if not workers:
524
  return "No workers registered yet."
525
+
526
  info = f"**Registered Workers ({len(workers)}):**\n\n"
527
  for i, worker in enumerate(workers, 1):
528
  info += f"{i}. **{worker['Name']}** (ID: {worker['Worker_ID__c']}) - Caption: {worker['Image_Caption__c'] or 'N/A'}\n"
529
  return info
530
  except Exception as e:
531
+ logger.error(f"Error fetching workers from Salesforce: {e}")
532
  return self._get_local_workers_info()
533
+
534
  def _get_local_workers_info(self):
535
  """Fallback to local worker info if Salesforce query fails"""
536
  if not self.known_face_names:
537
  return "No workers registered yet."
538
+
539
  info = f"**Registered Workers ({len(self.known_face_names)}):**\n\n"
540
  for i, (worker_id, name) in enumerate(zip(self.known_face_ids, self.known_face_names), 1):
541
  info += f"{i}. **{name}** (ID: {worker_id})\n"
542
  return info
543
+
544
  def get_today_attendance(self):
545
  """Get today's attendance records from Salesforce"""
546
  if not self.sf:
547
  return "❌ Salesforce connection not established."
548
+
549
  today = date.today().isoformat()
550
  try:
551
  records = self.sf.query_all(
552
  f"SELECT Name__c, Worker_ID__c, Time__c, Method__c FROM Attendance__c WHERE Date__c = '{today}'"
553
  )['records']
554
+
555
  if not records:
556
  return f"**Today's Attendance ({today}):**\n\nNo attendance marked yet."
557
+
558
  info = f"**Today's Attendance ({today}):**\n\n"
559
  for record in records:
560
  method_icon = "πŸ€–" if record['Method__c'] == "Auto" else "πŸ‘€"
561
  info += f"{method_icon} **{record['Name__c']}** (ID: {record['Worker_ID__c']}) - {record['Time__c']}\n"
562
  return info
563
  except Exception as e:
564
+ logger.error(f"Error fetching attendance from Salesforce: {e}")
565
  return self._get_local_today_attendance()
566
+
567
  def _get_local_today_attendance(self):
568
  """Fallback to local attendance records if Salesforce query fails"""
569
  today = date.today().isoformat()
570
  today_records = [r for r in self.attendance_records if r["date"] == today]
571
+
572
  if not today_records:
573
  return f"**Today's Attendance ({today}):**\n\nNo attendance marked yet."
574
+
575
  info = f"**Today's Attendance ({today}):**\n\n"
576
  for record in today_records:
577
  method_icon = "πŸ€–" if record.get("method") == "Auto" else "πŸ‘€"
578
  info += f"{method_icon} **{record['name']}** (ID: {record['worker_id']}) - {record['time']}\n"
579
  return info
580
+
581
  def get_attendance_report(self, start_date, end_date):
582
  """Generate attendance report for date range from Salesforce"""
583
  if not start_date or not end_date:
584
  return "Please select both start and end dates."
585
+
586
  try:
587
  # Validate date format
588
  datetime.strptime(start_date, '%Y-%m-%d')
589
  datetime.strptime(end_date, '%Y-%m-%d')
590
  except ValueError:
591
  return "Invalid date format. Please use YYYY-MM-DD."
592
+
593
  if not self.sf:
594
  return "❌ Salesforce connection not established."
595
+
596
  try:
597
  # Query Salesforce for attendance records
598
  records = self.sf.query_all(
599
  f"SELECT Worker_ID__c, Name__c, Date__c, Time__c, Method__c FROM Attendance__c "
600
  f"WHERE Date__c >= '{start_date}' AND Date__c <= '{end_date}'"
601
  )['records']
602
+
603
  if not records:
604
  return f"No attendance records found between {start_date} and {end_date}."
605
+
606
  # Create DataFrame for analysis
607
  df = pd.DataFrame(records)
608
+
609
  # Summary statistics
610
  total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
611
  unique_workers = df['Worker_ID__c'].nunique()
612
  total_attendances = len(df)
613
  auto_registrations = len(df[df['Method__c'] == 'Auto'])
614
+
615
  report = f"**πŸ“Š Attendance Report ({start_date} to {end_date})**\n\n"
616
  report += f"**Summary:**\n"
617
  report += f"β€’ Total Days: {total_days}\n"
618
  report += f"β€’ Unique Workers: {unique_workers}\n"
619
+ report += f"β€’ Total Attend atances: {total_attendances}\n"
620
  report += f"β€’ Auto Detections: {auto_registrations}\n\n"
621
+
622
  # Individual attendance counts
623
  if not df.empty:
624
  attendance_counts = df.groupby(['Worker_ID__c', 'Name__c']).size().reset_index(name='count')
 
626
  for _, row in attendance_counts.iterrows():
627
  percentage = (row['count'] / total_days) * 100
628
  report += f"β€’ **{row['Name__c']}** ({row['Worker_ID__c']}): {row['count']} days ({percentage:.1f}%)\n"
629
+
630
  return report
631
  except Exception as e:
632
+ logger.error(f"Error generating report from Salesforce: {e}")
633
  return self._get_local_attendance_report(start_date, end_date)
634
+
635
  def _get_local_attendance_report(self, start_date, end_date):
636
  """Fallback to local attendance report if Salesforce query fails"""
637
  # Filter records by date range
 
639
  r for r in self.attendance_records
640
  if start_date <= r["date"] <= end_date
641
  ]
642
+
643
  if not filtered_records:
644
  return f"No attendance records found between {start_date} and {end_date}."
645
+
646
  # Create DataFrame for analysis
647
  df = pd.DataFrame(filtered_records)
648
+
649
  # Summary statistics
650
  total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
651
  unique_workers = df['worker_id'].nunique()
652
  total_attendances = len(df)
653
  auto_registrations = len(df[df['method'] == 'Auto'])
654
+
655
  report = f"**πŸ“Š Attendance Report ({start_date} to {end_date})**\n\n"
656
  report += f"**Summary:**\n"
657
  report += f"β€’ Total Days: {total_days}\n"
658
  report += f"β€’ Unique Workers: {unique_workers}\n"
659
  report += f"β€’ Total Attendances: {total_attendances}\n"
660
  report += f"β€’ Auto Detections: {auto_registrations}\n\n"
661
+
662
  # Individual attendance counts
663
  if not df.empty:
664
  attendance_counts = df.groupby(['worker_id', 'name']).size().reset_index(name='count')
 
666
  for _, row in attendance_counts.iterrows():
667
  percentage = (row['count'] / total_days) * 100
668
  report += f"β€’ **{row['name']}** ({row['worker_id']}): {row['count']} days ({percentage:.1f}%)\n"
 
 
669
 
670
+ return report
671
+
672
  def export_attendance_csv(self):
673
  """Export attendance records to CSV"""
674
  try:
675
  if not self.attendance_records:
676
  return None, "No attendance records to export."
677
+
678
  df = pd.DataFrame(self.attendance_records)
679
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
680
  csv_file = f"attendance_report_{timestamp}.csv"
681
  df.to_csv(csv_file, index=False)
682
+
683
  return csv_file, f"βœ… Attendance exported to {csv_file}"
684
+
685
  except Exception as e:
686
  return None, f"❌ Error exporting data: {e}"
687
 
 
709
  }
710
  """
711
  ) as demo:
 
712
  gr.Markdown(
713
  """
714
  # 🎯 Advanced Attendance System with Face Recognition
 
726
  - **☁️ Salesforce Integration** - Store worker and attendance data in Salesforce
727
  """
728
  )
729
+
730
  with gr.Tabs():
731
  # Video Recognition Tab
732
  with gr.Tab("πŸŽ₯ Video Recognition", elem_classes="tab-nav"):
733
  gr.Markdown("### Face Recognition from Live Camera or Video File")
734
+
735
  with gr.Row():
736
  with gr.Column(scale=1):
737
  with gr.Tabs(selected="live", elem_classes="video-option-tabs") as video_tabs:
 
741
  value=0,
742
  precision=0
743
  )
744
+
745
  with gr.Row():
746
  start_stream_btn = gr.Button(
747
  "πŸŽ₯ Start Live Recognition",
748
  variant="primary",
749
  size="lg"
750
  )
751
+
752
  with gr.Tab("Upload Video", id="upload"):
753
  video_file = gr.Video(
754
  label="Upload Video File",
755
  sources=["upload"],
756
  format="mp4"
757
  )
758
+
759
  with gr.Row():
760
  process_video_btn = gr.Button(
761
  "πŸ“Ή Process Video File",
762
  variant="primary",
763
  size="lg"
764
  )
765
+
766
  stop_stream_btn = gr.Button(
767
  "⏹️ Stop Processing",
768
  variant="stop",
769
  size="lg"
770
  )
771
+
772
  stream_status = gr.Textbox(
773
  label="Processing Status",
774
  value="Ready to start...",
775
  interactive=False,
776
  lines=2
777
  )
778
+
779
  gr.Markdown(
780
  """
781
  **πŸ“‹ Instructions:**
 
789
  - πŸ”΄ **Red:** Face detected but processing
790
  """
791
  )
792
+
793
  with gr.Column(scale=1):
794
  video_output = gr.Image(
795
  label="Recognition Output",
796
  streaming=True,
797
  interactive=False
798
  )
799
+
800
  live_attendance_display = gr.Markdown(
801
  value=attendance_system.get_today_attendance(),
802
  label="Live Attendance Updates"
803
  )
804
+
805
  refresh_attendance_btn = gr.Button(
806
  "πŸ”„ Refresh Attendance",
807
  variant="secondary"
808
  )
809
+
810
  # Manual Registration Tab
811
  with gr.Tab("πŸ‘€ Manual Registration", elem_classes="tab-nav"):
812
  gr.Markdown("### Register Workers Manually")
813
+
814
  with gr.Row():
815
  with gr.Column(scale=1):
816
  register_image = gr.Image(
 
828
  variant="primary",
829
  size="lg"
830
  )
831
+
832
  with gr.Column(scale=1):
833
  register_output = gr.Textbox(
834
  label="Registration Status",
 
839
  value=attendance_system.get_registered_workers_info(),
840
  label="Registered Workers Database"
841
  )
842
+
843
  # Reports & Analytics Tab
844
  with gr.Tab("πŸ“Š Reports & Analytics", elem_classes="tab-nav"):
845
  gr.Markdown("### Attendance Reports and Data Export")
846
+
847
  with gr.Row():
848
  with gr.Column():
849
  gr.Markdown("#### πŸ“… Generate Report")
 
859
  "πŸ“Š Generate Report",
860
  variant="primary"
861
  )
862
+
863
  gr.Markdown("#### πŸ’Ύ Export Data")
864
  export_btn = gr.Button(
865
  "πŸ“₯ Export to CSV",
 
874
  label="Download File",
875
  visible=False
876
  )
877
+
878
  with gr.Column():
879
  report_output = gr.Markdown(
880
  value="Select date range and click 'Generate Report' to view attendance analytics.",
881
  label="Attendance Report"
882
  )
883
+
884
  # Event handlers
885
  start_stream_btn.click(
886
  fn=attendance_system.start_video_stream,
887
  inputs=[camera_source],
888
  outputs=[stream_status]
889
  )
890
+
891
  process_video_btn.click(
892
  fn=attendance_system.process_uploaded_video,
893
  inputs=[video_file],
894
  outputs=[stream_status]
895
  )
896
+
897
  stop_stream_btn.click(
898
  fn=attendance_system.stop_video_stream,
899
  outputs=[stream_status]
900
  )
901
+
902
  refresh_attendance_btn.click(
903
  fn=attendance_system.get_today_attendance,
904
  outputs=[live_attendance_display]
905
  )
906
+
907
  register_btn.click(
908
  fn=attendance_system.register_worker_manual,
909
  inputs=[register_image, register_name],
910
  outputs=[register_output, registered_workers_info]
911
  )
912
+
913
  generate_report_btn.click(
914
  fn=attendance_system.get_attendance_report,
915
  inputs=[start_date, end_date],
916
  outputs=[report_output]
917
  )
918
+
919
  def export_and_show():
920
  file_path, status = attendance_system.export_attendance_csv()
921
  if file_path:
922
  return status, gr.update(visible=True, value=file_path)
923
  else:
924
  return status, gr.update(visible=False)
925
+
926
  export_btn.click(
927
  fn=export_and_show,
928
  outputs=[export_status, export_file]
929
  )
930
+
931
  # Video frame update
932
  def update_video_frame():
933
  start_time = time.time()
 
941
  return frame
942
  start_time = current_time
943
  time.sleep(0.01) # Small sleep to prevent busy-waiting
944
+
945
  # Start the video frame update as a background thread
946
  video_thread = threading.Thread(target=lambda: demo.queue()(update_video_frame)())
947
  video_thread.daemon = True
948
  video_thread.start()
949
+
950
  return demo
951
 
952
  # Create and launch the interface