Spaces:
Running
Running
Fix empty paper-stats heatmap (matplotlib 2-panel); tighten passport gridspec; fan out sibling alignment labels
Browse files- __pycache__/app.cpython-310.pyc +0 -0
- app.py +89 -70
__pycache__/app.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-310.pyc and b/__pycache__/app.cpython-310.pyc differ
|
|
|
app.py
CHANGED
|
@@ -648,11 +648,11 @@ def _sensory_profile(name):
|
|
| 648 |
return out
|
| 649 |
|
| 650 |
def render_passport(name):
|
| 651 |
-
fig = plt.figure(figsize=(
|
| 652 |
fig.patch.set_facecolor(KAIKAKU_DARK)
|
| 653 |
-
gs = gridspec.GridSpec(
|
| 654 |
-
height_ratios=[0.
|
| 655 |
-
hspace=0.
|
| 656 |
def styled(ax, title=None):
|
| 657 |
ax.set_facecolor(KAIKAKU_DARK)
|
| 658 |
for s in ax.spines.values():
|
|
@@ -662,22 +662,19 @@ def render_passport(name):
|
|
| 662 |
ax.set_title(title, color=KAIKAKU_ACCENT_LIGHT, fontsize=10,
|
| 663 |
family="monospace", loc="left", pad=8)
|
| 664 |
return ax
|
| 665 |
-
# Headline
|
| 666 |
ax_h = fig.add_subplot(gs[0, :]); ax_h.axis("off")
|
| 667 |
pretty = (name or "").replace("_", " ").upper()
|
| 668 |
-
ax_h.text(0.0, 0.
|
| 669 |
-
fontsize=
|
| 670 |
group = _NAME_TO_GROUP.get(name, "Other")
|
| 671 |
-
ax_h.text(0.0, 0.
|
| 672 |
-
color=KAIKAKU_ACCENT, fontsize=
|
| 673 |
-
ax_h.plot([0, 1], [0.
|
| 674 |
-
#
|
| 675 |
-
ax_s = fig.add_subplot(gs[1, :]); ax_s.axis("off")
|
| 676 |
-
ax_s.text(0.0, 0.5, "// NEAREST NEIGHBOURS PER SIBLING",
|
| 677 |
-
color=KAIKAKU_ACCENT_LIGHT, fontsize=11, family="monospace", va="center")
|
| 678 |
-
# Neighbours
|
| 679 |
for i, sib in enumerate(PASSPORT_SIBS):
|
| 680 |
-
ax = styled(fig.add_subplot(gs[
|
|
|
|
| 681 |
ax.set_xticks([]); ax.set_yticks([])
|
| 682 |
m = MODELS[sib]
|
| 683 |
if name not in m.vocab:
|
|
@@ -691,15 +688,15 @@ def render_passport(name):
|
|
| 691 |
fontsize=11, family="monospace", transform=ax.transAxes)
|
| 692 |
ax.text(0.96, y, f"{sim:.3f}", color=KAIKAKU_ACCENT_LIGHT,
|
| 693 |
fontsize=10, family="monospace", ha="right", transform=ax.transAxes)
|
| 694 |
-
#
|
| 695 |
-
ax_radar_host = fig.add_subplot(gs[
|
| 696 |
ax_radar_host.text(0.0, 1.02, "// SENSORY RADAR",
|
| 697 |
-
color=KAIKAKU_ACCENT_LIGHT, fontsize=
|
| 698 |
family="monospace", transform=ax_radar_host.transAxes)
|
| 699 |
bb = ax_radar_host.get_position()
|
| 700 |
-
pad_x, pad_y = bb.width * 0.
|
| 701 |
ax_polar = fig.add_axes(
|
| 702 |
-
[bb.x0 + pad_x, bb.y0 + pad_y, bb.width - 2*pad_x, bb.height - 2*pad_y - 0.
|
| 703 |
projection="polar")
|
| 704 |
sens = _sensory_profile(name)
|
| 705 |
theta = np.linspace(0, 2*np.pi, len(PASSPORT_SENS), endpoint=False)
|
|
@@ -711,12 +708,12 @@ def render_passport(name):
|
|
| 711 |
ax_polar.fill(theta_c, r_c, color=KAIKAKU_ACCENT, alpha=0.35)
|
| 712 |
ax_polar.set_xticks(theta)
|
| 713 |
ax_polar.set_xticklabels([a.upper() for a in PASSPORT_SENS],
|
| 714 |
-
color=KAIKAKU_ACCENT_LIGHT, fontsize=
|
| 715 |
ax_polar.set_yticklabels([])
|
| 716 |
-
ax_polar.set_ylim(0, max(0.
|
| 717 |
-
ax_polar.grid(color=KAIKAKU_ACCENT_LIGHT, alpha=0.20, lw=0.
|
| 718 |
-
# Cuisine bar
|
| 719 |
-
ax_c = styled(fig.add_subplot(gs[
|
| 720 |
m = MODELS["chem"]
|
| 721 |
if name in m.vocab:
|
| 722 |
v = _unit(m.E[m.vocab[name]])
|
|
@@ -735,8 +732,8 @@ def render_passport(name):
|
|
| 735 |
else:
|
| 736 |
ax_c.text(0.5, 0.5, "(not in chem vocab)", ha="center", va="center",
|
| 737 |
color=KAIKAKU_ACCENT_LIGHT, transform=ax_c.transAxes)
|
| 738 |
-
#
|
| 739 |
-
ax_m = styled(fig.add_subplot(gs[
|
| 740 |
title="// CLOSEST EMERGENT FACTOR MODES (top 3 across siblings)")
|
| 741 |
ax_m.set_xticks([]); ax_m.set_yticks([])
|
| 742 |
scored = []
|
|
@@ -749,21 +746,16 @@ def render_passport(name):
|
|
| 749 |
scored.append((float(_unit(md.pole) @ v), sib, md))
|
| 750 |
scored.sort(key=lambda x: -x[0])
|
| 751 |
for row, (sim, sib, md) in enumerate(scored[:3]):
|
| 752 |
-
y = 0.
|
| 753 |
ax_m.text(0.01, y, f"[{sib.upper()}]", color=KAIKAKU_ACCENT,
|
| 754 |
-
fontsize=
|
| 755 |
-
ax_m.text(0.
|
| 756 |
-
fontsize=
|
| 757 |
ax_m.text(0.99, y, f"cos {sim:.3f}", color=KAIKAKU_ACCENT_LIGHT,
|
| 758 |
fontsize=10, family="monospace", ha="right", transform=ax_m.transAxes)
|
| 759 |
members = ", ".join(md.members[:6])
|
| 760 |
-
ax_m.text(0.
|
| 761 |
fontsize=9, family="monospace", transform=ax_m.transAxes)
|
| 762 |
-
# Footer
|
| 763 |
-
ax_f = fig.add_subplot(gs[5, :]); ax_f.axis("off")
|
| 764 |
-
ax_f.text(0.5, 0.5, "EPICURE 路 300-D INGREDIENT EMBEDDING 路 KAIKAKU",
|
| 765 |
-
ha="center", va="center", color=KAIKAKU_ACCENT,
|
| 766 |
-
fontsize=9, family="monospace", transform=ax_f.transAxes)
|
| 767 |
return fig
|
| 768 |
|
| 769 |
|
|
@@ -890,17 +882,26 @@ def render_sibling_alignment(ingredient):
|
|
| 890 |
except Exception: pass
|
| 891 |
ax.scatter(coords[:,0], coords[:,1], s=2, c=GALLERY_DUST, alpha=0.5, linewidths=0, zorder=2)
|
| 892 |
q = _unit(m.E[m.vocab[ingredient]])
|
| 893 |
-
nb = _topk(m, q,
|
| 894 |
top1.append(nb[0][0] if nb else "-")
|
| 895 |
-
|
|
|
|
| 896 |
p = coords[m.vocab[nm]]
|
| 897 |
-
ax.scatter([p[0]], [p[1]], s=
|
| 898 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 899 |
sp = coords[m.vocab[ingredient]]
|
| 900 |
-
ax.scatter([sp[0]], [sp[1]], s=
|
| 901 |
-
edgecolors="white", linewidths=1.
|
| 902 |
-
ax.text(sp[0], sp[1]
|
| 903 |
-
fontsize=
|
|
|
|
|
|
|
| 904 |
ax.set_xlim(xmin, xmax); ax.set_ylim(ymin, ymax); ax.set_aspect("equal")
|
| 905 |
ax.set_title(f"{sib.upper()} 路 {ingredient}", color=GALLERY_TXTDIM,
|
| 906 |
fontsize=11, family="monospace", pad=6)
|
|
@@ -996,31 +997,49 @@ def recipe_coherence(sibling, basket):
|
|
| 996 |
|
| 997 |
|
| 998 |
def render_direction_quality_heatmap():
|
| 999 |
-
"""Paper 搂3.2
|
| 1000 |
-
|
| 1001 |
-
("CF baked-in
|
| 1002 |
-
("CF basic-taste
|
| 1003 |
-
("USDA macros
|
| 1004 |
-
("Cuisine (Cohen's d)", "8 macro-regions, one-vs-rest", [2.43, 2.70, 3.07]),
|
| 1005 |
]
|
| 1006 |
-
|
| 1007 |
-
|
| 1008 |
-
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
)
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
|
| 1018 |
-
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
|
| 1022 |
-
|
| 1023 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1024 |
return fig
|
| 1025 |
|
| 1026 |
|
|
|
|
| 648 |
return out
|
| 649 |
|
| 650 |
def render_passport(name):
|
| 651 |
+
fig = plt.figure(figsize=(12, 10.5), facecolor=KAIKAKU_DARK)
|
| 652 |
fig.patch.set_facecolor(KAIKAKU_DARK)
|
| 653 |
+
gs = gridspec.GridSpec(4, 6, figure=fig,
|
| 654 |
+
height_ratios=[0.85, 2.0, 2.4, 1.8],
|
| 655 |
+
hspace=0.40, wspace=0.30, left=0.05, right=0.96, top=0.95, bottom=0.05)
|
| 656 |
def styled(ax, title=None):
|
| 657 |
ax.set_facecolor(KAIKAKU_DARK)
|
| 658 |
for s in ax.spines.values():
|
|
|
|
| 662 |
ax.set_title(title, color=KAIKAKU_ACCENT_LIGHT, fontsize=10,
|
| 663 |
family="monospace", loc="left", pad=8)
|
| 664 |
return ax
|
| 665 |
+
# Headline (row 0)
|
| 666 |
ax_h = fig.add_subplot(gs[0, :]); ax_h.axis("off")
|
| 667 |
pretty = (name or "").replace("_", " ").upper()
|
| 668 |
+
ax_h.text(0.0, 0.62, pretty, color=KAIKAKU_ACCENT_LIGHT,
|
| 669 |
+
fontsize=34, fontweight="bold", va="center")
|
| 670 |
group = _NAME_TO_GROUP.get(name, "Other")
|
| 671 |
+
ax_h.text(0.0, 0.15, f"INGREDIENT PASSPORT 路 FOOD GROUP: {group.upper()}",
|
| 672 |
+
color=KAIKAKU_ACCENT, fontsize=10, family="monospace", va="center")
|
| 673 |
+
ax_h.plot([0, 1], [0.05, 0.05], color=KAIKAKU_ACCENT, lw=1.5, transform=ax_h.transAxes)
|
| 674 |
+
# Row 1: 3 neighbour panels (each spans 2 cols of a 6-col grid)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
for i, sib in enumerate(PASSPORT_SIBS):
|
| 676 |
+
ax = styled(fig.add_subplot(gs[1, i*2:(i+1)*2]),
|
| 677 |
+
title=f"[{sib.upper()}] NEAREST NEIGHBOURS")
|
| 678 |
ax.set_xticks([]); ax.set_yticks([])
|
| 679 |
m = MODELS[sib]
|
| 680 |
if name not in m.vocab:
|
|
|
|
| 688 |
fontsize=11, family="monospace", transform=ax.transAxes)
|
| 689 |
ax.text(0.96, y, f"{sim:.3f}", color=KAIKAKU_ACCENT_LIGHT,
|
| 690 |
fontsize=10, family="monospace", ha="right", transform=ax.transAxes)
|
| 691 |
+
# Row 2: sensory radar (cols 0-1) + cuisine bar (cols 2-5)
|
| 692 |
+
ax_radar_host = fig.add_subplot(gs[2, 0:2]); ax_radar_host.axis("off")
|
| 693 |
ax_radar_host.text(0.0, 1.02, "// SENSORY RADAR",
|
| 694 |
+
color=KAIKAKU_ACCENT_LIGHT, fontsize=10,
|
| 695 |
family="monospace", transform=ax_radar_host.transAxes)
|
| 696 |
bb = ax_radar_host.get_position()
|
| 697 |
+
pad_x, pad_y = bb.width * 0.06, bb.height * 0.06
|
| 698 |
ax_polar = fig.add_axes(
|
| 699 |
+
[bb.x0 + pad_x, bb.y0 + pad_y, bb.width - 2*pad_x, bb.height - 2*pad_y - 0.012],
|
| 700 |
projection="polar")
|
| 701 |
sens = _sensory_profile(name)
|
| 702 |
theta = np.linspace(0, 2*np.pi, len(PASSPORT_SENS), endpoint=False)
|
|
|
|
| 708 |
ax_polar.fill(theta_c, r_c, color=KAIKAKU_ACCENT, alpha=0.35)
|
| 709 |
ax_polar.set_xticks(theta)
|
| 710 |
ax_polar.set_xticklabels([a.upper() for a in PASSPORT_SENS],
|
| 711 |
+
color=KAIKAKU_ACCENT_LIGHT, fontsize=8, family="monospace")
|
| 712 |
ax_polar.set_yticklabels([])
|
| 713 |
+
ax_polar.set_ylim(0, max(0.5, float(r.max()) + 0.05))
|
| 714 |
+
ax_polar.grid(color=KAIKAKU_ACCENT_LIGHT, alpha=0.20, lw=0.5)
|
| 715 |
+
# Cuisine bar (row 2, cols 2-5)
|
| 716 |
+
ax_c = styled(fig.add_subplot(gs[2, 2:6]), title="// CUISINE AFFILIATION (chem)")
|
| 717 |
m = MODELS["chem"]
|
| 718 |
if name in m.vocab:
|
| 719 |
v = _unit(m.E[m.vocab[name]])
|
|
|
|
| 732 |
else:
|
| 733 |
ax_c.text(0.5, 0.5, "(not in chem vocab)", ha="center", va="center",
|
| 734 |
color=KAIKAKU_ACCENT_LIGHT, transform=ax_c.transAxes)
|
| 735 |
+
# Row 3: closest factor modes (all 6 cols)
|
| 736 |
+
ax_m = styled(fig.add_subplot(gs[3, :]),
|
| 737 |
title="// CLOSEST EMERGENT FACTOR MODES (top 3 across siblings)")
|
| 738 |
ax_m.set_xticks([]); ax_m.set_yticks([])
|
| 739 |
scored = []
|
|
|
|
| 746 |
scored.append((float(_unit(md.pole) @ v), sib, md))
|
| 747 |
scored.sort(key=lambda x: -x[0])
|
| 748 |
for row, (sim, sib, md) in enumerate(scored[:3]):
|
| 749 |
+
y = 0.85 - row * 0.30
|
| 750 |
ax_m.text(0.01, y, f"[{sib.upper()}]", color=KAIKAKU_ACCENT,
|
| 751 |
+
fontsize=10, family="monospace", transform=ax_m.transAxes)
|
| 752 |
+
ax_m.text(0.09, y, md.label, color="#FFFFFF",
|
| 753 |
+
fontsize=12, fontweight="bold", transform=ax_m.transAxes)
|
| 754 |
ax_m.text(0.99, y, f"cos {sim:.3f}", color=KAIKAKU_ACCENT_LIGHT,
|
| 755 |
fontsize=10, family="monospace", ha="right", transform=ax_m.transAxes)
|
| 756 |
members = ", ".join(md.members[:6])
|
| 757 |
+
ax_m.text(0.09, y - 0.09, members, color=KAIKAKU_ACCENT_LIGHT,
|
| 758 |
fontsize=9, family="monospace", transform=ax_m.transAxes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 759 |
return fig
|
| 760 |
|
| 761 |
|
|
|
|
| 882 |
except Exception: pass
|
| 883 |
ax.scatter(coords[:,0], coords[:,1], s=2, c=GALLERY_DUST, alpha=0.5, linewidths=0, zorder=2)
|
| 884 |
q = _unit(m.E[m.vocab[ingredient]])
|
| 885 |
+
nb = _topk(m, q, 3, exclude=[ingredient])
|
| 886 |
top1.append(nb[0][0] if nb else "-")
|
| 887 |
+
# Label placement with vertical fan-out to avoid overlap
|
| 888 |
+
for j, (nm, _s) in enumerate(nb):
|
| 889 |
p = coords[m.vocab[nm]]
|
| 890 |
+
ax.scatter([p[0]], [p[1]], s=130, c="#F4B86E", edgecolors="white", linewidths=0.8, zorder=4)
|
| 891 |
+
# Stagger labels vertically: top label up, second to right, third down
|
| 892 |
+
dx = 0.25 if j == 1 else 0.0
|
| 893 |
+
dy = (0.45, 0.0, -0.45)[j % 3]
|
| 894 |
+
ha = "left" if j == 1 else "center"
|
| 895 |
+
ax.text(p[0] + dx, p[1] + dy, nm.replace("_", " "), color=GALLERY_TEXT,
|
| 896 |
+
fontsize=9.5, fontweight="bold", alpha=0.95, ha=ha,
|
| 897 |
+
va=("bottom" if dy > 0 else "top" if dy < 0 else "center"), zorder=5)
|
| 898 |
sp = coords[m.vocab[ingredient]]
|
| 899 |
+
ax.scatter([sp[0]], [sp[1]], s=420, c=KAIKAKU_ACCENT_LIGHT, marker="*",
|
| 900 |
+
edgecolors="white", linewidths=1.2, zorder=6)
|
| 901 |
+
ax.text(sp[0], sp[1] - 0.45, ingredient.replace("_", " "), color=KAIKAKU_ACCENT_LIGHT,
|
| 902 |
+
ha="center", va="top", fontsize=11.5, fontweight="bold", zorder=7,
|
| 903 |
+
bbox=dict(boxstyle="round,pad=0.18", facecolor=GALLERY_BG,
|
| 904 |
+
edgecolor=KAIKAKU_ACCENT_LIGHT, alpha=0.85, linewidth=0.6))
|
| 905 |
ax.set_xlim(xmin, xmax); ax.set_ylim(ymin, ymax); ax.set_aspect("equal")
|
| 906 |
ax.set_title(f"{sib.upper()} 路 {ingredient}", color=GALLERY_TXTDIM,
|
| 907 |
fontsize=11, family="monospace", pad=6)
|
|
|
|
| 997 |
|
| 998 |
|
| 999 |
def render_direction_quality_heatmap():
|
| 1000 |
+
"""Paper 搂3.2 as a matplotlib heatmap (Plotly mixed-scale was breaking the render)."""
|
| 1001 |
+
rho_probes = [
|
| 1002 |
+
("CF baked-in (Spearman 蟻)", [0.28, 0.40, 0.46]),
|
| 1003 |
+
("CF basic-taste held-out (蟻)", [0.32, 0.42, 0.47]),
|
| 1004 |
+
("USDA macros (蟻)", [0.41, 0.45, 0.49]),
|
|
|
|
| 1005 |
]
|
| 1006 |
+
d_probes = [
|
| 1007 |
+
("Cuisine, mean Cohen's d", [2.43, 2.70, 3.07]),
|
| 1008 |
+
]
|
| 1009 |
+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(11, 5.6), facecolor=GALLERY_BG,
|
| 1010 |
+
gridspec_kw=dict(height_ratios=[3, 1], hspace=0.35))
|
| 1011 |
+
for ax in (ax1, ax2):
|
| 1012 |
+
ax.set_facecolor(GALLERY_BG)
|
| 1013 |
+
for s in ax.spines.values(): s.set_visible(False)
|
| 1014 |
+
# Panel 1: rho (range 0.2-0.5)
|
| 1015 |
+
z1 = np.array([r[1] for r in rho_probes])
|
| 1016 |
+
im1 = ax1.imshow(z1, cmap="viridis", vmin=0.20, vmax=0.55, aspect="auto")
|
| 1017 |
+
ax1.set_xticks([0, 1, 2]); ax1.set_xticklabels(["Cooc", "Core", "Chem"],
|
| 1018 |
+
color=GALLERY_TEXT, fontsize=12, fontweight="bold")
|
| 1019 |
+
ax1.set_yticks(range(len(rho_probes)))
|
| 1020 |
+
ax1.set_yticklabels([r[0] for r in rho_probes], color=GALLERY_TEXT, fontsize=11)
|
| 1021 |
+
ax1.tick_params(colors=GALLERY_TEXT)
|
| 1022 |
+
for i, row in enumerate(z1):
|
| 1023 |
+
for j, v in enumerate(row):
|
| 1024 |
+
ax1.text(j, i, f"{v:.2f}", ha="center", va="center",
|
| 1025 |
+
color=("white" if v < 0.38 else "#111111"),
|
| 1026 |
+
fontsize=15, fontweight="bold")
|
| 1027 |
+
# Panel 2: Cohen's d (range 2-3)
|
| 1028 |
+
z2 = np.array([r[1] for r in d_probes])
|
| 1029 |
+
im2 = ax2.imshow(z2, cmap="viridis", vmin=2.2, vmax=3.2, aspect="auto")
|
| 1030 |
+
ax2.set_xticks([0, 1, 2]); ax2.set_xticklabels(["Cooc", "Core", "Chem"],
|
| 1031 |
+
color=GALLERY_TEXT, fontsize=12, fontweight="bold")
|
| 1032 |
+
ax2.set_yticks(range(len(d_probes)))
|
| 1033 |
+
ax2.set_yticklabels([r[0] for r in d_probes], color=GALLERY_TEXT, fontsize=11)
|
| 1034 |
+
ax2.tick_params(colors=GALLERY_TEXT)
|
| 1035 |
+
for i, row in enumerate(z2):
|
| 1036 |
+
for j, v in enumerate(row):
|
| 1037 |
+
ax2.text(j, i, f"{v:.2f}", ha="center", va="center",
|
| 1038 |
+
color=("white" if v < 2.7 else "#111111"),
|
| 1039 |
+
fontsize=15, fontweight="bold")
|
| 1040 |
+
fig.suptitle("DIRECTION QUALITY 路 Cooc < Core < Chem (paper 搂3.2)",
|
| 1041 |
+
color=GALLERY_TXTDIM, fontsize=12, family="monospace", y=0.97)
|
| 1042 |
+
plt.tight_layout(rect=[0, 0, 1, 0.94])
|
| 1043 |
return fig
|
| 1044 |
|
| 1045 |
|