PolySen_plus / app.py
ksmaru's picture
Update app.py
8472522 verified
Raw
History Blame Contribute Delete
10.7 kB
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())