Spaces:
Running
Running
Revert to light theme with mint accent only. White plot backgrounds. Keep sibling cards visible.
Browse files
app.py
CHANGED
|
@@ -34,16 +34,16 @@ KAIKAKU_MINT_BRIGHT = "#D8F0E5"
|
|
| 34 |
KAIKAKU_TEXT = "#E8F4F1"
|
| 35 |
KAIKAKU_MUTED = "#7AA8A2"
|
| 36 |
|
| 37 |
-
#
|
| 38 |
plt.rcParams.update({
|
| 39 |
-
"figure.facecolor":
|
| 40 |
-
"axes.facecolor":
|
| 41 |
-
"axes.edgecolor":
|
| 42 |
-
"axes.labelcolor":
|
| 43 |
-
"xtick.color":
|
| 44 |
-
"ytick.color":
|
| 45 |
-
"text.color":
|
| 46 |
-
"savefig.facecolor":
|
| 47 |
})
|
| 48 |
|
| 49 |
MODELS = {
|
|
@@ -60,14 +60,14 @@ NAMES_BY_IDX: list[str] = _lab["names"]
|
|
| 60 |
FOOD_GROUPS: list[str] = _lab["food_groups"]
|
| 61 |
|
| 62 |
FG_COLORS = {
|
| 63 |
-
"Vegetable": "#
|
| 64 |
-
"Fruit": "#
|
| 65 |
-
"Grain": "#
|
| 66 |
-
"Dairy": "#
|
| 67 |
-
"Spice": "#
|
| 68 |
-
"Pantry": "#
|
| 69 |
-
"Beverage": "#
|
| 70 |
-
"Other": "#
|
| 71 |
}
|
| 72 |
|
| 73 |
# Sanity-check log on import so Space logs show whether assets loaded
|
|
@@ -127,11 +127,9 @@ def _basket_heatmap(m, basket):
|
|
| 127 |
fig, ax = plt.subplots(figsize=(6, 5))
|
| 128 |
if len(valid) < 2:
|
| 129 |
ax.text(0.5, 0.5, "Add 2+ ingredients to see pairwise cosines",
|
| 130 |
-
ha="center", va="center", fontsize=13, color=
|
| 131 |
transform=ax.transAxes)
|
| 132 |
-
ax.set_facecolor(KAIKAKU_DARK)
|
| 133 |
ax.axis("off")
|
| 134 |
-
fig.patch.set_facecolor(KAIKAKU_DARK)
|
| 135 |
plt.tight_layout()
|
| 136 |
return fig
|
| 137 |
idxs = [m.vocab[n] for n in valid]
|
|
@@ -140,20 +138,16 @@ def _basket_heatmap(m, basket):
|
|
| 140 |
im = ax.imshow(sim, cmap="viridis", vmin=-0.2, vmax=1.0, aspect="auto")
|
| 141 |
ax.set_xticks(range(len(valid)))
|
| 142 |
ax.set_yticks(range(len(valid)))
|
| 143 |
-
ax.set_xticklabels(valid, rotation=35, ha="right"
|
| 144 |
-
ax.set_yticklabels(valid
|
| 145 |
for i in range(len(valid)):
|
| 146 |
for j in range(len(valid)):
|
| 147 |
v = float(sim[i, j])
|
| 148 |
color = "white" if v < 0.55 else "black"
|
| 149 |
ax.text(j, i, f"{v:.2f}", ha="center", va="center", fontsize=10, color=color)
|
| 150 |
cb = plt.colorbar(im, ax=ax)
|
| 151 |
-
cb.
|
| 152 |
-
|
| 153 |
-
cb.set_label("cosine", color=KAIKAKU_TEXT)
|
| 154 |
-
ax.set_title("Pairwise cosine within the basket", color=KAIKAKU_TEXT, fontsize=12)
|
| 155 |
-
ax.set_facecolor(KAIKAKU_DARK)
|
| 156 |
-
fig.patch.set_facecolor(KAIKAKU_DARK)
|
| 157 |
plt.tight_layout()
|
| 158 |
return fig
|
| 159 |
|
|
@@ -215,35 +209,26 @@ def umap_view(sibling, basket, show_neighbours, k, three_d=False):
|
|
| 215 |
showlegend=False,
|
| 216 |
))
|
| 217 |
|
| 218 |
-
# Neighbour highlights (
|
| 219 |
if neighbour_set:
|
| 220 |
ni = [i for i in range(n) if NAMES_BY_IDX[i] in neighbour_set]
|
| 221 |
nx = [float(coords2[i, 0]) for i in ni]
|
| 222 |
ny = [float(coords2[i, 1]) for i in ni]
|
| 223 |
nz = [float(z[i]) for i in ni] if three_d else None
|
| 224 |
nlabels = [NAMES_BY_IDX[i] for i in ni]
|
| 225 |
-
marker = dict(size=
|
| 226 |
-
color="#
|
| 227 |
opacity=0.95,
|
| 228 |
-
line=dict(color=
|
| 229 |
-
if three_d
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
fig.add_trace(go.Scatter(
|
| 239 |
-
x=nx, y=ny, mode="markers+text",
|
| 240 |
-
marker=marker, text=nlabels, textposition="top center",
|
| 241 |
-
textfont=dict(color=KAIKAKU_TEXT, size=10),
|
| 242 |
-
hovertemplate="<b>%{text}</b> (neighbour)<extra></extra>",
|
| 243 |
-
name=f"top-{k} neighbours",
|
| 244 |
-
))
|
| 245 |
-
|
| 246 |
-
# Basket highlights (mint star)
|
| 247 |
if basket_idxs:
|
| 248 |
bx = [float(coords2[i, 0]) for i in basket_idxs]
|
| 249 |
by = [float(coords2[i, 1]) for i in basket_idxs]
|
|
@@ -252,47 +237,31 @@ def umap_view(sibling, basket, show_neighbours, k, three_d=False):
|
|
| 252 |
marker = dict(size=18 if not three_d else 9,
|
| 253 |
color=KAIKAKU_MINT,
|
| 254 |
symbol="star" if not three_d else "diamond",
|
| 255 |
-
line=dict(color=
|
| 256 |
-
if three_d
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
))
|
| 263 |
-
else:
|
| 264 |
-
fig.add_trace(go.Scatter(
|
| 265 |
-
x=bx, y=by, mode="markers+text",
|
| 266 |
-
marker=marker, text=blabels, textposition="top center",
|
| 267 |
-
textfont=dict(color=KAIKAKU_MINT_BRIGHT, size=13),
|
| 268 |
-
hovertemplate="<b>%{text}</b> (basket)<extra></extra>", name="basket",
|
| 269 |
-
))
|
| 270 |
|
| 271 |
title_suffix = " (3D)" if three_d else ""
|
| 272 |
fig.update_layout(
|
| 273 |
title=dict(text=f"UMAP of Epicure-{sibling.capitalize()}{title_suffix} - {n} ingredients",
|
| 274 |
-
font=dict(
|
| 275 |
height=650, margin=dict(l=40, r=40, t=60, b=40),
|
| 276 |
-
paper_bgcolor=
|
| 277 |
-
font=dict(
|
| 278 |
-
legend=dict(orientation="v", x=1.02, y=1,
|
| 279 |
-
bgcolor="rgba(26,61,63,0.85)",
|
| 280 |
-
bordercolor=KAIKAKU_EDGE,
|
| 281 |
-
font=dict(color=KAIKAKU_TEXT, size=11)),
|
| 282 |
)
|
| 283 |
if not three_d:
|
| 284 |
-
fig.update_xaxes(showgrid=True, gridcolor=
|
| 285 |
-
|
| 286 |
-
tickfont=dict(color=KAIKAKU_TEXT))
|
| 287 |
-
fig.update_yaxes(showgrid=True, gridcolor=KAIKAKU_EDGE, zeroline=False,
|
| 288 |
-
title=dict(text="UMAP 2", font=dict(color=KAIKAKU_TEXT)),
|
| 289 |
-
tickfont=dict(color=KAIKAKU_TEXT))
|
| 290 |
else:
|
| 291 |
fig.update_layout(scene=dict(
|
| 292 |
-
xaxis=dict(title="UMAP 1"
|
| 293 |
-
yaxis=dict(title="UMAP 2"
|
| 294 |
-
zaxis=dict(title="PC1 (z)"
|
| 295 |
-
bgcolor=
|
| 296 |
))
|
| 297 |
return fig
|
| 298 |
|
|
@@ -456,78 +425,48 @@ def parse_fridge(raw_text, sibling, min_score=70):
|
|
| 456 |
|
| 457 |
# ===== UI =====
|
| 458 |
|
| 459 |
-
#
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
THEME = gr.themes.Base(
|
| 468 |
-
primary_hue=mint_palette,
|
| 469 |
-
secondary_hue=mint_palette,
|
| 470 |
neutral_hue="slate",
|
| 471 |
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
|
| 472 |
-
).set(
|
| 473 |
-
body_background_fill=KAIKAKU_DARK,
|
| 474 |
-
body_text_color=KAIKAKU_TEXT,
|
| 475 |
-
background_fill_primary=KAIKAKU_DARK,
|
| 476 |
-
background_fill_secondary=KAIKAKU_MID,
|
| 477 |
-
block_background_fill=KAIKAKU_MID,
|
| 478 |
-
block_border_color=KAIKAKU_EDGE,
|
| 479 |
-
block_border_width="1px",
|
| 480 |
-
block_label_background_fill=KAIKAKU_MID,
|
| 481 |
-
block_label_text_color=KAIKAKU_MINT,
|
| 482 |
-
block_title_text_color=KAIKAKU_MINT,
|
| 483 |
-
button_primary_background_fill=KAIKAKU_MINT,
|
| 484 |
-
button_primary_background_fill_hover=KAIKAKU_MINT_BRIGHT,
|
| 485 |
-
button_primary_text_color=KAIKAKU_DARK,
|
| 486 |
-
button_primary_border_color=KAIKAKU_MINT,
|
| 487 |
-
button_secondary_background_fill=KAIKAKU_MID,
|
| 488 |
-
button_secondary_background_fill_hover=KAIKAKU_EDGE,
|
| 489 |
-
button_secondary_text_color=KAIKAKU_MINT,
|
| 490 |
-
border_color_primary=KAIKAKU_EDGE,
|
| 491 |
-
input_background_fill=KAIKAKU_DEEP,
|
| 492 |
-
input_border_color=KAIKAKU_EDGE,
|
| 493 |
-
input_placeholder_color=KAIKAKU_MUTED,
|
| 494 |
-
checkbox_background_color=KAIKAKU_DEEP,
|
| 495 |
-
checkbox_background_color_selected=KAIKAKU_MINT,
|
| 496 |
-
slider_color=KAIKAKU_MINT,
|
| 497 |
-
color_accent=KAIKAKU_MINT,
|
| 498 |
-
color_accent_soft=KAIKAKU_MID,
|
| 499 |
)
|
| 500 |
|
| 501 |
CUSTOM_CSS = f"""
|
| 502 |
-
.gradio-container {{max-width: 1280px !important;
|
| 503 |
footer {{visibility: hidden;}}
|
| 504 |
-
h1, h2, h3 {{color: {KAIKAKU_MINT};}}
|
| 505 |
-
a {{color: {KAIKAKU_MINT};}}
|
| 506 |
.sibling-card {{
|
| 507 |
-
|
| 508 |
-
|
|
|
|
|
|
|
|
|
|
| 509 |
}}
|
| 510 |
-
.sibling-name {{color: {
|
| 511 |
-
.sibling-desc {{color:
|
| 512 |
-
.gr-dataframe table {{color: {KAIKAKU_TEXT} !important;}}
|
| 513 |
"""
|
| 514 |
|
| 515 |
# Precompute initial figures so plots are populated on first page load
|
| 516 |
_INITIAL_UMAP = umap_view("chem", ["chicken","lemon","garlic"], True, 8, three_d=False)
|
| 517 |
_INITIAL_HEATMAP = _basket_heatmap(MODELS["chem"], ["chicken","lemon","garlic"])
|
| 518 |
|
| 519 |
-
SIBLING_CARDS =
|
| 520 |
<div class="sibling-card">
|
| 521 |
-
<
|
| 522 |
-
<
|
| 523 |
</div>
|
| 524 |
<div class="sibling-card">
|
| 525 |
-
<
|
| 526 |
-
<
|
| 527 |
</div>
|
| 528 |
<div class="sibling-card">
|
| 529 |
-
<
|
| 530 |
-
<
|
| 531 |
</div>
|
| 532 |
"""
|
| 533 |
|
|
|
|
| 34 |
KAIKAKU_TEXT = "#E8F4F1"
|
| 35 |
KAIKAKU_MUTED = "#7AA8A2"
|
| 36 |
|
| 37 |
+
# Light matplotlib defaults; mint is an accent only
|
| 38 |
plt.rcParams.update({
|
| 39 |
+
"figure.facecolor": "#ffffff",
|
| 40 |
+
"axes.facecolor": "#ffffff",
|
| 41 |
+
"axes.edgecolor": "#cccccc",
|
| 42 |
+
"axes.labelcolor": "#111111",
|
| 43 |
+
"xtick.color": "#333333",
|
| 44 |
+
"ytick.color": "#333333",
|
| 45 |
+
"text.color": "#111111",
|
| 46 |
+
"savefig.facecolor": "#ffffff",
|
| 47 |
})
|
| 48 |
|
| 49 |
MODELS = {
|
|
|
|
| 60 |
FOOD_GROUPS: list[str] = _lab["food_groups"]
|
| 61 |
|
| 62 |
FG_COLORS = {
|
| 63 |
+
"Vegetable": "#2ca02c",
|
| 64 |
+
"Fruit": "#e377c2",
|
| 65 |
+
"Grain": "#bcbd22",
|
| 66 |
+
"Dairy": "#17becf",
|
| 67 |
+
"Spice": "#d62728",
|
| 68 |
+
"Pantry": "#ff7f0e",
|
| 69 |
+
"Beverage": "#9467bd",
|
| 70 |
+
"Other": "#cccccc",
|
| 71 |
}
|
| 72 |
|
| 73 |
# Sanity-check log on import so Space logs show whether assets loaded
|
|
|
|
| 127 |
fig, ax = plt.subplots(figsize=(6, 5))
|
| 128 |
if len(valid) < 2:
|
| 129 |
ax.text(0.5, 0.5, "Add 2+ ingredients to see pairwise cosines",
|
| 130 |
+
ha="center", va="center", fontsize=13, color="#888",
|
| 131 |
transform=ax.transAxes)
|
|
|
|
| 132 |
ax.axis("off")
|
|
|
|
| 133 |
plt.tight_layout()
|
| 134 |
return fig
|
| 135 |
idxs = [m.vocab[n] for n in valid]
|
|
|
|
| 138 |
im = ax.imshow(sim, cmap="viridis", vmin=-0.2, vmax=1.0, aspect="auto")
|
| 139 |
ax.set_xticks(range(len(valid)))
|
| 140 |
ax.set_yticks(range(len(valid)))
|
| 141 |
+
ax.set_xticklabels(valid, rotation=35, ha="right")
|
| 142 |
+
ax.set_yticklabels(valid)
|
| 143 |
for i in range(len(valid)):
|
| 144 |
for j in range(len(valid)):
|
| 145 |
v = float(sim[i, j])
|
| 146 |
color = "white" if v < 0.55 else "black"
|
| 147 |
ax.text(j, i, f"{v:.2f}", ha="center", va="center", fontsize=10, color=color)
|
| 148 |
cb = plt.colorbar(im, ax=ax)
|
| 149 |
+
cb.set_label("cosine")
|
| 150 |
+
ax.set_title("Pairwise cosine within the basket", fontsize=12)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
plt.tight_layout()
|
| 152 |
return fig
|
| 153 |
|
|
|
|
| 209 |
showlegend=False,
|
| 210 |
))
|
| 211 |
|
| 212 |
+
# Neighbour highlights (amber)
|
| 213 |
if neighbour_set:
|
| 214 |
ni = [i for i in range(n) if NAMES_BY_IDX[i] in neighbour_set]
|
| 215 |
nx = [float(coords2[i, 0]) for i in ni]
|
| 216 |
ny = [float(coords2[i, 1]) for i in ni]
|
| 217 |
nz = [float(z[i]) for i in ni] if three_d else None
|
| 218 |
nlabels = [NAMES_BY_IDX[i] for i in ni]
|
| 219 |
+
marker = dict(size=11 if not three_d else 6,
|
| 220 |
+
color="#ff8800",
|
| 221 |
opacity=0.95,
|
| 222 |
+
line=dict(color="#ffffff", width=1.2))
|
| 223 |
+
TR = go.Scatter3d if three_d else go.Scatter
|
| 224 |
+
kwargs = dict(mode="markers+text",
|
| 225 |
+
marker=marker, text=nlabels, textposition="top center",
|
| 226 |
+
textfont=dict(size=10),
|
| 227 |
+
hovertemplate="<b>%{text}</b> (neighbour)<extra></extra>",
|
| 228 |
+
name=f"top-{k} neighbours")
|
| 229 |
+
fig.add_trace(TR(x=nx, y=ny, z=nz, **kwargs) if three_d else TR(x=nx, y=ny, **kwargs))
|
| 230 |
+
|
| 231 |
+
# Basket highlights (mint star, accent only)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
if basket_idxs:
|
| 233 |
bx = [float(coords2[i, 0]) for i in basket_idxs]
|
| 234 |
by = [float(coords2[i, 1]) for i in basket_idxs]
|
|
|
|
| 237 |
marker = dict(size=18 if not three_d else 9,
|
| 238 |
color=KAIKAKU_MINT,
|
| 239 |
symbol="star" if not three_d else "diamond",
|
| 240 |
+
line=dict(color="#111111", width=1.5))
|
| 241 |
+
TR = go.Scatter3d if three_d else go.Scatter
|
| 242 |
+
kwargs = dict(mode="markers+text",
|
| 243 |
+
marker=marker, text=blabels, textposition="top center",
|
| 244 |
+
textfont=dict(size=13, color="#111111"),
|
| 245 |
+
hovertemplate="<b>%{text}</b> (basket)<extra></extra>", name="basket")
|
| 246 |
+
fig.add_trace(TR(x=bx, y=by, z=bz, **kwargs) if three_d else TR(x=bx, y=by, **kwargs))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
|
| 248 |
title_suffix = " (3D)" if three_d else ""
|
| 249 |
fig.update_layout(
|
| 250 |
title=dict(text=f"UMAP of Epicure-{sibling.capitalize()}{title_suffix} - {n} ingredients",
|
| 251 |
+
font=dict(size=15)),
|
| 252 |
height=650, margin=dict(l=40, r=40, t=60, b=40),
|
| 253 |
+
paper_bgcolor="#ffffff", plot_bgcolor="#ffffff",
|
| 254 |
+
legend=dict(orientation="v", x=1.02, y=1, font=dict(size=11)),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
)
|
| 256 |
if not three_d:
|
| 257 |
+
fig.update_xaxes(showgrid=True, gridcolor="#eeeeee", zeroline=False, title="UMAP 1")
|
| 258 |
+
fig.update_yaxes(showgrid=True, gridcolor="#eeeeee", zeroline=False, title="UMAP 2")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
else:
|
| 260 |
fig.update_layout(scene=dict(
|
| 261 |
+
xaxis=dict(title="UMAP 1"),
|
| 262 |
+
yaxis=dict(title="UMAP 2"),
|
| 263 |
+
zaxis=dict(title="PC1 (z)"),
|
| 264 |
+
bgcolor="#ffffff",
|
| 265 |
))
|
| 266 |
return fig
|
| 267 |
|
|
|
|
| 425 |
|
| 426 |
# ===== UI =====
|
| 427 |
|
| 428 |
+
# Simple default light theme with mint as the primary accent only
|
| 429 |
+
THEME = gr.themes.Soft(
|
| 430 |
+
primary_hue=gr.themes.Color(
|
| 431 |
+
c50="#F0FAF6", c100=KAIKAKU_MINT_BRIGHT, c200=KAIKAKU_MINT,
|
| 432 |
+
c300="#92DCBE", c400="#6FD2AA", c500=KAIKAKU_MINT,
|
| 433 |
+
c600="#3FA579", c700="#32835C", c800="#1F5A3F",
|
| 434 |
+
c900=KAIKAKU_DARK, c950=KAIKAKU_DEEP,
|
| 435 |
+
),
|
|
|
|
|
|
|
|
|
|
| 436 |
neutral_hue="slate",
|
| 437 |
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
)
|
| 439 |
|
| 440 |
CUSTOM_CSS = f"""
|
| 441 |
+
.gradio-container {{max-width: 1280px !important;}}
|
| 442 |
footer {{visibility: hidden;}}
|
|
|
|
|
|
|
| 443 |
.sibling-card {{
|
| 444 |
+
border-left: 3px solid {KAIKAKU_MINT};
|
| 445 |
+
padding: 10px 14px;
|
| 446 |
+
margin: 6px 0;
|
| 447 |
+
background: #fafafa;
|
| 448 |
+
border-radius: 4px;
|
| 449 |
}}
|
| 450 |
+
.sibling-name {{color: {KAIKAKU_DARK}; font-weight: 700;}}
|
| 451 |
+
.sibling-desc {{color: #333; font-size: 0.95em; line-height: 1.5;}}
|
|
|
|
| 452 |
"""
|
| 453 |
|
| 454 |
# Precompute initial figures so plots are populated on first page load
|
| 455 |
_INITIAL_UMAP = umap_view("chem", ["chicken","lemon","garlic"], True, 8, three_d=False)
|
| 456 |
_INITIAL_HEATMAP = _basket_heatmap(MODELS["chem"], ["chicken","lemon","garlic"])
|
| 457 |
|
| 458 |
+
SIBLING_CARDS = """
|
| 459 |
<div class="sibling-card">
|
| 460 |
+
<div class="sibling-name">Cooc - recipe-context only</div>
|
| 461 |
+
<div class="sibling-desc">Walks recipe co-occurrence (NPMI graph) only. Neighbours are recipe <em>companions</em>: things that get cooked with the seed. Isotropic geometry (PR=173.6 of 300). Best for "what else do I cook with X".</div>
|
| 462 |
</div>
|
| 463 |
<div class="sibling-card">
|
| 464 |
+
<div class="sibling-name">Core - blended (the middle ground)</div>
|
| 465 |
+
<div class="sibling-desc">Typed FlavorDB compound walks blended with injected I-I walks at ii_repeat=10. Concentrated geometry (PR=94.2), tightest emergent modes. Chemistry-aware but keeps recipe context.</div>
|
| 466 |
</div>
|
| 467 |
<div class="sibling-card">
|
| 468 |
+
<div class="sibling-name">Chem - chemistry only</div>
|
| 469 |
+
<div class="sibling-desc">Typed FlavorDB compound metapaths only (ii_repeat=0). Neighbours are flavour-profile <em>peers</em>: things that share aroma chemistry with the seed. Best supervised-direction recovery; cuisine Cohen's d = 3.07 across 8 macro-regions.</div>
|
| 470 |
</div>
|
| 471 |
"""
|
| 472 |
|