File size: 4,510 Bytes
5920b3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import streamlit as st
from PIL import Image
import numpy as np
import torch 
import torch.nn as nn
import timm
from torchvision import transforms
import cv2

# page config 
st.set_page_config(page_title="ManuSpec Medical AI: Pneumonia Detection", layout="wide")

# load saved weights / model
# @st.cache_resource is a decorator that tells Streamlit to run this function only ONCE = 
# loading the model into memory and caching it. This prevents the model from being reloaded
# every time the user interacts with the app, which would be very slow.

@st.cache_resource
def load_model():
    #setup device
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # rebuilt the same model architecture 
    model = timm.create_model('efficientnet_b0', pretrained=False)
    num_features = model.classifier.in_features
    model.classifier = torch.nn.Linear(num_features, 1)

    # load our saved weights into the model struct
    model.load_state_dict(torch.load('pneumonia_model.pth', map_location=device))

    # move the model to the selected device
    model.to(device)

    # set model to evaluation mode 
    model.eval()
    return model, device

model, device = load_model()

# image transformations 
val_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])
])

# grad cam logic 
activations = None
gradients = None

def forward_hook(module, input, output):
    global activations
    activations = output 

def backward_hook(module, grad_in, grad_out):
    global gradients
    gradients = grad_out[0]

def generate_grad_cam(model, input_tensor, original_image):
    target_layer = model.conv_head
    forward_handle = target_layer.register_forward_hook(forward_hook)
    backward_handle = target_layer.register_backward_hook(backward_hook)

    output = model(input_tensor)
    model.zero_grad()
    output.backward()

    pooled_gradients = torch.mean(gradients, dim=[0, 2, 3])
    for i in range(activations.shape[1]):
        activations[:, i, :, :] *= pooled_gradients[i]
    heatmap = torch.mean(activations, dim=1).squeeze().cpu().detach().numpy()
    heatmap = np.maximum(heatmap, 0)
    heatmap /= np.max(heatmap)

    heatmap_resized = cv2.resize(heatmap, (original_image.shape[1], original_image.shape[0]))
    heatmap_colored = cv2.applyColorMap(np.uint8(255 * heatmap_resized), cv2.COLORMAP_JET)

    superimposed_img = heatmap_colored * 0.4 + original_image
    superimposed_img = np.clip(superimposed_img, 0, 255).astype(np.uint8)

    forward_handle.remove()
    backward_handle.remove()

    return superimposed_img, output
    
# header
st.title("ManuSpec Medical AI Pneumonia Detection")
st.write("Upload a chest X-Ray image and the AI model will analyze it for signs of pneumonia")

# sidebar and file uploader 
st.sidebar.header("Upload X-Ray")
uploaded_file = st.sidebar.file_uploader("Choose an image...", type=["jpeg", "jpg", "png"])

if uploaded_file is not None:
    # display uploaded img
    st.sidebar.image(uploaded_file, caption="Uploaded X-Ray", use_container_width=True)

    #convert PIL Image into an opencv 
    pil_image = Image.open(uploaded_file).convert("RGB")
    opencv_image = np.array(pil_image)

    # main content areas
    st.write("---")
    st.header("Analysis")
    col1, col2 = st.columns(2)

    # convert the uploaded file to image that model can understand
    image_tensor = val_transform(pil_image).unsqueeze(0).to(device) 
    
    # get the model's prediction 
    superimposed_image, output = generate_grad_cam(model, image_tensor, opencv_image)
    
    # convert the output to a probability and then binary prediction
    prob = torch.sigmoid(output).item()
    prediction = 1 if prob > 0.5 else 0 

    with col1:
        st.subheader("Diagnosis: ")
        if prediction == 1:
            st.error(f"Pneumonia Detected (Confidence: {prob:.2%})", icon="⚠️")
        else:
            st.success(f"Normal (Confidence: {1-prob:.2%})", icon="✅")
        st.write("*(Model prediction will appear here)*")

    with col2: 
        st.subheader("Model's Focus (Heatmap):")
        superimposed_image_rgb = cv2.cvtColor(superimposed_image, cv2.COLOR_BGR2RGB)
        st.image(superimposed_image_rgb, caption="Heatmap shows areas of interest to the model.", use_container_width=True)

# Launch App: __NV__PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia streamlit run app.py