File size: 5,672 Bytes
7c5c440
666646e
27514c1
 
7116b90
666646e
c559bc7
294389b
afa5ea3
 
 
 
27514c1
 
afa5ea3
 
 
 
 
666646e
 
 
afa5ea3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666646e
27514c1
 
 
 
 
 
 
 
 
 
 
afa5ea3
 
 
666646e
 
afa5ea3
666646e
 
 
 
afa5ea3
666646e
 
27514c1
afa5ea3
27514c1
666646e
 
 
 
27514c1
666646e
7fae8fb
666646e
 
 
 
 
 
 
 
 
 
 
 
afa5ea3
666646e
 
 
 
afa5ea3
666646e
 
 
afa5ea3
666646e
afa5ea3
666646e
afa5ea3
 
666646e
 
dd67299
7c5c440
afa5ea3
666646e
c559bc7
666646e
 
 
afa5ea3
 
666646e
 
 
27514c1
 
 
 
 
 
 
afa5ea3
666646e
 
 
 
 
 
 
 
 
 
afa5ea3
 
03852d5
afa5ea3
666646e
 
 
 
7116b90
afa5ea3
666646e
afa5ea3
666646e
 
 
294389b
666646e
 
 
 
 
 
22ef1d5
666646e
 
 
 
 
 
7c5c440
666646e
 
 
 
22ef1d5
afa5ea3
666646e
afa5ea3
7116b90
 
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
172
173
174
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"  # πŸ”‘ Replace with your cluster URL
QDRANT_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.jjeB1JgnUSlb1hOOKMdRpVvMrUER57-udT-X1AWXT1E"                    # πŸ”‘ Replace with your API key
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:
    """Convert PIL image to base64 string"""
    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:
    """Convert base64 string back to PIL 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):
    """Add a found item to Qdrant database"""
    if image is None or description.strip() == "":
        return "❌ Please provide both an image and a description."

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

    # Store metadata including 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
    }

    # Insert into Qdrant
    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):
    """Search by text or image"""
    if not query_text and query_image is None:
        return "❌ Please enter text or upload an image to search.", []

    # Use text or image embedding
    if query_image:
        query_vector = embed_image(query_image)
    else:
        query_vector = embed_text(query_text)

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

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

    # Format results
    gallery = []
    output_text = "βœ… Found Matches:\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():
    """Clear all stored items in Qdrant"""
    qclient.delete_collection(COLLECTION_NAME)
    qclient.recreate_collection(
        collection_name=COLLECTION_NAME,
        vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE),
    )
    return "πŸ—‘οΈ Database cleared!"

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

with gr.Blocks() as demo:
    gr.Markdown("# πŸ” Lost & Found System")

    with gr.Tab("βž• Add Found Item"):
        with gr.Row():
            image_in = gr.Image(type="pil", label="Upload Found Item Image")
            desc_in = gr.Textbox(label="Item Description")
        with gr.Row():
            finder_name = gr.Textbox(label="Finder's Name")
            finder_phone = gr.Textbox(label="Finder's Phone Number")
        add_btn = gr.Button("Add Item")
        add_output = gr.Textbox(label="Status")

    with gr.Tab("πŸ”Ž Search Items"):
        with gr.Row():
            search_text = gr.Textbox(label="Search by Text")
            search_image = gr.Image(type="pil", label="Or Search by Image")
        search_btn = gr.Button("Search")
        search_output = gr.Markdown(label="Results")
        gallery = gr.Gallery(label="Matched Items", show_label=True, elem_id="gallery")

    with gr.Tab("⚠️ Admin"):
        clear_btn = gr.Button("Clear Entire Database")
        clear_output = gr.Textbox(label="Status")

    # 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)