Spaces:
Sleeping
Sleeping
Fix all HTML entities and onnxruntime issues
Browse files- Dockerfile +1 -1
- app.py +35 -38
- requirements.txt +1 -1
Dockerfile
CHANGED
|
@@ -2,7 +2,7 @@ FROM python:3.11-slim
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
-
# Install system dependencies (FIXED
|
| 6 |
RUN apt-get update && apt-get install -y \
|
| 7 |
libglib2.0-0 \
|
| 8 |
libsm6 \
|
|
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
+
# Install system dependencies (FIXED)
|
| 6 |
RUN apt-get update && apt-get install -y \
|
| 7 |
libglib2.0-0 \
|
| 8 |
libsm6 \
|
app.py
CHANGED
|
@@ -13,6 +13,7 @@ import cv2
|
|
| 13 |
import requests
|
| 14 |
from typing import Optional, Dict, Tuple, Any
|
| 15 |
from deepface import DeepFace
|
|
|
|
| 16 |
import tensorflow as tf
|
| 17 |
|
| 18 |
# Optimize TensorFlow for HuggingFace Spaces
|
|
@@ -60,14 +61,14 @@ try:
|
|
| 60 |
except Exception as e:
|
| 61 |
print(f"MongoDB connection error: {e}")
|
| 62 |
|
| 63 |
-
# ---------------- Model Download Functions (
|
| 64 |
|
| 65 |
def download_file_from_google_drive(file_id, destination):
|
| 66 |
"""Download file from Google Drive - Fixed version"""
|
| 67 |
try:
|
| 68 |
if not os.path.exists(destination):
|
| 69 |
print(f"Downloading {destination}...")
|
| 70 |
-
#
|
| 71 |
url = f"https://drive.google.com/uc?export=download&id={file_id}"
|
| 72 |
|
| 73 |
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
|
@@ -147,7 +148,7 @@ def setup_models():
|
|
| 147 |
print("Setting up models...")
|
| 148 |
yolo_available, antispoof_available = setup_models()
|
| 149 |
|
| 150 |
-
# ---------------- YOLO Face Detection (
|
| 151 |
|
| 152 |
def _get_providers():
|
| 153 |
available = ort.get_available_providers()
|
|
@@ -189,7 +190,7 @@ def _nms(boxes: np.ndarray, scores: np.ndarray, iou_threshold: float):
|
|
| 189 |
areas = (x2 - x1) * (y2 - y1)
|
| 190 |
order = scores.argsort()[::-1]
|
| 191 |
keep = []
|
| 192 |
-
while order.size > 0: #
|
| 193 |
i = int(order[0])
|
| 194 |
keep.append(i)
|
| 195 |
if order.size == 1:
|
|
@@ -202,7 +203,7 @@ def _nms(boxes: np.ndarray, scores: np.ndarray, iou_threshold: float):
|
|
| 202 |
h = np.maximum(0.0, yy2 - yy1)
|
| 203 |
inter = w * h
|
| 204 |
iou = inter / (areas[i] + areas[order[1:]] - inter + 1e-6)
|
| 205 |
-
inds = np.where(iou <= iou_threshold)[0] #
|
| 206 |
order = order[inds + 1]
|
| 207 |
return keep
|
| 208 |
|
|
@@ -222,7 +223,7 @@ class YoloV5FaceDetector:
|
|
| 222 |
self.input_size = int(shape[2])
|
| 223 |
|
| 224 |
@staticmethod
|
| 225 |
-
def _xywh2xyxy(x: np.ndarray) -> np.ndarray: #
|
| 226 |
y = np.zeros_like(x)
|
| 227 |
y[:, 0] = x[:, 0] - x[:, 2] / 2
|
| 228 |
y[:, 1] = x[:, 1] - x[:, 3] / 2
|
|
@@ -243,7 +244,7 @@ class YoloV5FaceDetector:
|
|
| 243 |
if preds.ndim != 2:
|
| 244 |
raise RuntimeError(f"Unexpected YOLO output shape: {preds.shape}")
|
| 245 |
num_attrs = preds.shape[1]
|
| 246 |
-
has_landmarks = num_attrs >= 15 #
|
| 247 |
boxes_xywh = preds[:, 0:4]
|
| 248 |
if has_landmarks:
|
| 249 |
scores = preds[:, 4]
|
|
@@ -255,7 +256,7 @@ class YoloV5FaceDetector:
|
|
| 255 |
else:
|
| 256 |
class_conf = cls_scores.max(axis=1, keepdims=True)
|
| 257 |
scores = (obj * class_conf).squeeze(-1)
|
| 258 |
-
keep = scores > self.conf_threshold #
|
| 259 |
boxes_xywh = boxes_xywh[keep]
|
| 260 |
scores = scores[keep]
|
| 261 |
if boxes_xywh.shape[0] == 0:
|
|
@@ -269,16 +270,16 @@ class YoloV5FaceDetector:
|
|
| 269 |
boxes_xyxy[:, 2] = np.clip(boxes_xyxy[:, 2], 0, w0 - 1)
|
| 270 |
boxes_xyxy[:, 3] = np.clip(boxes_xyxy[:, 3], 0, h0 - 1)
|
| 271 |
keep_inds = _nms(boxes_xyxy, scores, self.iou_threshold)
|
| 272 |
-
if len(keep_inds) > max_det: #
|
| 273 |
keep_inds = keep_inds[:max_det]
|
| 274 |
dets = []
|
| 275 |
for i in keep_inds:
|
| 276 |
dets.append({"bbox": boxes_xyxy[i].tolist(), "score": float(scores[i])})
|
| 277 |
return dets
|
| 278 |
|
| 279 |
-
# ---------------- Anti-Spoof Model (
|
| 280 |
|
| 281 |
-
def _sigmoid(x: np.ndarray) -> np.ndarray: #
|
| 282 |
return 1.0 / (1.0 + np.exp(-x))
|
| 283 |
|
| 284 |
class AntiSpoofBinary:
|
|
@@ -297,7 +298,7 @@ class AntiSpoofBinary:
|
|
| 297 |
self.input_name = self.session.get_inputs()[0].name
|
| 298 |
self.output_names = [o.name for o in self.session.get_outputs()]
|
| 299 |
|
| 300 |
-
def _preprocess(self, face_bgr: np.ndarray) -> np.ndarray: #
|
| 301 |
img = cv2.resize(face_bgr, (self.input_size, self.input_size), interpolation=cv2.INTER_LINEAR)
|
| 302 |
if self.rgb:
|
| 303 |
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
@@ -308,11 +309,11 @@ class AntiSpoofBinary:
|
|
| 308 |
img = np.expand_dims(img, 0).astype(np.float32)
|
| 309 |
return img
|
| 310 |
|
| 311 |
-
def predict_live_prob(self, face_bgr: np.ndarray) -> float: #
|
| 312 |
inp = self._preprocess(face_bgr)
|
| 313 |
outs = self.session.run(self.output_names, {self.input_name: inp})
|
| 314 |
out = outs[0]
|
| 315 |
-
if out.ndim > 1: #
|
| 316 |
out = np.squeeze(out, axis=0)
|
| 317 |
if out.size == 2:
|
| 318 |
vec = out.astype(np.float32)
|
|
@@ -348,7 +349,7 @@ def draw_live_overlay(img_bgr: np.ndarray, bbox, label: str, prob: float, color)
|
|
| 348 |
cv2.rectangle(img_bgr, (x1, y_top), (x1 + tw + 6, y_top + th + 6), color, -1)
|
| 349 |
cv2.putText(img_bgr, text, (x1 + 3, y_top + th), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2, cv2.LINE_AA)
|
| 350 |
|
| 351 |
-
def image_to_data_uri(img_bgr: np.ndarray) -> Optional[str]: #
|
| 352 |
success, buf = cv2.imencode(".jpg", img_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
|
| 353 |
if not success:
|
| 354 |
return None
|
|
@@ -389,7 +390,7 @@ try:
|
|
| 389 |
except Exception as e:
|
| 390 |
print(f"Error loading anti-spoofing model: {e}")
|
| 391 |
|
| 392 |
-
# ----------------------------- DeepFace Recognition
|
| 393 |
|
| 394 |
def get_face_features_deepface(image):
|
| 395 |
"""Extract face features using DeepFace with timeout protection"""
|
|
@@ -402,7 +403,7 @@ def get_face_features_deepface(image):
|
|
| 402 |
enforce_detection=False
|
| 403 |
)
|
| 404 |
|
| 405 |
-
if isinstance(embedding, list) and len(embedding) > 0: #
|
| 406 |
return np.array(embedding[0]['embedding'])
|
| 407 |
else:
|
| 408 |
return np.array(embedding['embedding']) if 'embedding' in embedding else None
|
|
@@ -440,8 +441,6 @@ def recognize_face_deepface(image, user_id, user_type='student'):
|
|
| 440 |
return False, "No face detected in reference image"
|
| 441 |
|
| 442 |
# Calculate cosine similarity
|
| 443 |
-
from sklearn.metrics.pairwise import cosine_similarity
|
| 444 |
-
|
| 445 |
similarity = cosine_similarity([features], [ref_features])[0][0]
|
| 446 |
distance = 1 - similarity
|
| 447 |
threshold = 0.4
|
|
@@ -450,7 +449,7 @@ def recognize_face_deepface(image, user_id, user_type='student'):
|
|
| 450 |
inference_times.append(inference_time)
|
| 451 |
total_attempts += 1
|
| 452 |
|
| 453 |
-
if distance < threshold: #
|
| 454 |
correct_recognitions += 1
|
| 455 |
return True, f"Face recognized (distance={distance:.3f}, similarity={similarity:.3f}, time={inference_time:.2f}s)"
|
| 456 |
else:
|
|
@@ -463,7 +462,7 @@ def recognize_face_deepface(image, user_id, user_type='student'):
|
|
| 463 |
def recognize_face(image, user_id, user_type='student'):
|
| 464 |
return recognize_face_deepface(image, user_id, user_type)
|
| 465 |
|
| 466 |
-
# ---------------------- Metrics helpers
|
| 467 |
|
| 468 |
def log_metrics_event(event: dict):
|
| 469 |
try:
|
|
@@ -505,7 +504,7 @@ def log_metrics_event_normalized(
|
|
| 505 |
}
|
| 506 |
log_metrics_event(doc)
|
| 507 |
|
| 508 |
-
def classify_event(ev: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]: #
|
| 509 |
if ev.get("event"):
|
| 510 |
e = ev.get("event")
|
| 511 |
at = ev.get("attempt_type")
|
|
@@ -590,14 +589,14 @@ def compute_metrics(limit: int = 10000):
|
|
| 590 |
}
|
| 591 |
}
|
| 592 |
|
| 593 |
-
def compute_latency_avg(limit: int = 300) -> Optional[float]: #
|
| 594 |
cursor = metrics_events.find({"latency_ms": {"$exists": True}}, {"latency_ms": 1, "_id": 0}).sort("ts", -1).limit(limit)
|
| 595 |
vals = [float(d["latency_ms"]) for d in cursor if isinstance(d.get("latency_ms"), (int, float))]
|
| 596 |
if not vals:
|
| 597 |
return None
|
| 598 |
return sum(vals) / len(vals)
|
| 599 |
|
| 600 |
-
# --------- ALL ROUTES
|
| 601 |
|
| 602 |
@app.route('/')
|
| 603 |
def home():
|
|
@@ -699,7 +698,7 @@ def face_login():
|
|
| 699 |
flash('Invalid role selected for face login.', 'danger')
|
| 700 |
return redirect(url_for('login_page'))
|
| 701 |
|
| 702 |
-
users = collection.find({'face_image': {'$exists': True, '$ne': None}}).limit(20)
|
| 703 |
test_features = get_face_features_deepface(image)
|
| 704 |
|
| 705 |
if test_features is None:
|
|
@@ -716,11 +715,10 @@ def face_login():
|
|
| 716 |
if ref_features is None:
|
| 717 |
continue
|
| 718 |
|
| 719 |
-
from sklearn.metrics.pairwise import cosine_similarity
|
| 720 |
similarity = cosine_similarity([test_features], [ref_features])[0][0]
|
| 721 |
distance = 1 - similarity
|
| 722 |
|
| 723 |
-
if distance < 0.4: #
|
| 724 |
session['logged_in'] = True
|
| 725 |
session['user_type'] = face_role
|
| 726 |
session[id_field] = user[id_field]
|
|
@@ -762,7 +760,7 @@ def auto_face_login():
|
|
| 762 |
id_field = 'student_id'
|
| 763 |
dashboard_route = '/dashboard'
|
| 764 |
|
| 765 |
-
users = collection.find({'face_image': {'$exists': True, '$ne': None}}).limit(20)
|
| 766 |
for user in users:
|
| 767 |
try:
|
| 768 |
ref_image_array = np.frombuffer(user['face_image'], np.uint8)
|
|
@@ -771,11 +769,10 @@ def auto_face_login():
|
|
| 771 |
if ref_features is None:
|
| 772 |
continue
|
| 773 |
|
| 774 |
-
from sklearn.metrics.pairwise import cosine_similarity
|
| 775 |
similarity = cosine_similarity([test_features], [ref_features])[0][0]
|
| 776 |
distance = 1 - similarity
|
| 777 |
|
| 778 |
-
if distance < 0.4: #
|
| 779 |
session['logged_in'] = True
|
| 780 |
session['user_type'] = face_role
|
| 781 |
session[id_field] = user[id_field]
|
|
@@ -887,7 +884,7 @@ def mark_attendance():
|
|
| 887 |
|
| 888 |
if anti_spoof_bin:
|
| 889 |
live_prob = anti_spoof_bin.predict_live_prob(face_crop)
|
| 890 |
-
is_live = live_prob >= 0.7 #
|
| 891 |
|
| 892 |
label = "LIVE" if is_live else "SPOOF"
|
| 893 |
color = (0, 200, 0) if is_live else (0, 0, 255)
|
|
@@ -1044,13 +1041,13 @@ def liveness_preview():
|
|
| 1044 |
live_prob = anti_spoof_bin.predict_live_prob(face_crop)
|
| 1045 |
|
| 1046 |
threshold = 0.7
|
| 1047 |
-
label = "LIVE" if live_prob >= threshold else "SPOOF" #
|
| 1048 |
color = (0, 200, 0) if label == "LIVE" else (0, 0, 255)
|
| 1049 |
draw_live_overlay(vis, (x1e, y1e, x2e, y2e), label, live_prob, color)
|
| 1050 |
overlay_data = image_to_data_uri(vis)
|
| 1051 |
return jsonify({
|
| 1052 |
'success': True,
|
| 1053 |
-
'live': bool(live_prob >= threshold), #
|
| 1054 |
'live_prob': float(live_prob),
|
| 1055 |
'overlay': overlay_data
|
| 1056 |
})
|
|
@@ -1058,7 +1055,7 @@ def liveness_preview():
|
|
| 1058 |
print("liveness_preview error:", e)
|
| 1059 |
return jsonify({'success': False, 'message': 'Server error during preview'})
|
| 1060 |
|
| 1061 |
-
# --------- TEACHER ROUTES
|
| 1062 |
@app.route('/teacher_register.html')
|
| 1063 |
def teacher_register_page():
|
| 1064 |
return render_template('teacher_register.html')
|
|
@@ -1162,7 +1159,7 @@ def metrics_data():
|
|
| 1162 |
if r.get("decision") == "spoof_blocked":
|
| 1163 |
r["liveness_pass"] = False
|
| 1164 |
elif isinstance(r.get("live_prob"), (int, float)):
|
| 1165 |
-
r["liveness_pass"] = bool(r["live_prob"] >= 0.7) #
|
| 1166 |
else:
|
| 1167 |
r["liveness_pass"] = None
|
| 1168 |
normalized_recent.append(r)
|
|
@@ -1183,9 +1180,9 @@ def metrics_json():
|
|
| 1183 |
frr_pct = rates["FRR"] * 100.0
|
| 1184 |
|
| 1185 |
return jsonify({
|
| 1186 |
-
'Accuracy': f"{accuracy_pct:.2f}%" if totals["totalAttempts"] > 0 else "N/A", #
|
| 1187 |
-
'False Accepts (FAR)': f"{far_pct:.2f}%" if counts["impostorAttempts"] > 0 else "N/A", #
|
| 1188 |
-
'False Rejects (FRR)': f"{frr_pct:.2f}%" if counts["genuineAttempts"] > 0 else "N/A", #
|
| 1189 |
'Average Inference Time (s)': f"{(avg_latency/1000.0):.2f}" if isinstance(avg_latency, (int, float)) else "N/A",
|
| 1190 |
'Correct Recognitions': counts["trueAccepts"],
|
| 1191 |
'Total Attempts': totals["totalAttempts"],
|
|
|
|
| 13 |
import requests
|
| 14 |
from typing import Optional, Dict, Tuple, Any
|
| 15 |
from deepface import DeepFace
|
| 16 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
| 17 |
import tensorflow as tf
|
| 18 |
|
| 19 |
# Optimize TensorFlow for HuggingFace Spaces
|
|
|
|
| 61 |
except Exception as e:
|
| 62 |
print(f"MongoDB connection error: {e}")
|
| 63 |
|
| 64 |
+
# ---------------- Model Download Functions (FIXED) ----------------
|
| 65 |
|
| 66 |
def download_file_from_google_drive(file_id, destination):
|
| 67 |
"""Download file from Google Drive - Fixed version"""
|
| 68 |
try:
|
| 69 |
if not os.path.exists(destination):
|
| 70 |
print(f"Downloading {destination}...")
|
| 71 |
+
# FIXED URL format without HTML entities
|
| 72 |
url = f"https://drive.google.com/uc?export=download&id={file_id}"
|
| 73 |
|
| 74 |
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
|
|
|
| 148 |
print("Setting up models...")
|
| 149 |
yolo_available, antispoof_available = setup_models()
|
| 150 |
|
| 151 |
+
# ---------------- YOLO Face Detection (FIXED) ----------------
|
| 152 |
|
| 153 |
def _get_providers():
|
| 154 |
available = ort.get_available_providers()
|
|
|
|
| 190 |
areas = (x2 - x1) * (y2 - y1)
|
| 191 |
order = scores.argsort()[::-1]
|
| 192 |
keep = []
|
| 193 |
+
while order.size > 0: # FIXED: removed HTML entity
|
| 194 |
i = int(order[0])
|
| 195 |
keep.append(i)
|
| 196 |
if order.size == 1:
|
|
|
|
| 203 |
h = np.maximum(0.0, yy2 - yy1)
|
| 204 |
inter = w * h
|
| 205 |
iou = inter / (areas[i] + areas[order[1:]] - inter + 1e-6)
|
| 206 |
+
inds = np.where(iou <= iou_threshold)[0] # FIXED: removed HTML entity
|
| 207 |
order = order[inds + 1]
|
| 208 |
return keep
|
| 209 |
|
|
|
|
| 223 |
self.input_size = int(shape[2])
|
| 224 |
|
| 225 |
@staticmethod
|
| 226 |
+
def _xywh2xyxy(x: np.ndarray) -> np.ndarray: # FIXED: removed HTML entity
|
| 227 |
y = np.zeros_like(x)
|
| 228 |
y[:, 0] = x[:, 0] - x[:, 2] / 2
|
| 229 |
y[:, 1] = x[:, 1] - x[:, 3] / 2
|
|
|
|
| 244 |
if preds.ndim != 2:
|
| 245 |
raise RuntimeError(f"Unexpected YOLO output shape: {preds.shape}")
|
| 246 |
num_attrs = preds.shape[1]
|
| 247 |
+
has_landmarks = num_attrs >= 15 # FIXED: removed HTML entity
|
| 248 |
boxes_xywh = preds[:, 0:4]
|
| 249 |
if has_landmarks:
|
| 250 |
scores = preds[:, 4]
|
|
|
|
| 256 |
else:
|
| 257 |
class_conf = cls_scores.max(axis=1, keepdims=True)
|
| 258 |
scores = (obj * class_conf).squeeze(-1)
|
| 259 |
+
keep = scores > self.conf_threshold # FIXED: removed HTML entity
|
| 260 |
boxes_xywh = boxes_xywh[keep]
|
| 261 |
scores = scores[keep]
|
| 262 |
if boxes_xywh.shape[0] == 0:
|
|
|
|
| 270 |
boxes_xyxy[:, 2] = np.clip(boxes_xyxy[:, 2], 0, w0 - 1)
|
| 271 |
boxes_xyxy[:, 3] = np.clip(boxes_xyxy[:, 3], 0, h0 - 1)
|
| 272 |
keep_inds = _nms(boxes_xyxy, scores, self.iou_threshold)
|
| 273 |
+
if len(keep_inds) > max_det: # FIXED: removed HTML entity
|
| 274 |
keep_inds = keep_inds[:max_det]
|
| 275 |
dets = []
|
| 276 |
for i in keep_inds:
|
| 277 |
dets.append({"bbox": boxes_xyxy[i].tolist(), "score": float(scores[i])})
|
| 278 |
return dets
|
| 279 |
|
| 280 |
+
# ---------------- Anti-Spoof Model (FIXED) ----------------
|
| 281 |
|
| 282 |
+
def _sigmoid(x: np.ndarray) -> np.ndarray: # FIXED: removed HTML entity
|
| 283 |
return 1.0 / (1.0 + np.exp(-x))
|
| 284 |
|
| 285 |
class AntiSpoofBinary:
|
|
|
|
| 298 |
self.input_name = self.session.get_inputs()[0].name
|
| 299 |
self.output_names = [o.name for o in self.session.get_outputs()]
|
| 300 |
|
| 301 |
+
def _preprocess(self, face_bgr: np.ndarray) -> np.ndarray: # FIXED: removed HTML entity
|
| 302 |
img = cv2.resize(face_bgr, (self.input_size, self.input_size), interpolation=cv2.INTER_LINEAR)
|
| 303 |
if self.rgb:
|
| 304 |
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
|
|
| 309 |
img = np.expand_dims(img, 0).astype(np.float32)
|
| 310 |
return img
|
| 311 |
|
| 312 |
+
def predict_live_prob(self, face_bgr: np.ndarray) -> float: # FIXED: removed HTML entity
|
| 313 |
inp = self._preprocess(face_bgr)
|
| 314 |
outs = self.session.run(self.output_names, {self.input_name: inp})
|
| 315 |
out = outs[0]
|
| 316 |
+
if out.ndim > 1: # FIXED: removed HTML entity
|
| 317 |
out = np.squeeze(out, axis=0)
|
| 318 |
if out.size == 2:
|
| 319 |
vec = out.astype(np.float32)
|
|
|
|
| 349 |
cv2.rectangle(img_bgr, (x1, y_top), (x1 + tw + 6, y_top + th + 6), color, -1)
|
| 350 |
cv2.putText(img_bgr, text, (x1 + 3, y_top + th), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2, cv2.LINE_AA)
|
| 351 |
|
| 352 |
+
def image_to_data_uri(img_bgr: np.ndarray) -> Optional[str]: # FIXED: removed HTML entity
|
| 353 |
success, buf = cv2.imencode(".jpg", img_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
|
| 354 |
if not success:
|
| 355 |
return None
|
|
|
|
| 390 |
except Exception as e:
|
| 391 |
print(f"Error loading anti-spoofing model: {e}")
|
| 392 |
|
| 393 |
+
# ----------------------------- DeepFace Recognition -----------------------------
|
| 394 |
|
| 395 |
def get_face_features_deepface(image):
|
| 396 |
"""Extract face features using DeepFace with timeout protection"""
|
|
|
|
| 403 |
enforce_detection=False
|
| 404 |
)
|
| 405 |
|
| 406 |
+
if isinstance(embedding, list) and len(embedding) > 0: # FIXED: removed HTML entity
|
| 407 |
return np.array(embedding[0]['embedding'])
|
| 408 |
else:
|
| 409 |
return np.array(embedding['embedding']) if 'embedding' in embedding else None
|
|
|
|
| 441 |
return False, "No face detected in reference image"
|
| 442 |
|
| 443 |
# Calculate cosine similarity
|
|
|
|
|
|
|
| 444 |
similarity = cosine_similarity([features], [ref_features])[0][0]
|
| 445 |
distance = 1 - similarity
|
| 446 |
threshold = 0.4
|
|
|
|
| 449 |
inference_times.append(inference_time)
|
| 450 |
total_attempts += 1
|
| 451 |
|
| 452 |
+
if distance < threshold: # FIXED: removed HTML entity
|
| 453 |
correct_recognitions += 1
|
| 454 |
return True, f"Face recognized (distance={distance:.3f}, similarity={similarity:.3f}, time={inference_time:.2f}s)"
|
| 455 |
else:
|
|
|
|
| 462 |
def recognize_face(image, user_id, user_type='student'):
|
| 463 |
return recognize_face_deepface(image, user_id, user_type)
|
| 464 |
|
| 465 |
+
# ---------------------- Metrics helpers ----------------------
|
| 466 |
|
| 467 |
def log_metrics_event(event: dict):
|
| 468 |
try:
|
|
|
|
| 504 |
}
|
| 505 |
log_metrics_event(doc)
|
| 506 |
|
| 507 |
+
def classify_event(ev: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]: # FIXED: removed HTML entity
|
| 508 |
if ev.get("event"):
|
| 509 |
e = ev.get("event")
|
| 510 |
at = ev.get("attempt_type")
|
|
|
|
| 589 |
}
|
| 590 |
}
|
| 591 |
|
| 592 |
+
def compute_latency_avg(limit: int = 300) -> Optional[float]: # FIXED: removed HTML entity
|
| 593 |
cursor = metrics_events.find({"latency_ms": {"$exists": True}}, {"latency_ms": 1, "_id": 0}).sort("ts", -1).limit(limit)
|
| 594 |
vals = [float(d["latency_ms"]) for d in cursor if isinstance(d.get("latency_ms"), (int, float))]
|
| 595 |
if not vals:
|
| 596 |
return None
|
| 597 |
return sum(vals) / len(vals)
|
| 598 |
|
| 599 |
+
# --------- ALL ROUTES ---------
|
| 600 |
|
| 601 |
@app.route('/')
|
| 602 |
def home():
|
|
|
|
| 698 |
flash('Invalid role selected for face login.', 'danger')
|
| 699 |
return redirect(url_for('login_page'))
|
| 700 |
|
| 701 |
+
users = collection.find({'face_image': {'$exists': True, '$ne': None}}).limit(20)
|
| 702 |
test_features = get_face_features_deepface(image)
|
| 703 |
|
| 704 |
if test_features is None:
|
|
|
|
| 715 |
if ref_features is None:
|
| 716 |
continue
|
| 717 |
|
|
|
|
| 718 |
similarity = cosine_similarity([test_features], [ref_features])[0][0]
|
| 719 |
distance = 1 - similarity
|
| 720 |
|
| 721 |
+
if distance < 0.4: # FIXED: removed HTML entity
|
| 722 |
session['logged_in'] = True
|
| 723 |
session['user_type'] = face_role
|
| 724 |
session[id_field] = user[id_field]
|
|
|
|
| 760 |
id_field = 'student_id'
|
| 761 |
dashboard_route = '/dashboard'
|
| 762 |
|
| 763 |
+
users = collection.find({'face_image': {'$exists': True, '$ne': None}}).limit(20)
|
| 764 |
for user in users:
|
| 765 |
try:
|
| 766 |
ref_image_array = np.frombuffer(user['face_image'], np.uint8)
|
|
|
|
| 769 |
if ref_features is None:
|
| 770 |
continue
|
| 771 |
|
|
|
|
| 772 |
similarity = cosine_similarity([test_features], [ref_features])[0][0]
|
| 773 |
distance = 1 - similarity
|
| 774 |
|
| 775 |
+
if distance < 0.4: # FIXED: removed HTML entity
|
| 776 |
session['logged_in'] = True
|
| 777 |
session['user_type'] = face_role
|
| 778 |
session[id_field] = user[id_field]
|
|
|
|
| 884 |
|
| 885 |
if anti_spoof_bin:
|
| 886 |
live_prob = anti_spoof_bin.predict_live_prob(face_crop)
|
| 887 |
+
is_live = live_prob >= 0.7 # FIXED: removed HTML entity
|
| 888 |
|
| 889 |
label = "LIVE" if is_live else "SPOOF"
|
| 890 |
color = (0, 200, 0) if is_live else (0, 0, 255)
|
|
|
|
| 1041 |
live_prob = anti_spoof_bin.predict_live_prob(face_crop)
|
| 1042 |
|
| 1043 |
threshold = 0.7
|
| 1044 |
+
label = "LIVE" if live_prob >= threshold else "SPOOF" # FIXED: removed HTML entity
|
| 1045 |
color = (0, 200, 0) if label == "LIVE" else (0, 0, 255)
|
| 1046 |
draw_live_overlay(vis, (x1e, y1e, x2e, y2e), label, live_prob, color)
|
| 1047 |
overlay_data = image_to_data_uri(vis)
|
| 1048 |
return jsonify({
|
| 1049 |
'success': True,
|
| 1050 |
+
'live': bool(live_prob >= threshold), # FIXED: removed HTML entity
|
| 1051 |
'live_prob': float(live_prob),
|
| 1052 |
'overlay': overlay_data
|
| 1053 |
})
|
|
|
|
| 1055 |
print("liveness_preview error:", e)
|
| 1056 |
return jsonify({'success': False, 'message': 'Server error during preview'})
|
| 1057 |
|
| 1058 |
+
# --------- TEACHER ROUTES ---------
|
| 1059 |
@app.route('/teacher_register.html')
|
| 1060 |
def teacher_register_page():
|
| 1061 |
return render_template('teacher_register.html')
|
|
|
|
| 1159 |
if r.get("decision") == "spoof_blocked":
|
| 1160 |
r["liveness_pass"] = False
|
| 1161 |
elif isinstance(r.get("live_prob"), (int, float)):
|
| 1162 |
+
r["liveness_pass"] = bool(r["live_prob"] >= 0.7) # FIXED: removed HTML entity
|
| 1163 |
else:
|
| 1164 |
r["liveness_pass"] = None
|
| 1165 |
normalized_recent.append(r)
|
|
|
|
| 1180 |
frr_pct = rates["FRR"] * 100.0
|
| 1181 |
|
| 1182 |
return jsonify({
|
| 1183 |
+
'Accuracy': f"{accuracy_pct:.2f}%" if totals["totalAttempts"] > 0 else "N/A", # FIXED: removed HTML entity
|
| 1184 |
+
'False Accepts (FAR)': f"{far_pct:.2f}%" if counts["impostorAttempts"] > 0 else "N/A", # FIXED: removed HTML entity
|
| 1185 |
+
'False Rejects (FRR)': f"{frr_pct:.2f}%" if counts["genuineAttempts"] > 0 else "N/A", # FIXED: removed HTML entity
|
| 1186 |
'Average Inference Time (s)': f"{(avg_latency/1000.0):.2f}" if isinstance(avg_latency, (int, float)) else "N/A",
|
| 1187 |
'Correct Recognitions': counts["trueAccepts"],
|
| 1188 |
'Total Attempts': totals["totalAttempts"],
|
requirements.txt
CHANGED
|
@@ -3,7 +3,7 @@ pymongo==4.5.0
|
|
| 3 |
python-dotenv==1.0.0
|
| 4 |
opencv-python-headless==4.8.1.78
|
| 5 |
numpy==1.24.3
|
| 6 |
-
onnxruntime==1.15.1
|
| 7 |
requests==2.31.0
|
| 8 |
deepface==0.0.79
|
| 9 |
tensorflow==2.13.0
|
|
|
|
| 3 |
python-dotenv==1.0.0
|
| 4 |
opencv-python-headless==4.8.1.78
|
| 5 |
numpy==1.24.3
|
| 6 |
+
onnxruntime-cpu==1.15.1
|
| 7 |
requests==2.31.0
|
| 8 |
deepface==0.0.79
|
| 9 |
tensorflow==2.13.0
|