EduTechTeam commited on
Commit
0d6e1d4
·
verified ·
1 Parent(s): ef8aa92

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +403 -0
app.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import random
3
+ from PIL import Image
4
+ import numpy as np
5
+ from datetime import datetime, timedelta
6
+ import pandas as pd
7
+ import plotly.graph_objects as go
8
+ from plotly.subplots import make_subplots
9
+
10
+ # 食物營養資料庫
11
+ food_nutrition = {
12
+ "米飯": {
13
+ "calories": 130, "protein": 2.7, "carbs": 28.7, "fat": 0.3, "fiber": 0.4,
14
+ "category": "主食", "glycemic_index": "高", "allergens": [],
15
+ "vitamins": {"B1": 0.02, "B2": 0.01, "B3": 0.4},
16
+ "minerals": {"鐵": 0.2, "鎂": 12, "鋅": 0.4}
17
+ },
18
+ "牛肉": {
19
+ "calories": 250, "protein": 26, "carbs": 0, "fat": 17, "fiber": 0,
20
+ "category": "蛋白質", "glycemic_index": "低", "allergens": [],
21
+ "vitamins": {"B12": 2.1, "B6": 0.3, "D": 0.1},
22
+ "minerals": {"鐵": 2.7, "鋅": 4.8, "硒": 33.2}
23
+ },
24
+ "麵包": {
25
+ "calories": 265, "protein": 9, "carbs": 49, "fat": 3.2, "fiber": 2.5,
26
+ "category": "主食", "glycemic_index": "高", "allergens": ["麩質"],
27
+ "vitamins": {"B1": 0.1, "B3": 1.3, "葉酸": 0.04},
28
+ "minerals": {"鎂": 26, "磷": 86, "鐵": 1.4}
29
+ },
30
+ "蔬菜": {
31
+ "calories": 25, "protein": 1.5, "carbs": 5, "fat": 0.2, "fiber": 2.5,
32
+ "category": "蔬菜", "glycemic_index": "低", "allergens": [],
33
+ "vitamins": {"A": 0.5, "C": 10, "K": 0.1},
34
+ "minerals": {"鉀": 290, "鈣": 40, "鎂": 15}
35
+ },
36
+ "番茄": {
37
+ "calories": 18, "protein": 0.9, "carbs": 3.9, "fat": 0.2, "fiber": 1.2,
38
+ "category": "蔬果", "glycemic_index": "低", "allergens": [],
39
+ "vitamins": {"C": 13.7, "A": 0.8, "K": 0.07},
40
+ "minerals": {"鉀": 237, "鎂": 11, "鈣": 10}
41
+ },
42
+ "雞蛋": {
43
+ "calories": 155, "protein": 13, "carbs": 0.6, "fat": 11, "fiber": 0,
44
+ "category": "蛋白質", "glycemic_index": "低", "allergens": ["蛋"],
45
+ "vitamins": {"D": 1.1, "B12": 0.6, "B2": 0.3},
46
+ "minerals": {"硒": 20, "鋅": 1.3, "鐵": 1.8}
47
+ },
48
+ "豬肉": {
49
+ "calories": 242, "protein": 22.5, "carbs": 0, "fat": 17, "fiber": 0,
50
+ "category": "蛋白質", "glycemic_index": "低", "allergens": [],
51
+ "vitamins": {"B1": 0.6, "B6": 0.4, "B12": 0.7},
52
+ "minerals": {"鋅": 4.5, "鐵": 1.2, "硒": 35.3}
53
+ },
54
+ "三文魚": {
55
+ "calories": 208, "protein": 22, "carbs": 0, "fat": 13, "fiber": 0,
56
+ "category": "蛋白質", "glycemic_index": "低", "allergens": ["魚"],
57
+ "vitamins": {"D": 9.5, "B12": 3.2, "B6": 0.6},
58
+ "minerals": {"硒": 40.2, "鋅": 0.5, "鐵": 0.5}
59
+ },
60
+ "青菜": {
61
+ "calories": 16, "protein": 1.3, "carbs": 2.9, "fat": 0.2, "fiber": 2.1,
62
+ "category": "蔬菜", "glycemic_index": "低", "allergens": [],
63
+ "vitamins": {"A": 0.3, "C": 35, "K": 0.2},
64
+ "minerals": {"鈣": 105, "鎂": 22, "鉀": 370}
65
+ },
66
+ "水果": {
67
+ "calories": 52, "protein": 0.5, "carbs": 14, "fat": 0.2, "fiber": 2.4,
68
+ "category": "蔬果", "glycemic_index": "中", "allergens": [],
69
+ "vitamins": {"C": 8.5, "A": 0.2, "葉酸": 0.02},
70
+ "minerals": {"鉀": 200, "鎂": 10, "鈣": 20}
71
+ },
72
+ "土豆": {
73
+ "calories": 77, "protein": 2, "carbs": 17, "fat": 0.1, "fiber": 2.2,
74
+ "category": "主食", "glycemic_index": "高", "allergens": [],
75
+ "vitamins": {"C": 10, "B6": 0.3, "葉酸": 0.01},
76
+ "minerals": {"鉀": 421, "鎂": 23, "磷": 57}
77
+ },
78
+ "豆腐": {
79
+ "calories": 84, "protein": 9, "carbs": 2, "fat": 5, "fiber": 0.5,
80
+ "category": "蛋白質", "glycemic_index": "低", "allergens": ["大豆"],
81
+ "vitamins": {"A": 0.1, "葉酸": 0.02, "K": 0.02},
82
+ "minerals": {"鈣": 350, "鐵": 2.1, "鎂": 37}
83
+ },
84
+ "花生": {
85
+ "calories": 567, "protein": 25.8, "carbs": 16.1, "fat": 49.2, "fiber": 8.5,
86
+ "category": "脂肪", "glycemic_index": "低", "allergens": ["花生"],
87
+ "vitamins": {"E": 8.3, "B3": 14.4, "葉酸": 0.24},
88
+ "minerals": {"鎂": 168, "磷": 376, "鋅": 3.3}
89
+ },
90
+ "牛奶": {
91
+ "calories": 42, "protein": 3.4, "carbs": 4.8, "fat": 1.0, "fiber": 0,
92
+ "category": "乳製品", "glycemic_index": "低", "allergens": ["乳製品"],
93
+ "vitamins": {"B12": 0.4, "B2": 0.2, "D": 0.1},
94
+ "minerals": {"鈣": 120, "磷": 93, "鉀": 150}
95
+ },
96
+ "香蕉": {
97
+ "calories": 89, "protein": 1.1, "carbs": 22.8, "fat": 0.3, "fiber": 2.6,
98
+ "category": "蔬果", "glycemic_index": "中", "allergens": [],
99
+ "vitamins": {"C": 8.7, "B6": 0.4, "A": 0.1},
100
+ "minerals": {"鉀": 358, "鎂": 27, "鐵": 0.3}
101
+ },
102
+ "雞肉": {
103
+ "calories": 239, "protein": 27, "carbs": 0, "fat": 14, "fiber": 0,
104
+ "category": "蛋白質", "glycemic_index": "低", "allergens": [],
105
+ "vitamins": {"B6": 0.5, "B12": 0.3, "A": 0.02},
106
+ "minerals": {"鋅": 2.7, "鐵": 0.9, "硒": 24.5}
107
+ }
108
+
109
+
110
+ }
111
+
112
+ class UserProfile:
113
+ # 初始化用戶資料
114
+ def __init__(self, age, gender, height, weight, activity_level):
115
+ # 身高與體重範圍檢查
116
+ if not (50 <= height <= 250):
117
+ raise ValueError("身高應在50到250公分之間")
118
+ if not (20 <= weight <= 300):
119
+ raise ValueError("體重應在20到300公斤之間")
120
+
121
+ self.age = age
122
+ self.gender = gender
123
+ self.height = height
124
+ self.weight = weight
125
+ self.activity_level = activity_level
126
+
127
+ def calculate_bmr(self):
128
+ # 計算基礎代謢率(BMR)
129
+ if self.gender == "男":
130
+ return 88.362 + (13.397 * self.weight) + (4.799 * self.height) - (5.677 * self.age)
131
+ else:
132
+ return 447.593 + (9.247 * self.weight) + (3.098 * self.height) - (4.330 * self.age)
133
+
134
+ def calculate_tdee(self):
135
+ # 計算每日總能量消耗(TDEE)
136
+ activity_factors = {
137
+ "久坐": 1.2,
138
+ "輕度活動": 1.375,
139
+ "中度活動": 1.55,
140
+ "重度活動": 1.725,
141
+ "極重度活動": 1.9
142
+ }
143
+ return self.calculate_bmr() * activity_factors[self.activity_level]
144
+
145
+ def create_nutrition_charts(total_nutrients, recommended_nutrients, total_calories):
146
+ """創建優化後的營養圖表"""
147
+ # 創建子圖布局
148
+ fig = make_subplots(
149
+ rows=2, cols=2,
150
+ specs=[
151
+ [{"colspan": 2}, None],
152
+ [{"type": "pie"}, {"type": "bar"}]
153
+ ],
154
+ subplot_titles=(
155
+ '每日營養素攝入比較',
156
+ '熱量來源分布',
157
+ '營養素達標率'
158
+ ),
159
+ vertical_spacing=0.12,
160
+ horizontal_spacing=0.1
161
+ )
162
+
163
+ # 1. 營養素對比條形圖(上方大圖)
164
+ nutrients = ['蛋白質', '碳水化合物', '脂肪', '膳食纖維']
165
+
166
+ fig.add_trace(
167
+ go.Bar(
168
+ name='實際攝入',
169
+ x=nutrients,
170
+ y=[total_nutrients[n] for n in nutrients],
171
+ marker_color='rgb(55, 83, 109)'
172
+ ),
173
+ row=1, col=1
174
+ )
175
+
176
+ fig.add_trace(
177
+ go.Bar(
178
+ name='建議攝入',
179
+ x=nutrients,
180
+ y=[recommended_nutrients[n] for n in nutrients],
181
+ marker_color='rgb(26, 118, 255)'
182
+ ),
183
+ row=1, col=1
184
+ )
185
+
186
+ # 2. 熱量來源分布圓餅圖(左下)
187
+ calorie_values = [
188
+ total_nutrients['蛋白質'] * 4,
189
+ total_nutrients['碳水化合物'] * 4,
190
+ total_nutrients['脂肪'] * 9
191
+ ]
192
+
193
+ fig.add_trace(
194
+ go.Pie(
195
+ labels=['蛋白質', '碳水化合物', '脂肪'],
196
+ values=calorie_values,
197
+ hole=0.4,
198
+ marker=dict(colors=['rgb(55, 83, 109)', 'rgb(26, 118, 255)', 'rgb(95, 70, 144)'])
199
+ ),
200
+ row=2, col=1
201
+ )
202
+
203
+ # 3. 營養素達標率(右下)
204
+ achievement_rates = [
205
+ (total_nutrients[n] / recommended_nutrients[n] * 100)
206
+ if recommended_nutrients[n] > 0 else 0
207
+ for n in nutrients
208
+ ]
209
+
210
+ fig.add_trace(
211
+ go.Bar(
212
+ x=nutrients,
213
+ y=achievement_rates,
214
+ marker_color='rgb(158, 202, 225)',
215
+ text=[f"{rate:.1f}%" for rate in achievement_rates],
216
+ textposition='auto',
217
+ ),
218
+ row=2, col=2
219
+ )
220
+
221
+ # 更新布局
222
+ fig.update_layout(
223
+ height=800, # 增加圖表高度
224
+ width=900, # 適當的寬度
225
+ showlegend=True,
226
+ legend=dict(
227
+ orientation="h",
228
+ yanchor="bottom",
229
+ y=1.02,
230
+ xanchor="right",
231
+ x=1
232
+ ),
233
+ title_text="營養分析總覽",
234
+ title_x=0.5,
235
+ font=dict(size=12),
236
+ )
237
+
238
+ # 更新y軸標題
239
+ fig.update_yaxes(title_text="克", row=1, col=1)
240
+ fig.update_yaxes(title_text="達標率(%)", row=2, col=2)
241
+
242
+ return fig
243
+
244
+ def analyze_meals_advanced(
245
+ images, age, gender, height, weight,
246
+ activity_level, diet_preference, health_goal,
247
+ allergies, medical_conditions
248
+ ):
249
+ try:
250
+ # 新增的輸入驗證
251
+ if not all([age, gender, height, weight, activity_level]):
252
+ return "請填寫所有必要的個人信息", "", "", None, ""
253
+
254
+ # 檢查過敏原
255
+ if "無" in allergies and len(allergies) > 1:
256
+ return "過敏原不能同時選擇「無」和其他選項", "", "", None, ""
257
+
258
+ # 檢查醫療狀況
259
+ if "無" in medical_conditions and len(medical_conditions) > 1:
260
+ return "醫療狀況不能同時選擇「無」和其他選項", "", "", None, ""
261
+
262
+ # 嘗試建立UserProfile並檢查身高體重範圍
263
+ user = UserProfile(age, gender, height, weight, activity_level)
264
+ tdee = user.calculate_tdee()
265
+
266
+ # 模擬食物識別
267
+ recognized_foods = []
268
+ for img in images:
269
+ foods = random.sample(list(food_nutrition.keys()), k=random.randint(1, 3))
270
+ recognized_foods.extend(foods)
271
+
272
+ # 計算營養總量
273
+ total_nutrients = {
274
+ "蛋白質": sum(food_nutrition[food]["protein"] for food in recognized_foods),
275
+ "碳水化合物": sum(food_nutrition[food]["carbs"] for food in recognized_foods),
276
+ "脂肪": sum(food_nutrition[food]["fat"] for food in recognized_foods),
277
+ "膳食纖維": sum(food_nutrition[food]["fiber"] for food in recognized_foods)
278
+ }
279
+
280
+ # 計算建議攝入量
281
+ recommended_nutrients = {
282
+ "蛋白質": weight * 1.2,
283
+ "碳水化合物": tdee * 0.5 / 4,
284
+ "脂肪": tdee * 0.3 / 9,
285
+ "膳食纖維": 25
286
+ }
287
+
288
+ total_calories = sum(food_nutrition[food]["calories"] for food in recognized_foods)
289
+
290
+ # 創建優化後的圖表
291
+ charts = create_nutrition_charts(total_nutrients, recommended_nutrients, total_calories)
292
+
293
+ # 生成更簡潔的報告
294
+ report = f"""📊 營養攝入概要
295
+ --------------------------------
296
+ ⚡ 每日能量需求:{tdee:.0f} 大卡
297
+ 🍽️ 實際攝入:{total_calories:.0f} 大卡
298
+ 🥩 蛋白質:{total_nutrients['蛋白質']:.1f}g
299
+ 🍚 碳水化合物:{total_nutrients['碳水化合物']:.1f}g
300
+ 🥑 脂肪:{total_nutrients['脂肪']:.1f}g
301
+ 🥬 膳食纖維:{total_nutrients['膳食纖維']:.1f}g"""
302
+
303
+ # 過敏警告
304
+ allergy_warnings = "⚠️ 過敏原警告:\n"
305
+ if allergies and "無" not in allergies:
306
+ found_allergens = []
307
+ for food in recognized_foods:
308
+ food_allergens = food_nutrition[food].get("allergens", [])
309
+ found_allergens.extend([a for a in allergies if a in food_allergens])
310
+
311
+ if found_allergens:
312
+ allergy_warnings += "\n".join(f"- 發現含有{allergen}的食物" for allergen in set(found_allergens))
313
+ else:
314
+ allergy_warnings += "未發現任何過敏原"
315
+ else:
316
+ allergy_warnings += "未設置過敏原信息"
317
+
318
+ # 醫療建議
319
+ medical_advice = "👨‍⚕️ 健康建議:\n"
320
+ if medical_conditions:
321
+ for condition in medical_conditions:
322
+ if condition == "糖尿病":
323
+ if total_nutrients['碳水化合物'] > recommended_nutrients['碳水化合物']:
324
+ medical_advice += "- 碳水化合物攝入偏高,建議調整\n"
325
+ elif condition == "高血壓":
326
+ medical_advice += "- 建議選擇低鈉食物,控制鹽分攝入\n"
327
+ else:
328
+ medical_advice += "無特殊醫療注意事項"
329
+
330
+ return report, allergy_warnings, recognized_foods, charts, medical_advice
331
+
332
+ except ValueError as e:
333
+ return f"錯誤:{str(e)}", "", "", None, "" # 返回錯誤訊息
334
+ except Exception as e:
335
+ return f"分析過程出錯:{str(e)}", "", "", None, ""
336
+
337
+ def create_interface():
338
+ with gr.Blocks(theme=gr.themes.Soft()) as iface:
339
+ gr.Markdown("# 🥗 智能營養分析系統")
340
+
341
+ with gr.Row():
342
+ with gr.Column(scale=1):
343
+ images = gr.Gallery(label="上傳一日三餐的照片", height=200)
344
+
345
+ with gr.Row():
346
+ with gr.Column(scale=1):
347
+ age = gr.Number(label="年齡", minimum=1, maximum=120)
348
+ height = gr.Number(label="身高(cm)", minimum=50, maximum=250)
349
+ with gr.Column(scale=1):
350
+ gender = gr.Radio(["男", "女"], label="性別")
351
+ weight = gr.Number(label="體重(kg)", minimum=20, maximum=200)
352
+
353
+ activity_level = gr.Dropdown(
354
+ ["久坐", "輕度活動", "中度活動", "重度活動", "極重度活動"],
355
+ label="活動程度"
356
+ )
357
+
358
+ with gr.Row():
359
+ with gr.Column(scale=1):
360
+ diet_preference = gr.Radio(
361
+ ["一般", "素食", "低碳", "高蛋白", "低脂", "無糖"],
362
+ label="飲食偏好"
363
+ )
364
+ health_goal = gr.Radio(
365
+ ["減肥", "增肌", "維持健康", "改善消化"],
366
+ label="健康目標"
367
+ )
368
+
369
+ with gr.Column(scale=1):
370
+ allergies = gr.Radio(
371
+ ["無", "花生", "海鮮", "乳製品", "麩質", "蛋", "豆類"],
372
+ label="過敏原",
373
+ type="value" # 新增這個參數
374
+ )
375
+ medical_conditions = gr.Radio(
376
+ ["無", "糖尿病", "高血壓", "高血脂", "腎臟病"],
377
+ label="醫療狀況",
378
+ type="value" # 新增這個參數
379
+ )
380
+
381
+ with gr.Column(scale=1):
382
+ charts_output = gr.Plot(label="營養分析圖表")
383
+ report_output = gr.Textbox(label="營養分析報告", lines=6)
384
+ with gr.Row():
385
+ allergy_warning_output = gr.Textbox(label="過敏警告", lines=3)
386
+ medical_advice_output = gr.Textbox(label="醫療建議", lines=3)
387
+ suggestions_output = gr.Textbox(label="個人化建議", lines=8)
388
+
389
+ analyze_btn = gr.Button("開始分析", variant="primary")
390
+ analyze_btn.click(
391
+ fn=analyze_meals_advanced,
392
+ inputs=[images, age, gender, height, weight,
393
+ activity_level, diet_preference, health_goal,
394
+ allergies, medical_conditions],
395
+ outputs=[report_output, allergy_warning_output,
396
+ medical_advice_output, charts_output, suggestions_output]
397
+ )
398
+
399
+ return iface
400
+
401
+ if __name__ == "__main__":
402
+ iface = create_interface()
403
+ iface.launch()