| | import gradio as gr |
| | import base64 |
| | import json |
| | import os |
| | from PIL import Image |
| | import io |
| | from handler import EndpointHandler |
| |
|
| | handler = EndpointHandler() |
| |
|
| | def classify_image(image, top_k=10): |
| | """ |
| | Main classification function for public interface. |
| | """ |
| | if image is None: |
| | return None, "Please upload an image" |
| | |
| | try: |
| | |
| | buffered = io.BytesIO() |
| | image.save(buffered, format="PNG") |
| | img_b64 = base64.b64encode(buffered.getvalue()).decode() |
| | |
| | |
| | result = handler({ |
| | "inputs": { |
| | "image": img_b64, |
| | "top_k": int(top_k) |
| | } |
| | }) |
| | |
| | |
| | if isinstance(result, list): |
| | |
| | output_text = "**Top {} Classifications:**\n\n".format(len(result)) |
| | |
| | |
| | chart_data = {} |
| | |
| | for i, item in enumerate(result, 1): |
| | score_pct = item['score'] * 100 |
| | output_text += f"{i}. **{item['label']}** (ID: {item['id']}): {score_pct:.2f}%\n" |
| | chart_data[item['label']] = item['score'] |
| | |
| | return chart_data, output_text |
| | else: |
| | return None, f"Error: {result.get('error', 'Unknown error')}" |
| | |
| | except Exception as e: |
| | return None, f"Error: {str(e)}" |
| |
|
| | def upsert_labels_admin(admin_token, new_items_json): |
| | """ |
| | Admin function to add new labels. |
| | """ |
| | if not admin_token: |
| | return "Error: Admin token required" |
| | |
| | try: |
| | |
| | items = json.loads(new_items_json) if new_items_json else [] |
| | |
| | result = handler({ |
| | "inputs": { |
| | "op": "upsert_labels", |
| | "token": admin_token, |
| | "items": items |
| | } |
| | }) |
| | |
| | if result.get("status") == "ok": |
| | return f"β
Success! Added {result.get('added', 0)} new labels. Current version: {result.get('labels_version', 'unknown')}" |
| | elif result.get("error") == "unauthorized": |
| | return "β Error: Invalid admin token" |
| | else: |
| | return f"β Error: {result.get('detail', result.get('error', 'Unknown error'))}" |
| | |
| | except json.JSONDecodeError: |
| | return "β Error: Invalid JSON format" |
| | except Exception as e: |
| | return f"β Error: {str(e)}" |
| |
|
| | def reload_labels_admin(admin_token, version): |
| | """ |
| | Admin function to reload a specific label version. |
| | """ |
| | if not admin_token: |
| | return "Error: Admin token required" |
| | |
| | try: |
| | result = handler({ |
| | "inputs": { |
| | "op": "reload_labels", |
| | "token": admin_token, |
| | "version": int(version) if version else 1 |
| | } |
| | }) |
| | |
| | if result.get("status") == "ok": |
| | return f"β
Labels reloaded successfully! Current version: {result.get('labels_version', 'unknown')}" |
| | elif result.get("status") == "nochange": |
| | return f"βΉοΈ No change needed. Current version: {result.get('labels_version', 'unknown')}" |
| | elif result.get("error") == "unauthorized": |
| | return "β Error: Invalid admin token" |
| | elif result.get("error") == "invalid_version": |
| | return "β Error: Invalid version number" |
| | else: |
| | return f"β Error: {result.get('error', 'Unknown error')}" |
| | |
| | except Exception as e: |
| | return f"β Error: {str(e)}" |
| |
|
| | def get_current_stats(): |
| | """ |
| | Get current label statistics. |
| | """ |
| | try: |
| | num_labels = len(handler.class_ids) if hasattr(handler, 'class_ids') else 0 |
| | version = getattr(handler, 'labels_version', 1) |
| | device = handler.device if hasattr(handler, 'device') else "unknown" |
| | |
| | stats = f""" |
| | **Current Statistics:** |
| | - Number of labels: {num_labels} |
| | - Labels version: {version} |
| | - Device: {device} |
| | - Model: MobileCLIP-B |
| | """ |
| | |
| | if hasattr(handler, 'class_names') and len(handler.class_names) > 0: |
| | stats += f"\n- Sample labels: {', '.join(handler.class_names[:5])}" |
| | if len(handler.class_names) > 5: |
| | stats += "..." |
| | |
| | return stats |
| | except Exception as e: |
| | return f"Error getting stats: {str(e)}" |
| |
|
| | |
| | with gr.Blocks(title="MobileCLIP Image Classifier") as demo: |
| | gr.Markdown(""" |
| | # πΌοΈ MobileCLIP-B Zero-Shot Image Classifier |
| | |
| | Upload an image to classify it using MobileCLIP-B model with dynamic label management. |
| | """) |
| | |
| | with gr.Tab("π Image Classification"): |
| | with gr.Row(): |
| | with gr.Column(): |
| | input_image = gr.Image( |
| | type="pil", |
| | label="Upload Image" |
| | ) |
| | top_k_slider = gr.Slider( |
| | minimum=1, |
| | maximum=50, |
| | value=10, |
| | step=1, |
| | label="Number of top results to show" |
| | ) |
| | classify_btn = gr.Button("π Classify Image", variant="primary") |
| | |
| | with gr.Column(): |
| | output_chart = gr.BarPlot( |
| | label="Classification Confidence", |
| | x_label="Label", |
| | y_label="Confidence", |
| | vertical=False, |
| | height=400 |
| | ) |
| | output_text = gr.Markdown(label="Classification Results") |
| | |
| | gr.Examples( |
| | examples=[ |
| | ["https://raw.githubusercontent.com/gradio-app/gradio/main/demo/image_classifier/examples/cheetah.jpg"], |
| | ["https://raw.githubusercontent.com/gradio-app/gradio/main/demo/image_classifier/examples/elephant.jpg"], |
| | ["https://raw.githubusercontent.com/gradio-app/gradio/main/demo/image_classifier/examples/giraffe.jpg"] |
| | ], |
| | inputs=input_image, |
| | label="Example Images" |
| | ) |
| | |
| | classify_btn.click( |
| | classify_image, |
| | inputs=[input_image, top_k_slider], |
| | outputs=[output_chart, output_text] |
| | ) |
| | |
| | with gr.Tab("π§ Admin Panel"): |
| | gr.Markdown(""" |
| | ### Admin Functions |
| | **Note:** Requires admin token (set via environment variable `ADMIN_TOKEN`) |
| | """) |
| | |
| | with gr.Row(): |
| | admin_token_input = gr.Textbox( |
| | label="Admin Token", |
| | type="password", |
| | placeholder="Enter admin token" |
| | ) |
| | |
| | with gr.Accordion("π Current Statistics", open=True): |
| | stats_display = gr.Markdown(value=get_current_stats()) |
| | refresh_stats_btn = gr.Button("π Refresh Stats") |
| | refresh_stats_btn.click( |
| | get_current_stats, |
| | outputs=stats_display |
| | ) |
| | |
| | with gr.Accordion("β Add New Labels", open=False): |
| | gr.Markdown(""" |
| | Add new labels by providing JSON array: |
| | ```json |
| | [ |
| | {"id": 100, "name": "new_object", "prompt": "a photo of a new_object"}, |
| | {"id": 101, "name": "another_object", "prompt": "a photo of another_object"} |
| | ] |
| | ``` |
| | """) |
| | new_items_input = gr.Code( |
| | label="New Items JSON", |
| | language="json", |
| | lines=5, |
| | value='[\n {"id": 100, "name": "example", "prompt": "a photo of example"}\n]' |
| | ) |
| | upsert_btn = gr.Button("β Add Labels", variant="primary") |
| | upsert_output = gr.Markdown() |
| | |
| | upsert_btn.click( |
| | upsert_labels_admin, |
| | inputs=[admin_token_input, new_items_input], |
| | outputs=upsert_output |
| | ) |
| | |
| | with gr.Accordion("π Reload Label Version", open=False): |
| | gr.Markdown("Reload labels from a specific version stored in the Hub") |
| | version_input = gr.Number( |
| | label="Version Number", |
| | value=1, |
| | precision=0 |
| | ) |
| | reload_btn = gr.Button("π Reload Version", variant="primary") |
| | reload_output = gr.Markdown() |
| | |
| | reload_btn.click( |
| | reload_labels_admin, |
| | inputs=[admin_token_input, version_input], |
| | outputs=reload_output |
| | ) |
| | |
| | with gr.Tab("βΉοΈ About"): |
| | gr.Markdown(""" |
| | ## About MobileCLIP-B Classifier |
| | |
| | This Space provides a web interface for Apple's MobileCLIP-B model, optimized for fast zero-shot image classification. |
| | |
| | ### Features: |
| | - π **Fast inference**: < 30ms on GPU |
| | - π·οΈ **Dynamic labels**: Add/update labels without redeployment |
| | - π **Version control**: Track and reload label versions |
| | - π **Visual results**: Bar charts and confidence scores |
| | |
| | ### Environment Variables (set in Space Settings): |
| | - `ADMIN_TOKEN`: Secret token for admin operations |
| | - `HF_LABEL_REPO`: Hub repository for label storage (e.g., "username/labels") |
| | - `HF_WRITE_TOKEN`: Token with write permissions to label repo |
| | - `HF_READ_TOKEN`: Token with read permissions (optional, defaults to write token) |
| | |
| | ### Model Details: |
| | - **Architecture**: MobileCLIP-B with MobileOne blocks |
| | - **Text Encoder**: Transformer-based, 77 token context |
| | - **Image Size**: 224x224 |
| | - **Embedding Dim**: 512 |
| | |
| | ### License: |
| | Model weights are licensed under Apple Sample Code License (ASCL). |
| | """) |
| |
|
| | if __name__ == "__main__": |
| | demo.launch() |