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!") | |