Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| """ | |
| Emotion Wheel - Graph-based emotion categorization and similarity | |
| Implements an emotion wheel/graph where: | |
| - Emotions are grouped into categories | |
| - Similar emotions are acceptable substitutes | |
| - Contradictory emotions are failures | |
| - Allows natural variation in emotion detection | |
| Based on Plutchik's Wheel of Emotions with extensions | |
| """ | |
| from typing import Dict, List, Set, Tuple, Optional | |
| from dataclasses import dataclass | |
| from enum import Enum | |
| class EmotionCategory(Enum): | |
| """Main emotion categories on the wheel""" | |
| JOY = "joy" | |
| TRUST = "trust" | |
| FEAR = "fear" | |
| SURPRISE = "surprise" | |
| SADNESS = "sadness" | |
| DISGUST = "disgust" | |
| ANGER = "anger" | |
| ANTICIPATION = "anticipation" | |
| # Extended categories | |
| LOVE = "love" # Joy + Trust | |
| GUILT = "guilt" # Joy opposite area | |
| NEUTRAL = "neutral" # Center | |
| CONFUSION = "confusion" # Between fear and surprise | |
| class EmotionNode: | |
| """Single emotion in the wheel""" | |
| name: str | |
| category: EmotionCategory | |
| intensity: str # "high", "medium", "low" | |
| emoji: str | |
| adjacent_categories: List[EmotionCategory] # Similar categories | |
| opposite_category: Optional[EmotionCategory] # Contradictory | |
| class EmotionWheel: | |
| """ | |
| Emotion Wheel Graph for categorizing and relating emotions | |
| Structure (Plutchik-inspired): | |
| JOY | |
| / \ | |
| ANTICIPATION TRUST | |
| | | | |
| ANGER FEAR | |
| \ / | |
| DISGUST-SADNESS | |
| | | |
| SURPRISE | |
| Center: NEUTRAL | |
| Combinations: LOVE (Joy+Trust), CONFUSION (Fear+Surprise) | |
| """ | |
| # Category relationships - adjacent categories are similar | |
| CATEGORY_WHEEL = { | |
| EmotionCategory.JOY: { | |
| "adjacent": [EmotionCategory.TRUST, EmotionCategory.ANTICIPATION, EmotionCategory.LOVE], | |
| "opposite": EmotionCategory.SADNESS, | |
| }, | |
| EmotionCategory.TRUST: { | |
| "adjacent": [EmotionCategory.JOY, EmotionCategory.FEAR, EmotionCategory.LOVE], | |
| "opposite": EmotionCategory.DISGUST, | |
| }, | |
| EmotionCategory.FEAR: { | |
| "adjacent": [EmotionCategory.TRUST, EmotionCategory.SURPRISE, EmotionCategory.CONFUSION], | |
| "opposite": EmotionCategory.ANGER, | |
| }, | |
| EmotionCategory.SURPRISE: { | |
| "adjacent": [EmotionCategory.FEAR, EmotionCategory.SADNESS, EmotionCategory.CONFUSION], | |
| "opposite": EmotionCategory.ANTICIPATION, | |
| }, | |
| EmotionCategory.SADNESS: { | |
| "adjacent": [EmotionCategory.SURPRISE, EmotionCategory.DISGUST, EmotionCategory.GUILT], | |
| "opposite": EmotionCategory.JOY, | |
| }, | |
| EmotionCategory.DISGUST: { | |
| "adjacent": [EmotionCategory.SADNESS, EmotionCategory.ANGER], | |
| "opposite": EmotionCategory.TRUST, | |
| }, | |
| EmotionCategory.ANGER: { | |
| "adjacent": [EmotionCategory.DISGUST, EmotionCategory.ANTICIPATION], | |
| "opposite": EmotionCategory.FEAR, | |
| }, | |
| EmotionCategory.ANTICIPATION: { | |
| "adjacent": [EmotionCategory.ANGER, EmotionCategory.JOY], | |
| "opposite": EmotionCategory.SURPRISE, | |
| }, | |
| # Extended categories | |
| EmotionCategory.LOVE: { | |
| "adjacent": [EmotionCategory.JOY, EmotionCategory.TRUST], | |
| "opposite": EmotionCategory.DISGUST, | |
| }, | |
| EmotionCategory.GUILT: { | |
| "adjacent": [EmotionCategory.SADNESS, EmotionCategory.FEAR], | |
| "opposite": EmotionCategory.JOY, | |
| }, | |
| EmotionCategory.NEUTRAL: { | |
| "adjacent": [cat for cat in EmotionCategory], # Center connects to all | |
| "opposite": None, | |
| }, | |
| EmotionCategory.CONFUSION: { | |
| "adjacent": [EmotionCategory.FEAR, EmotionCategory.SURPRISE, EmotionCategory.NEUTRAL], | |
| "opposite": EmotionCategory.JOY, | |
| }, | |
| } | |
| # Map each emotion to its category | |
| EMOTION_CATEGORIES: Dict[str, EmotionCategory] = { | |
| # === JOY FAMILY === | |
| "ecstasy": EmotionCategory.JOY, | |
| "joy": EmotionCategory.JOY, | |
| "happiness": EmotionCategory.JOY, | |
| "delight": EmotionCategory.JOY, | |
| "elation": EmotionCategory.JOY, | |
| "euphoria": EmotionCategory.JOY, | |
| "excitement": EmotionCategory.JOY, | |
| "thrill": EmotionCategory.JOY, | |
| "enthusiasm": EmotionCategory.JOY, | |
| "cheerfulness": EmotionCategory.JOY, | |
| "contentment": EmotionCategory.JOY, | |
| "satisfaction": EmotionCategory.JOY, | |
| "pleasure": EmotionCategory.JOY, | |
| "relief": EmotionCategory.JOY, | |
| "serenity": EmotionCategory.JOY, | |
| "calm": EmotionCategory.JOY, | |
| "relaxed": EmotionCategory.JOY, | |
| "amusement": EmotionCategory.JOY, | |
| "playful": EmotionCategory.JOY, | |
| "silly": EmotionCategory.JOY, | |
| "mischievous": EmotionCategory.JOY, | |
| "funny": EmotionCategory.JOY, | |
| "hope": EmotionCategory.JOY, | |
| "optimism": EmotionCategory.JOY, | |
| # === TRUST FAMILY === | |
| "trust": EmotionCategory.TRUST, | |
| "acceptance": EmotionCategory.TRUST, | |
| "admiration": EmotionCategory.TRUST, | |
| "confidence": EmotionCategory.TRUST, | |
| # === LOVE FAMILY (Joy + Trust) === | |
| "love": EmotionCategory.LOVE, | |
| "adoration": EmotionCategory.LOVE, | |
| "affection": EmotionCategory.LOVE, | |
| "tenderness": EmotionCategory.LOVE, | |
| "caring": EmotionCategory.LOVE, | |
| "compassion": EmotionCategory.LOVE, | |
| "empathy": EmotionCategory.LOVE, | |
| "gratitude": EmotionCategory.LOVE, | |
| "thankful": EmotionCategory.LOVE, | |
| "sympathy": EmotionCategory.LOVE, | |
| # === FEAR FAMILY === | |
| "fear": EmotionCategory.FEAR, | |
| "terror": EmotionCategory.FEAR, | |
| "horror": EmotionCategory.FEAR, | |
| "dread": EmotionCategory.FEAR, | |
| "anxiety": EmotionCategory.FEAR, | |
| "worry": EmotionCategory.FEAR, | |
| "nervousness": EmotionCategory.FEAR, | |
| "apprehension": EmotionCategory.FEAR, | |
| "panic": EmotionCategory.FEAR, | |
| # === SURPRISE FAMILY === | |
| "surprise": EmotionCategory.SURPRISE, | |
| "astonishment": EmotionCategory.SURPRISE, | |
| "shock": EmotionCategory.SURPRISE, | |
| "startled": EmotionCategory.SURPRISE, | |
| "amazement": EmotionCategory.SURPRISE, | |
| "wonder": EmotionCategory.SURPRISE, | |
| "awe": EmotionCategory.SURPRISE, | |
| "curiosity": EmotionCategory.SURPRISE, | |
| "interest": EmotionCategory.SURPRISE, | |
| "fascination": EmotionCategory.SURPRISE, | |
| "intrigue": EmotionCategory.SURPRISE, | |
| # === SADNESS FAMILY === | |
| "sadness": EmotionCategory.SADNESS, | |
| "sorrow": EmotionCategory.SADNESS, | |
| "grief": EmotionCategory.SADNESS, | |
| "melancholy": EmotionCategory.SADNESS, | |
| "disappointment": EmotionCategory.SADNESS, | |
| "dejection": EmotionCategory.SADNESS, | |
| "despair": EmotionCategory.SADNESS, | |
| "hopelessness": EmotionCategory.SADNESS, | |
| "loneliness": EmotionCategory.SADNESS, | |
| "hurt": EmotionCategory.SADNESS, | |
| "misery": EmotionCategory.SADNESS, | |
| "nostalgia": EmotionCategory.SADNESS, | |
| "longing": EmotionCategory.SADNESS, | |
| "yearning": EmotionCategory.SADNESS, | |
| "pessimism": EmotionCategory.SADNESS, | |
| # === DISGUST FAMILY === | |
| "disgust": EmotionCategory.DISGUST, | |
| "revulsion": EmotionCategory.DISGUST, | |
| "contempt": EmotionCategory.DISGUST, | |
| "disdain": EmotionCategory.DISGUST, | |
| "loathing": EmotionCategory.DISGUST, | |
| "scorn": EmotionCategory.DISGUST, | |
| "sarcasm": EmotionCategory.DISGUST, | |
| # === ANGER FAMILY === | |
| "anger": EmotionCategory.ANGER, | |
| "rage": EmotionCategory.ANGER, | |
| "fury": EmotionCategory.ANGER, | |
| "irritation": EmotionCategory.ANGER, | |
| "annoyance": EmotionCategory.ANGER, | |
| "frustration": EmotionCategory.ANGER, | |
| "exasperation": EmotionCategory.ANGER, | |
| "resentment": EmotionCategory.ANGER, | |
| "hostility": EmotionCategory.ANGER, | |
| "bitterness": EmotionCategory.ANGER, | |
| "envy": EmotionCategory.ANGER, | |
| "jealousy": EmotionCategory.ANGER, | |
| # === ANTICIPATION FAMILY === | |
| "anticipation": EmotionCategory.ANTICIPATION, | |
| "determination": EmotionCategory.ANTICIPATION, | |
| "inspiration": EmotionCategory.ANTICIPATION, | |
| "pride": EmotionCategory.ANTICIPATION, | |
| "triumph": EmotionCategory.ANTICIPATION, | |
| # === GUILT FAMILY (Sadness + Fear area) === | |
| "shame": EmotionCategory.GUILT, | |
| "embarrassment": EmotionCategory.GUILT, | |
| "guilt": EmotionCategory.GUILT, | |
| "regret": EmotionCategory.GUILT, | |
| "remorse": EmotionCategory.GUILT, | |
| "humiliation": EmotionCategory.GUILT, | |
| # === CONFUSION FAMILY (Fear + Surprise) === | |
| "confused": EmotionCategory.CONFUSION, | |
| "confusion": EmotionCategory.CONFUSION, | |
| "puzzled": EmotionCategory.CONFUSION, | |
| "perplexed": EmotionCategory.CONFUSION, | |
| "bewildered": EmotionCategory.CONFUSION, | |
| "baffled": EmotionCategory.CONFUSION, | |
| "uncertain": EmotionCategory.CONFUSION, | |
| # === NEUTRAL FAMILY (Center) === | |
| "neutral": EmotionCategory.NEUTRAL, | |
| "thinking": EmotionCategory.NEUTRAL, | |
| "contemplative": EmotionCategory.NEUTRAL, | |
| "pensive": EmotionCategory.NEUTRAL, | |
| "reflective": EmotionCategory.NEUTRAL, | |
| "ambivalent": EmotionCategory.NEUTRAL, | |
| "indifferent": EmotionCategory.NEUTRAL, | |
| "boredom": EmotionCategory.NEUTRAL, | |
| "tiredness": EmotionCategory.NEUTRAL, | |
| "exhaustion": EmotionCategory.NEUTRAL, | |
| "fatigue": EmotionCategory.NEUTRAL, | |
| "weariness": EmotionCategory.NEUTRAL, | |
| "sleepy": EmotionCategory.NEUTRAL, | |
| } | |
| def __init__(self): | |
| """Initialize emotion wheel""" | |
| self._build_similarity_graph() | |
| def _build_similarity_graph(self): | |
| """Build graph of emotion similarities""" | |
| self.similarity_graph: Dict[str, Set[str]] = {} | |
| # For each emotion, find all similar emotions | |
| for emotion, category in self.EMOTION_CATEGORIES.items(): | |
| similar = set() | |
| # Same category emotions are very similar | |
| for other_emotion, other_cat in self.EMOTION_CATEGORIES.items(): | |
| if other_cat == category: | |
| similar.add(other_emotion) | |
| # Adjacent category emotions are somewhat similar | |
| if category in self.CATEGORY_WHEEL: | |
| adjacent_cats = self.CATEGORY_WHEEL[category]["adjacent"] | |
| for other_emotion, other_cat in self.EMOTION_CATEGORIES.items(): | |
| if other_cat in adjacent_cats: | |
| similar.add(other_emotion) | |
| self.similarity_graph[emotion] = similar | |
| def get_category(self, emotion: str) -> Optional[EmotionCategory]: | |
| """Get the category of an emotion""" | |
| return self.EMOTION_CATEGORIES.get(emotion.lower()) | |
| def get_opposite_category(self, emotion: str) -> Optional[EmotionCategory]: | |
| """Get the opposite/contradictory category""" | |
| category = self.get_category(emotion) | |
| if category and category in self.CATEGORY_WHEEL: | |
| return self.CATEGORY_WHEEL[category].get("opposite") | |
| return None | |
| def are_similar(self, emotion1: str, emotion2: str) -> bool: | |
| """Check if two emotions are similar (same or adjacent category)""" | |
| e1 = emotion1.lower() | |
| e2 = emotion2.lower() | |
| if e1 == e2: | |
| return True | |
| if e1 in self.similarity_graph: | |
| return e2 in self.similarity_graph[e1] | |
| return False | |
| def are_contradictory(self, emotion1: str, emotion2: str) -> bool: | |
| """Check if two emotions are contradictory (opposite categories)""" | |
| cat1 = self.get_category(emotion1) | |
| cat2 = self.get_category(emotion2) | |
| if not cat1 or not cat2: | |
| return False | |
| # Check if cat2 is opposite of cat1 | |
| if cat1 in self.CATEGORY_WHEEL: | |
| opposite = self.CATEGORY_WHEEL[cat1].get("opposite") | |
| if opposite == cat2: | |
| return True | |
| # Check reverse | |
| if cat2 in self.CATEGORY_WHEEL: | |
| opposite = self.CATEGORY_WHEEL[cat2].get("opposite") | |
| if opposite == cat1: | |
| return True | |
| return False | |
| def get_similarity_score(self, expected: str, detected: str) -> Tuple[float, str]: | |
| """ | |
| Calculate similarity score between expected and detected emotion | |
| Returns: | |
| Tuple of (score, relationship_type) | |
| - score: 1.0 = exact, 0.8 = same category, 0.5 = adjacent, 0.0 = opposite | |
| - relationship_type: "exact", "same_category", "adjacent", "distant", "opposite" | |
| """ | |
| e1 = expected.lower() | |
| e2 = detected.lower() | |
| # Exact match | |
| if e1 == e2: | |
| return (1.0, "exact") | |
| cat1 = self.get_category(e1) | |
| cat2 = self.get_category(e2) | |
| if not cat1 or not cat2: | |
| return (0.3, "unknown") | |
| # Same category | |
| if cat1 == cat2: | |
| return (0.8, "same_category") | |
| # Check if adjacent | |
| if cat1 in self.CATEGORY_WHEEL: | |
| adjacent = self.CATEGORY_WHEEL[cat1]["adjacent"] | |
| if cat2 in adjacent: | |
| return (0.5, "adjacent") | |
| # Check if opposite | |
| if self.are_contradictory(e1, e2): | |
| return (0.0, "opposite") | |
| # Distant but not opposite | |
| return (0.2, "distant") | |
| def get_emotions_in_category(self, category: EmotionCategory) -> List[str]: | |
| """Get all emotions in a category""" | |
| return [e for e, c in self.EMOTION_CATEGORIES.items() if c == category] | |
| def get_all_categories(self) -> List[EmotionCategory]: | |
| """Get all emotion categories""" | |
| return list(EmotionCategory) | |
| def visualize_wheel(self) -> str: | |
| """Generate ASCII visualization of the emotion wheel""" | |
| lines = [ | |
| "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ", | |
| "โ EMOTION WHEEL GRAPH โ", | |
| "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ", | |
| "โ โ", | |
| "โ JOY ๐ โ", | |
| "โ / \\ โ", | |
| "โ / \\ โ", | |
| "โ ANTICIPATION ๐ค TRUST ๐ค โ", | |
| "โ | \\ / | โ", | |
| "โ | \\ / | โ", | |
| "โ | LOVE ๐ฅฐ | โ", | |
| "โ | | | โ", | |
| "โ ANGER ๐ NEUTRAL ๐ FEAR ๐จ โ", | |
| "โ | | | โ", | |
| "โ | CONFUSION ๐ | โ", | |
| "โ | / \\ | โ", | |
| "โ \\ / \\ / โ", | |
| "โ DISGUST ๐คข SURPRISE ๐ฎ โ", | |
| "โ \\ / โ", | |
| "โ \\ / โ", | |
| "โ SADNESS ๐ข โ", | |
| "โ | โ", | |
| "โ GUILT ๐ฃ โ", | |
| "โ โ", | |
| "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ", | |
| "โ RELATIONSHIPS: โ", | |
| "โ โโโ Adjacent (similar) โโโ Opposite (contradictory) โ", | |
| "โ โ", | |
| "โ JOY โโโ SADNESS TRUST โโโ DISGUST FEAR โโโ ANGER โ", | |
| "โ SURPRISE โโโ ANTICIPATION โ", | |
| "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ", | |
| ] | |
| return "\n".join(lines) | |
| # Singleton instance | |
| _wheel_instance = None | |
| def get_emotion_wheel() -> EmotionWheel: | |
| """Get singleton emotion wheel instance""" | |
| global _wheel_instance | |
| if _wheel_instance is None: | |
| _wheel_instance = EmotionWheel() | |
| return _wheel_instance | |
| if __name__ == "__main__": | |
| wheel = EmotionWheel() | |
| print(wheel.visualize_wheel()) | |
| print() | |
| # Test similarity scores | |
| test_pairs = [ | |
| ("joy", "happiness"), # Same category | |
| ("joy", "love"), # Adjacent | |
| ("joy", "sadness"), # Opposite | |
| ("anger", "frustration"), # Same category | |
| ("fear", "anxiety"), # Same category | |
| ("confusion", "puzzled"), # Same category | |
| ("happiness", "anger"), # Distant | |
| ] | |
| print("\nSimilarity Tests:") | |
| print("-" * 60) | |
| for e1, e2 in test_pairs: | |
| score, rel = wheel.get_similarity_score(e1, e2) | |
| print(f"{e1:15} vs {e2:15} โ {score:.1f} ({rel})") | |