File size: 5,473 Bytes
7c5c440
7a8dd54
 
 
7116b90
7a8dd54
c559bc7
294389b
afa5ea3
7a8dd54
 
 
 
 
226d235
27514c1
7a8dd54
 
 
109869f
7a8dd54
 
109869f
7a8dd54
 
 
 
 
afa5ea3
7a8dd54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
afa5ea3
7a8dd54
 
afa5ea3
7a8dd54
 
 
 
 
 
7fae8fb
666646e
 
 
7a8dd54
 
 
 
 
 
666646e
7a8dd54
afa5ea3
7a8dd54
 
 
 
 
 
 
 
7c5c440
7a8dd54
 
 
 
 
afa5ea3
7a8dd54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666646e
7a8dd54
226d235
7a8dd54
 
 
47edb75
 
7a8dd54
 
47edb75
 
666646e
7a8dd54
 
666646e
7a8dd54
 
 
 
 
 
 
 
 
 
666646e
7a8dd54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7116b90
7a8dd54
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import gradio as gr
import uuid
import base64
import io
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, VectorParams, Distance
from sentence_transformers import SentenceTransformer
from PIL import Image

# --------------------------
# Qdrant Cloud Connection
# --------------------------
QDRANT_URL = "https://ff4da494-27b1-413c-ba58-d5ea14932fe1.europe-west3-0.gcp.cloud.qdrant.io:6333"
QDRANT_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.jjeB1JgnUSlb1hOOKMdRpVvMrUER57-udT-X1AWXT1E"
COLLECTION_NAME = "lost_and_found"

# CLIP model (text + image embeddings)
MODEL_NAME = "sentence-transformers/clip-ViT-B-32"
embedder = SentenceTransformer(MODEL_NAME)

# CLIP ViT-B/32 always gives 512-dimensional embeddings
VECTOR_SIZE = 512

# Qdrant Client (Cloud)
qclient = QdrantClient(
    url=QDRANT_URL,
    api_key=QDRANT_API_KEY
)

# Ensure collection exists
qclient.recreate_collection(
    collection_name=COLLECTION_NAME,
    vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE),
)

# --------------------------
# Helper Functions
# --------------------------

def image_to_base64(img: Image.Image) -> str:
    buf = io.BytesIO()
    img.save(buf, format="PNG")
    return base64.b64encode(buf.getvalue()).decode("utf-8")

def base64_to_image(b64_str: str) -> Image.Image:
    img_bytes = base64.b64decode(b64_str)
    return Image.open(io.BytesIO(img_bytes))

def embed_text(text: str):
    return embedder.encode(text).tolist()

def embed_image(img: Image.Image):
    return embedder.encode(img).tolist()

def add_item(image, description, finder_name, finder_phone):
    if image is None or description.strip() == "":
        return "Please provide both an image and a description."

    embedding = embed_image(image)
    img_b64 = image_to_base64(image)

    metadata = {
        "description": description,
        "finder_name": finder_name if finder_name.strip() else "NA",
        "finder_phone": finder_phone if finder_phone.strip() else "NA",
        "image_b64": img_b64
    }

    qclient.upsert(
        collection_name=COLLECTION_NAME,
        points=[
            PointStruct(
                id=str(uuid.uuid4()),
                vector=embedding,
                payload=metadata
            )
        ]
    )
    return "Item successfully added!"

def search_items(query_text, query_image):
    if not query_text and query_image is None:
        return "Please enter text or upload an image to search.", []

    if query_image:
        query_vector = embed_image(query_image)
    else:
        query_vector = embed_text(query_text)

    results = qclient.search(
        collection_name=COLLECTION_NAME,
        query_vector=query_vector,
        limit=5
    )

    if not results:
        return "No matches found.", []

    gallery = []
    output_text = "### Matches Found\n\n"
    for r in results:
        desc = r.payload.get("description", "No description")
        name = r.payload.get("finder_name", "NA")
        phone = r.payload.get("finder_phone", "NA")
        output_text += f"- **{desc}** — Finder: {name}, Phone: {phone}\n"

        if "image_b64" in r.payload:
            try:
                img = base64_to_image(r.payload["image_b64"])
                gallery.append(img)
            except Exception:
                pass

    return output_text, gallery

def clear_database():
    qclient.delete_collection(COLLECTION_NAME)
    qclient.recreate_collection(
        collection_name=COLLECTION_NAME,
        vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE),
    )
    return "Database cleared successfully."

# --------------------------
# Gradio UI
# --------------------------

with gr.Blocks() as demo:
    gr.Markdown(
        """
        # Lost & Found System  
        A simple platform to add and search lost items.  
        """
    )

    with gr.Tab("Add Item"):
        gr.Markdown("### Add a Found Item")
        with gr.Row():
            image_in = gr.Image(type="pil", label="Upload Image")
            desc_in = gr.Textbox(label="Item Description")
        with gr.Row():
            finder_name = gr.Textbox(label="Finder's Name (optional)")
            finder_phone = gr.Textbox(label="Finder's Phone Number (optional)")
        add_btn = gr.Button("Submit Item", variant="primary")
        add_output = gr.Textbox(label="Status", interactive=False)

    with gr.Tab("Search"):
        gr.Markdown("### Search Lost Items")
        with gr.Row():
            search_text = gr.Textbox(label="Search by Text")
            search_image = gr.Image(type="pil", label="Or Upload Image")
        search_btn = gr.Button("Search", variant="primary")
        search_output = gr.Markdown()
        gallery = gr.Gallery(label="Matched Items", show_label=True, elem_id="gallery")

    with gr.Tab("Admin"):
        gr.Markdown("### Admin Controls")
        clear_btn = gr.Button("Clear Database", variant="stop")
        clear_output = gr.Textbox(label="Status", interactive=False)

    # Button actions
    add_btn.click(add_item, inputs=[image_in, desc_in, finder_name, finder_phone], outputs=add_output)
    search_btn.click(search_items, inputs=[search_text, search_image], outputs=[search_output, gallery])
    clear_btn.click(clear_database, outputs=clear_output)

# --------------------------
# Launch App
# --------------------------
if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)