File size: 9,663 Bytes
7215d13
 
 
 
 
 
146963b
 
 
f3a1d44
 
 
146963b
7215d13
 
 
333240a
7215d13
 
 
 
 
 
146963b
7215d13
 
 
 
 
333240a
 
146963b
7215d13
 
 
 
146963b
7215d13
146963b
7215d13
 
 
146963b
7215d13
 
 
 
 
146963b
7215d13
 
146963b
7215d13
146963b
 
 
 
7215d13
 
 
146963b
 
7215d13
146963b
7215d13
146963b
7215d13
146963b
7215d13
 
 
146963b
7215d13
146963b
 
 
 
 
7215d13
 
146963b
 
 
 
 
 
 
 
 
 
 
7215d13
 
 
146963b
7215d13
146963b
7215d13
 
 
146963b
7215d13
 
146963b
 
 
 
 
 
 
 
 
 
 
 
 
7215d13
146963b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7215d13
146963b
7215d13
 
 
 
 
 
146963b
7215d13
 
146963b
7215d13
 
 
 
146963b
 
 
7215d13
 
 
 
 
 
 
 
 
 
 
 
 
146963b
 
7215d13
 
146963b
 
 
 
 
 
 
 
24d016e
146963b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24d016e
 
 
 
146963b
24d016e
146963b
 
 
7215d13
 
146963b
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
import gradio as gr
import tensorflow as tf
from transformers import AutoImageProcessor, AutoModelForImageClassification
import numpy as np
import pymongo
import random
import base64 # Import base64 module
import io      # Import io module
from PIL import Image # Import PIL

print(gr.__version__)

# --- Loading models and DB connection (Keep as is) ---
processor = AutoImageProcessor.from_pretrained("SowmyaKannan/fine_tuned_vit_model")
model = AutoModelForImageClassification.from_pretrained("SowmyaKannan/Vit-FER")
labels = ['angry', 'disgust', 'fear', 'happy', 'sad', 'neutral', 'surprise']
mongo_db_uri = "mongodb+srv://sowmyakannan10:O2BkajONKtfZsNqm@cluster0.zehazwd.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0"
client = None
db = None
quotes_collection = None
author_list = []
try:
    mongodb_uri = mongo_db_uri
    if not mongodb_uri: print("MONGODB_URI not found!!")
    else:
        print("Connecting to MongoDB...")
        client = pymongo.MongoClient(mongodb_uri, serverSelectionTimeoutMS=5000)
        client.admin.command('ping')
        print("Successfully connected to MongoDB!")
        db = client['AhumDB']
        quotes_collection = db['AhumQuotes']
        author_list = sorted(filter(None, quotes_collection.distinct("author")))
        print(f"Fetched {len(author_list)} authors.")
except Exception as e:
    print(f"Error during MongoDB setup: {e}")
    client = None
# --- End loading models and DB connection ---

# --- Quote fetching function (Keep as is) ---
def get_quotes(index=None, authors=None):
    if client is None or quotes_collection is None:
        print("MongoDB connection not available. Cannot fetch quote.")
        return "Quote data unavailable (DB connection issue).", "Unknown"
    pipeline = []
    emotion_label = None
    emotion_match = {}
    if index is not None and 0 <= index < len(labels):
        emotion_label = labels[index]
        emotion_match = {"emotions": {"$regex": f"^{emotion_label}$", "$options": "i"}}
    author_match = {}
    if authors:
        author_match = {"$or": [{"author": {"$regex": f"^{a}$", "$options": "i"}} for a in authors]}
    combined_match = {}
    if emotion_match and author_match: combined_match = {"$and": [emotion_match, author_match]}
    elif emotion_match: combined_match = emotion_match
    elif author_match: combined_match = author_match
    if combined_match: pipeline.append({"$match": combined_match})
    pipeline.append({"$sample": {"size": 1}})
    try:
        result = list(quotes_collection.aggregate(pipeline))
        if result: return result[0].get('quote', "Quote not found."), result[0].get('author', "Unknown")
        else: # Fallback logic
            if emotion_label:
                fallback_pipeline = [{"$match": {"emotions": {"$regex": f"^{emotion_label}$", "$options": "i"}}}, {"$sample": {"size": 1}}]
                fallback_result = list(quotes_collection.aggregate(fallback_pipeline))
                if fallback_result: return fallback_result[0].get('quote', "Quote not found."), fallback_result[0].get('author', "Unknown")
                return f"Finding strength in '{emotion_label}' comes from within.", "Unknown"
            else: return "Could not find a suitable quote at this time.", "Unknown"
    except Exception as e:
        print(f"Error in get_quotes: {e}")
        return "Quote data unavailable (DB query error).", "Unknown"
# --- End quote fetching ---

# --- CORE LOGIC: Process PIL image and get prediction/quote ---
def process_pil_image_and_get_quote(img_pil, selected_authors):
    """Takes a PIL image and author list, returns formatted prediction string."""
    if img_pil is None:
        return "Error: Process function received an empty image."

    try:
        print(f"Processing PIL Image - Mode: {img_pil.mode}, Size: {img_pil.size}") # Debug
        # Ensure image is RGB if required by processor/model
        if img_pil.mode != 'RGB':
            img_pil = img_pil.convert('RGB')
            print("Converted image to RGB.") # Debug

        # Process image with Hugging Face processor
        inputs = processor(images=img_pil, return_tensors="pt")

        # Model prediction
        outputs = model(**inputs)
        logits = outputs.logits
        predicted_class_index = np.argmax(logits.detach().numpy(), axis=1)[0]
        predicted_emotion = labels[predicted_class_index]
        print(f"Predicted Emotion Index: {predicted_class_index}, Emotion: {predicted_emotion}") # Debug

        # Get quote
        quote, author = get_quotes(predicted_class_index, authors=selected_authors)

        return f"Predicted Emotion: {predicted_emotion.capitalize()}\n\n\"{quote}\"\n\n— {author}"

    except Exception as e:
        import traceback
        print(f"Error in process_pil_image_and_get_quote: {e}\n{traceback.format_exc()}")
        return f"An error occurred during image processing: {e}"

# --- API FUNCTION: Handles Base64 input ---
def main_fn_api(img_base64_string, selected_authors):
    """
    API endpoint function. Expects a base64 data URI string for the image.
    Decodes it and calls the core processing function.
    """
    print(">>> API endpoint main_fn_api called.") # Debug
    if not isinstance(img_base64_string, str) or not img_base64_string.startswith('data:image'):
        print(f"Error: Invalid base64 string received: {str(img_base64_string)[:100]}...") # Debug
        return "Error: Invalid image data received via API."

    try:
        # 1. Split header (e.g., "data:image/png;base64,") and encoded data
        try:
            header, encoded_data = img_base64_string.split(',', 1)
        except ValueError:
             print("Error: Base64 string does not contain expected comma separator.")
             return "Error: Malformed Base64 image data."

        # 2. Decode Base64 data
        print(f"Attempting to decode base64 data (starts with {encoded_data[:10]}...).") # Debug
        decoded_bytes = base64.b64decode(encoded_data)
        print(f"Decoded {len(decoded_bytes)} bytes.") # Debug

        # 3. Create PIL Image from decoded bytes
        img_pil = Image.open(io.BytesIO(decoded_bytes))
        print("Successfully created PIL Image from decoded bytes.") # Debug

        # 4. Call the core processing function with the PIL image
        return process_pil_image_and_get_quote(img_pil, selected_authors)

    except base64.binascii.Error as e:
        print(f"Error decoding Base64: {e}") # Debug
        return "Error: Invalid Base64 encoding."
    except Exception as e:
        import traceback
        print(f"Error in main_fn_api: {e}\n{traceback.format_exc()}")
        return f"An error occurred during API image processing: {e}"

# --- Other helper functions (Keep as is) ---
def get_random_quote(authors):
    quote, author = get_quotes(index=None, authors=authors)
    return f"\"{quote}\"\n\n— {author}"

def select_all_authors():
    return author_list
# --- End other helper functions ---


# --- Build Gradio Interface ---
with gr.Blocks() as demo:
    gr.Markdown("## Emotion Detection from Image & Quote")

    with gr.Row():
        # This Image component is primarily for the Web UI interaction
        image_input_ui = gr.Image(type="pil", label="Upload Image (for Web UI)")
        output_text = gr.Textbox(label="Prediction & Quote", lines=5) # Increased lines

    with gr.Row():
        author_selector = gr.Dropdown(
            choices=author_list,
            multiselect=True,
            label="Select Author(s)",
            info="Optional: filter quotes by author"
        )

    with gr.Row():
        select_all_button = gr.Button("Select All Authors")

    with gr.Row():
        # This button triggers the processing for images uploaded via the UI
        submit_button_ui = gr.Button("Detect Emotion & Get Quote (from UI Upload)")
        random_quote_button = gr.Button("Get a Random Quote")

    # --- Event Handlers ---

    # 1. UI Submit Button: Calls the core logic function directly with the PIL image from gr.Image
    submit_button_ui.click(
        process_pil_image_and_get_quote, # Target function expects PIL image
        inputs=[image_input_ui, author_selector], # Inputs from UI components
        outputs=output_text
        # No api_name here means this specific button click isn't easily callable via API
    )

    # 2. API Endpoint Registration:
    # We need to expose `main_fn_api` so the Android app can call it.
    # We can link it to a component's event and give it the correct `api_name`.
    # Using a "hidden" Textbox's `change` event is a way to register it without a visible trigger.
    api_trigger_input = gr.Textbox(visible=False) # Hidden component to hook the API call

    api_trigger_input.change( # Event doesn't really matter, just need to link the function
         fn=main_fn_api, # The function that expects BASE64 STRING
         inputs=[api_trigger_input, author_selector], # Inputs match main_fn_api signature
         outputs=output_text, # API calls need an output defined
         api_name="main_fn" # <--- CRITICAL: This name matches the Android API URL target
                            #       (.../gradio_api/call/main_fn)
     )

    # 3. Random Quote Button (Keep as is)
    random_quote_button.click(
        get_random_quote,
        inputs=author_selector,
        outputs=output_text,
        api_name="get_random_quote" # Keep this if Android uses it too
    )

    # 4. Select All Authors Button (Keep as is)
    select_all_button.click(select_all_authors, inputs=None, outputs=author_selector)

print("Launching Gradio Interface...")
demo.launch() # share=True if you need external access temporarily for testing