hackerloi45 commited on
Commit
8f370f4
Β·
1 Parent(s): 7c5c440
Files changed (1) hide show
  1. app.py +61 -79
app.py CHANGED
@@ -1,43 +1,42 @@
1
  import os
2
  import uuid
3
- import shutil
4
  import gradio as gr
5
  import numpy as np
6
  from PIL import Image
7
  from qdrant_client import QdrantClient
8
- from qdrant_client.models import PointStruct, Distance, VectorParams
9
  from sentence_transformers import SentenceTransformer
10
 
11
- # =====================
12
  # CONFIG
13
- # =====================
14
  UPLOAD_DIR = "uploads"
15
- DB_PATH = "./qdrant_db"
16
- COLLECTION = "items"
17
-
18
  os.makedirs(UPLOAD_DIR, exist_ok=True)
19
 
20
- # =====================
21
- # INIT
22
- # =====================
23
- qclient = QdrantClient(path=DB_PATH)
24
- if COLLECTION not in [c.name for c in qclient.get_collections().collections]:
25
- qclient.recreate_collection(
 
 
26
  collection_name=COLLECTION,
27
  vectors_config=VectorParams(size=512, distance=Distance.COSINE)
28
  )
29
 
 
30
  model = SentenceTransformer("clip-ViT-B-32")
31
 
32
- # =====================
33
- # HELPERS
34
- # =====================
 
35
  def encode_data(text=None, image=None):
36
- """Encodes either text or image into embedding."""
37
  if image is not None:
38
- if isinstance(image, str):
39
  img = Image.open(image).convert("RGB")
40
- else:
41
  img = image.convert("RGB")
42
  emb = model.encode(img, convert_to_numpy=True, normalize_embeddings=True)
43
  elif text:
@@ -46,8 +45,11 @@ def encode_data(text=None, image=None):
46
  raise ValueError("Need text or image")
47
  return emb.astype(np.float32)
48
 
 
 
 
 
49
  def add_item(mode, text, image, name, phone):
50
- """Add new lost/found item."""
51
  try:
52
  vector = encode_data(text=text if text else None, image=image if image else None)
53
 
@@ -57,6 +59,7 @@ def add_item(mode, text, image, name, phone):
57
  "has_image": image is not None,
58
  }
59
 
 
60
  if image is not None:
61
  if isinstance(image, str):
62
  img = Image.open(image).convert("RGB")
@@ -79,10 +82,14 @@ def add_item(mode, text, image, name, phone):
79
  except Exception as e:
80
  return f"❌ Error: {e}"
81
 
 
 
 
 
82
  def search_items(text, image, max_results, min_score):
83
- """Search lost/found items."""
84
  try:
85
  vector = encode_data(text=text if text else None, image=image if image else None)
 
86
  results = qclient.search(
87
  collection_name=COLLECTION,
88
  query_vector=vector.tolist(),
@@ -91,65 +98,42 @@ def search_items(text, image, max_results, min_score):
91
  with_payload=True
92
  )
93
 
94
- if not results:
95
- return "No matches found.", []
96
-
97
- text_results = []
98
- gallery = []
99
-
100
  for r in results:
101
- pl = r.payload
102
- txt = f"πŸ†” {r.id} | Mode: {pl.get('mode')} | Text: {pl.get('text')}"
103
- if pl.get("mode") == "found":
104
- txt += f" | Found by: {pl.get('finder_name')} ({pl.get('finder_phone')})"
105
- txt += f" | Score: {r.score:.3f}"
106
- text_results.append(txt)
107
-
108
- if pl.get("image_path") and os.path.exists(pl["image_path"]):
109
- gallery.append((pl["image_path"], txt))
110
- else:
111
- gallery.append((None, txt))
112
-
113
- return "\n\n".join(text_results), gallery
114
  except Exception as e:
115
  return f"❌ Error: {e}", []
116
 
117
- def delete_item(item_id):
118
- """Delete one item by ID."""
119
- try:
120
- qclient.delete(collection_name=COLLECTION, points_selector={"points": [item_id]})
121
- return f"πŸ—‘οΈ Deleted item {item_id}"
122
- except Exception as e:
123
- return f"❌ Error: {e}"
124
 
125
- def clear_database():
126
- """Clear everything."""
127
- try:
128
- qclient.delete_collection(COLLECTION)
129
- qclient.recreate_collection(
130
- collection_name=COLLECTION,
131
- vectors_config=VectorParams(size=512, distance=Distance.COSINE)
132
- )
133
- shutil.rmtree(UPLOAD_DIR, ignore_errors=True)
134
- os.makedirs(UPLOAD_DIR, exist_ok=True)
135
- return "🧹 Database cleared."
136
- except Exception as e:
137
- return f"❌ Error: {e}"
138
 
139
- # =====================
140
- # UI
141
- # =====================
142
  with gr.Blocks() as demo:
143
- gr.Markdown("# πŸ” Lost & Found System")
144
 
145
- with gr.Tab("βž• Add Lost/Found"):
146
  mode = gr.Radio(["lost", "found"], label="Mode", value="lost")
147
- text = gr.Textbox(label="Description (optional)")
148
  image = gr.Image(type="pil", label="Upload Image")
149
- name = gr.Textbox(label="Finder Name (for Found only)")
150
- phone = gr.Textbox(label="Finder Phone (for Found only)")
151
  add_btn = gr.Button("Add Item")
152
- add_output = gr.Textbox(label="Status")
153
  add_btn.click(add_item, [mode, text, image, name, phone], add_output)
154
 
155
  with gr.Tab("πŸ”Ž Search"):
@@ -159,17 +143,15 @@ with gr.Blocks() as demo:
159
  min_score = gr.Slider(0.5, 1.0, value=0.9, step=0.01, label="Min similarity threshold")
160
  search_btn = gr.Button("Search")
161
  search_text = gr.Textbox(label="Search results (text)")
162
- search_gallery = gr.Gallery(label="Search Results").style(grid=[2], height="auto")
163
  search_btn.click(search_items, [s_text, s_image, max_results, min_score], [search_text, search_gallery])
164
 
165
- with gr.Tab("βš™οΈ Admin"):
166
- del_id = gr.Textbox(label="Delete Item by ID")
167
- del_btn = gr.Button("Delete Item")
168
- del_output = gr.Textbox(label="Delete Status")
169
- del_btn.click(delete_item, del_id, del_output)
170
 
171
- clear_btn = gr.Button("Clear All Database")
172
- clear_output = gr.Textbox(label="Clear Status")
173
- clear_btn.click(clear_database, outputs=clear_output)
174
 
175
- demo.launch()
 
 
 
1
  import os
2
  import uuid
 
3
  import gradio as gr
4
  import numpy as np
5
  from PIL import Image
6
  from qdrant_client import QdrantClient
7
+ from qdrant_client.http.models import Distance, VectorParams, PointStruct
8
  from sentence_transformers import SentenceTransformer
9
 
10
+ # =========================
11
  # CONFIG
12
+ # =========================
13
  UPLOAD_DIR = "uploads"
 
 
 
14
  os.makedirs(UPLOAD_DIR, exist_ok=True)
15
 
16
+ COLLECTION = "lostfound"
17
+
18
+ # Connect to Qdrant (local or remote if deployed)
19
+ qclient = QdrantClient(":memory:") # use ":memory:" for demo, change for persistent DB
20
+
21
+ # Create collection only if missing
22
+ if not qclient.collection_exists(COLLECTION):
23
+ qclient.create_collection(
24
  collection_name=COLLECTION,
25
  vectors_config=VectorParams(size=512, distance=Distance.COSINE)
26
  )
27
 
28
+ # Load CLIP model
29
  model = SentenceTransformer("clip-ViT-B-32")
30
 
31
+
32
+ # =========================
33
+ # ENCODING FUNCTION
34
+ # =========================
35
  def encode_data(text=None, image=None):
 
36
  if image is not None:
37
+ if isinstance(image, str): # path
38
  img = Image.open(image).convert("RGB")
39
+ else: # PIL.Image
40
  img = image.convert("RGB")
41
  emb = model.encode(img, convert_to_numpy=True, normalize_embeddings=True)
42
  elif text:
 
45
  raise ValueError("Need text or image")
46
  return emb.astype(np.float32)
47
 
48
+
49
+ # =========================
50
+ # ADD ITEM
51
+ # =========================
52
  def add_item(mode, text, image, name, phone):
 
53
  try:
54
  vector = encode_data(text=text if text else None, image=image if image else None)
55
 
 
59
  "has_image": image is not None,
60
  }
61
 
62
+ # Save image if uploaded
63
  if image is not None:
64
  if isinstance(image, str):
65
  img = Image.open(image).convert("RGB")
 
82
  except Exception as e:
83
  return f"❌ Error: {e}"
84
 
85
+
86
+ # =========================
87
+ # SEARCH FUNCTION
88
+ # =========================
89
  def search_items(text, image, max_results, min_score):
 
90
  try:
91
  vector = encode_data(text=text if text else None, image=image if image else None)
92
+
93
  results = qclient.search(
94
  collection_name=COLLECTION,
95
  query_vector=vector.tolist(),
 
98
  with_payload=True
99
  )
100
 
101
+ texts, imgs = [], []
 
 
 
 
 
102
  for r in results:
103
+ p = r.payload
104
+ desc = f"id:{r.id} | score:{r.score:.3f} | mode:{p.get('mode','')} | text:{p.get('text','')}"
105
+ if p.get("mode") == "found":
106
+ desc += f" | finder:{p.get('finder_name','')} ({p.get('finder_phone','')})"
107
+ texts.append(desc)
108
+ if p.get("has_image") and "image_path" in p:
109
+ imgs.append(p["image_path"])
110
+ return "\n".join(texts) if texts else "No matches", imgs
 
 
 
 
 
111
  except Exception as e:
112
  return f"❌ Error: {e}", []
113
 
 
 
 
 
 
 
 
114
 
115
+ # =========================
116
+ # CLEAR DATABASE
117
+ # =========================
118
+ def clear_all_items():
119
+ qclient.delete(collection_name=COLLECTION, points_selector={"filter": {}})
120
+ return "πŸ—‘οΈ All items deleted!"
121
+
 
 
 
 
 
 
122
 
123
+ # =========================
124
+ # GRADIO APP
125
+ # =========================
126
  with gr.Blocks() as demo:
127
+ gr.Markdown("# πŸ” Lost & Found Search with Images + Text")
128
 
129
+ with gr.Tab("βž• Add Item"):
130
  mode = gr.Radio(["lost", "found"], label="Mode", value="lost")
131
+ text = gr.Textbox(label="Description")
132
  image = gr.Image(type="pil", label="Upload Image")
133
+ name = gr.Textbox(label="Finder Name (only if found)")
134
+ phone = gr.Textbox(label="Finder Phone (only if found)")
135
  add_btn = gr.Button("Add Item")
136
+ add_output = gr.Textbox(label="Add result")
137
  add_btn.click(add_item, [mode, text, image, name, phone], add_output)
138
 
139
  with gr.Tab("πŸ”Ž Search"):
 
143
  min_score = gr.Slider(0.5, 1.0, value=0.9, step=0.01, label="Min similarity threshold")
144
  search_btn = gr.Button("Search")
145
  search_text = gr.Textbox(label="Search results (text)")
146
+ search_gallery = gr.Gallery(label="Search Results", columns=2, height="auto")
147
  search_btn.click(search_items, [s_text, s_image, max_results, min_score], [search_text, search_gallery])
148
 
149
+ with gr.Tab("πŸ—‘οΈ Admin"):
150
+ clear_btn = gr.Button("Clear ALL items")
151
+ clear_out = gr.Textbox(label="Status")
152
+ clear_btn.click(clear_all_items, outputs=clear_out)
 
153
 
 
 
 
154
 
155
+ # Launch
156
+ if __name__ == "__main__":
157
+ demo.launch()