tricc commited on
Commit
26b69a3
·
verified ·
1 Parent(s): 33fb6e6
Files changed (2) hide show
  1. demo.py +236 -0
  2. dental_models_v1.pkl +3 -0
demo.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import seaborn as sns
6
+ import joblib
7
+
8
+ plt.style.use('seaborn-v0_8-muted')
9
+ sns.set_theme(style="whitegrid")
10
+
11
+ import warnings
12
+ warnings.filterwarnings('ignore')
13
+
14
+ # Load mô hình
15
+ try:
16
+ models = joblib.load('dental_models_v1.pkl')
17
+ print("✅ Load model thành công.")
18
+ except:
19
+ print("⚠️ Lỗi: Không tìm thấy file model.")
20
+
21
+ def preprocess_input(tuoi, gioi_tinh, ben_pt, kinh_nghiem,
22
+ r7, canh_dung, goc, chan_rang, ord_lq,
23
+ ha_mieng, ma, so_thuoc):
24
+
25
+ # Ở đây 'tuoi' đóng vai trò là năm sinh theo mẫu dữ liệu
26
+ raw_data = {
27
+ 'Tuổi': tuoi, 'Giới tính': gioi_tinh, 'Bên PT': ben_pt,
28
+ 'Kinh nghiệm PTV': kinh_nghiem, 'Tương quan R7': r7,
29
+ 'Tương quan cành đứng': canh_dung, 'Góc nghiêng': goc,
30
+ 'Hình thái chân răng': chan_rang, 'Liên quan ORD': ord_lq,
31
+ 'Độ há miệng': ha_mieng, 'Độ linh động má': ma,
32
+ 'Số viên thuốc GD': so_thuoc,
33
+ 'Thời gian PT (phút)': 0 # Placeholder sẽ cập nhật sau
34
+ }
35
+ df = pd.DataFrame([raw_data])
36
+
37
+ # Tính toán các đặc trưng (Feature Engineering)
38
+ df['Age'] = 2025 - df['Tuổi']
39
+ df['R7_Grouped'] = 'Class A' if df['Tương quan R7'].iloc[0] == 'A' else 'Class B/C'
40
+ canh_dung_val = df['Tương quan cành đứng'].iloc[0]
41
+ df['Ramus_Grouped'] = 'Class I/II' if canh_dung_val in ['I', 'II'] else 'Class III'
42
+
43
+ chan_val = str(df['Hình thái chân răng'].iloc[0])
44
+ if chan_val in ['2', '3']: df['Root_Grouped'] = 'Easy (2/3)'
45
+ elif chan_val in ['1', '4']: df['Root_Grouped'] = 'Hard (1/4)'
46
+ else: df['Root_Grouped'] = 'Other'
47
+
48
+ df['Age_Group'] = pd.cut(df['Age'], bins=[0, 22, 30, 100], labels=['Young (<22)', 'Adult (22-30)', 'Senior (>30)'])
49
+
50
+ return df
51
+
52
+ def predict_with_uncertainty(model, input_df):
53
+ rf_model = model.named_steps['model']
54
+ preprocessor = model.named_steps['preprocessor']
55
+ X_transformed = preprocessor.transform(input_df)
56
+ predictions = [tree.predict(X_transformed)[0] for tree in rf_model.estimators_]
57
+ mean_pred = np.mean(predictions)
58
+ std_pred = np.std(predictions)
59
+ return mean_pred, std_pred
60
+
61
+ def predict_with_error(model, input_df):
62
+ mean_pred, std_pred = predict_with_uncertainty(model, input_df)
63
+ error_margin = 1. * std_pred
64
+ return mean_pred, error_margin
65
+
66
+ def predict_general_v2(tuoi, gioi, ben, exp, r7, cd, goc, chan, ord_lq, ha, ma, thuoc):
67
+ df = preprocess_input(tuoi, gioi, ben, exp, r7, cd, goc, chan, ord_lq, ha, ma, thuoc)
68
+ time_mean, time_err = predict_with_error(models['Op_Time'], df)
69
+ df['Thời gian PT (phút)'] = time_mean
70
+
71
+ p1_m, p1_e = predict_with_error(models['Pain_D1'], df)
72
+ p3_m, p3_e = predict_with_error(models['Pain_D3'], df)
73
+ p7_m, p7_e = predict_with_error(models['Pain_D7'], df)
74
+ end_m, end_e = predict_with_error(models['End_Day'], df)
75
+
76
+ res_text = (
77
+ f"⏱️ THỜI GIAN NHỔ RĂNG DỰ KIẾN: {time_mean:.1f} ± {time_err:.1f} phút\n"
78
+ f"(Dải biến thiên: {max(0, time_mean-time_err):.1f} - {time_mean+time_err:.1f} phút)\n\n"
79
+ f"📈 DỰ BÁO MỨC ĐỘ ĐAU:\n"
80
+ f"• Ngày 1: {p1_m:.1f} (±{p1_e:.1f})\n"
81
+ f"• Ngày 3: {p3_m:.1f} (±{p3_e:.1f})\n"
82
+ f"• Ngày 7: {p7_m:.1f} (±{p7_e:.1f})\n"
83
+ f"• Hết đau hoàn toàn: Ngày thứ {end_m:.1f} (±{end_e:.1f})"
84
+ )
85
+
86
+ fig, ax = plt.subplots(figsize=(8, 4.5))
87
+ days = np.array([1, 3, 7])
88
+ means = np.array([p1_m, p3_m, p7_m])
89
+ errors = np.array([p1_e, p3_e, p7_e])
90
+
91
+ ax.fill_between(days, means - errors, means + errors, color='#4A90E2', alpha=0.2, label='Dải sai số dự báo')
92
+ ax.plot(days, means, 'o-', color='#4A90E2', linewidth=3, markersize=8, label='Mức đau trung bình')
93
+
94
+ for i, m in enumerate(means):
95
+ ax.text(days[i], m + 0.3, f"{m:.1f}", ha='center', fontweight='bold', color='#2C3E50')
96
+
97
+ ax.set_ylim(0, 5.5)
98
+ ax.set_xticks([1, 3, 7])
99
+ ax.set_xticklabels(['Ngày 1', 'Ngày 3', 'Ngày 7'])
100
+ ax.set_title("Biểu đồ diễn tiến đau và sai số dự báo", fontsize=14, pad=15)
101
+ ax.set_ylabel("Mức độ đau (VAS)")
102
+ ax.legend()
103
+ plt.tight_layout()
104
+ plt.close()
105
+
106
+ return res_text, fig
107
+
108
+ def predict_experience(tuoi, gioi, ben, r7, cd, goc, chan, ord_lq, ha, ma, thuoc):
109
+ exp_mapping = ["<5 năm", "5-10 năm", ">10 năm"]
110
+ times = []
111
+ for e in exp_mapping:
112
+ df = preprocess_input(tuoi, gioi, ben, e, r7, cd, goc, chan, ord_lq, ha, ma, thuoc)
113
+ times.append(models['Op_Time'].predict(df)[0])
114
+
115
+ fig, ax = plt.subplots(figsize=(7, 3.5))
116
+ colors = ['#FFADAD', '#FFD6A5', '#CAFFBF']
117
+ bars = ax.barh(["Dưới 5 năm", "5-10 năm", "Trên 10 năm"], times, color=colors, height=0.6)
118
+ ax.bar_label(bars, fmt='%.1f phút', padding=5, fontweight='bold')
119
+ ax.set_title("So sánh thời gian nhổ theo kinh nghiệm bác sĩ", fontsize=12)
120
+ ax.set_xlim(0, max(times) * 1.3)
121
+ plt.tight_layout()
122
+ plt.close()
123
+
124
+ advice = f"💡 Bác sĩ trên 10 năm kinh nghiệm làm nhanh hơn bác sĩ mới khoảng {times[0]-times[2]:.1f} phút."
125
+ return fig, advice
126
+
127
+ def predict_actual_with_range(tuoi, gioi, ben, exp, r7, cd, goc, chan, ord_lq, ha, ma, thuoc_ke_don, time_actual):
128
+ df = preprocess_input(tuoi, gioi, ben, exp, r7, cd, goc, chan, ord_lq, ha, ma, thuoc_ke_don)
129
+ df['Thời gian PT (phút)'] = time_actual
130
+
131
+ targets = {
132
+ 'Ngày 1': models['Pain_D1'],
133
+ 'Ngày 3': models['Pain_D3'],
134
+ 'Ngày 7': models['Pain_D7'],
135
+ 'Hết đau': models['End_Day']
136
+ }
137
+
138
+ results_mean = []
139
+ results_std = []
140
+ labels = ['Ngày 1', 'Ngày 3', 'Ngày 7']
141
+
142
+ for label in labels:
143
+ m, s = predict_with_uncertainty(targets[label], df)
144
+ results_mean.append(m)
145
+ results_std.append(s)
146
+
147
+ end_mean, end_std = predict_with_uncertainty(targets['Hết đau'], df)
148
+
149
+ fig, ax = plt.subplots(figsize=(8, 4))
150
+ x_vals = [1, 3, 7]
151
+ means = np.array(results_mean)
152
+ stds = np.array(results_std)
153
+
154
+ ax.plot(x_vals, means, 'o-', color='#d9534f', linewidth=2, label='Dự báo trung bình')
155
+ ax.fill_between(x_vals, means - 1.96*stds, means + 1.96*stds, color='#d9534f', alpha=0.2, label='Khoảng sai số dự báo')
156
+
157
+ for i, txt in enumerate(means):
158
+ ax.annotate(f'{txt:.1f}', (x_vals[i], means[i]), xytext=(0, 10), textcoords='offset points', ha='center')
159
+
160
+ ax.set_ylim(0, 5.5)
161
+ ax.set_xticks([1, 3, 7])
162
+ ax.set_xticklabels(['Ngày 1', 'Ngày 3', 'Ngày 7'])
163
+ ax.set_title(f"Diễn tiến đau thực tế (Thời gian: {time_actual}ph, Thuốc: {thuoc_ke_don} viên)")
164
+ ax.set_ylabel("Mức độ đau (VAS)")
165
+ ax.legend()
166
+ plt.tight_layout()
167
+ plt.close()
168
+
169
+ res_text = (f"✅ CẬP NHẬT SAU PHẪU THUẬT:\n"
170
+ f"• Thời gian thực hiện: {time_actual} phút\n"
171
+ f"• Thuốc giảm đau kê đơn: {thuoc_ke_don} viên\n"
172
+ f"----------------------------------------\n"
173
+ f"🕒 DỰ KIẾN HỒI PHỤC:\n"
174
+ f"• Ngày hết đau trung bình: Ngày thứ {end_mean:.1f} (± {end_std*1.96:.1f} ngày)\n"
175
+ f"• Mức độ đau ngày đầu: {results_mean[0]:.1f} / 5")
176
+
177
+ return res_text, fig
178
+
179
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
180
+ gr.Markdown("# 🦷 HỆ THỐNG DỰ BÁO THỜI GIAN PHẪU THUẬT RĂNG KHÔN VÀ PHÂN TÍCH MỨC ĐỘ ĐAU")
181
+
182
+ with gr.Row():
183
+ with gr.Column():
184
+ gr.Markdown("### 📋 Thông tin người bệnh")
185
+ tuoi = gr.Number(label="Tuổi", value=2005)
186
+ gioi_tinh = gr.Dropdown(["Nam", "Nữ"], label="Giới tính", value="Nữ")
187
+ ben_pt = gr.Dropdown(["Trái", "Phải"], label="Bên PT", value="Trái")
188
+ ha_mieng = gr.Slider(10, 70, value=52, label="Độ há miệng")
189
+ linh_dong_ma = gr.Slider(10, 70, value=48, label="Độ linh động má")
190
+ so_thuoc = gr.Number(label="Số viên thuốc GD", value=5, visible=False)
191
+
192
+ with gr.Column():
193
+ gr.Markdown("### 🔍 Đặc điểm răng (X-quang)")
194
+ kinh_nghiem = gr.Dropdown(["<5 năm", "5-10 năm", ">10 năm"], label="Kinh nghiệm PTV", value="<5 năm")
195
+ r7 = gr.Dropdown(["A", "B", "C"], label="Tương quan R7", value="B")
196
+ canh_dung = gr.Dropdown(["I", "II", "III"], label="Tương quan cành đứng", value="II")
197
+ goc_nghieng = gr.Number(label="Góc nghiêng", value=7)
198
+ chan_rang = gr.Dropdown([1, 2, 3, 4], label="Hình thái chân răng", value=4)
199
+ ord_lq = gr.Dropdown(["Không", "Có"], label="Liên quan ORD", value="Không")
200
+
201
+ base_inputs = [tuoi, gioi_tinh, ben_pt, kinh_nghiem, r7, canh_dung, goc_nghieng, chan_rang, ord_lq, ha_mieng, linh_dong_ma]
202
+ inputs_all = base_inputs + [so_thuoc]
203
+
204
+ with gr.Tabs():
205
+ with gr.TabItem("🕒 Thời gian phẫu thuật dự kiến"):
206
+ btn1 = gr.Button("🚀 Dự đoán thời gian phẫu thuật", variant="primary")
207
+ with gr.Row():
208
+ out_txt1 = gr.Textbox(label="Kết quả dự báo chi tiết", lines=8)
209
+ out_plot1 = gr.Plot(label="Diễn tiến đau")
210
+ btn1.click(predict_general_v2, inputs=inputs_all, outputs=[out_txt1, out_plot1])
211
+
212
+ with gr.TabItem("👨‍⚕️ So sánh theo kinh nghiệm bác sĩ"):
213
+ btn2 = gr.Button("📊 Dự đoán thời gian phẫu thuật theo kinh nghiệm bác sĩ")
214
+ with gr.Row():
215
+ out_plot2 = gr.Plot(label="Biểu đồ so sánh")
216
+ out_txt2 = gr.Textbox(label="Nhận xét", lines=2)
217
+ inputs_for_exp = [tuoi, gioi_tinh, ben_pt, r7, canh_dung, goc_nghieng, chan_rang, ord_lq, ha_mieng, linh_dong_ma, so_thuoc]
218
+ btn2.click(predict_experience, inputs=inputs_for_exp, outputs=[out_plot2, out_txt2])
219
+
220
+ with gr.TabItem("📝 Đánh giá sau Phẫu thuật"):
221
+ gr.Markdown("### Nhập thông số thực tế sau ca mổ để tư vấn cho bệnh nhân")
222
+ with gr.Row():
223
+ time_actual = gr.Number(label="Thời gian mổ thực tế (phút)", value=20)
224
+ thuoc_post = gr.Number(label="Số viên thuốc giảm đau đã kê đơn", value=10)
225
+
226
+ btn3 = gr.Button("🔄 Dự đoán thời gian hết đau", variant="secondary")
227
+
228
+ with gr.Row():
229
+ out_txt3 = gr.Textbox(label="Chi tiết", lines=8)
230
+ out_plot3 = gr.Plot(label="Biểu đồ diễn tiến đau")
231
+
232
+ btn3.click(predict_actual_with_range,
233
+ inputs=base_inputs + [thuoc_post, time_actual],
234
+ outputs=[out_txt3, out_plot3])
235
+
236
+ demo.launch(share=True)
dental_models_v1.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b830de97a48e2a02e8cac63c6b5d6b519ffb68e9fcbf34588f0902478a817acb
3
+ size 418516