Hank20041016 commited on
Commit
d150f44
·
verified ·
1 Parent(s): 9a42f08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +301 -38
app.py CHANGED
@@ -1,39 +1,145 @@
 
 
 
1
  from transformers import pipeline
2
  import gradio as gr
3
  from PIL import Image
 
 
 
 
4
 
5
- # 建立 pipeline
6
- pipe = pipeline("image-to-text", model="google/medgemma-4b-it")
 
 
7
 
8
- def simple_predict(image, question):
9
- """
10
- 最簡單的處理函數 - 用於調試
11
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  try:
13
- print(f"圖片類型: {type(image)}")
14
- print(f"問題: {question}")
 
15
 
16
- if image is None:
17
- return "請上傳圖片"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- if not question:
20
- question = "請描述這張圖片"
21
 
22
- # 確保是 RGB 模式
 
 
 
 
23
  if image.mode != 'RGB':
24
  image = image.convert('RGB')
 
25
 
26
- # 嘗試最簡單的調用方式
27
- print("嘗試直接傳遞圖片...")
28
- result = pipe(image)
29
- print(f"直接傳遞結果: {result}")
 
 
 
30
 
31
- return str(result)
 
 
32
 
33
- except Exception as e:
34
- print(f"直接傳遞失敗,嘗試消息格式...")
35
- try:
36
- # 嘗試消息格式
 
 
 
37
  messages = [
38
  {
39
  "role": "user",
@@ -43,23 +149,180 @@ def simple_predict(image, question):
43
  ]
44
  }
45
  ]
 
46
  result = pipe(messages)
47
- print(f"消息格式結果: {result}")
48
- return str(result)
49
- except Exception as e2:
50
- return f"兩種方式都失敗了:\n直接傳遞: {str(e)}\n消息格式: {str(e2)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- # 最簡單的介面
53
- demo = gr.Interface(
54
- fn=simple_predict,
55
- inputs=[
56
- gr.Image(type="pil", file_types=["jpg", "jpeg"]),
57
- gr.Textbox(value="請分析這張醫療影像", lines=2)
58
- ],
59
- outputs=gr.Textbox(lines=10),
60
- title="MedGemma 調試版",
61
- description="上傳 JPG 圖片測試 MedGemma 模型"
62
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
 
64
  if __name__ == "__main__":
65
- demo.launch(debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gc
3
+ import torch
4
  from transformers import pipeline
5
  import gradio as gr
6
  from PIL import Image
7
+ import requests
8
+ from io import BytesIO
9
+ import psutil
10
+ from datetime import datetime
11
 
12
+ # 設定環境變數,使用臨時目錄避免快速填滿存儲
13
+ os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers_cache"
14
+ os.environ["HF_HOME"] = "/tmp/hf_home"
15
+ os.environ["TORCH_HOME"] = "/tmp/torch_cache"
16
 
17
+ def clear_memory():
18
+ """清理記憶體和快取"""
19
+ gc.collect()
20
+ if torch.cuda.is_available():
21
+ torch.cuda.empty_cache()
22
+
23
+ def check_storage():
24
+ """檢查存儲空間"""
25
+ try:
26
+ disk_usage = psutil.disk_usage('/')
27
+ free_gb = disk_usage.free / (1024**3)
28
+ used_percent = (disk_usage.used / disk_usage.total) * 100
29
+ return free_gb, used_percent
30
+ except:
31
+ return 0, 100
32
+
33
+ def load_medgemma_model():
34
+ """載入 MedGemma 模型,使用優化設定"""
35
+ try:
36
+ print("🏥 正在載入 MedGemma-4B 模型...")
37
+ print(f"⏰ 載入時間: {datetime.now().strftime('%H:%M:%S')}")
38
+
39
+ # 檢查存儲空間
40
+ free_gb, used_percent = check_storage()
41
+ print(f"💾 可用空間: {free_gb:.1f}GB, 使用率: {used_percent:.1f}%")
42
+
43
+ if free_gb < 5: # 如果可用空間少於5GB
44
+ raise Exception(f"存儲空間不足 ({free_gb:.1f}GB),建議至少需要 5GB")
45
+
46
+ # 使用優化設定載入模型
47
+ pipe = pipeline(
48
+ "image-to-text",
49
+ model="google/medgemma-4b-it",
50
+ torch_dtype=torch.float16, # 使用半精度節省記憶體
51
+ device_map="auto",
52
+ low_cpu_mem_usage=True,
53
+ cache_dir="/tmp/transformers_cache"
54
+ )
55
+
56
+ print("✅ MedGemma-4B 模型載入成功!")
57
+ return pipe, "google/medgemma-4b-it"
58
+
59
+ except Exception as e:
60
+ print(f"❌ MedGemma 載入失敗: {e}")
61
+ print("🔄 嘗試載入較小的替代模型...")
62
+
63
+ try:
64
+ # 載入較小的醫療相關模型作為替代
65
+ pipe = pipeline(
66
+ "image-to-text",
67
+ model="Salesforce/blip-image-captioning-base",
68
+ cache_dir="/tmp/transformers_cache"
69
+ )
70
+ print("✅ 已載入 BLIP 模型作為替代")
71
+ return pipe, "Salesforce/blip-image-captioning-base"
72
+ except Exception as e2:
73
+ raise Exception(f"所有模型載入失敗: MedGemma({e}), BLIP({e2})")
74
+
75
+ def load_image_from_input(image_input):
76
+ """處理圖片輸入:PIL Image、檔案路徑或 URL"""
77
  try:
78
+ # JPG 檔案上傳(Gradio 返回 PIL Image)
79
+ if isinstance(image_input, Image.Image):
80
+ return image_input
81
 
82
+ # URL 輸入
83
+ elif isinstance(image_input, str):
84
+ if image_input.startswith(("http://", "https://")):
85
+ print(f"📥 正在下載圖片: {image_input[:50]}...")
86
+ response = requests.get(image_input, timeout=10)
87
+ response.raise_for_status()
88
+ image = Image.open(BytesIO(response.content))
89
+ print("✅ 圖片下載成功")
90
+ return image
91
+ else:
92
+ # 檔案路徑
93
+ return Image.open(image_input)
94
+ else:
95
+ return Image.open(image_input)
96
+
97
+ except Exception as e:
98
+ raise Exception(f"無法載入圖片: {e}")
99
+
100
+ def predict(image_input, question, url_input):
101
+ """主要預測函數"""
102
+ try:
103
+ # 確定圖片來源(優先使用上傳的圖片)
104
+ if image_input is not None:
105
+ image_source = image_input
106
+ source_type = "上傳檔案"
107
+ elif url_input and url_input.strip():
108
+ image_source = url_input.strip()
109
+ source_type = "URL"
110
+ else:
111
+ return "❌ 請上傳圖片或輸入圖片 URL"
112
 
113
+ print(f"📷 處理圖片來源: {source_type}")
 
114
 
115
+ # 載入圖片
116
+ image = load_image_from_input(image_source)
117
+
118
+ # 圖片預處理
119
+ original_size = image.size
120
  if image.mode != 'RGB':
121
  image = image.convert('RGB')
122
+ print(f"🔄 轉換圖片格式: {image.mode}")
123
 
124
+ # 調整圖片大小以節省記憶體(保持品質)
125
+ max_size = 768 # MedGemma 建議大小
126
+ if max(image.size) > max_size:
127
+ ratio = max_size / max(image.size)
128
+ new_size = tuple(int(dim * ratio) for dim in image.size)
129
+ image = image.resize(new_size, Image.Resampling.LANCZOS)
130
+ print(f"📐 調整圖片大小: {original_size} → {image.size}")
131
 
132
+ # 處理問題輸入
133
+ if not question or not question.strip():
134
+ question = "請詳細分析這張醫療影像,描述你看到的重要特徵、可能的病理變化,以及任何需要注意的異常。"
135
 
136
+ question = question.strip()
137
+ print(f"❓ 醫療問題: {question[:100]}...")
138
+
139
+ # 根據模型類型選擇輸入格式
140
+ global model_name
141
+ if "medgemma" in model_name.lower():
142
+ # MedGemma 使用對話格式
143
  messages = [
144
  {
145
  "role": "user",
 
149
  ]
150
  }
151
  ]
152
+ print("🔬 使用 MedGemma 專業醫療分析模式")
153
  result = pipe(messages)
154
+ else:
155
+ # 其他模型直接使用圖片
156
+ print("🔍 使用通用圖片描述模式")
157
+ result = pipe(image)
158
+
159
+ # 清理記憶體
160
+ clear_memory()
161
+
162
+ # 解析結果
163
+ if isinstance(result, list) and len(result) > 0:
164
+ if isinstance(result[0], dict):
165
+ generated_text = result[0].get('generated_text', str(result[0]))
166
+ else:
167
+ generated_text = str(result[0])
168
+ else:
169
+ generated_text = str(result)
170
+
171
+ # 添加分析資訊
172
+ analysis_info = f"""
173
+ 🏥 **醫療影像分析結果**
174
+
175
+ 📊 **圖片資訊:**
176
+ - 原始尺寸: {original_size}
177
+ - 處理尺寸: {image.size}
178
+ - 來源: {source_type}
179
+
180
+ 🤖 **使用模型:** {model_name}
181
+
182
+ 🔬 **分析結果:**
183
+ {generated_text}
184
+
185
+ ---
186
+ ⚠️ **重要提醒:** 此分析僅供參考,不能替代專業醫療診斷。如有疑慮請諮詢專業醫師。
187
+ """
188
+
189
+ return analysis_info
190
+
191
+ except Exception as e:
192
+ clear_memory()
193
+ error_msg = f"❌ 處理錯誤: {str(e)}"
194
+ print(error_msg)
195
+ return error_msg
196
+
197
+ # 載入模型
198
+ try:
199
+ pipe, model_name = load_medgemma_model()
200
+ model_status = f"✅ {model_name} 已準備就緒"
201
+ except Exception as e:
202
+ model_status = f"❌ 模型載入失敗: {e}"
203
+ pipe = None
204
+ model_name = "未載入"
205
 
206
+ # 創建 Gradio 介面
207
+ def create_interface():
208
+ with gr.Blocks(
209
+ title="MedGemma 醫療影像分析系統",
210
+ theme=gr.themes.Soft(),
211
+ css=".gradio-container {max-width: 1200px; margin: auto;}"
212
+ ) as demo:
213
+
214
+ gr.Markdown(f"""
215
+ # 🏥 MedGemma 醫療影像分析系統
216
+
217
+ **模型狀態:** {model_status}
218
+ **更新時間:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
219
+
220
+ 上傳醫療影像(JPG/PNG)或輸入圖片 URL,獲得專業的 AI 醫療影像分析。
221
+ """)
222
+
223
+ with gr.Row():
224
+ with gr.Column(scale=1):
225
+ # 圖片上傳
226
+ image_input = gr.Image(
227
+ label="📤 上傳醫療影像",
228
+ type="pil",
229
+ file_types=["jpg", "jpeg", "png"],
230
+ height=300
231
+ )
232
+
233
+ # URL 輸入
234
+ url_input = gr.Textbox(
235
+ label="🔗 或輸入圖片 URL",
236
+ placeholder="https://example.com/medical-image.jpg",
237
+ lines=1
238
+ )
239
+
240
+ # 問題輸入
241
+ question_input = gr.Textbox(
242
+ label="❓ 醫療問題或分析要求",
243
+ placeholder="請分析這張X光片中的異常...",
244
+ lines=3,
245
+ value="請詳細分析這張醫療影像,包括任何可見的異常或重要特徵。"
246
+ )
247
+
248
+ # 分析按鈕
249
+ analyze_btn = gr.Button(
250
+ "🔬 開始分析",
251
+ variant="primary",
252
+ size="lg"
253
+ )
254
+
255
+ # 清理按鈕
256
+ clear_btn = gr.Button("🧹 清理", variant="secondary")
257
+
258
+ with gr.Column(scale=2):
259
+ # 分析結果
260
+ output = gr.Textbox(
261
+ label="📋 分析結果",
262
+ lines=20,
263
+ interactive=False,
264
+ show_copy_button=True
265
+ )
266
+
267
+ # 使用說明
268
+ with gr.Accordion("📖 使用說明", open=False):
269
+ gr.Markdown("""
270
+ ### 如何使用:
271
+ 1. **上傳圖片**: 點擊上傳區域選擇 JPG/PNG 醫療影像
272
+ 2. **或使用 URL**: 在 URL 欄位貼上圖片連結
273
+ 3. **輸入問題**: 描述你想了解的醫療問題
274
+ 4. **開始分析**: 點擊分析按鈕獲得結果
275
+
276
+ ### 支援的影像類型:
277
+ - X光片 (X-ray)
278
+ - CT 掃描 (CT Scan)
279
+ - MRI 影像 (MRI)
280
+ - 超音波影像 (Ultrasound)
281
+ - 病理切片 (Pathology)
282
+
283
+ ### 重要提醒:
284
+ ⚠️ 此 AI 分析僅供參考學習,不可作為醫療診斷依據
285
+ ⚠️ 如有健康疑慮,請務必諮詢專業醫師
286
+ """)
287
+
288
+ # 事件綁定
289
+ analyze_btn.click(
290
+ fn=predict,
291
+ inputs=[image_input, question_input, url_input],
292
+ outputs=output
293
+ )
294
+
295
+ clear_btn.click(
296
+ fn=lambda: ("", "", ""),
297
+ outputs=[image_input, url_input, output]
298
+ )
299
+
300
+ # 圖片上傳時自動分析
301
+ image_input.change(
302
+ fn=lambda img, q, url: predict(img, q, url) if img is not None else "",
303
+ inputs=[image_input, question_input, url_input],
304
+ outputs=output
305
+ )
306
+
307
+ return demo
308
 
309
+ # 啟動應用
310
  if __name__ == "__main__":
311
+ if pipe is None:
312
+ print("❌ 無法啟動:模型載入失敗")
313
+ exit(1)
314
+
315
+ print("🚀 啟動 MedGemma 醫療影像分析系統...")
316
+
317
+ # 檢查最終狀態
318
+ free_gb, used_percent = check_storage()
319
+ print(f"💾 當前存儲狀態: {free_gb:.1f}GB 可用, {used_percent:.1f}% 已使用")
320
+
321
+ demo = create_interface()
322
+ demo.launch(
323
+ server_name="0.0.0.0",
324
+ server_port=7860,
325
+ debug=False,
326
+ show_error=True,
327
+ share=False
328
+ )