danielritchie commited on
Commit
4885c5b
·
verified ·
1 Parent(s): 8fca3fd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -133
app.py CHANGED
@@ -36,111 +36,132 @@ def apply_passion(raw: dict, passion: float) -> dict:
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"],
53
  s=180,
54
- facecolor=base_color,
55
- alpha=0.5,
56
- label="Natural (Extraction)"
 
57
  )
58
 
59
- # After Passion
60
  ax.scatter(
61
  amplified["V"], amplified["A"],
62
  s=180,
63
- facecolors="none",
64
- edgecolors=base_color,
65
- linewidth=2,
66
- label="After Passion (Radial Gain)"
67
  )
68
 
69
- # After Drama
70
  ax.scatter(
71
  cinematic["V"], cinematic["A"],
72
  s=220,
73
- facecolor=base_color,
74
  edgecolor="black",
75
- linewidth=1.5,
76
- alpha=0.9,
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="--",
90
- linewidth=2,
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="-",
104
- linewidth=2,
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)}")
141
 
142
- ax.legend(loc="lower right")
143
- ax.grid(alpha=0.15)
 
 
144
 
145
  plt.tight_layout()
146
  return fig
@@ -163,7 +184,15 @@ def run_pipeline(preset_name, passion, drama):
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,
@@ -184,9 +213,6 @@ with gr.Blocks(title="Affection 👁️ — Edge Emotional Intelligence") as dem
184
  gr.Markdown("# Affection 👁️")
185
  gr.Markdown("## Simulation Layer for an Edge AI Emotional Robotics System")
186
 
187
- # ---------------------------
188
- # Robot Speech
189
- # ---------------------------
190
  gr.Markdown("### 🗣 Robot Speech")
191
 
192
  preset_selector = gr.Radio(
@@ -199,87 +225,24 @@ with gr.Blocks(title="Affection 👁️ — Edge Emotional Intelligence") as dem
199
 
200
  gr.Markdown("---")
201
 
202
- # ---------------------------
203
- # Edge Affect Processing
204
- # ---------------------------
205
- gr.Markdown("### ⚡ Edge Affect Processing — NVIDIA Jetson Orin Nano")
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
 
229
  with gr.Row():
230
- passion = gr.Slider(
231
- minimum=0.0,
232
- maximum=3.0,
233
- value=2.25,
234
- step=0.1,
235
- label="Passion (Radial Emotional Amplification)"
236
- )
237
-
238
- drama = gr.Slider(
239
- minimum=0.0,
240
- maximum=1.5,
241
- value=0.65,
242
- step=0.05,
243
- label="Drama (Cinematic Alignment)"
244
- )
245
 
246
  with gr.Row():
247
- natural_output = gr.JSON(label="Natural VAD+CC")
248
  amplified_output = gr.JSON(label="After Passion")
249
  cinematic_output = gr.JSON(label="After Drama")
250
 
251
  scatter_output = gr.Plot(label="Valence–Arousal Projection")
252
 
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
-
261
  gr.Markdown("---")
262
 
263
- # ---------------------------
264
- # Emotional Expression
265
- # ---------------------------
266
  gr.Markdown("### 💡 Emotional Expression")
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
-
282
- rgb_output = gr.JSON(label="Model Output (RGB + Expressive Parameters)")
283
  color_display = gr.HTML(label="Rendered Expression")
284
 
285
  outputs = [
@@ -298,4 +261,4 @@ VAD+CC (Affect Engine) → Embedded Model → Color Rendering (Expression)
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)
 
36
  # ------------------------------------------------------------
37
  # Valence–Arousal Visualization (2D Projection)
38
  # ------------------------------------------------------------
39
+ def generate_scatter(raw, amplified, cinematic, target, target_name, passion, drama):
40
 
41
+ fig, ax = plt.subplots(figsize=(6, 7)) # slightly taller
42
+ plt.subplots_adjust(right=0.75) # leave room for legend
43
 
44
+ # ----------------------------------
45
+ # Background Anchors
46
+ # ----------------------------------
47
+ for name, preset in EMOTION_PRESETS.items():
48
  t = preset["target"]
49
+ ax.scatter(t["V"], t["A"], alpha=0.06, s=90, color="#DDDDDD")
50
+
51
+ # ----------------------------------
52
+ # Trajectory Points (Styled)
53
+ # ----------------------------------
54
 
55
+ # 1️⃣ Natural — light grey thin border
56
  ax.scatter(
57
  raw["V"], raw["A"],
58
  s=180,
59
+ facecolor="#F0F0F0",
60
+ edgecolor="#CCCCCC",
61
+ linewidth=1,
62
+ label="Natural"
63
  )
64
 
65
+ # 2️⃣ After Passion — medium grey
66
  ax.scatter(
67
  amplified["V"], amplified["A"],
68
  s=180,
69
+ facecolor="#9E9E9E",
70
+ edgecolor="#666666",
71
+ linewidth=1.5,
72
+ label="After Passion"
73
  )
74
 
75
+ # 3️⃣ After Drama — dark grey thin border
76
  ax.scatter(
77
  cinematic["V"], cinematic["A"],
78
  s=220,
79
+ facecolor="#2F2F2F",
80
  edgecolor="black",
81
+ linewidth=1,
82
+ label="After Drama"
 
83
  )
84
 
85
+ # Cinematic Anchor
86
+ ax.scatter(
87
+ target["V"],
88
+ target["A"],
89
+ s=180,
90
+ marker="X",
91
+ color="#E74C3C",
92
+ edgecolor="black",
93
+ linewidth=1.2,
94
+ label=f"Anchor ({target_name})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  )
96
 
97
  # ----------------------------------
98
+ # Dynamic Zoom (20% padded)
99
  # ----------------------------------
100
+ xs = [raw["V"], amplified["V"], cinematic["V"], target["V"]]
101
+ ys = [raw["A"], amplified["A"], cinematic["A"], target["A"]]
102
 
103
  min_x, max_x = min(xs), max(xs)
104
  min_y, max_y = min(ys), max(ys)
105
 
106
  span_x = max_x - min_x
107
  span_y = max_y - min_y
 
 
108
  span = max(span_x, span_y)
 
 
109
  span = max(span, 0.05)
110
 
111
+ padding = span * 0.20
 
112
  center_x = (min_x + max_x) / 2
113
  center_y = (min_y + max_y) / 2
114
 
115
+ # shift center slightly upward
116
+ center_y += span * 0.10
117
+
118
  half_range = (span / 2) + padding
119
 
120
  ax.set_xlim(center_x - half_range, center_x + half_range)
121
  ax.set_ylim(center_y - half_range, center_y + half_range)
122
 
123
  ax.set_aspect('equal', adjustable='box')
124
+
125
+ # ----------------------------------
126
+ # Proportional Arrows
127
+ # ----------------------------------
128
+ arrow_head = span * 0.035
129
+
130
+ ax.arrow(
131
+ raw["V"], raw["A"],
132
+ amplified["V"] - raw["V"],
133
+ amplified["A"] - raw["A"],
134
+ head_width=arrow_head,
135
+ length_includes_head=True,
136
+ color="#888888",
137
+ linestyle="--",
138
+ linewidth=1.8,
139
+ alpha=0.7
140
+ )
141
+
142
+ ax.arrow(
143
+ amplified["V"], amplified["A"],
144
+ cinematic["V"] - amplified["V"],
145
+ cinematic["A"] - amplified["A"],
146
+ head_width=arrow_head,
147
+ length_includes_head=True,
148
+ color="#444444",
149
+ linestyle="-",
150
+ linewidth=2,
151
+ alpha=0.9
152
+ )
153
+
154
+ # ----------------------------------
155
+ # Labels & Legend
156
+ # ----------------------------------
157
  ax.set_xlabel("Valence")
158
  ax.set_ylabel("Arousal")
159
+ ax.set_title(f"{target_name}\nPassion={round(passion,2)} | Drama={round(drama,2)}")
160
 
161
+ ax.grid(alpha=0.12)
162
+
163
+ # Move legend outside plot
164
+ ax.legend(loc="center left", bbox_to_anchor=(1.02, 0.5), frameon=False)
165
 
166
  plt.tight_layout()
167
  return fig
 
184
  color_params = infer_color(cinematic)
185
  color_block = render_color(color_params)
186
 
187
+ fig = generate_scatter(
188
+ natural,
189
+ amplified,
190
+ cinematic,
191
+ target,
192
+ preset_name,
193
+ passion,
194
+ drama
195
+ )
196
 
197
  return (
198
  text,
 
213
  gr.Markdown("# Affection 👁️")
214
  gr.Markdown("## Simulation Layer for an Edge AI Emotional Robotics System")
215
 
 
 
 
216
  gr.Markdown("### 🗣 Robot Speech")
217
 
218
  preset_selector = gr.Radio(
 
225
 
226
  gr.Markdown("---")
227
 
228
+ gr.Markdown("### ⚡ Edge Affect Processing")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
  with gr.Row():
231
+ passion = gr.Slider(0.0, 3.0, value=2.25, step=0.1, label="Passion")
232
+ drama = gr.Slider(0.0, 1.5, value=0.65, step=0.05, label="Drama")
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
  with gr.Row():
235
+ natural_output = gr.JSON(label="Natural")
236
  amplified_output = gr.JSON(label="After Passion")
237
  cinematic_output = gr.JSON(label="After Drama")
238
 
239
  scatter_output = gr.Plot(label="Valence–Arousal Projection")
240
 
 
 
 
 
 
 
 
 
241
  gr.Markdown("---")
242
 
 
 
 
243
  gr.Markdown("### 💡 Emotional Expression")
244
 
245
+ rgb_output = gr.JSON(label="Model Output")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  color_display = gr.HTML(label="Rendered Expression")
247
 
248
  outputs = [
 
261
 
262
  demo.load(fn=run_pipeline, inputs=[preset_selector, passion, drama], outputs=outputs)
263
 
264
+ demo.launch(server_name="0.0.0.0", server_port=7860)