PrashanthB461 commited on
Commit
efbcca1
Β·
verified Β·
1 Parent(s): 18a517b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +504 -255
app.py CHANGED
@@ -2,7 +2,7 @@ import gradio as gr
2
  import cv2
3
  import numpy as np
4
  import pandas as pd
5
- from datetime import datetime, date
6
  import face_recognition
7
  import pickle
8
  import os
@@ -10,51 +10,79 @@ from io import BytesIO
10
  import base64
11
  from PIL import Image
12
  import json
 
 
 
13
 
14
- class AttendanceAnalyzer:
15
  def __init__(self):
16
  self.known_face_encodings = []
17
  self.known_face_names = []
 
18
  self.attendance_records = []
19
- self.load_known_faces()
20
-
21
- def load_known_faces(self):
22
- """Load known faces from storage"""
23
- if os.path.exists("known_faces.pkl"):
24
- try:
25
- with open("known_faces.pkl", "rb") as f:
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  data = pickle.load(f)
27
  self.known_face_encodings = data.get("encodings", [])
28
  self.known_face_names = data.get("names", [])
29
- except:
30
- self.known_face_encodings = []
31
- self.known_face_names = []
32
-
33
- if os.path.exists("attendance_records.json"):
34
- try:
35
- with open("attendance_records.json", "r") as f:
36
  self.attendance_records = json.load(f)
37
- except:
38
- self.attendance_records = []
39
-
40
- def save_known_faces(self):
41
- """Save known faces to storage"""
42
- data = {
43
- "encodings": self.known_face_encodings,
44
- "names": self.known_face_names
45
- }
46
- with open("known_faces.pkl", "wb") as f:
47
- pickle.dump(data, f)
48
 
49
- def save_attendance_records(self):
50
- """Save attendance records to storage"""
51
- with open("attendance_records.json", "w") as f:
52
- json.dump(self.attendance_records, f, indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- def register_face(self, image, name):
55
- """Register a new face for attendance tracking"""
56
  if image is None or not name.strip():
57
- return "❌ Please provide both image and name!", self.get_registered_faces_info()
58
 
59
  # Convert PIL image to RGB array
60
  if isinstance(image, Image.Image):
@@ -65,102 +93,250 @@ class AttendanceAnalyzer:
65
  face_encodings = face_recognition.face_encodings(image, face_locations)
66
 
67
  if len(face_encodings) == 0:
68
- return "❌ No face detected in the image! Please try again with a clear face image.", self.get_registered_faces_info()
69
 
70
  if len(face_encodings) > 1:
71
- return "❌ Multiple faces detected! Please upload an image with only one face.", self.get_registered_faces_info()
72
 
73
  # Check if person already exists
74
  name = name.strip().title()
75
  if name in self.known_face_names:
76
- return f"❌ {name} is already registered!", self.get_registered_faces_info()
77
 
78
- # Add the face encoding and name
 
 
 
79
  self.known_face_encodings.append(face_encodings[0])
80
  self.known_face_names.append(name)
81
- self.save_known_faces()
82
-
83
- return f"βœ… {name} has been successfully registered!", self.get_registered_faces_info()
84
-
85
- def mark_attendance(self, image):
86
- """Mark attendance for detected faces"""
87
- if image is None:
88
- return "❌ Please provide an image!", self.get_today_attendance()
89
-
90
- if len(self.known_face_encodings) == 0:
91
- return "❌ No registered faces found! Please register faces first.", self.get_today_attendance()
92
-
93
- # Convert PIL image to RGB array
94
- if isinstance(image, Image.Image):
95
- image = np.array(image)
96
 
97
- # Find faces in the image
98
- face_locations = face_recognition.face_locations(image)
99
- face_encodings = face_recognition.face_encodings(image, face_locations)
100
-
101
- if len(face_encodings) == 0:
102
- return "❌ No faces detected in the image!", self.get_today_attendance()
103
 
104
- # Process each face
105
- recognized_faces = []
106
- unknown_faces = 0
107
 
108
- for face_encoding in face_encodings:
109
- # Compare with known faces
110
- matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding, tolerance=0.6)
111
- face_distances = face_recognition.face_distance(self.known_face_encodings, face_encoding)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
- if len(face_distances) > 0:
114
- best_match_index = np.argmin(face_distances)
 
 
 
 
 
 
 
115
 
116
- if matches[best_match_index] and face_distances[best_match_index] < 0.6:
117
- name = self.known_face_names[best_match_index]
118
-
119
- # Check if already marked today
120
- today = date.today().isoformat()
121
- already_marked = any(
122
- record["name"] == name and record["date"] == today
123
- for record in self.attendance_records
124
- )
 
 
 
 
125
 
126
- if not already_marked:
127
- # Mark attendance
128
- self.attendance_records.append({
129
- "name": name,
130
- "date": today,
131
- "time": datetime.now().strftime("%H:%M:%S"),
132
- "status": "Present"
133
- })
134
- recognized_faces.append(name)
 
 
 
 
 
 
135
  else:
136
- recognized_faces.append(f"{name} (Already marked)")
137
- else:
138
- unknown_faces += 1
139
- else:
140
- unknown_faces += 1
141
-
142
- # Save attendance records
143
- self.save_attendance_records()
144
-
145
- # Prepare result message
146
- result_parts = []
147
- if recognized_faces:
148
- result_parts.append(f"βœ… Attendance marked for: {', '.join(recognized_faces)}")
149
- if unknown_faces > 0:
150
- result_parts.append(f"❓ {unknown_faces} unknown face(s) detected")
151
-
152
- result = "\n".join(result_parts) if result_parts else "❌ No faces could be processed!"
153
-
154
- return result, self.get_today_attendance()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- def get_registered_faces_info(self):
157
- """Get information about registered faces"""
 
 
 
 
 
 
 
 
 
 
158
  if not self.known_face_names:
159
- return "No faces registered yet."
160
 
161
- info = f"**Registered Faces ({len(self.known_face_names)}):**\n"
162
- for i, name in enumerate(self.known_face_names, 1):
163
- info += f"{i}. {name}\n"
164
  return info
165
 
166
  def get_today_attendance(self):
@@ -169,11 +345,12 @@ class AttendanceAnalyzer:
169
  today_records = [r for r in self.attendance_records if r["date"] == today]
170
 
171
  if not today_records:
172
- return f"**Today's Attendance ({today}):**\nNo attendance marked yet."
173
 
174
- info = f"**Today's Attendance ({today}):**\n"
175
  for record in today_records:
176
- info += f"β€’ {record['name']} - {record['time']} ({record['status']})\n"
 
177
 
178
  return info
179
 
@@ -182,6 +359,12 @@ class AttendanceAnalyzer:
182
  if not start_date or not end_date:
183
  return "Please select both start and end dates."
184
 
 
 
 
 
 
 
185
  # Filter records by date range
186
  filtered_records = [
187
  r for r in self.attendance_records
@@ -191,148 +374,183 @@ class AttendanceAnalyzer:
191
  if not filtered_records:
192
  return f"No attendance records found between {start_date} and {end_date}."
193
 
194
- # Create DataFrame for better presentation
195
  df = pd.DataFrame(filtered_records)
196
 
197
  # Summary statistics
198
  total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
199
- unique_people = df['name'].nunique()
200
  total_attendances = len(df)
 
201
 
202
- report = f"**Attendance Report ({start_date} to {end_date})**\n\n"
203
- report += f"πŸ“Š **Summary:**\n"
204
  report += f"β€’ Total Days: {total_days}\n"
205
- report += f"β€’ Unique People: {unique_people}\n"
206
- report += f"β€’ Total Attendances: {total_attendances}\n\n"
 
207
 
208
  # Individual attendance counts
209
  if not df.empty:
210
- attendance_counts = df['name'].value_counts()
211
- report += f"πŸ‘₯ **Individual Attendance:**\n"
212
- for name, count in attendance_counts.items():
213
- percentage = (count / total_days) * 100
214
- report += f"β€’ {name}: {count} days ({percentage:.1f}%)\n"
215
 
216
  return report
217
 
218
  def export_attendance_csv(self):
219
  """Export attendance records to CSV"""
220
- if not self.attendance_records:
221
- return None, "No attendance records to export."
222
-
223
- df = pd.DataFrame(self.attendance_records)
224
- csv_file = f"attendance_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
225
- df.to_csv(csv_file, index=False)
226
-
227
- return csv_file, f"βœ… Attendance exported to {csv_file}"
 
 
 
 
 
228
 
229
- # Initialize the attendance analyzer
230
- analyzer = AttendanceAnalyzer()
231
 
232
- # Create Gradio interface
233
  def create_interface():
234
  with gr.Blocks(
235
- title="Attendance Analyzer Using Face Detection",
236
  theme=gr.themes.Soft(),
237
  css="""
238
  .gradio-container {
239
- max-width: 1200px !important;
240
  }
241
  .tab-nav {
242
  font-weight: bold;
243
  }
 
 
 
 
 
244
  """
245
  ) as demo:
246
 
247
  gr.Markdown(
248
  """
249
- # πŸ‘€ Attendance Analyzer Using Face Detection
250
 
251
- **A comprehensive face recognition system for attendance management**
252
 
253
- πŸ“‹ **Features:**
254
- - Register new faces for attendance tracking
255
- - Automatic attendance marking via face detection
256
- - Real-time attendance reports and analytics
257
- - Export attendance data to CSV
 
 
 
 
 
 
 
258
  """
259
  )
260
 
261
  with gr.Tabs():
262
- # Face Registration Tab
263
- with gr.Tab("πŸ‘₯ Register Face", elem_classes="tab-nav"):
264
- gr.Markdown("### Register New Person for Attendance Tracking")
265
 
266
  with gr.Row():
267
  with gr.Column(scale=1):
268
- register_image = gr.Image(
269
- label="Upload Person's Photo",
270
- type="pil",
271
- height=300
272
  )
273
- register_name = gr.Textbox(
274
- label="Person's Name",
275
- placeholder="Enter full name...",
276
- lines=1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  )
278
- register_btn = gr.Button(
279
- "πŸ” Register Face",
280
- variant="primary",
281
- size="lg"
 
 
 
 
 
 
 
 
 
 
282
  )
283
 
284
  with gr.Column(scale=1):
285
- register_output = gr.Textbox(
286
- label="Registration Status",
287
- lines=3,
288
- interactive=False
289
  )
290
- registered_faces_info = gr.Markdown(
291
- value=analyzer.get_registered_faces_info(),
292
- label="Registered Faces"
 
293
  )
294
-
295
- register_btn.click(
296
- fn=analyzer.register_face,
297
- inputs=[register_image, register_name],
298
- outputs=[register_output, registered_faces_info]
299
- )
300
 
301
- # Attendance Marking Tab
302
- with gr.Tab("βœ… Mark Attendance", elem_classes="tab-nav"):
303
- gr.Markdown("### Mark Attendance via Face Recognition")
304
 
305
  with gr.Row():
306
  with gr.Column(scale=1):
307
- attendance_image = gr.Image(
308
- label="Upload Photo for Attendance",
309
  type="pil",
310
  height=300
311
  )
312
- mark_attendance_btn = gr.Button(
313
- "πŸ“‹ Mark Attendance",
 
 
 
 
 
314
  variant="primary",
315
  size="lg"
316
  )
317
 
318
  with gr.Column(scale=1):
319
- attendance_output = gr.Textbox(
320
- label="Attendance Status",
321
- lines=4,
322
  interactive=False
323
  )
324
- today_attendance = gr.Markdown(
325
- value=analyzer.get_today_attendance(),
326
- label="Today's Attendance"
327
  )
328
-
329
- mark_attendance_btn.click(
330
- fn=analyzer.mark_attendance,
331
- inputs=[attendance_image],
332
- outputs=[attendance_output, today_attendance]
333
- )
334
 
335
- # Reports Tab
336
  with gr.Tab("πŸ“Š Reports & Analytics", elem_classes="tab-nav"):
337
  gr.Markdown("### Attendance Reports and Data Export")
338
 
@@ -341,11 +559,11 @@ def create_interface():
341
  gr.Markdown("#### πŸ“… Generate Report")
342
  start_date = gr.Date(
343
  label="Start Date",
344
- value=date.today().replace(day=1).isoformat()
345
  )
346
  end_date = gr.Date(
347
  label="End Date",
348
- value=date.today().isoformat()
349
  )
350
  generate_report_btn = gr.Button(
351
  "πŸ“Š Generate Report",
@@ -372,77 +590,107 @@ def create_interface():
372
  value="Select date range and click 'Generate Report' to view attendance analytics.",
373
  label="Attendance Report"
374
  )
375
-
376
- generate_report_btn.click(
377
- fn=analyzer.get_attendance_report,
378
- inputs=[start_date, end_date],
379
- outputs=[report_output]
380
- )
381
-
382
- def export_and_show(analyzer=analyzer):
383
- file_path, status = analyzer.export_attendance_csv()
384
- if file_path:
385
- return status, gr.update(visible=True, value=file_path)
386
- else:
387
- return status, gr.update(visible=False)
388
-
389
- export_btn.click(
390
- fn=export_and_show,
391
- outputs=[export_status, export_file]
392
- )
393
 
394
- # Instructions Tab
395
- with gr.Tab("ℹ️ Instructions", elem_classes="tab-nav"):
396
  gr.Markdown(
397
  """
398
- ## πŸ“– How to Use the Attendance Analyzer
399
 
400
- ### 1. πŸ‘₯ Register Faces
401
- - Go to the **"Register Face"** tab
402
- - Upload a clear photo of the person (one face per image)
403
- - Enter the person's full name
404
- - Click **"Register Face"** to add them to the system
405
 
406
- ### 2. βœ… Mark Attendance
407
- - Go to the **"Mark Attendance"** tab
408
- - Upload a photo containing one or more registered faces
409
- - Click **"Mark Attendance"** to automatically detect and mark attendance
410
- - The system will show which people were recognized and marked present
411
 
412
- ### 3. πŸ“Š View Reports
413
- - Go to the **"Reports & Analytics"** tab
414
- - Select a date range to generate attendance reports
415
- - Export attendance data to CSV for external analysis
 
 
 
 
 
 
416
 
417
- ### πŸ’‘ Tips for Best Results:
418
- - Use high-quality, well-lit photos
419
- - Ensure faces are clearly visible and not obscured
420
- - For registration, use photos with the person looking directly at camera
421
- - The system works best with front-facing photos
422
- - Multiple people can be detected in a single attendance photo
423
 
424
- ### πŸ”§ Technical Features:
425
- - **Face Detection**: Automatically locates faces in images
426
- - **Face Recognition**: Matches detected faces with registered users
427
- - **Duplicate Prevention**: Prevents multiple attendance marks for the same person on the same day
428
- - **Data Persistence**: All data is saved and persists between sessions
429
- - **Export Capability**: Generate CSV reports for external analysis
430
 
431
- ### 🚨 Troubleshooting:
432
- - **"No face detected"**: Ensure the image contains a clear, visible face
433
- - **"Multiple faces detected"** (during registration): Use images with only one person
434
- - **"Unknown face"**: The person needs to be registered first
435
- - **Poor recognition**: Try re-registering with a clearer photo
436
  """
437
  )
438
 
439
- # Footer
440
- gr.Markdown(
441
- """
442
- ---
443
- **πŸ”’ Privacy Note:** All face data is processed locally and securely stored.
444
- No personal data is transmitted to external servers.
445
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  )
447
 
448
  return demo
@@ -454,5 +702,6 @@ if __name__ == "__main__":
454
  server_name="0.0.0.0",
455
  server_port=7860,
456
  share=False,
457
- show_error=True
 
458
  )
 
2
  import cv2
3
  import numpy as np
4
  import pandas as pd
5
+ from datetime import datetime, date, timedelta
6
  import face_recognition
7
  import pickle
8
  import os
 
10
  import base64
11
  from PIL import Image
12
  import json
13
+ import threading
14
+ import time
15
+ import queue
16
 
17
+ class AttendanceSystem:
18
  def __init__(self):
19
  self.known_face_encodings = []
20
  self.known_face_names = []
21
+ self.known_face_ids = []
22
  self.attendance_records = []
23
+ self.next_worker_id = 1
24
+ self.video_capture = None
25
+ self.is_streaming = False
26
+ self.frame_queue = queue.Queue(maxsize=2)
27
+ self.recognition_thread = None
28
+ self.last_recognition_time = {}
29
+ self.recognition_cooldown = 5 # seconds between recognitions for same person
30
+
31
+ # Create directories for data storage
32
+ os.makedirs("data", exist_ok=True)
33
+ os.makedirs("data/faces", exist_ok=True)
34
+
35
+ self.load_data()
36
+
37
+ def load_data(self):
38
+ """Load all stored data"""
39
+ try:
40
+ # Load face encodings and worker data
41
+ if os.path.exists("data/workers.pkl"):
42
+ with open("data/workers.pkl", "rb") as f:
43
  data = pickle.load(f)
44
  self.known_face_encodings = data.get("encodings", [])
45
  self.known_face_names = data.get("names", [])
46
+ self.known_face_ids = data.get("ids", [])
47
+ self.next_worker_id = data.get("next_id", 1)
48
+
49
+ # Load attendance records
50
+ if os.path.exists("data/attendance.json"):
51
+ with open("data/attendance.json", "r") as f:
 
52
  self.attendance_records = json.load(f)
53
+
54
+ except Exception as e:
55
+ print(f"Error loading data: {e}")
56
+ self.known_face_encodings = []
57
+ self.known_face_names = []
58
+ self.known_face_ids = []
59
+ self.attendance_records = []
60
+ self.next_worker_id = 1
 
 
 
61
 
62
+ def save_data(self):
63
+ """Save all data to files"""
64
+ try:
65
+ # Save worker data
66
+ worker_data = {
67
+ "encodings": self.known_face_encodings,
68
+ "names": self.known_face_names,
69
+ "ids": self.known_face_ids,
70
+ "next_id": self.next_worker_id
71
+ }
72
+ with open("data/workers.pkl", "wb") as f:
73
+ pickle.dump(worker_data, f)
74
+
75
+ # Save attendance records
76
+ with open("data/attendance.json", "w") as f:
77
+ json.dump(self.attendance_records, f, indent=2)
78
+
79
+ except Exception as e:
80
+ print(f"Error saving data: {e}")
81
 
82
+ def register_worker_manual(self, image, name):
83
+ """Manual worker registration"""
84
  if image is None or not name.strip():
85
+ return "❌ Please provide both image and name!", self.get_registered_workers_info()
86
 
87
  # Convert PIL image to RGB array
88
  if isinstance(image, Image.Image):
 
93
  face_encodings = face_recognition.face_encodings(image, face_locations)
94
 
95
  if len(face_encodings) == 0:
96
+ return "❌ No face detected in the image! Please try again with a clear face image.", self.get_registered_workers_info()
97
 
98
  if len(face_encodings) > 1:
99
+ return "❌ Multiple faces detected! Please upload an image with only one face.", self.get_registered_workers_info()
100
 
101
  # Check if person already exists
102
  name = name.strip().title()
103
  if name in self.known_face_names:
104
+ return f"❌ {name} is already registered!", self.get_registered_workers_info()
105
 
106
+ # Generate new worker ID
107
+ worker_id = f"W{self.next_worker_id:04d}"
108
+
109
+ # Add the face encoding, name, and ID
110
  self.known_face_encodings.append(face_encodings[0])
111
  self.known_face_names.append(name)
112
+ self.known_face_ids.append(worker_id)
113
+ self.next_worker_id += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
+ # Save face image
116
+ face_image = Image.fromarray(image)
117
+ face_image.save(f"data/faces/{worker_id}_{name.replace(' ', '_')}.jpg")
 
 
 
118
 
119
+ self.save_data()
 
 
120
 
121
+ return f"βœ… {name} has been successfully registered with ID: {worker_id}!", self.get_registered_workers_info()
122
+
123
+ def register_worker_auto(self, face_encoding, face_image):
124
+ """Automatic worker registration for unrecognized faces"""
125
+ try:
126
+ # Generate new worker ID and name
127
+ worker_id = f"W{self.next_worker_id:04d}"
128
+ worker_name = f"Unknown_Worker_{self.next_worker_id}"
129
+
130
+ # Add to database
131
+ self.known_face_encodings.append(face_encoding)
132
+ self.known_face_names.append(worker_name)
133
+ self.known_face_ids.append(worker_id)
134
+ self.next_worker_id += 1
135
+
136
+ # Save face image
137
+ face_pil = Image.fromarray(cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB))
138
+ face_pil.save(f"data/faces/{worker_id}_{worker_name}.jpg")
139
+
140
+ self.save_data()
141
+
142
+ return worker_id, worker_name
143
+
144
+ except Exception as e:
145
+ print(f"Error in auto registration: {e}")
146
+ return None, None
147
+
148
+ def mark_attendance(self, worker_id, worker_name):
149
+ """Mark attendance for a worker"""
150
+ try:
151
+ today = date.today().isoformat()
152
+ current_time = datetime.now()
153
+
154
+ # Check if already marked today
155
+ already_marked = any(
156
+ record["worker_id"] == worker_id and record["date"] == today
157
+ for record in self.attendance_records
158
+ )
159
+
160
+ if not already_marked:
161
+ # Mark attendance
162
+ self.attendance_records.append({
163
+ "worker_id": worker_id,
164
+ "name": worker_name,
165
+ "date": today,
166
+ "time": current_time.strftime("%H:%M:%S"),
167
+ "timestamp": current_time.isoformat(),
168
+ "status": "Present",
169
+ "method": "Auto"
170
+ })
171
+ self.save_data()
172
+ return True
173
+ return False
174
+
175
+ except Exception as e:
176
+ print(f"Error marking attendance: {e}")
177
+ return False
178
+
179
+ def process_video_frame(self, frame):
180
+ """Process a single video frame for face recognition"""
181
+ try:
182
+ # Resize frame for faster processing
183
+ small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
184
+ rgb_small_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB)
185
+
186
+ # Find faces in the frame
187
+ face_locations = face_recognition.face_locations(rgb_small_frame)
188
+ face_encodings = face_recognition.face_encodings(rgb_small_frame, face_locations)
189
 
190
+ current_time = time.time()
191
+
192
+ for (face_encoding, face_location) in zip(face_encodings, face_locations):
193
+ # Scale back up face locations
194
+ top, right, bottom, left = face_location
195
+ top *= 4
196
+ right *= 4
197
+ bottom *= 4
198
+ left *= 4
199
 
200
+ # Extract face image
201
+ face_image = frame[top:bottom, left:right]
202
+
203
+ # Compare with known faces
204
+ matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding, tolerance=0.6)
205
+ face_distances = face_recognition.face_distance(self.known_face_encodings, face_encoding)
206
+
207
+ worker_id = None
208
+ worker_name = "Unknown"
209
+ color = (0, 0, 255) # Red for unknown
210
+
211
+ if len(face_distances) > 0:
212
+ best_match_index = np.argmin(face_distances)
213
 
214
+ if matches[best_match_index] and face_distances[best_match_index] < 0.6:
215
+ # Known worker
216
+ worker_id = self.known_face_ids[best_match_index]
217
+ worker_name = self.known_face_names[best_match_index]
218
+ color = (0, 255, 0) # Green for known
219
+
220
+ # Check cooldown period
221
+ if worker_id not in self.last_recognition_time or \
222
+ current_time - self.last_recognition_time[worker_id] > self.recognition_cooldown:
223
+
224
+ # Mark attendance
225
+ if self.mark_attendance(worker_id, worker_name):
226
+ print(f"βœ… Attendance marked for {worker_name} ({worker_id})")
227
+
228
+ self.last_recognition_time[worker_id] = current_time
229
  else:
230
+ # Unknown face - auto register
231
+ if face_image.size > 0:
232
+ new_id, new_name = self.register_worker_auto(face_encoding, face_image)
233
+ if new_id:
234
+ worker_id = new_id
235
+ worker_name = new_name
236
+ color = (255, 165, 0) # Orange for newly registered
237
+ print(f"πŸ†• New worker registered: {new_name} ({new_id})")
238
+
239
+ # Mark attendance for new worker
240
+ self.mark_attendance(worker_id, worker_name)
241
+
242
+ # Draw rectangle and label
243
+ cv2.rectangle(frame, (left, top), (right, bottom), color, 2)
244
+ cv2.rectangle(frame, (left, bottom - 35), (right, bottom), color, cv2.FILLED)
245
+
246
+ label = f"{worker_name}"
247
+ if worker_id:
248
+ label += f" ({worker_id})"
249
+
250
+ cv2.putText(frame, label, (left + 6, bottom - 6),
251
+ cv2.FONT_HERSHEY_DUPLEX, 0.6, (255, 255, 255), 1)
252
+
253
+ return frame
254
+
255
+ except Exception as e:
256
+ print(f"Error processing frame: {e}")
257
+ return frame
258
+
259
+ def start_video_stream(self, camera_source=0):
260
+ """Start video streaming and recognition"""
261
+ try:
262
+ if self.is_streaming:
263
+ return "⚠️ Video stream is already running!"
264
+
265
+ self.video_capture = cv2.VideoCapture(camera_source)
266
+ if not self.video_capture.isOpened():
267
+ return "❌ Could not open camera/video source!"
268
+
269
+ self.is_streaming = True
270
+
271
+ def video_loop():
272
+ while self.is_streaming:
273
+ ret, frame = self.video_capture.read()
274
+ if not ret:
275
+ break
276
+
277
+ # Process frame for face recognition
278
+ processed_frame = self.process_video_frame(frame)
279
+
280
+ # Add to queue for display
281
+ if not self.frame_queue.full():
282
+ try:
283
+ self.frame_queue.put_nowait(processed_frame)
284
+ except queue.Full:
285
+ pass
286
+
287
+ time.sleep(0.1) # Limit processing rate
288
+
289
+ self.recognition_thread = threading.Thread(target=video_loop)
290
+ self.recognition_thread.daemon = True
291
+ self.recognition_thread.start()
292
+
293
+ return "βœ… Video stream started successfully!"
294
+
295
+ except Exception as e:
296
+ return f"❌ Error starting video stream: {e}"
297
+
298
+ def stop_video_stream(self):
299
+ """Stop video streaming"""
300
+ try:
301
+ self.is_streaming = False
302
+
303
+ if self.video_capture:
304
+ self.video_capture.release()
305
+ self.video_capture = None
306
+
307
+ if self.recognition_thread:
308
+ self.recognition_thread.join(timeout=2)
309
+
310
+ # Clear frame queue
311
+ while not self.frame_queue.empty():
312
+ try:
313
+ self.frame_queue.get_nowait()
314
+ except queue.Empty:
315
+ break
316
+
317
+ return "βœ… Video stream stopped successfully!"
318
+
319
+ except Exception as e:
320
+ return f"❌ Error stopping video stream: {e}"
321
 
322
+ def get_current_frame(self):
323
+ """Get current frame for display"""
324
+ try:
325
+ if not self.frame_queue.empty():
326
+ frame = self.frame_queue.get_nowait()
327
+ return frame
328
+ return None
329
+ except queue.Empty:
330
+ return None
331
+
332
+ def get_registered_workers_info(self):
333
+ """Get information about registered workers"""
334
  if not self.known_face_names:
335
+ return "No workers registered yet."
336
 
337
+ info = f"**Registered Workers ({len(self.known_face_names)}):**\n\n"
338
+ for i, (worker_id, name) in enumerate(zip(self.known_face_ids, self.known_face_names), 1):
339
+ info += f"{i}. **{name}** (ID: {worker_id})\n"
340
  return info
341
 
342
  def get_today_attendance(self):
 
345
  today_records = [r for r in self.attendance_records if r["date"] == today]
346
 
347
  if not today_records:
348
+ return f"**Today's Attendance ({today}):**\n\nNo attendance marked yet."
349
 
350
+ info = f"**Today's Attendance ({today}):**\n\n"
351
  for record in today_records:
352
+ method_icon = "πŸ€–" if record.get("method") == "Auto" else "πŸ‘€"
353
+ info += f"{method_icon} **{record['name']}** (ID: {record['worker_id']}) - {record['time']}\n"
354
 
355
  return info
356
 
 
359
  if not start_date or not end_date:
360
  return "Please select both start and end dates."
361
 
362
+ # Convert to string format if needed
363
+ if hasattr(start_date, 'strftime'):
364
+ start_date = start_date.strftime('%Y-%m-%d')
365
+ if hasattr(end_date, 'strftime'):
366
+ end_date = end_date.strftime('%Y-%m-%d')
367
+
368
  # Filter records by date range
369
  filtered_records = [
370
  r for r in self.attendance_records
 
374
  if not filtered_records:
375
  return f"No attendance records found between {start_date} and {end_date}."
376
 
377
+ # Create DataFrame for analysis
378
  df = pd.DataFrame(filtered_records)
379
 
380
  # Summary statistics
381
  total_days = (pd.to_datetime(end_date) - pd.to_datetime(start_date)).days + 1
382
+ unique_workers = df['worker_id'].nunique()
383
  total_attendances = len(df)
384
+ auto_registrations = len(df[df['method'] == 'Auto'])
385
 
386
+ report = f"**πŸ“Š Attendance Report ({start_date} to {end_date})**\n\n"
387
+ report += f"**Summary:**\n"
388
  report += f"β€’ Total Days: {total_days}\n"
389
+ report += f"β€’ Unique Workers: {unique_workers}\n"
390
+ report += f"β€’ Total Attendances: {total_attendances}\n"
391
+ report += f"β€’ Auto Detections: {auto_registrations}\n\n"
392
 
393
  # Individual attendance counts
394
  if not df.empty:
395
+ attendance_counts = df.groupby(['worker_id', 'name']).size().reset_index(name='count')
396
+ report += f"**πŸ‘₯ Individual Attendance:**\n"
397
+ for _, row in attendance_counts.iterrows():
398
+ percentage = (row['count'] / total_days) * 100
399
+ report += f"β€’ **{row['name']}** ({row['worker_id']}): {row['count']} days ({percentage:.1f}%)\n"
400
 
401
  return report
402
 
403
  def export_attendance_csv(self):
404
  """Export attendance records to CSV"""
405
+ try:
406
+ if not self.attendance_records:
407
+ return None, "No attendance records to export."
408
+
409
+ df = pd.DataFrame(self.attendance_records)
410
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
411
+ csv_file = f"attendance_report_{timestamp}.csv"
412
+ df.to_csv(csv_file, index=False)
413
+
414
+ return csv_file, f"βœ… Attendance exported to {csv_file}"
415
+
416
+ except Exception as e:
417
+ return None, f"❌ Error exporting data: {e}"
418
 
419
+ # Initialize the attendance system
420
+ attendance_system = AttendanceSystem()
421
 
 
422
  def create_interface():
423
  with gr.Blocks(
424
+ title="🎯 Advanced Attendance System with Live Recognition",
425
  theme=gr.themes.Soft(),
426
  css="""
427
  .gradio-container {
428
+ max-width: 1400px !important;
429
  }
430
  .tab-nav {
431
  font-weight: bold;
432
  }
433
+ .status-box {
434
+ padding: 10px;
435
+ border-radius: 5px;
436
+ margin: 5px 0;
437
+ }
438
  """
439
  ) as demo:
440
 
441
  gr.Markdown(
442
  """
443
+ # 🎯 Advanced Attendance System with Live Face Recognition
444
 
445
+ **Comprehensive facial recognition system with automatic worker registration and attendance tracking**
446
 
447
+ ## πŸš€ **Key Features:**
448
+ - **πŸŽ₯ Live Video Stream Recognition** - Real-time face detection from camera/CCTV
449
+ - **πŸ€– Automatic Worker Registration** - Auto-register unknown faces with unique IDs
450
+ - **πŸ‘€ Manual Registration** - Register workers manually with photos
451
+ - **πŸ“… 24-Hour Attendance Rule** - One attendance mark per worker per day
452
+ - **πŸ“Š Advanced Analytics** - Detailed reports and data export
453
+ - **πŸ’Ύ Persistent Data Storage** - All data saved locally in `/data` folder
454
+
455
+ ## πŸ“ **Data Storage Location:**
456
+ - **Worker Database:** `/data/workers.pkl`
457
+ - **Attendance Records:** `/data/attendance.json`
458
+ - **Face Images:** `/data/faces/` folder
459
  """
460
  )
461
 
462
  with gr.Tabs():
463
+ # Live Recognition Tab
464
+ with gr.Tab("πŸŽ₯ Live Recognition", elem_classes="tab-nav"):
465
+ gr.Markdown("### Real-time Face Recognition and Attendance")
466
 
467
  with gr.Row():
468
  with gr.Column(scale=1):
469
+ camera_source = gr.Number(
470
+ label="Camera Source (0 for default camera, or RTSP URL)",
471
+ value=0,
472
+ precision=0
473
  )
474
+
475
+ with gr.Row():
476
+ start_stream_btn = gr.Button(
477
+ "πŸŽ₯ Start Live Recognition",
478
+ variant="primary",
479
+ size="lg"
480
+ )
481
+ stop_stream_btn = gr.Button(
482
+ "⏹️ Stop Stream",
483
+ variant="secondary",
484
+ size="lg"
485
+ )
486
+
487
+ stream_status = gr.Textbox(
488
+ label="Stream Status",
489
+ value="Ready to start...",
490
+ interactive=False,
491
+ lines=2
492
  )
493
+
494
+ gr.Markdown(
495
+ """
496
+ **πŸ“‹ Instructions:**
497
+ 1. Click "Start Live Recognition" to begin
498
+ 2. System will automatically detect and register new faces
499
+ 3. Known workers will be marked present (once per day)
500
+ 4. New workers get auto-assigned IDs (W0001, W0002, etc.)
501
+
502
+ **🎨 Color Coding:**
503
+ - 🟒 **Green:** Known worker (attendance marked)
504
+ - 🟠 **Orange:** New worker (auto-registered)
505
+ - πŸ”΄ **Red:** Face detected but processing
506
+ """
507
  )
508
 
509
  with gr.Column(scale=1):
510
+ live_attendance_display = gr.Markdown(
511
+ value=attendance_system.get_today_attendance(),
512
+ label="Live Attendance Updates"
 
513
  )
514
+
515
+ refresh_attendance_btn = gr.Button(
516
+ "πŸ”„ Refresh Attendance",
517
+ variant="secondary"
518
  )
 
 
 
 
 
 
519
 
520
+ # Manual Registration Tab
521
+ with gr.Tab("πŸ‘€ Manual Registration", elem_classes="tab-nav"):
522
+ gr.Markdown("### Register Workers Manually")
523
 
524
  with gr.Row():
525
  with gr.Column(scale=1):
526
+ register_image = gr.Image(
527
+ label="Upload Worker's Photo",
528
  type="pil",
529
  height=300
530
  )
531
+ register_name = gr.Textbox(
532
+ label="Worker's Full Name",
533
+ placeholder="Enter full name...",
534
+ lines=1
535
+ )
536
+ register_btn = gr.Button(
537
+ "πŸ‘€ Register Worker",
538
  variant="primary",
539
  size="lg"
540
  )
541
 
542
  with gr.Column(scale=1):
543
+ register_output = gr.Textbox(
544
+ label="Registration Status",
545
+ lines=3,
546
  interactive=False
547
  )
548
+ registered_workers_info = gr.Markdown(
549
+ value=attendance_system.get_registered_workers_info(),
550
+ label="Registered Workers Database"
551
  )
 
 
 
 
 
 
552
 
553
+ # Reports & Analytics Tab
554
  with gr.Tab("πŸ“Š Reports & Analytics", elem_classes="tab-nav"):
555
  gr.Markdown("### Attendance Reports and Data Export")
556
 
 
559
  gr.Markdown("#### πŸ“… Generate Report")
560
  start_date = gr.Date(
561
  label="Start Date",
562
+ value=date.today().replace(day=1)
563
  )
564
  end_date = gr.Date(
565
  label="End Date",
566
+ value=date.today()
567
  )
568
  generate_report_btn = gr.Button(
569
  "πŸ“Š Generate Report",
 
590
  value="Select date range and click 'Generate Report' to view attendance analytics.",
591
  label="Attendance Report"
592
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
 
594
+ # System Info Tab
595
+ with gr.Tab("ℹ️ System Information", elem_classes="tab-nav"):
596
  gr.Markdown(
597
  """
598
+ ## πŸ“– System Guide
599
 
600
+ ### πŸŽ₯ Live Recognition System
601
+ - **Camera Setup:** Use camera index (0, 1, 2...) or RTSP URL for IP cameras
602
+ - **Auto Registration:** Unknown faces automatically get worker IDs (W0001, W0002...)
603
+ - **24-Hour Rule:** Each worker can only be marked present once per day
604
+ - **Real-time Processing:** Continuous face detection and recognition
605
 
606
+ ### πŸ‘€ Manual Registration
607
+ - Upload clear, front-facing photos for best results
608
+ - One face per image for registration
609
+ - Workers get unique IDs automatically assigned
 
610
 
611
+ ### πŸ“ Data Storage Structure
612
+ ```
613
+ /data/
614
+ β”œβ”€β”€ workers.pkl # Worker database (encodings, names, IDs)
615
+ β”œβ”€β”€ attendance.json # All attendance records
616
+ └── faces/ # Saved face images
617
+ β”œβ”€β”€ W0001_John_Doe.jpg
618
+ β”œβ”€β”€ W0002_Jane_Smith.jpg
619
+ └── ...
620
+ ```
621
 
622
+ ### πŸ”§ Technical Features
623
+ - **Face Recognition:** Uses state-of-the-art face_recognition library
624
+ - **Tolerance:** 0.6 threshold for face matching accuracy
625
+ - **Threading:** Separate threads for video processing and UI
626
+ - **Queue Management:** Efficient frame processing with queue system
627
+ - **Error Handling:** Robust error handling and recovery
628
 
629
+ ### 🚨 Troubleshooting
630
+ - **Camera Issues:** Check camera permissions and connections
631
+ - **Poor Recognition:** Ensure good lighting and clear face visibility
632
+ - **Performance:** Reduce video resolution for better performance
633
+ - **Storage:** Check disk space for face image storage
 
634
 
635
+ ### πŸ”’ Privacy & Security
636
+ - All data stored locally in `/data` folder
637
+ - No external API calls or data transmission
638
+ - Face images saved securely with worker IDs
639
+ - Attendance records in JSON format for easy backup
640
  """
641
  )
642
 
643
+ # Event handlers
644
+ start_stream_btn.click(
645
+ fn=attendance_system.start_video_stream,
646
+ inputs=[camera_source],
647
+ outputs=[stream_status]
648
+ )
649
+
650
+ stop_stream_btn.click(
651
+ fn=attendance_system.stop_video_stream,
652
+ outputs=[stream_status]
653
+ )
654
+
655
+ refresh_attendance_btn.click(
656
+ fn=attendance_system.get_today_attendance,
657
+ outputs=[live_attendance_display]
658
+ )
659
+
660
+ register_btn.click(
661
+ fn=attendance_system.register_worker_manual,
662
+ inputs=[register_image, register_name],
663
+ outputs=[register_output, registered_workers_info]
664
+ )
665
+
666
+ generate_report_btn.click(
667
+ fn=attendance_system.get_attendance_report,
668
+ inputs=[start_date, end_date],
669
+ outputs=[report_output]
670
+ )
671
+
672
+ def export_and_show():
673
+ file_path, status = attendance_system.export_attendance_csv()
674
+ if file_path:
675
+ return status, gr.update(visible=True, value=file_path)
676
+ else:
677
+ return status, gr.update(visible=False)
678
+
679
+ export_btn.click(
680
+ fn=export_and_show,
681
+ outputs=[export_status, export_file]
682
+ )
683
+
684
+ # Auto-refresh attendance every 10 seconds when streaming
685
+ def auto_refresh():
686
+ if attendance_system.is_streaming:
687
+ return attendance_system.get_today_attendance()
688
+ return gr.update()
689
+
690
+ demo.load(
691
+ fn=auto_refresh,
692
+ outputs=[live_attendance_display],
693
+ every=10
694
  )
695
 
696
  return demo
 
702
  server_name="0.0.0.0",
703
  server_port=7860,
704
  share=False,
705
+ show_error=True,
706
+ debug=True
707
  )