danielritchie's picture
Update app.py
4885c5b verified
# app.py — Affection 👁️ (Hugging Face Space)
import os
import gradio as gr
import matplotlib.pyplot as plt
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
os.environ["SPACES_DISABLE_RELOAD"] = "1"
from utils.presets import EMOTION_PRESETS
from utils.drama import apply_drama
from utils.color_model import infer_color, render_color
# ------------------------------------------------------------
# Passion (Radial Amplification)
# ------------------------------------------------------------
def apply_passion(raw: dict, passion: float) -> dict:
passion = max(0.0, min(3.5, float(passion)))
out = {}
for k, v in raw.items():
v = float(v)
if k in ("V", "A", "D"):
delta = v - 0.5
magnitude = abs(delta)
gain = 1.0 + passion * magnitude
out[k] = max(0.0, min(1.0, 0.5 + delta * gain))
else:
out[k] = max(0.0, min(1.0, v))
return out
# ------------------------------------------------------------
# Valence–Arousal Visualization (2D Projection)
# ------------------------------------------------------------
def generate_scatter(raw, amplified, cinematic, target, target_name, passion, drama):
fig, ax = plt.subplots(figsize=(6, 7)) # slightly taller
plt.subplots_adjust(right=0.75) # leave room for legend
# ----------------------------------
# Background Anchors
# ----------------------------------
for name, preset in EMOTION_PRESETS.items():
t = preset["target"]
ax.scatter(t["V"], t["A"], alpha=0.06, s=90, color="#DDDDDD")
# ----------------------------------
# Trajectory Points (Styled)
# ----------------------------------
# 1️⃣ Natural — light grey thin border
ax.scatter(
raw["V"], raw["A"],
s=180,
facecolor="#F0F0F0",
edgecolor="#CCCCCC",
linewidth=1,
label="Natural"
)
# 2️⃣ After Passion — medium grey
ax.scatter(
amplified["V"], amplified["A"],
s=180,
facecolor="#9E9E9E",
edgecolor="#666666",
linewidth=1.5,
label="After Passion"
)
# 3️⃣ After Drama — dark grey thin border
ax.scatter(
cinematic["V"], cinematic["A"],
s=220,
facecolor="#2F2F2F",
edgecolor="black",
linewidth=1,
label="After Drama"
)
# Cinematic Anchor
ax.scatter(
target["V"],
target["A"],
s=180,
marker="X",
color="#E74C3C",
edgecolor="black",
linewidth=1.2,
label=f"Anchor ({target_name})"
)
# ----------------------------------
# Dynamic Zoom (20% padded)
# ----------------------------------
xs = [raw["V"], amplified["V"], cinematic["V"], target["V"]]
ys = [raw["A"], amplified["A"], cinematic["A"], target["A"]]
min_x, max_x = min(xs), max(xs)
min_y, max_y = min(ys), max(ys)
span_x = max_x - min_x
span_y = max_y - min_y
span = max(span_x, span_y)
span = max(span, 0.05)
padding = span * 0.20
center_x = (min_x + max_x) / 2
center_y = (min_y + max_y) / 2
# shift center slightly upward
center_y += span * 0.10
half_range = (span / 2) + padding
ax.set_xlim(center_x - half_range, center_x + half_range)
ax.set_ylim(center_y - half_range, center_y + half_range)
ax.set_aspect('equal', adjustable='box')
# ----------------------------------
# Proportional Arrows
# ----------------------------------
arrow_head = span * 0.035
ax.arrow(
raw["V"], raw["A"],
amplified["V"] - raw["V"],
amplified["A"] - raw["A"],
head_width=arrow_head,
length_includes_head=True,
color="#888888",
linestyle="--",
linewidth=1.8,
alpha=0.7
)
ax.arrow(
amplified["V"], amplified["A"],
cinematic["V"] - amplified["V"],
cinematic["A"] - amplified["A"],
head_width=arrow_head,
length_includes_head=True,
color="#444444",
linestyle="-",
linewidth=2,
alpha=0.9
)
# ----------------------------------
# Labels & Legend
# ----------------------------------
ax.set_xlabel("Valence")
ax.set_ylabel("Arousal")
ax.set_title(f"{target_name}\nPassion={round(passion,2)} | Drama={round(drama,2)}")
ax.grid(alpha=0.12)
# Move legend outside plot
ax.legend(loc="center left", bbox_to_anchor=(1.02, 0.5), frameon=False)
plt.tight_layout()
return fig
# ------------------------------------------------------------
# Fast-Loop Simulation
# ------------------------------------------------------------
def run_pipeline(preset_name, passion, drama):
preset = EMOTION_PRESETS[preset_name]
text = preset["text"]
natural = preset["raw"]
target = preset["target"]
amplified = apply_passion(natural, passion)
cinematic = apply_drama(amplified, target, drama)
color_params = infer_color(cinematic)
color_block = render_color(color_params)
fig = generate_scatter(
natural,
amplified,
cinematic,
target,
preset_name,
passion,
drama
)
return (
text,
natural,
amplified,
cinematic,
color_params,
color_block,
fig
)
# ------------------------------------------------------------
# UI
# ------------------------------------------------------------
with gr.Blocks(title="Affection 👁️ — Edge Emotional Intelligence") as demo:
gr.Markdown("# Affection 👁️")
gr.Markdown("## Simulation Layer for an Edge AI Emotional Robotics System")
gr.Markdown("### 🗣 Robot Speech")
preset_selector = gr.Radio(
choices=list(EMOTION_PRESETS.keys()),
label="Select Transcript Sample",
value=list(EMOTION_PRESETS.keys())[0],
)
transcript_output = gr.Textbox(label="Input Transcript", interactive=False)
gr.Markdown("---")
gr.Markdown("### ⚡ Edge Affect Processing")
with gr.Row():
passion = gr.Slider(0.0, 3.0, value=2.25, step=0.1, label="Passion")
drama = gr.Slider(0.0, 1.5, value=0.65, step=0.05, label="Drama")
with gr.Row():
natural_output = gr.JSON(label="Natural")
amplified_output = gr.JSON(label="After Passion")
cinematic_output = gr.JSON(label="After Drama")
scatter_output = gr.Plot(label="Valence–Arousal Projection")
gr.Markdown("---")
gr.Markdown("### 💡 Emotional Expression")
rgb_output = gr.JSON(label="Model Output")
color_display = gr.HTML(label="Rendered Expression")
outputs = [
transcript_output,
natural_output,
amplified_output,
cinematic_output,
rgb_output,
color_display,
scatter_output
]
preset_selector.change(fn=run_pipeline, inputs=[preset_selector, passion, drama], outputs=outputs)
passion.change(fn=run_pipeline, inputs=[preset_selector, passion, drama], outputs=outputs)
drama.change(fn=run_pipeline, inputs=[preset_selector, passion, drama], outputs=outputs)
demo.load(fn=run_pipeline, inputs=[preset_selector, passion, drama], outputs=outputs)
demo.launch(server_name="0.0.0.0", server_port=7860)