Spaces:
Running on Zero
Running on Zero
| """ | |
| docs/make_spec_figures.py — regenerate the illustrations embedded in app_spec.md. | |
| These are rendered with the app's OWN engine (engine/mappings.py + renderer.py), | |
| so the figures are accurate to what the toy actually draws and reproducible: | |
| python docs/make_spec_figures.py | |
| Outputs PNGs into docs/figures/. The PAD (valence/arousal/dominance) values for | |
| the example sentences are the model-anchored few-shot values from | |
| prompts/prompt_core.md (so no model call is needed to regenerate). | |
| """ | |
| import os, sys | |
| import numpy as np | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| from matplotlib.patches import Polygon | |
| # import the real engine | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from engine.mappings import affect_to_geometry, affect_to_color, hex_of | |
| from engine.renderer import geom_to_points, geom_to_points_segmented | |
| OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "figures") | |
| os.makedirs(OUT, exist_ok=True) | |
| INK = "#11131A" | |
| PAPER = "#ECE7DB" | |
| MIST = "#9AA3B2" | |
| GREY = "#8A8F9C" # the app's greyscale-during-play fill | |
| ACCENT = "#E8A33D" | |
| def _shape(ax, pts, fill, edge, lw=1.6, alpha=0.92): | |
| ax.add_patch(Polygon(pts, closed=True, facecolor=fill, edgecolor=edge, | |
| lw=lw, alpha=alpha, joinstyle="round")) | |
| ax.set_xlim(0.02, 0.98); ax.set_ylim(0.02, 0.98) | |
| ax.set_aspect("equal"); ax.axis("off") | |
| def fig_sentence_to_shape(): | |
| """Hero: four beats -> their shapes, greyscale (during play) then revealed.""" | |
| examples = [ | |
| ('"The blade flashed once\nin the dark."', 0.12, 0.88, 0.80), | |
| ('"Snow covered everything,\nsoft and still."', 0.82, 0.15, 0.40), | |
| ('"She waited by the phone\nthat never rang."', 0.25, 0.18, 0.30), | |
| ('"Fireworks burst over\nthe laughing crowd."', 0.85, 0.82, 0.62), | |
| ] | |
| fig, axes = plt.subplots(2, len(examples), figsize=(len(examples) * 2.5, 5.4)) | |
| fig.patch.set_facecolor(INK) | |
| for col, (sent, v, a, d) in enumerate(examples): | |
| g = affect_to_geometry(v, a, d) | |
| pts = geom_to_points(g, seed_key=sent) | |
| col_hex = hex_of(affect_to_color(v, a, d)) | |
| # row 0 — during play (greyscale, colour hidden) | |
| _shape(axes[0, col], pts, GREY, "#C9CDD6") | |
| axes[0, col].set_title(sent, color=PAPER, fontsize=10, pad=8) | |
| # row 1 — revealed (affect-driven colour) | |
| _shape(axes[1, col], pts, col_hex, "#FFFFFF") | |
| axes[1, col].text(0.5, -0.04, f"V {v:.2f} A {a:.2f} D {d:.2f}", | |
| color=MIST, fontsize=8, ha="center", va="top", | |
| family="monospace", transform=axes[1, col].transAxes) | |
| axes[0, 0].text(-0.12, 0.5, "during play", rotation=90, va="center", ha="center", | |
| color=MIST, fontsize=10, family="monospace", | |
| transform=axes[0, 0].transAxes) | |
| axes[1, 0].text(-0.12, 0.5, "revealed", rotation=90, va="center", ha="center", | |
| color=ACCENT, fontsize=10, family="monospace", | |
| transform=axes[1, 0].transAxes) | |
| fig.suptitle("Sentence → essence → shape (colour hidden until the reveal)", | |
| color=PAPER, fontsize=13, y=0.99) | |
| fig.tight_layout(rect=(0.02, 0, 1, 0.96)) | |
| _save(fig, "sentence_to_shape.png") | |
| def fig_color_corners(): | |
| """The four emotion-corner hue anchors, each as a shape in its colour.""" | |
| corners = [ | |
| ("calm · pleasant\n(low A, high V)", 0.85, 0.12, 0.45), # green | |
| ("joyful\n(high A, high V)", 0.88, 0.85, 0.60), # yellow-orange | |
| ("angry\n(high A, low V)", 0.12, 0.88, 0.80), # red | |
| ("sad\n(low A, low V)", 0.20, 0.16, 0.30), # blue | |
| ] | |
| fig, axes = plt.subplots(1, 4, figsize=(10, 3.0)) | |
| fig.patch.set_facecolor(INK) | |
| for ax, (label, v, a, d) in zip(axes, corners): | |
| g = affect_to_geometry(v, a, d) | |
| pts = geom_to_points(g, seed_key=label) | |
| _shape(ax, pts, hex_of(affect_to_color(v, a, d)), "#FFFFFF") | |
| ax.set_title(label, color=PAPER, fontsize=10, pad=8) | |
| fig.suptitle("Affect → colour: four hue anchors, interpolated circularly", | |
| color=PAPER, fontsize=13, y=1.02) | |
| fig.tight_layout() | |
| _save(fig, "color_corners.png") | |
| def fig_segmented(): | |
| """Per-segment essence: 'calm at first, then suddenly sharp' in one form.""" | |
| calm = affect_to_geometry(0.75, 0.15, 0.45) | |
| sharp = affect_to_geometry(0.15, 0.90, 0.75) | |
| pts = geom_to_points_segmented([calm, sharp], [1.0, 1.0], seed_key="calm-then-sharp") | |
| fig, ax = plt.subplots(figsize=(4.4, 4.4)) | |
| fig.patch.set_facecolor(INK) | |
| _shape(ax, pts, "#7F77DD", "#FFFFFF") | |
| ax.set_title('"Calm at first,\nthen suddenly sharp"', color=PAPER, fontsize=12, pad=10) | |
| fig.tight_layout() | |
| _save(fig, "segmented_essence.png") | |
| def fig_geometry_axes(): | |
| """Each scored geometric axis swept 0.0 -> 1.0 (one axis per row).""" | |
| baseline = {"spikiness": 0.15, "compactness": 0.7, "segmentability": 0.1, | |
| "symmetry": 0.9, "scale": 1.0} | |
| rows = ["spikiness", "compactness", "segmentability"] | |
| steps = np.round(np.arange(0.0, 1.01, 0.2), 1) | |
| fig, axes = plt.subplots(len(rows), len(steps), | |
| figsize=(len(steps) * 1.5, len(rows) * 1.6)) | |
| fig.patch.set_facecolor(INK) | |
| for r, axis in enumerate(rows): | |
| for c, val in enumerate(steps): | |
| g = dict(baseline); g[axis] = float(val) | |
| pts = geom_to_points(g, seed_key=f"{axis}{val}") | |
| _shape(axes[r, c], pts, "#7F77DD", "#26215C", lw=1.2) | |
| if r == 0: | |
| axes[r, c].set_title(f"{val:.1f}", color=MIST, fontsize=9) | |
| if c == 0: | |
| axes[r, c].text(-0.2, 0.5, axis, rotation=90, va="center", ha="center", | |
| color=PAPER, fontsize=10, weight="bold", | |
| transform=axes[r, c].transAxes) | |
| fig.suptitle("Scored geometric axes (Huang 2020) — each row varies ONE axis 0→1", | |
| color=PAPER, fontsize=12, y=1.0) | |
| fig.tight_layout(rect=(0.02, 0, 1, 0.95)) | |
| _save(fig, "geometry_axes.png") | |
| def _save(fig, name): | |
| path = os.path.join(OUT, name) | |
| fig.savefig(path, dpi=120, facecolor=fig.get_facecolor(), bbox_inches="tight") | |
| plt.close(fig) | |
| print("saved", os.path.relpath(path)) | |
| if __name__ == "__main__": | |
| fig_sentence_to_shape() | |
| fig_color_corners() | |
| fig_segmented() | |
| fig_geometry_axes() | |
| print("done") | |