Views
No views yet
| Size | Params | F1 Score | mAPᵛᵃᴵ | Accuracy | ROC-AUC |
|---|---|---|---|---|---|
| 80x80x80 | 8.32M | 68.27% | 51.28% | 51.82% | 54.39% |
streamlit run test.py1"""
2Vbai-3D 1.0 Real-Time MRI Monitoring System
3Streamlit-based 3D MRI slice-by-slice visualization and AI prediction system
4
5Usage:
6 streamlit run {this_file}.py
7
8Features:
9 - 3D MRI (.nii/.nii.gz) file upload
10 - Slice-by-slice visualization (Axial, Coronal, Sagittal)
11 - Real-time AI prediction (CN, MCI, AD)
12 - Probability distributions
13 - Interactive visualization
14 - Multi-view mode
15"""
16
17import streamlit as st
18import numpy as np
19import nibabel as nib
20import torch
21import torch.nn as nn
22import torch.nn.functional as F
23from PIL import Image
24import matplotlib.pyplot as plt
25import tempfile
26import os
27import time
28
29
30class ResBlock3D(nn.Module):
31 def __init__(self, in_channels, out_channels, stride=1):
32 super(ResBlock3D, self).__init__()
33 self.conv1 = nn.Conv3d(in_channels, out_channels, kernel_size=3,
34 stride=stride, padding=1, bias=False)
35 self.bn1 = nn.BatchNorm3d(out_channels)
36 self.conv2 = nn.Conv3d(out_channels, out_channels, kernel_size=3,
37 stride=1, padding=1, bias=False)
38 self.bn2 = nn.BatchNorm3d(out_channels)
39
40 self.shortcut = nn.Sequential()
41 if stride != 1 or in_channels != out_channels:
42 self.shortcut = nn.Sequential(
43 nn.Conv3d(in_channels, out_channels, kernel_size=1,
44 stride=stride, bias=False),
45 nn.BatchNorm3d(out_channels)
46 )
47 self.dropout = nn.Dropout3d(0.2)
48
49 def forward(self, x):
50 residual = x
51 out = F.relu(self.bn1(self.conv1(x)))
52 out = self.bn2(self.conv2(out))
53 out = self.dropout(out)
54 out += self.shortcut(residual)
55 out = F.relu(out)
56 return out
57
58
59class SEBlock3D(nn.Module):
60 def __init__(self, channels, reduction=16):
61 super(SEBlock3D, self).__init__()
62 self.avg_pool = nn.AdaptiveAvgPool3d(1)
63 self.fc = nn.Sequential(
64 nn.Linear(channels, channels // reduction, bias=False),
65 nn.ReLU(inplace=True),
66 nn.Linear(channels // reduction, channels, bias=False),
67 nn.Sigmoid()
68 )
69
70 def forward(self, x):
71 b, c, _, _, _ = x.size()
72 y = self.avg_pool(x).view(b, c)
73 y = self.fc(y).view(b, c, 1, 1, 1)
74 return x * y.expand_as(x)
75
76
77class ImprovedMRINet(nn.Module):
78 def __init__(self, num_classes=3, in_channels=1):
79 super(ImprovedMRINet, self).__init__()
80 self.conv1 = nn.Conv3d(in_channels, 32, kernel_size=7, stride=2, padding=3, bias=False)
81 self.bn1 = nn.BatchNorm3d(32)
82 self.maxpool = nn.MaxPool3d(kernel_size=3, stride=2, padding=1)
83
84 self.layer1 = self._make_layer(32, 64, 2, stride=1)
85 self.se1 = SEBlock3D(64)
86 self.layer2 = self._make_layer(64, 128, 2, stride=2)
87 self.se2 = SEBlock3D(128)
88 self.layer3 = self._make_layer(128, 256, 2, stride=2)
89 self.se3 = SEBlock3D(256)
90
91 self.global_avg_pool = nn.AdaptiveAvgPool3d(1)
92 self.global_max_pool = nn.AdaptiveMaxPool3d(1)
93
94 self.fc = nn.Sequential(
95 nn.Dropout(0.5),
96 nn.Linear(256 * 2, 256),
97 nn.ReLU(inplace=True),
98 nn.Dropout(0.3),
99 nn.Linear(256, num_classes)
100 )
101
102 def _make_layer(self, in_channels, out_channels, num_blocks, stride):
103 layers = []
104 layers.append(ResBlock3D(in_channels, out_channels, stride))
105 for _ in range(1, num_blocks):
106 layers.append(ResBlock3D(out_channels, out_channels, 1))
107 return nn.Sequential(*layers)
108
109 def forward(self, x):
110 x = F.relu(self.bn1(self.conv1(x)))
111 x = self.maxpool(x)
112
113 x = self.layer1(x)
114 x = self.se1(x)
115 x = self.layer2(x)
116 x = self.se2(x)
117 x = self.layer3(x)
118 x = self.se3(x)
119
120 avg_pool = self.global_avg_pool(x).view(x.size(0), -1)
121 max_pool = self.global_max_pool(x).view(x.size(0), -1)
122 x = torch.cat([avg_pool, max_pool], dim=1)
123
124 x = self.fc(x)
125 return x
126
127
128def load_and_preprocess_nifti(file_path, target_shape=(80, 80, 80)):
129 try:
130 img = nib.load(file_path)
131 data = img.get_fdata()
132 except Exception as e:
133 st.error(f"File loading error: {e}")
134 return None
135
136 data = np.nan_to_num(data, nan=0.0, posinf=0.0, neginf=0.0)
137 brain_mask = data > data.mean()
138
139 if brain_mask.sum() > 0:
140 brain_pixels = data[brain_mask]
141 p1, p99 = np.percentile(brain_pixels, [1, 99])
142 data = np.clip(data, p1, p99)
143
144 mean = brain_pixels.mean()
145 std = brain_pixels.std()
146 if std > 1e-6:
147 data = (data - mean) / (std + 1e-8)
148 else:
149 data = data - mean
150 else:
151 mean = data.mean()
152 std = data.std()
153 if std > 1e-6:
154 data = (data - mean) / (std + 1e-8)
155 else:
156 data = data - mean
157
158 data_min, data_max = data.min(), data.max()
159 if abs(data_max - data_min) > 1e-6:
160 data = (data - data_min) / (data_max - data_min + 1e-8)
161 else:
162 data = np.zeros_like(data)
163
164 data = np.clip(data, 0, 1)
165 data = np.nan_to_num(data, nan=0.0, posinf=1.0, neginf=0.0)
166
167 return data
168
169
170def resize_volume(volume, target_shape):
171 volume_tensor = torch.from_numpy(volume).float().unsqueeze(0).unsqueeze(0)
172 resized = F.interpolate(volume_tensor, size=target_shape,
173 mode='trilinear', align_corners=False)
174 return resized.squeeze(0).squeeze(0).numpy()
175
176
177@st.cache_resource
178def load_model(model_path):
179 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
180 model = ImprovedMRINet(num_classes=3).to(device)
181
182 try:
183 checkpoint = torch.load(model_path, map_location=device)
184 if 'model_state_dict' in checkpoint:
185 model.load_state_dict(checkpoint['model_state_dict'])
186 else:
187 model.load_state_dict(checkpoint)
188 model.eval()
189 return model, device
190 except Exception as e:
191 st.error(f"Model loading error: {e}")
192 return None, device
193
194
195def predict_mri(model, device, volume, target_shape=(80, 80, 80)):
196 if volume.shape != target_shape:
197 volume = resize_volume(volume, target_shape)
198
199 volume_tensor = torch.from_numpy(volume).float().unsqueeze(0).unsqueeze(0).to(device)
200
201 with torch.no_grad():
202 outputs = model(volume_tensor)
203 probs = F.softmax(outputs, dim=1)
204 pred = torch.argmax(probs, dim=1)
205
206 return pred.item(), probs.cpu().numpy()[0]
207
208
209def create_slice_image(slice_2d, colormap='gray'):
210 slice_norm = ((slice_2d - slice_2d.min()) / (slice_2d.max() - slice_2d.min() + 1e-8) * 255).astype(np.uint8)
211
212 if colormap == 'gray':
213 return Image.fromarray(slice_norm, mode='L')
214 else:
215 cmap = plt.get_cmap(colormap)
216 colored = cmap(slice_norm / 255.0)
217 return Image.fromarray((colored[:, :, :3] * 255).astype(np.uint8))
218
219
220def plot_probability_bars(probs, class_names):
221 fig, ax = plt.subplots(figsize=(10, 4))
222
223 colors = ['#2ecc71', '#f39c12', '#e74c3c']
224 bars = ax.barh(class_names, probs, color=colors)
225
226 for i, (bar, prob) in enumerate(zip(bars, probs)):
227 width = bar.get_width()
228 ax.text(width, bar.get_y() + bar.get_height()/2,
229 f'{prob*100:.2f}%',
230 ha='left', va='center', fontweight='bold', fontsize=12)
231
232 ax.set_xlim([0, 1])
233 ax.set_xlabel('Probability', fontsize=12, fontweight='bold')
234 ax.set_title('Class Prediction Probabilities', fontsize=14, fontweight='bold')
235 ax.grid(axis='x', alpha=0.3)
236
237 return fig
238
239
240def main():
241 st.set_page_config(
242 page_title="Vbai-3D 1.0 Monitoring",
243 page_icon="🧠",
244 layout="wide",
245 initial_sidebar_state="expanded"
246 )
247
248 st.title("🧠 Vbai-3D 1.0 - Real-Time MRI Monitoring System")
249 st.markdown("---")
250
251 with st.sidebar:
252 st.header("⚙️ Settings")
253
254 model_path = st.text_input(
255 "Model Path",
256 value="Vbai-3D 1.0.pth/model/path"
257 )
258
259 if st.button("🔄 Load Model"):
260 with st.spinner("Loading model..."):
261 st.session_state.model, st.session_state.device = load_model(model_path)
262 if st.session_state.model is not None:
263 st.success("✅ Model loaded successfully!")
264 st.info(f"Device: {st.session_state.device}")
265
266 st.markdown("---")
267
268 st.subheader("🎨 Visualization")
269 colormap = st.selectbox(
270 "Color Palette",
271 ['gray', 'viridis', 'plasma', 'inferno', 'magma', 'hot', 'cool']
272 )
273
274 multi_view = st.checkbox("Multi-View Mode", value=False)
275
276 st.markdown("---")
277
278 st.subheader("📐 Model Parameters")
279 target_shape = (80, 80, 80)
280 st.info(f"Target Size: {target_shape}")
281
282 col1, col2 = st.columns([2, 1])
283
284 with col1:
285 st.header("📂 File Upload")
286
287 uploaded_file = st.file_uploader(
288 "Upload 3D MRI file (.nii or .nii.gz)",
289 type=['nii', 'nii.gz'],
290 help="Select a NIfTI format 3D MRI file"
291 )
292
293 with col2:
294 st.header("ℹ️ Information")
295 st.info("""
296 **Supported Classes:**
297 - 🟢 CN: Cognitively Normal
298 - 🟡 MCI: Mild Cognitive Impairment
299 - 🔴 AD: Alzheimer's Disease
300 """)
301
302 if uploaded_file is not None:
303 with tempfile.NamedTemporaryFile(delete=False, suffix='.nii') as tmp:
304 tmp.write(uploaded_file.getbuffer())
305 tmp_path = tmp.name
306
307 try:
308 st.success(f"✅ File uploaded: {uploaded_file.name}")
309
310 progress_bar = st.progress(0)
311 status_text = st.empty()
312
313 status_text.text("Reading file...")
314 progress_bar.progress(20)
315 data = load_and_preprocess_nifti(tmp_path, target_shape)
316
317 if data is None:
318 st.error("File could not be loaded!")
319 st.stop()
320
321 progress_bar.progress(40)
322 status_text.text("Preprocessing data...")
323
324 st.info(f"📊 Data Size: {data.shape}")
325
326 if 'model' in st.session_state and st.session_state.model is not None:
327 status_text.text("Running AI prediction...")
328 progress_bar.progress(60)
329
330 start_time = time.time()
331 pred_class, probs = predict_mri(
332 st.session_state.model,
333 st.session_state.device,
334 data,
335 target_shape
336 )
337 inference_time = time.time() - start_time
338
339 progress_bar.progress(80)
340
341 class_names = ['CN (Normal)', 'MCI (Mild)', 'AD (Alzheimer)']
342 class_colors = ['🟢', '🟡', '🔴']
343
344 st.markdown("---")
345 st.header("🎯 AI Prediction Results")
346
347 col1, col2, col3 = st.columns(3)
348
349 with col1:
350 st.metric(
351 "Predicted Class",
352 f"{class_colors[pred_class]} {class_names[pred_class]}"
353 )
354
355 with col2:
356 st.metric(
357 "Confidence Score",
358 f"{probs[pred_class]*100:.2f}%"
359 )
360
361 with col3:
362 st.metric(
363 "Prediction Time",
364 f"{inference_time:.3f} sec"
365 )
366
367 st.subheader("📊 Class Probabilities")
368 fig = plot_probability_bars(probs, class_names)
369 st.pyplot(fig)
370
371 entropy = -np.sum(probs * np.log(probs + 1e-10))
372 max_entropy = -np.log(1.0 / 3)
373 uncertainty = entropy / max_entropy
374
375 col1, col2, col3 = st.columns(3)
376 with col1:
377 st.metric("1st Choice", f"{class_names[np.argsort(probs)[-1]]}")
378 with col2:
379 st.metric("2nd Choice", f"{class_names[np.argsort(probs)[-2]]}")
380 with col3:
381 st.metric("Uncertainty", f"{uncertainty:.3f}")
382
383 if uncertainty > 0.5:
384 st.warning("⚠️ Model is uncertain! Indecisive between different classes.")
385 else:
386 st.success("✅ Model made a confident prediction.")
387
388 else:
389 st.warning("⚠️ Model not loaded. Please load the model from sidebar.")
390
391 progress_bar.progress(100)
392 status_text.text("Ready!")
393
394 st.markdown("---")
395 st.header("🔍 Slice Visualization")
396
397 if multi_view:
398 st.subheader("Multi-View (Axial, Coronal, Sagittal)")
399
400 col1, col2, col3 = st.columns(3)
401 with col1:
402 axial_idx = st.slider("Axial (Z)", 0, data.shape[2]-1, data.shape[2]//2)
403 with col2:
404 coronal_idx = st.slider("Coronal (Y)", 0, data.shape[1]-1, data.shape[1]//2)
405 with col3:
406 sagittal_idx = st.slider("Sagittal (X)", 0, data.shape[0]-1, data.shape[0]//2)
407
408 col1, col2, col3 = st.columns(3)
409
410 with col1:
411 st.markdown("**Axial (Z-axis)**")
412 axial_slice = data[:, :, axial_idx]
413 axial_img = create_slice_image(axial_slice, colormap)
414 st.image(axial_img, caption=f"Axial Slice #{axial_idx}", use_container_width=True)
415
416 with col2:
417 st.markdown("**Coronal (Y-axis)**")
418 coronal_slice = data[:, coronal_idx, :]
419 coronal_img = create_slice_image(coronal_slice, colormap)
420 st.image(coronal_img, caption=f"Coronal Slice #{coronal_idx}", use_container_width=True)
421
422 with col3:
423 st.markdown("**Sagittal (X-axis)**")
424 sagittal_slice = data[sagittal_idx, :, :]
425 sagittal_img = create_slice_image(sagittal_slice, colormap)
426 st.image(sagittal_img, caption=f"Sagittal Slice #{sagittal_idx}", use_container_width=True)
427
428 else:
429 axis = st.radio(
430 "Select Slice Axis",
431 ['Axial (Z)', 'Coronal (Y)', 'Sagittal (X)'],
432 horizontal=True
433 )
434
435 axis_map = {
436 'Axial (Z)': 2,
437 'Coronal (Y)': 1,
438 'Sagittal (X)': 0
439 }
440 axis_idx = axis_map[axis]
441
442 slice_idx = st.slider(
443 "Slice Index",
444 0,
445 data.shape[axis_idx] - 1,
446 data.shape[axis_idx] // 2,
447 help=f"Select a value between 0 and {data.shape[axis_idx]-1}"
448 )
449
450 if axis_idx == 2:
451 slice_2d = data[:, :, slice_idx]
452 elif axis_idx == 1:
453 slice_2d = data[:, slice_idx, :]
454 else:
455 slice_2d = data[slice_idx, :, :]
456
457 slice_img = create_slice_image(slice_2d, colormap)
458
459 col1, col2 = st.columns([3, 1])
460
461 with col1:
462 st.image(
463 slice_img,
464 caption=f"{axis} - Slice #{slice_idx}",
465 use_container_width=True
466 )
467
468 with col2:
469 st.markdown("**Statistics**")
470 st.metric("Min", f"{slice_2d.min():.3f}")
471 st.metric("Max", f"{slice_2d.max():.3f}")
472 st.metric("Mean", f"{slice_2d.mean():.3f}")
473 st.metric("Std", f"{slice_2d.std():.3f}")
474
475 st.markdown("---")
476 st.subheader("💾 Download Options")
477
478 col1, col2 = st.columns(2)
479
480 with col1:
481 if st.button("📊 Generate Report"):
482 try:
483 pred_class_name = class_names[pred_class] if 'pred_class' in locals() and pred_class is not None else 'N/A'
484 pred_confidence = f"{probs[pred_class]*100:.2f}" if 'probs' in locals() and 'pred_class' in locals() and probs is not None and pred_class is not None else 'N/A'
485 uncertainty_value = f"{uncertainty:.3f}" if 'uncertainty' in locals() and uncertainty is not None else 'N/A'
486 prob_cn = f"{probs[0]*100:.2f}" if 'probs' in locals() and probs is not None else 'N/A'
487 prob_mci = f"{probs[1]*100:.2f}" if 'probs' in locals() and probs is not None else 'N/A'
488 prob_ad = f"{probs[2]*100:.2f}" if 'probs' in locals() and probs is not None else 'N/A'
489
490 report = f"""
491VBAI-3D 1.0 - MRI Analysis Report
492================================
493
494File: {uploaded_file.name}
495Date: {time.strftime("%Y-%m-%d %H:%M:%S")}
496
497Prediction Results:
498-----------------
499Class: {pred_class_name}
500Confidence: {pred_confidence}%
501Uncertainty: {uncertainty_value}
502
503Probabilities:
504-----------
505CN (Normal): {prob_cn}%
506MCI (Mild): {prob_mci}%
507AD (Alzheimer): {prob_ad}%
508
509Data Information:
510--------------
511Size: {data.shape}
512Min: {data.min():.3f}
513Max: {data.max():.3f}
514Mean: {data.mean():.3f}
515 """
516 st.download_button(
517 label="📥 Download Report",
518 data=report,
519 file_name="mri_report.txt",
520 mime="text/plain"
521 )
522 except Exception as e:
523 st.error(f"Error generating report: {e}")
524
525 progress_bar.empty()
526 status_text.empty()
527
528 except Exception as e:
529 st.error(f"❌ Error occurred: {e}")
530 import traceback
531 st.code(traceback.format_exc())
532
533 finally:
534 if os.path.exists(tmp_path):
535 os.remove(tmp_path)
536
537 else:
538 st.info("👆 Please upload an MRI file")
539
540 with st.expander("📖 User Guide"):
541 st.markdown("""
542 ### Steps:
543 1. **Load model from sidebar**
544 2. **Upload your 3D MRI file (.nii/.nii.gz)**
545 3. **AI prediction will be done automatically**
546 4. **Use sliders to examine slices**
547 5. **Generate report if needed**
548
549 ### Supported Formats:
550 - .nii (NIfTI)
551 - .nii.gz (Compressed NIfTI)
552
553 ### Features:
554 - Real-time AI prediction
555 - 3-axis visualization (Axial, Coronal, Sagittal)
556 - Multi-view mode
557 - Probability analysis
558 - Uncertainty calculation
559 - Report generation
560 """)
561
562 st.markdown("---")
563 st.markdown(
564 "<div style='text-align: center; color: gray;'>"
565 "Vbai-3D 1.0 | Powered by PyTorch & Streamlit | 2025"
566 "</div>",
567 unsafe_allow_html=True
568 )
569
570
571if __name__ == "__main__":
572 main()