phamnguyenhuan/rapidpredict
0
1import streamlit as st2import numpy as np3 4st.set_page_config(page_title="Công Cụ Tiên Lượng mRS", page_icon="🧠")5 6text_dict = {7 "TOOL_TITLE": {"vi": "CÔNG CỤ TIÊN LƯỢNG PHỤC HỒI CHỨC NĂNG THẦN KINH (mRS)", "en": "Prognostic Tool for Neurological Functional Outcome (mRS)"},8 "TOOL_DESC_1": {"vi": "Mô hình tiên lượng phục hồi chức năng thần kinh tốt (mRS 0-2) trên bệnh nhân đột quỵ thiếu máu não cấp cửa sổ 6-24 giờ được điều trị tái thông lấy huyết khối cơ học có sử dụng phần mềm RAPID", "en": "A prognostic model for functional independence outcome (mRS 0–2) in acute ischemic stroke patients treated with thrombectomy within the 6–24 hour window using RAPID software"},9 "LANG_TITLE": {"vi": "🌐 Chọn ngôn ngữ", "en": "🌐 Choose Language"},10 "AGE": {"vi": "TUỔI - NĂM", "en": "AGE - YEARS"},11 "SEX": {"vi": "GIỚI", "en": "SEX"},12 "HYPERTENSION": {"vi": "TĂNG HUYẾT ÁP", "en": "HYPERTENSION"},13 "HYPERLIPIDEMIA": {"vi": "RỐI LOẠN LIPID MÁU", "en": "HYPERLIPIDEMIA"},14 "ONSET_TYPE_LABEL": {"vi": "KIỂU KHỞI PHÁT", "en": "TYPE OF STROKE ONSET"},15 "ONSET_HOS": {"vi": "THỜI GIAN KHỞI PHÁT ĐẾN NHẬP VIỆN - giờ", "en": "TIME FROM ONSET TO HOSPITALIZATION (hours)"},16 "ONSET_GROIN": {"vi": "THỜI GIAN KHỞI PHÁT ĐẾN ĐÂM KIM - giờ", "en": "TIME FROM ONSET TO GROIN PUNCTURE (hours)"},17 "TMAX": {"vi": "Thể tích Tmax > 4 giây (ml)", "en": "Tmax > 4 seconds (ml)"},18 "OCCLUSION": {"vi": "ĐỘNG MẠCH TẮC TRÊN DSA", "en": "OCCLUSION LOCATION"},19 "TICI": {"vi": "MỨC ĐỘ TÁI THÔNG (eTICI)", "en": "eTICI SCORE POST-INTERVENTION"},20 "NIHSS_24H": {"vi": "NIHSS 24H SAU CAN THIỆP", "en": "NIHSS AFTER 24H"},21 "SEX_CHOICES": {"vi": ["Nữ", "Nam"], "en": ["Female", "Male"]},22 "YES_NO": {"vi": ["Không", "Có"], "en": ["No", "Yes"]},23 "ONSET_TYPE": {"vi": ["Không xác định", "Rõ thời gian"], "en": ["Unclear onset", "Clear onset"]},24 "TICI_CHOICES": {"vi": ["TICI 2B-3", "TICI 0-2A"], "en": ["TICI 2B-3", "TICI 0-2A"]},25 "ARTERY_DSA": {"vi": ["ICA", "M1", "M2", "TANDEM"], "en": ["ICA", "M1", "M2", "Tandem"]},26 "RESULT_TITLE": {"vi": "KHẢ NĂNG PHỤC HỒI CHỨC NĂNG TỐT (mRS 0-2) SAU 3 THÁNG",27 "en": "PREDICTED PROBABILITY OF 3-MONTH FUNCTIONAL INDEPENDENCE (mRS 0-2)"},28 "PERCENT": {"vi": "Xác suất", "en": "Probability"}29}30 31def process_decimal_input(label, default_value_vi="12,0", default_value_en="12.0", key=None, lang="vi"):32 # Pick default value based on language33 default_value = default_value_vi if lang == "vi" else default_value_en34 input_str = st.text_input(label, value=default_value, key=key)35 36 # Normalize input: replace comma with dot for calculation37 normalized_str = input_str.replace(',', '.') if lang == "vi" else input_str.replace(',', '.')38 39 try:40 value = float(normalized_str)41 except ValueError:42 st.error("Vui lòng nhập số hợp lệ!" if lang == "vi" else "Please enter a valid number!")43 return None44 45 return value46 47def calc_logit_and_prob(48 tuoi, tha, khoiphat, nihss24h, tmax_4, ticig, gioi, rllm, 49 onset_hos, onset_punc, m1_occl, m2_occl, tandem_occl50):51 logit = (52 10.81453 - 0.066 * tuoi54 - 2.805 * tha55 + 1.405 * khoiphat56 - 0.276 * nihss24h57 - 0.005 * tmax_458 - 1.525 * ticig59 + 0.895 * gioi60 + 0.608 * rllm61 + 0.139 * onset_hos62 - 0.162 * onset_punc63 - 0.807 * m1_occl64 - 1.522 * m2_occl65 + 0.653 * tandem_occl66 )67 p = 1 / (1 + np.exp(-logit))68 return logit, p69 70if "lang" not in st.session_state:71 st.session_state.lang = "vi" # Default is Vietnamese72 73st.markdown(f"""74<div style='text-align: center; margin-top: -10px; margin-bottom: 10px;'>75 <h4>{text_dict['LANG_TITLE'][st.session_state.lang]}</h4>76</div>77""", unsafe_allow_html=True)78 79_, col1, col2, _ = st.columns([5, 3, 3, 5])80with col1:81 if st.button(" Tiếng Việt"):82 st.session_state.lang = "vi"83 st.rerun()84with col2:85 if st.button(" English"):86 st.session_state.lang = "en"87 st.rerun()88 89lang = st.session_state.lang90 91st.markdown(f"""92 <div style='text-align: center;'>93 <p style='font-size: 24px; font-weight: bold; color: darkblue;'>94 RAPID PREDICT95 </p>96 <p style='font-size: 20px; font-weight: bold; color: darkblue;'>97 {text_dict["TOOL_TITLE"][lang]}98 </p>99 <p style='font-size: 16px; color: black;'>100 {text_dict["TOOL_DESC_1"][lang]}101 </p>102 </div>103""", unsafe_allow_html=True)104 105# --- GIAO DIỆN NHẬP LIỆU ---106st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['AGE'][lang]}</strong></div>", unsafe_allow_html=True)107tuoi = st.number_input("", value=60)108 109if tuoi < 30 or tuoi > 95:110 st.warning("Tuổi phải nằm trong khoảng từ 30 - 95!")111 112st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['SEX'][lang]}</strong></div>", unsafe_allow_html=True)113gioi = st.selectbox('', text_dict["SEX_CHOICES"][lang], key="gioi_select")114 115st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['HYPERTENSION'][lang]}</strong></div>", unsafe_allow_html=True)116tha = st.selectbox('', text_dict["YES_NO"][lang], key="tha_select")117 118st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['HYPERLIPIDEMIA'][lang]}</strong></div>", unsafe_allow_html=True)119rllm = st.selectbox('', text_dict["YES_NO"][lang], key="rllm_select")120 121st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['ONSET_TYPE_LABEL'][lang]}</strong></div>", unsafe_allow_html=True)122khoiphat = st.selectbox('', text_dict["ONSET_TYPE"][lang], key="khoiphat_select")123 124st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['ONSET_HOS'][lang]}</strong></div>", unsafe_allow_html=True)125onset_hos = process_decimal_input(126 "", default_value_vi="12,0", default_value_en="12.0", key="onset_hos_input", lang=lang127) 128 129st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['ONSET_GROIN'][lang]}</strong></div>", unsafe_allow_html=True)130onset_groin = process_decimal_input(131 "", default_value_vi="13,0", default_value_en="13.0", key="onset_groin_input", lang=lang132) 133 134st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['TMAX'][lang]}</strong></div>", unsafe_allow_html=True)135tmax_4 = process_decimal_input('', default_value_vi="100,0", default_value_en="100.0", key="tmax_input", lang=lang)136 137st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['OCCLUSION'][lang]}</strong></div>", unsafe_allow_html=True)138artery_dsa_final = st.selectbox('', text_dict["ARTERY_DSA"][lang], key="artery_dsa_select")139 140st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['TICI'][lang]}</strong></div>", unsafe_allow_html=True)141ticig = st.selectbox('', text_dict["TICI_CHOICES"][lang], key="ticig_select")142 143st.markdown(f"<div style='margin-bottom: -30px'><strong>{text_dict['NIHSS_24H'][lang]}</strong></div>", unsafe_allow_html=True)144nihss_24h = st.number_input('', min_value=0, max_value=42, value=10, step=1, key="nihss_input")145 146# --- CHUYỂN ĐỔI GIÁ TRỊ INPUT ---147tha_value = 1 if tha in ["Có", "Yes"] else 0148rllm_value = 1 if rllm in ["Có", "Yes"] else 0149khoiphat_value = 1 if khoiphat in ["Không xác định", "Unclear onset"] else 0150gioi_value = 1 if gioi in ["Nam", "Male"] else 0151ticig_value = 1 if ticig in ['TICI 0-2A'] else 0152 153m1_occl = 1 if artery_dsa_final.upper() == "M1" else 0154m2_occl = 1 if artery_dsa_final.upper() == "M2" else 0155tandem_occl = 1 if artery_dsa_final.upper() == "TANDEM" else 0156 157onset_punc = onset_groin # (giống ONSETTOGROIN)158 159if None not in [onset_hos, onset_groin, tmax_4]:160 logit_value, predicted_prob = calc_logit_and_prob(161 tuoi=tuoi,162 tha=tha_value,163 khoiphat=khoiphat_value,164 nihss24h=nihss_24h,165 tmax_4=tmax_4,166 ticig=ticig_value,167 gioi=gioi_value,168 rllm=rllm_value,169 onset_hos=onset_hos,170 onset_punc=onset_punc,171 m1_occl=m1_occl,172 m2_occl=m2_occl,173 tandem_occl=tandem_occl174 )175 176 st.write(f"🧮 Computed logit value: {logit_value:.4f}")177 178 formatted_percent = f"{predicted_prob * 100:.1f}".replace('.', ',') if lang == "vi" else f"{predicted_prob * 100:.1f}"179 180 st.markdown(f"""181 <h1 style='text-align: center; font-size: 24px; color: darkblue;'>182 {text_dict["RESULT_TITLE"][lang]}183 </h1>184 <h2 style='text-align: center; font-size: 38px; color: red;'>185 {formatted_percent}%186 </h2>187 """, unsafe_allow_html=True)188 image_url = "https://raw.githubusercontent.com/huanpham1211/public_image/main/mrs_scale.jpg"189 st.image(image_url, caption="Modified Rankin Scale (mRS)", use_container_width=True)190 