lifedebugger commited on
Commit
bfa6bbb
·
verified ·
1 Parent(s): d6aacc9

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +127 -326
main.py CHANGED
@@ -2,7 +2,7 @@ import os
2
  import base64
3
  import asyncio
4
  import json
5
- from typing import List, Dict, Optional, Iterable
6
 
7
  import numpy as np
8
  import cv2
@@ -12,445 +12,246 @@ from starlette.concurrency import run_in_threadpool
12
  from starlette.middleware.cors import CORSMiddleware
13
  from fastapi.responses import JSONResponse
14
 
15
- # Import Uniface (Single Library)
16
  from uniface import RetinaFace, ArcFace
17
 
18
- # Konfigurasi Threshold
19
- # Stage 1: Detection Confidence (Pengganti Liveness Check sementara)
20
- # Wajah dengan confidence di bawah ini dianggap tidak valid/buruk
21
- DETECTION_THRESHOLD = 0.70
22
-
23
- # Stage 2: Recognition Similarity (Cosine Similarity)
24
- SIM_THRESHOLD = 0.40
25
-
26
- app = FastAPI(
27
- title="Face Verification API (Uniface Pure)",
28
- version="2.2.0",
29
- description="2-Stage Verification: High-Confidence Detection -> Uniface Recognition",
30
- )
31
-
32
- app.add_middleware(
33
- CORSMiddleware,
34
- allow_origins=["*"],
35
- allow_credentials=True,
36
- allow_methods=["*"],
37
- allow_headers=["*"],
38
- )
39
 
40
  FACE_DB_ROOT = "face_db"
41
- os.makedirs(FACE_DB_ROOT, exist_ok=True)
42
-
43
  LIVE_DB_ROOT = os.path.join(FACE_DB_ROOT, "_live")
 
 
44
  os.makedirs(LIVE_DB_ROOT, exist_ok=True)
45
 
46
  processing_lock = asyncio.Lock()
47
 
48
- # --- INITIALIZATION (UNIFACE ONLY) ---
 
 
49
  detector = None
50
  recognizer = None
51
 
52
  try:
53
- print("[Uniface] Initializing RetinaFace (Stage 1)...")
54
- detector = RetinaFace()
55
- print("[Uniface] Initializing ArcFace (Stage 2)...")
56
  recognizer = ArcFace()
57
  except Exception as e:
58
- print(f"[Uniface] Error initializing models: {e}")
59
 
 
 
 
 
 
 
 
60
 
 
 
 
 
 
 
 
 
 
 
 
61
  class EnrollRequest(BaseModel):
62
- employee_id: str = Field(default="1")
63
- images: List[str] = Field(..., description="Base64 images")
64
 
65
  class VerifyImageRequest(BaseModel):
66
- employee_id: str = Field(default="1")
67
- image: str = Field(..., description="Base64 image")
68
- threshold: Optional[float] = Field(default=None)
69
 
70
  class ClearRequest(BaseModel):
71
- employee_id: str = Field(default="1")
72
-
73
 
 
 
 
74
  def _strip_b64(s: str) -> str:
75
- if isinstance(s, str) and "," in s and s.lstrip().lower().startswith("data:"):
76
  return s.split(",", 1)[1]
77
  return s
78
 
79
- def _b64_to_bgr(b64: str) -> Optional[np.ndarray]:
80
  try:
81
  raw = base64.b64decode(_strip_b64(b64), validate=False)
82
- arr = np.frombuffer(raw, np.uint8)
83
- return cv2.imdecode(arr, cv2.IMREAD_COLOR)
84
  except Exception:
85
  return None
86
 
87
- def _bytes_to_bgr(data: bytes) -> Optional[np.ndarray]:
88
  try:
89
- arr = np.frombuffer(data, np.uint8)
90
- return cv2.imdecode(arr, cv2.IMREAD_COLOR)
91
  except Exception:
92
  return None
93
 
94
- def _frame_path_for(employee_id: str) -> str:
95
- safe = (employee_id or "live").replace("/", "_")
96
  return os.path.join(LIVE_DB_ROOT, f"{safe}.jpg")
97
 
98
- async def _decode_image_from_request(request: Request, field_names: Iterable[str] = ("frame", "image")) -> Optional[np.ndarray]:
99
- ct = (request.headers.get("content-type") or "").lower()
100
- if "multipart/form-data" in ct:
101
- form = await request.form()
102
- for name in field_names:
103
- file = form.get(name)
104
- if isinstance(file, UploadFile):
105
- data = await file.read()
106
- return _bytes_to_bgr(data)
107
- for name in field_names:
108
- raw = form.get(name)
109
- if isinstance(raw, (bytes, bytearray)):
110
- return _bytes_to_bgr(bytes(raw))
111
- if isinstance(raw, str):
112
- img = _b64_to_bgr(raw)
113
- if img is not None:
114
- return img
115
- return None
116
- if "application/json" in ct or "text/json" in ct:
117
- try:
118
- obj = await request.json()
119
- except Exception:
120
- return None
121
- for name in field_names:
122
- val = obj.get(name)
123
- if isinstance(val, str):
124
- img = _b64_to_bgr(val)
125
- if img is not None:
126
- return img
127
- return None
128
- return None
129
-
130
- def _compute_embedding_uniface(img_bgr, landmarks):
131
- """Helper to get embedding using Uniface ArcFace"""
132
- if recognizer is None:
133
- return None
134
  try:
135
- # get_normalized_embedding requires the image and landmarks
136
- embedding = recognizer.get_normalized_embedding(img_bgr, landmarks)
137
- return embedding
138
  except Exception:
139
  return None
140
 
 
 
 
141
  async def _run_pipeline(img_path: str, target_id: str):
142
  if detector is None or recognizer is None:
143
  return None
144
 
145
- # Read image
146
  img = cv2.imread(img_path)
147
  if img is None:
148
  return None
149
 
150
- # --- STAGE 1: DETECTION & QUALITY CHECK ---
151
- # Menggunakan RetinaFace untuk mendeteksi wajah
152
  async with processing_lock:
153
  faces = await run_in_threadpool(detector.detect, img)
154
-
155
  if not faces:
156
  return None
157
 
158
- # Ambil wajah dengan confidence tertinggi atau area terbesar
159
- target_face = faces[0]
160
- bbox = target_face['bbox'] # [x1, y1, x2, y2]
161
- landmarks = target_face['landmarks']
162
- confidence = target_face['confidence']
163
-
164
- x1, y1, x2, y2 = map(int, bbox)
165
-
166
- # Filter Stage 1: Check Confidence
167
- # Jika confidence rendah, anggap sebagai "Fake" atau "Low Quality" dan tolak
168
- if confidence < DETECTION_THRESHOLD:
169
  return {
170
- "bbox": [float(x1), float(y1), float(x2), float(y2)],
171
  "match_user": None,
172
  "confidence": 0.0,
173
  "authorized": False,
174
- "det_score": float(confidence),
175
- "fake": True, # Flagged as fake/bad quality
176
  "model_threshold": DETECTION_THRESHOLD,
177
  "raw_distance": 0.0,
178
- "reason": "Low quality/confidence detection"
179
  }
180
 
181
- # --- STAGE 2: RECOGNITION (ArcFace) ---
182
- # Jika lolos Stage 1, lanjut ke Recognition
183
- probe_emb = await run_in_threadpool(_compute_embedding_uniface, img, landmarks)
184
  if probe_emb is None:
185
  return None
186
 
187
- best_score = -1.0
188
  best_user = None
189
 
190
- # Determine which users to check
191
  search_dirs = []
192
  if target_id == "*":
193
- for d in os.listdir(FACE_DB_ROOT):
194
- if not d.startswith("_"):
195
- search_dirs.append(d)
196
  else:
197
- if os.path.exists(os.path.join(FACE_DB_ROOT, target_id)):
198
- search_dirs.append(target_id)
199
 
200
- # Search in DB
201
  for uid in search_dirs:
202
  user_dir = os.path.join(FACE_DB_ROOT, uid)
203
  for fname in os.listdir(user_dir):
204
- if not fname.lower().endswith(('.jpg', '.png', '.jpeg')):
205
  continue
206
-
207
- ref_path = os.path.join(user_dir, fname)
208
-
209
- try:
210
- # Load ref image
211
- # Note: In production, embeddings should be cached in memory/database
212
- ref_img = cv2.imread(ref_path)
213
- if ref_img is None: continue
214
-
215
- # Detect face in ref image to get landmarks
216
- ref_faces = detector.detect(ref_img)
217
- if not ref_faces:
218
- continue
219
-
220
- # Get embedding
221
- ref_emb = _compute_embedding_uniface(ref_img, ref_faces[0]['landmarks'])
222
-
223
- if ref_emb is not None:
224
- # Cosine similarity
225
- sim = np.dot(probe_emb, ref_emb)
226
- if sim > best_score:
227
- best_score = sim
228
- best_user = uid
229
- except Exception:
230
  continue
231
 
232
- authorized = (best_score > SIM_THRESHOLD)
233
-
 
 
 
 
 
 
 
 
 
234
  return {
235
- "bbox": [float(x1), float(y1), float(x2), float(y2)],
236
  "match_user": best_user if authorized else None,
237
- "confidence": round(float(best_score), 4),
238
  "authorized": authorized,
239
- "det_score": float(confidence),
240
- "fake": False, # Passed Stage 1
241
  "model_threshold": SIM_THRESHOLD,
242
- "raw_distance": float(best_score)
243
  }
244
 
245
-
 
 
246
  @app.get("/health")
247
  async def health():
248
- status = "ok"
249
- if detector is None or recognizer is None:
250
- status = "models_loading_or_failed"
251
- return {"status": status, "system": "Uniface Pure (Stage1:Detect, Stage2:Recognize)"}
252
-
253
 
254
  @app.post("/face/enroll")
255
- async def face_enroll(payload: EnrollRequest):
256
- if not payload.images:
257
- raise HTTPException(status_code=400, detail="No images provided")
258
- employee_id = payload.employee_id.strip() or "1"
259
- save_dir = os.path.join(FACE_DB_ROOT, employee_id)
260
- os.makedirs(save_dir, exist_ok=True)
261
  count = 0
262
  for s in payload.images:
263
  img = _b64_to_bgr(s)
264
  if img is None:
265
  continue
266
- out_path = os.path.join(save_dir, f"face_{count}.jpg")
267
- cv2.imwrite(out_path, img)
268
  count += 1
269
- return {"employee_id": employee_id, "added": count, "total": len(os.listdir(save_dir))}
270
-
271
-
272
- @app.post("/face/enroll-files")
273
- async def face_enroll_files(
274
- employee_id: str = Form(default="1"),
275
- files: List[UploadFile] = File(default=[]),
276
- ):
277
- employee_id = employee_id.strip() or "1"
278
- if not files:
279
- raise HTTPException(status_code=400, detail="No files uploaded")
280
- save_dir = os.path.join(FACE_DB_ROOT, employee_id)
281
- os.makedirs(save_dir, exist_ok=True)
282
- count = 0
283
- for f in files:
284
- try:
285
- data = await f.read()
286
- img = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)
287
- if img is None:
288
- continue
289
- out_path = os.path.join(save_dir, f"face_{count}.jpg")
290
- cv2.imwrite(out_path, img)
291
- count += 1
292
- except Exception:
293
- continue
294
- return {"employee_id": employee_id, "added": count, "total": len(os.listdir(save_dir))}
295
 
 
296
 
297
  @app.post("/face/verify")
298
- async def face_verify(
299
  request: Request,
300
- employee_id: str = Query(default="1"),
301
- threshold: float = Query(default=SIM_THRESHOLD),
302
  ):
303
- employee_id = employee_id.strip() or "1"
304
-
305
  img = await _decode_image_from_request(request, ("frame", "image"))
306
  if img is None:
307
- raise HTTPException(status_code=400, detail="No image provided")
308
-
309
- frame_path = _frame_path_for(employee_id)
310
- cv2.imwrite(frame_path, img)
311
-
312
- try:
313
- det = await _run_pipeline(frame_path, employee_id)
314
- if det is None:
315
- return {
316
- "employee_id": employee_id,
317
- "threshold": threshold,
318
- "detections": [],
319
- "count": 0,
320
- "authorized": False,
321
- "reason": "no face found",
322
- }
323
-
324
- return {
325
- "employee_id": employee_id,
326
- "threshold": threshold,
327
- "detections": [det],
328
- "count": 1,
329
- "authorized": bool(det["authorized"]),
330
- }
331
- except Exception as e:
332
- import traceback
333
- traceback.print_exc()
334
- return JSONResponse(status_code=500, content={"error": f"{type(e).__name__}: {e}"})
335
-
336
-
337
- @app.websocket("/face/verify")
338
- async def face_verify_ws(
339
- websocket: WebSocket,
340
- employee_id: str = Query(default="1"),
341
- threshold: float = Query(default=SIM_THRESHOLD),
342
- ):
343
- await websocket.accept()
344
- employee_id = employee_id.strip() or "1"
345
-
346
- try:
347
- while True:
348
- try:
349
- msg = await websocket.receive()
350
- except WebSocketDisconnect:
351
- break
352
-
353
- img = None
354
- if "bytes" in msg and msg["bytes"] is not None:
355
- img = _bytes_to_bgr(msg["bytes"])
356
- elif "text" in msg and msg["text"] is not None:
357
- try:
358
- obj = json.loads(msg["text"])
359
- s = obj.get("frame") or obj.get("image")
360
- if isinstance(s, str):
361
- img = _b64_to_bgr(s)
362
- except Exception:
363
- img = None
364
-
365
- if img is None:
366
- try:
367
- await websocket.send_json({"error": "no/invalid frame"})
368
- except (RuntimeError, WebSocketDisconnect):
369
- break
370
- continue
371
-
372
- frame_path = _frame_path_for(employee_id)
373
- cv2.imwrite(frame_path, img)
374
-
375
- try:
376
- det = await _run_pipeline(frame_path, employee_id)
377
- except Exception as e:
378
- try:
379
- await websocket.send_json({"error": f"{type(e).__name__}: {e}"})
380
- except (RuntimeError, WebSocketDisconnect):
381
- break
382
- continue
383
-
384
- if det is None:
385
- payload = {
386
- "employee_id": employee_id,
387
- "threshold": threshold,
388
- "detections": [],
389
- "count": 0,
390
- "authorized": False,
391
- "reason": "no face found",
392
- }
393
- else:
394
- payload = {
395
- "employee_id": employee_id,
396
- "threshold": threshold,
397
- "detections": [det],
398
- "count": 1,
399
- "authorized": bool(det["authorized"]),
400
- }
401
-
402
- try:
403
- await websocket.send_json(payload)
404
- except (RuntimeError, WebSocketDisconnect):
405
- break
406
- finally:
407
- return
408
-
409
-
410
- @app.post("/face/verify-image")
411
- async def face_verify_image(payload: VerifyImageRequest):
412
- img = _b64_to_bgr(payload.image)
413
- if img is None:
414
- raise HTTPException(status_code=400, detail="Invalid or missing image")
415
- employee_id = payload.employee_id.strip() or "1"
416
- thr = payload.threshold if payload.threshold is not None else SIM_THRESHOLD
417
 
418
- frame_path = _frame_path_for(employee_id)
419
  cv2.imwrite(frame_path, img)
420
 
421
  det = await _run_pipeline(frame_path, employee_id)
422
  if det is None:
423
  return {
424
  "employee_id": employee_id,
425
- "threshold": thr,
426
- "detections": [],
427
- "count": 0,
428
  "authorized": False,
429
- "reason": "no face found",
430
  }
 
431
  return {
432
  "employee_id": employee_id,
433
- "threshold": thr,
434
  "detections": [det],
435
- "count": 1,
436
- "authorized": bool(det["authorized"]),
437
  }
438
 
439
-
440
  @app.post("/face/clear")
441
- async def face_clear(payload: ClearRequest):
442
- employee_id = payload.employee_id.strip() or "1"
443
- folder = os.path.join(FACE_DB_ROOT, employee_id)
444
  removed = 0
445
- if os.path.isdir(folder):
446
- for f in os.listdir(folder):
447
- try:
448
- os.remove(os.path.join(folder, f))
449
- removed += 1
450
- except Exception:
451
- pass
452
- try:
453
- os.rmdir(folder)
454
- except Exception:
455
- pass
456
- return {"employee_id": employee_id, "removed": removed}
 
2
  import base64
3
  import asyncio
4
  import json
5
+ from typing import List, Optional, Iterable
6
 
7
  import numpy as np
8
  import cv2
 
12
  from starlette.middleware.cors import CORSMiddleware
13
  from fastapi.responses import JSONResponse
14
 
 
15
  from uniface import RetinaFace, ArcFace
16
 
17
+ # =========================
18
+ # CONFIG
19
+ # =========================
20
+ DETECTION_THRESHOLD = 0.70
21
+ SIM_THRESHOLD = 0.40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  FACE_DB_ROOT = "face_db"
 
 
24
  LIVE_DB_ROOT = os.path.join(FACE_DB_ROOT, "_live")
25
+
26
+ os.makedirs(FACE_DB_ROOT, exist_ok=True)
27
  os.makedirs(LIVE_DB_ROOT, exist_ok=True)
28
 
29
  processing_lock = asyncio.Lock()
30
 
31
+ # =========================
32
+ # INIT MODELS
33
+ # =========================
34
  detector = None
35
  recognizer = None
36
 
37
  try:
38
+ print("[Uniface] Initializing RetinaFace...")
39
+ detector = RetinaFace()
40
+ print("[Uniface] Initializing ArcFace...")
41
  recognizer = ArcFace()
42
  except Exception as e:
43
+ print("[Uniface] Init error:", e)
44
 
45
+ # =========================
46
+ # FASTAPI
47
+ # =========================
48
+ app = FastAPI(
49
+ title="Face Verification API (Uniface Fixed)",
50
+ version="2.3.0",
51
+ )
52
 
53
+ app.add_middleware(
54
+ CORSMiddleware,
55
+ allow_origins=["*"],
56
+ allow_credentials=True,
57
+ allow_methods=["*"],
58
+ allow_headers=["*"],
59
+ )
60
+
61
+ # =========================
62
+ # SCHEMAS
63
+ # =========================
64
  class EnrollRequest(BaseModel):
65
+ employee_id: str = "1"
66
+ images: List[str]
67
 
68
  class VerifyImageRequest(BaseModel):
69
+ employee_id: str = "1"
70
+ image: str
71
+ threshold: Optional[float] = None
72
 
73
  class ClearRequest(BaseModel):
74
+ employee_id: str = "1"
 
75
 
76
+ # =========================
77
+ # HELPERS
78
+ # =========================
79
  def _strip_b64(s: str) -> str:
80
+ if "," in s and s.lower().startswith("data:"):
81
  return s.split(",", 1)[1]
82
  return s
83
 
84
+ def _b64_to_bgr(b64: str):
85
  try:
86
  raw = base64.b64decode(_strip_b64(b64), validate=False)
87
+ return cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
 
88
  except Exception:
89
  return None
90
 
91
+ def _bytes_to_bgr(data: bytes):
92
  try:
93
+ return cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)
 
94
  except Exception:
95
  return None
96
 
97
+ def _frame_path(employee_id: str):
98
+ safe = employee_id.replace("/", "_")
99
  return os.path.join(LIVE_DB_ROOT, f"{safe}.jpg")
100
 
101
+ def _compute_embedding(img, landmarks):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  try:
103
+ return recognizer.get_normalized_embedding(img, landmarks)
 
 
104
  except Exception:
105
  return None
106
 
107
+ # =========================
108
+ # PIPELINE (FIXED)
109
+ # =========================
110
  async def _run_pipeline(img_path: str, target_id: str):
111
  if detector is None or recognizer is None:
112
  return None
113
 
 
114
  img = cv2.imread(img_path)
115
  if img is None:
116
  return None
117
 
118
+ # -------- STAGE 1: DETECT --------
 
119
  async with processing_lock:
120
  faces = await run_in_threadpool(detector.detect, img)
121
+
122
  if not faces:
123
  return None
124
 
125
+ face = faces[0]
126
+ bbox = list(map(float, face["bbox"]))
127
+ landmarks = face["landmarks"]
128
+ det_conf = float(face["confidence"])
129
+
130
+ if det_conf < DETECTION_THRESHOLD:
 
 
 
 
 
131
  return {
132
+ "bbox": bbox,
133
  "match_user": None,
134
  "confidence": 0.0,
135
  "authorized": False,
136
+ "det_score": det_conf,
137
+ "fake": True,
138
  "model_threshold": DETECTION_THRESHOLD,
139
  "raw_distance": 0.0,
140
+ "reason": "low detection confidence",
141
  }
142
 
143
+ # -------- STAGE 2: EMBEDDING --------
144
+ probe_emb = await run_in_threadpool(_compute_embedding, img, landmarks)
 
145
  if probe_emb is None:
146
  return None
147
 
148
+ best_score = 0.0
149
  best_user = None
150
 
151
+ # users to search
152
  search_dirs = []
153
  if target_id == "*":
154
+ search_dirs = [d for d in os.listdir(FACE_DB_ROOT) if not d.startswith("_")]
 
 
155
  else:
156
+ if os.path.isdir(os.path.join(FACE_DB_ROOT, target_id)):
157
+ search_dirs = [target_id]
158
 
159
+ # -------- MATCHING --------
160
  for uid in search_dirs:
161
  user_dir = os.path.join(FACE_DB_ROOT, uid)
162
  for fname in os.listdir(user_dir):
163
+ if not fname.lower().endswith((".jpg", ".png", ".jpeg")):
164
  continue
165
+
166
+ ref_img = cv2.imread(os.path.join(user_dir, fname))
167
+ if ref_img is None:
168
+ continue
169
+
170
+ # IMPORTANT FIX: lock + threadpool
171
+ async with processing_lock:
172
+ ref_faces = await run_in_threadpool(detector.detect, ref_img)
173
+
174
+ if not ref_faces:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  continue
176
 
177
+ ref_emb = _compute_embedding(ref_img, ref_faces[0]["landmarks"])
178
+ if ref_emb is None:
179
+ continue
180
+
181
+ sim = float(np.dot(probe_emb, ref_emb))
182
+ if sim > best_score:
183
+ best_score = sim
184
+ best_user = uid
185
+
186
+ authorized = best_user is not None and best_score >= SIM_THRESHOLD
187
+
188
  return {
189
+ "bbox": bbox,
190
  "match_user": best_user if authorized else None,
191
+ "confidence": round(best_score, 4),
192
  "authorized": authorized,
193
+ "det_score": det_conf,
194
+ "fake": False,
195
  "model_threshold": SIM_THRESHOLD,
196
+ "raw_distance": round(best_score, 4),
197
  }
198
 
199
+ # =========================
200
+ # ENDPOINTS
201
+ # =========================
202
  @app.get("/health")
203
  async def health():
204
+ return {"status": "ok" if detector and recognizer else "model_error"}
 
 
 
 
205
 
206
  @app.post("/face/enroll")
207
+ async def enroll(payload: EnrollRequest):
208
+ path = os.path.join(FACE_DB_ROOT, payload.employee_id)
209
+ os.makedirs(path, exist_ok=True)
210
+
 
 
211
  count = 0
212
  for s in payload.images:
213
  img = _b64_to_bgr(s)
214
  if img is None:
215
  continue
216
+ cv2.imwrite(os.path.join(path, f"face_{count}.jpg"), img)
 
217
  count += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
+ return {"employee_id": payload.employee_id, "added": count}
220
 
221
  @app.post("/face/verify")
222
+ async def verify(
223
  request: Request,
224
+ employee_id: str = Query("1"),
225
+ threshold: float = Query(SIM_THRESHOLD),
226
  ):
 
 
227
  img = await _decode_image_from_request(request, ("frame", "image"))
228
  if img is None:
229
+ raise HTTPException(400, "no image")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
+ frame_path = _frame_path(employee_id)
232
  cv2.imwrite(frame_path, img)
233
 
234
  det = await _run_pipeline(frame_path, employee_id)
235
  if det is None:
236
  return {
237
  "employee_id": employee_id,
 
 
 
238
  "authorized": False,
239
+ "detections": [],
240
  }
241
+
242
  return {
243
  "employee_id": employee_id,
244
+ "authorized": det["authorized"],
245
  "detections": [det],
 
 
246
  }
247
 
 
248
  @app.post("/face/clear")
249
+ async def clear(payload: ClearRequest):
250
+ path = os.path.join(FACE_DB_ROOT, payload.employee_id)
 
251
  removed = 0
252
+ if os.path.isdir(path):
253
+ for f in os.listdir(path):
254
+ os.remove(os.path.join(path, f))
255
+ removed += 1
256
+ os.rmdir(path)
257
+ return {"employee_id": payload.employee_id, "removed": removed}