kkt-2002 commited on
Commit
83ac47a
·
1 Parent(s): a9ee815

Fix all HTML entities and onnxruntime issues

Browse files
Files changed (3) hide show
  1. Dockerfile +1 -1
  2. app.py +35 -38
  3. 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 VERSION)
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 (Fixed) ----------------
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
- # Fixed URL format without HTML entities
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 (Fixed HTML entities) ----------------
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: # Fixed: removed HTML entity
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] # Fixed: removed HTML entity
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: # Fixed: removed HTML entity
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 # Fixed: removed HTML entity
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 # Fixed: removed HTML entity
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: # Fixed: removed HTML entity
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 (Fixed) ----------------
280
 
281
- def _sigmoid(x: np.ndarray) -> np.ndarray: # Fixed: removed HTML entity
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: # Fixed: removed HTML entity
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: # Fixed: removed HTML entity
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: # Fixed: removed HTML entity
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]: # Fixed: removed HTML entity
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 (with timeout) -----------------------------
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: # Fixed: removed HTML entity
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: # Fixed: removed HTML entity
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 (same as before but fixed HTML entities) ----------------------
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]]: # Fixed: removed HTML entity
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]: # Fixed: removed HTML entity
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 (keeping all your existing 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) # Limit for performance
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: # Fixed: removed HTML entity
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) # Limit for performance
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: # Fixed: removed HTML entity
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 # Fixed: removed HTML entity
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" # Fixed: removed HTML entity
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), # Fixed: removed HTML entity
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 (same as before) ---------
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) # Fixed: removed HTML entity
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", # Fixed: removed HTML entity
1187
- 'False Accepts (FAR)': f"{far_pct:.2f}%" if counts["impostorAttempts"] > 0 else "N/A", # Fixed: removed HTML entity
1188
- 'False Rejects (FRR)': f"{frr_pct:.2f}%" if counts["genuineAttempts"] > 0 else "N/A", # Fixed: removed HTML entity
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