ankitkr9911 commited on
Commit
9ae9767
·
1 Parent(s): 2f535b3

Upload app from GitHub

Browse files
Insightface_Model/Models/buffalo_s/1k3d68.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:df5c06b8a0c12e422b2ed8947b8869faa4105387f199c477af038aa01f9a45cc
3
+ size 143607619
Insightface_Model/Models/buffalo_s/2d106det.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f001b856447c413801ef5c42091ed0cd516fcd21f2d6b79635b1e733a7109dbf
3
+ size 5030888
Insightface_Model/Models/buffalo_s/det_500m.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5e4447f50245bbd7966bd6c0fa52938c61474a04ec7def48753668a9d8b4ea3a
3
+ size 2524817
Insightface_Model/Models/buffalo_s/genderage.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4fde69b1c810857b88c64a335084f1c3fe8f01246c9a191b48c7bb756d6652fb
3
+ size 1322532
Insightface_Model/Models/buffalo_s/w600k_mbf.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9cc6e4a75f0e2bf0b1aed94578f144d15175f357bdc05e815e5c4a02b319eb4f
3
+ size 13616099
configure.sh ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ echo "
2
+ <VirtualHost *:80>
3
+ ServerName <domain or ip address>
4
+ Redirect / https://<domain or ip address>
5
+ </VirtualHost>
6
+
7
+ <VirtualHost *:443>
8
+
9
+ ServerName <domain or ip address>
10
+ SSLEngine on
11
+ SSLProxyEngine On
12
+ SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
13
+ SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
14
+
15
+ ProxyRequests Off
16
+ ProxyPreserveHost On
17
+ #AllowEncodedSlashes NoDecode
18
+ <Proxy *>
19
+ Order deny,allow
20
+ Allow from all
21
+ </Proxy>
22
+
23
+ ProxyPass /_stcore ws://localhost:8501/_stcore
24
+ ProxyPassReverse /_stcore ws://localhost:8501/_stcore
25
+
26
+ # The order is important here
27
+ ProxyPass / http://localhost:8501/
28
+ ProxyPassReverse / http://localhost:8501/
29
+
30
+ </VirtualHost>" > /etc/apache2/sites-available/deploy_attendance_app.conf
face_rec.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ import cv2
4
+
5
+ import redis
6
+
7
+ from insightface.app import FaceAnalysis
8
+ from sklearn.metrics import pairwise
9
+
10
+ import time
11
+ from datetime import datetime
12
+ import os
13
+
14
+ hostname = 'redis-18916.c83.us-east-1-2.ec2.redns.redis-cloud.com'
15
+ port = 18916
16
+ password = 'vTig9W0x2XtLCyR3MDFQFyZjSMSId6Gr'
17
+ r = redis.StrictRedis(host=hostname,
18
+ port=port,
19
+ password=password)
20
+ # extracting data from redis database
21
+ def retrive_data(name):
22
+ retrive_dict = r.hgetall(name)
23
+ retrive_series = pd.Series(retrive_dict)
24
+ retrive_series = retrive_series.apply(lambda x:np.frombuffer(x,dtype=np.float32))
25
+ index = retrive_series.index
26
+ index = list(map(lambda x: x.decode(),index))
27
+ retrive_series.index = index
28
+ retrive_df = retrive_series.to_frame().reset_index()
29
+ retrive_df.columns = ['name_role','Facial Feature']
30
+ retrive_df[['Name','Role']] = retrive_df['name_role'].apply(lambda x : x.split('@')).apply(pd.Series)
31
+ return retrive_df[['Name','Role','Facial Feature']]
32
+
33
+ # Configure face analysis
34
+ face_app = FaceAnalysis(name='buffalo_s',
35
+ root='insightface_model',
36
+ providers=['CPUExecutionProvider'])
37
+ face_app.prepare(ctx_id=0,det_size=(640,640),det_thresh=0.5)
38
+
39
+ # def ml_search_algorithm(dataframe,feature_column,test_vector,name_role=['Name','Role'],thresh=0.5):
40
+
41
+ # # cosine similarity based search algorithm
42
+ # # feature_column --> column name that contains features(embeddings) in dataframe
43
+
44
+ # dataframe = dataframe.copy()
45
+
46
+ # X_list = dataframe[feature_column].tolist()
47
+ # x = np.asarray(X_list)
48
+
49
+ # # Debugging step: print shape of each embedding
50
+ # for idx, item in enumerate(X_list):
51
+ # print(f"Item {idx} shape: {np.array(item).shape}")
52
+
53
+ # try:
54
+ # x = np.asarray(X_list)
55
+ # except ValueError as e:
56
+ # print("Error converting X_list to array:", e)
57
+ # # Handle inconsistent shapes here, for example, by filtering out invalid items
58
+ # return 'Unknown', 'Unknown'
59
+
60
+
61
+ # similar = pairwise.cosine_similarity(x,test_vector.reshape(1,-1))
62
+ # similar_arr = np.array(similar).flatten()
63
+ # dataframe['cosine'] = similar_arr
64
+
65
+ # data_filter = dataframe.query(f'cosine >= {thresh}')
66
+ # if(len(data_filter) > 0):
67
+ # data_filter.reset_index(drop=True,inplace=True)
68
+ # argmax = data_filter['cosine'].argmax()
69
+ # person_name,person_role = data_filter.loc[argmax][name_role]
70
+ # else:
71
+ # person_name = 'Unknown'
72
+ # person_role = 'Unknown'
73
+
74
+ # return person_name,person_role
75
+
76
+ def ml_search_algorithm(dataframe, feature_column, test_vector, name_role=['Name', 'Role'], thresh=0.5):
77
+ # cosine similarity based search algorithm
78
+ dataframe = dataframe.copy()
79
+ X_list = dataframe[feature_column].tolist()
80
+
81
+ # Check if all embeddings have the same shape as the test vector
82
+ valid_indices = []
83
+ valid_embeddings = []
84
+
85
+ # Get test vector shape (usually 512 for InsightFace)
86
+ test_dim = test_vector.shape[0]
87
+
88
+ # Filter out embeddings with inconsistent shapes
89
+ for idx, item in enumerate(X_list):
90
+ try:
91
+ item_array = np.array(item)
92
+ if item_array.shape[0] == test_dim:
93
+ valid_indices.append(idx)
94
+ valid_embeddings.append(item_array)
95
+ except:
96
+ print(f"Skipping item {idx} due to shape issue")
97
+
98
+ if len(valid_embeddings) == 0:
99
+ print("No valid embeddings found in database")
100
+ return 'Unknown', 'Unknown'
101
+
102
+ # Create a new array with only valid embeddings
103
+ x = np.vstack(valid_embeddings)
104
+
105
+ # Create a new filtered dataframe
106
+ filtered_df = dataframe.iloc[valid_indices].copy()
107
+
108
+ # Calculate similarity
109
+ similar = pairwise.cosine_similarity(x, test_vector.reshape(1, -1))
110
+ similar_arr = np.array(similar).flatten()
111
+
112
+ # Add similarity scores to filtered dataframe
113
+ filtered_df['cosine'] = similar_arr
114
+
115
+ # Filter by threshold
116
+ data_filter = filtered_df.query(f'cosine >= {thresh}')
117
+
118
+ if len(data_filter) > 0:
119
+ data_filter.reset_index(drop=True, inplace=True)
120
+ argmax = data_filter['cosine'].argmax()
121
+ person_name, person_role = data_filter.loc[argmax][name_role]
122
+ else:
123
+ person_name = 'Unknown'
124
+ person_role = 'Unknown'
125
+
126
+ return person_name, person_role
127
+ class RealTimePred:
128
+ def __init__(self):
129
+ self.logs = dict(name=[],role=[],current_time=[])
130
+
131
+ def reset_dict(self):
132
+ self.logs = dict(name=[],role=[],current_time=[])
133
+
134
+ def saveLogs_redis(self):
135
+ dataframe = pd.DataFrame(self.logs)
136
+
137
+ dataframe.drop_duplicates('name',inplace=True)
138
+
139
+ name_list = dataframe['name'].tolist()
140
+ role_list = dataframe['role'].tolist()
141
+ ctime_list = dataframe['current_time'].tolist()
142
+ encoded_data = []
143
+
144
+ for name,role,ctime in zip(name_list,role_list,ctime_list):
145
+ if name != 'Unknown':
146
+ concat_string = f'{name}@{role}@{ctime}'
147
+ encoded_data.append(concat_string)
148
+
149
+ if len(encoded_data) > 0:
150
+ r.lpush('attendance:logs',*encoded_data)
151
+
152
+ self.reset_dict()
153
+
154
+
155
+ def face_prediction(self,test_image,dataframe,feature_column,name_role=['Name','Role'],thresh=0.5):
156
+ current_time = str(datetime.now().strftime('%Y-%m-%d %H:%M'))
157
+ results = face_app.get(test_image)
158
+ test_copy = test_image.copy()
159
+ for res in results:
160
+ x1,y1,x2,y2 = res['bbox'].astype(int)
161
+ embeddings = res['embedding']
162
+
163
+ person_name,person_role = ml_search_algorithm(dataframe,'Facial Feature',
164
+ test_vector=embeddings,
165
+ name_role=name_role,
166
+ thresh=thresh)
167
+
168
+ if person_name=='Unknown':
169
+ color = (0,0,255)
170
+ else:
171
+ color = (0,255,0)
172
+ cv2.rectangle(test_copy,(x1,y1),(x2,y2),color,2)
173
+ text_gen = person_name
174
+ cv2.putText(test_copy,text_gen,(x1,y1),cv2.FONT_HERSHEY_DUPLEX,0.7,color,2)
175
+ cv2.putText(test_copy,current_time,(x1,y2+20),cv2.FONT_HERSHEY_DUPLEX,0.7,color,2)
176
+
177
+ # save info in logs dict
178
+ self.logs['name'].append(person_name)
179
+ self.logs['role'].append(person_role)
180
+ self.logs['current_time'].append(current_time)
181
+
182
+ return test_copy
183
+
184
+
185
+
186
+ # Registration Form
187
+ class RegistrationForm:
188
+ def __init__(self):
189
+ self.sample = 0
190
+
191
+ def reset(self):
192
+ self.sample = 0
193
+
194
+ def get_embedding(self,frame):
195
+ # get results from insightface model
196
+ results = face_app.get(frame,max_num=1)
197
+ embeddings = None
198
+ for res in results:
199
+ self.sample = self.sample + 1
200
+ x1,y1,x2,y2 = res['bbox'].astype(int)
201
+ cv2.rectangle(frame,(x1,y1),(x2,y2),(0,255,0),2)
202
+
203
+ # put text samples info
204
+ text = f"samples : {self.sample}"
205
+ cv2.putText(frame,text,(x1,y1),cv2.FONT_HERSHEY_DUPLEX,0.6,(0,255,0),2)
206
+
207
+ embeddings = res['embedding']
208
+
209
+ return frame,embeddings
210
+
211
+
212
+ def save_data_in_redis_db(self,name,role):
213
+ if name is not None:
214
+ if name.strip() != '':
215
+ key = f'{name}@{role}'
216
+ else:
217
+ return 'name_false'
218
+ else:
219
+ return 'name_false'
220
+
221
+ if 'face_embedding.txt' not in os.listdir():
222
+ return 'file_false'
223
+ # step 1 : load "face_embedding.txt"
224
+ x_array = np.loadtxt('face_embedding.txt',dtype=np.float32) # flatten array
225
+
226
+ # step 2 : convert into array (proper shape)
227
+ received_samples = int(x_array.size/512)
228
+ x_array = x_array.reshape(received_samples,512)
229
+
230
+ # step 3 : cal. mean embeddings
231
+ x_mean = x_array.mean(axis=0)
232
+ x_mean_bytes = x_mean.tobytes()
233
+
234
+ # step 4 : save this into redis database (redis hashes)
235
+ r.hset(name='academy:register',key=key,value=x_mean_bytes)
236
+
237
+ os.remove('face_embedding.txt')
238
+ self.reset()
239
+ return True
home.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import time
3
+ from PIL import Image
4
+ import base64
5
+
6
+ # Page configuration
7
+ st.set_page_config(
8
+ page_title="AI Attendance System",
9
+ page_icon="👁️",
10
+ layout="wide",
11
+ initial_sidebar_state="expanded"
12
+ )
13
+
14
+ # Custom CSS for better appearance
15
+ st.markdown("""
16
+ <style>
17
+ .main-header {
18
+ font-size: 2.5rem;
19
+ color: #1E88E5;
20
+ text-align: center;
21
+ margin-bottom: 1rem;
22
+ font-weight: 700;
23
+ }
24
+ .sub-header {
25
+ font-size: 1.5rem;
26
+ color: #424242;
27
+ margin-top: 2rem;
28
+ font-weight: 600;
29
+ }
30
+ .card {
31
+ border-radius: 5px;
32
+ padding: 20px;
33
+ background-color: #f8f9fa;
34
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
35
+ margin-bottom: 20px;
36
+ }
37
+ .success-box {
38
+ background-color: #d4edda;
39
+ color: #155724;
40
+ padding: 15px;
41
+ border-radius: 5px;
42
+ margin-bottom: 20px;
43
+ }
44
+ .feature-icon {
45
+ font-size: 1.8rem;
46
+ margin-right: 10px;
47
+ }
48
+ .footer {
49
+ text-align: center;
50
+ margin-top: 50px;
51
+ padding: 20px;
52
+ color: #6c757d;
53
+ border-top: 1px solid #e9ecef;
54
+ }
55
+ </style>
56
+ """, unsafe_allow_html=True)
57
+
58
+ # Header with logo
59
+ col1, col2, col3 = st.columns([1, 3, 1])
60
+ with col2:
61
+ st.markdown("<h1 class='main-header'>🔷 AI-Powered Attendance System</h1>", unsafe_allow_html=True)
62
+ st.markdown("<p style='text-align: center; font-size: 1.2rem;'>Smart Recognition • Secure • Efficient</p>", unsafe_allow_html=True)
63
+
64
+ # Create two columns for content
65
+ left_col, right_col = st.columns([2, 1])
66
+
67
+ with left_col:
68
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
69
+ with st.spinner('🔄 Loading Models and Connecting to Database...'):
70
+ try:
71
+ import face_rec
72
+ time.sleep(2) # Simulating loading time
73
+ st.markdown("<div class='success-box'><b>✅ Face Recognition Model:</b> Loaded Successfully</div>", unsafe_allow_html=True)
74
+ st.markdown("<div class='success-box'><b>✅ Database Connection:</b> Connected to Redis DB</div>", unsafe_allow_html=True)
75
+ except Exception as e:
76
+ st.error(f"Error loading system components: {e}")
77
+ st.markdown("</div>", unsafe_allow_html=True)
78
+
79
+ # System features
80
+ st.markdown("<h2 class='sub-header'>System Features</h2>", unsafe_allow_html=True)
81
+
82
+ features = [
83
+ ("👁️ Real-time Face Recognition", "Instant identification of registered users using advanced ML models"),
84
+ ("⏱️ Automatic Attendance Logging", "Timestamps recorded automatically with entry and exit times"),
85
+ ("🔐 Role-based Access Management", "Different access levels for students and teachers"),
86
+ ("📊 Comprehensive Reports", "Generate detailed attendance reports with various filters"),
87
+ ("⚡ High Performance", "Optimized for speed even with multiple simultaneous recognitions")
88
+ ]
89
+
90
+ for icon_title, description in features:
91
+ st.markdown(f"""
92
+ <div style='display: flex; align-items: center; margin-bottom: 15px;'>
93
+ <div class='feature-icon'>{icon_title.split()[0]}</div>
94
+ <div>
95
+ <b>{" ".join(icon_title.split()[1:])}</b><br>
96
+ <span style='color: #6c757d; font-size: 0.9rem;'>{description}</span>
97
+ </div>
98
+ </div>
99
+ """, unsafe_allow_html=True)
100
+
101
+ with right_col:
102
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
103
+ st.markdown("<h3 style='text-align: center;'>Quick Navigation</h3>", unsafe_allow_html=True)
104
+
105
+ # Quick action buttons
106
+ if st.button("📝 Register New User", use_container_width=True):
107
+ st.switch_page("pages/2_registration_form.py")
108
+
109
+ if st.button("🟢 Start Attendance Tracking", use_container_width=True):
110
+ st.switch_page("pages/1_real_time_prediction.py")
111
+
112
+ if st.button("📊 View Attendance Reports", use_container_width=True):
113
+ st.switch_page("pages/3_report.py")
114
+
115
+ st.markdown("</div>", unsafe_allow_html=True)
116
+
117
+ # System status
118
+ st.markdown("<div class='card' style='margin-top: 20px;'>", unsafe_allow_html=True)
119
+ st.markdown("<h3 style='text-align: center;'>System Status</h3>", unsafe_allow_html=True)
120
+
121
+ # Display some system metrics
122
+ col1, col2 = st.columns(2)
123
+ with col1:
124
+ st.metric(label="System Status", value="Online", delta="Active")
125
+ with col2:
126
+ st.metric(label="Database Status", value="Connected", delta="Active")
127
+
128
+ st.markdown("</div>", unsafe_allow_html=True)
129
+
130
+ # Footer
131
+ st.markdown("<div class='footer'>AI-Powered Attendance System • © 2025</div>", unsafe_allow_html=True)
main.sh ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ bash configure.sh
2
+ streamlit run home.py
pages/1_real_time_prediction.py ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from home import face_rec
3
+ from streamlit_webrtc import webrtc_streamer
4
+ import av
5
+ import time
6
+ import pandas as pd
7
+ from datetime import datetime
8
+
9
+ # Page configuration
10
+ st.set_page_config(
11
+ page_title="Live Attendance | AI Attendance",
12
+ page_icon="🟢",
13
+ layout="wide"
14
+ )
15
+
16
+ # Custom CSS
17
+ st.markdown("""
18
+ <style>
19
+ .main-header {
20
+ font-size: 2.2rem;
21
+ color: #4CAF50;
22
+ text-align: center;
23
+ margin-bottom: 1.5rem;
24
+ font-weight: 700;
25
+ }
26
+ .card {
27
+ border-radius: 8px;
28
+ padding: 25px;
29
+ background-color: #f8f9fa;
30
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
31
+ margin-bottom: 20px;
32
+ }
33
+ .webcam-container {
34
+ border: 2px solid #4CAF50;
35
+ border-radius: 8px;
36
+ padding: 15px;
37
+ background-color: #f1f8e9;
38
+ }
39
+ .status-indicator {
40
+ display: flex;
41
+ align-items: center;
42
+ margin-bottom: 10px;
43
+ }
44
+ .status-dot {
45
+ width: 12px;
46
+ height: 12px;
47
+ border-radius: 50%;
48
+ margin-right: 8px;
49
+ }
50
+ .active {
51
+ background-color: #4CAF50;
52
+ box-shadow: 0 0 8px #4CAF50;
53
+ animation: pulse 2s infinite;
54
+ }
55
+ @keyframes pulse {
56
+ 0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7); }
57
+ 70% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0); }
58
+ 100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
59
+ }
60
+ .inactive {
61
+ background-color: #9e9e9e;
62
+ }
63
+ .section-title {
64
+ font-size: 1.3rem;
65
+ font-weight: 600;
66
+ color: #424242;
67
+ margin-bottom: 15px;
68
+ padding-bottom: 8px;
69
+ border-bottom: 2px solid #e0e0e0;
70
+ }
71
+ .attendance-table {
72
+ font-size: 0.9rem;
73
+ }
74
+ .footer {
75
+ text-align: center;
76
+ margin-top: 30px;
77
+ padding: 20px;
78
+ color: #6c757d;
79
+ border-top: 1px solid #e9ecef;
80
+ }
81
+ .stats-box {
82
+ background-color: #f8f9fa;
83
+ border-radius: 8px;
84
+ padding: 15px;
85
+ text-align: center;
86
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
87
+ }
88
+ .stats-number {
89
+ font-size: 1.8rem;
90
+ font-weight: bold;
91
+ color: #4CAF50;
92
+ }
93
+ .stats-label {
94
+ color: #757575;
95
+ font-size: 0.9rem;
96
+ }
97
+ </style>
98
+ """, unsafe_allow_html=True)
99
+
100
+ # Header
101
+ st.markdown("<h1 class='main-header'>🟢 Live Attendance Tracking</h1>", unsafe_allow_html=True)
102
+
103
+ # System status indicators
104
+ col1, col2, col3 = st.columns(3)
105
+ with col1:
106
+ st.markdown("""
107
+ <div class="status-indicator">
108
+ <div class="status-dot active"></div>
109
+ <div>Recognition System: <b>Active</b></div>
110
+ </div>
111
+ """, unsafe_allow_html=True)
112
+ with col2:
113
+ st.markdown("""
114
+ <div class="status-indicator">
115
+ <div class="status-dot active"></div>
116
+ <div>Database Connection: <b>Active</b></div>
117
+ </div>
118
+ """, unsafe_allow_html=True)
119
+ with col3:
120
+ st.markdown("""
121
+ <div class="status-indicator">
122
+ <div class="status-dot active"></div>
123
+ <div>Auto-Logging: <b>Enabled</b></div>
124
+ </div>
125
+ """, unsafe_allow_html=True)
126
+
127
+ # Main content
128
+ left_col, right_col = st.columns([3, 2])
129
+
130
+ with left_col:
131
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
132
+ st.markdown("<h2 class='section-title'>📹 Live Recognition Feed</h2>", unsafe_allow_html=True)
133
+
134
+ # Retrieve registered data
135
+ with st.spinner('Retrieving data from Redis database...'):
136
+ try:
137
+ redis_face_db = face_rec.retrive_data(name='academy:register')
138
+ if not redis_face_db.empty:
139
+ st.success('✅ User data retrieved successfully!')
140
+ else:
141
+ st.warning('⚠️ No registered users found in the database.')
142
+ except Exception as e:
143
+ st.error(f"Error retrieving data: {e}")
144
+ redis_face_db = pd.DataFrame()
145
+
146
+ # Configuration options
147
+ with st.expander("Recognition Settings"):
148
+ wait_time = st.slider("Log Update Interval (seconds)", 10, 120, 30)
149
+ confidence_threshold = st.slider("Recognition Confidence Threshold", 0.3, 0.9, 0.5, 0.05)
150
+
151
+ # Initialize real-time prediction
152
+ set_time = time.time()
153
+ realtime_pred = face_rec.RealTimePred()
154
+
155
+ # Video frame callback function
156
+ def video_frame_callback(frame):
157
+ global set_time
158
+ img = frame.to_ndarray(format="bgr24")
159
+
160
+ # Perform face prediction
161
+ pred_img = realtime_pred.face_prediction(
162
+ img,
163
+ redis_face_db,
164
+ 'Facial Feature',
165
+ ['Name', 'Role'],
166
+ thresh=confidence_threshold
167
+ )
168
+
169
+ # Check if it's time to save logs
170
+ time_now = time.time()
171
+ diff_time = time_now - set_time
172
+ if diff_time >= wait_time:
173
+ realtime_pred.saveLogs_redis()
174
+ set_time = time.time() # Reset timer
175
+
176
+ return av.VideoFrame.from_ndarray(pred_img, format="bgr24")
177
+
178
+ # Webcam feed with face recognition
179
+ st.markdown("<div class='webcam-container'>", unsafe_allow_html=True)
180
+ webrtc_streamer(
181
+ key="realTimePrediction",
182
+ video_frame_callback=video_frame_callback,
183
+ rtc_configuration={"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]},
184
+ media_stream_constraints={"video": True, "audio": False},
185
+ )
186
+ st.markdown("</div>", unsafe_allow_html=True)
187
+
188
+ # Instructions
189
+ st.info("""
190
+ **Instructions:**
191
+ 1. Stand in front of the camera
192
+ 2. Wait for the system to recognize your face
193
+ 3. Your attendance will be logged automatically
194
+ 4. The system records entry and exit times
195
+ """)
196
+
197
+ st.markdown("</div>", unsafe_allow_html=True)
198
+
199
+ with right_col:
200
+ # User database card
201
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
202
+ st.markdown("<h2 class='section-title'>👥 Registered Users</h2>", unsafe_allow_html=True)
203
+
204
+ # Display registered users
205
+ if not redis_face_db.empty:
206
+ st.dataframe(
207
+ redis_face_db[['Name', 'Role']].sort_values('Name'),
208
+ use_container_width=True,
209
+ hide_index=True,
210
+ height=200
211
+ )
212
+
213
+ # Display statistics
214
+ total_users = len(redis_face_db)
215
+ students = len(redis_face_db[redis_face_db['Role'] == 'Student'])
216
+ teachers = len(redis_face_db[redis_face_db['Role'] == 'Teacher'])
217
+
218
+ st.markdown("<br>", unsafe_allow_html=True)
219
+ stats_cols = st.columns(3)
220
+ with stats_cols[0]:
221
+ st.markdown(f"""
222
+ <div class="stats-box">
223
+ <div class="stats-number">{total_users}</div>
224
+ <div class="stats-label">Total Users</div>
225
+ </div>
226
+ """, unsafe_allow_html=True)
227
+ with stats_cols[1]:
228
+ st.markdown(f"""
229
+ <div class="stats-box">
230
+ <div class="stats-number">{students}</div>
231
+ <div class="stats-label">Students</div>
232
+ </div>
233
+ """, unsafe_allow_html=True)
234
+ with stats_cols[2]:
235
+ st.markdown(f"""
236
+ <div class="stats-box">
237
+ <div class="stats-number">{teachers}</div>
238
+ <div class="stats-label">Teachers</div>
239
+ </div>
240
+ """, unsafe_allow_html=True)
241
+ else:
242
+ st.warning("No registered users found in the database.")
243
+
244
+ st.markdown("</div>", unsafe_allow_html=True)
245
+
246
+ # Recent activity card
247
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
248
+ st.markdown("<h2 class='section-title'>🕒 Recent Activity</h2>", unsafe_allow_html=True)
249
+
250
+ # Load and display recent logs
251
+ try:
252
+ logs_list = face_rec.r.lrange('attendance:logs', 0, 9) # Get last 10 logs
253
+
254
+ if logs_list:
255
+ # Convert bytes to string and create dataframe
256
+ logs_string = [log.decode('utf-8').split('@') for log in logs_list]
257
+ logs_df = pd.DataFrame(logs_string, columns=['Name', 'Role', 'Timestamp'])
258
+
259
+ # Format timestamp
260
+ logs_df['Timestamp'] = pd.to_datetime(logs_df['Timestamp'],format='ISO8601')
261
+ logs_df['Time'] = logs_df['Timestamp'].dt.strftime('%H:%M:%S')
262
+ logs_df['Date'] = logs_df['Timestamp'].dt.strftime('%Y-%m-%d')
263
+
264
+ # Display recent logs
265
+ st.dataframe(
266
+ logs_df[['Name', 'Role', 'Time', 'Date']],
267
+ use_container_width=True,
268
+ hide_index=True
269
+ )
270
+ else:
271
+ st.info("No recent activity logged.")
272
+ except Exception as e:
273
+ st.error(f"Error loading logs: {e}")
274
+
275
+ st.markdown("</div>", unsafe_allow_html=True)
276
+
277
+ # Quick actions
278
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
279
+ st.markdown("<h2 class='section-title'>⚡ Quick Actions</h2>", unsafe_allow_html=True)
280
+
281
+ col1, col2 = st.columns(2)
282
+ with col1:
283
+ if st.button("📊 View Reports", use_container_width=True):
284
+ st.switch_page("pages/report.py")
285
+ with col2:
286
+ if st.button("📝 Add New User", use_container_width=True):
287
+ st.switch_page("pages/page1.py")
288
+
289
+ st.markdown("</div>", unsafe_allow_html=True)
290
+
291
+ # Footer
292
+ st.markdown("<div class='footer'>AI-Powered Attendance System • © 2025</div>", unsafe_allow_html=True)
pages/2_registration_form.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import face_rec
3
+ import cv2
4
+ import numpy as np
5
+ from streamlit_webrtc import webrtc_streamer
6
+ import av
7
+ import time
8
+
9
+ # Page configuration
10
+ st.set_page_config(
11
+ page_title="User Registration | AI Attendance",
12
+ page_icon="📝",
13
+ layout="wide"
14
+ )
15
+
16
+ # Custom CSS
17
+ st.markdown("""
18
+ <style>
19
+ .main-header {
20
+ font-size: 2.2rem;
21
+ color: #1E88E5;
22
+ text-align: center;
23
+ margin-bottom: 1.5rem;
24
+ font-weight: 700;
25
+ }
26
+ .card {
27
+ border-radius: 8px;
28
+ padding: 25px;
29
+ background-color: #f8f9fa;
30
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
31
+ margin-bottom: 20px;
32
+ }
33
+ .instructions {
34
+ background-color: #e8f4f8;
35
+ border-left: 4px solid #1E88E5;
36
+ padding: 15px;
37
+ margin-bottom: 20px;
38
+ border-radius: 4px;
39
+ }
40
+ .submit-button {
41
+ background-color: #4CAF50;
42
+ color: white;
43
+ padding: 12px 24px;
44
+ font-size: 16px;
45
+ border-radius: 4px;
46
+ border: none;
47
+ cursor: pointer;
48
+ width: 100%;
49
+ }
50
+ .input-label {
51
+ font-weight: 600;
52
+ color: #424242;
53
+ margin-bottom: 8px;
54
+ }
55
+ .webcam-container {
56
+ border: 2px dashed #1E88E5;
57
+ border-radius: 8px;
58
+ padding: 10px;
59
+ margin-top: 10px;
60
+ }
61
+ .step-counter {
62
+ background-color: #1E88E5;
63
+ color: white;
64
+ border-radius: 50%;
65
+ width: 25px;
66
+ height: 25px;
67
+ display: inline-flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ margin-right: 10px;
71
+ }
72
+ .step-title {
73
+ font-size: 1.2rem;
74
+ font-weight: 600;
75
+ color: #1E88E5;
76
+ }
77
+ .footer {
78
+ text-align: center;
79
+ margin-top: 30px;
80
+ padding: 20px;
81
+ color: #6c757d;
82
+ border-top: 1px solid #e9ecef;
83
+ }
84
+ </style>
85
+ """, unsafe_allow_html=True)
86
+
87
+ # Header
88
+ st.markdown("<h1 class='main-header'>📝 User Registration Form</h1>", unsafe_allow_html=True)
89
+
90
+ # Progress bar for registration process
91
+ step = 1
92
+ steps = ["Enter Information", "Capture Face", "Submit Registration"]
93
+ progress = (step / len(steps))
94
+
95
+ st.progress(progress, text=f"Step {step} of {len(steps)}: {steps[step-1]}")
96
+
97
+ # Main content in columns
98
+ left_col, right_col = st.columns([3, 2])
99
+
100
+ with left_col:
101
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
102
+
103
+ # Instructions
104
+ st.markdown("<div class='instructions'>", unsafe_allow_html=True)
105
+ st.markdown("""
106
+ 🔹 Please complete all fields below
107
+ 🔹 Face capture requires good lighting
108
+ 🔹 Look directly at the camera
109
+ 🔹 Remove glasses and face coverings
110
+ """)
111
+ st.markdown("</div>", unsafe_allow_html=True)
112
+
113
+ # Initialize registration form
114
+ registration_form = face_rec.RegistrationForm()
115
+
116
+ # Step 1: Collect person name and role
117
+ st.markdown("<div class='step-title'><span class='step-counter'>1</span> Personal Information</div>", unsafe_allow_html=True)
118
+ st.markdown("<p class='input-label'>Full Name</p>", unsafe_allow_html=True)
119
+ person_name = st.text_input(label="", placeholder="Enter your first and last name", label_visibility="collapsed")
120
+
121
+ st.markdown("<p class='input-label'>Select Role</p>", unsafe_allow_html=True)
122
+ role = st.selectbox(label="", options=("Student", "Teacher"), label_visibility="collapsed")
123
+
124
+ # Additional information (optional)
125
+ with st.expander("Additional Information (Optional)"):
126
+ st.text_input("Email Address")
127
+ st.text_input("ID Number")
128
+ col1, col2 = st.columns(2)
129
+ with col1:
130
+ st.date_input("Date of Birth")
131
+ with col2:
132
+ st.selectbox("Department", ["Computer Science", "Engineering", "Business", "Arts", "Science"])
133
+
134
+ st.markdown("</div>", unsafe_allow_html=True)
135
+
136
+ with right_col:
137
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
138
+
139
+ # Step 2: Collect facial embedding of that person
140
+ st.markdown("<div class='step-title'><span class='step-counter'>2</span> Face Capture</div>", unsafe_allow_html=True)
141
+ st.write("Please position your face in the frame and wait for the system to capture your facial features.")
142
+
143
+ # Face capture status indicators
144
+ status_placeholder = st.empty()
145
+
146
+ # Custom face capture function
147
+ def video_callback_func(frame):
148
+ img = frame.to_ndarray(format='bgr24')
149
+ reg_img, embedding = registration_form.get_embedding(img)
150
+
151
+ # Save embedding if available
152
+ if embedding is not None:
153
+ with open('face_embedding.txt', mode='ab') as f:
154
+ np.savetxt(f, embedding)
155
+ status_placeholder.success("✅ Face captured successfully!")
156
+
157
+ return av.VideoFrame.from_ndarray(reg_img, format='bgr24')
158
+
159
+ # Webcam feed
160
+ st.markdown("<div class='webcam-container'>", unsafe_allow_html=True)
161
+ webrtc_streamer(
162
+ key='registration',
163
+ video_frame_callback=video_callback_func,
164
+ rtc_configuration={"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]},
165
+ media_stream_constraints={"video": True, "audio": False},
166
+ )
167
+ st.markdown("</div>", unsafe_allow_html=True)
168
+
169
+ st.markdown("</div>", unsafe_allow_html=True)
170
+
171
+ # Step 3: Submit button and save the data
172
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
173
+ st.markdown("<div class='step-title'><span class='step-counter'>3</span> Complete Registration</div>", unsafe_allow_html=True)
174
+
175
+ col1, col2, col3 = st.columns([1, 2, 1])
176
+ with col2:
177
+ if st.button("Register User", key="submit_button", use_container_width=True):
178
+ with st.spinner("Processing registration..."):
179
+ time.sleep(1) # Simulate processing
180
+ return_val = registration_form.save_data_in_redis_db(person_name, role)
181
+
182
+ if return_val == True:
183
+ st.success(f"✅ {person_name} registered successfully!")
184
+ st.balloons()
185
+ # Add a timer for redirecting
186
+ st.write("Redirecting to home page in 5 seconds...")
187
+ time.sleep(5)
188
+ st.switch_page("home.py")
189
+ elif return_val == 'name_false':
190
+ st.error("❌ Please enter a valid name. Name cannot be empty or contain only spaces.")
191
+ elif return_val == 'file_false':
192
+ st.error("❌ Face embedding data not found. Please refresh the page and try again.")
193
+
194
+ st.markdown("</div>", unsafe_allow_html=True)
195
+
196
+ # Footer
197
+ st.markdown("<div class='footer'>AI-Powered Attendance System • © 2025</div>", unsafe_allow_html=True)
pages/3_report.py ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import datetime
4
+ import plotly.express as px
5
+ from home import face_rec
6
+ import time
7
+
8
+ # Page configuration
9
+ st.set_page_config(
10
+ page_title="Attendance Dashboard",
11
+ layout="wide",
12
+ initial_sidebar_state="expanded"
13
+ )
14
+
15
+ # Custom CSS for better styling
16
+ st.markdown("""
17
+ <style>
18
+ .main-header {
19
+ font-size: 2.5rem;
20
+ color: #1E3A8A;
21
+ margin-bottom: 1rem;
22
+ text-align: center;
23
+ padding: 1rem;
24
+ border-bottom: 2px solid #E5E7EB;
25
+ }
26
+ .sub-header {
27
+ font-size: 1.8rem;
28
+ color: #1E3A8A;
29
+ margin-top: 1rem;
30
+ }
31
+ .card {
32
+ background-color: #F9FAFB;
33
+ border-radius: 10px;
34
+ padding: 1.5rem;
35
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
36
+ margin-bottom: 1rem;
37
+ }
38
+ .metric-card {
39
+ background-color: #EFF6FF;
40
+ border-radius: 10px;
41
+ padding: 1rem;
42
+ text-align: center;
43
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
44
+ }
45
+ .metric-value {
46
+ font-size: 2rem;
47
+ font-weight: bold;
48
+ color: #1E40AF;
49
+ }
50
+ .metric-label {
51
+ font-size: 1rem;
52
+ color: #6B7280;
53
+ }
54
+ .stTabs [data-baseweb="tab-list"] {
55
+ gap: 10px;
56
+ }
57
+ .stTabs [data-baseweb="tab"] {
58
+ border-radius: 4px 4px 0px 0px;
59
+ padding: 10px 16px;
60
+ background-color: #F3F4F6;
61
+ }
62
+ .stTabs [aria-selected="true"] {
63
+ background-color: #DBEAFE;
64
+ border-bottom: 2px solid #2563EB;
65
+ }
66
+ .dataframe {
67
+ font-size: 14px;
68
+ }
69
+ .refresh-btn {
70
+ background-color: #3B82F6;
71
+ color: white;
72
+ font-weight: bold;
73
+ border-radius: 6px;
74
+ padding: 8px 16px;
75
+ }
76
+ .filter-section {
77
+ background-color: #F9FAFB;
78
+ padding: 15px;
79
+ border-radius: 10px;
80
+ margin-bottom: 20px;
81
+ }
82
+ </style>
83
+ """, unsafe_allow_html=True)
84
+
85
+ # Header section
86
+ st.markdown('<h1 class="main-header">Attendance Management Dashboard</h1>', unsafe_allow_html=True)
87
+
88
+ # Function to load logs
89
+ def load_logs(name, end=-1):
90
+ with st.spinner('Loading attendance logs...'):
91
+ logs_list = face_rec.r.lrange(name, start=0, end=end)
92
+ return logs_list
93
+
94
+ # Function to process logs into DataFrame
95
+ def process_logs(logs_list):
96
+ # Convert bytes to string
97
+ logs_list_string = [log.decode('utf-8') for log in logs_list]
98
+
99
+ # Split string by @ and create nested list
100
+ logs_nested_list = [log.split('@') for log in logs_list_string]
101
+
102
+ # Convert nested list into dataframe
103
+ logs_df = pd.DataFrame(logs_nested_list, columns=['Name', 'Role', 'Timestamp'])
104
+
105
+ # Time based analysis
106
+ logs_df['Timestamp'] = pd.to_datetime(logs_df['Timestamp'], format='mixed')
107
+ logs_df['Date'] = logs_df['Timestamp'].dt.date
108
+
109
+ # Calculate Intime and outtime
110
+ report_df = logs_df.groupby(by=['Date', 'Name', 'Role']).agg(
111
+ In_time=pd.NamedAgg('Timestamp', 'min'),
112
+ Out_time=pd.NamedAgg('Timestamp', 'max')
113
+ ).reset_index()
114
+
115
+ report_df['In_time'] = pd.to_datetime(report_df['In_time'])
116
+ report_df['Out_time'] = pd.to_datetime(report_df['Out_time'])
117
+ report_df['duration'] = report_df['Out_time'] - report_df['In_time']
118
+
119
+ # Mark attendance status
120
+ all_dates = report_df['Date'].unique()
121
+ name_role = report_df[['Name', 'Role']].drop_duplicates().values.tolist()
122
+ date_name_role_zip = []
123
+ for dt in all_dates:
124
+ for name, role in name_role:
125
+ date_name_role_zip.append([dt, name, role])
126
+
127
+ full_df = pd.DataFrame(date_name_role_zip, columns=['Date', 'Name', 'Role'])
128
+ full_df = pd.merge(full_df, report_df, how='left', on=['Date', 'Name', 'Role'])
129
+ full_df['Duration_seconds'] = full_df['duration'].dt.total_seconds()
130
+ full_df['Duration_hours'] = full_df['Duration_seconds'] / (60 * 60)
131
+
132
+ def status_marker(x):
133
+ if pd.isna(x):
134
+ return 'Absent'
135
+ elif x >= 0 and x < 1:
136
+ return 'Absent (Less than 1 hour)'
137
+ elif x >= 1 and x < 4:
138
+ return 'Half Day (less than 4 hours)'
139
+ elif x >= 4 and x < 6:
140
+ return 'Half Day'
141
+ elif x >= 6:
142
+ return "Present"
143
+
144
+ full_df['Status'] = full_df['Duration_hours'].apply(status_marker)
145
+ return logs_df, full_df
146
+
147
+ # Create tabs with more visual appeal
148
+ tab1, tab2, tab3 = st.tabs(["📊 Dashboard", "📋 Attendance Records", "🔍 Search & Filter"])
149
+
150
+ # Tab 1: Dashboard
151
+ with tab1:
152
+ st.markdown('<div class="card">', unsafe_allow_html=True)
153
+ st.markdown('<h2 class="sub-header">Registered Personnel</h2>', unsafe_allow_html=True)
154
+
155
+ col1, col2 = st.columns([1, 3])
156
+
157
+ with col1:
158
+ if st.button('Refresh Data', key='refresh_data', help="Load the latest data from database"):
159
+ with st.spinner('Retrieving data from database...'):
160
+ redis_face_db = face_rec.retrive_data(name='academy:register')
161
+ total_registered = len(redis_face_db)
162
+ st.success(f"Successfully loaded {total_registered} records")
163
+
164
+ with col2:
165
+ try:
166
+ redis_face_db = face_rec.retrive_data(name='academy:register')
167
+ st.dataframe(redis_face_db[['Name', 'Role']], use_container_width=True)
168
+
169
+ # Count by role for visualization
170
+ role_counts = redis_face_db['Role'].value_counts().reset_index()
171
+ role_counts.columns = ['Role', 'Count']
172
+
173
+ fig = px.pie(role_counts, values='Count', names='Role',
174
+ title='Distribution by Role',
175
+ color_discrete_sequence=px.colors.qualitative.Pastel)
176
+ st.plotly_chart(fig, use_container_width=True)
177
+ except Exception as e:
178
+ st.info("Click 'Refresh Data' to load registered personnel")
179
+
180
+ st.markdown('</div>', unsafe_allow_html=True)
181
+
182
+ # Recent attendance logs
183
+ st.markdown('<div class="card">', unsafe_allow_html=True)
184
+ st.markdown('<h2 class="sub-header">Recent Attendance Activity</h2>', unsafe_allow_html=True)
185
+
186
+ if st.button('Load Recent Activity', key='load_recent'):
187
+ logs_list = load_logs(name='attendance:logs', end=10)
188
+ if logs_list:
189
+ st.success(f"Showing {len(logs_list)} recent logs")
190
+ logs_list_string = [log.decode('utf-8') for log in logs_list]
191
+
192
+ # Create a more structured view of logs
193
+ for i, log in enumerate(logs_list_string):
194
+ parts = log.split('@')
195
+ if len(parts) >= 3:
196
+ name, role, timestamp = parts[0], parts[1], parts[2]
197
+ col1, col2, col3 = st.columns([1, 1, 2])
198
+ with col1:
199
+ st.write(f"**{name}**")
200
+ with col2:
201
+ st.write(f"{role}")
202
+ with col3:
203
+ st.write(f"{timestamp}")
204
+ st.divider()
205
+ else:
206
+ st.info("No recent logs found")
207
+
208
+ st.markdown('</div>', unsafe_allow_html=True)
209
+
210
+ # Tab 2: Attendance Records
211
+ with tab2:
212
+ st.markdown('<div class="card">', unsafe_allow_html=True)
213
+ st.markdown('<h2 class="sub-header">Complete Attendance Report</h2>', unsafe_allow_html=True)
214
+
215
+ if st.button('Generate Full Report', key='gen_report'):
216
+ with st.spinner("Processing attendance data..."):
217
+ # Load and process logs
218
+ logs_list = load_logs(name='attendance:logs')
219
+ if logs_list:
220
+ logs_df, full_df = process_logs(logs_list)
221
+
222
+ # Summary metrics
223
+ col1, col2, col3, col4 = st.columns(4)
224
+ with col1:
225
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
226
+ st.markdown(f'<div class="metric-value">{len(full_df["Date"].unique())}</div>', unsafe_allow_html=True)
227
+ st.markdown('<div class="metric-label">Total Days</div>', unsafe_allow_html=True)
228
+ st.markdown('</div>', unsafe_allow_html=True)
229
+
230
+ with col2:
231
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
232
+ st.markdown(f'<div class="metric-value">{len(full_df["Name"].unique())}</div>', unsafe_allow_html=True)
233
+ st.markdown('<div class="metric-label">Total Personnel</div>', unsafe_allow_html=True)
234
+ st.markdown('</div>', unsafe_allow_html=True)
235
+
236
+ with col3:
237
+ present_count = len(full_df[full_df['Status'] == 'Present'])
238
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
239
+ st.markdown(f'<div class="metric-value">{present_count}</div>', unsafe_allow_html=True)
240
+ st.markdown('<div class="metric-label">Present Records</div>', unsafe_allow_html=True)
241
+ st.markdown('</div>', unsafe_allow_html=True)
242
+
243
+ with col4:
244
+ absent_count = len(full_df[full_df['Status'] == 'Absent'])
245
+ st.markdown('<div class="metric-card">', unsafe_allow_html=True)
246
+ st.markdown(f'<div class="metric-value">{absent_count}</div>', unsafe_allow_html=True)
247
+ st.markdown('<div class="metric-label">Absent Records</div>', unsafe_allow_html=True)
248
+ st.markdown('</div>', unsafe_allow_html=True)
249
+
250
+ # Status distribution visualization
251
+ status_counts = full_df['Status'].value_counts().reset_index()
252
+ status_counts.columns = ['Status', 'Count']
253
+
254
+ fig = px.bar(status_counts, x='Status', y='Count',
255
+ title='Attendance Status Distribution',
256
+ color='Status',
257
+ color_discrete_sequence=px.colors.qualitative.Bold)
258
+ st.plotly_chart(fig, use_container_width=True)
259
+
260
+ # Display the complete report table
261
+ st.subheader("Detailed Attendance Records")
262
+
263
+ # Format columns for better display
264
+ display_df = full_df.copy()
265
+ display_df['Date'] = pd.to_datetime(display_df['Date']).dt.strftime('%Y-%m-%d')
266
+ display_df['In_time'] = pd.to_datetime(display_df['In_time']).dt.strftime('%H:%M:%S')
267
+ display_df['Out_time'] = pd.to_datetime(display_df['Out_time']).dt.strftime('%H:%M:%S')
268
+ display_df['Duration_hours'] = display_df['Duration_hours'].round(2)
269
+
270
+ # Choose columns to display
271
+ display_cols = ['Date', 'Name', 'Role', 'In_time', 'Out_time', 'Duration_hours', 'Status']
272
+ st.dataframe(display_df[display_cols], use_container_width=True)
273
+
274
+ # Option to download the report
275
+ csv = display_df[display_cols].to_csv(index=False).encode('utf-8')
276
+ st.download_button(
277
+ label="Download Report as CSV",
278
+ data=csv,
279
+ file_name=f"attendance_report_{datetime.datetime.now().strftime('%Y%m%d')}.csv",
280
+ mime="text/csv",
281
+ )
282
+ else:
283
+ st.warning("No attendance logs found. Make sure the system is recording attendance.")
284
+ else:
285
+ st.info("Click 'Generate Full Report' to process and display attendance data")
286
+
287
+ st.markdown('</div>', unsafe_allow_html=True)
288
+
289
+ # Tab 3: Search & Filter
290
+ with tab3:
291
+ st.markdown('<div class="card">', unsafe_allow_html=True)
292
+ st.markdown('<h2 class="sub-header">Search Records</h2>', unsafe_allow_html=True)
293
+
294
+ # Load data first before showing filters
295
+ load_data_btn = st.button("Load Data for Filtering", key="load_filter_data")
296
+
297
+ if load_data_btn:
298
+ with st.spinner("Loading data..."):
299
+ logs_list = load_logs(name='attendance:logs')
300
+ if logs_list:
301
+ logs_df, full_df = process_logs(logs_list)
302
+ st.session_state['full_df'] = full_df
303
+ st.success("Data loaded successfully! You can now filter the records.")
304
+ else:
305
+ st.warning("No attendance logs found to filter.")
306
+
307
+ if 'full_df' in st.session_state:
308
+ full_df = st.session_state['full_df']
309
+
310
+ st.markdown('<div class="filter-section">', unsafe_allow_html=True)
311
+ col1, col2 = st.columns(2)
312
+
313
+ with col1:
314
+ # Date filter
315
+ date_options = sorted([str(date) for date in full_df['Date'].unique()])
316
+ date_in = st.selectbox('Select Date', ['ALL'] + date_options, index=0)
317
+
318
+ # Name filter
319
+ name_list = sorted(full_df['Name'].unique().tolist())
320
+ name_in = st.selectbox('Select Name', ['ALL'] + name_list)
321
+
322
+ with col2:
323
+ # Role filter
324
+ role_list = sorted(full_df['Role'].unique().tolist())
325
+ role_in = st.selectbox('Select Role', ['ALL'] + role_list)
326
+
327
+ # Status filter
328
+ status_list = sorted(full_df['Status'].unique().tolist())
329
+ status_in = st.multiselect('Select Status', ['ALL'] + status_list, default=['Present'])
330
+
331
+ # Duration filter with slider
332
+ duration_in = st.slider('Filter by minimum duration in hours', 0, 12, 0)
333
+
334
+ st.markdown('</div>', unsafe_allow_html=True)
335
+
336
+ # Apply filters
337
+ if st.button('Apply Filters', key='apply_filters'):
338
+ with st.spinner("Filtering records..."):
339
+ # Convert date column to string for comparison
340
+ filter_df = full_df.copy()
341
+ filter_df['Date'] = filter_df['Date'].astype(str)
342
+
343
+ # Apply each filter
344
+ if date_in != 'ALL':
345
+ filter_df = filter_df[filter_df['Date'] == date_in]
346
+
347
+ if name_in != 'ALL':
348
+ filter_df = filter_df[filter_df['Name'] == name_in]
349
+
350
+ if role_in != 'ALL':
351
+ filter_df = filter_df[filter_df['Role'] == role_in]
352
+
353
+ if duration_in > 0:
354
+ filter_df = filter_df[filter_df['Duration_hours'] > duration_in]
355
+
356
+ if 'ALL' not in status_in and len(status_in) > 0:
357
+ filter_df = filter_df[filter_df['Status'].isin(status_in)]
358
+
359
+ if len(filter_df) > 0:
360
+ # Format display columns
361
+ display_df = filter_df.copy()
362
+ display_df['Date'] = pd.to_datetime(display_df['Date']).dt.strftime('%Y-%m-%d') if 'datetime' in str(type(display_df['Date'].iloc[0])) else display_df['Date']
363
+ if not pd.isna(display_df['In_time'].iloc[0]):
364
+ display_df['In_time'] = pd.to_datetime(display_df['In_time']).dt.strftime('%H:%M:%S')
365
+ display_df['Out_time'] = pd.to_datetime(display_df['Out_time']).dt.strftime('%H:%M:%S')
366
+ display_df['Duration_hours'] = display_df['Duration_hours'].round(2)
367
+
368
+ display_cols = ['Date', 'Name', 'Role', 'In_time', 'Out_time', 'Duration_hours', 'Status']
369
+ st.dataframe(display_df[display_cols], use_container_width=True)
370
+
371
+ # Option to download filtered results
372
+ csv = display_df[display_cols].to_csv(index=False).encode('utf-8')
373
+ st.download_button(
374
+ label="Download Filtered Results",
375
+ data=csv,
376
+ file_name=f"filtered_attendance_{datetime.datetime.now().strftime('%Y%m%d')}.csv",
377
+ mime="text/csv",
378
+ )
379
+
380
+ # Visual summary of filtered results
381
+ if len(display_df) > 1:
382
+ status_filtered = display_df['Status'].value_counts().reset_index()
383
+ status_filtered.columns = ['Status', 'Count']
384
+
385
+ fig = px.pie(status_filtered, values='Count', names='Status',
386
+ title='Status Distribution in Filtered Results',
387
+ hole=0.4,
388
+ color_discrete_sequence=px.colors.qualitative.Pastel)
389
+ st.plotly_chart(fig, use_container_width=True)
390
+ else:
391
+ st.warning("No records match your filter criteria.")
392
+ else:
393
+ st.info("Please click 'Load Data for Filtering' first to enable search functionality.")
394
+
395
+ st.markdown('</div>', unsafe_allow_html=True)
396
+
397
+ # Footer
398
+ st.markdown("""
399
+ <div style="text-align: center; margin-top: 30px; padding: 20px; background-color: #F3F4F6; border-radius: 10px;">
400
+ <p style="color: #6B7280; font-size: 14px;">Attendance Management System © 2025</p>
401
+ </div>
402
+ """, unsafe_allow_html=True)
simulated_logs.txt ADDED
The diff for this file is too large to render. See raw diff
 
upload_logs.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import redis
2
+
3
+ # Connect to Redis Client
4
+ hostname = 'redis-18916.c83.us-east-1-2.ec2.redns.redis-cloud.com'
5
+ portnumber = 18916
6
+ password = 'vTig9W0x2XtLCyR3MDFQFyZjSMSId6Gr'
7
+
8
+ r = redis.StrictRedis(host=hostname,
9
+ port=portnumber,
10
+ password=password)
11
+
12
+ # Simulated Logs
13
+ with open('simulated_logs.txt', 'r') as f:
14
+ logs_text = f.read()
15
+
16
+ encoded_logs = logs_text.split('\n')
17
+
18
+ # Push into Redis database
19
+ r.lpush('attendance:logs', *encoded_logs)