AdarshDRC commited on
Commit
12c1479
·
1 Parent(s): a7e7d81

Feat: Face Camera Search Enahced

Browse files
src/api/__pycache__/search.cpython-312.pyc ADDED
Binary file (19.7 kB). View file
 
src/api/search.py CHANGED
@@ -263,4 +263,166 @@ async def _run_object_search(object_vectors, idx_obj, start, user_id, ip, mode)
263
  user_id=user_id or "anonymous", ip=ip, mode=mode,
264
  lanes=["object"], results=len(final), duration_ms=duration_ms)
265
 
266
- return {"mode": "object", "results": final, "face_groups": []}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  user_id=user_id or "anonymous", ip=ip, mode=mode,
264
  lanes=["object"], results=len(final), duration_ms=duration_ms)
265
 
266
+ return {"mode": "object", "results": final, "face_groups": []}
267
+
268
+
269
+ @router.post("/api/search-by-face")
270
+ async def search_by_face(
271
+ request: Request,
272
+ front: UploadFile = File(...),
273
+ left: UploadFile = File(None),
274
+ right: UploadFile = File(None),
275
+ user_id: str = Form(""),
276
+ keys: dict = Depends(get_verified_keys),
277
+ ):
278
+ """
279
+ Multi-angle face search: accepts 1-3 face images, fuses embeddings server-side,
280
+ performs single Pinecone query. 3x faster + lower quota usage vs 3 sequential queries.
281
+ """
282
+ import numpy as np
283
+
284
+ ip = get_ip(request)
285
+ start = time.perf_counter()
286
+ mode = "guest" if is_default_key(keys["pinecone_key"], DEFAULT_PINECONE_KEY) else "personal"
287
+
288
+ log("INFO", "search.search_by_face.start",
289
+ user_id=user_id or "anonymous", ip=ip, mode=mode)
290
+
291
+ try:
292
+ ai_manager = request.app.state.ai
293
+ sem = request.app.state.ai_semaphore
294
+
295
+ # Read all image bytes in parallel
296
+ images = {}
297
+ for name, file in [("front", front), ("left", left), ("right", right)]:
298
+ if file:
299
+ images[name] = await file.read()
300
+
301
+ if not images:
302
+ raise HTTPException(400, "At least front image required")
303
+
304
+ # Process all images in parallel
305
+ async def process_img(name, data):
306
+ async with sem:
307
+ return name, await ai_manager.process_image_bytes_async(
308
+ data, detect_faces=True
309
+ )
310
+
311
+ results = await asyncio.gather(
312
+ *[process_img(name, data) for name, data in images.items()],
313
+ return_exceptions=True
314
+ )
315
+
316
+ # Extract face vectors from successful results
317
+ face_vectors_by_angle = {}
318
+ for result in results:
319
+ if isinstance(result, Exception):
320
+ log("WARN", "search.search_by_face.process_error",
321
+ user_id=user_id or "anonymous", ip=ip, error=str(result))
322
+ continue
323
+
324
+ name, vectors = result
325
+ face_vecs = [v for v in vectors if v["type"] == "face"]
326
+ if face_vecs:
327
+ face_vectors_by_angle[name] = face_vecs[0]
328
+
329
+ if not face_vectors_by_angle:
330
+ raise HTTPException(400, "No face detected in provided images")
331
+
332
+ # Fuse embeddings: front weighted higher
333
+ weights = {"front": 0.5, "left": 0.25, "right": 0.25}
334
+ arcface_vectors = []
335
+ adaface_vectors = []
336
+ det_scores = []
337
+
338
+ for angle, vec in face_vectors_by_angle.items():
339
+ w = weights.get(angle, 0)
340
+ if w > 0:
341
+ arcface_vectors.append(np.array(to_list(vec["arcface_vector"])) * w)
342
+ det_scores.append(vec.get("det_score", 1.0))
343
+
344
+ if vec.get("has_adaface") and vec.get("adaface_vector"):
345
+ adaface_vectors.append(np.array(to_list(vec["adaface_vector"])) * w)
346
+
347
+ if not arcface_vectors:
348
+ raise HTTPException(400, "Could not fuse face embeddings")
349
+
350
+ # Fuse and normalize
351
+ fused_arcface = np.sum(arcface_vectors, axis=0)
352
+ fused_arcface = fused_arcface / (np.linalg.norm(fused_arcface) + 1e-7)
353
+
354
+ fused_adaface = None
355
+ has_adaface = False
356
+ if adaface_vectors and len(adaface_vectors) > 0:
357
+ fused_adaface = np.sum(adaface_vectors, axis=0)
358
+ fused_adaface = fused_adaface / (np.linalg.norm(fused_adaface) + 1e-7)
359
+ has_adaface = True
360
+
361
+ # Build synthetic face vector dict for query
362
+ fv = {
363
+ "face_idx": 0,
364
+ "det_score": float(np.mean(det_scores)),
365
+ "arcface_vector": fused_arcface.tolist(),
366
+ "has_adaface": has_adaface,
367
+ "adaface_vector": fused_adaface.tolist() if has_adaface else None,
368
+ "bbox": [0, 0, 0, 0],
369
+ "face_width_px": 0,
370
+ "face_crop": "",
371
+ }
372
+
373
+ inference_ms = round((time.perf_counter() - start) * 1000)
374
+ log("INFO", "search.search_by_face.fused",
375
+ user_id=user_id or "anonymous", ip=ip,
376
+ angles=list(face_vectors_by_angle.keys()),
377
+ inference_ms=inference_ms)
378
+
379
+ pc = pinecone_pool.get(keys["pinecone_key"])
380
+ cluster_uid = hashlib.sha256(keys["pinecone_key"].encode()).hexdigest()[:16]
381
+
382
+ # Ensure indexes exist
383
+ try:
384
+ created = await asyncio.to_thread(ensure_indexes, pc)
385
+ if created:
386
+ log("INFO", "search.indexes_auto_created",
387
+ user_id=user_id or "anonymous", ip=ip, created=created)
388
+ await asyncio.sleep(8)
389
+ except Exception as e:
390
+ log("ERROR", "search.ensure_indexes_failed",
391
+ user_id=user_id or "anonymous", ip=ip, error=str(e))
392
+
393
+ # Setup indexes
394
+ if USE_SPLIT_FACE_INDEXES:
395
+ idx_arcface = pc.Index(IDX_FACES_ARCFACE)
396
+ idx_adaface = pc.Index(IDX_FACES_ADAFACE)
397
+ idx_face_legacy = None
398
+ else:
399
+ idx_face_legacy = pc.Index(IDX_FACES)
400
+ idx_arcface = None
401
+ idx_adaface = None
402
+
403
+ # Query with fused vector
404
+ if USE_SPLIT_FACE_INDEXES:
405
+ face_group = await _query_face_split(fv, idx_arcface, idx_adaface, pc=pc, cluster_uid=cluster_uid)
406
+ else:
407
+ face_group = await _query_face_legacy(fv, idx_face_legacy)
408
+
409
+ duration_ms = round((time.perf_counter() - start) * 1000)
410
+ log("INFO", "search.search_by_face.complete",
411
+ user_id=user_id or "anonymous", ip=ip,
412
+ results=len(face_group.get("matches", [])),
413
+ duration_ms=duration_ms)
414
+
415
+ return {
416
+ "mode": "face",
417
+ "face_groups": [face_group] if face_group.get("matches") else [],
418
+ "results": [],
419
+ "object_results": [],
420
+ }
421
+
422
+ except HTTPException:
423
+ raise
424
+ except Exception as e:
425
+ log("ERROR", "search.search_by_face.error",
426
+ user_id=user_id or "anonymous", ip=ip, mode=mode,
427
+ error=str(e), traceback=traceback.format_exc()[-800:])
428
+ raise HTTPException(500, str(e))