AdarshDRC commited on
Commit
8f0f0e4
·
verified ·
1 Parent(s): dbb5852

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +51 -22
main.py CHANGED
@@ -38,20 +38,21 @@ def _get_pinecone(api_key: str) -> Pinecone:
38
  _pinecone_pool.move_to_end(api_key)
39
  return _pinecone_pool[api_key]
40
 
41
- # Cloudinary helpers — credentials injected per-call, never globally configured.
42
- # This is the ONLY safe pattern when multiple users share one server process.
43
- def _cld_upload(tmp_path: str, folder: str, creds: dict):
 
44
  return cloudinary.uploader.upload(
45
  tmp_path, folder=folder,
46
  api_key=creds["api_key"], api_secret=creds["api_secret"], cloud_name=creds["cloud_name"],
47
  )
48
 
49
- def _cld_ping(creds: dict):
50
  return cloudinary.api.ping(
51
  api_key=creds["api_key"], api_secret=creds["api_secret"], cloud_name=creds["cloud_name"],
52
  )
53
 
54
- def _cld_root_folders(creds: dict):
55
  return cloudinary.api.root_folders(
56
  api_key=creds["api_key"], api_secret=creds["api_secret"], cloud_name=creds["cloud_name"],
57
  )
@@ -93,9 +94,9 @@ def get_cloudinary_creds(env_url: str) -> dict:
93
  async def verify_keys(pinecone_key: str = Form(""), cloudinary_url: str = Form("")):
94
  if cloudinary_url:
95
  try:
96
- creds = get_cloudinary_creds(cloudinary_url)
97
- if not creds.get("cloud_name"): raise ValueError("bad url")
98
- await asyncio.to_thread(_cld_ping, creds)
99
  except HTTPException: raise
100
  except Exception:
101
  raise HTTPException(400, "Invalid Cloudinary Environment URL.")
@@ -120,13 +121,12 @@ async def verify_keys(pinecone_key: str = Form(""), cloudinary_url: str = Form("
120
  # ══════════════════════════════════════════════════════════════════
121
  @app.post("/api/upload")
122
  async def upload_new_images(files: List[UploadFile] = File(...), folder_name: str = Form(...), detect_faces: bool = Form(True), user_pinecone_key: str = Form(""), user_cloudinary_url: str = Form("")):
123
- # Frontend always sends the correct keys (guest keys = hardcoded in App.jsx,
124
- # PRO keys = from Supabase). Backend never falls back to env vars here.
125
- actual_pc_key = (user_pinecone_key or "").strip()
126
- actual_cld_url = (user_cloudinary_url or "").strip()
127
-
128
  if not actual_pc_key or not actual_cld_url:
129
- raise HTTPException(400, "API keys missing. Configure them in Settings or check the app.")
130
 
131
  folder = standardize_category_name(folder_name)
132
  uploaded_urls = []
@@ -135,8 +135,8 @@ async def upload_new_images(files: List[UploadFile] = File(...), folder_name: st
135
  if not creds.get("cloud_name"):
136
  raise HTTPException(400, "Invalid Cloudinary URL format.")
137
 
138
- pc = _get_pinecone(actual_pc_key)
139
- idx_obj = pc.Index(IDX_OBJECTS)
140
  idx_face = pc.Index(IDX_FACES)
141
 
142
  for file in files:
@@ -144,7 +144,7 @@ async def upload_new_images(files: List[UploadFile] = File(...), folder_name: st
144
  try:
145
  with open(tmp_path, "wb") as buf:
146
  shutil.copyfileobj(file.file, buf)
147
-
148
  res = await asyncio.to_thread(_cld_upload, tmp_path, folder, creds)
149
  image_url = res["secure_url"]
150
  uploaded_urls.append(image_url)
@@ -176,9 +176,9 @@ async def upload_new_images(files: List[UploadFile] = File(...), folder_name: st
176
  # ══════════════════════════════════════════════════════════════════
177
  @app.post("/api/search")
178
  async def search_database(file: UploadFile = File(...), detect_faces: bool = Form(True), user_pinecone_key: str = Form(""), user_cloudinary_url: str = Form("")):
179
- actual_pc_key = (user_pinecone_key or "").strip()
180
  if not actual_pc_key:
181
- raise HTTPException(400, "Pinecone key missing.")
182
 
183
  tmp_path = f"temp_uploads/query_{uuid.uuid4().hex}_{sanitize_filename(file.filename)}"
184
  try:
@@ -205,8 +205,37 @@ async def search_database(file: UploadFile = File(...), detect_faces: bool = For
205
 
206
  out = []
207
  for match in res.get("matches", []):
208
- caption = "👤 Verified Identity" if vec_dict["type"] == "face" else match["metadata"].get("folder", "🎯 Object Match")
209
- out.append({"url": match["metadata"].get("url", ""), "score": match["score"], "caption": caption})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  return out
211
 
212
  nested = await asyncio.gather(*[_query_one(v) for v in vectors])
@@ -233,7 +262,7 @@ async def search_database(file: UploadFile = File(...), detect_faces: bool = For
233
  # ══════════════════════════════════════════════════════════════════
234
  @app.post("/api/categories")
235
  async def get_categories(user_cloudinary_url: str = Form("")):
236
- actual_cld_url = (user_cloudinary_url or "").strip()
237
  if not actual_cld_url:
238
  return {"categories": []}
239
 
 
38
  _pinecone_pool.move_to_end(api_key)
39
  return _pinecone_pool[api_key]
40
 
41
+ # ── Cloudinary: credentials injected per-call, NEVER globally configured.
42
+ # If cloudinary.config() is called once, it applies to the whole process
43
+ # User A's credentials would bleed into User B's request under concurrency.
44
+ def _cld_upload(tmp_path, folder, creds):
45
  return cloudinary.uploader.upload(
46
  tmp_path, folder=folder,
47
  api_key=creds["api_key"], api_secret=creds["api_secret"], cloud_name=creds["cloud_name"],
48
  )
49
 
50
+ def _cld_ping(creds):
51
  return cloudinary.api.ping(
52
  api_key=creds["api_key"], api_secret=creds["api_secret"], cloud_name=creds["cloud_name"],
53
  )
54
 
55
+ def _cld_root_folders(creds):
56
  return cloudinary.api.root_folders(
57
  api_key=creds["api_key"], api_secret=creds["api_secret"], cloud_name=creds["cloud_name"],
58
  )
 
94
  async def verify_keys(pinecone_key: str = Form(""), cloudinary_url: str = Form("")):
95
  if cloudinary_url:
96
  try:
97
+ creds_v = get_cloudinary_creds(cloudinary_url)
98
+ if not creds_v.get("cloud_name"): raise ValueError("bad url")
99
+ await asyncio.to_thread(_cld_ping, creds_v)
100
  except HTTPException: raise
101
  except Exception:
102
  raise HTTPException(400, "Invalid Cloudinary Environment URL.")
 
121
  # ══════════════════════════════════════════════════════════════════
122
  @app.post("/api/upload")
123
  async def upload_new_images(files: List[UploadFile] = File(...), folder_name: str = Form(...), detect_faces: bool = Form(True), user_pinecone_key: str = Form(""), user_cloudinary_url: str = Form("")):
124
+ # DEFENSIVE FIX: The 'or ""' ensures it never becomes None, preventing 500 crashes
125
+ actual_pc_key = user_pinecone_key or os.getenv("DEFAULT_PINECONE_KEY", "")
126
+ actual_cld_url = user_cloudinary_url or os.getenv("DEFAULT_CLOUDINARY_URL", "")
127
+
 
128
  if not actual_pc_key or not actual_cld_url:
129
+ raise HTTPException(400, "API Keys are missing. If you are a guest, the server is missing its DEFAULT_ secrets in Hugging Face.")
130
 
131
  folder = standardize_category_name(folder_name)
132
  uploaded_urls = []
 
135
  if not creds.get("cloud_name"):
136
  raise HTTPException(400, "Invalid Cloudinary URL format.")
137
 
138
+ pc = _get_pinecone(actual_pc_key)
139
+ idx_obj = pc.Index(IDX_OBJECTS)
140
  idx_face = pc.Index(IDX_FACES)
141
 
142
  for file in files:
 
144
  try:
145
  with open(tmp_path, "wb") as buf:
146
  shutil.copyfileobj(file.file, buf)
147
+
148
  res = await asyncio.to_thread(_cld_upload, tmp_path, folder, creds)
149
  image_url = res["secure_url"]
150
  uploaded_urls.append(image_url)
 
176
  # ══════════════════════════════════════════════════════════════════
177
  @app.post("/api/search")
178
  async def search_database(file: UploadFile = File(...), detect_faces: bool = Form(True), user_pinecone_key: str = Form(""), user_cloudinary_url: str = Form("")):
179
+ actual_pc_key = user_pinecone_key or os.getenv("DEFAULT_PINECONE_KEY", "")
180
  if not actual_pc_key:
181
+ raise HTTPException(400, "Pinecone Key is missing. If you are a guest, the server is missing its DEFAULT_PINECONE_KEY in Hugging Face.")
182
 
183
  tmp_path = f"temp_uploads/query_{uuid.uuid4().hex}_{sanitize_filename(file.filename)}"
184
  try:
 
205
 
206
  out = []
207
  for match in res.get("matches", []):
208
+ score = match["score"]
209
+ is_face = vec_dict["type"] == "face"
210
+
211
+ # ── Score filtering ──────────────────────────────────────
212
+ # Face lane: RetinaFace+GhostFaceNet 512-D cosine similarity.
213
+ # Raw scores cluster around 0.3-0.5 for same person; anything
214
+ # below 0.30 is noise. We remap to 75-99% for the UI.
215
+ if is_face:
216
+ RAW_THRESHOLD = 0.30
217
+ if score < RAW_THRESHOLD:
218
+ continue
219
+ ui_score = 0.75 + ((score - RAW_THRESHOLD) / (1.0 - RAW_THRESHOLD)) * 0.24
220
+ ui_score = min(0.99, ui_score)
221
+ else:
222
+ # Object lane: SigLIP+DINOv2 1536-D fused cosine similarity.
223
+ # These vectors are dense and high-dimensional — even completely
224
+ # different images can score 0.70-0.80 just by chance.
225
+ # 0.82+ = genuinely similar images (same animal, same scene type)
226
+ # 0.70-0.82 = loosely related (outdoor vs outdoor, animal vs animal)
227
+ # < 0.70 = unrelated — hard filter these out entirely
228
+ MIN_OBJECT_SCORE = 0.70
229
+ if score < MIN_OBJECT_SCORE:
230
+ continue
231
+ ui_score = score
232
+
233
+ caption = "👤 Verified Identity" if is_face else match["metadata"].get("folder", "🎯 Object Match")
234
+ out.append({
235
+ "url": match["metadata"].get("url", ""),
236
+ "score": round(ui_score, 4),
237
+ "caption": caption,
238
+ })
239
  return out
240
 
241
  nested = await asyncio.gather(*[_query_one(v) for v in vectors])
 
262
  # ══════════════════════════════════════════════════════════════════
263
  @app.post("/api/categories")
264
  async def get_categories(user_cloudinary_url: str = Form("")):
265
+ actual_cld_url = user_cloudinary_url or os.getenv("DEFAULT_CLOUDINARY_URL", "")
266
  if not actual_cld_url:
267
  return {"categories": []}
268