Comp-I / src /utils /emotion_utils.py
axrzce's picture
Deploy from GitHub main
338d95d verified
raw
history blame
20.6 kB
"""
CompI Emotion Processing Utilities
This module provides utilities for Phase 2.C: Emotional/Contextual Input Integration
- Emotion detection and sentiment analysis
- Mood mapping and emotional context processing
- Color palette generation based on emotions
- Contextual prompt enhancement
- Emoji and text-based emotion recognition
"""
import re
import json
from typing import Dict, List, Optional, Tuple, Union, Any
from dataclasses import dataclass
from enum import Enum
import logging
# Optional imports with fallbacks
try:
from textblob import TextBlob
TEXTBLOB_AVAILABLE = True
except ImportError:
TEXTBLOB_AVAILABLE = False
TextBlob = None
try:
import emoji
EMOJI_AVAILABLE = True
except ImportError:
EMOJI_AVAILABLE = False
emoji = None
logger = logging.getLogger(__name__)
class EmotionCategory(Enum):
"""Primary emotion categories"""
JOY = "joy"
SADNESS = "sadness"
ANGER = "anger"
FEAR = "fear"
SURPRISE = "surprise"
DISGUST = "disgust"
LOVE = "love"
ANTICIPATION = "anticipation"
TRUST = "trust"
NEUTRAL = "neutral"
@dataclass
class EmotionAnalysis:
"""Container for emotion analysis results"""
# Primary emotion detection
primary_emotion: EmotionCategory
emotion_confidence: float # 0-1 confidence score
# Sentiment analysis
sentiment_polarity: float # -1 to 1 (negative to positive)
sentiment_subjectivity: float # 0 to 1 (objective to subjective)
# Detected emotions with scores
emotion_scores: Dict[str, float]
# Contextual information
detected_emojis: List[str]
emotion_keywords: List[str]
intensity_level: str # 'low', 'medium', 'high'
# Generated artistic attributes
color_palette: List[str]
artistic_descriptors: List[str]
mood_modifiers: List[str]
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization"""
return {
'primary_emotion': self.primary_emotion.value,
'emotion_confidence': self.emotion_confidence,
'sentiment_polarity': self.sentiment_polarity,
'sentiment_subjectivity': self.sentiment_subjectivity,
'emotion_scores': self.emotion_scores,
'detected_emojis': self.detected_emojis,
'emotion_keywords': self.emotion_keywords,
'intensity_level': self.intensity_level,
'color_palette': self.color_palette,
'artistic_descriptors': self.artistic_descriptors,
'mood_modifiers': self.mood_modifiers
}
class EmotionProcessor:
"""Core emotion processing and analysis functionality"""
def __init__(self):
"""Initialize the emotion processor with predefined mappings"""
# Predefined emotion sets
self.preset_emotions = {
"joyful": {"category": EmotionCategory.JOY, "intensity": "high", "emoji": "😊"},
"happy": {"category": EmotionCategory.JOY, "intensity": "medium", "emoji": "😄"},
"ecstatic": {"category": EmotionCategory.JOY, "intensity": "high", "emoji": "🤩"},
"sad": {"category": EmotionCategory.SADNESS, "intensity": "medium", "emoji": "😢"},
"melancholic": {"category": EmotionCategory.SADNESS, "intensity": "high", "emoji": "😔"},
"depressed": {"category": EmotionCategory.SADNESS, "intensity": "high", "emoji": "😞"},
"angry": {"category": EmotionCategory.ANGER, "intensity": "high", "emoji": "😡"},
"frustrated": {"category": EmotionCategory.ANGER, "intensity": "medium", "emoji": "😤"},
"furious": {"category": EmotionCategory.ANGER, "intensity": "high", "emoji": "🤬"},
"fearful": {"category": EmotionCategory.FEAR, "intensity": "high", "emoji": "😱"},
"anxious": {"category": EmotionCategory.FEAR, "intensity": "medium", "emoji": "😰"},
"nervous": {"category": EmotionCategory.FEAR, "intensity": "low", "emoji": "😬"},
"surprised": {"category": EmotionCategory.SURPRISE, "intensity": "medium", "emoji": "😲"},
"amazed": {"category": EmotionCategory.SURPRISE, "intensity": "high", "emoji": "🤯"},
"romantic": {"category": EmotionCategory.LOVE, "intensity": "high", "emoji": "💖"},
"loving": {"category": EmotionCategory.LOVE, "intensity": "medium", "emoji": "❤️"},
"peaceful": {"category": EmotionCategory.TRUST, "intensity": "medium", "emoji": "🕊️"},
"serene": {"category": EmotionCategory.TRUST, "intensity": "high", "emoji": "🌱"},
"mysterious": {"category": EmotionCategory.ANTICIPATION, "intensity": "medium", "emoji": "🕵️‍♂️"},
"nostalgic": {"category": EmotionCategory.SADNESS, "intensity": "medium", "emoji": "🕰️"},
"energetic": {"category": EmotionCategory.JOY, "intensity": "high", "emoji": "⚡"},
"whimsical": {"category": EmotionCategory.JOY, "intensity": "medium", "emoji": "🎠"},
"uplifting": {"category": EmotionCategory.JOY, "intensity": "high", "emoji": "🌞"},
"dark": {"category": EmotionCategory.SADNESS, "intensity": "high", "emoji": "🌑"},
"moody": {"category": EmotionCategory.SADNESS, "intensity": "medium", "emoji": "🌫️"}
}
# Emotion-to-color mappings
self.emotion_colors = {
EmotionCategory.JOY: ["#FFD700", "#FFA500", "#FF69B4", "#00CED1", "#32CD32"],
EmotionCategory.SADNESS: ["#4169E1", "#6495ED", "#708090", "#2F4F4F", "#191970"],
EmotionCategory.ANGER: ["#DC143C", "#B22222", "#8B0000", "#FF4500", "#FF6347"],
EmotionCategory.FEAR: ["#800080", "#4B0082", "#2E2E2E", "#696969", "#A9A9A9"],
EmotionCategory.SURPRISE: ["#FF1493", "#FF69B4", "#FFB6C1", "#FFC0CB", "#FFFF00"],
EmotionCategory.LOVE: ["#FF69B4", "#DC143C", "#FF1493", "#C71585", "#DB7093"],
EmotionCategory.TRUST: ["#00CED1", "#20B2AA", "#48D1CC", "#40E0D0", "#AFEEEE"],
EmotionCategory.ANTICIPATION: ["#9370DB", "#8A2BE2", "#7B68EE", "#6A5ACD", "#483D8B"],
EmotionCategory.NEUTRAL: ["#808080", "#A9A9A9", "#C0C0C0", "#D3D3D3", "#DCDCDC"]
}
# Artistic descriptors for each emotion
self.artistic_descriptors = {
EmotionCategory.JOY: ["vibrant", "luminous", "radiant", "effervescent", "sparkling"],
EmotionCategory.SADNESS: ["muted", "somber", "melancholic", "wistful", "contemplative"],
EmotionCategory.ANGER: ["intense", "fiery", "bold", "dramatic", "powerful"],
EmotionCategory.FEAR: ["shadowy", "mysterious", "ethereal", "haunting", "enigmatic"],
EmotionCategory.SURPRISE: ["dynamic", "explosive", "unexpected", "striking", "vivid"],
EmotionCategory.LOVE: ["warm", "tender", "passionate", "romantic", "intimate"],
EmotionCategory.TRUST: ["serene", "peaceful", "harmonious", "balanced", "tranquil"],
EmotionCategory.ANTICIPATION: ["electric", "suspenseful", "charged", "expectant", "tense"],
EmotionCategory.NEUTRAL: ["balanced", "calm", "steady", "composed", "neutral"]
}
# Emoji to emotion mapping
self.emoji_emotions = {
"😊": EmotionCategory.JOY, "😄": EmotionCategory.JOY, "😃": EmotionCategory.JOY,
"🤩": EmotionCategory.JOY, "😍": EmotionCategory.LOVE, "🥰": EmotionCategory.LOVE,
"😢": EmotionCategory.SADNESS, "😭": EmotionCategory.SADNESS, "😔": EmotionCategory.SADNESS,
"😡": EmotionCategory.ANGER, "🤬": EmotionCategory.ANGER, "😤": EmotionCategory.ANGER,
"😱": EmotionCategory.FEAR, "😰": EmotionCategory.FEAR, "😨": EmotionCategory.FEAR,
"😲": EmotionCategory.SURPRISE, "😮": EmotionCategory.SURPRISE, "🤯": EmotionCategory.SURPRISE,
"❤️": EmotionCategory.LOVE, "💖": EmotionCategory.LOVE, "💕": EmotionCategory.LOVE,
"🕊️": EmotionCategory.TRUST, "🌱": EmotionCategory.TRUST, "☮️": EmotionCategory.TRUST
}
# Keyword patterns for emotion detection
self.emotion_keywords = {
EmotionCategory.JOY: ["happy", "joyful", "cheerful", "delighted", "elated", "euphoric", "blissful"],
EmotionCategory.SADNESS: ["sad", "depressed", "melancholy", "sorrowful", "gloomy", "dejected"],
EmotionCategory.ANGER: ["angry", "furious", "rage", "irritated", "annoyed", "livid", "irate"],
EmotionCategory.FEAR: ["afraid", "scared", "terrified", "anxious", "worried", "nervous", "fearful"],
EmotionCategory.SURPRISE: ["surprised", "amazed", "astonished", "shocked", "stunned", "bewildered"],
EmotionCategory.LOVE: ["love", "romantic", "affectionate", "tender", "passionate", "adoring"],
EmotionCategory.TRUST: ["peaceful", "serene", "calm", "tranquil", "secure", "confident"],
EmotionCategory.ANTICIPATION: ["excited", "eager", "hopeful", "expectant", "anticipating"]
}
def analyze_emotion(self, text: str, selected_emotion: Optional[str] = None) -> EmotionAnalysis:
"""
Comprehensive emotion analysis of input text
Args:
text: Input text to analyze
selected_emotion: Optional pre-selected emotion
Returns:
EmotionAnalysis object with complete analysis
"""
logger.info(f"Analyzing emotion for text: {text[:100]}...")
# Initialize analysis components
detected_emojis = self._extract_emojis(text)
emotion_keywords = self._extract_emotion_keywords(text)
# Determine primary emotion
if selected_emotion and selected_emotion.lower() in self.preset_emotions:
# Use selected emotion
emotion_info = self.preset_emotions[selected_emotion.lower()]
primary_emotion = emotion_info["category"]
emotion_confidence = 0.9
intensity_level = emotion_info["intensity"]
else:
# Analyze text for emotion
primary_emotion, emotion_confidence, intensity_level = self._analyze_text_emotion(text, detected_emojis, emotion_keywords)
# Sentiment analysis
sentiment_polarity, sentiment_subjectivity = self._analyze_sentiment(text)
# Generate emotion scores
emotion_scores = self._generate_emotion_scores(primary_emotion, emotion_confidence)
# Generate artistic attributes
color_palette = self.emotion_colors.get(primary_emotion, self.emotion_colors[EmotionCategory.NEUTRAL])
artistic_descriptors = self.artistic_descriptors.get(primary_emotion, ["neutral"])
mood_modifiers = self._generate_mood_modifiers(primary_emotion, intensity_level)
return EmotionAnalysis(
primary_emotion=primary_emotion,
emotion_confidence=emotion_confidence,
sentiment_polarity=sentiment_polarity,
sentiment_subjectivity=sentiment_subjectivity,
emotion_scores=emotion_scores,
detected_emojis=detected_emojis,
emotion_keywords=emotion_keywords,
intensity_level=intensity_level,
color_palette=color_palette[:3], # Top 3 colors
artistic_descriptors=artistic_descriptors[:3], # Top 3 descriptors
mood_modifiers=mood_modifiers
)
def _extract_emojis(self, text: str) -> List[str]:
"""Extract emojis from text"""
if not EMOJI_AVAILABLE:
# Simple emoji detection using Unicode ranges
emoji_pattern = re.compile(
"["
"\U0001F600-\U0001F64F" # emoticons
"\U0001F300-\U0001F5FF" # symbols & pictographs
"\U0001F680-\U0001F6FF" # transport & map symbols
"\U0001F1E0-\U0001F1FF" # flags (iOS)
"\U00002702-\U000027B0"
"\U000024C2-\U0001F251"
"]+",
flags=re.UNICODE
)
return emoji_pattern.findall(text)
else:
return [char for char in text if char in emoji.UNICODE_EMOJI['en']]
def _extract_emotion_keywords(self, text: str) -> List[str]:
"""Extract emotion-related keywords from text"""
text_lower = text.lower()
found_keywords = []
for emotion, keywords in self.emotion_keywords.items():
for keyword in keywords:
if keyword in text_lower:
found_keywords.append(keyword)
return found_keywords
def _analyze_text_emotion(self, text: str, emojis: List[str], keywords: List[str]) -> Tuple[EmotionCategory, float, str]:
"""Analyze emotion from text, emojis, and keywords"""
# Check emojis first
for emoji_char in emojis:
if emoji_char in self.emoji_emotions:
return self.emoji_emotions[emoji_char], 0.8, "medium"
# Check keywords
emotion_votes = {}
for keyword in keywords:
for emotion, emotion_keywords in self.emotion_keywords.items():
if keyword in emotion_keywords:
emotion_votes[emotion] = emotion_votes.get(emotion, 0) + 1
if emotion_votes:
primary_emotion = max(emotion_votes, key=emotion_votes.get)
confidence = min(emotion_votes[primary_emotion] * 0.3, 0.9)
intensity = "high" if emotion_votes[primary_emotion] > 2 else "medium"
return primary_emotion, confidence, intensity
# Fallback to sentiment analysis
sentiment_polarity, _ = self._analyze_sentiment(text)
if sentiment_polarity > 0.3:
return EmotionCategory.JOY, 0.6, "medium"
elif sentiment_polarity < -0.3:
return EmotionCategory.SADNESS, 0.6, "medium"
else:
return EmotionCategory.NEUTRAL, 0.5, "low"
def _analyze_sentiment(self, text: str) -> Tuple[float, float]:
"""Analyze sentiment using TextBlob or fallback method"""
if not text.strip():
return 0.0, 0.0
if TEXTBLOB_AVAILABLE:
try:
blob = TextBlob(text)
return blob.sentiment.polarity, blob.sentiment.subjectivity
except Exception as e:
logger.warning(f"TextBlob sentiment analysis failed: {e}")
# Simple fallback sentiment analysis
positive_words = ["good", "great", "excellent", "amazing", "wonderful", "fantastic", "love", "like", "happy", "joy"]
negative_words = ["bad", "terrible", "awful", "hate", "dislike", "sad", "angry", "fear", "worried", "depressed"]
text_lower = text.lower()
positive_count = sum(1 for word in positive_words if word in text_lower)
negative_count = sum(1 for word in negative_words if word in text_lower)
total_words = len(text.split())
if total_words == 0:
return 0.0, 0.0
polarity = (positive_count - negative_count) / max(total_words, 1)
subjectivity = (positive_count + negative_count) / max(total_words, 1)
return max(-1.0, min(1.0, polarity)), max(0.0, min(1.0, subjectivity))
def _generate_emotion_scores(self, primary_emotion: EmotionCategory, confidence: float) -> Dict[str, float]:
"""Generate scores for all emotions"""
scores = {emotion.value: 0.1 for emotion in EmotionCategory}
scores[primary_emotion.value] = confidence
# Add some secondary emotions based on primary
secondary_emotions = {
EmotionCategory.JOY: [EmotionCategory.LOVE, EmotionCategory.TRUST],
EmotionCategory.SADNESS: [EmotionCategory.FEAR, EmotionCategory.NEUTRAL],
EmotionCategory.ANGER: [EmotionCategory.DISGUST, EmotionCategory.FEAR],
EmotionCategory.FEAR: [EmotionCategory.SADNESS, EmotionCategory.SURPRISE],
EmotionCategory.LOVE: [EmotionCategory.JOY, EmotionCategory.TRUST],
EmotionCategory.TRUST: [EmotionCategory.JOY, EmotionCategory.LOVE]
}
if primary_emotion in secondary_emotions:
for secondary in secondary_emotions[primary_emotion]:
scores[secondary.value] = min(0.4, confidence * 0.5)
return scores
def _generate_mood_modifiers(self, emotion: EmotionCategory, intensity: str) -> List[str]:
"""Generate mood modifiers for prompt enhancement"""
base_modifiers = {
EmotionCategory.JOY: ["bright", "cheerful", "uplifting", "radiant"],
EmotionCategory.SADNESS: ["melancholic", "somber", "wistful", "contemplative"],
EmotionCategory.ANGER: ["intense", "dramatic", "powerful", "bold"],
EmotionCategory.FEAR: ["mysterious", "dark", "ethereal", "haunting"],
EmotionCategory.SURPRISE: ["dynamic", "striking", "unexpected", "vivid"],
EmotionCategory.LOVE: ["romantic", "warm", "tender", "passionate"],
EmotionCategory.TRUST: ["peaceful", "serene", "harmonious", "tranquil"],
EmotionCategory.ANTICIPATION: ["electric", "suspenseful", "charged", "expectant"],
EmotionCategory.NEUTRAL: ["balanced", "calm", "neutral", "composed"]
}
modifiers = base_modifiers.get(emotion, ["neutral"])
# Adjust based on intensity
if intensity == "high":
intensity_modifiers = ["very", "extremely", "deeply", "intensely"]
return [f"{intensity_modifiers[0]} {mod}" for mod in modifiers[:2]]
elif intensity == "low":
return [f"subtly {mod}" for mod in modifiers[:2]]
else:
return modifiers[:3]
class EmotionalPromptEnhancer:
"""Enhance prompts with emotional context"""
def __init__(self):
"""Initialize the prompt enhancer"""
self.emotion_processor = EmotionProcessor()
def enhance_prompt_with_emotion(
self,
base_prompt: str,
style: str,
emotion_analysis: EmotionAnalysis,
enhancement_strength: float = 0.7
) -> str:
"""
Enhance prompt with emotional context
Args:
base_prompt: Original text prompt
style: Art style
emotion_analysis: Emotion analysis results
enhancement_strength: How strongly to apply emotion (0-1)
Returns:
Enhanced prompt with emotional context
"""
enhanced_prompt = base_prompt.strip()
# Add style
if style:
enhanced_prompt += f", {style}"
# Add emotional descriptors based on strength
if enhancement_strength > 0.5:
# Strong emotional enhancement
descriptors = emotion_analysis.artistic_descriptors[:2]
mood_modifiers = emotion_analysis.mood_modifiers[:2]
enhanced_prompt += f", {', '.join(descriptors)}"
enhanced_prompt += f", with a {', '.join(mood_modifiers)} atmosphere"
# Add intensity if high
if emotion_analysis.intensity_level == "high":
enhanced_prompt += f", deeply {emotion_analysis.primary_emotion.value}"
elif enhancement_strength > 0.2:
# Moderate emotional enhancement
descriptor = emotion_analysis.artistic_descriptors[0]
mood = emotion_analysis.mood_modifiers[0]
enhanced_prompt += f", {descriptor}, {mood}"
else:
# Subtle emotional enhancement
if emotion_analysis.artistic_descriptors:
enhanced_prompt += f", {emotion_analysis.artistic_descriptors[0]}"
return enhanced_prompt
def generate_emotion_tags(self, emotion_analysis: EmotionAnalysis) -> List[str]:
"""Generate descriptive tags for the emotion"""
tags = []
# Primary emotion
tags.append(emotion_analysis.primary_emotion.value)
# Intensity
tags.append(f"{emotion_analysis.intensity_level}_intensity")
# Sentiment
if emotion_analysis.sentiment_polarity > 0.3:
tags.append("positive_sentiment")
elif emotion_analysis.sentiment_polarity < -0.3:
tags.append("negative_sentiment")
else:
tags.append("neutral_sentiment")
# Artistic descriptors
tags.extend(emotion_analysis.artistic_descriptors[:2])
return tags