Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import shap | |
| import matplotlib.pyplot as plt | |
| import pandas as pd | |
| import numpy as np | |
| import io | |
| import joblib | |
| import os | |
| from PIL import Image | |
| from fpdf import FPDF | |
| from fpdf.enums import XPos, YPos | |
| import umap | |
| # Безопасный импорт HDBSCAN | |
| try: | |
| import hdbscan | |
| except ImportError: | |
| try: | |
| from sklearn.cluster import HDBSCAN as hdbscan | |
| except ImportError: | |
| hdbscan = None | |
| # 1. Настройки среды и загрузка моделей | |
| plt.switch_backend('Agg') | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| def load_model(file_name): | |
| path = os.path.join(BASE_DIR, file_name) | |
| if not os.path.exists(path): | |
| path = os.path.join(BASE_DIR, 'models', file_name) | |
| try: | |
| return joblib.load(path) | |
| except: | |
| return None | |
| # Загрузка компонентов (названия файлов из вашего репозитория) | |
| xgb_reg = load_model('xgb_reg_model.joblib') | |
| xgb_clf = load_model('xgb_clf_model.joblib') | |
| umap_reducer = load_model('umap_reducer.joblib') | |
| hdbscan_model = load_model('hdbscan_clusterer.joblib') | |
| train_coords = load_model('train_umap_coords.joblib') | |
| train_labels = load_model('train_clusters.joblib') | |
| # Если файла с признаками нет, мы создадим его структуру из модели | |
| train_features = load_model('train_data_for_umap.joblib') | |
| # Словарь для перевода признаков в SHAP | |
| rename_dict = { | |
| 'RIDAGEYR': 'Возраст', 'RIAGENDR': 'Пол', 'BMXBMI': 'ИМТ', | |
| 'BMXWAIST': 'Талия', 'BPXSY1': 'Сист. АД', 'BPXDI1': 'Диаст. АД', | |
| 'LBXGH': 'Гликированный гемоглобин', 'LBDTCSI': 'Общий холестерин', | |
| 'LBDHDD': 'ЛПВП', 'LBXTR': 'Триглицериды', 'LBXCRP': 'СРБ', | |
| 'PHQ9_score': 'Депрессия (PHQ-9)', 'LBXBPB': 'Свинец', | |
| 'LBXBCD': 'Кадмий', 'LBXTHG': 'Ртуть', 'URXPHL': 'Фталаты', | |
| 'LBXIN': 'Инсулин', 'URXMEP': 'Моноэтилфталат' | |
| } | |
| # 2. Логика PDF (Исправленная работа со шрифтами) | |
| class PDF(FPDF): | |
| def __init__(self): | |
| super().__init__() | |
| self.font_name = "DejaVu" | |
| font_path = os.path.join(BASE_DIR, "DejaVuSans.ttf") | |
| if os.path.exists(font_path): | |
| self.add_font(self.font_name, "", font_path) | |
| self.unicode_ready = True | |
| else: | |
| self.unicode_ready = False | |
| def header(self): | |
| use_font = self.font_name if self.unicode_ready else "Helvetica" | |
| self.set_font(use_font, size=14) | |
| self.cell(0, 10, text="Отчет PolySen Plus: Мониторинг здоровья", | |
| new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C') | |
| self.ln(5) | |
| def create_report(al_score, stage, recs, shap_img, umap_img): | |
| pdf = PDF() | |
| pdf.add_page() | |
| use_font = pdf.font_name if pdf.unicode_ready else "Helvetica" | |
| pdf.set_font(use_font, size=11) | |
| pdf.cell(0, 10, text=f"Индекс нагрузки (AL Score): {al_score}", new_x=XPos.LMARGIN, new_y=YPos.NEXT) | |
| pdf.cell(0, 10, text=f"Клиническая стадия: {stage}", new_x=XPos.LMARGIN, new_y=YPos.NEXT) | |
| pdf.ln(5) | |
| pdf.set_font(use_font, size=10) | |
| clean_recs = "ПЕРСОНАЛЬНЫЕ РЕКОМЕНДАЦИИ:\n" + recs.replace('###', '').replace('**', '').replace('-', '•') | |
| pdf.multi_cell(0, 6, text=clean_recs) | |
| if shap_img: | |
| shap_img.save("temp_sh.png") | |
| pdf.image("temp_sh.png", x=10, w=180) | |
| pdf.ln(5) | |
| if umap_img: | |
| umap_img.save("temp_um.png") | |
| pdf.image("temp_um.png", x=40, w=120) | |
| path = "PolySen_Health_Report.pdf" | |
| pdf.output(path) | |
| return path | |
| # 3. Функция рекомендаций | |
| def get_recommendations(al_score, stage, data): | |
| recs = [] | |
| if al_score > 1.1: | |
| recs.append("- Внимание: Повышенный биологический износ систем организма.") | |
| if data['BMXBMI'] > 27: | |
| recs.append("- Рекомендуется снижение ИМТ и контроль калорийности.") | |
| if data['BPXSY1'] > 135: | |
| recs.append("- Высокое давление: ограничьте натрий и проверьте почки.") | |
| if data['PHQ9_score'] > 10: | |
| recs.append("- Ментальное здоровье: высокий балл PHQ-9, обратитесь к специалисту.") | |
| if not recs: | |
| recs.append("- Все биомаркеры в норме. Рекомендуется плановое наблюдение.") | |
| return "\n".join(recs) | |
| # 4. Основной процесс | |
| def main_process(age, gender_txt, bmi, waist, sbp, dbp, gh, tc, hdl, tg, crp, phq, lead, cadmium, mercury, phthalates): | |
| try: | |
| gender = 1 if gender_txt == "Мужской" else 2 | |
| input_data = { | |
| 'RIDAGEYR': age, 'RIAGENDR': gender, 'BMXBMI': bmi, 'BMXWAIST': waist, | |
| 'BPXSY1': sbp, 'BPXDI1': dbp, 'LBXGH': gh, 'LBDTCSI': tc, | |
| 'LBDHDD': hdl, 'LBXTR': tg, 'LBXCRP': crp, 'PHQ9_score': phq, | |
| 'LBXBPB': lead, 'LBXBCD': cadmium, 'LBXTHG': mercury, 'URXPHL': phthalates | |
| } | |
| # Регрессия AL Score | |
| reg_features = xgb_reg.get_booster().feature_names | |
| df_reg = pd.DataFrame(0.0, index=[0], columns=reg_features) | |
| for c in reg_features: | |
| if c in input_data: df_reg.at[0, c] = input_data[c] | |
| al_val = float(xgb_reg.predict(df_reg)[0]) | |
| # Классификация Стадии | |
| clf_features = xgb_clf.get_booster().feature_names | |
| df_clf = pd.DataFrame(0.0, index=[0], columns=clf_features) | |
| input_data['AL_score'] = al_val | |
| for c in clf_features: | |
| if c in input_data: df_clf.at[0, c] = input_data[c] | |
| st_idx = int(xgb_clf.predict(df_clf)[0]) | |
| st_map = {0: "Норма", 1: "Начальная", 2: "Выраженная", 3: "Критическая"} | |
| res_stage = st_map.get(st_idx, f"Стадия {st_idx}") | |
| # SHAP | |
| df_shap = df_reg.rename(columns=rename_dict) | |
| explainer = shap.TreeExplainer(xgb_reg) | |
| shap_vals = explainer.shap_values(df_reg) | |
| plt.figure(figsize=(10, 3)) | |
| shap.force_plot(explainer.expected_value, shap_vals[0], df_shap.iloc[0], matplotlib=True, show=False) | |
| buf_s = io.BytesIO() | |
| plt.savefig(buf_s, format='png', bbox_inches='tight', dpi=120) | |
| img_shap = Image.open(buf_s) | |
| plt.close() | |
| # UMAP (Исправленный блок) | |
| img_umap = None | |
| phenotype = "Фенотип не определен" | |
| if umap_reducer is not None: | |
| try: | |
| # Автоматически определяем нужные колонки из модели, если train_features не загружен | |
| expected_cols = getattr(umap_reducer, 'feature_names_in_', reg_features) | |
| u_in = pd.DataFrame(0.0, index=[0], columns=expected_cols) | |
| for col in u_in.columns: | |
| if col in input_data: u_in.at[0, col] = input_data[col] | |
| # Координаты | |
| coords = umap_reducer.transform(u_in)[0] | |
| # Отрисовка | |
| plt.figure(figsize=(6, 4)) | |
| if train_coords is not None: | |
| plt.scatter(train_coords[:, 0], train_coords[:, 1], c=train_labels, cmap='viridis', s=2, alpha=0.1) | |
| plt.scatter(coords[0], coords[1], color='red', marker='X', s=150, label='Вы здесь') | |
| plt.axis('off') | |
| plt.title("Позиция в популяции") | |
| buf_u = io.BytesIO() | |
| plt.savefig(buf_u, format='png', bbox_inches='tight') | |
| img_umap = Image.open(buf_u) | |
| plt.close() | |
| if hdbscan_model: | |
| pid = hdbscan_model.predict([coords])[0] | |
| phenotype = f"Фенотип здоровья №{pid}" | |
| except Exception as e: | |
| phenotype = f"Ошибка визуализации: {str(e)[:50]}" | |
| recs = get_recommendations(al_val, res_stage, input_data) | |
| pdf_path = create_report(f"{al_val:.2f}", res_stage, recs, img_shap, img_umap) | |
| return f"{al_val:.2f}", res_stage, recs, phenotype, img_shap, img_umap, pdf_path | |
| except Exception as e: | |
| return f"Ошибка: {str(e)}", "---", "---", "---", None, None, None | |
| # 5. Интерфейс | |
| with gr.Blocks(title="PolySen Plus") as demo: | |
| gr.Markdown("# 🏥 PolySen Plus: Мониторинг здоровья") | |
| with gr.Row(): | |
| with gr.Column(): | |
| age = gr.Slider(18, 100, 45, label="Возраст") | |
| gender = gr.Radio(["Мужской", "Женский"], value="Мужской", label="Пол") | |
| with gr.Row(): | |
| bmi = gr.Number(value=25.0, label="ИМТ") | |
| waist = gr.Number(value=90.0, label="Талия (см)") | |
| with gr.Row(): | |
| sbp = gr.Slider(80, 200, 120, label="Сист. АД") | |
| dbp = gr.Slider(40, 120, 80, label="Диаст. АД") | |
| with gr.Accordion("Дополнительные биомаркеры", open=False): | |
| gh, tc, hdl, tg, crp = [gr.Number(v, label=l) for v, l in [(5.5, "HbA1c"), (5.0, "Общий Холест."), (1.2, "ЛПВП"), (1.5, "ТГ"), (1.0, "СРБ")]] | |
| phq = gr.Slider(0, 27, 5, label="PHQ-9 (Депрессия)") | |
| l, c, m, p = [gr.Number(v, label=n) for v, n in [(1.0, "Свинец"), (0.3, "Кадмий"), (0.5, "Ртуть"), (5.0, "Фталаты")]] | |
| btn = gr.Button("🚀 Провести анализ", variant="primary") | |
| with gr.Column(): | |
| with gr.Row(): | |
| o1 = gr.Label(label="AL Score") | |
| o2 = gr.Label(label="Стадия") | |
| o3 = gr.Markdown(label="Рекомендации") | |
| o4 = gr.Label(label="Ваш фенотип") | |
| o5 = gr.Image(label="Влияние факторов (SHAP)") | |
| o6 = gr.Image(label="Позиция в популяции (UMAP)") | |
| o7 = gr.File(label="📄 Скачать отчет") | |
| btn.click(main_process, | |
| inputs=[age, gender, bmi, waist, sbp, dbp, gh, tc, hdl, tg, crp, phq, l, c, m, p], | |
| outputs=[o1, o2, o3, o4, o5, o6, o7]) | |
| demo.launch(theme=gr.themes.Soft()) |