josefchen commited on
Commit
fbdf299
verified
1 Parent(s): 801f3aa

Fix empty paper-stats heatmap (matplotlib 2-panel); tighten passport gridspec; fan out sibling alignment labels

Browse files
Files changed (2) hide show
  1. __pycache__/app.cpython-310.pyc +0 -0
  2. 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=(11.7, 16.5), facecolor=KAIKAKU_DARK)
652
  fig.patch.set_facecolor(KAIKAKU_DARK)
653
- gs = gridspec.GridSpec(6, 3, figure=fig,
654
- height_ratios=[0.9, 0.4, 2.4, 2.0, 2.0, 0.4],
655
- hspace=0.55, wspace=0.35, left=0.06, right=0.94, top=0.96, bottom=0.04)
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.55, pretty, color=KAIKAKU_ACCENT_LIGHT,
669
- fontsize=42, fontweight="bold", va="center")
670
  group = _NAME_TO_GROUP.get(name, "Other")
671
- ax_h.text(0.0, 0.05, f"INGREDIENT PASSPORT 路 FOOD GROUP: {group.upper()}",
672
- color=KAIKAKU_ACCENT, fontsize=11, family="monospace", va="center")
673
- ax_h.plot([0, 1], [0.0, 0.0], color=KAIKAKU_ACCENT, lw=2, transform=ax_h.transAxes)
674
- # Section label
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[2, i]), title=f"[{sib.upper()}]")
 
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
- # Sensory radar (polar inset)
695
- ax_radar_host = fig.add_subplot(gs[3, 0]); ax_radar_host.axis("off")
696
  ax_radar_host.text(0.0, 1.02, "// SENSORY RADAR",
697
- color=KAIKAKU_ACCENT_LIGHT, fontsize=11,
698
  family="monospace", transform=ax_radar_host.transAxes)
699
  bb = ax_radar_host.get_position()
700
- pad_x, pad_y = bb.width * 0.08, bb.height * 0.10
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.01],
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=9, family="monospace")
715
  ax_polar.set_yticklabels([])
716
- ax_polar.set_ylim(0, max(0.6, float(r.max()) + 0.05))
717
- ax_polar.grid(color=KAIKAKU_ACCENT_LIGHT, alpha=0.20, lw=0.6)
718
- # Cuisine bar
719
- ax_c = styled(fig.add_subplot(gs[3, 1:]), title="// CUISINE AFFILIATION (chem)")
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
- # Closest 3 emergent modes
739
- ax_m = styled(fig.add_subplot(gs[4, :]),
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.82 - row * 0.30
753
  ax_m.text(0.01, y, f"[{sib.upper()}]", color=KAIKAKU_ACCENT,
754
- fontsize=11, family="monospace", transform=ax_m.transAxes)
755
- ax_m.text(0.10, y, md.label, color="#FFFFFF",
756
- fontsize=13, fontweight="bold", transform=ax_m.transAxes)
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.10, y - 0.10, members, color=KAIKAKU_ACCENT_LIGHT,
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, 5, exclude=[ingredient])
894
  top1.append(nb[0][0] if nb else "-")
895
- for nm, _s in nb:
 
896
  p = coords[m.vocab[nm]]
897
- ax.scatter([p[0]], [p[1]], s=110, c="#F4B86E", edgecolors="white", linewidths=0.7, zorder=4)
898
- ax.text(p[0]+0.10, p[1]+0.10, nm, color=GALLERY_TEXT, fontsize=8.5, alpha=0.95, zorder=5)
 
 
 
 
 
 
899
  sp = coords[m.vocab[ingredient]]
900
- ax.scatter([sp[0]], [sp[1]], s=380, c=KAIKAKU_ACCENT_LIGHT, marker="*",
901
- edgecolors="white", linewidths=1.0, zorder=6)
902
- ax.text(sp[0], sp[1]+0.32, ingredient, color=KAIKAKU_ACCENT_LIGHT, ha="center",
903
- fontsize=10.5, fontweight="bold", zorder=7)
 
 
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 table as a Plotly heatmap."""
1000
- probes = [
1001
- ("CF baked-in", "Compound feature (seen by Core/Chem walk schema)", [0.28, 0.40, 0.46]),
1002
- ("CF basic-taste (held-out)", "Sweet/sour/bitter/salt/umami", [0.32, 0.42, 0.47]),
1003
- ("USDA macros", "Protein/fat/carb/fibre/etc.", [0.41, 0.45, 0.49]),
1004
- ("Cuisine (Cohen's d)", "8 macro-regions, one-vs-rest", [2.43, 2.70, 3.07]),
1005
  ]
1006
- z = np.array([row[2] for row in probes])
1007
- text = [[f"{v:.2f}" for v in row] for row in z]
1008
- fig = go.Figure(go.Heatmap(
1009
- z=z, x=["Cooc", "Core", "Chem"], y=[r[0] for r in probes],
1010
- text=text, texttemplate="%{text}", textfont=dict(size=14, color="white"),
1011
- colorscale=[[0, GALLERY_BG], [0.3, GALLERY_DUST], [0.7, KAIKAKU_ACCENT], [1, KAIKAKU_ACCENT_LIGHT]],
1012
- showscale=True, colorbar=dict(title="score"),
1013
- hovertemplate="<b>%{y}</b><br>%{x}: %{z:.3f}<extra></extra>",
1014
- ))
1015
- fig.update_layout(
1016
- title=dict(text="DIRECTION QUALITY 路 Cooc &lt; Core &lt; Chem (paper 搂3.2)",
1017
- font=dict(size=14, color=GALLERY_TEXT, family="monospace")),
1018
- paper_bgcolor=GALLERY_BG, plot_bgcolor=GALLERY_BG,
1019
- height=380, margin=dict(l=180, r=40, t=70, b=40),
1020
- font=dict(color=GALLERY_TEXT),
1021
- xaxis=dict(tickfont=dict(color=GALLERY_TEXT, size=12)),
1022
- yaxis=dict(tickfont=dict(color=GALLERY_TEXT, size=11), autorange="reversed"),
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