medPS / app.py
Song
hi
c1187c1
import gradio as gr
import torch
import torch.nn as nn
from torchvision import transforms, models
from PIL import Image
import numpy as np
# === 使用 grad-cam (套件名稱已修正) ===
from pytorch_grad_cam import GradCAM
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
from pytorch_grad_cam.utils.image import show_cam_on_image
# 設定設備
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# ==================== 1. 載入肺炎模型 (ResNet50) ====================
# 使用 weights=None 取代已棄用的 pretrained=False
model_pneumonia = models.resnet50(weights=None)
num_ftrs = model_pneumonia.fc.in_features
model_pneumonia.fc = nn.Linear(num_ftrs, 2) # 兩類:NORMAL vs PNEUMONIA
model_path_pneumonia = "best_pneumonia_model.pth"
# 載入模型權重
try:
# 建議使用 weights_only=False 以確保舊版模型權重能正確讀取
model_pneumonia.load_state_dict(torch.load(model_path_pneumonia, map_location=device, weights_only=False))
print(f"成功載入模型權重:{model_path_pneumonia}")
except Exception as e:
print(f"模型載入失敗,請確認檔案是否存在:{e}")
model_pneumonia.to(device)
model_pneumonia.eval()
# 設定 Grad-CAM 目標層 (ResNet50 的最後一層捲積層)
target_layers = [model_pneumonia.layer4[-1]]
# ==================== 2. 影像前處理 ====================
img_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
# ==================== 3. 肺炎預測函數 ====================
def predict_pneumonia(image):
if image is None:
return "請上傳影像", None, "無影像"
# 轉換圖片格式
pil_img = Image.fromarray(image).convert("RGB")
input_tensor = img_transform(pil_img).unsqueeze(0).to(device)
# 模型推論
with torch.no_grad():
output = model_pneumonia(input_tensor)
probs = torch.softmax(output, dim=1).cpu().numpy()[0]
pred_class = np.argmax(probs)
confidence = probs[pred_class]
label = "PNEUMONIA" if pred_class == 1 else "NORMAL"
result_text = f"肺炎檢測結果:{label} (信心度:{confidence:.1%})"
# --- Grad-CAM 視覺化 ---
# 注意:GradCAM 在計算時需要梯度,所以不放在 no_grad 中
cam = GradCAM(model=model_pneumonia, target_layers=target_layers)
targets = [ClassifierOutputTarget(pred_class)]
# 產生熱圖
grayscale_cam = cam(input_tensor=input_tensor, targets=targets)[0]
# 將原始圖片縮放到 224x224 以符合熱圖尺寸
input_float_img = np.array(pil_img.resize((224, 224))).astype(np.float32) / 255.0
visualization = show_cam_on_image(input_float_img, grayscale_cam, use_rgb=True)
# --- 風險評估建議 ---
if label == "PNEUMONIA" and confidence > 0.7:
risk_assessment = "🚨 **高風險**:高度懷疑肺炎,建議立即就醫並進行進一步檢查。"
elif label == "PNEUMONIA":
risk_assessment = "⚠️ **中等風險**:疑似肺炎徵象,建議儘快諮詢醫療專業人員。"
else:
risk_assessment = "✅ **正常**:目前影像未顯示明顯肺炎特徵,若有呼吸道症狀仍請留意。"
return result_text, visualization, risk_assessment
# ==================== 4. Gradio 介面 ====================
with gr.Blocks(title="肺炎檢測 AI 輔助系統") as demo:
gr.Markdown("# 🏥 肺炎檢測 AI 輔助系統")
gr.Markdown("這是一個基於深度學習的胸部 X 光影像分析工具。請上傳一張 X 光片,模型將分析是否存在肺炎徵兆,並顯示模型關注的區域。")
with gr.Row():
with gr.Column(scale=1):
img_input = gr.Image(label="1. 上傳胸部 X 光影像", type="numpy")
btn = gr.Button("🔍 開始檢測", variant="primary")
with gr.Column(scale=1):
pneumonia_out = gr.Textbox(label="檢測結果")
cam_out = gr.Image(label="模型關注區域(紅色代表重點關注部位)")
risk_out = gr.Markdown(label="專業建議與風險評估")
# 設定按鈕邏輯
btn.click(
fn=predict_pneumonia,
inputs=[img_input],
outputs=[pneumonia_out, cam_out, risk_out]
)
gr.Markdown("---")
gr.Markdown("⚠️ **免責聲明**:本工具僅作為研究及技術展示用途,**不可**取代專業醫師的醫療診斷。如果您感到身體不適,請務必尋求正式醫療協助。")
# ==================== 5. 啟動服務 ====================
if __name__ == "__main__":
# 部署在 Hugging Face 時 share=True 是可選的,建議維持預設
demo.launch()