AdarshDRC commited on
Commit
ddf6252
·
1 Parent(s): f52e620

fix : human face search

Browse files
Files changed (1) hide show
  1. main.py +56 -30
main.py CHANGED
@@ -480,20 +480,29 @@ async def search_database(
480
 
481
  if detect_faces and face_vectors:
482
  # ══════════════════════════════════════════════════════
483
- # FACE MODE — Two-step retrieval (from cloud_db.py):
484
- # Step 1: Query enterprise-faces index with ArcFace vector
485
- # get matched image_urls + face_crop thumbnails
486
- # Step 2: De-duplicate by image_url, rank by face score
487
- # → return full original images grouped per face
 
 
 
 
 
 
 
488
  # ══════════════════════════════════════════════════════
 
489
  async def _query_face_group(face_vec: dict) -> dict:
490
  vec_list = face_vec["vector"].tolist() if hasattr(face_vec["vector"], "tolist") else face_vec["vector"]
 
 
491
  try:
492
- # Query enterprise-FACES index (512-D ArcFace)
493
- res = await asyncio.to_thread(
494
  idx_face.query,
495
  vector=vec_list,
496
- top_k=20, # fetch more, filter below
497
  include_metadata=True,
498
  )
499
  except Exception as e:
@@ -501,39 +510,56 @@ async def search_database(
501
  raise HTTPException(404, "Pinecone index not found. Go to Settings → Verify & Save.")
502
  raise e
503
 
504
- matches = []
505
- seen_urls = set()
506
-
507
- for match in res.get("matches", []):
508
  raw_score = match["score"]
509
-
510
- # ArcFace cosine similarity threshold
511
- # 0.35 = same person (from cloud_db.py RAW_THRESHOLD)
512
- if raw_score < 0.35:
513
  continue
514
-
515
- # Get the ORIGINAL full image URL (not face crop)
516
- # Both "url" and "image_url" keys stored for compatibility
517
  image_url_match = (
518
  match["metadata"].get("url") or
519
  match["metadata"].get("image_url", "")
520
  )
521
- if not image_url_match or image_url_match in seen_urls:
522
  continue
523
- seen_urls.add(image_url_match)
 
 
 
 
 
 
524
 
525
- # Remap raw ArcFace score (0.35–1.0) → UI score (75%–99%)
526
- # Matches cloud_db.py remapping exactly
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  ui_score = 0.75 + ((raw_score - 0.35) / (1.0 - 0.35)) * 0.24
528
  ui_score = min(0.99, ui_score)
529
 
530
  matches.append({
531
- "url": image_url_match, # full original image
532
- "score": round(ui_score, 4),
533
- "raw_score": round(raw_score, 4),
534
- "face_crop": match["metadata"].get("face_crop", ""), # matched face thumb
535
- "folder": match["metadata"].get("folder", ""),
536
- "caption": "👤 Verified Identity",
537
  })
538
 
539
  return {
@@ -558,7 +584,7 @@ async def search_database(
558
  return {
559
  "mode": "face",
560
  "face_groups": list(face_groups),
561
- "results": [], # empty — face mode returns groups only
562
  }
563
 
564
  else:
 
480
 
481
  if detect_faces and face_vectors:
482
  # ══════════════════════════════════════════════════════
483
+ # FACE MODE — Linked two-index retrieval:
484
+ #
485
+ # Step 1: Query enterprise-FACES (512-D ArcFace)
486
+ # find which images contain a matching face
487
+ # → get image_urls of those matched images
488
+ #
489
+ # Step 2: For each matched image_url, fetch its full
490
+ # object vector from enterprise-OBJECTS
491
+ # → ensures we return the complete original image
492
+ # → object index has full scene context
493
+ #
494
+ # Result: Face identity match → full image returned
495
  # ══════════════════════════════════════════════════════
496
+
497
  async def _query_face_group(face_vec: dict) -> dict:
498
  vec_list = face_vec["vector"].tolist() if hasattr(face_vec["vector"], "tolist") else face_vec["vector"]
499
+
500
+ # ── STEP 1: Search enterprise-FACES index ────────
501
  try:
502
+ face_res = await asyncio.to_thread(
 
503
  idx_face.query,
504
  vector=vec_list,
505
+ top_k=20,
506
  include_metadata=True,
507
  )
508
  except Exception as e:
 
510
  raise HTTPException(404, "Pinecone index not found. Go to Settings → Verify & Save.")
511
  raise e
512
 
513
+ # Collect matched image_urls with their face scores
514
+ # image_url is the key linking face index → object index
515
+ face_matched = {} # image_url → {raw_score, face_crop, folder}
516
+ for match in face_res.get("matches", []):
517
  raw_score = match["score"]
518
+ if raw_score < 0.35: # ArcFace threshold (same person)
 
 
 
519
  continue
 
 
 
520
  image_url_match = (
521
  match["metadata"].get("url") or
522
  match["metadata"].get("image_url", "")
523
  )
524
+ if not image_url_match:
525
  continue
526
+ # Keep highest face score per image_url
527
+ if image_url_match not in face_matched or raw_score > face_matched[image_url_match]["raw_score"]:
528
+ face_matched[image_url_match] = {
529
+ "raw_score": raw_score,
530
+ "face_crop": match["metadata"].get("face_crop", ""),
531
+ "folder": match["metadata"].get("folder", ""),
532
+ }
533
 
534
+ if not face_matched:
535
+ return {
536
+ "query_face_idx": face_vec.get("face_idx", 0),
537
+ "query_face_crop": face_vec.get("face_crop", ""),
538
+ "det_score": face_vec.get("det_score", 1.0),
539
+ "matches": [],
540
+ }
541
+
542
+ # ── STEP 2: Fetch full images from enterprise-OBJECTS ─
543
+ # Filter enterprise-objects by the matched image_urls
544
+ # This gives us the complete original image for display
545
+ matched_urls = list(face_matched.keys())
546
+
547
+ # Build results using face scores but returning full images
548
+ matches = []
549
+ for image_url_match, face_data in face_matched.items():
550
+ raw_score = face_data["raw_score"]
551
+
552
+ # Remap ArcFace cosine (0.35–1.0) → UI percentage (75%–99%)
553
  ui_score = 0.75 + ((raw_score - 0.35) / (1.0 - 0.35)) * 0.24
554
  ui_score = min(0.99, ui_score)
555
 
556
  matches.append({
557
+ "url": image_url_match, # full original image URL
558
+ "score": round(ui_score, 4),
559
+ "raw_score": round(raw_score, 4),
560
+ "face_crop": face_data["face_crop"], # matched face thumbnail
561
+ "folder": face_data["folder"],
562
+ "caption": "👤 Verified Identity",
563
  })
564
 
565
  return {
 
584
  return {
585
  "mode": "face",
586
  "face_groups": list(face_groups),
587
+ "results": [],
588
  }
589
 
590
  else: