Spaces:
Sleeping
Sleeping
| import subprocess | |
| import sys | |
| import os | |
| # Check if running in a standard environment (not Colab/Jupyter) | |
| # and install packages if needed | |
| if not os.path.exists("/.dockerenv") and not os.path.exists("/kaggle"): | |
| try: | |
| import spacy | |
| import matplotlib | |
| import gradio | |
| except ImportError: | |
| print("Installing required packages...") | |
| subprocess.check_call([sys.executable, "-m", "pip", "install", | |
| "spacy", "matplotlib", "gradio"]) | |
| # Download spaCy model | |
| subprocess.check_call([sys.executable, "-m", "spacy", "download", "en_core_web_md"]) | |
| import spacy | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import gradio as gr | |
| import re | |
| from collections import Counter | |
| print("Setting up spaCy-based emotion analysis model...") | |
| # Load spaCy model | |
| print("Loading spaCy model (this takes just a moment)...") | |
| nlp = spacy.load("en_core_web_md") | |
| # Enhanced emotion categories with carefully selected keywords | |
| EMOTION_CATEGORIES = { | |
| 'joy': [ | |
| 'happy', 'joyful', 'delighted', 'excited', 'cheerful', | |
| 'glad', 'elated', 'jubilant', 'overjoyed', 'pleased', | |
| 'ecstatic', 'thrilled', 'euphoric', 'content', 'blissful' | |
| ], | |
| 'sadness': [ | |
| 'sad', 'unhappy', 'depressed', 'disappointed', 'sorrowful', | |
| 'heartbroken', 'melancholy', 'grief', 'somber', 'mournful', | |
| 'gloomy', 'despondent', 'downcast', 'miserable', 'devastated' | |
| ], | |
| 'anger': [ | |
| 'angry', 'furious', 'enraged', 'irritated', 'annoyed', | |
| 'outraged', 'hostile', 'mad', 'infuriated', 'indignant', | |
| 'livid', 'irate', 'fuming', 'seething', 'resentful' | |
| ], | |
| 'fear': [ | |
| 'afraid', 'scared', 'frightened', 'terrified', 'anxious', | |
| 'worried', 'nervous', 'panicked', 'horrified', 'apprehensive', | |
| 'fearful', 'uneasy', 'alarmed', 'dread', 'paranoid' | |
| ], | |
| 'surprise': [ | |
| 'surprised', 'amazed', 'astonished', 'shocked', 'stunned', | |
| 'startled', 'astounded', 'bewildered', 'unexpected', 'awestruck', | |
| 'flabbergasted', 'dumbfounded', 'incredulous', 'perplexed', 'thunderstruck' | |
| ], | |
| 'love': [ | |
| 'loving', 'affectionate', 'fond', 'adoring', 'caring', | |
| 'devoted', 'passionate', 'tender', 'compassionate', 'cherishing', | |
| 'enamored', 'smitten', 'infatuated', 'admiring', 'doting' | |
| ], | |
| 'sarcasm': [ | |
| 'sarcastic', 'ironic', 'mocking', 'cynical', 'satirical', | |
| 'sardonic', 'facetious', 'contemptuous', 'caustic', 'biting', | |
| 'scornful', 'derisive', 'snide', 'taunting', 'wry' | |
| ], | |
| 'disgust': [ | |
| 'disgusted', 'revolted', 'nauseated', 'repulsed', 'sickened', | |
| 'appalled', 'repelled', 'abhorred', 'loathing', 'distaste', | |
| 'aversion', 'revulsion', 'repugnance', 'horrified', 'offended' | |
| ], | |
| 'anticipation': [ | |
| 'anticipating', 'expecting', 'awaiting', 'looking forward', 'hopeful', | |
| 'eager', 'excited', 'impatient', 'prepared', 'ready', | |
| 'vigilant', 'attentive', 'watchful', 'alert', 'expectant' | |
| ], | |
| 'trust': [ | |
| 'trusting', 'confident', 'assured', 'secure', 'certain', | |
| 'reliant', 'faithful', 'believing', 'dependable', 'reliable', | |
| 'credible', 'trustworthy', 'honest', 'loyal', 'devoted' | |
| ] | |
| } | |
| # Define emotion colors for visualization | |
| EMOTION_COLORS = { | |
| 'joy': '#F1C40F', # Yellow | |
| 'sadness': '#3498DB', # Blue | |
| 'anger': '#E74C3C', # Red | |
| 'fear': '#7D3C98', # Purple | |
| 'surprise': '#2ECC71', # Green | |
| 'love': '#E91E63', # Pink | |
| 'sarcasm': '#FF7F50', # Coral | |
| 'disgust': '#8E44AD', # Dark Purple | |
| 'anticipation': '#F39C12', # Orange | |
| 'trust': '#16A085' # Teal | |
| } | |
| # Common sentiment phrases and expressions for improved detection | |
| EMOTION_PHRASES = { | |
| 'joy': [ | |
| 'over the moon', 'on cloud nine', 'couldn\'t be happier', | |
| 'best day ever', 'made my day', 'feeling great', | |
| 'absolutely thrilled', 'jumping for joy', 'bursting with happiness', | |
| 'walking on sunshine', 'flying high', 'tickled pink' | |
| ], | |
| 'sadness': [ | |
| 'broke my heart', 'in tears', 'feel like crying', | |
| 'deeply saddened', 'lost all hope', 'feel empty', | |
| 'devastating news', 'hit hard', 'feel down', 'soul-crushing', | |
| 'falling apart', 'world is ending', 'deeply hurt' | |
| ], | |
| 'anger': [ | |
| 'makes my blood boil', 'fed up with', 'had it with', | |
| 'sick and tired of', 'drives me crazy', 'lost my temper', | |
| 'absolutely furious', 'beyond frustrated', 'driving me up the wall', | |
| 'at my wit\'s end', 'through the roof', 'blow a gasket', 'see red' | |
| ], | |
| 'fear': [ | |
| 'scared to death', 'freaking out', 'keeps me up at night', | |
| 'terrified of', 'living in fear', 'panic attack', | |
| 'nervous wreck', 'can\'t stop worrying', 'break out in a cold sweat', | |
| 'shaking like a leaf', 'scared stiff', 'frozen with fear' | |
| ], | |
| 'surprise': [ | |
| 'can\'t believe', 'took me by surprise', 'out of nowhere', | |
| 'never expected', 'caught off guard', 'mind blown', | |
| 'plot twist', 'jaw dropped', 'knocked my socks off', | |
| 'took my breath away', 'blew me away', 'speechless' | |
| ], | |
| 'love': [ | |
| 'deeply in love', 'means the world to me', 'treasure every moment', | |
| 'hold dear', 'close to my heart', 'forever grateful', | |
| 'truly blessed', 'never felt this way', 'head over heels', | |
| 'madly in love', 'heart skips a beat', 'love with all my heart' | |
| ], | |
| 'sarcasm': [ | |
| 'just what I needed', 'couldn\'t get any better', 'how wonderful', | |
| 'oh great', 'lucky me', 'my favorite part', | |
| 'thrilled to bits', 'way to go', 'thanks for nothing', | |
| 'brilliant job', 'story of my life', 'what a surprise' | |
| ], | |
| 'disgust': [ | |
| 'makes me sick', 'turn my stomach', 'can\'t stand', | |
| 'absolutely disgusting', 'utterly repulsive', 'gross', | |
| 'revolting sight', 'nauseating', 'skin crawl', | |
| 'makes me want to vomit', 'repulsed by', 'can hardly look at' | |
| ], | |
| 'anticipation': [ | |
| 'looking forward to', 'can\'t wait for', 'counting down the days', | |
| 'eagerly awaiting', 'excited about', 'in anticipation of', | |
| 'on the edge of my seat', 'can hardly wait', 'dying to see', | |
| 'marked on my calendar', 'preparing for', 'gearing up for' | |
| ], | |
| 'trust': [ | |
| 'rely on completely', 'trust with my life', 'put my faith in', | |
| 'never let me down', 'count on', 'believe in', | |
| 'have confidence in', 'trustworthy', 'dependable', | |
| 'true to their word', 'rock solid', 'through thick and thin' | |
| ] | |
| } | |
| # Contextual emotion indicators for better analysis | |
| CONTEXTUAL_INDICATORS = { | |
| 'intensifiers': ['very', 'extremely', 'incredibly', 'absolutely', 'totally', 'completely', 'utterly'], | |
| 'negators': ['not', 'never', 'no', 'none', 'neither', 'nor', 'hardly', 'barely'], | |
| 'hedges': ['somewhat', 'kind of', 'sort of', 'a bit', 'slightly', 'perhaps', 'maybe'], | |
| 'boosters': ['definitely', 'certainly', 'absolutely', 'undoubtedly', 'surely', 'clearly'], | |
| 'punctuation': {'!': 'emphasis', '?': 'question', '...': 'hesitation'} | |
| } | |
| # Emotional verdict categories for intelligently classifying mixed emotions | |
| EMOTION_VERDICT_CATEGORIES = { | |
| # Single dominant emotions (when over 35%) | |
| 'purely_joyful': {'conditions': [('joy', 0.35)], 'label': 'Purely Joyful', 'description': 'Expressing happiness and positive emotions'}, | |
| 'deeply_sad': {'conditions': [('sadness', 0.35)], 'label': 'Deeply Sad', 'description': 'Expressing sadness and negative emotions'}, | |
| 'intensely_angry': {'conditions': [('anger', 0.35)], 'label': 'Intensely Angry', 'description': 'Expressing anger and frustration'}, | |
| 'primarily_fearful': {'conditions': [('fear', 0.35)], 'label': 'Primarily Fearful', 'description': 'Expressing fear and anxiety'}, | |
| 'genuinely_surprised': {'conditions': [('surprise', 0.35)], 'label': 'Genuinely Surprised', 'description': 'Expressing surprise and astonishment'}, | |
| 'deeply_loving': {'conditions': [('love', 0.35)], 'label': 'Deeply Loving', 'description': 'Expressing love and affection'}, | |
| 'clearly_sarcastic': {'conditions': [('sarcasm', 0.35)], 'label': 'Clearly Sarcastic', 'description': 'Expressing sarcasm and irony'}, | |
| 'utterly_disgusted': {'conditions': [('disgust', 0.35)], 'label': 'Utterly Disgusted', 'description': 'Expressing disgust and revulsion'}, | |
| 'eagerly_anticipating': {'conditions': [('anticipation', 0.35)], 'label': 'Eagerly Anticipating', 'description': 'Expressing anticipation and eagerness'}, | |
| 'firmly_trusting': {'conditions': [('trust', 0.35)], 'label': 'Firmly Trusting', 'description': 'Expressing trust and confidence'}, | |
| # Common emotional combinations | |
| 'bitter_sweet': { | |
| 'conditions': [('joy', 0.2), ('sadness', 0.2)], | |
| 'label': 'Bittersweet', | |
| 'description': 'Mixed feelings of happiness and sadness' | |
| }, | |
| 'anxious_excitement': { | |
| 'conditions': [('anticipation', 0.2), ('fear', 0.2)], | |
| 'label': 'Anxious Excitement', | |
| 'description': 'Mixture of excitement and nervousness' | |
| }, | |
| 'angry_disappointment': { | |
| 'conditions': [('anger', 0.2), ('sadness', 0.2)], | |
| 'label': 'Angry Disappointment', | |
| 'description': 'Disappointment expressed through anger' | |
| }, | |
| 'ironic_amusement': { | |
| 'conditions': [('sarcasm', 0.2), ('joy', 0.15)], | |
| 'label': 'Ironic Amusement', | |
| 'description': 'Finding humor through irony or sarcasm' | |
| }, | |
| 'fearful_anticipation': { | |
| 'conditions': [('fear', 0.2), ('anticipation', 0.2)], | |
| 'label': 'Fearful Anticipation', | |
| 'description': 'Anxiously awaiting something' | |
| }, | |
| 'relieved_surprise': { | |
| 'conditions': [('surprise', 0.2), ('joy', 0.15)], | |
| 'label': 'Relieved Surprise', | |
| 'description': 'Surprise with positive outcome' | |
| }, | |
| 'shocked_disappointment': { | |
| 'conditions': [('surprise', 0.2), ('sadness', 0.15)], | |
| 'label': 'Shocked Disappointment', | |
| 'description': 'Unexpectedly negative outcome' | |
| }, | |
| 'disgusted_anger': { | |
| 'conditions': [('disgust', 0.2), ('anger', 0.2)], | |
| 'label': 'Disgusted Anger', | |
| 'description': 'Angry response to something repulsive' | |
| }, | |
| 'loving_trust': { | |
| 'conditions': [('love', 0.2), ('trust', 0.2)], | |
| 'label': 'Loving Trust', | |
| 'description': 'Deep affection with confidence' | |
| }, | |
| 'sarcastic_frustration': { | |
| 'conditions': [('sarcasm', 0.2), ('anger', 0.15)], | |
| 'label': 'Sarcastic Frustration', | |
| 'description': 'Using sarcasm to express frustration' | |
| }, | |
| 'confused_surprise': { | |
| 'conditions': [('surprise', 0.2), ('fear', 0.15)], | |
| 'label': 'Confused Surprise', | |
| 'description': 'Startled with uncertainty' | |
| }, | |
| 'hopeful_joy': { | |
| 'conditions': [('joy', 0.2), ('anticipation', 0.2)], | |
| 'label': 'Hopeful Joy', | |
| 'description': 'Happy anticipation of something positive' | |
| }, | |
| 'betrayed_trust': { | |
| 'conditions': [('sadness', 0.2), ('trust', 0.15), ('anger', 0.15)], | |
| 'label': 'Betrayed Trust', | |
| 'description': 'Sadness from broken trust' | |
| }, | |
| 'fearful_disgust': { | |
| 'conditions': [('fear', 0.2), ('disgust', 0.2)], | |
| 'label': 'Fearful Disgust', | |
| 'description': 'Fear of something repulsive' | |
| }, | |
| # Special cases for multiple emotions | |
| 'emotionally_complex': { | |
| 'conditions': ['multiple_over_15'], | |
| 'label': 'Emotionally Complex', | |
| 'description': 'Multiple competing emotions' | |
| }, | |
| 'mildly_emotional': { | |
| 'conditions': ['all_under_20'], | |
| 'label': 'Mildly Emotional', | |
| 'description': 'Low intensity emotional content' | |
| }, | |
| 'predominantly_neutral': { | |
| 'conditions': ['all_under_15'], | |
| 'label': 'Predominantly Neutral', | |
| 'description': 'No strong emotional signals detected' | |
| } | |
| } | |
| # Sarcasm patterns with refined detection logic | |
| SARCASM_PATTERNS = [ | |
| # Exaggerated positive with negative context | |
| r'(?i)\b(?:so+|really|absolutely|totally|completely)\s+(?:thrilled|excited|happy|delighted)\s+(?:about|with|by)\b.*?(?:terrible|awful|worst|bad)', | |
| # Classic sarcastic phrases | |
| r'(?i)(?:^|\W)just\s+what\s+(?:I|we)\s+(?:need|wanted|hoped for)\b', | |
| r'(?i)(?:^|\W)how\s+(?:wonderful|nice|great|lovely|exciting)\b.*?(?:\!|\?{2,})', | |
| # Thanks for nothing pattern | |
| r'(?i)(?:^|\W)thanks\s+for\s+(?:nothing|that|pointing|stating)\b', | |
| # Quotation marks around positive words (scare quotes) | |
| r'(?i)"(?:great|wonderful|excellent|perfect|amazing)"', | |
| # Typical sarcastic responses | |
| r'(?i)^(?:yeah|sure|right|oh)\s+(?:right|sure|okay|ok)(?:\W|$)', | |
| # Exaggerated praise in negative context | |
| r'(?i)\b(?:brilliant|genius|impressive)\b.*?(?:disaster|failure|mess)', | |
| # Obvious understatements | |
| r'(?i)\b(?:slightly|bit|little)\s+(?:catastrophic|disastrous|terrible|awful)\b', | |
| # Oh great patterns | |
| r'(?i)(?:^|\W)oh\s+(?:great|wonderful|perfect|fantastic|awesome)(?:\W|$)' | |
| ] | |
| def tokenize_and_clean(text): | |
| """Tokenize text using spaCy""" | |
| doc = nlp(text.lower().strip()) | |
| # Return only alphabetic tokens | |
| return [token.text for token in doc if token.is_alpha] | |
| def detect_phrases(text, emotion_phrases): | |
| """Detect emotion-specific phrases in text""" | |
| text_lower = text.lower() | |
| detected_phrases = {} | |
| for emotion, phrases in emotion_phrases.items(): | |
| found_phrases = [] | |
| for phrase in phrases: | |
| if phrase.lower() in text_lower: | |
| found_phrases.append(phrase) | |
| if found_phrases: | |
| detected_phrases[emotion] = found_phrases | |
| return detected_phrases | |
| def detect_contextual_features(text): | |
| """Detect contextual features in text that may influence emotion""" | |
| features = { | |
| 'intensifiers': 0, | |
| 'negators': 0, | |
| 'hedges': 0, | |
| 'boosters': 0, | |
| 'exclamations': text.count('!'), | |
| 'questions': text.count('?'), | |
| 'ellipses': text.count('...'), | |
| 'capitalized_words': len(re.findall(r'\b[A-Z]{2,}\b', text)) | |
| } | |
| doc = nlp(text.lower()) | |
| # Get tokens for counting | |
| tokens = [token.text for token in doc] | |
| # Count contextual indicators | |
| for indicator_type, words in CONTEXTUAL_INDICATORS.items(): | |
| if indicator_type != 'punctuation': | |
| for word in words: | |
| if ' ' in word: # Multi-word phrase | |
| if word in text.lower(): | |
| features[indicator_type] += 1 | |
| else: # Single word | |
| features[indicator_type] += tokens.count(word) | |
| return features | |
| def detect_sarcasm_patterns(text): | |
| """Detect linguistic patterns of sarcasm in text with context awareness""" | |
| # Match sarcasm patterns | |
| matches = 0 | |
| pattern_matches = [] | |
| for pattern in SARCASM_PATTERNS: | |
| if re.search(pattern, text): | |
| matches += 1 | |
| pattern_matches.append(pattern) | |
| # Get contextual features | |
| features = detect_contextual_features(text) | |
| # Check for phrases specific to sarcasm | |
| phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']}) | |
| sarcasm_phrases = len(phrases.get('sarcasm', [])) | |
| # Calculate raw score based on pattern matches and features | |
| raw_score = (matches * 0.15) + (sarcasm_phrases * 0.2) | |
| # Adjust based on contextual features | |
| if features['exclamations'] > 1: | |
| raw_score += min(features['exclamations'] * 0.05, 0.2) | |
| if features['capitalized_words'] > 0: | |
| raw_score += min(features['capitalized_words'] * 0.1, 0.3) | |
| # Detect positive-negative contrasts | |
| pos_neg_contrast = 0 | |
| emotion_phrases = detect_phrases(text, { | |
| 'positive': EMOTION_PHRASES['joy'] + EMOTION_PHRASES['love'], | |
| 'negative': EMOTION_PHRASES['sadness'] + EMOTION_PHRASES['anger'] | |
| }) | |
| if emotion_phrases.get('positive') and emotion_phrases.get('negative'): | |
| pos_neg_contrast = 0.3 | |
| # Add contrast score | |
| raw_score += pos_neg_contrast | |
| # Normalize to [0, 1] | |
| return min(raw_score, 1.0), pattern_matches | |
| def calculate_emotion_similarity(text, emotion_keywords): | |
| """Calculate similarity between text and emotion keywords using spaCy""" | |
| if not text.strip(): | |
| return 0.0 | |
| # Process the input text | |
| doc = nlp(text.lower()) | |
| # Get average similarity with emotion keywords | |
| keyword_scores = [] | |
| # Use a subset of keywords for efficiency | |
| for keyword in emotion_keywords[:6]: # Use first 6 keywords for each emotion | |
| keyword_doc = nlp(keyword) | |
| # Calculate maximum similarity between any token in text and the keyword | |
| max_similarity = 0 | |
| for token in doc: | |
| if token.is_alpha and not token.is_stop: | |
| for keyword_token in keyword_doc: | |
| similarity = token.similarity(keyword_token) | |
| max_similarity = max(max_similarity, similarity) | |
| keyword_scores.append(max_similarity) | |
| # Return average of top 3 similarities if we have at least 3 scores | |
| if len(keyword_scores) >= 3: | |
| return sum(sorted(keyword_scores, reverse=True)[:3]) / 3 | |
| # Otherwise return average of all scores | |
| elif keyword_scores: | |
| return sum(keyword_scores) / len(keyword_scores) | |
| else: | |
| return 0.0 | |
| def get_emotion_score(text, emotion, keywords): | |
| """Calculate emotion score based on similarity, context, and phrase detection""" | |
| # Get emotion score using spaCy word vectors | |
| similarity_score = calculate_emotion_similarity(text, keywords) | |
| # Check for emotion-specific phrases | |
| detected_phrases = detect_phrases(text, {emotion: EMOTION_PHRASES[emotion]}) | |
| phrase_count = len(detected_phrases.get(emotion, [])) | |
| phrase_score = min(phrase_count * 0.2, 0.6) # Cap at 0.6 | |
| # Get contextual features | |
| features = detect_contextual_features(text) | |
| # Calculate feature-based adjustment | |
| feature_adjustment = 0 | |
| # Search for direct emotion mentions in text | |
| doc = nlp(text.lower()) | |
| direct_mention_score = 0 | |
| for token in doc: | |
| if token.lemma_ in keywords: | |
| direct_mention_score += 0.2 # Direct mention of emotion word | |
| break | |
| # Adjust score based on emotional context | |
| if emotion in ['joy', 'love', 'surprise'] and features['exclamations'] > 0: | |
| feature_adjustment += min(features['exclamations'] * 0.05, 0.2) | |
| if emotion in ['anger', 'sadness'] and features['negators'] > 0: | |
| feature_adjustment += min(features['negators'] * 0.05, 0.2) | |
| if emotion == 'fear' and features['intensifiers'] > 0: | |
| feature_adjustment += min(features['intensifiers'] * 0.05, 0.2) | |
| # Combine scores with appropriate weights | |
| final_score = (similarity_score * 0.5) + (phrase_score * 0.3) + (feature_adjustment * 0.1) + (direct_mention_score * 0.1) | |
| # Normalize to ensure it's in [0, 1] | |
| return max(0, min(final_score, 1.0)), detected_phrases.get(emotion, []) | |
| def analyze_sarcasm(text): | |
| """Specialized analysis for sarcasm detection using spaCy and pattern matching""" | |
| # 1. Keyword similarity for sarcasm words | |
| sarcasm_keywords = EMOTION_CATEGORIES['sarcasm'] | |
| similarity_score = calculate_emotion_similarity(text, sarcasm_keywords) | |
| # 2. Linguistic pattern detection | |
| pattern_score, pattern_matches = detect_sarcasm_patterns(text) | |
| # 3. Check for semantic incongruity between sentences | |
| incongruity_score = 0 | |
| sentences = list(nlp(text).sents) | |
| if len(sentences) > 1: | |
| # Calculate similarity between adjacent sentences | |
| similarities = [] | |
| for i in range(len(sentences) - 1): | |
| sim = sentences[i].similarity(sentences[i+1]) | |
| similarities.append(sim) | |
| # Low similarity between adjacent sentences might indicate sarcasm | |
| if similarities and min(similarities) < 0.5: | |
| incongruity_score = 0.3 | |
| # 4. Check for sarcasm phrases | |
| detected_phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']}) | |
| phrase_score = min(len(detected_phrases.get('sarcasm', [])) * 0.2, 0.6) | |
| # 5. Check for emotional contrast | |
| # (positive words in negative context or vice versa) | |
| doc = nlp(text.lower()) | |
| # Count positive and negative words | |
| pos_count = 0 | |
| neg_count = 0 | |
| for token in doc: | |
| if token.is_alpha and not token.is_stop: | |
| # Check against positive and negative emotion keywords | |
| if any(token.similarity(nlp(word)) > 0.7 for word in EMOTION_CATEGORIES['joy'][:5]): | |
| pos_count += 1 | |
| if any(token.similarity(nlp(word)) > 0.7 for word in EMOTION_CATEGORIES['sadness'][:5] + EMOTION_CATEGORIES['anger'][:5]): | |
| neg_count += 1 | |
| contrast_score = 0 | |
| if pos_count > 0 and neg_count > 0: | |
| contrast_score = min(0.3, pos_count * neg_count * 0.05) | |
| # Weighted combination of all scores | |
| combined_score = (0.15 * similarity_score) + (0.35 * pattern_score) + \ | |
| (0.15 * incongruity_score) + (0.25 * phrase_score) + \ | |
| (0.1 * contrast_score) | |
| # Normalize to [0, 1] | |
| return max(0, min(combined_score, 1.0)), detected_phrases.get('sarcasm', []), pattern_matches | |
| def determine_emotional_verdict(emotion_scores): | |
| """Determine the emotional verdict based on the emotional profile""" | |
| # Create a sorted list of emotions by score | |
| sorted_emotions = sorted(emotion_scores.items(), key=lambda x: x[1], reverse=True) | |
| # Count emotions over different thresholds | |
| emotions_over_35 = [e for e, s in sorted_emotions if s > 0.35] | |
| emotions_over_20 = [e for e, s in sorted_emotions if s > 0.20] | |
| emotions_over_15 = [e for e, s in sorted_emotions if s > 0.15] | |
| # Check if we have a strong dominant emotion (over 35%) | |
| if emotions_over_35: | |
| dominant_emotion = emotions_over_35[0] | |
| for verdict_key, verdict_info in EMOTION_VERDICT_CATEGORIES.items(): | |
| conditions = verdict_info['conditions'] | |
| # Check single emotion conditions | |
| if len(conditions) == 1 and isinstance(conditions[0], tuple): | |
| emotion, threshold = conditions[0] | |
| if emotion == dominant_emotion and emotion_scores[emotion] >= threshold: | |
| return verdict_info['label'], verdict_info['description'] | |
| # Check for emotion combinations | |
| for verdict_key, verdict_info in EMOTION_VERDICT_CATEGORIES.items(): | |
| conditions = verdict_info['conditions'] | |
| # Skip single emotion conditions we've already checked | |
| if len(conditions) == 1 and isinstance(conditions[0], tuple): | |
| continue | |
| # Handle special condition types | |
| if conditions == ['multiple_over_15'] and len(emotions_over_15) >= 3: | |
| return verdict_info['label'], verdict_info['description'] | |
| if conditions == ['all_under_20'] and not emotions_over_20: | |
| return verdict_info['label'], verdict_info['description'] | |
| if conditions == ['all_under_15'] and not emotions_over_15: | |
| return verdict_info['label'], verdict_info['description'] | |
| # Check standard combination conditions | |
| if all(emotion_scores.get(emotion, 0) >= threshold for emotion, threshold in conditions): | |
| return verdict_info['label'], verdict_info['description'] | |
| # If we've found nothing specific but have some emotions over 15% | |
| if emotions_over_15: | |
| if len(emotions_over_15) == 1: | |
| # Use the single emotion even though it's not super strong | |
| emotion = emotions_over_15[0] | |
| return f"Moderately {emotion.capitalize()}", f"Shows some signs of {emotion}" | |
| else: | |
| # Create a custom mixed emotion label | |
| primary = emotions_over_15[0].capitalize() | |
| secondary = emotions_over_15[1].capitalize() | |
| return f"{primary} with {secondary}", f"A mix of {primary.lower()} and {secondary.lower()} emotions" | |
| # Default fallback | |
| return "Neutral or Subtle", "No clear emotional signals detected" | |
| def analyze_emotions(text): | |
| """Analyze emotions in text using spaCy with robust sarcasm detection and emotional verdict""" | |
| if not text or not text.strip(): | |
| return None, {"error": "Please enter some text to analyze"} | |
| try: | |
| # Calculate scores for each emotion with supporting phrases | |
| emotion_data = {} | |
| # For each standard emotion category (excluding sarcasm) | |
| for emotion, keywords in EMOTION_CATEGORIES.items(): | |
| if emotion == 'sarcasm': | |
| continue | |
| # Use specialized function to get emotion score and supporting phrases | |
| score, phrases = get_emotion_score(text, emotion, keywords) | |
| emotion_data[emotion] = { | |
| 'score': score, | |
| 'phrases': phrases | |
| } | |
| # Special handling for sarcasm with multi-method approach | |
| sarcasm_score, sarcasm_phrases, sarcasm_patterns = analyze_sarcasm(text) | |
| emotion_data['sarcasm'] = { | |
| 'score': sarcasm_score, | |
| 'phrases': sarcasm_phrases, | |
| 'patterns': sarcasm_patterns | |
| } | |
| # Get contextual features for overall analysis | |
| context_features = detect_contextual_features(text) | |
| # Apply decision making for final analysis | |
| # 1. Check for dominant emotions by raw scores | |
| emotion_scores = {emotion: data['score'] for emotion, data in emotion_data.items()} | |
| # 2. Adjust based on contextual evidence | |
| # If we have strong phrase evidence, boost the score slightly | |
| for emotion, data in emotion_data.items(): | |
| if len(data.get('phrases', [])) > 1: | |
| emotion_scores[emotion] = min(emotion_scores[emotion] * 1.2, 1.0) | |
| # 3. Get emotional verdict | |
| verdict_label, verdict_description = determine_emotional_verdict(emotion_scores) | |
| # 4. Normalize scores to percentages | |
| total_score = sum(emotion_scores.values()) or 1 # Avoid division by zero | |
| emotion_percentages = {emotion: (score / total_score) * 100 for emotion, score in emotion_scores.items()} | |
| # Sort emotions by percentage for display | |
| sorted_emotions = sorted(emotion_percentages.items(), key=lambda x: x[1], reverse=True) | |
| # Prepare result | |
| result = { | |
| 'emotion_scores': sorted_emotions, | |
| 'emotional_verdict': { | |
| 'label': verdict_label, | |
| 'description': verdict_description | |
| }, | |
| 'top_emotions': sorted_emotions[:3], | |
| 'supporting_evidence': { | |
| emotion: data.get('phrases', []) for emotion, data in emotion_data.items() if data.get('phrases') | |
| }, | |
| 'context_features': context_features | |
| } | |
| return create_visualization(result), result | |
| except Exception as e: | |
| import traceback | |
| error_msg = traceback.format_exc() | |
| return None, {"error": f"Analysis error: {str(e)}", "details": error_msg} | |
| def create_visualization(result): | |
| """Create visualization of emotion analysis results""" | |
| # Create figure and axis | |
| fig, ax = plt.subplots(figsize=(12, 8)) | |
| # Extract emotion data | |
| emotions = [e[0] for e in result['emotion_scores']] | |
| scores = [e[1] for e in result['emotion_scores']] | |
| # Get colors | |
| colors = [EMOTION_COLORS.get(emotion, '#CCCCCC') for emotion in emotions] | |
| # Create horizontal bar chart | |
| bars = ax.barh(emotions, scores, color=colors) | |
| # Set x-axis to a static 100% | |
| ax.set_xlim(0, 100) | |
| ax.set_xticks(range(0, 101, 10)) | |
| ax.set_xticklabels([f'{i}%' for i in range(0, 101, 10)]) | |
| # Add title and labels | |
| ax.set_title('Emotion Analysis', fontsize=16, fontweight='bold') | |
| ax.set_ylabel('Emotions', fontsize=12) | |
| ax.set_xlabel('Score (%)', fontsize=12) | |
| # Add verdict as text annotation below the chart | |
| verdict = result['emotional_verdict']['label'] | |
| description = result['emotional_verdict']['description'] | |
| ax.text(0.5, -0.2, f"Verdict: {verdict}", ha='center', transform=ax.transAxes, fontsize=14, fontweight='bold') | |
| ax.text(0.5, -0.27, f"{description}", ha='center', transform=ax.transAxes, fontsize=12) | |
| # Add value labels on top of bars | |
| for bar in bars: | |
| width = bar.get_width() | |
| ax.text(width + 0.5, bar.get_y() + bar.get_height() / 2, | |
| f'{width:.1f}%', ha='left', va='center', fontsize=10) | |
| # Adjust layout | |
| plt.tight_layout() | |
| plt.subplots_adjust(bottom=0.3) # Make room for the verdict text | |
| # Return figure | |
| return fig | |
| def analyze_text(text): | |
| """Analyze text and return visualization and detailed results""" | |
| fig, result = analyze_emotions(text) | |
| if 'error' in result: | |
| return None, result['error'] | |
| # Format the results for display | |
| verdict = result['emotional_verdict']['label'] | |
| description = result['emotional_verdict']['description'] | |
| # Create a formatted summary | |
| summary = f"## Emotional Analysis Results\n\n" | |
| summary += f"**Verdict:** {verdict}\n\n" | |
| summary += f"**Description:** {description}\n\n" | |
| summary += "### Top Emotions:\n" | |
| for emotion, score in result['top_emotions']: | |
| summary += f"- {emotion.capitalize()}: {score:.1f}%\n" | |
| if result['supporting_evidence']: | |
| summary += "\n### Supporting Evidence:\n" | |
| for emotion, phrases in result['supporting_evidence'].items(): | |
| if phrases: | |
| summary += f"- **{emotion.capitalize()}**: {', '.join(phrases)}\n" | |
| return fig, summary | |
| # Create Gradio interface | |
| def create_interface(): | |
| with gr.Blocks(title="Emotional Analysis Tool") as demo: | |
| gr.Markdown("# Advanced Emotion Analysis") | |
| gr.Markdown("Enter text to analyze the emotional content and receive a detailed breakdown.") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| text_input = gr.Textbox( | |
| label="Text to analyze", | |
| placeholder="Enter text here...", | |
| lines=10 | |
| ) | |
| analyze_button = gr.Button("Analyze Emotions") | |
| with gr.Column(scale=3): | |
| with gr.Tab("Visualization"): | |
| plot_output = gr.Plot(label="Emotion Distribution") | |
| with gr.Tab("Summary"): | |
| text_output = gr.Markdown(label="Analysis Summary") | |
| analyze_button.click( | |
| fn=analyze_text, | |
| inputs=text_input, | |
| outputs=[plot_output, text_output] | |
| ) | |
| gr.Markdown(""" | |
| ## About This Tool | |
| This advanced emotion analysis tool uses NLP techniques to detect and analyze emotions in text. | |
| It can identify: | |
| - 10 different emotions (joy, sadness, anger, fear, surprise, love, sarcasm, disgust, anticipation, trust) | |
| - Complex emotional combinations | |
| - Contextual features that affect emotional interpretation | |
| - Intelligent emotional verdicts for mixed emotion states | |
| The model uses word vectors, phrase detection, and contextual analysis for accurate emotion recognition. | |
| """) | |
| return demo | |
| # Launch the interface if running directly | |
| if __name__ == "__main__": | |
| print("Creating Gradio interface...") | |
| demo = create_interface() | |
| demo.launch(share=True) | |
| print("Gradio interface launched!") | |