From a1e1d71f66ccc1bd899f6dc305d2140542eee400 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 3 Jun 2026 09:49:45 +0800 Subject: [PATCH] Fix Chinese font rendering in Streamlit charts --- app.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index 09b1ea3..24a7cd5 100644 --- a/app.py +++ b/app.py @@ -10,7 +10,9 @@ import json from pathlib import Path from typing import Dict, List, Sequence, Tuple +import matplotlib as mpl import matplotlib.pyplot as plt +from matplotlib import font_manager import numpy as np import streamlit as st @@ -73,6 +75,46 @@ CUSTOM_CSS = """ """ +def configure_matplotlib_cjk_font() -> None: + """配置 Matplotlib 中文字体。 + + Streamlit 自身用浏览器字体渲染中文,通常没有问题;Matplotlib 图像会走 + 后端字体管理,如果默认 DejaVu Sans 不含中文字形,就会显示成方块。 + """ + + candidate_paths = [ + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc", + "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", + ] + candidate_names = [ + "Noto Sans CJK SC", + "Noto Sans CJK JP", + "Droid Sans Fallback", + "WenQuanYi Micro Hei", + "Source Han Sans SC", + "SimHei", + ] + + discovered_names: List[str] = [] + for path_str in candidate_paths: + path = Path(path_str) + if not path.exists(): + continue + try: + font_manager.fontManager.addfont(str(path)) + discovered_names.append(font_manager.FontProperties(fname=str(path)).get_name()) + except Exception: + continue + + mpl.rcParams["font.family"] = "sans-serif" + mpl.rcParams["font.sans-serif"] = [*discovered_names, *candidate_names, "DejaVu Sans"] + mpl.rcParams["axes.unicode_minus"] = False + + +configure_matplotlib_cjk_font() + + def _require_nibabel(): try: import nibabel as nib # type: ignore @@ -199,7 +241,7 @@ def render_image(image: np.ndarray, caption: str, cmap: str = "gray") -> None: ax.imshow(orient_for_display(image), cmap=cmap, interpolation="nearest") ax.set_title(caption, fontsize=10) ax.axis("off") - st.pyplot(fig, use_container_width=True) + st.pyplot(fig, width="stretch") plt.close(fig) @@ -208,7 +250,7 @@ def render_rgb(image: np.ndarray, caption: str) -> None: ax.imshow(orient_for_display(image), interpolation="nearest") ax.set_title(caption, fontsize=10) ax.axis("off") - st.pyplot(fig, use_container_width=True) + st.pyplot(fig, width="stretch") plt.close(fig) @@ -220,7 +262,7 @@ def render_heatmap_overlay(base_slice: np.ndarray, mag_slice: np.ndarray, alpha: ax.set_title(caption, fontsize=10) ax.axis("off") fig.colorbar(im, ax=ax, fraction=0.046, pad=0.02) - st.pyplot(fig, use_container_width=True) + st.pyplot(fig, width="stretch") plt.close(fig) @@ -321,7 +363,7 @@ def render_metric_charts(fixed_xyz: np.ndarray, moving_xyz: np.ndarray, warped_x axes[1].set_xlabel("Slice") axes[1].grid(alpha=0.22) axes[1].legend(frameon=False) - st.pyplot(fig, use_container_width=True) + st.pyplot(fig, width="stretch") plt.close(fig) if ddf_xyz is not None: @@ -332,7 +374,7 @@ def render_metric_charts(fixed_xyz: np.ndarray, moving_xyz: np.ndarray, warped_x ax.set_xlabel("mm") ax.set_ylabel("Voxel count") ax.grid(axis="y", alpha=0.2) - st.pyplot(fig2, use_container_width=True) + st.pyplot(fig2, width="stretch") plt.close(fig2) @@ -370,7 +412,7 @@ def main() -> None: out_dir = st.text_input("输出目录", value=str(INFERENCE_DIR)) display_max_voxels = st.slider("Web显示体素上限", 2_000_000, 30_000_000, 14_000_000, 1_000_000) - start = st.button("开始推理", type="primary", use_container_width=True) + start = st.button("开始推理", type="primary", width="stretch") if start: with st.spinner("推理运行中"): try: