Download app.py from danielritchie/generative-affect-engine: direct link, hf CLI and curl.
- Browser
- Download file 7.37 kB
-
https://huggingface.co/spaces/danielritchie/generative-affect-engine/resolve/main/app.py
- Command line
-
hf download hf://spaces/danielritchie/generative-affect-engine/app.py
-
curl -L -o app.py https://huggingface.co/spaces/danielritchie/generative-affect-engine/resolve/main/app.py
7.37 kB
| # 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) | |