Anusha806 commited on
Commit
bbb3cfa
·
1 Parent(s): 0b33309
Files changed (1) hide show
  1. app.py +120 -58
app.py CHANGED
@@ -83,6 +83,13 @@ from PIL import Image, ImageOps
83
  import numpy as np
84
  from PIL import Image, ImageOps
85
  import numpy as np
 
 
 
 
 
 
 
86
 
87
  def extract_metadata_filters(query: str):
88
  query_lower = query.lower()
@@ -97,7 +104,8 @@ def extract_metadata_filters(query: str):
97
  "women": "Women", "woman": "Women", "womens": "Women", "female": "Women",
98
  "boys": "Boys", "boy": "Boys",
99
  "girls": "Girls", "girl": "Girls",
100
- "kids": "Kids", "unisex": "Unisex"
 
101
  }
102
  for term, mapped_value in gender_map.items():
103
  if term in query_lower:
@@ -115,7 +123,7 @@ def extract_metadata_filters(query: str):
115
  "trousers": "Trousers", "pants": "Trousers",
116
  "shorts": "Shorts",
117
  "footwear": "Footwear",
118
- "shoes": "Footwear",
119
  "fashion": "Apparel"
120
  }
121
  for term, mapped_value in category_map.items():
@@ -169,51 +177,77 @@ def extract_metadata_filters(query: str):
169
  return gender, category, subcategory, color
170
 
171
 
172
- def search_fashion(query: str, alpha: float):
173
  gender, category, subcategory, color = extract_metadata_filters(query)
174
 
175
- # Build Pinecone filter
 
 
 
 
176
  filter = {}
 
177
  if gender:
178
  filter["gender"] = gender
 
179
  if category:
180
- filter["articleType"] = category
 
 
 
 
 
 
 
 
181
  if subcategory:
182
  filter["subCategory"] = subcategory
 
183
  if color:
184
  filter["baseColour"] = color
185
 
186
- print(f"🔍 Using filter: {filter}")
187
 
188
- # hybrid
189
  sparse = bm25.encode_queries(query)
190
  dense = model.encode(query).tolist()
191
  hdense, hsparse = hybrid_scale(dense, sparse, alpha=alpha)
192
 
193
- # initial search
194
  result = index.query(
195
- top_k=12,
196
  vector=hdense,
197
  sparse_vector=hsparse,
198
  include_metadata=True,
199
  filter=filter if filter else None
200
  )
201
 
202
- # fallback: if zero results with gender, relax gender
 
 
 
 
 
 
 
 
 
 
 
 
203
  if gender and len(result["matches"]) == 0:
204
- print(f"⚠️ No results with gender {gender}, relaxing gender filter")
205
- filter.pop("gender")
206
  result = index.query(
207
- top_k=12,
208
  vector=hdense,
209
  sparse_vector=hsparse,
210
  include_metadata=True,
211
  filter=filter if filter else None
212
  )
213
 
214
- # results
 
215
  imgs_with_captions = []
216
- for r in result["matches"]:
217
  idx = int(r["id"])
218
  img = images[idx]
219
  meta = r.get("metadata", {})
@@ -226,36 +260,34 @@ def search_fashion(query: str, alpha: float):
226
  return imgs_with_captions
227
 
228
 
229
- from transformers import CLIPProcessor, CLIPModel
230
-
231
- clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
232
- clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
233
-
234
 
 
235
 
236
  from PIL import Image, ImageOps
237
  import numpy as np
238
 
239
- def search_by_image(uploaded_image, alpha=0.5):
240
  """
241
- Given a PIL image from Gradio, find visually similar products.
242
  """
243
- # Preprocess as CLIP expects
244
  processed = clip_processor(images=uploaded_image, return_tensors="pt").to(device)
245
 
246
  with torch.no_grad():
247
  image_vec = clip_model.get_image_features(**processed)
248
  image_vec = image_vec.cpu().numpy().flatten().tolist()
249
 
250
- # since your Pinecone is purely visual, we query on visual vector
251
  result = index.query(
252
- top_k=12,
253
  vector=image_vec,
254
  include_metadata=True
255
  )
256
 
 
 
257
  imgs_with_captions = []
258
- for r in result["matches"]:
259
  idx = int(r["id"])
260
  img = images[idx]
261
  meta = r.get("metadata", {})
@@ -267,20 +299,6 @@ def search_by_image(uploaded_image, alpha=0.5):
267
 
268
  return imgs_with_captions
269
 
270
-
271
-
272
- custom_css = """
273
- .search-btn {
274
- width: 100%;
275
- }
276
- .gr-row {
277
- gap: 8px !important; /* slightly tighter column gap */
278
- }
279
- .query-slider > div {
280
- margin-bottom: 4px !important; /* reduce space between textbox and slider */
281
- }
282
- """
283
-
284
  # with gr.Blocks(css=custom_css) as demo:
285
  # gr.Markdown("# 🛍️ Fashion Product Hybrid Search")
286
 
@@ -311,6 +329,18 @@ custom_css = """
311
  # height="40vh"
312
  # )
313
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
  with gr.Blocks(css=custom_css) as demo:
316
  gr.Markdown("# 🛍️ Fashion Product Hybrid Search")
@@ -321,41 +351,73 @@ with gr.Blocks(css=custom_css) as demo:
321
  label="Enter your fashion search query",
322
  placeholder="Type something or leave blank to only use the image"
323
  )
324
- alpha = gr.Slider(
325
- 0, 1, value=0.5,
326
- label="Hybrid Weight (alpha: 0=sparse, 1=dense)"
 
 
327
  )
328
  with gr.Column(scale=1):
329
  image_input = gr.Image(
330
- source="webcam", # 👈 Enables webcam button
331
  type="pil",
332
- label="📷 Capture or Upload Image",
333
  height=256,
334
- width=356,
335
- show_label=True
336
  )
337
 
338
  search_btn = gr.Button("Search", elem_classes="search-btn")
 
 
339
 
340
- gallery = gr.Gallery(
341
- label="Search Results",
342
- columns=6,
343
- height="40vh"
344
- )
 
 
 
 
 
345
 
 
346
 
347
- def unified_search(q, uploaded_image, a):
348
  if uploaded_image is not None:
349
- return search_by_image(uploaded_image, a)
350
  elif q.strip() != "":
351
- return search_fashion(q, a)
352
  else:
353
- return []
 
 
 
354
 
355
  search_btn.click(
356
  unified_search,
357
- inputs=[query, image_input, alpha],
358
- outputs=gallery
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  )
360
 
361
  gr.Markdown("Powered by your hybrid AI search model 🚀")
 
83
  import numpy as np
84
  from PIL import Image, ImageOps
85
  import numpy as np
86
+ from PIL import Image, ImageOps
87
+ import numpy as np
88
+
89
+ from transformers import CLIPProcessor, CLIPModel
90
+
91
+ clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
92
+ clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
93
 
94
  def extract_metadata_filters(query: str):
95
  query_lower = query.lower()
 
104
  "women": "Women", "woman": "Women", "womens": "Women", "female": "Women",
105
  "boys": "Boys", "boy": "Boys",
106
  "girls": "Girls", "girl": "Girls",
107
+ "kids": "Kids","kid": "Kids",
108
+ "unisex": "Unisex"
109
  }
110
  for term, mapped_value in gender_map.items():
111
  if term in query_lower:
 
123
  "trousers": "Trousers", "pants": "Trousers",
124
  "shorts": "Shorts",
125
  "footwear": "Footwear",
126
+ "shoes": "Shoes", # note kept as Shoes
127
  "fashion": "Apparel"
128
  }
129
  for term, mapped_value in category_map.items():
 
177
  return gender, category, subcategory, color
178
 
179
 
180
+ def search_fashion(query: str, alpha: float, start: int = 0, end: int = 12, gender_override: str = None):
181
  gender, category, subcategory, color = extract_metadata_filters(query)
182
 
183
+ # override from dropdown
184
+ if gender_override:
185
+ gender = gender_override
186
+
187
+ # --- Pinecone Filter ---
188
  filter = {}
189
+
190
  if gender:
191
  filter["gender"] = gender
192
+
193
  if category:
194
+ if category in ["Footwear", "Shoes"]:
195
+ shoe_article_types = [
196
+ "Casual Shoes", "Sports Shoes", "Formal Shoes", "Training Shoes",
197
+ "Sneakers", "Sandals", "Slippers", "Boots", "Flip Flops"
198
+ ]
199
+ filter["articleType"] = {"$in": shoe_article_types}
200
+ else:
201
+ filter["articleType"] = category
202
+
203
  if subcategory:
204
  filter["subCategory"] = subcategory
205
+
206
  if color:
207
  filter["baseColour"] = color
208
 
209
+ print(f"🔍 Using filter: {filter} (showing {start} to {end})")
210
 
 
211
  sparse = bm25.encode_queries(query)
212
  dense = model.encode(query).tolist()
213
  hdense, hsparse = hybrid_scale(dense, sparse, alpha=alpha)
214
 
 
215
  result = index.query(
216
+ top_k=end,
217
  vector=hdense,
218
  sparse_vector=hsparse,
219
  include_metadata=True,
220
  filter=filter if filter else None
221
  )
222
 
223
+ # fallback if no results
224
+ if len(result["matches"]) == 0:
225
+ print("⚠️ No results, retrying with alpha=0 sparse only")
226
+ hdense, hsparse = hybrid_scale(dense, sparse, alpha=0)
227
+ result = index.query(
228
+ top_k=end,
229
+ vector=hdense,
230
+ sparse_vector=hsparse,
231
+ include_metadata=True,
232
+ filter=filter if filter else None
233
+ )
234
+
235
+ # fallback if no results with gender
236
  if gender and len(result["matches"]) == 0:
237
+ print(f"⚠️ No results for gender {gender}, relaxing gender filter")
238
+ filter.pop("gender", None)
239
  result = index.query(
240
+ top_k=end,
241
  vector=hdense,
242
  sparse_vector=hsparse,
243
  include_metadata=True,
244
  filter=filter if filter else None
245
  )
246
 
247
+ matches = result["matches"][start:end]
248
+
249
  imgs_with_captions = []
250
+ for r in matches:
251
  idx = int(r["id"])
252
  img = images[idx]
253
  meta = r.get("metadata", {})
 
260
  return imgs_with_captions
261
 
262
 
 
 
 
 
 
263
 
264
+ # this is working code block
265
 
266
  from PIL import Image, ImageOps
267
  import numpy as np
268
 
269
+ def search_by_image(uploaded_image, alpha=0.5, start=0, end=12):
270
  """
271
+ Search visually similar products with support for pagination.
272
  """
273
+ # Preprocess image for CLIP
274
  processed = clip_processor(images=uploaded_image, return_tensors="pt").to(device)
275
 
276
  with torch.no_grad():
277
  image_vec = clip_model.get_image_features(**processed)
278
  image_vec = image_vec.cpu().numpy().flatten().tolist()
279
 
280
+ # Query a larger top_k so you have enough to paginate
281
  result = index.query(
282
+ top_k=end,
283
  vector=image_vec,
284
  include_metadata=True
285
  )
286
 
287
+ matches = result["matches"][start:end] # slice for pagination
288
+
289
  imgs_with_captions = []
290
+ for r in matches:
291
  idx = int(r["id"])
292
  img = images[idx]
293
  meta = r.get("metadata", {})
 
299
 
300
  return imgs_with_captions
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  # with gr.Blocks(css=custom_css) as demo:
303
  # gr.Markdown("# 🛍️ Fashion Product Hybrid Search")
304
 
 
329
  # height="40vh"
330
  # )
331
 
332
+ import gradio as gr
333
+ custom_css = """
334
+ .search-btn {
335
+ width: 100%;
336
+ }
337
+ .gr-row {
338
+ gap: 8px !important;
339
+ }
340
+ .query-slider > div {
341
+ margin-bottom: 4px !important;
342
+ }
343
+ """
344
 
345
  with gr.Blocks(css=custom_css) as demo:
346
  gr.Markdown("# 🛍️ Fashion Product Hybrid Search")
 
351
  label="Enter your fashion search query",
352
  placeholder="Type something or leave blank to only use the image"
353
  )
354
+ alpha = gr.Slider(0, 1, value=0.5, label="Hybrid Weight (alpha: 0=sparse, 1=dense)")
355
+
356
+ gender_dropdown = gr.Dropdown(
357
+ ["", "Men", "Women", "Boys", "Girls", "Kids", "Unisex"],
358
+ label="Gender Filter (optional)"
359
  )
360
  with gr.Column(scale=1):
361
  image_input = gr.Image(
 
362
  type="pil",
363
+ label="Upload an image (optional)",
364
  height=256,
365
+ width=356
 
366
  )
367
 
368
  search_btn = gr.Button("Search", elem_classes="search-btn")
369
+ gallery = gr.Gallery(label="Search Results", columns=6, height="50vh")
370
+ load_more_btn = gr.Button("Load More")
371
 
372
+ # States to track
373
+ search_offset = gr.State(0)
374
+ current_query = gr.State("")
375
+ current_image = gr.State(None)
376
+ current_gender = gr.State("")
377
+ shown_results = gr.State([]) # new: store the list of shown images
378
+
379
+ def unified_search(q, uploaded_image, a, offset, gender_ui):
380
+ start = 0
381
+ end = 12
382
 
383
+ gender_override = gender_ui if gender_ui else None
384
 
 
385
  if uploaded_image is not None:
386
+ results = search_by_image(uploaded_image, a, start, end)
387
  elif q.strip() != "":
388
+ results = search_fashion(q, a, start, end, gender_override)
389
  else:
390
+ results = []
391
+
392
+ # reset shown_results to just these first 12
393
+ return results, end, q, uploaded_image, gender_ui, results
394
 
395
  search_btn.click(
396
  unified_search,
397
+ inputs=[query, image_input, alpha, search_offset, gender_dropdown],
398
+ outputs=[gallery, search_offset, current_query, current_image, current_gender, shown_results]
399
+ )
400
+
401
+ def load_more_fn(a, offset, q, img, gender_ui, prev_results):
402
+ start = offset
403
+ end = offset + 12
404
+
405
+ gender_override = gender_ui if gender_ui else None
406
+
407
+ if img is not None:
408
+ new_results = search_by_image(img, a, start, end)
409
+ elif q.strip() != "":
410
+ new_results = search_fashion(q, a, start, end, gender_override)
411
+ else:
412
+ new_results = []
413
+
414
+ combined_results = prev_results + new_results
415
+ return combined_results, end, combined_results
416
+
417
+ load_more_btn.click(
418
+ load_more_fn,
419
+ inputs=[alpha, search_offset, current_query, current_image, current_gender, shown_results],
420
+ outputs=[gallery, search_offset, shown_results]
421
  )
422
 
423
  gr.Markdown("Powered by your hybrid AI search model 🚀")