File size: 12,709 Bytes
625c0a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8999815
625c0a2
 
 
b0cc1ff
625c0a2
 
 
b0cc1ff
 
625c0a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b0cc1ff
625c0a2
 
 
 
b0cc1ff
 
 
 
 
 
 
625c0a2
8999815
625c0a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760fccf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625c0a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8999815
625c0a2
 
 
b78b263
625c0a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8999815
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625c0a2
 
8999815
 
 
 
 
 
625c0a2
8999815
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760fccf
 
 
 
 
8999815
 
 
 
 
 
 
760fccf
8999815
625c0a2
 
 
b78b263
625c0a2
760fccf
625c0a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760fccf
 
 
 
 
625c0a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8999815
625c0a2
8999815
625c0a2
 
 
 
 
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
import gradio as gr
import torch
import clip
from PIL import Image
import numpy as np
import json
from typing import List, Tuple
import io
import base64

# Load CLIP model
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)

# Load categories
def load_categories():
    """Load drawing categories from categories.txt file"""
    try:
        with open("categories.txt", "r") as f:
            categories = [line.strip() for line in f.readlines()]
        return categories
    except FileNotFoundError:
        # Fallback to a subset of common categories if file not found
        return [
            "airplane", "apple", "car", "cat", "dog", "flower", "house", "tree",
            "sun", "moon", "star", "bird", "fish", "butterfly", "bicycle", "boat"
        ]

# Load categories
CATEGORIES = load_categories()

def preprocess_drawing(image):
    """Preprocess the drawing image for CLIP"""
    if image is None:
        return None
    print(image)
    # Convert to PIL Image if it's not already
    if isinstance(image, np.ndarray):
        image = Image.fromarray(image)
    # print(image)
    # Convert to RGB if it's not already
    if image.mode != 'RGB':
        image = image.convert('RGB')
    # Save the image to a temporary file for debugging
    image.save("temp_drawing.png")
    # Preprocess for CLIP
    return preprocess(image).unsqueeze(0).to(device)

def classify_drawing(image, top_k: int = 5) -> List[Tuple[str, float]]:
    """
    Classify a quickdraw-style drawing using CLIP zero-shot classification
    
    Args:
        image: PIL Image or numpy array of the drawing
        top_k: Number of top predictions to return
    
    Returns:
        List of tuples (category, confidence_score)
    """
    if image is None:
        print("Here")
        return []
    
    try:
        # Preprocess the image
        if isinstance(image, dict):
            print("Image is a dictionary")
            # return []
            print(image)
            image_input = preprocess_drawing(image['composite'])
        else:
            image_input = preprocess_drawing(image)
        if image_input is None:
            # print("Here2")
            return []
        
        # Create text prompts for all categories
        text_prompts = [f"a drawing of a {category}" for category in CATEGORIES]
        text_inputs = clip.tokenize(text_prompts).to(device)
        
        # Get features
        with torch.no_grad():
            image_features = model.encode_image(image_input)
            text_features = model.encode_text(text_inputs)
            
            # Normalize features
            image_features = image_features / image_features.norm(dim=-1, keepdim=True)
            text_features = text_features / text_features.norm(dim=-1, keepdim=True)
            
            # Calculate similarities
            similarities = (100.0 * image_features @ text_features.T).softmax(dim=-1)
            values, indices = similarities[0].topk(top_k)
        
        # Format results
        results = []
        for i in range(top_k):
            category = CATEGORIES[indices[i]]
            confidence = float(values[i])
            results.append((category, confidence))
        
        return results
    
    except Exception as e:
        print(f"Error in classification: {str(e)}")
        return []
    
def classify_drawing1(image, top_k: int = 5,word: str = "dog") -> List[Tuple[str, float]]:
    """
    Classify a quickdraw-style drawing using CLIP zero-shot classification
    
    Args:
        image: PIL Image or numpy array of the drawing
        top_k: Number of top predictions to return
    
    Returns:
        List of tuples (category, confidence_score)
    """
    if image is None:
        print("Here")
        return []
    
    try:
        # Preprocess the image
        if isinstance(image, dict):
            print("Image is a dictionary")
            # return []
            print(image)
            image_input = preprocess_drawing(image['composite'])
        else:
            image_input = preprocess_drawing(image)
        if image_input is None:
            # print("Here2")
            return []
        if word.lower() not in [cat.lower() for cat in CATEGORIES]:
            CATEGORIES.append(word)
            # print(f"Added new category: {word}")
        
        # Create text prompts for all categories
        text_prompts = [f"a drawing of a {category}" for category in CATEGORIES]
        text_inputs = clip.tokenize(text_prompts).to(device)
        
        # Get features
        with torch.no_grad():
            image_features = model.encode_image(image_input)
            text_features = model.encode_text(text_inputs)
            
            # Normalize features
            image_features = image_features / image_features.norm(dim=-1, keepdim=True)
            text_features = text_features / text_features.norm(dim=-1, keepdim=True)
            
            # Calculate similarities
            similarities = (100.0 * image_features @ text_features.T).softmax(dim=-1)
            values, indices = similarities[0].topk(top_k)
        
        # Format results
        results = []
        for i in range(top_k):
            category = CATEGORIES[indices[i]]
            confidence = float(values[i])
            results.append((category, confidence))

        if word.lower() not in [cat.lower() for cat, _ in results]:
            # Find the original confidence score for the word
            word_idx = CATEGORIES.index(word)
            word_confidence = float(similarities[0][word_idx])
            
            # Remove the last result if we have top_k results
            if len(results) >= top_k:
                results.pop()
            # Add the word with its original confidence score
            results.append((word, word_confidence))
        
        return results
    
    except Exception as e:
        print(f"Error in classification: {str(e)}")
        return []

def gradio_classify(image):
    """Gradio interface function"""
    if image is None:
        return "Please draw something first!"
    
    results = classify_drawing(image, top_k=5)
    
    if not results:
        return "Sorry, couldn't classify the drawing. Please try again."
    
    # Format results for display
    output = "🎨 **Drawing Classification Results:**\n\n"
    for i, (category, confidence) in enumerate(results, 1):
        percentage = confidence * 100
        output += f"{i}. **{category.title()}** - {percentage:.1f}%\n"
    
    return output

# Create Gradio interface
def create_interface():
    """Create the Gradio interface with both drawing and API tabs"""
    
    with gr.Blocks(
        title="QuickDraw Classifier",
        theme=gr.themes.Soft(),
        css="""
        .main-header { text-align: center; margin-bottom: 2rem; }
        .description { text-align: center; margin-bottom: 1.5rem; font-size: 1.1rem; }
        """
    ) as demo:
        
        gr.HTML("""
        <div class="main-header">
            <h1>🎨 QuickDraw Classifier</h1>
            <p class="description">
                Draw anything and let AI guess what it is! This app uses CLIP for zero-shot classification 
                of your drawings across 345+ categories from Google's QuickDraw dataset.
            </p>
        </div>
        """)
        
        with gr.Tabs():
            # Main Drawing Interface Tab
            with gr.Tab("πŸ–ŠοΈ Draw & Classify"):
                with gr.Row():
                    with gr.Column(scale=1):
                        # Drawing canvas
                        canvas = gr.Sketchpad(
                            label="Draw here! πŸ–ŠοΈ",
                            type="pil",
                            image_mode="RGB",
                            canvas_size=(400, 400),
                            brush=gr.Brush(colors=["#000000"], color_mode="fixed", default_size=5),
                            show_download_button=False,
                            show_share_button=False,
                        )
                        
                        with gr.Row():
                            classify_btn = gr.Button("πŸ” Classify Drawing", variant="primary", size="lg")
                            clear_btn = gr.ClearButton(canvas, value="πŸ—‘οΈ Clear Canvas", size="lg")
                    
                    with gr.Column(scale=1):
                        # Results
                        output = gr.Markdown(
                            label="Classification Results",
                            value="πŸ‘ˆ Draw something and click 'Classify Drawing' to see results!"
                        )
                
                # Examples section
                gr.HTML("""
                <div style="margin-top: 2rem; text-align: center;">
                    <h3>πŸ’‘ Drawing Tips:</h3>
                    <p>β€’ Draw simple, clear shapes<br>
                    β€’ Try common objects like animals, vehicles, or everyday items<br>
                    β€’ The AI knows 345+ categories including: airplane, cat, house, tree, car, flower, etc.</p>
                </div>
                """)
                
                # Event handlers
                classify_btn.click(
                    fn=gradio_classify,
                    inputs=[canvas],
                    outputs=[output]
                )
                
                # Auto-classify on canvas change (optional, can be removed if too frequent)
                canvas.change(
                    fn=gradio_classify,
                    inputs=[canvas],
                    outputs=[output]
                )
            
            # API Interface Tab
            with gr.Tab("πŸ”Œ API Testing"):
                gr.HTML("""
                <div style="text-align: center; margin-bottom: 1rem;">
                    <h3>Base64 Image Classification API</h3>
                    <p>Test the API endpoint with base64 encoded images</p>
                </div>
                """)
                
                with gr.Row():
                    with gr.Column():
                        api_input = gr.Textbox(
                            label="Base64 Image Data", 
                            placeholder="Paste base64 encoded image here...",
                            lines=3
                        )
                        api_top_k = gr.Slider(
                            minimum=1, 
                            maximum=10, 
                            value=5, 
                            step=1, 
                            label="Top K Predictions"
                        )
                        api_word = gr.Textbox(
                            label="Word to Add",
                            placeholder="Enter a word to add to the categories",
                            lines=1
                        )
                        api_btn = gr.Button("πŸš€ Classify", variant="primary")
                    
                    with gr.Column():
                        api_output = gr.JSON(label="API Response")
                
                api_btn.click(
                    fn=classify_image_api,
                    inputs=[api_input, api_top_k, api_word],
                    outputs=[api_output]
                )
    
    return demo

# API endpoint function for external use
def classify_image_api(image_data: str, top_k: int = 5, word: str = "dog"):
    """
    API endpoint for classifying base64 encoded images
    
    Args:
        image_data: Base64 encoded image string
        top_k: Number of top predictions to return
    
    Returns:
        JSON response with classification results
    """
    try:
        # Decode base64 image
        image_bytes = base64.b64decode(image_data)
        image = Image.open(io.BytesIO(image_bytes))
        
        # Classify
        results = classify_drawing1(image, top_k, word)
        # Normalize confidence scores to sum to 100%
        total_conf = sum(conf for _, conf in results)
        if total_conf > 0:
            results = [(cat, (conf/total_conf) * 100) for cat, conf in results]
        
        return {
            "success": True,
            "predictions": [
                {"category": cat, "confidence": conf} 
                for cat, conf in results
            ]
        }
    
    except Exception as e:
        return {
            "success": False,
            "error": str(e)
        }

if __name__ == "__main__":
    # Create and launch the main interface
    demo = create_interface()
    
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=True
    )