hackerloi45 commited on
Commit
7c5c440
Β·
1 Parent(s): 0c4adc5
Files changed (1) hide show
  1. app.py +108 -76
app.py CHANGED
@@ -1,33 +1,44 @@
1
- import gradio as gr
2
- import uuid
3
  import os
 
 
 
 
 
4
  from qdrant_client import QdrantClient
5
- from qdrant_client.models import PointStruct
6
  from sentence_transformers import SentenceTransformer
7
- from PIL import Image
8
- import numpy as np
9
 
10
- # Create uploads folder
 
 
11
  UPLOAD_DIR = "uploads"
12
- os.makedirs(UPLOAD_DIR, exist_ok=True)
13
-
14
- # Connect to Qdrant
15
- COLLECTION = "lost_and_found"
16
- qclient = QdrantClient(":memory:") # for demo, replace with your Qdrant server for persistence
17
 
18
- # Load CLIP model
19
- model = SentenceTransformer("sentence-transformers/clip-ViT-B-32-multilingual-v1")
20
-
21
- # Ensure collection exists
22
- qclient.recreate_collection(
23
- collection_name=COLLECTION,
24
- vectors_config={"size": 512, "distance": "Cosine"}
25
- )
26
 
27
- # Encode helper
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  def encode_data(text=None, image=None):
 
29
  if image is not None:
30
- img = Image.open(image).convert("RGB")
 
 
 
31
  emb = model.encode(img, convert_to_numpy=True, normalize_embeddings=True)
32
  elif text:
33
  emb = model.encode(text, convert_to_numpy=True, normalize_embeddings=True)
@@ -35,19 +46,22 @@ def encode_data(text=None, image=None):
35
  raise ValueError("Need text or image")
36
  return emb.astype(np.float32)
37
 
38
- # Add item
39
  def add_item(mode, text, image, name, phone):
 
40
  try:
41
  vector = encode_data(text=text if text else None, image=image if image else None)
 
42
  payload = {
43
  "mode": mode,
44
  "text": text,
45
  "has_image": image is not None,
46
  }
47
 
48
- # Save image file if uploaded
49
  if image is not None:
50
- img = Image.open(image).convert("RGB")
 
 
 
51
  fname = f"{uuid.uuid4().hex}.png"
52
  fpath = os.path.join(UPLOAD_DIR, fname)
53
  img.save(fpath)
@@ -65,79 +79,97 @@ def add_item(mode, text, image, name, phone):
65
  except Exception as e:
66
  return f"❌ Error: {e}"
67
 
68
- # Search items
69
- def search_items(query_image, query_text, limit, min_score):
70
  try:
71
- query_vector = encode_data(
72
- text=query_text if query_text else None,
73
- image=query_image if query_image else None
74
- )
75
  results = qclient.search(
76
  collection_name=COLLECTION,
77
- query_vector=query_vector.tolist(),
78
- limit=limit,
 
 
79
  )
80
- out_texts, out_imgs = [], []
 
 
 
 
 
 
81
  for r in results:
82
- if r.score < min_score:
83
- continue
84
  pl = r.payload
85
- info = f"id:{r.id} | score:{r.score:.4f} | mode:{pl.get('mode','')}"
86
- if pl.get("text"):
87
- info += f" | text:{pl['text']}"
88
  if pl.get("mode") == "found":
89
- info += f" | found by: {pl.get('finder_name','?')} ({pl.get('finder_phone','?')})"
90
- out_texts.append(info)
 
91
 
92
- if pl.get("image_path"):
93
- out_imgs.append(pl["image_path"])
 
 
94
 
95
- return "\n".join(out_texts) if out_texts else "No matches.", out_imgs
96
  except Exception as e:
97
  return f"❌ Error: {e}", []
98
 
99
- # Clear all images
100
- def clear_all_images():
 
 
 
 
 
 
 
 
101
  try:
102
- # Clear uploads folder
103
- for f in os.listdir(UPLOAD_DIR):
104
- os.remove(os.path.join(UPLOAD_DIR, f))
105
- # Clear Qdrant
106
- qclient.delete(
107
  collection_name=COLLECTION,
108
- points_selector={"filter": {"must": [{"key": "has_image", "match": {"value": True}}]}}
109
  )
110
- return "πŸ—‘οΈ All image items cleared!"
 
 
111
  except Exception as e:
112
- return f"❌ Error clearing images: {e}"
113
 
114
- # Gradio UI
 
 
115
  with gr.Blocks() as demo:
116
- gr.Markdown("# πŸ”Ž Lost & Found System")
117
-
118
- with gr.Tab("βž• Add Item"):
119
- mode = gr.Radio(["lost", "found"], label="Mode")
120
- text = gr.Textbox(label="Describe the item (optional)")
121
- img = gr.Image(type="filepath", label="Upload image (optional)")
122
- name = gr.Textbox(label="Finder's Name (only if found)", placeholder="John Doe")
123
- phone = gr.Textbox(label="Finder's Phone (only if found)", placeholder="+1234567890")
124
  add_btn = gr.Button("Add Item")
125
- add_out = gr.Textbox(label="Add result")
126
- add_btn.click(add_item, inputs=[mode, text, img, name, phone], outputs=add_out)
127
 
128
- with gr.Tab("πŸ” Search"):
129
- query_text = gr.Textbox(label="Search by text (optional)")
130
- query_img = gr.Image(type="filepath", label="Search by image (optional)")
131
  max_results = gr.Slider(1, 10, value=5, step=1, label="Max results")
132
- score_slider = gr.Slider(0.5, 1.0, value=0.9, step=0.01, label="Min similarity threshold")
133
  search_btn = gr.Button("Search")
134
- search_out = gr.Textbox(label="Search results (text)")
135
- gallery = gr.Gallery(label="Search Results", show_label=True, elem_id="gallery", columns=2, height="auto")
136
- search_btn.click(search_items, inputs=[query_img, query_text, max_results, score_slider], outputs=[search_out, gallery])
137
-
138
- with gr.Tab("πŸ—‘οΈ Admin"):
139
- clear_btn = gr.Button("Clear All Images")
140
- clear_out = gr.Textbox(label="Clear Result")
141
- clear_btn.click(clear_all_images, outputs=clear_out)
 
 
 
 
 
142
 
143
  demo.launch()
 
 
 
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:
44
  emb = model.encode(text, convert_to_numpy=True, normalize_embeddings=True)
 
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
+
54
  payload = {
55
  "mode": mode,
56
  "text": text,
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")
63
+ else:
64
+ img = image.convert("RGB")
65
  fname = f"{uuid.uuid4().hex}.png"
66
  fpath = os.path.join(UPLOAD_DIR, fname)
67
  img.save(fpath)
 
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(),
89
+ limit=max_results,
90
+ score_threshold=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"):
156
+ s_text = gr.Textbox(label="Search by text (optional)")
157
+ s_image = gr.Image(type="pil", label="Search by image (optional)")
158
  max_results = gr.Slider(1, 10, value=5, step=1, label="Max results")
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()