File size: 14,301 Bytes
2b4d16b
cfa5c80
50334f0
 
 
2c33c1f
67c449b
1879d3b
50334f0
26c6012
2b4d16b
 
 
 
 
 
 
 
 
cfa5c80
 
 
 
 
 
 
 
d000266
 
 
 
 
 
 
8e586ae
f8ec48b
1d5d00a
f8ec48b
d000266
7f36c84
d000266
8e586ae
 
 
1d5d00a
 
2b4d16b
d000266
 
 
 
67c449b
 
 
 
 
 
1879d3b
eceb86c
1879d3b
67c449b
 
 
 
1879d3b
67c449b
 
 
1879d3b
67c449b
1879d3b
67c449b
 
 
1879d3b
 
 
 
 
 
13c64be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d000266
 
50334f0
 
99a76a3
1879d3b
50334f0
 
 
 
 
1879d3b
f94e45c
1879d3b
eceb86c
1879d3b
 
 
 
 
eceb86c
 
 
 
 
 
1879d3b
 
 
e788cbb
50334f0
 
 
e788cbb
50334f0
c171bb5
 
0bd6c76
50334f0
c171bb5
2c33c1f
c171bb5
7dfcca0
f94e45c
d5ccaaa
a5214cf
 
0bd6c76
 
ecf64e8
0bd6c76
 
cc0cacd
fdd99f9
a524bf1
1844b2a
 
a524bf1
0bd6c76
 
 
 
8b83033
0bd6c76
 
cc0cacd
a524bf1
1844b2a
 
a524bf1
0bd6c76
 
cc0cacd
 
67c449b
cc0cacd
c171bb5
67c449b
 
 
eceb86c
1879d3b
67c449b
 
7e96f5f
1879d3b
67c449b
 
 
 
f94e45c
 
50334f0
500e531
c987c13
 
 
 
 
 
500e531
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c987c13
 
 
 
 
 
b7afaa9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c987c13
 
 
 
 
 
b7afaa9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500e531
84e1d73
 
 
 
 
 
 
 
 
 
 
500e531
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
import torch.utils.data as _tud
from pytorch_tabular.tabular_datamodule import TabularDatamodule
from streamlit_option_menu import option_menu
import google.generativeai as genai
import os
from deep_translator import GoogleTranslator

translator = GoogleTranslator()

genai.configure(api_key=os.getenv("GEMINI_API_KEY"))

_OriginalDataLoader = _tud.DataLoader
class SafeDataLoader(_OriginalDataLoader):
    def __init__(self, *args, **kwargs):
        kwargs.pop("batch_size", None)
        kwargs.pop("num_workers", None)
        super().__init__(*args, **kwargs)
_tud.DataLoader = SafeDataLoader

_old_prepare = TabularDatamodule.prepare_inference_dataloader
def _patched_prepare(self, df):
    if not hasattr(self.config, "dataloader_kwargs"):
        self.config.dataloader_kwargs = {}
    return _old_prepare(self, df)

TabularDatamodule.prepare_inference_dataloader = _patched_prepare

import streamlit as st
import pandas as pd
import numpy as np
import torch
import json
import pickle
from pytorch_tabular import TabularModel
from sklearn.preprocessing import LabelEncoder
from omegaconf import OmegaConf, DictConfig
from types import SimpleNamespace

# Load model
model = TabularModel.load_model("FTTransformerModel")

dm = model.datamodule
if hasattr(dm, "label_encoder") and isinstance(dm.label_encoder, LabelEncoder):
    dm.label_encoder = [dm.label_encoder]

dm._inferred_config = SimpleNamespace(output_cardinality=[2])
    
# Load threshold
with open("FTTransformerModel/threshold.json", "r") as f:
    threshold = json.load(f)["threshold"]

# Sidebar: language selection
with st.sidebar:
    lang = st.selectbox("🌐 Language / 語言 / Bahasa", ["English", "中文", "Malay"])

# Label dictionary for multilingual UI
LABELS = {
    "user_type": {"English": "I am a...", "中文": "我的身份是...", "Malay": "Saya seorang..."},
    "user_type_options": {"English": ["Patient", "Medical Professional"], "中文": ["病患", "醫護人員"], "Malay": ["Pesakit", "Profesional Perubatan"]},
    "input_header": {"English": "Enter Health Info", "中文": "輸入健康資料", "Malay": "Masukkan Maklumat Kesihatan"},
    "age": {"English": "Patient's Age", "中文": "年齡", "Malay": "Umur"},
    "height": {"English": "Patient's Height (cm)", "中文": "身高 (cm)", "Malay": "Tinggi (cm)"},
    "weight": {"English": "Patient's Weight (kg)", "中文": "體重 (kg)", "Malay": "Berat (kg)"},
    "systolic": {"English": "Patient's Systolic BP", "中文": "收縮壓", "Malay": "Tekanan Sistolik"},
    "diastolic": {"English": "Patient's Diastolic BP", "中文": "舒張壓", "Malay": "Tekanan Diastolik"},
    "cholesterol": {"English": "Patient's Cholesterol", "中文": "膽固醇", "Malay": "Kolesterol"},
    "gluc": {"English": "Patient's Glucose", "中文": "血糖", "Malay": "Glukosa"},
    "gender": {"English": "Patient's Gender", "中文": "性別", "Malay": "Jantina"},
    "smoke": {"English": "Do patient smoke?", "中文": "是否吸菸?", "Malay": "Adakah merokok?"},
    "alco": {"English": "Do patient drink alcohol?", "中文": "是否喝酒?", "Malay": "Adakah minum alkohol?"},
    "active": {"English": "Are patient physically active?", "中文": "是否有運動習慣?", "Malay": "Adakah aktif secara fizikal?"},
    "predict": {"English": "Predict CVD Risk", "中文": "預測心血管風險", "Malay": "Ramalkan Risiko CVD"}
}

MENU = {
    "English": ["Prediction Tool", "Let's know more about CVD!"],
    "中文": ["預測工具", "了解更多心血管資訊"],
    "Malay": ["Alat Ramalan", "Ketahui lebih lanjut tentang CVD"]
}

chol_options = {
    "English": {0: "Normal", 1: "High", 2: "Very High"},
    "中文": {0: "正常", 1: "偏高", 2: "非常高"},
    "Malay": {0: "Normal", 1: "Tinggi", 2: "Sangat Tinggi"}
}
gluc_options = chol_options.copy()
gender_options = {
    "English": {0: "Female", 1: "Male"},
    "中文": {0: "女性", 1: "男性"},
    "Malay": {0: "Perempuan", 1: "Lelaki"}
}
yn_options = {
    "English": {0: "No", 1: "Yes"},
    "中文": {0: "否", 1: "是"},
    "Malay": {0: "Tidak", 1: "Ya"}
}

# User input
with st.sidebar:
    selected = option_menu(
        menu_title="Menu",
        options=MENU[lang],
        icons=["activity", "book"],
        menu_icon="cast",
        default_index=0,
    )

if selected == MENU[lang][0]:
    with st.sidebar:
        st.header(LABELS["input_header"][lang])
        user_type = st.selectbox(LABELS["user_type"][lang], LABELS["user_type_options"][lang])
        age = st.number_input(LABELS["age"][lang], min_value=18, max_value=100, value=50)
        height = st.number_input(LABELS["height"][lang], min_value=100, max_value=250, value=170)
        weight = st.number_input(LABELS["weight"][lang], min_value=30, max_value=200, value=70)
        systolic = st.number_input(LABELS["systolic"][lang], min_value=80, max_value=250, value=120)
        diastolic = st.number_input(LABELS["diastolic"][lang], min_value=40, max_value=150, value=80)
        cholesterol = st.selectbox(LABELS["cholesterol"][lang], [0, 1, 2], format_func=lambda x: chol_options[lang][x])
        gluc = st.selectbox(LABELS["gluc"][lang], [0, 1, 2], format_func=lambda x: gluc_options[lang][x])
        gender = st.selectbox(LABELS["gender"][lang], [0, 1], format_func=lambda x: gender_options[lang][x])
        smoke = st.selectbox(LABELS["smoke"][lang], [0, 1], format_func=lambda x: yn_options[lang][x])
        alco = st.selectbox(LABELS["alco"][lang], [0, 1], format_func=lambda x: yn_options[lang][x])
        active = st.selectbox(LABELS["active"][lang], [0, 1], format_func=lambda x: yn_options[lang][x])
        input_data = pd.DataFrame([{ "age": age * 365, "height": height, "weight": weight, "ap_hi": systolic, "ap_lo": diastolic, "cholesterol": cholesterol, "gluc": gluc, "gender": gender, "smoke": smoke, "alco": alco, "active": active }])
        predict_clicked = st.button(LABELS["predict"][lang])
        
    if predict_clicked:
        input_data["bmi"] = input_data["weight"] / ((input_data["height"]/100)**2)
        input_data["pulse_pressure"] = input_data["ap_hi"] - input_data["ap_lo"]
        input_data["hypertension"] = ((input_data["ap_hi"] > 140) | (input_data["ap_lo"] > 90)).astype(int)
        

        preds = model.predict(input_data)
        proba = preds["cardio_1_probability"].iloc[0]
        result = "❌ At Risk of CVD" if proba >= threshold else " ✅ Low Risk"

        with st.container():
            st.markdown("### 🧬Prediction Result🧬")
            st.markdown(f"<div style='text-align:center; font-size:26px'>{result}</div>", unsafe_allow_html=True)
            # st.write(f"(Probability: {proba:.2%}, Threshold: {threshold:.2f})")

            is_patient = user_type in ["Patient", "病患", "Pesakit"]
            
            if is_patient:
                prompt = (
                    f"As a healthcare professional, explain to the patient what this result '{result}' means "
                    f"Then, give five actionable lifestyle suggestions "
                    f"based on this result. Lastly, remind them clearly that this prediction is just a reference and "
                    f"they should consult a certified medical professional for an official diagnosis."
                    f"The title of response must be Health Advice From Your Lovely AI Friend."
                    f"Always care about the patient and be empathetic."
                    f"Add interesting emoji."
                    f"Do not use Markdown symbols such as # or **"
                    f"The punctuation format needs to follow the language standard。 For example, Chinese using Chinese punctuation format, Malay follows punctuation rules in Malay language"
                    f"Please keep the entire response within 150 words."
                 )
            else:
                prompt = (
                    f"You are a cardiovascular disease specialist reviewing a prediction result of '{result}' "
                    f"Please interpret this result for clinical use, and suggest the next diagnostic steps such as ECG, "
                    f"angiogram, or lab work. Make it clear that this model is a support tool and not a substitute for "
                    f"clinical judgment."
                    f"The title of response must be Diagnosis Support From Your Lovely AI Friend."
                    f"Keep the response in a clear and clean format."
                    f"Do not use Markdown symbols such as # or **"
                    f"The punctuation format needs to follow the language standard。 For example, Chinese using Chinese punctuation format, Malay follows punctuation rules in Malay language"
                    f"Please keep the entire response within 150 words."
                )

            model_gemini = genai.GenerativeModel(
                model_name="models/gemini-2.0-flash",
                generation_config=genai.types.GenerationConfig(temperature=0.2)
            )
            response = model_gemini.generate_content(prompt)
            original_text = response.text

            if lang == "中文":
                translated = GoogleTranslator(source="auto", target="zh-TW").translate(original_text)
                st.markdown("### 🔹 AI 健康建議")
                st.write(translated)
            elif lang == "Malay":
                translated = GoogleTranslator(source="auto", target="ms").translate(original_text)
                st.markdown("### 🔹 Nasihat Kesihatan AI")
                st.write(translated)
            else:
                st.markdown("### 🔹 AI Health Advice")
                st.write(original_text)

            

elif selected == MENU[lang][1]:
    st.title({
        "English": "Understanding Cardiovascular Disease",
        "中文": "了解心血管疾病",
        "Malay": "Memahami Penyakit Kardiovaskular"
    }[lang])
    
    st.markdown({
    "English": """
**What is Cardiovascular Disease (CVD)?**

CVDs are a group of heart and blood vessel conditions, such as coronary heart disease, stroke, and heart failure. They are the leading cause of death globally.

**Key Facts (by WHO):**
- Over 4 out of 5 CVD deaths are due to heart attacks and strokes.
- One-third of these deaths happen in people under age 70.
- Many CVDs can be prevented by addressing risk factors such as tobacco use, poor diet, obesity, inactivity, and harmful alcohol use.

**Prevention:**
- Stop smoking and avoid secondhand smoke
- Choose healthy foods with less salt and saturated fat
- Stay active at least 30 minutes a day
- Get regular health checkups
""",
    "中文": """
**什麼是心血管疾病(CVD)?**

CVD 是一類包括冠心病、中風、心力衰竭在內的心臟與血管疾病。它是全球主要死因之一。

**世界衛生組織重點資訊:**
- 超過八成的心血管死亡來自心臟病和中風
- 三分之一發生在70歲以下的人
- 多數可透過控制菸酒、不良飲食、運動不足、肥胖等風險因素來預防

**預防建議:**
- 戒菸,遠離二手菸
- 飲食清淡,減少油脂和鹽分
- 每天至少運動 30 分鐘
- 定期健康檢查
""",
    "Malay": """
**Apakah Penyakit Kardiovaskular (CVD)?**

CVD ialah kumpulan penyakit jantung dan saluran darah seperti penyakit jantung koronari, strok dan kegagalan jantung. Ia adalah penyebab utama kematian di dunia.

**Fakta Penting (WHO):**
- Lebih 80% kematian CVD berpunca daripada serangan jantung dan strok
- Sepertiga berlaku pada mereka di bawah umur 70 tahun
- Sebahagian besar boleh dicegah melalui gaya hidup sihat

**Langkah Pencegahan:**
- Berhenti merokok dan elakkan asap rokok
- Pilih makanan sihat kurang garam dan lemak
- Kekal aktif sekurang-kurangnya 30 minit sehari
- Lakukan pemeriksaan kesihatan secara berkala
"""
}[lang])

    st.markdown("## " + {
        "English": "🥗 Healthy Diet Guidelines",
        "中文": "🥗 健康飲食指引",
        "Malay": "🥗 Garis Panduan Pemakanan Sihat"
    }[lang])
    
    st.markdown({
        "English": """
- Eat at least 400g of fruits and vegetables per day.
- Reduce total fat intake, especially saturated and trans fats.
- Limit salt to less than 5g per day.
- Avoid sugar-sweetened beverages and processed snacks.
- Breastfeed exclusively for 6 months where applicable.
""",
        "中文": """
- 每天攝取至少400克蔬果
- 減少脂肪攝取,特別是飽和脂肪與反式脂肪
- 每日鹽分少於5克
- 避免含糖飲料與加工零食
- 鼓勵6個月內純母乳哺育(如適用)
""",
        "Malay": """
- Makan sekurang-kurangnya 400g buah dan sayur setiap hari
- Kurangkan pengambilan lemak tepu dan lemak trans
- Hadkan garam kurang daripada 5g sehari
- Elakkan minuman bergula dan makanan ringan diproses
- Susukan bayi secara eksklusif selama 6 bulan jika boleh
"""
    }[lang])

    st.markdown("## " + {
        "English": "🧘️‍♂️ Physical Activity Tips",
        "中文": "🧘️‍♂️ 身體活動建議",
        "Malay": "🧘️‍♂️ Petua Aktiviti Fizikal"
    }[lang])
    
    st.markdown({
        "English": """
- Adults: At least 150–300 minutes of moderate-intensity aerobic activity weekly
- Children/teens: 60 minutes per day of physical activity
- Reduce sedentary behavior (screen time, sitting)
- Include muscle-strengthening exercises twice a week
""",
        "中文": """
- 成人:每週應進行150至300分鐘中等強度的有氧運動
- 兒童與青少年:每天至少60分鐘身體活動
- 減少久坐與看螢幕時間
- 每週應有兩次肌力訓練
""",
        "Malay": """
- Dewasa: Sekurang-kurangnya 150–300 minit senaman aerobik sederhana setiap minggu
- Kanak-kanak/remaja: 60 minit sehari aktiviti fizikal
- Kurangkan masa duduk atau menghadap skrin
- Lakukan latihan kekuatan otot dua kali seminggu
"""
    }[lang])


    MAP_TITLE = {
        "English": "🏥 Search Nearby Hospitals",
        "中文": "🏥 搜尋附近的醫院",
        "Malay": "🏥 Cari Hospital Berdekatan"
    }
    st.markdown(f"### {MAP_TITLE[lang]}")
    st.markdown("""
    <iframe src="https://www.google.com/maps?q=hospital+near+me&output=embed"
            width="100%" height="400" style="border:0;" allowfullscreen="" loading="lazy">
    </iframe>
    """, unsafe_allow_html=True)