from uniface import RetinaFace, ArcFace, compute_similarity import gradio as gr import cv2 import numpy as np import pandas as pd from datetime import datetime import os import pickle from pathlib import Path import warnings warnings.filterwarnings('ignore') # Initialize face detection and recognition models detector = RetinaFace() recognizer = ArcFace() # Create necessary directories DATA_DIR = Path("face_data") ATTENDANCE_DIR = Path("attendance_records") DATA_DIR.mkdir(exist_ok=True) ATTENDANCE_DIR.mkdir(exist_ok=True) # Database files FACE_DB_FILE = DATA_DIR / "face_database.pkl" USER_DB_FILE = DATA_DIR / "user_database.pkl" ATTENDANCE_FILE = ATTENDANCE_DIR / f"attendance_{datetime.now().strftime('%Y_%m_%d')}.csv" # Initialize databases if FACE_DB_FILE.exists(): with open(FACE_DB_FILE, 'rb') as f: face_database = pickle.load(f) else: face_database = {} if USER_DB_FILE.exists(): with open(USER_DB_FILE, 'rb') as f: user_database = pickle.load(f) else: user_database = {} # Initialize attendance file if not ATTENDANCE_FILE.exists(): df_attendance = pd.DataFrame(columns=['Timestamp', 'User_ID', 'Name', 'Roll_No', 'Status']) df_attendance.to_csv(ATTENDANCE_FILE, index=False) class FaceAttendanceSystem: def __init__(self): self.camera = None self.recognition_threshold = 0.4 self.current_frame = None def start_camera(self): if self.camera is None or not self.camera.isOpened(): self.camera = cv2.VideoCapture(0) if not self.camera.isOpened(): return False, "Cannot open camera" return True, "Camera started successfully" def stop_camera(self): if self.camera is not None: self.camera.release() self.camera = None return "Camera stopped" def capture_frame(self): if self.camera is None or not self.camera.isOpened(): return None ret, frame = self.camera.read() if ret: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) self.current_frame = frame_rgb return frame_rgb return None def register_face(self, name, roll_no, image): if image is None: return "No image provided", None if isinstance(image, np.ndarray): frame = image else: frame = np.array(image) try: faces = detector.detect(frame) if len(faces) == 0: return "No face detected", None elif len(faces) > 1: return "Multiple faces detected", None face = faces[0] landmarks = face['landmarks'] embedding = recognizer.get_normalized_embedding(frame, landmarks) if embedding is None or len(embedding) == 0: return "Could not extract face features", None # Check if this face is already registered if face_database: for existing_user_id, db_embedding in face_database.items(): similarity = compute_similarity(embedding, db_embedding) if similarity > self.recognition_threshold: # Face already exists in database existing_user = user_database.get(existing_user_id, {}) existing_name = existing_user.get('name', 'Unknown') existing_roll = existing_user.get('roll_no', 'Unknown') return f"⚠️ Already registered! This face matches:\n• User ID: {existing_user_id}\n• Name: {existing_name}\n• Roll No: {existing_roll}\n• Similarity: {similarity:.2%}", None user_id = f"USER_{len(user_database) + 1:03d}" face_database[user_id] = embedding user_database[user_id] = { 'name': name, 'roll_no': roll_no, 'registration_date': datetime.now().strftime("%Y-%m-%d %H:%M:%S") } with open(FACE_DB_FILE, 'wb') as f: pickle.dump(face_database, f) with open(USER_DB_FILE, 'wb') as f: pickle.dump(user_database, f) return f"✅ Successfully registered {name} (Roll No: {roll_no}) as {user_id}", user_id except Exception as e: return f"Error: {str(e)}", None def recognize_face(self, frame): if not face_database: return frame, "No registered users", [] try: faces = detector.detect(frame) if len(faces) == 0: return frame, "No face detected", [] frame_with_boxes = frame.copy() recognized_users = [] for face in faces: bbox = face['bbox'] landmarks = face['landmarks'] x1, y1, x2, y2 = bbox cv2.rectangle(frame_with_boxes, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) embedding = recognizer.get_normalized_embedding(frame, landmarks) if embedding is not None: best_match = None best_score = 0 for user_id, db_embedding in face_database.items(): similarity = compute_similarity(embedding, db_embedding) if similarity > best_score and similarity > self.recognition_threshold: best_score = similarity best_match = user_id if best_match: user_info = user_database[best_match] attendance_status = self.mark_attendance(best_match) recognized_users.append({ 'user_id': best_match, 'name': user_info['name'], 'roll_no': user_info['roll_no'], 'confidence': best_score, 'attendance_status': attendance_status }) cv2.putText(frame_with_boxes, f"{user_info['name']}", (int(x1), int(y1)-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.putText(frame_with_boxes, f"{best_score:.2f}", (int(x1), int(y2)+20), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1) if recognized_users: status = f"Recognized {len(recognized_users)} user(s)" else: status = "Unknown user" return frame_with_boxes, status, recognized_users except Exception as e: return frame, f"Error: {str(e)}", [] def mark_attendance(self, user_id): """Mark attendance and return status: 'already_present', 'marked', or 'error'""" current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") user_info = user_database.get(user_id) if not user_info: return 'error' df = pd.read_csv(ATTENDANCE_FILE) today = datetime.now().strftime("%Y-%m-%d") roll_no = user_info['roll_no'] # Check if this user_id OR this roll_no already has attendance today # This prevents duplicate attendance even if same person registered multiple times already_marked = df[ ((df['User_ID'] == user_id) | (df['Roll_No'] == roll_no)) & (df['Timestamp'].str.startswith(today)) ].shape[0] > 0 if already_marked: return 'already_present' new_entry = { 'Timestamp': current_time, 'User_ID': user_id, 'Name': user_info['name'], 'Roll_No': user_info['roll_no'], 'Status': 'Present' } df = pd.concat([df, pd.DataFrame([new_entry])], ignore_index=True) df.to_csv(ATTENDANCE_FILE, index=False) user_database[user_id]['last_attendance'] = current_time with open(USER_DB_FILE, 'wb') as f: pickle.dump(user_database, f) return 'marked' def get_attendance_report(self): if ATTENDANCE_FILE.exists(): df = pd.read_csv(ATTENDANCE_FILE) return df return pd.DataFrame() def get_registered_users(self): users_list = [] for user_id, info in user_database.items(): users_list.append({ 'User ID': user_id, 'Name': info['name'], 'Roll No': info['roll_no'], 'Registered On': info.get('registration_date', 'N/A'), 'Last Attendance': info.get('last_attendance', 'Never') }) return pd.DataFrame(users_list) system = FaceAttendanceSystem() def start_camera_interface(): success, message = system.start_camera() return message def capture_and_register(name, roll_no): if not name or not roll_no: return "Please provide both name and roll number", None frame = system.capture_frame() if frame is None: return "Camera not started", None message, user_id = system.register_face(name, roll_no, frame) try: faces = detector.detect(frame) if faces: bbox = faces[0]['bbox'] x1, y1, x2, y2 = bbox frame_with_box = frame.copy() cv2.rectangle(frame_with_box, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) return message, frame_with_box except: pass return message, frame def detect_and_recognize(): import random # Funny quotes for unknown/absent users absent_quotes = [ "🤔 Who dis? Register first, ghostie! 👻", "🕵️ Face not found in our database... Are you a spy? 🔍", "❓ Unknown face detected! Are you lost, traveler? 🧭", "🎭 Nice try, mystery person! Register to join the club! 🎪", "👽 Alien detected! Please register on Earth first! 🌍", "🦸 Even superheroes need to register their secret identity! 🦹", "🤖 Face not recognized. Beep boop, please register! 🔌", ] frame = system.capture_frame() if frame is None: return None, "Camera not available", "" frame_with_boxes, status, recognized_users = system.recognize_face(frame) user_messages = [] for user in recognized_users: name = user['name'] roll_no = user['roll_no'] confidence = user['confidence'] attendance_status = user.get('attendance_status', 'error') if attendance_status == 'already_present': user_messages.append(f"😊 Welcome back, {name}!\n You're already marked PRESENT today. Chill! 🎉") elif attendance_status == 'marked': user_messages.append(f"✅ {name} ({roll_no})\n Attendance marked! Have a great day! 🌟") else: user_messages.append(f"⚠️ {name} - Error marking attendance") if user_messages: return frame_with_boxes, status, "\n\n".join(user_messages) else: # Unknown user - show funny quote funny_quote = random.choice(absent_quotes) return frame_with_boxes, "Unknown Face 👀", funny_quote def get_todays_attendance(): df = system.get_attendance_report() if df.empty: return pd.DataFrame({'Message': ['No attendance records for today']}) return df def get_all_users(): df = system.get_registered_users() if df.empty: return pd.DataFrame({'Message': ['No users registered yet']}) return df def get_all_attendance_files(): """Get all attendance CSV files""" all_data = [] for file in ATTENDANCE_DIR.glob("attendance_*.csv"): try: df = pd.read_csv(file) all_data.append(df) except: pass if all_data: return pd.concat(all_data, ignore_index=True) return pd.DataFrame() def filter_attendance_by_date(filter_type, start_date, end_date): """Filter attendance records by date range""" df = get_all_attendance_files() if df.empty: return pd.DataFrame({'Message': ['No attendance records found']}), "No records found" # Parse timestamps df['Date'] = pd.to_datetime(df['Timestamp']).dt.date today = datetime.now().date() if filter_type == "Today": filtered = df[df['Date'] == today] date_info = f"📅 Today: {today.strftime('%d %b %Y')}" elif filter_type == "This Week": week_start = today - pd.Timedelta(days=today.weekday()) filtered = df[df['Date'] >= week_start] date_info = f"📅 This Week: {week_start.strftime('%d %b')} - {today.strftime('%d %b %Y')}" elif filter_type == "This Month": month_start = today.replace(day=1) filtered = df[df['Date'] >= month_start] date_info = f"📅 This Month: {month_start.strftime('%d %b')} - {today.strftime('%d %b %Y')}" elif filter_type == "Custom Range": if start_date and end_date: start = pd.to_datetime(start_date).date() end = pd.to_datetime(end_date).date() filtered = df[(df['Date'] >= start) & (df['Date'] <= end)] date_info = f"📅 Custom: {start.strftime('%d %b %Y')} - {end.strftime('%d %b %Y')}" else: return pd.DataFrame({'Message': ['Please select both start and end dates']}), "⚠️ Select date range" else: # All Time filtered = df if not df.empty: min_date = df['Date'].min() max_date = df['Date'].max() date_info = f"📅 All Time: {min_date.strftime('%d %b %Y')} - {max_date.strftime('%d %b %Y')}" else: date_info = "📅 All Time" if filtered.empty: return pd.DataFrame({'Message': ['No records found for selected period']}), f"{date_info} | 0 records" # Drop the temporary Date column and return result = filtered.drop(columns=['Date']) record_count = len(result) unique_users = result['User_ID'].nunique() if 'User_ID' in result.columns else 0 return result, f"{date_info} | {record_count} records | {unique_users} unique users" def download_report(filter_type, start_date, end_date): """Generate downloadable CSV report""" df, _ = filter_attendance_by_date(filter_type, start_date, end_date) if 'Message' in df.columns: return None # Create download file timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"attendance_report_{filter_type.lower().replace(' ', '_')}_{timestamp}.csv" filepath = ATTENDANCE_DIR / filename df.to_csv(filepath, index=False) return str(filepath) def get_attendance_summary(filter_type, start_date, end_date): """Get summary statistics""" df, info = filter_attendance_by_date(filter_type, start_date, end_date) if 'Message' in df.columns: return df, info, pd.DataFrame({'Summary': ['No data available']}) # Create summary summary_data = { 'Metric': ['Total Records', 'Unique Users', 'Present Count'], 'Value': [ len(df), df['User_ID'].nunique() if 'User_ID' in df.columns else 0, len(df[df['Status'] == 'Present']) if 'Status' in df.columns else len(df) ] } summary_df = pd.DataFrame(summary_data) return df, info, summary_df # Comprehensive Responsive CSS custom_css = """ /* Base styles */ .gradio-container { max-width: 1400px !important; margin: 0 auto !important; padding: 20px !important; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; } /* Header styling */ .main-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-size: 2.5rem !important; font-weight: 700 !important; text-align: center; margin-bottom: 10px !important; } .sub-header { text-align: center; color: #6c757d; font-size: 1.1rem !important; margin-bottom: 20px !important; } /* Tab styling */ .tabs { border-radius: 15px !important; overflow: hidden !important; } /* Button styling */ button.primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border: none !important; border-radius: 12px !important; padding: 12px 24px !important; font-weight: 600 !important; transition: all 0.3s ease !important; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important; } button.primary:hover { transform: translateY(-2px) !important; box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important; } button.secondary { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%) !important; border: none !important; border-radius: 12px !important; padding: 12px 24px !important; font-weight: 600 !important; transition: all 0.3s ease !important; } /* Card-like sections */ .gr-form, .gr-box { background: #ffffff !important; border-radius: 16px !important; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important; padding: 20px !important; margin-bottom: 15px !important; } /* Input styling */ input, textarea { border-radius: 10px !important; border: 2px solid #e9ecef !important; padding: 12px !important; transition: border-color 0.3s ease !important; } input:focus, textarea:focus { border-color: #667eea !important; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important; } /* Image container */ .image-container img { border-radius: 12px !important; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important; } /* Dataframe styling */ .dataframe { border-radius: 12px !important; overflow: hidden !important; } /* Status box styling */ .status-success { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%) !important; color: white !important; padding: 15px !important; border-radius: 12px !important; } /* Mobile First - Base styles (320px and up) */ @media (max-width: 480px) { .gradio-container { padding: 10px !important; } .main-header { font-size: 1.5rem !important; } .sub-header { font-size: 0.9rem !important; } button { width: 100% !important; margin-bottom: 10px !important; padding: 14px 20px !important; font-size: 16px !important; min-height: 50px !important; } .gr-form, .gr-box { padding: 12px !important; margin: 8px 0 !important; } input, textarea { font-size: 16px !important; padding: 14px !important; } .gr-row { flex-direction: column !important; } .gr-column { width: 100% !important; min-width: 100% !important; } } /* Tablet styles (481px to 768px) */ @media (min-width: 481px) and (max-width: 768px) { .gradio-container { padding: 15px !important; } .main-header { font-size: 2rem !important; } button { padding: 12px 20px !important; min-height: 48px !important; } .gr-row { flex-wrap: wrap !important; } .gr-column { min-width: 45% !important; } } /* Small desktop (769px to 1024px) */ @media (min-width: 769px) and (max-width: 1024px) { .gradio-container { padding: 20px !important; max-width: 95% !important; } } /* Large desktop (1025px and up) */ @media (min-width: 1025px) { .gradio-container { padding: 30px !important; } .main-header { font-size: 2.8rem !important; } } /* Animation for buttons */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.02); } 100% { transform: scale(1); } } button.primary:active { animation: pulse 0.3s ease; } /* Dark mode support */ @media (prefers-color-scheme: dark) { .gr-form, .gr-box { background: #1a1a2e !important; } } """ # Create Gradio interface - compatible with different versions demo = None try: # Try newest Gradio with all features demo = gr.Blocks(title="Face Attendance System", theme=gr.themes.Soft(), css=custom_css) except (TypeError, AttributeError): try: # Try without theme demo = gr.Blocks(title="Face Attendance System", css=custom_css) except TypeError: try: # Try without css demo = gr.Blocks(title="Face Attendance System") except TypeError: # Oldest version - no parameters demo = gr.Blocks() with demo: # Header gr.Markdown("# 🎯 Face Recognition Attendance System") gr.Markdown("### Powered by UniFace (RetinaFace + ArcFace)") # Camera Tab with gr.Tab("📷 Camera"): gr.Markdown("## 📷 Camera Control") with gr.Row(): start_btn = gr.Button("▶️ Start Camera", variant="primary") stop_btn = gr.Button("⏹️ Stop Camera", variant="secondary") refresh_btn = gr.Button("🔄 Refresh Preview", variant="secondary") camera_status = gr.Textbox( label="📡 Status", interactive=False, value="📷 Camera auto-started! Click 'Refresh Preview' to see the feed." ) preview = gr.Image(label="📺 Live Preview", interactive=False) # Register Tab with gr.Tab("📝 Register"): gr.Markdown("## 📝 Register New User") gr.Markdown("*Fill in details and capture your face to register*") with gr.Row(): with gr.Column(): gr.Markdown("### 📋 User Details") name_input = gr.Textbox( label="👤 Full Name", placeholder="Enter your full name..." ) roll_no_input = gr.Textbox( label="🔢 Roll Number / ID", placeholder="Enter roll number or ID..." ) register_btn = gr.Button("📸 Capture & Register", variant="primary") with gr.Column(): gr.Markdown("### 📊 Registration Status") registration_output = gr.Textbox( label="ℹ️ Status", interactive=False, lines=5 ) registration_image = gr.Image( label="📸 Captured Face", interactive=False ) # Attendance Tab with gr.Tab("✅ Attendance"): gr.Markdown("## ✅ Mark Your Attendance") gr.Markdown("*Click the button below to detect your face and mark attendance*") detect_btn = gr.Button("🔍 Detect & Mark Attendance", variant="primary") with gr.Row(): with gr.Column(): detection_image = gr.Image( label="📸 Detection Result", interactive=False ) with gr.Column(): detection_status = gr.Textbox( label="📡 Status", interactive=False ) recognized_users = gr.Textbox( label="🎯 Result", lines=8, interactive=False ) # Reports Tab with gr.Tab("📊 Reports"): gr.Markdown("## 📊 Attendance Reports & Analytics") # Filter Section with gr.Row(): with gr.Column(): gr.Markdown("### 📅 Select Date Range") filter_type = gr.Radio( choices=["Today", "This Week", "This Month", "Custom Range", "All Time"], value="Today", label="Filter Period" ) with gr.Column(): gr.Markdown("### 📆 Custom Date Range") start_date = gr.Textbox( label="Start Date", placeholder="YYYY-MM-DD (e.g., 2024-01-01)" ) end_date = gr.Textbox( label="End Date", placeholder="YYYY-MM-DD (e.g., 2024-12-31)" ) # Action Buttons gr.Markdown("### 🎛️ Actions") with gr.Row(): filter_btn = gr.Button("🔍 Apply Filter", variant="primary") users_btn = gr.Button("👥 All Users", variant="secondary") download_btn = gr.Button("📥 Download CSV", variant="secondary") # Report Info report_info = gr.Textbox( label="📊 Report Summary", interactive=False, value="👆 Select a filter and click 'Apply Filter' to view records" ) # Summary Stats gr.Markdown("### 📈 Quick Stats") summary_output = gr.Dataframe( label="Statistics", interactive=False ) # Main Report Table gr.Markdown("### 📋 Attendance Records") report_output = gr.Dataframe( label="Records", interactive=False ) # Download Section download_file = gr.File(label="📥 Downloaded Report") # Event Handlers start_btn.click(start_camera_interface, inputs=None, outputs=camera_status) stop_btn.click(system.stop_camera, inputs=None, outputs=camera_status) # Camera preview refresh function def refresh_preview(): frame = system.capture_frame() if frame is not None: return frame return None refresh_btn.click(refresh_preview, inputs=None, outputs=preview) register_btn.click( capture_and_register, inputs=[name_input, roll_no_input], outputs=[registration_output, registration_image] ) detect_btn.click( detect_and_recognize, inputs=None, outputs=[detection_image, detection_status, recognized_users] ) # Report filter handler def apply_filter(filter_type, start_date, end_date): df, info, summary = get_attendance_summary(filter_type, start_date, end_date) return df, info, summary filter_btn.click( apply_filter, inputs=[filter_type, start_date, end_date], outputs=[report_output, report_info, summary_output] ) users_btn.click(get_all_users, inputs=None, outputs=report_output) download_btn.click( download_report, inputs=[filter_type, start_date, end_date], outputs=download_file ) if __name__ == "__main__": print("=" * 50) print("🎯 Face Recognition Attendance System") print("=" * 50) print("📦 Models: RetinaFace (detection) + ArcFace (recognition)") print("📷 Camera: Auto-starting...") system.start_camera() print("✅ Camera ready!") print("🌐 Launching web interface...") print("=" * 50) demo.launch(share=False, server_name="0.0.0.0", server_port=7860)