| | |
| | |
| | |
| | |
| | |
| |
|
| | import gradio as gr |
| | import torch |
| | from transformers import ViTForImageClassification, ViTImageProcessor |
| | from PIL import Image |
| | import numpy as np |
| | import os |
| |
|
| | print("="*70) |
| | print("π AI EMOTION DETECTOR - INITIALIZING") |
| | print("="*70) |
| |
|
| | |
| | MODEL_ID = "koyelog/face" |
| | DEVICE = "cuda" if torch.cuda.is_available() else "cpu" |
| |
|
| | print(f"\nπ¦ Model ID: {MODEL_ID}") |
| | print(f"π₯οΈ Device: {DEVICE}") |
| | print(f"πΎ PyTorch Version: {torch.__version__}") |
| |
|
| | |
| | print("\nβ³ Loading model from HuggingFace...") |
| |
|
| | try: |
| | model = ViTForImageClassification.from_pretrained( |
| | MODEL_ID, |
| | cache_dir="./model_cache" |
| | ) |
| | processor = ViTImageProcessor.from_pretrained( |
| | MODEL_ID, |
| | cache_dir="./model_cache" |
| | ) |
| | model.to(DEVICE) |
| | model.eval() |
| | print("β
Model loaded successfully!") |
| | print(f"π Model Parameters: {sum(p.numel() for p in model.parameters()):,}") |
| | |
| | except Exception as e: |
| | print(f"β ERROR loading model: {e}") |
| | raise |
| |
|
| | |
| | EMOTIONS = { |
| | 0: {'name': 'Angry', 'emoji': 'π ', 'color': '#ff4444', 'description': 'Showing anger or frustration'}, |
| | 1: {'name': 'Disgust', 'emoji': 'π€’', 'color': '#44ff44', 'description': 'Expressing disgust or dislike'}, |
| | 2: {'name': 'Fear', 'emoji': 'π¨', 'color': '#9944ff', 'description': 'Showing fear or anxiety'}, |
| | 3: {'name': 'Happy', 'emoji': 'π', 'color': '#ffdd44', 'description': 'Expressing happiness or joy'}, |
| | 4: {'name': 'Sad', 'emoji': 'π’', 'color': '#4444ff', 'description': 'Showing sadness or sorrow'}, |
| | 5: {'name': 'Surprise', 'emoji': 'π²', 'color': '#ff44ff', 'description': 'Expressing surprise or shock'}, |
| | 6: {'name': 'Neutral', 'emoji': 'π', 'color': '#888888', 'description': 'No strong emotion detected'} |
| | } |
| |
|
| | print(f"\nπ Loaded {len(EMOTIONS)} emotion classes:") |
| | for idx, emo in EMOTIONS.items(): |
| | print(f" {idx}: {emo['emoji']} {emo['name']}") |
| |
|
| | |
| | @torch.no_grad() |
| | def predict_emotion(image): |
| | """ |
| | Predict emotion from image |
| | Args: |
| | image: PIL Image or numpy array |
| | Returns: |
| | results_dict: Dictionary for Gradio Label |
| | html_output: Formatted HTML result |
| | """ |
| | |
| | if image is None: |
| | return None, """ |
| | <div style='text-align: center; padding: 40px; color: #ff4444;'> |
| | <h2>β οΈ No Image Provided</h2> |
| | <p>Please upload an image or use webcam to capture!</p> |
| | </div> |
| | """ |
| | |
| | try: |
| | |
| | if isinstance(image, np.ndarray): |
| | image = Image.fromarray(image) |
| | |
| | |
| | if image.mode != 'RGB': |
| | image = image.convert('RGB') |
| | |
| | original_size = image.size |
| | print(f"\nπΈ Processing image: {original_size[0]}x{original_size[1]}") |
| | |
| | |
| | inputs = processor(images=image, return_tensors="pt") |
| | inputs = {k: v.to(DEVICE) for k, v in inputs.items()} |
| | |
| | |
| | outputs = model(**inputs) |
| | logits = outputs.logits |
| | probs = torch.nn.functional.softmax(logits, dim=-1)[0].cpu() |
| | |
| | |
| | predicted_id = torch.argmax(probs).item() |
| | confidence = probs[predicted_id].item() |
| | |
| | |
| | emotion = EMOTIONS[predicted_id] |
| | |
| | print(f"π― Prediction: {emotion['emoji']} {emotion['name']}") |
| | print(f"π Confidence: {confidence*100:.2f}%") |
| | print(f"π Top 3 emotions:") |
| | top3_indices = torch.topk(probs, 3).indices |
| | for idx in top3_indices: |
| | print(f" {EMOTIONS[idx.item()]['emoji']} {EMOTIONS[idx.item()]['name']}: {probs[idx]*100:.2f}%") |
| | |
| | |
| | results = { |
| | f"{EMOTIONS[i]['emoji']} {EMOTIONS[i]['name']}": float(probs[i]) |
| | for i in range(len(EMOTIONS)) |
| | } |
| | |
| | |
| | html = generate_result_html( |
| | emotion['name'], |
| | emotion['emoji'], |
| | emotion['color'], |
| | emotion['description'], |
| | confidence, |
| | probs |
| | ) |
| | |
| | return results, html |
| | |
| | except Exception as e: |
| | print(f"β ERROR during prediction: {e}") |
| | import traceback |
| | traceback.print_exc() |
| | |
| | error_html = f""" |
| | <div style='text-align: center; padding: 40px; background: #ffe6e6; border-radius: 15px;'> |
| | <h2 style='color: #ff4444;'>β Prediction Error</h2> |
| | <p style='color: #666;'>{str(e)}</p> |
| | <p style='color: #999; font-size: 0.9em;'>Please try a different image</p> |
| | </div> |
| | """ |
| | return None, error_html |
| |
|
| | |
| | def generate_result_html(name, emoji, color, description, confidence, probs): |
| | """Generate beautiful HTML result display""" |
| | |
| | |
| | bars_html = "" |
| | for idx in sorted(range(len(EMOTIONS)), key=lambda i: probs[i], reverse=True): |
| | emo = EMOTIONS[idx] |
| | prob = probs[idx].item() |
| | percentage = prob * 100 |
| | bar_width = min(percentage, 100) |
| | |
| | bars_html += f""" |
| | <div style='margin: 12px 0;'> |
| | <div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;'> |
| | <div style='display: flex; align-items: center; gap: 10px;'> |
| | <span style='font-size: 1.8em;'>{emo['emoji']}</span> |
| | <span style='font-weight: 600; color: #333;'>{emo['name']}</span> |
| | </div> |
| | <span style='font-weight: 700; color: {emo['color']}; font-size: 1.1em;'>{percentage:.1f}%</span> |
| | </div> |
| | <div style='width: 100%; background: #e9ecef; border-radius: 10px; height: 12px; overflow: hidden; box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);'> |
| | <div style='width: {bar_width}%; background: linear-gradient(90deg, {emo['color']}, {emo['color']}dd); height: 100%; transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 10px;'></div> |
| | </div> |
| | </div> |
| | """ |
| | |
| | |
| | html = f""" |
| | <div style='font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, sans-serif; max-width: 100%;'> |
| | |
| | <!-- Main Result Card --> |
| | <div style=' |
| | text-align: center; |
| | padding: 50px 30px; |
| | background: linear-gradient(135deg, {color}18 0%, {color}30 100%); |
| | border-radius: 25px; |
| | box-shadow: 0 10px 40px rgba(0,0,0,0.12); |
| | margin-bottom: 30px; |
| | border: 2px solid {color}40; |
| | '> |
| | <div style=' |
| | font-size: 120px; |
| | margin: 0 0 20px 0; |
| | animation: bounceIn 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55); |
| | display: inline-block; |
| | '> |
| | {emoji} |
| | </div> |
| | |
| | <h1 style=' |
| | color: {color}; |
| | font-size: 3.5em; |
| | margin: 20px 0 10px 0; |
| | font-weight: 800; |
| | text-shadow: 2px 2px 8px rgba(0,0,0,0.1); |
| | letter-spacing: -1px; |
| | '> |
| | {name} |
| | </h1> |
| | |
| | <p style=' |
| | font-size: 1.3em; |
| | color: #555; |
| | margin: 15px 0; |
| | font-weight: 500; |
| | '> |
| | {description} |
| | </p> |
| | |
| | <div style=' |
| | display: inline-flex; |
| | align-items: center; |
| | gap: 15px; |
| | margin: 25px 0; |
| | padding: 15px 35px; |
| | background: white; |
| | border-radius: 50px; |
| | box-shadow: 0 4px 20px rgba(0,0,0,0.1); |
| | '> |
| | <span style='font-size: 1.2em; color: #666;'>Confidence:</span> |
| | <span style='font-size: 2em; font-weight: 800; color: {color};'>{confidence*100:.1f}%</span> |
| | </div> |
| | |
| | <!-- Animated Confidence Bar --> |
| | <div style=' |
| | width: 100%; |
| | max-width: 500px; |
| | height: 50px; |
| | background: #e9ecef; |
| | border-radius: 25px; |
| | overflow: hidden; |
| | margin: 30px auto 0; |
| | box-shadow: inset 0 4px 8px rgba(0,0,0,0.1); |
| | position: relative; |
| | '> |
| | <div style=' |
| | width: {confidence*100}%; |
| | height: 100%; |
| | background: linear-gradient(90deg, {color}, {color}cc); |
| | border-radius: 25px; |
| | transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1); |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | box-shadow: 0 0 20px {color}80; |
| | '> |
| | <span style=' |
| | color: white; |
| | font-weight: 800; |
| | font-size: 1.3em; |
| | text-shadow: 0 2px 4px rgba(0,0,0,0.3); |
| | '> |
| | {confidence*100:.1f}% |
| | </span> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <!-- Detailed Breakdown --> |
| | <div style=' |
| | background: white; |
| | padding: 35px; |
| | border-radius: 20px; |
| | box-shadow: 0 8px 32px rgba(0,0,0,0.08); |
| | border: 1px solid #e9ecef; |
| | '> |
| | <h2 style=' |
| | margin: 0 0 25px 0; |
| | color: #333; |
| | font-size: 1.8em; |
| | font-weight: 700; |
| | display: flex; |
| | align-items: center; |
| | gap: 10px; |
| | '> |
| | π Detailed Emotion Analysis |
| | </h2> |
| | |
| | {bars_html} |
| | </div> |
| | |
| | <!-- Model Info Footer --> |
| | <div style=' |
| | margin-top: 25px; |
| | padding: 20px; |
| | background: linear-gradient(135deg, #f8f9fa, #e9ecef); |
| | border-radius: 15px; |
| | text-align: center; |
| | font-size: 0.9em; |
| | color: #666; |
| | '> |
| | <p style='margin: 5px 0;'> |
| | <strong>Model:</strong> koyelog/face (Vision Transformer) | |
| | <strong>Accuracy:</strong> 98.80% | |
| | <strong>Parameters:</strong> 85.8M |
| | </p> |
| | </div> |
| | </div> |
| | |
| | <style> |
| | @keyframes bounceIn {{ |
| | 0% {{ |
| | opacity: 0; |
| | transform: scale(0.3) translateY(-50px); |
| | }} |
| | 50% {{ |
| | opacity: 1; |
| | transform: scale(1.05); |
| | }} |
| | 70% {{ |
| | transform: scale(0.9); |
| | }} |
| | 100% {{ |
| | transform: scale(1); |
| | }} |
| | }} |
| | </style> |
| | """ |
| | |
| | return html |
| |
|
| | |
| | print("\nπ¨ Building Gradio interface...") |
| |
|
| | |
| | custom_css = """ |
| | .gradio-container { |
| | font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif !important; |
| | max-width: 1400px !important; |
| | } |
| | |
| | .main-header { |
| | text-align: center; |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | color: white; |
| | padding: 60px 30px; |
| | border-radius: 25px; |
| | margin-bottom: 40px; |
| | box-shadow: 0 15px 50px rgba(102, 126, 234, 0.3); |
| | } |
| | |
| | .tab-nav button { |
| | font-size: 18px !important; |
| | font-weight: 600 !important; |
| | padding: 18px 30px !important; |
| | } |
| | |
| | .gr-button-primary { |
| | background: linear-gradient(135deg, #667eea, #764ba2) !important; |
| | border: none !important; |
| | font-size: 18px !important; |
| | font-weight: 600 !important; |
| | padding: 16px 40px !important; |
| | border-radius: 12px !important; |
| | transition: all 0.3s ease !important; |
| | } |
| | |
| | .gr-button-primary:hover { |
| | transform: translateY(-2px) !important; |
| | box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4) !important; |
| | } |
| | |
| | footer { |
| | visibility: hidden !important; |
| | } |
| | """ |
| |
|
| | |
| | with gr.Blocks( |
| | theme=gr.themes.Soft( |
| | primary_hue="purple", |
| | secondary_hue="pink", |
| | font=gr.themes.GoogleFont("Inter") |
| | ), |
| | css=custom_css, |
| | title="π AI Emotion Detector | koyelog", |
| | analytics_enabled=False |
| | ) as demo: |
| | |
| | |
| | gr.HTML(""" |
| | <div class="main-header"> |
| | <h1 style='font-size: 4em; margin: 0; font-weight: 900; text-shadow: 3px 3px 6px rgba(0,0,0,0.2);'> |
| | π AI Emotion Detector |
| | </h1> |
| | <p style='font-size: 1.5em; margin: 20px 0 10px; opacity: 0.95; font-weight: 500;'> |
| | Powered by Vision Transformer | 98.80% Validation Accuracy |
| | </p> |
| | <p style='font-size: 1.1em; opacity: 0.85;'> |
| | Model: <strong>koyelog/face</strong> | 85.8M Parameters | Real-time Detection |
| | </p> |
| | <div style='margin-top: 20px; display: flex; gap: 15px; justify-content: center; flex-wrap: wrap;'> |
| | <span style='background: rgba(255,255,255,0.25); padding: 10px 25px; border-radius: 25px; backdrop-filter: blur(10px);'> |
| | π Angry |
| | </span> |
| | <span style='background: rgba(255,255,255,0.25); padding: 10px 25px; border-radius: 25px; backdrop-filter: blur(10px);'> |
| | π€’ Disgust |
| | </span> |
| | <span style='background: rgba(255,255,255,0.25); padding: 10px 25px; border-radius: 25px; backdrop-filter: blur(10px);'> |
| | π¨ Fear |
| | </span> |
| | <span style='background: rgba(255,255,255,0.25); padding: 10px 25px; border-radius: 25px; backdrop-filter: blur(10px);'> |
| | π Happy |
| | </span> |
| | <span style='background: rgba(255,255,255,0.25); padding: 10px 25px; border-radius: 25px; backdrop-filter: blur(10px);'> |
| | π’ Sad |
| | </span> |
| | <span style='background: rgba(255,255,255,0.25); padding: 10px 25px; border-radius: 25px; backdrop-filter: blur(10px);'> |
| | π² Surprise |
| | </span> |
| | <span style='background: rgba(255,255,255,0.25); padding: 10px 25px; border-radius: 25px; backdrop-filter: blur(10px);'> |
| | π Neutral |
| | </span> |
| | </div> |
| | </div> |
| | """) |
| | |
| | with gr.Tabs(): |
| | |
| | |
| | with gr.Tab("πΉ Live Webcam Detection"): |
| | gr.Markdown(""" |
| | ### π₯ Capture Your Emotion in Real-Time |
| | Click the camera button to capture your face and instantly detect your emotion! |
| | """) |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=1): |
| | webcam_input = gr.Image( |
| | sources=["webcam"], |
| | type="pil", |
| | label="πΈ Your Face", |
| | streaming=False, |
| | mirror_webcam=True |
| | ) |
| | webcam_button = gr.Button( |
| | "π Detect My Emotion", |
| | variant="primary", |
| | size="lg", |
| | scale=1 |
| | ) |
| | |
| | with gr.Column(scale=1): |
| | webcam_html = gr.HTML(label="π― Emotion Result") |
| | webcam_label = gr.Label( |
| | label="π Emotion Probabilities", |
| | num_top_classes=7 |
| | ) |
| | |
| | webcam_button.click( |
| | fn=predict_emotion, |
| | inputs=webcam_input, |
| | outputs=[webcam_label, webcam_html] |
| | ) |
| | |
| | |
| | with gr.Tab("πΌοΈ Upload Image"): |
| | gr.Markdown(""" |
| | ### π€ Upload or Drag & Drop Face Image |
| | Supports JPG, PNG, JPEG formats. Best results with front-facing, well-lit photos! |
| | """) |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=1): |
| | image_input = gr.Image( |
| | type="pil", |
| | label="πΌοΈ Upload Face Image", |
| | sources=["upload", "clipboard"] |
| | ) |
| | image_button = gr.Button( |
| | "π Detect Emotion", |
| | variant="primary", |
| | size="lg" |
| | ) |
| | |
| | with gr.Column(scale=1): |
| | image_html = gr.HTML(label="π― Emotion Result") |
| | image_label = gr.Label( |
| | label="π Emotion Probabilities", |
| | num_top_classes=7 |
| | ) |
| | |
| | image_button.click( |
| | fn=predict_emotion, |
| | inputs=image_input, |
| | outputs=[image_label, image_html] |
| | ) |
| | |
| | |
| | gr.HTML(""" |
| | <div style=' |
| | text-align: center; |
| | margin-top: 60px; |
| | padding: 50px 30px; |
| | background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
| | border-radius: 25px; |
| | box-shadow: 0 8px 32px rgba(0,0,0,0.08); |
| | '> |
| | <h2 style='color: #333; margin-bottom: 30px; font-size: 2em;'> |
| | π Model Information |
| | </h2> |
| | |
| | <div style=' |
| | display: grid; |
| | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| | gap: 25px; |
| | margin: 30px 0; |
| | '> |
| | <div style='background: white; padding: 25px; border-radius: 15px; box-shadow: 0 4px 16px rgba(0,0,0,0.06);'> |
| | <p style='font-weight: 700; color: #667eea; font-size: 1.1em; margin-bottom: 10px;'>Model ID</p> |
| | <p style='font-size: 1.2em; color: #333; font-weight: 600;'>koyelog/face</p> |
| | </div> |
| | <div style='background: white; padding: 25px; border-radius: 15px; box-shadow: 0 4px 16px rgba(0,0,0,0.06);'> |
| | <p style='font-weight: 700; color: #667eea; font-size: 1.1em; margin-bottom: 10px;'>Architecture</p> |
| | <p style='font-size: 1.2em; color: #333; font-weight: 600;'>Vision Transformer (ViT)</p> |
| | </div> |
| | <div style='background: white; padding: 25px; border-radius: 15px; box-shadow: 0 4px 16px rgba(0,0,0,0.06);'> |
| | <p style='font-weight: 700; color: #667eea; font-size: 1.1em; margin-bottom: 10px;'>Parameters</p> |
| | <p style='font-size: 1.2em; color: #333; font-weight: 600;'>85.8 Million</p> |
| | </div> |
| | <div style='background: white; padding: 25px; border-radius: 15px; box-shadow: 0 4px 16px rgba(0,0,0,0.06);'> |
| | <p style='font-weight: 700; color: #667eea; font-size: 1.1em; margin-bottom: 10px;'>Accuracy</p> |
| | <p style='font-size: 1.2em; color: #333; font-weight: 600;'>Train: 99.29% | Val: 98.80%</p> |
| | </div> |
| | </div> |
| | |
| | <div style='margin: 30px 0; padding: 25px; background: white; border-radius: 15px; box-shadow: 0 4px 16px rgba(0,0,0,0.06);'> |
| | <p style='font-weight: 700; color: #333; font-size: 1.3em; margin-bottom: 15px;'> |
| | Training Details |
| | </p> |
| | <p style='color: #666; font-size: 1.05em; line-height: 1.6;'> |
| | <strong>Dataset:</strong> 181,230 images across 7 emotion categories<br> |
| | <strong>Training Epochs:</strong> 20 epochs with dual T4 GPUs<br> |
| | <strong>Best Epoch:</strong> Epoch 20/20 (Val Acc: 98.80%)<br> |
| | <strong>License:</strong> MIT License |
| | </p> |
| | </div> |
| | |
| | <p style='color: #666; font-size: 1.05em; margin-top: 30px; line-height: 1.6;'> |
| | β οΈ <strong>Best Results:</strong> Front-facing photos | Good lighting | Single face | Clear expressions |
| | </p> |
| | |
| | <p style='color: #999; font-size: 0.95em; margin-top: 30px;'> |
| | Created by <strong style='color: #667eea;'>Koyeliya Ghosh</strong><br> |
| | <a href='https://huggingface.co/koyelog/face' target='_blank' style='color: #667eea; font-weight: 600;'> |
| | View Model on HuggingFace β |
| | </a> |
| | </p> |
| | </div> |
| | """) |
| |
|
| | |
| | if __name__ == "__main__": |
| | print("\n" + "="*70) |
| | print("π LAUNCHING EMOTION DETECTION APP") |
| | print("="*70) |
| | print("β
Model loaded and ready") |
| | print("β
Gradio interface built") |
| | print("β
Starting server...\n") |
| | |
| | demo.launch( |
| | server_name="0.0.0.0", |
| | server_port=7860, |
| | share=False, |
| | show_error=True, |
| | show_api=True |
| | ) |
| |
|