Spaces:
Sleeping
Sleeping
Download utils/basic_info_chart.py from thinkhong/AI_Challenge2026: direct link, hf CLI and curl.
- Browser
- Download file 7.53 kB
-
https://huggingface.co/spaces/thinkhong/AI_Challenge2026/resolve/main/utils/basic_info_chart.py
- Command line
-
hf download hf://spaces/thinkhong/AI_Challenge2026/utils/basic_info_chart.py
-
curl -L -o basic_info_chart.py https://huggingface.co/spaces/thinkhong/AI_Challenge2026/resolve/main/utils/basic_info_chart.py
7.53 kB
| """๊ธฐ์ด ์ ๋ณด(ํ์/BMI/ํ๋ฆฌ๋๋ /TG/TCHL/HDL)๋ฅผ ํ ๋์ ํฌ๋ ์คํธ ํ๋กฏ ๋๋์ | |
| ์ (dot) + ๊ธฐ์ค์ (line) ์ฐจํธ๋ก ๊ทธ๋ฆฐ๋ค. ํญ๋ชฉ๋ง๋ค ์์ ํ์ ๊ธฐ์ค์ ์์ "์ ์" | |
| ๊ตฌ๊ฐ์ ํ์คํ ์ด๋ก ๊ตต์ ์ ์ผ๋ก ๊ฐ์กฐํ๊ณ , ์ด ์ฌ๋์ ์ค์ ๊ฐ์ ๊ทธ ๊ตฌ๊ฐ์ ์ํ | |
| ์ํ์ ๋ง๋ ํ์คํ ํค ์ ์ผ๋ก ํ์ํ๋ค(์๋ง์ผ๋ก ํ๋จํ์ง ์๋๋ก ๊ฐ๊ณผ ๊ตฌ๊ฐ | |
| ์ด๋ฆ์ ํญ์ ์ ์์ ๊ธ์๋ก๋ ๊ฐ์ด ๋ณด์ฌ์ค๋ค). ๊ตฌ๊ฐ ๊ฒฝ๊ณ๊ฐ์ | |
| utils/clinical_reference.CHART_ZONES(=classify_*()์ ๊ฐ์ ๊ฒฝ๊ณ)๋ฅผ ๊ทธ๋๋ก | |
| ์ด๋ค. | |
| ๋ฆฌํฌํธ๋ Gradio์ gr.Markdown ์ปดํฌ๋ํธ ํ๋์ ๋ฌธ์์ด๋ก ๋ค์ด๊ฐ๋ฏ๋ก, ์ | |
| ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๋ ๋์ PNG๋ฅผ base64๋ก ์ธ์ฝ๋ฉํด ๋งํฌ๋ค์ด ์ด๋ฏธ์ง ๋ฌธ๋ฒ | |
| (data URI)์ผ๋ก ๊ทธ ๋ฌธ์์ด ์์ ๊ทธ๋๋ก ๋ฐ์ ๋ฃ๋๋ค - app.py/report_writer.py | |
| ์ชฝ ๊ตฌ์กฐ๋ฅผ ์ ๋ฐ๊ฟ๋ ๋๋ค. | |
| """ | |
| import base64 | |
| import colorsys | |
| import io | |
| from pathlib import Path | |
| import matplotlib | |
| matplotlib.use("Agg") # Gradio ์๋ฒ ํ๋ก์ธ์ค์ ๋์คํ๋ ์ด๊ฐ ์์ | |
| import matplotlib.font_manager as fm # noqa: E402 - matplotlib.use()๊ฐ ๋จผ์ ์คํ๋ผ์ผ ํจ | |
| import matplotlib.pyplot as plt # noqa: E402 | |
| from .clinical_reference import CHART_ZONES, classify_bmi, classify_hdl, classify_waist | |
| # matplotlib ๊ธฐ๋ณธ ํฐํธ(DejaVu Sans)์ ํ๊ธ ๊ธ๋ฆฌํ๊ฐ ์์ด์ ๋ผ๋ฒจ์ด ๋ค๋ชจ(โก)๋ก | |
| # ๊นจ์ง๋ค. HF Spaces ์ปจํ ์ด๋์๋ ํ๊ธ ํฐํธ๊ฐ ๊น๋ ค์๋ค๋ ๋ณด์ฅ์ด ์์ด์, | |
| # ์ด ์ฐจํธ์ ์ค์ ๋ก ์ฐ๋ ๊ธ์๋ง ์ถ์ถํด 8.5KB๋ก ์๋ธ์ ํ ํฐํธ๋ฅผ | |
| # assets/fonts/์ ๊ฐ์ด ์ปค๋ฐํด๋๊ณ ์ฌ๊ธฐ์ ์ง์ ๋ก๋ํ๋ค(์์คํ ํฐํธ์ | |
| # ์์กดํ์ง ์์ - ์ฐจํธ ๋ผ๋ฒจ ๋ฌธ๊ตฌ๋ฅผ ๋ฐ๊พธ๋ฉด ์ด ์๋ธ์ ๋ ๋ค์ ๋ง๋ค์ด์ผ ํจ). | |
| _FONT_PATH = Path(__file__).resolve().parent.parent / "assets" / "fonts" / "ChartFontKR-Subset.otf" | |
| fm.fontManager.addfont(str(_FONT_PATH)) | |
| _FONT_FAMILY = fm.FontProperties(fname=str(_FONT_PATH)).get_name() | |
| plt.rcParams["font.family"] = _FONT_FAMILY | |
| plt.rcParams["axes.unicode_minus"] = False # ์ด ํฐํธ ์๋ธ์ ์ ์ ๋์ฝ๋ ๋ง์ด๋์ค ๊ธ๋ฆฌํ๊ฐ ์์ | |
| TEXT = "#0b0b0b" | |
| MUTED = "#8a8983" | |
| BASELINE = "#dedcd3" | |
| SURFACE = "#fcfcfb" | |
| def _pastel(hex_color: str, white_frac: float = 0.62) -> str: | |
| """status ํ๋ ํธ ์์ ํฐ์๊ณผ ์์ด ํ์คํ ํค์ผ๋ก ๋ฎ์ถ๋ค(๊ฐ์ ์์๊ตฐ์ | |
| ์ ์งํด good/warning/serious/critical์ ์๋ฏธ ์์๋ ๊ทธ๋๋ก ๋๊ณ , ์ฑ๋๋ง | |
| ๋์ ํธํ๊ฒ ๋ฎ์ถค - ์์์ ์์ ์๋ก ๊ณ ๋ฅด์ง ์๊ณ ๊ณ์ฐ์ผ๋ก ํ์์ํจ๋ค).""" | |
| r, g, b = (int(hex_color[i : i + 2], 16) / 255 for i in (1, 3, 5)) | |
| r = r + (1 - r) * white_frac | |
| g = g + (1 - g) * white_frac | |
| b = b + (1 - b) * white_frac | |
| return f"#{int(r * 255):02x}{int(g * 255):02x}{int(b * 255):02x}" | |
| def _darker_for_text(hex_color: str) -> str: | |
| """์ ์ ๋ผ๋ฒจ ๊ธ์์ฉ์ผ๋ก, ํ์คํ ๋ฐฐ๊ฒฝ์์๋ ์ฝํ๋๋ก ๊ฐ์ ์์กฐ๋ฅผ ๋๋ฌ | |
| ์ด๋ก๊ฒ ๋ง๋ ๋ค(๋ผ๋ฒจ์ ์์ด ์๋๋ผ ๊ธ์๋ก ์ํ๋ฅผ ์ ๋ฌํ๋ ๊ฒ ๋ชฉ์ ์ด๋ผ | |
| ์๊ทผํ๊ฒ๋ง - ์งํ๊ฒ ํค์๋ดค์ ๋ณด์กฐ ์ ํธ์ผ ๋ฟ).""" | |
| r, g, b = (int(hex_color[i : i + 2], 16) / 255 for i in (1, 3, 5)) | |
| h, l, s = colorsys.rgb_to_hls(r, g, b) | |
| r2, g2, b2 = colorsys.hls_to_rgb(h, max(l * 0.55, 0.28), min(s * 1.3, 1.0)) | |
| return f"#{int(r2 * 255):02x}{int(g2 * 255):02x}{int(b2 * 255):02x}" | |
| def _waist_zones(sex: float) -> tuple: | |
| cutoff = 90 if sex == 1 else 85 | |
| return (max(0, cutoff - 30), cutoff + 30, [ | |
| (max(0, cutoff - 30), cutoff, "#0ca30c", "์ ์"), | |
| (cutoff, cutoff + 30, "#d03b3b", "๋ณต๋ถ๋น๋ง"), | |
| ]) | |
| def _hdl_zones(sex: float) -> tuple: | |
| cutoff = 40 if sex == 1 else 50 | |
| return (20, 90, [ | |
| (20, cutoff, "#ec835a", "๋ฎ์(์ํ์ธ์)"), | |
| (cutoff, 60, "#0ca30c", "์ ์"), | |
| (60, 90, "#0ca30c", "๋์(๋ณดํธ์ธ์)"), | |
| ]) | |
| def _build_rows(input_values: dict) -> list: | |
| """๋ฐํ: [(๋ผ๋ฒจ, ๋จ์, ๊ฐ, (lo,hi,zones)), ...] - ๊ฐ์ด ์๋ ํญ๋ชฉ๋ง.""" | |
| rows = [] | |
| sex = input_values.get("SEX") | |
| sbp = input_values.get("SBP") | |
| if sbp is not None: | |
| rows.append(("์์ถ๊ธฐ ํ์", "mmHg", sbp, CHART_ZONES["SBP"])) | |
| dbp = input_values.get("DBP") | |
| if dbp is not None: | |
| rows.append(("์ด์๊ธฐ ํ์", "mmHg", dbp, CHART_ZONES["DBP"])) | |
| height, weight = input_values.get("HEIGHT"), input_values.get("WEIGHT") | |
| if height is not None and weight is not None: | |
| bmi, _ = classify_bmi(height, weight) | |
| rows.append(("BMI", "kg/mยฒ", bmi, CHART_ZONES["BMI"])) | |
| waist = input_values.get("WAIST") | |
| if waist is not None and sex is not None: | |
| rows.append(("ํ๋ฆฌ๋๋ ", "cm", waist, _waist_zones(sex))) | |
| tg = input_values.get("TG") | |
| if tg is not None: | |
| rows.append(("์ค์ฑ์ง๋ฐฉ", "mg/dL", tg, CHART_ZONES["TG"])) | |
| tchl = input_values.get("TCHL") | |
| if tchl is not None: | |
| rows.append(("์ด์ฝ๋ ์คํ ๋กค", "mg/dL", tchl, CHART_ZONES["TCHL"])) | |
| hdl = input_values.get("HDL") | |
| if hdl is not None and sex is not None: | |
| rows.append(("HDL", "mg/dL", hdl, _hdl_zones(sex))) | |
| return rows | |
| def _zone_for_value(value: float, zones: list) -> tuple: | |
| """value๊ฐ ์ํ (color, name)์ ์ฐพ๋๋ค. ๋ชป ์ฐพ์ผ๋ฉด(๋ฒ์ ๋ฐ) ๊ฐ์ฅ ๊ฐ๊น์ด | |
| ๊ตฌ๊ฐ์ผ๋ก ๋์ฒด.""" | |
| for z_lo, z_hi, color, name in zones: | |
| if z_lo <= value < z_hi: | |
| return color, name | |
| return zones[0][2], zones[0][3] if value < zones[0][0] else (zones[-1][2], zones[-1][3]) | |
| def render_basic_info_chart_markdown(input_values: dict) -> str: | |
| """๊ธฐ์ด ์ ๋ณด ์ฐจํธ๋ฅผ base64 PNG data URI๋ฅผ ํ์ ๋งํฌ๋ค์ด ์ด๋ฏธ์ง ๋ฌธ์์ด๋ก | |
| ๋ฐํํ๋ค. ํ์ํ ํญ๋ชฉ์ด ํ๋๋ ์์ผ๋ฉด ๋น ๋ฌธ์์ด์ ๋ฐํํ๋ค.""" | |
| rows = _build_rows(input_values) | |
| if not rows: | |
| return "" | |
| fig, axes = plt.subplots(len(rows), 1, figsize=(6.8, 0.72 * len(rows) + 0.3), facecolor=SURFACE) | |
| if len(rows) == 1: | |
| axes = [axes] | |
| for ax, (label, unit, value, (lo, hi, zones)) in zip(axes, rows): | |
| ax.set_facecolor(SURFACE) | |
| # ๊ธฐ์ค์ (์์ ํ์) - ์ด ํญ๋ชฉ์ด ์์ ์ ์๋ ์ ์ฒด ๋ฒ์ | |
| ax.hlines(0, lo, hi, color=BASELINE, linewidth=2, zorder=1, capstyle="round") | |
| v = min(max(value, lo), hi) | |
| zone_color, zone_name = _zone_for_value(value, zones) | |
| dot_color = _pastel(zone_color, 0.45) | |
| label_color = _darker_for_text(zone_color) | |
| ax.plot( | |
| v, 0, marker="o", markersize=13, color=dot_color, markeredgecolor="white", | |
| markeredgewidth=1.6, zorder=5, clip_on=False, | |
| ) | |
| ax.text( | |
| v, 0.4, f"{value:g}{unit}", ha="center", va="bottom", fontsize=9.5, | |
| color=TEXT, fontweight="bold", zorder=6, | |
| ) | |
| ax.text(v, -0.42, zone_name, ha="center", va="top", fontsize=8, color=label_color, zorder=6) | |
| ax.set_xlim(lo, hi) | |
| ax.set_ylim(-0.65, 0.65) | |
| ax.set_yticks([]) | |
| ax.set_xticks([]) | |
| for spine in ax.spines.values(): | |
| spine.set_visible(False) | |
| ax.text(lo, 0, f"{label} ", ha="right", va="center", fontsize=9.5, color=TEXT, fontweight="medium") | |
| fig.tight_layout(h_pad=1.7) | |
| buf = io.BytesIO() | |
| fig.savefig(buf, format="png", dpi=160, facecolor=SURFACE, bbox_inches="tight") | |
| plt.close(fig) | |
| encoded = base64.b64encode(buf.getvalue()).decode("ascii") | |
| return f"" | |