Abdullah4747 commited on
Commit
6be1063
·
verified ·
1 Parent(s): d5b5853

Upload app.py

Browse files
Files changed (1) hide show
  1. src/app.py +349 -0
src/app.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import streamlit as st
3
+ import tensorflow as tf
4
+ from PIL import Image
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ import pandas as pd
9
+ import cv2
10
+ ##
11
+
12
+ # Load saved model (custom CNN)
13
+ @st.cache_resource
14
+ def load_model():
15
+ """Loads and returns the trained Keras model."""
16
+ model = tf.keras.models.load_model('best_model.h5')
17
+ return model
18
+
19
+ model = load_model()
20
+
21
+ # Class labels with descriptions
22
+ tumor_info = {
23
+ 'glioma': {
24
+ 'description': "Glioma is a type of tumor that occurs in the brain and spinal cell. Gliomas begin in the gluey supportive cells (glial cells) that surround nerve cells.",
25
+ 'prevalence': "Most common malignant brain tumor in adults",
26
+ 'treatment': "Surgery, radiation therapy, chemotherapy"
27
+ },
28
+ 'meningioma': {
29
+ 'description': "Meningioma is a tumor that arises from the meninges — the membranes that surround the brain and spinal cord. Most meningiomas are non-cancerous (benign).",
30
+ 'prevalence': "Most common primary brain tumor (30% of all brain tumors)",
31
+ 'treatment': "Monitoring, surgery, radiation therapy"
32
+ },
33
+ 'no_tumor': {
34
+ 'description': "No signs of tumor detected in the MRI scan. Normal brain tissue appears healthy.",
35
+ 'prevalence': "Normal brain MRI",
36
+ 'treatment': "No treatment needed"
37
+ },
38
+ 'pituitary': {
39
+ 'description': "Pituitary tumors are abnormal growths that develop in the pituitary gland. Most are benign and many don't cause symptoms.",
40
+ 'prevalence': "10-15% of all primary brain tumors",
41
+ 'treatment': "Medication, surgery, radiation therapy"
42
+ }
43
+ }
44
+
45
+ def generate_gradcam(model, img_array, interpolant=0.5):
46
+ """
47
+ Generates Grad-CAM visualization for a custom CNN model.
48
+ Args:
49
+ model: Compiled Keras model.
50
+ img_array: Preprocessed image array (1, 224, 224, 3).
51
+ interpolant: Opacity for heatmap overlay (0-1).
52
+ Returns:
53
+ tuple: (superimposed_img, heatmap) or (None, error_message).
54
+ """
55
+ try:
56
+ # Find the last convolutional layer automatically
57
+ last_conv_layer = None
58
+ for layer in reversed(model.layers):
59
+ if isinstance(layer, tf.keras.layers.Conv2D):
60
+ last_conv_layer = layer
61
+ break
62
+ if last_conv_layer is None:
63
+ raise ValueError("No Conv2D layer found in the model.")
64
+
65
+ # Define a symbolic input tensor for the new `gradient_model`.
66
+ grad_model_input = tf.keras.Input(shape=img_array.shape[1:])
67
+
68
+ # Reconstruct the forward pass *symbolically* through the original model's layers
69
+ x = grad_model_input
70
+ last_conv_output_symbolic = None
71
+
72
+ for layer in model.layers:
73
+ x = layer(x)
74
+ if layer == last_conv_layer:
75
+ last_conv_output_symbolic = x
76
+
77
+ final_output_symbolic = x
78
+
79
+ if last_conv_output_symbolic is None:
80
+ raise ValueError(f"Could not find the symbolic output for the last convolutional layer ('{last_conv_layer.name}').")
81
+
82
+ gradient_model = tf.keras.models.Model(
83
+ inputs=grad_model_input,
84
+ outputs=[last_conv_output_symbolic, final_output_symbolic]
85
+ )
86
+
87
+ with tf.GradientTape() as tape:
88
+ inputs_for_tape = tf.cast(img_array, tf.float32)
89
+ conv_outputs, predictions = gradient_model(inputs_for_tape)
90
+
91
+ # Use argmax to get the predicted class index
92
+ pred_index = tf.argmax(predictions[0])
93
+
94
+ # Extract the loss for the predicted class
95
+ loss = predictions[:, pred_index]
96
+
97
+ grads = tape.gradient(loss, conv_outputs)
98
+
99
+ # --- Crucial Error Handling for Gradients & Heatmap ---
100
+ if grads is None:
101
+ return None, "Grad-CAM failed: Gradients are None. This might indicate an issue with differentiability or an unusual model state for this input."
102
+
103
+ # Global average pooling of gradients
104
+ pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))
105
+
106
+ # Weight the conv outputs by pooled gradients
107
+ conv_outputs = conv_outputs[0] # Remove batch dimension
108
+ heatmap = tf.reduce_sum(conv_outputs * pooled_grads, axis=-1)
109
+
110
+ # Normalize the heatmap
111
+ heatmap = tf.maximum(heatmap, 0) # Apply ReLU to heatmap
112
+
113
+ # Check if heatmap is all zeros AFTER ReLU. If so, normalization will fail.
114
+ max_heatmap_value = tf.math.reduce_max(heatmap)
115
+ if tf.equal(max_heatmap_value, 0):
116
+ return None, "Grad-CAM failed: Heatmap is entirely zero, cannot normalize. This may happen if the model's activations or gradients are all zero for this input and predicted class."
117
+
118
+ heatmap = heatmap / max_heatmap_value # Normalize by max value
119
+ heatmap = heatmap.numpy()
120
+
121
+ # Resize heatmap to original image size
122
+ heatmap = cv2.resize(heatmap, (img_array.shape[2], img_array.shape[1]))
123
+
124
+ # Convert to RGB heatmap
125
+ heatmap_colored = cv2.applyColorMap(np.uint8(255 * heatmap), cv2.COLORMAP_JET)
126
+ heatmap_colored = cv2.cvtColor(heatmap_colored, cv2.COLOR_BGR2RGB)
127
+
128
+ # Prepare original image
129
+ img = np.uint8(img_array[0] * 255)
130
+
131
+ # Superimpose heatmap on original image
132
+ superimposed_img = cv2.addWeighted(
133
+ img, 1 - interpolant,
134
+ heatmap_colored, interpolant,
135
+ 0
136
+ )
137
+
138
+ return superimposed_img, heatmap
139
+
140
+ except Exception as e:
141
+ return None, f"Grad-CAM failed: {str(e)}"
142
+
143
+ # --- Streamlit UI (No changes needed below this point) ---
144
+ st.set_page_config(
145
+ page_title="Brain Tumor MRI Classifier",
146
+ page_icon="🧠",
147
+ layout="wide",
148
+ initial_sidebar_state="expanded"
149
+ )
150
+
151
+ # Custom CSS
152
+ st.markdown("""
153
+ <style>
154
+ .reportview-container {
155
+ background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
156
+ color: white;
157
+ }
158
+ .sidebar .sidebar-content {
159
+ background: #0c151c !important;
160
+ }
161
+ .stButton>button {
162
+ background-color: #4CAF50;
163
+ color: white;
164
+ border-radius: 8px;
165
+ padding: 10px 24px;
166
+ font-weight: bold;
167
+ transition: all 0.3s;
168
+ }
169
+ .stButton>button:hover {
170
+ background-color: #3d8b40;
171
+ transform: scale(1.05);
172
+ }
173
+ .prediction-highlight {
174
+ font-size: 28px;
175
+ font-weight: bold;
176
+ color: #4CAF50;
177
+ text-shadow: 0 0 8px rgba(76, 175, 80, 0.4);
178
+ }
179
+ .stTabs [data-baseweb="tab"] {
180
+ background: rgba(30, 136, 229, 0.2) !important;
181
+ border-radius: 8px !important;
182
+ padding: 10px 20px !important;
183
+ transition: all 0.3s;
184
+ }
185
+ .stTabs [aria-selected="true"] {
186
+ background: #1e88e5 !important;
187
+ font-weight: bold;
188
+ }
189
+ </style>
190
+ """, unsafe_allow_html=True)
191
+
192
+ # Title and description
193
+ st.title("🧠 Brain Tumor MRI Classification")
194
+ st.markdown("This AI-powered tool analyzes brain MRI scans to detect and classify tumors using a Convolutional Neural Network (CNN). Upload an MRI image to get a prediction and detailed insights.")
195
+
196
+ # Sidebar with info and metrics
197
+ with st.sidebar:
198
+ st.header("Model Information")
199
+ st.markdown("""
200
+ - **Model Architecture**: Custom CNN
201
+ - **Training Data**: 1,695 MRI scans
202
+ - **Test Accuracy**: 76.0%
203
+ - **Balanced Accuracy**: 74.8%
204
+ - **Macro F1-Score**: 74.5%
205
+ """)
206
+ st.divider()
207
+ st.header("Performance by Tumor Type")
208
+ with st.expander("Glioma"):
209
+ st.markdown("**Precision**: 0.78 | **Recall**: 0.93 | **F1-Score**: 0.85")
210
+ with st.expander("Meningioma"):
211
+ st.markdown("**Precision**: 0.65 | **Recall**: 0.51 | **F1-Score**: 0.57")
212
+ with st.expander("No Tumor"):
213
+ st.markdown("**Precision**: 0.89 | **Recall**: 0.63 | **F1-Score**: 0.74")
214
+ with st.expander("Pituitary"):
215
+ st.markdown("**Precision**: 0.75 | **Recall**: 0.93 | **F1-Score**: 0.83")
216
+ st.divider()
217
+ st.warning("⚠️ **Disclaimer**: This tool is for educational purposes only. Always consult a medical professional for diagnosis.")
218
+
219
+ # Main content area
220
+ col1, col2 = st.columns([1, 1])
221
+
222
+ if 'prediction_made' not in st.session_state:
223
+ st.session_state['prediction_made'] = False
224
+ st.session_state['predicted_class'] = None
225
+ st.session_state['confidence'] = None
226
+ st.session_state['gradcam_img'] = None
227
+ st.session_state['heatmap_error'] = None
228
+ st.session_state['prediction_probs'] = None
229
+
230
+
231
+ with col1:
232
+ st.subheader("Upload MRI Scan")
233
+ uploaded_file = st.file_uploader(
234
+ "Choose a brain MRI image (JPEG)",
235
+ type=["jpg","jpeg"],
236
+ label_visibility="collapsed"
237
+ )
238
+
239
+ if uploaded_file is not None:
240
+ # --- Single Analysis Block ---
241
+ image = Image.open(uploaded_file).convert('RGB')
242
+ uploaded_file.close()
243
+ img_display = image.copy()
244
+ image = image.resize((224, 224))
245
+ img_array = np.array(image) / 255.0
246
+ img_array = np.expand_dims(img_array, axis=0)
247
+
248
+ # Display uploaded image in the second column
249
+ with col2:
250
+ st.subheader("Uploaded MRI Scan")
251
+ st.image(img_display, caption="Original MRI", use_container_width=True)
252
+
253
+ with st.spinner('Analyzing MRI scan...'):
254
+ prediction = model.predict(img_array, verbose=0)
255
+
256
+ predicted_class = list(tumor_info.keys())[np.argmax(prediction)]
257
+ confidence = np.max(prediction) * 100
258
+
259
+ gradcam_img, heatmap_status = generate_gradcam(model, img_array, interpolant=0.6)
260
+
261
+ st.session_state['prediction_made'] = True
262
+ st.session_state['predicted_class'] = predicted_class
263
+ st.session_state['confidence'] = confidence
264
+ st.session_state['gradcam_img'] = gradcam_img
265
+ st.session_state['heatmap_error'] = heatmap_status
266
+ st.session_state['prediction_probs'] = prediction[0]
267
+
268
+ # --- Results Section (display only if prediction was made) ---
269
+ if st.session_state['prediction_made']:
270
+ st.divider()
271
+
272
+ col_res1, col_res2 = st.columns([1, 2])
273
+
274
+ with col_res1:
275
+ st.subheader("AI Analysis Result")
276
+ st.markdown(f"<div class='prediction-highlight'>{st.session_state['predicted_class'].replace('_', ' ').title()}</div>", unsafe_allow_html=True)
277
+
278
+ st.metric("Confidence Level", f"{st.session_state['confidence']:.2f}%")
279
+ st.progress(int(st.session_state['confidence']))
280
+
281
+ info = tumor_info[st.session_state['predicted_class']]
282
+ with st.expander("Tumor Information", expanded=True):
283
+ st.markdown(f"**Description**: {info['description']}")
284
+ st.markdown(f"**Prevalence**: {info['prevalence']}")
285
+ st.markdown(f"**Treatment**: {info['treatment']}")
286
+
287
+ st.info("💡 **Clinical Note**: The AI analysis should be reviewed by a qualified radiologist. It is not a substitute for professional medical diagnosis.")
288
+
289
+ with col_res2:
290
+ st.subheader("Model Insights")
291
+
292
+ tab1, tab2, tab3 = st.tabs(["📊 Probability Distribution", "🔥 Attention Map", "📈 Performance Metrics"])
293
+
294
+ with tab1:
295
+ classes = list(tumor_info.keys())
296
+ probs = st.session_state['prediction_probs'] * 100
297
+
298
+ fig, ax = plt.subplots(figsize=(10, 5))
299
+ colors = ['#1e88e5' if c != st.session_state['predicted_class'] else '#4CAF50' for c in classes]
300
+ bars = ax.barh([c.replace('_', ' ').title() for c in classes], probs, color=colors)
301
+ ax.set_xlabel('Probability (%)')
302
+ ax.set_title('Prediction Confidence Distribution', fontsize=14, fontweight='bold')
303
+ ax.set_xlim(0, 100)
304
+
305
+ for bar, prob in zip(bars, probs):
306
+ ax.text(min(prob + 2, 98), bar.get_y() + bar.get_height()/2, f'{prob:.1f}%',
307
+ ha='left', va='center', color='white', fontweight='bold', fontsize=12)
308
+ st.pyplot(fig)
309
+
310
+ with tab2:
311
+ if st.session_state['gradcam_img'] is not None:
312
+ st.image(st.session_state['gradcam_img'], caption="AI Attention Map (Grad-CAM)", use_container_width=True)
313
+ st.markdown("The highlighted areas indicate the regions the model focused on to make its prediction.")
314
+ else:
315
+ st.warning(st.session_state['heatmap_error']) # Display error message from generate_gradcam
316
+
317
+ with tab3:
318
+ cnn_cm = np.array([
319
+ [74, 6, 0, 0], # glioma
320
+ [17, 32, 4, 10], # meningioma
321
+ [1, 10, 31, 7], # no_tumor
322
+ [3, 1, 0, 50] # pituitary
323
+ ])
324
+
325
+ st.write("**Custom CNN Confusion Matrix (Test Set)**")
326
+ fig, ax = plt.subplots(figsize=(8, 6))
327
+ sns.heatmap(
328
+ cnn_cm, annot=True, fmt='d', cmap='Blues',
329
+ xticklabels=[c.replace('_', ' ').title() for c in tumor_info.keys()],
330
+ yticklabels=[c.replace('_', ' ').title() for c in tumor_info.keys()],
331
+ ax=ax
332
+ )
333
+ ax.set_xlabel('Predicted Label', fontsize=12)
334
+ ax.set_ylabel('True Label', fontsize=12)
335
+ st.pyplot(fig)
336
+
337
+ st.write("**Performance by Class:**")
338
+ class_data = {
339
+ 'Tumor Type': ['Glioma', 'Meningioma', 'No Tumor', 'Pituitary'],
340
+ 'Precision': [0.78, 0.65, 0.89, 0.75],
341
+ 'Recall': [0.93, 0.51, 0.63, 0.93],
342
+ 'F1-Score': [0.85, 0.57, 0.74, 0.83]
343
+ }
344
+ df = pd.DataFrame(class_data).set_index('Tumor Type')
345
+ st.dataframe(df.style.format("{:.2f}").highlight_max(axis=0, color='rgba(76, 175, 80, 0.3)'))
346
+
347
+ # Footer
348
+ st.markdown("---")
349
+ st.caption("© 2025 Brain Tumor Classification System | For Research Use Only")