File size: 4,839 Bytes
544060a
 
 
 
 
 
 
c1187c1
e1ef7fb
 
 
544060a
c1187c1
544060a
 
9ad6f22
c1187c1
 
544060a
c1187c1
544060a
9ad6f22
c1187c1
 
 
 
 
 
 
 
 
544060a
 
 
c1187c1
544060a
 
29d32e6
544060a
 
 
 
 
 
 
29d32e6
544060a
 
 
 
c1187c1
544060a
 
 
c1187c1
544060a
 
 
 
 
 
 
29d32e6
544060a
c1187c1
 
544060a
 
c1187c1
 
544060a
 
c1187c1
 
 
544060a
c1187c1
29d32e6
c1187c1
29d32e6
c1187c1
544060a
c1187c1
544060a
29d32e6
544060a
29d32e6
c1187c1
 
 
544060a
 
 
c1187c1
 
544060a
 
c1187c1
 
 
544060a
c1187c1
544060a
29d32e6
 
 
544060a
 
 
c1187c1
544060a
c1187c1
544060a
c1187c1
 
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
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()