|
|
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') |
|
|
|
|
|
|
|
|
detector = RetinaFace() |
|
|
recognizer = ArcFace() |
|
|
|
|
|
|
|
|
DATA_DIR = Path("face_data") |
|
|
ATTENDANCE_DIR = Path("attendance_records") |
|
|
DATA_DIR.mkdir(exist_ok=True) |
|
|
ATTENDANCE_DIR.mkdir(exist_ok=True) |
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
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 = {} |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
if face_database: |
|
|
for existing_user_id, db_embedding in face_database.items(): |
|
|
similarity = compute_similarity(embedding, db_embedding) |
|
|
if similarity > self.recognition_threshold: |
|
|
|
|
|
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'] |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
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: |
|
|
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" |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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']}) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
demo = None |
|
|
try: |
|
|
|
|
|
demo = gr.Blocks(title="Face Attendance System", theme=gr.themes.Soft(), css=custom_css) |
|
|
except (TypeError, AttributeError): |
|
|
try: |
|
|
|
|
|
demo = gr.Blocks(title="Face Attendance System", css=custom_css) |
|
|
except TypeError: |
|
|
try: |
|
|
|
|
|
demo = gr.Blocks(title="Face Attendance System") |
|
|
except TypeError: |
|
|
|
|
|
demo = gr.Blocks() |
|
|
|
|
|
with demo: |
|
|
|
|
|
gr.Markdown("# π― Face Recognition Attendance System") |
|
|
gr.Markdown("### Powered by UniFace (RetinaFace + ArcFace)") |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("π Reports"): |
|
|
gr.Markdown("## π Attendance Reports & Analytics") |
|
|
|
|
|
|
|
|
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)" |
|
|
) |
|
|
|
|
|
|
|
|
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 = gr.Textbox( |
|
|
label="π Report Summary", |
|
|
interactive=False, |
|
|
value="π Select a filter and click 'Apply Filter' to view records" |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### π Quick Stats") |
|
|
summary_output = gr.Dataframe( |
|
|
label="Statistics", |
|
|
interactive=False |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### π Attendance Records") |
|
|
report_output = gr.Dataframe( |
|
|
label="Records", |
|
|
interactive=False |
|
|
) |
|
|
|
|
|
|
|
|
download_file = gr.File(label="π₯ Downloaded Report") |
|
|
|
|
|
|
|
|
start_btn.click(start_camera_interface, inputs=None, outputs=camera_status) |
|
|
stop_btn.click(system.stop_camera, inputs=None, outputs=camera_status) |
|
|
|
|
|
|
|
|
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] |
|
|
) |
|
|
|
|
|
|
|
|
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) |