AdarshDRC commited on
Commit
55a16c0
·
1 Parent(s): dfc44c0

feat: friday commit

Browse files
src/api/people.py CHANGED
@@ -94,7 +94,7 @@ async def get_cluster_images(
94
  {
95
  "cluster_id": "uuid",
96
  "images": [
97
- {"url": "...", "folder": "...", "face_crop": "<base64>"},
98
  ...
99
  ],
100
  "total": 12
 
94
  {
95
  "cluster_id": "uuid",
96
  "images": [
97
+ {"url": "...", "thumb_url": "...", "folder": "...", "face_crop": "<base64>"},
98
  ...
99
  ],
100
  "total": 12
src/common/utils.py CHANGED
@@ -73,6 +73,13 @@ def cld_thumb_url(url: str) -> str:
73
  return url.replace("/upload/", "/upload/c_limit,w_500/")
74
 
75
 
 
 
 
 
 
 
 
76
  def face_ui_score(raw_score: float, mode: str = "fused") -> float:
77
  """
78
  Platt-scaled probability score for the UI.
 
73
  return url.replace("/upload/", "/upload/c_limit,w_500/")
74
 
75
 
76
+ def cld_face_thumb_url(url: str, width: int = 300) -> str:
77
+ """Generate optimized thumbnail URL for face images (smaller than general thumbs)."""
78
+ if not url:
79
+ return ""
80
+ return url.replace("/upload/", f"/upload/c_limit,w_{width},q_30,f_auto/")
81
+
82
+
83
  def face_ui_score(raw_score: float, mode: str = "fused") -> float:
84
  """
85
  Platt-scaled probability score for the UI.
src/services/ai_manager.py CHANGED
@@ -398,23 +398,38 @@ class AIModelManager:
398
  self, bgr_enhanced: np.ndarray, scale: tuple
399
  ) -> list:
400
  H, W = bgr_enhanced.shape[:2]
401
- scale_w, scale_h = min(W, scale[0]), min(H, scale[1])
402
- if scale_w == W and scale_h == H:
 
 
 
 
 
 
403
  bgr_scaled = bgr_enhanced
 
404
  else:
 
 
 
405
  bgr_scaled = cv2.resize(bgr_enhanced, (scale_w, scale_h))
406
  try:
407
  with self._face_lock:
408
  # input_size must be set inside the lock — setting it outside
409
  # is a race condition when two inference threads run concurrently,
410
  # causing the wrong scale to be used and faces to be missed.
411
- self.face_app.det_model.input_size = scale
 
 
412
  faces_at_scale = self.face_app.get(bgr_scaled)
413
  sx, sy = W / scale_w, H / scale_h
414
  for f in faces_at_scale:
415
  if sx != 1.0 or sy != 1.0:
416
  f.bbox[0] *= sx; f.bbox[1] *= sy
417
  f.bbox[2] *= sx; f.bbox[3] *= sy
 
 
 
418
  return faces_at_scale
419
  except Exception:
420
  return []
@@ -447,16 +462,15 @@ class AIModelManager:
447
 
448
  if ENABLE_HORIZONTAL_FLIP:
449
  bgr_flip = cv2.flip(bgr_enhanced, 1)
450
- try:
451
- with self._face_lock:
452
- self.face_app.det_model.input_size = DET_SIZE_PRIMARY
453
- faces_flip = self.face_app.get(bgr_flip)
454
- for f in faces_flip:
455
- x1, y1, x2, y2 = f.bbox
456
- f.bbox[0], f.bbox[2] = W - x2, W - x1
457
- all_raw_faces.extend(faces_flip)
458
- except Exception:
459
- pass
460
 
461
  self.face_app.det_model.input_size = DET_SIZE_PRIMARY
462
  faces = _dedup_faces(all_raw_faces)
 
398
  self, bgr_enhanced: np.ndarray, scale: tuple
399
  ) -> list:
400
  H, W = bgr_enhanced.shape[:2]
401
+ # Preserve aspect ratio: scale longest side to match scale's longest side.
402
+ # The previous code clamped each dim independently which squashed wide
403
+ # images (e.g. 4032x1816 → 640x640) and produced distorted face crops
404
+ # whose embeddings would not match the same person shot in a normal
405
+ # aspect ratio.
406
+ target_max = max(scale[0], scale[1])
407
+ long_side = max(W, H)
408
+ if long_side <= target_max:
409
  bgr_scaled = bgr_enhanced
410
+ scale_w, scale_h = W, H
411
  else:
412
+ ratio = target_max / long_side
413
+ scale_w = max(1, int(round(W * ratio)))
414
+ scale_h = max(1, int(round(H * ratio)))
415
  bgr_scaled = cv2.resize(bgr_enhanced, (scale_w, scale_h))
416
  try:
417
  with self._face_lock:
418
  # input_size must be set inside the lock — setting it outside
419
  # is a race condition when two inference threads run concurrently,
420
  # causing the wrong scale to be used and faces to be missed.
421
+ # Use the actual scaled dims so the detector's letterboxing
422
+ # math matches the image we're feeding it.
423
+ self.face_app.det_model.input_size = (scale_w, scale_h)
424
  faces_at_scale = self.face_app.get(bgr_scaled)
425
  sx, sy = W / scale_w, H / scale_h
426
  for f in faces_at_scale:
427
  if sx != 1.0 or sy != 1.0:
428
  f.bbox[0] *= sx; f.bbox[1] *= sy
429
  f.bbox[2] *= sx; f.bbox[3] *= sy
430
+ if hasattr(f, 'kps') and f.kps is not None:
431
+ f.kps[:, 0] *= sx
432
+ f.kps[:, 1] *= sy
433
  return faces_at_scale
434
  except Exception:
435
  return []
 
462
 
463
  if ENABLE_HORIZONTAL_FLIP:
464
  bgr_flip = cv2.flip(bgr_enhanced, 1)
465
+ # Reuse the aspect-ratio-preserving scaler so flipped detection
466
+ # also avoids the wide-image squash.
467
+ faces_flip = self._run_detection_at_scale(bgr_flip, DET_SIZE_PRIMARY)
468
+ for f in faces_flip:
469
+ x1, y1, x2, y2 = f.bbox
470
+ f.bbox[0], f.bbox[2] = W - x2, W - x1
471
+ if hasattr(f, 'kps') and f.kps is not None:
472
+ f.kps[:, 0] = W - f.kps[:, 0]
473
+ all_raw_faces.extend(faces_flip)
 
474
 
475
  self.face_app.det_model.input_size = DET_SIZE_PRIMARY
476
  faces = _dedup_faces(all_raw_faces)
src/services/clustering.py CHANGED
@@ -37,6 +37,7 @@ from src.core.config import (
37
  CLUSTER_MIN_SAMPLES, CLUSTER_MIN_CLUSTER_SIZE, CLUSTER_EPSILON,
38
  FACE_SEARCH_TOP_K, CLUSTERING_BLUR_THRESHOLD,
39
  )
 
40
 
41
 
42
  # ──────────────────────────────────────────────────────────────
@@ -309,6 +310,7 @@ async def get_person_images(cluster_id: str, user_id: str) -> list[dict]:
309
  seen.add(url)
310
  out.append({
311
  "url": url,
 
312
  "folder": r.get("folder", ""),
313
  "face_crop": r.get("face_crop", ""),
314
  })
 
37
  CLUSTER_MIN_SAMPLES, CLUSTER_MIN_CLUSTER_SIZE, CLUSTER_EPSILON,
38
  FACE_SEARCH_TOP_K, CLUSTERING_BLUR_THRESHOLD,
39
  )
40
+ from src.common.utils import cld_face_thumb_url
41
 
42
 
43
  # ──────────────────────────────────────────────────────────────
 
310
  seen.add(url)
311
  out.append({
312
  "url": url,
313
+ "thumb_url": cld_face_thumb_url(url),
314
  "folder": r.get("folder", ""),
315
  "face_crop": r.get("face_crop", ""),
316
  })