hari7261's picture
Update app.py
a609d26 verified
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)