Subh775 commited on
Commit
e858642
·
verified ·
1 Parent(s): cfc0824

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +399 -0
app.py CHANGED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import json
4
+ import base64
5
+ import sqlite3
6
+ from datetime import datetime, timedelta
7
+ import pytz
8
+ from flask import Flask, render_template, request, jsonify, Response
9
+ from deepface import DeepFace
10
+ import numpy as np
11
+ import threading
12
+ import logging
13
+ from werkzeug.utils import secure_filename
14
+
15
+ # Configure logging
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ app = Flask(__name__)
20
+ app.config['SECRET_KEY'] = 'your-secret-key-here'
21
+ app.config['UPLOAD_FOLDER'] = 'static/known_faces'
22
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
23
+
24
+ # Create upload directory if it doesn't exist
25
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
26
+
27
+ # Indian Standard Time
28
+ IST = pytz.timezone('Asia/Kolkata')
29
+
30
+ class DatabaseManager:
31
+ def __init__(self, db_path='attendance.db'):
32
+ self.db_path = db_path
33
+ self.init_database()
34
+
35
+ def init_database(self):
36
+ """Initialize the database with required tables"""
37
+ conn = sqlite3.connect(self.db_path)
38
+ cursor = conn.cursor()
39
+
40
+ # Users table
41
+ cursor.execute('''
42
+ CREATE TABLE IF NOT EXISTS users (
43
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
44
+ name TEXT NOT NULL UNIQUE,
45
+ face_encoding_path TEXT,
46
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
47
+ )
48
+ ''')
49
+
50
+ # Attendance table
51
+ cursor.execute('''
52
+ CREATE TABLE IF NOT EXISTS attendance (
53
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
54
+ user_id INTEGER,
55
+ date DATE,
56
+ check_in_time TIMESTAMP,
57
+ check_out_time TIMESTAMP,
58
+ status TEXT DEFAULT 'present',
59
+ FOREIGN KEY (user_id) REFERENCES users (id),
60
+ UNIQUE(user_id, date)
61
+ )
62
+ ''')
63
+
64
+ conn.commit()
65
+ conn.close()
66
+
67
+ def add_user(self, name, face_image_path):
68
+ """Add a new user to the database"""
69
+ try:
70
+ conn = sqlite3.connect(self.db_path)
71
+ cursor = conn.cursor()
72
+ cursor.execute('INSERT INTO users (name, face_encoding_path) VALUES (?, ?)',
73
+ (name, face_image_path))
74
+ user_id = cursor.lastrowid
75
+ conn.commit()
76
+ conn.close()
77
+ return user_id
78
+ except sqlite3.IntegrityError:
79
+ return None
80
+
81
+ def get_all_users(self):
82
+ """Get all users from database"""
83
+ conn = sqlite3.connect(self.db_path)
84
+ cursor = conn.cursor()
85
+ cursor.execute('SELECT id, name, face_encoding_path FROM users')
86
+ users = cursor.fetchall()
87
+ conn.close()
88
+ return users
89
+
90
+ def mark_attendance(self, user_id, attendance_type='check_in'):
91
+ """Mark attendance for a user"""
92
+ try:
93
+ conn = sqlite3.connect(self.db_path)
94
+ cursor = conn.cursor()
95
+
96
+ # Get current date and time in IST
97
+ now = datetime.now(IST)
98
+ today = now.date()
99
+ current_time = now
100
+
101
+ if attendance_type == 'check_in':
102
+ # Check if user already checked in today
103
+ cursor.execute('''
104
+ SELECT id, check_in_time FROM attendance
105
+ WHERE user_id = ? AND date = ?
106
+ ''', (user_id, today))
107
+ existing = cursor.fetchone()
108
+
109
+ if existing:
110
+ conn.close()
111
+ return {'success': False, 'message': 'Already checked in today'}
112
+
113
+ # Insert new attendance record
114
+ cursor.execute('''
115
+ INSERT INTO attendance (user_id, date, check_in_time, status)
116
+ VALUES (?, ?, ?, 'present')
117
+ ''', (user_id, today, current_time))
118
+
119
+ elif attendance_type == 'check_out':
120
+ # Update existing record with check-out time
121
+ cursor.execute('''
122
+ UPDATE attendance
123
+ SET check_out_time = ?
124
+ WHERE user_id = ? AND date = ? AND check_out_time IS NULL
125
+ ''', (current_time, user_id, today))
126
+
127
+ if cursor.rowcount == 0:
128
+ conn.close()
129
+ return {'success': False, 'message': 'No check-in record found or already checked out'}
130
+
131
+ conn.commit()
132
+ conn.close()
133
+ return {'success': True, 'message': f'Successfully {attendance_type.replace("_", "-")}'}
134
+
135
+ except Exception as e:
136
+ logger.error(f"Error marking attendance: {e}")
137
+ return {'success': False, 'message': 'Database error'}
138
+
139
+ def get_today_stats(self):
140
+ """Get today's attendance statistics"""
141
+ conn = sqlite3.connect(self.db_path)
142
+ cursor = conn.cursor()
143
+
144
+ today = datetime.now(IST).date()
145
+
146
+ # Total users
147
+ cursor.execute('SELECT COUNT(*) FROM users')
148
+ total_users = cursor.fetchone()[0]
149
+
150
+ # Present today (checked in)
151
+ cursor.execute('''
152
+ SELECT COUNT(*) FROM attendance
153
+ WHERE date = ? AND check_in_time IS NOT NULL
154
+ ''', (today,))
155
+ present_today = cursor.fetchone()[0]
156
+
157
+ # Absent today
158
+ absent_today = total_users - present_today
159
+
160
+ # Get today's attendance records with user names
161
+ cursor.execute('''
162
+ SELECT u.name, a.check_in_time, a.check_out_time
163
+ FROM attendance a
164
+ JOIN users u ON a.user_id = u.id
165
+ WHERE a.date = ?
166
+ ORDER BY a.check_in_time DESC
167
+ ''', (today,))
168
+ today_attendance = cursor.fetchall()
169
+
170
+ conn.close()
171
+
172
+ return {
173
+ 'total_users': total_users,
174
+ 'present_today': present_today,
175
+ 'absent_today': absent_today,
176
+ 'today_attendance': today_attendance
177
+ }
178
+
179
+ class FaceRecognizer:
180
+ def __init__(self, known_faces_dir='static/known_faces'):
181
+ self.known_faces_dir = known_faces_dir
182
+ self.known_faces = {}
183
+ self.load_known_faces()
184
+
185
+ def load_known_faces(self):
186
+ """Load known faces from the directory"""
187
+ self.known_faces = {}
188
+ if not os.path.exists(self.known_faces_dir):
189
+ os.makedirs(self.known_faces_dir)
190
+ return
191
+
192
+ db_manager = DatabaseManager()
193
+ users = db_manager.get_all_users()
194
+
195
+ for user_id, name, face_path in users:
196
+ full_path = os.path.join(self.known_faces_dir, face_path) if face_path else None
197
+ if full_path and os.path.exists(full_path):
198
+ self.known_faces[name] = {
199
+ 'user_id': user_id,
200
+ 'image_path': full_path
201
+ }
202
+
203
+ logger.info(f"Loaded {len(self.known_faces)} known faces")
204
+
205
+ def recognize_face(self, frame):
206
+ """Recognize face in the given frame"""
207
+ try:
208
+ if not self.known_faces:
209
+ return None, 0
210
+
211
+ # Convert frame to RGB for DeepFace
212
+ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
213
+
214
+ # Save temporary frame for DeepFace
215
+ temp_path = 'temp_frame.jpg'
216
+ cv2.imwrite(temp_path, frame)
217
+
218
+ best_match = None
219
+ highest_confidence = 0
220
+
221
+ for name, face_data in self.known_faces.items():
222
+ try:
223
+ # Use DeepFace to verify faces
224
+ result = DeepFace.verify(
225
+ img1_path=temp_path,
226
+ img2_path=face_data['image_path'],
227
+ model_name='VGG-Face',
228
+ distance_metric='cosine',
229
+ enforce_detection=False
230
+ )
231
+
232
+ if result['verified']:
233
+ confidence = (1 - result['distance']) * 100
234
+ if confidence > highest_confidence and confidence > 60: # 60% threshold
235
+ highest_confidence = confidence
236
+ best_match = {
237
+ 'name': name,
238
+ 'user_id': face_data['user_id'],
239
+ 'confidence': confidence
240
+ }
241
+
242
+ except Exception as e:
243
+ logger.debug(f"Recognition error for {name}: {e}")
244
+ continue
245
+
246
+ # Clean up temp file
247
+ if os.path.exists(temp_path):
248
+ os.remove(temp_path)
249
+
250
+ if best_match:
251
+ return best_match, highest_confidence
252
+
253
+ return None, 0
254
+
255
+ except Exception as e:
256
+ logger.error(f"Face recognition error: {e}")
257
+ return None, 0
258
+
259
+ # Global instances
260
+ db_manager = DatabaseManager()
261
+ face_recognizer = FaceRecognizer()
262
+
263
+ @app.route('/')
264
+ def index():
265
+ """Main page"""
266
+ stats = db_manager.get_today_stats()
267
+ return render_template('index.html', stats=stats)
268
+
269
+ @app.route('/api/stats')
270
+ def get_stats():
271
+ """Get current statistics"""
272
+ stats = db_manager.get_today_stats()
273
+
274
+ # Format attendance records for display
275
+ formatted_attendance = []
276
+ for name, check_in, check_out in stats['today_attendance']:
277
+ record = {
278
+ 'name': name,
279
+ 'check_in': None,
280
+ 'check_out': None
281
+ }
282
+
283
+ if check_in:
284
+ check_in_dt = datetime.fromisoformat(check_in.replace('Z', '+00:00'))
285
+ if check_in_dt.tzinfo is None:
286
+ check_in_dt = pytz.utc.localize(check_in_dt)
287
+ check_in_ist = check_in_dt.astimezone(IST)
288
+ record['check_in'] = check_in_ist.strftime('%I:%M %p')
289
+
290
+ if check_out:
291
+ check_out_dt = datetime.fromisoformat(check_out.replace('Z', '+00:00'))
292
+ if check_out_dt.tzinfo is None:
293
+ check_out_dt = pytz.utc.localize(check_out_dt)
294
+ check_out_ist = check_out_dt.astimezone(IST)
295
+ record['check_out'] = check_out_ist.strftime('%I:%M %p')
296
+
297
+ formatted_attendance.append(record)
298
+
299
+ stats['today_attendance'] = formatted_attendance
300
+ return jsonify(stats)
301
+
302
+ @app.route('/api/add_user', methods=['POST'])
303
+ def add_user():
304
+ """Add a new user with face image"""
305
+ try:
306
+ name = request.form.get('name')
307
+ if not name:
308
+ return jsonify({'success': False, 'message': 'Name is required'})
309
+
310
+ if 'face_image' not in request.files:
311
+ return jsonify({'success': False, 'message': 'Face image is required'})
312
+
313
+ file = request.files['face_image']
314
+ if file.filename == '':
315
+ return jsonify({'success': False, 'message': 'No file selected'})
316
+
317
+ # Save the uploaded file
318
+ filename = secure_filename(f"{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg")
319
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
320
+ file.save(filepath)
321
+
322
+ # Add user to database
323
+ user_id = db_manager.add_user(name, filename)
324
+ if user_id:
325
+ # Reload known faces
326
+ face_recognizer.load_known_faces()
327
+ return jsonify({'success': True, 'message': f'User {name} added successfully'})
328
+ else:
329
+ # Remove the uploaded file if database insertion failed
330
+ if os.path.exists(filepath):
331
+ os.remove(filepath)
332
+ return jsonify({'success': False, 'message': 'User already exists'})
333
+
334
+ except Exception as e:
335
+ logger.error(f"Error adding user: {e}")
336
+ return jsonify({'success': False, 'message': 'Server error'})
337
+
338
+ @app.route('/api/recognize', methods=['POST'])
339
+ def recognize_and_mark():
340
+ """Recognize face and mark attendance"""
341
+ try:
342
+ data = request.get_json()
343
+ image_data = data.get('image')
344
+ attendance_type = data.get('type', 'check_in') # 'check_in' or 'check_out'
345
+
346
+ if not image_data:
347
+ return jsonify({'success': False, 'message': 'No image data provided'})
348
+
349
+ # Decode base64 image
350
+ image_data = image_data.split(',')[1] # Remove data:image/jpeg;base64,
351
+ image_bytes = base64.b64decode(image_data)
352
+
353
+ # Convert to numpy array
354
+ nparr = np.frombuffer(image_bytes, np.uint8)
355
+ frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
356
+
357
+ # Recognize face
358
+ result, confidence = face_recognizer.recognize_face(frame)
359
+
360
+ if result and confidence > 70: # Higher threshold for attendance marking
361
+ # Mark attendance
362
+ attendance_result = db_manager.mark_attendance(result['user_id'], attendance_type)
363
+
364
+ if attendance_result['success']:
365
+ current_time = datetime.now(IST).strftime('%I:%M %p')
366
+ return jsonify({
367
+ 'success': True,
368
+ 'name': result['name'],
369
+ 'confidence': round(confidence, 2),
370
+ 'time': current_time,
371
+ 'type': attendance_type,
372
+ 'message': attendance_result['message']
373
+ })
374
+ else:
375
+ return jsonify({
376
+ 'success': False,
377
+ 'name': result['name'],
378
+ 'confidence': round(confidence, 2),
379
+ 'message': attendance_result['message']
380
+ })
381
+ else:
382
+ return jsonify({'success': False, 'message': 'Face not recognized or confidence too low'})
383
+
384
+ except Exception as e:
385
+ logger.error(f"Recognition error: {e}")
386
+ return jsonify({'success': False, 'message': 'Recognition failed'})
387
+
388
+ @app.route('/api/reload_faces')
389
+ def reload_faces():
390
+ """Reload known faces"""
391
+ try:
392
+ face_recognizer.load_known_faces()
393
+ return jsonify({'success': True, 'message': 'Faces reloaded successfully'})
394
+ except Exception as e:
395
+ logger.error(f"Error reloading faces: {e}")
396
+ return jsonify({'success': False, 'message': 'Failed to reload faces'})
397
+
398
+ if __name__ == '__main__':
399
+ app.run(debug=True, host='0.0.0.0', port=7860)