Update app.py
Browse files
app.py
CHANGED
|
@@ -1,15 +1,13 @@
|
|
| 1 |
# app.py — Affection 👁️ (Hugging Face Space)
|
| 2 |
|
| 3 |
import os
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
# Disable Spaces reload BEFORE importing gradio
|
| 6 |
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
|
| 7 |
os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
|
| 8 |
os.environ["SPACES_DISABLE_RELOAD"] = "1"
|
| 9 |
|
| 10 |
-
import gradio as gr
|
| 11 |
-
import matplotlib.pyplot as plt
|
| 12 |
-
|
| 13 |
from utils.presets import EMOTION_PRESETS
|
| 14 |
from utils.drama import apply_drama
|
| 15 |
from utils.color_model import infer_color, render_color
|
|
@@ -38,22 +36,17 @@ def apply_passion(raw: dict, passion: float) -> dict:
|
|
| 38 |
# ------------------------------------------------------------
|
| 39 |
# Valence–Arousal Visualization (2D Projection)
|
| 40 |
# ------------------------------------------------------------
|
| 41 |
-
def generate_scatter(raw, amplified, cinematic,
|
| 42 |
|
| 43 |
fig, ax = plt.subplots(figsize=(6, 6))
|
| 44 |
-
base_color = "#2C3E50"
|
| 45 |
|
| 46 |
-
#
|
| 47 |
-
|
| 48 |
-
#
|
| 49 |
for _, preset in EMOTION_PRESETS.items():
|
| 50 |
t = preset["target"]
|
| 51 |
ax.scatter(t["V"], t["A"], alpha=0.1, s=90, color="#BBBBBB")
|
| 52 |
|
| 53 |
-
# ----------------------------------
|
| 54 |
-
# Active Emotional Trajectory
|
| 55 |
-
# ----------------------------------
|
| 56 |
-
|
| 57 |
# Natural
|
| 58 |
ax.scatter(
|
| 59 |
raw["V"], raw["A"],
|
|
@@ -84,75 +77,13 @@ def generate_scatter(raw, amplified, cinematic, target, label, passion, drama):
|
|
| 84 |
label="After Drama (Cinematic Alignment)"
|
| 85 |
)
|
| 86 |
|
| 87 |
-
#
|
| 88 |
-
ax.scatter(
|
| 89 |
-
target["V"],
|
| 90 |
-
target["A"],
|
| 91 |
-
s=160,
|
| 92 |
-
marker="X",
|
| 93 |
-
color="#E74C3C",
|
| 94 |
-
edgecolor="black",
|
| 95 |
-
linewidth=1.2,
|
| 96 |
-
label="Cinematic Anchor"
|
| 97 |
-
)
|
| 98 |
-
|
| 99 |
-
ax.text(
|
| 100 |
-
target["V"],
|
| 101 |
-
target["A"],
|
| 102 |
-
f" {label} Target",
|
| 103 |
-
verticalalignment="center",
|
| 104 |
-
fontsize=9,
|
| 105 |
-
weight="bold",
|
| 106 |
-
color="#E74C3C"
|
| 107 |
-
)
|
| 108 |
-
|
| 109 |
-
# ----------------------------------
|
| 110 |
-
# Dynamic Zoom (20% padded, centered)
|
| 111 |
-
# ----------------------------------
|
| 112 |
-
xs = [
|
| 113 |
-
raw["V"],
|
| 114 |
-
amplified["V"],
|
| 115 |
-
cinematic["V"],
|
| 116 |
-
target["V"]
|
| 117 |
-
]
|
| 118 |
-
|
| 119 |
-
ys = [
|
| 120 |
-
raw["A"],
|
| 121 |
-
amplified["A"],
|
| 122 |
-
cinematic["A"],
|
| 123 |
-
target["A"]
|
| 124 |
-
]
|
| 125 |
-
|
| 126 |
-
min_x, max_x = min(xs), max(xs)
|
| 127 |
-
min_y, max_y = min(ys), max(ys)
|
| 128 |
-
|
| 129 |
-
span_x = max_x - min_x
|
| 130 |
-
span_y = max_y - min_y
|
| 131 |
-
|
| 132 |
-
span = max(span_x, span_y)
|
| 133 |
-
span = max(span, 0.05) # prevent collapse
|
| 134 |
-
|
| 135 |
-
padding = span * 0.20
|
| 136 |
-
center_x = (min_x + max_x) / 2
|
| 137 |
-
center_y = (min_y + max_y) / 2
|
| 138 |
-
half_range = (span / 2) + padding
|
| 139 |
-
|
| 140 |
-
ax.set_xlim(center_x - half_range, center_x + half_range)
|
| 141 |
-
ax.set_ylim(center_y - half_range, center_y + half_range)
|
| 142 |
-
ax.set_aspect("equal", adjustable="box")
|
| 143 |
-
|
| 144 |
-
# ----------------------------------
|
| 145 |
-
# Proportional Arrow Heads
|
| 146 |
-
# ----------------------------------
|
| 147 |
-
arrow_head = span * 0.04
|
| 148 |
-
|
| 149 |
-
# Raw → Amplified
|
| 150 |
ax.arrow(
|
| 151 |
raw["V"],
|
| 152 |
raw["A"],
|
| 153 |
amplified["V"] - raw["V"],
|
| 154 |
amplified["A"] - raw["A"],
|
| 155 |
-
head_width=
|
| 156 |
length_includes_head=True,
|
| 157 |
color=base_color,
|
| 158 |
linestyle="--",
|
|
@@ -160,13 +91,13 @@ def generate_scatter(raw, amplified, cinematic, target, label, passion, drama):
|
|
| 160 |
alpha=0.6
|
| 161 |
)
|
| 162 |
|
| 163 |
-
# Amplified → Cinematic
|
| 164 |
ax.arrow(
|
| 165 |
amplified["V"],
|
| 166 |
amplified["A"],
|
| 167 |
cinematic["V"] - amplified["V"],
|
| 168 |
cinematic["A"] - amplified["A"],
|
| 169 |
-
head_width=
|
| 170 |
length_includes_head=True,
|
| 171 |
color=base_color,
|
| 172 |
linestyle="-",
|
|
@@ -174,6 +105,36 @@ def generate_scatter(raw, amplified, cinematic, target, label, passion, drama):
|
|
| 174 |
alpha=0.9
|
| 175 |
)
|
| 176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
ax.set_xlabel("Valence")
|
| 178 |
ax.set_ylabel("Arousal")
|
| 179 |
ax.set_title(f"{label}\nPassion={round(passion,2)} | Drama={round(drama,2)}")
|
|
@@ -202,15 +163,7 @@ def run_pipeline(preset_name, passion, drama):
|
|
| 202 |
color_params = infer_color(cinematic)
|
| 203 |
color_block = render_color(color_params)
|
| 204 |
|
| 205 |
-
fig = generate_scatter(
|
| 206 |
-
natural,
|
| 207 |
-
amplified,
|
| 208 |
-
cinematic,
|
| 209 |
-
target,
|
| 210 |
-
preset_name,
|
| 211 |
-
passion,
|
| 212 |
-
drama
|
| 213 |
-
)
|
| 214 |
|
| 215 |
return (
|
| 216 |
text,
|
|
@@ -253,17 +206,23 @@ with gr.Blocks(title="Affection 👁️ — Edge Emotional Intelligence") as dem
|
|
| 253 |
|
| 254 |
gr.Markdown(
|
| 255 |
"""
|
| 256 |
-
This
|
| 257 |
|
| 258 |
-
|
| 259 |
-
• Transcript ingestion
|
| 260 |
-
• VAD extraction
|
| 261 |
-
• Structural metrics
|
| 262 |
-
• Radial amplification (Passion)
|
| 263 |
-
• Cinematic alignment (Drama)
|
| 264 |
-
• Continuous emotional state streaming
|
| 265 |
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
"""
|
| 268 |
)
|
| 269 |
|
|
@@ -294,8 +253,8 @@ Edge loop capable of ~200Hz execution.
|
|
| 294 |
gr.Markdown(
|
| 295 |
"""
|
| 296 |
**Note:**
|
| 297 |
-
This 2D
|
| 298 |
-
|
| 299 |
"""
|
| 300 |
)
|
| 301 |
|
|
@@ -308,10 +267,15 @@ The actual transformation operates in 5D VAD+CC space.
|
|
| 308 |
|
| 309 |
gr.Markdown(
|
| 310 |
"""
|
| 311 |
-
VAD+CC
|
|
|
|
|
|
|
|
|
|
| 312 |
|
| 313 |
-
Model:
|
| 314 |
https://huggingface.co/danielritchie/vibe-color-model
|
|
|
|
|
|
|
| 315 |
"""
|
| 316 |
)
|
| 317 |
|
|
@@ -334,9 +298,4 @@ https://huggingface.co/danielritchie/vibe-color-model
|
|
| 334 |
|
| 335 |
demo.load(fn=run_pipeline, inputs=[preset_selector, passion, drama], outputs=outputs)
|
| 336 |
|
| 337 |
-
|
| 338 |
-
demo.launch(
|
| 339 |
-
server_name="0.0.0.0",
|
| 340 |
-
server_port=7860,
|
| 341 |
-
ssr_mode=False
|
| 342 |
-
)
|
|
|
|
| 1 |
# app.py — Affection 👁️ (Hugging Face Space)
|
| 2 |
|
| 3 |
import os
|
| 4 |
+
import gradio as gr
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
|
|
|
|
| 7 |
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
|
| 8 |
os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
|
| 9 |
os.environ["SPACES_DISABLE_RELOAD"] = "1"
|
| 10 |
|
|
|
|
|
|
|
|
|
|
| 11 |
from utils.presets import EMOTION_PRESETS
|
| 12 |
from utils.drama import apply_drama
|
| 13 |
from utils.color_model import infer_color, render_color
|
|
|
|
| 36 |
# ------------------------------------------------------------
|
| 37 |
# Valence–Arousal Visualization (2D Projection)
|
| 38 |
# ------------------------------------------------------------
|
| 39 |
+
def generate_scatter(raw, amplified, cinematic, label, passion, drama):
|
| 40 |
|
| 41 |
fig, ax = plt.subplots(figsize=(6, 6))
|
|
|
|
| 42 |
|
| 43 |
+
base_color = "#2C3E50" # neutral deep tone
|
| 44 |
+
|
| 45 |
+
# Plot cinematic anchors faintly
|
| 46 |
for _, preset in EMOTION_PRESETS.items():
|
| 47 |
t = preset["target"]
|
| 48 |
ax.scatter(t["V"], t["A"], alpha=0.1, s=90, color="#BBBBBB")
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
# Natural
|
| 51 |
ax.scatter(
|
| 52 |
raw["V"], raw["A"],
|
|
|
|
| 77 |
label="After Drama (Cinematic Alignment)"
|
| 78 |
)
|
| 79 |
|
| 80 |
+
# Arrow 1 — Raw → Amplified
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
ax.arrow(
|
| 82 |
raw["V"],
|
| 83 |
raw["A"],
|
| 84 |
amplified["V"] - raw["V"],
|
| 85 |
amplified["A"] - raw["A"],
|
| 86 |
+
head_width=0.02,
|
| 87 |
length_includes_head=True,
|
| 88 |
color=base_color,
|
| 89 |
linestyle="--",
|
|
|
|
| 91 |
alpha=0.6
|
| 92 |
)
|
| 93 |
|
| 94 |
+
# Arrow 2 — Amplified → Cinematic
|
| 95 |
ax.arrow(
|
| 96 |
amplified["V"],
|
| 97 |
amplified["A"],
|
| 98 |
cinematic["V"] - amplified["V"],
|
| 99 |
cinematic["A"] - amplified["A"],
|
| 100 |
+
head_width=0.02,
|
| 101 |
length_includes_head=True,
|
| 102 |
color=base_color,
|
| 103 |
linestyle="-",
|
|
|
|
| 105 |
alpha=0.9
|
| 106 |
)
|
| 107 |
|
| 108 |
+
# ----------------------------------
|
| 109 |
+
# Dynamic Zoom (Centered + 20% Padding)
|
| 110 |
+
# ----------------------------------
|
| 111 |
+
xs = [raw["V"], amplified["V"], cinematic["V"]]
|
| 112 |
+
ys = [raw["A"], amplified["A"], cinematic["A"]]
|
| 113 |
+
|
| 114 |
+
min_x, max_x = min(xs), max(xs)
|
| 115 |
+
min_y, max_y = min(ys), max(ys)
|
| 116 |
+
|
| 117 |
+
span_x = max_x - min_x
|
| 118 |
+
span_y = max_y - min_y
|
| 119 |
+
|
| 120 |
+
# Use the larger span to keep square framing
|
| 121 |
+
span = max(span_x, span_y)
|
| 122 |
+
|
| 123 |
+
# Avoid zero-span collapse
|
| 124 |
+
span = max(span, 0.05)
|
| 125 |
+
|
| 126 |
+
padding = span * 0.20 # 20% larger
|
| 127 |
+
|
| 128 |
+
center_x = (min_x + max_x) / 2
|
| 129 |
+
center_y = (min_y + max_y) / 2
|
| 130 |
+
|
| 131 |
+
half_range = (span / 2) + padding
|
| 132 |
+
|
| 133 |
+
ax.set_xlim(center_x - half_range, center_x + half_range)
|
| 134 |
+
ax.set_ylim(center_y - half_range, center_y + half_range)
|
| 135 |
+
|
| 136 |
+
ax.set_aspect('equal', adjustable='box')
|
| 137 |
+
|
| 138 |
ax.set_xlabel("Valence")
|
| 139 |
ax.set_ylabel("Arousal")
|
| 140 |
ax.set_title(f"{label}\nPassion={round(passion,2)} | Drama={round(drama,2)}")
|
|
|
|
| 163 |
color_params = infer_color(cinematic)
|
| 164 |
color_block = render_color(color_params)
|
| 165 |
|
| 166 |
+
fig = generate_scatter(natural, amplified, cinematic, preset_name, passion, drama)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
return (
|
| 169 |
text,
|
|
|
|
| 206 |
|
| 207 |
gr.Markdown(
|
| 208 |
"""
|
| 209 |
+
This section provides a simplified visualization of a more complex on-device architecture.
|
| 210 |
|
| 211 |
+
In hardware deployment, the NVIDIA Jetson Orin Nano performs all of the following:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
|
| 213 |
+
• Robot hardware daemon service
|
| 214 |
+
• Interactive conversational application
|
| 215 |
+
• Real-time transcript ingestion
|
| 216 |
+
• VAD extraction (NRC-VAD lexicon)
|
| 217 |
+
• Structural language metrics (Complexity + Coherence)
|
| 218 |
+
• Radial emotional amplification (Passion)
|
| 219 |
+
• Cinematic nearest-exemplar alignment (Drama)
|
| 220 |
+
• Dual-timescale blending (fast burst + slow baseline via Nemotron/Ollama)
|
| 221 |
+
• Continuous emotional state streaming for display on an expression module
|
| 222 |
+
|
| 223 |
+
This demo isolates a single loop transformation for clarity.
|
| 224 |
+
|
| 225 |
+
Our NVIDIA edge device is capable of running this loop 200x per second.
|
| 226 |
"""
|
| 227 |
)
|
| 228 |
|
|
|
|
| 253 |
gr.Markdown(
|
| 254 |
"""
|
| 255 |
**Note:**
|
| 256 |
+
This plot shows a 2D Valence–Arousal projection for visualization only, but results are from the actual model.
|
| 257 |
+
Actual transformation and color inference are more complex and operate on the full 5D VAD+CC vector.
|
| 258 |
"""
|
| 259 |
)
|
| 260 |
|
|
|
|
| 267 |
|
| 268 |
gr.Markdown(
|
| 269 |
"""
|
| 270 |
+
The finalized VAD+CC vector is transmitted to an expressive display module. In this example, we are converting to colors to be used for eyes.
|
| 271 |
+
|
| 272 |
+
The module does not compute emotion.
|
| 273 |
+
It receives the 5D emotional state and runs a trained neural model to convert it into expressive color.
|
| 274 |
|
| 275 |
+
Model used here (same as deployment):
|
| 276 |
https://huggingface.co/danielritchie/vibe-color-model
|
| 277 |
+
|
| 278 |
+
VAD+CC (Affect Engine) → Embedded Model → Color Rendering (Expression)
|
| 279 |
"""
|
| 280 |
)
|
| 281 |
|
|
|
|
| 298 |
|
| 299 |
demo.load(fn=run_pipeline, inputs=[preset_selector, passion, drama], outputs=outputs)
|
| 300 |
|
| 301 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|