Sefat33 commited on
Commit
e29d0ed
·
verified ·
1 Parent(s): 1b84290

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -223
app.py CHANGED
@@ -10,26 +10,28 @@ import os
10
  from io import BytesIO
11
  import base64
12
 
13
- # Enhanced CSS with Medical-Professional Color Scheme
14
  st.markdown(
15
  """
16
  <style>
17
- /* Main App Styling */
18
  .stApp {
19
- background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
 
20
  }
21
 
22
- /* Header Styling */
23
  .main-header {
24
- background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
25
  color: white;
26
  padding: 1.5rem;
27
  border-radius: 12px;
28
  margin-bottom: 2rem;
29
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
 
30
  }
31
 
32
- /* Flex Container for Equal Height Columns */
33
  .flex-row {
34
  display: flex;
35
  gap: 2rem;
@@ -42,14 +44,16 @@ st.markdown(
42
  flex-direction: column;
43
  }
44
 
45
- /* Medical Information Cards */
46
  .medical-card {
47
- background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
48
  padding: 1.5rem;
49
  border-radius: 12px;
50
  border-left: 4px solid #3b82f6;
51
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
52
  flex-grow: 1;
 
 
53
  }
54
 
55
  .medical-card h3 {
@@ -58,88 +62,62 @@ st.markdown(
58
  padding-bottom: 0.5rem;
59
  }
60
 
61
- /* Remove conflicting prediction card styles */
62
- .prediction-card, .prediction-high, .prediction-medium, .prediction-low {
63
- display: none;
 
 
 
 
 
 
64
  }
65
 
66
- /* Enhanced Image Processing Steps */
67
  .processing-container {
68
  background: white;
69
  border-radius: 16px;
70
  padding: 2rem;
71
  margin: 2rem 0;
72
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
73
  border: 1px solid #e2e8f0;
 
74
  }
75
 
76
- /* LIME Analysis Container */
77
  .lime-container {
78
  background: white;
79
  border-radius: 16px;
80
  padding: 2rem;
81
  margin: 2rem 0;
82
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
83
- border: 1px solid #e2e8f0;
84
- }
85
-
86
- /* Enhanced Medical Cards */
87
- .medical-card {
88
- background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
89
- padding: 2rem;
90
- border-radius: 16px;
91
- border-left: 6px solid #3b82f6;
92
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
93
- flex-grow: 1;
94
  border: 1px solid #e2e8f0;
 
95
  }
96
 
97
- .medical-card h3 {
98
- margin-top: 0;
99
- margin-bottom: 1rem;
100
- border-bottom: 2px solid #e2e8f0;
101
- padding-bottom: 0.5rem;
102
- font-size: 1.25rem;
103
- }
104
-
105
- .medical-card li {
106
- margin-bottom: 0.5rem;
107
- padding-left: 0.5rem;
108
- }
109
-
110
- /* Enhanced Button Styling */
111
  .stDownloadButton > button {
112
- background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
113
  color: white;
114
  border: none;
115
  border-radius: 12px;
116
  padding: 0.75rem 1.5rem;
117
  font-weight: 600;
118
  font-size: 1rem;
119
- transition: all 0.3s ease;
120
  width: 100%;
121
  margin-top: 1rem;
 
122
  }
123
 
124
- .stDownloadButton > button:hover {
125
- background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
126
- transform: translateY(-2px);
127
- box-shadow: 0 8px 20px rgba(59, 130, 246, 0.4);
128
- }
129
-
130
- /* Enhanced Spinner */
131
- .stSpinner > div {
132
- border-color: #3b82f6 transparent transparent transparent;
133
- }
134
-
135
- /* Upload Instructions */
136
  .upload-instructions {
137
- background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
138
- border: 2px dashed #3b82f6;
139
  border-radius: 12px;
140
  padding: 3rem;
141
  text-align: center;
142
  margin: 2rem 0;
 
143
  }
144
 
145
  .upload-instructions h3 {
@@ -153,7 +131,7 @@ st.markdown(
153
  margin-bottom: 1rem;
154
  }
155
 
156
- /* Feature Grid */
157
  .feature-grid {
158
  display: grid;
159
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
@@ -167,12 +145,8 @@ st.markdown(
167
  padding: 1.5rem;
168
  text-align: center;
169
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
170
- border: 1px solid #e2e8f2;
171
- transition: transform 0.3s ease;
172
- }
173
-
174
- .feature-card:hover {
175
- transform: translateY(-4px);
176
  }
177
 
178
  .feature-icon {
@@ -192,75 +166,7 @@ st.markdown(
192
  font-size: 0.9rem;
193
  }
194
 
195
- /* Processing Steps Cards */
196
- .processing-step {
197
- background: white;
198
- border-radius: 8px;
199
- padding: 1rem;
200
- margin: 0.5rem 0;
201
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
202
- border-left: 3px solid #3b82f6;
203
- }
204
-
205
- /* Sidebar Styling */
206
- .sidebar-content {
207
- background: rgba(255, 255, 255, 0.95);
208
- border-radius: 12px;
209
- padding: 1rem;
210
- margin: 1rem 0;
211
- border: 1px solid #e2e8f0;
212
- }
213
-
214
- /* Button Styling */
215
- .stDownloadButton button {
216
- background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
217
- color: white;
218
- border: none;
219
- border-radius: 8px;
220
- padding: 0.75rem 1.5rem;
221
- font-weight: 500;
222
- transition: all 0.3s ease;
223
- }
224
-
225
- .stDownloadButton button:hover {
226
- transform: translateY(-2px);
227
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
228
- }
229
-
230
- /* Success/Warning/Error Messages */
231
- .stSuccess {
232
- background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
233
- border-left: 4px solid #22c55e;
234
- border-radius: 8px;
235
- }
236
-
237
- .stWarning {
238
- background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
239
- border-left: 4px solid #f59e0b;
240
- border-radius: 8px;
241
- }
242
-
243
- .stError {
244
- background: linear-gradient(135deg, #fef2f2 0%, #fecaca 100%);
245
- border-left: 4px solid #ef4444;
246
- border-radius: 8px;
247
- }
248
-
249
- /* Spinner Styling */
250
- .stSpinner {
251
- color: #3b82f6 !important;
252
- }
253
-
254
- /* Image Container */
255
- .image-container {
256
- background: white;
257
- border-radius: 12px;
258
- padding: 1rem;
259
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
260
- border: 1px solid #e2e8f0;
261
- }
262
-
263
- /* Confidence Bar - Fixed */
264
  .confidence-bar {
265
  background: #e2e8f0;
266
  border-radius: 10px;
@@ -274,49 +180,40 @@ st.markdown(
274
  height: 100%;
275
  border-radius: 10px;
276
  position: relative;
277
- transition: width 0.8s ease-out;
278
  }
279
 
280
  .confidence-fill.high {
281
- background: linear-gradient(90deg, #16a34a 0%, #059669 100%);
282
  }
283
 
284
  .confidence-fill.medium {
285
- background: linear-gradient(90deg, #f59e0b 0%, #d97706 100%);
286
  }
287
 
288
  .confidence-fill.low {
289
- background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
290
  }
291
 
292
- /* Prediction Results Container */
293
- .prediction-container {
294
  background: white;
295
- border-radius: 16px;
296
- padding: 2rem;
297
- margin: 2rem 0;
298
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
299
  border: 1px solid #e2e8f0;
300
  }
301
 
302
- /* Enhanced Typography */
303
- .prediction-title {
304
- font-size: 1.75rem;
305
- font-weight: 700;
306
- color: #1e40af;
307
- margin-bottom: 1rem;
308
- text-align: center;
309
- }
310
-
311
- .confidence-text {
312
- font-size: 1.2rem;
313
- font-weight: 600;
314
- color: #374151;
315
- text-align: center;
316
- margin-top: 0.5rem;
317
  }
318
 
319
- /* Additional Metrics */
320
  .metrics-row {
321
  display: flex;
322
  justify-content: space-around;
@@ -344,6 +241,65 @@ st.markdown(
344
  text-transform: uppercase;
345
  letter-spacing: 0.1em;
346
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  </style>
348
  """,
349
  unsafe_allow_html=True,
@@ -361,42 +317,23 @@ DepthwiseConv2D.from_config = classmethod(
361
  lambda cls, config, *a, **k: original_dw({k: v for k, v in config.items() if k != "groups"}, *a, **k)
362
  )
363
 
364
- # --- Enhanced Background Function ---
365
- def set_background(main_bg_path, sidebar_bg_path):
366
- def encode_image(path):
367
- if os.path.exists(path):
368
- with open(path, "rb") as f:
369
- return base64.b64encode(f.read()).decode()
370
- return None
371
-
372
- main_bg = encode_image(main_bg_path)
373
- sidebar_bg = encode_image(sidebar_bg_path)
374
-
375
- if main_bg:
376
- st.markdown(f"""
377
- <style>
378
- .stApp {{
379
- background-image: linear-gradient(rgba(248, 250, 252, 0.9), rgba(248, 250, 252, 0.9)), url("data:image/jpg;base64,{main_bg}");
380
- background-size: cover;
381
- background-attachment: fixed;
382
- }}
383
- </style>
384
- """, unsafe_allow_html=True)
385
-
386
- if sidebar_bg:
387
- st.markdown(f"""
388
- <style>
389
- [data-testid="stSidebar"] > div:first-child {{
390
- background-image: linear-gradient(rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.95)), url("data:image/jpg;base64,{sidebar_bg}");
391
- background-size: cover;
392
- background-position: center;
393
- border-radius: 0 15px 15px 0;
394
- }}
395
- </style>
396
- """, unsafe_allow_html=True)
397
 
398
- # Try to set background if images exist
399
- set_background("5858.jpg", "7070.jpg")
400
 
401
  # --- Constants ---
402
  IMG_SIZE = (224, 224)
@@ -432,7 +369,7 @@ def predict(images, model):
432
  else:
433
  return preds
434
 
435
- # --- Enhanced Preprocessing Steps ---
436
  def preprocess_with_steps(img):
437
  h, w = img.shape[:2]
438
  center, radius = (w // 2, h // 2), min(w, h) // 2
@@ -452,9 +389,9 @@ def preprocess_with_steps(img):
452
  sharp = cv2.addWeighted(clahe_img, 4, cv2.GaussianBlur(clahe_img, (0, 0), 10), -4, 128)
453
  resized = cv2.resize(sharp, IMG_SIZE) / 255.0
454
 
455
- # Enhanced visualization with medical styling
456
  fig, axs = plt.subplots(1, 4, figsize=(16, 4))
457
- fig.patch.set_facecolor('#f8fafc')
458
 
459
  for ax, image, title in zip(
460
  axs, [img, circ, clahe_img, resized],
@@ -463,17 +400,13 @@ def preprocess_with_steps(img):
463
  ax.imshow(image)
464
  ax.set_title(title, fontsize=14, fontweight='bold', color='#1e40af')
465
  ax.axis("off")
466
- # Add subtle border
467
- for spine in ax.spines.values():
468
- spine.set_edgecolor('#e2e8f0')
469
- spine.set_linewidth(1)
470
 
471
  plt.tight_layout()
472
  st.pyplot(fig)
473
  plt.close(fig)
474
  return resized
475
 
476
- # Enhanced explanation text with better medical styling
477
  explanation_text = {
478
  'Normal': """
479
  <div class="medical-card">
@@ -572,7 +505,7 @@ explanation_text = {
572
  """
573
  }
574
 
575
- # --- Enhanced LIME Display ---
576
  def show_lime(img, model, pred_idx, pred_label, all_probs):
577
  with st.spinner("🔬 Generating LIME explanation..."):
578
  explanation = LIME_EXPLAINER.explain_instance(
@@ -592,9 +525,7 @@ def show_lime(img, model, pred_idx, pred_label, all_probs):
592
  buf.seek(0)
593
  lime_data = buf.getvalue()
594
 
595
- # Enhanced layout with medical styling
596
- st.markdown('<div class="flex-row">', unsafe_allow_html=True)
597
-
598
  col1, col2 = st.columns(2)
599
  with col1:
600
  st.markdown("""
@@ -613,25 +544,24 @@ def show_lime(img, model, pred_idx, pred_label, all_probs):
613
  with col2:
614
  st.markdown(explanation_text.get(pred_label, "<p>No explanation available.</p>"), unsafe_allow_html=True)
615
 
616
- st.markdown('</div>', unsafe_allow_html=True)
617
-
618
- # --- Enhanced Confidence Display ---
619
  def show_confidence(confidence, pred_label):
 
620
  if confidence >= 80:
621
- card_class = "prediction-high"
622
  icon = "🎯"
 
623
  elif confidence >= 60:
624
- card_class = "prediction-medium"
625
  icon = "⚠️"
 
626
  else:
627
- card_class = "prediction-low"
628
  icon = "🔍"
 
629
 
630
  st.markdown(f"""
631
- <div class="prediction-card {card_class}">
632
  <h2 style="margin:0; color:#1e40af;">{icon} Diagnosis: <strong>{pred_label}</strong></h2>
633
  <div class="confidence-bar">
634
- <div class="confidence-fill" style="width:{confidence}%"></div>
635
  </div>
636
  <p style="margin:0.5rem 0 0 0; font-size:18px; font-weight:bold;">
637
  Confidence: {confidence:.1f}%
@@ -639,18 +569,18 @@ def show_confidence(confidence, pred_label):
639
  </div>
640
  """, unsafe_allow_html=True)
641
 
642
- # --- Enhanced Streamlit App UI ---
643
  st.set_page_config(
644
  page_title="👁️ Retina AI Classifier",
645
  layout="wide",
646
  initial_sidebar_state="expanded"
647
  )
648
 
649
- # Main header
650
  st.markdown("""
651
  <div class="main-header">
652
  <h1 style="margin:0; font-size:2.5rem;">👁️ Retina Disease Classifier</h1>
653
- <p style="margin:0.5rem 0 0 0; font-size:1.2rem; opacity:0.9;">
654
  AI-Powered Retinal Analysis with LIME Explainability
655
  </p>
656
  </div>
@@ -658,7 +588,7 @@ st.markdown("""
658
 
659
  model = load_model()
660
 
661
- # Enhanced sidebar
662
  with st.sidebar:
663
  st.markdown("""
664
  <div class="sidebar-content">
@@ -690,14 +620,14 @@ with st.sidebar:
690
  help="Select which image to analyze with LIME"
691
  )
692
 
693
- # Main content area
694
  if uploaded_files and selected_filename:
695
  file = next(f for f in uploaded_files if f.name == selected_filename)
696
  file.seek(0)
697
  bgr = cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR)
698
  rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
699
 
700
- # Processing steps section
701
  st.markdown("""
702
  <div class="processing-container">
703
  <h3 style="color:#1e40af; margin-top:0; font-size:1.5rem;">🔬 Image Preprocessing Pipeline</h3>
@@ -716,10 +646,10 @@ if uploaded_files and selected_filename:
716
  pred_label = CLASS_NAMES[pred_idx]
717
  confidence = np.max(preds) * 100
718
 
719
- # Enhanced prediction display
720
  show_confidence(confidence, pred_label)
721
 
722
- # Enhanced LIME explanation section
723
  st.markdown("""
724
  <div class="lime-container">
725
  <h3 style="color:#1e40af; margin-top:0; font-size:1.5rem;">🧠 AI Explanation & Clinical Insights</h3>
@@ -733,7 +663,7 @@ if uploaded_files and selected_filename:
733
  show_lime(preprocessed, model, pred_idx, pred_label, preds)
734
 
735
  else:
736
- # Enhanced welcome screen
737
  st.markdown("""
738
  <div class="upload-instructions">
739
  <h3>Welcome to the Retina AI Classifier</h3>
@@ -742,7 +672,7 @@ else:
742
  </div>
743
  """, unsafe_allow_html=True)
744
 
745
- # Feature grid
746
  st.markdown("""
747
  <div class="feature-grid">
748
  <div class="feature-card">
 
10
  from io import BytesIO
11
  import base64
12
 
13
+ # FIXED CSS - Removed animations and stabilized background
14
  st.markdown(
15
  """
16
  <style>
17
+ /* Main App Styling - FIXED: Stable background */
18
  .stApp {
19
+ background: #f8fafc !important;
20
+ /* Removed gradient and animations */
21
  }
22
 
23
+ /* Header Styling - FIXED: No animations */
24
  .main-header {
25
+ background: #1e40af;
26
  color: white;
27
  padding: 1.5rem;
28
  border-radius: 12px;
29
  margin-bottom: 2rem;
30
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
31
+ /* Removed gradient animations */
32
  }
33
 
34
+ /* FIXED: Stable flex container */
35
  .flex-row {
36
  display: flex;
37
  gap: 2rem;
 
44
  flex-direction: column;
45
  }
46
 
47
+ /* FIXED: Stable medical cards */
48
  .medical-card {
49
+ background: white;
50
  padding: 1.5rem;
51
  border-radius: 12px;
52
  border-left: 4px solid #3b82f6;
53
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
54
  flex-grow: 1;
55
+ border: 1px solid #e2e8f0;
56
+ /* Removed gradient and animations */
57
  }
58
 
59
  .medical-card h3 {
 
62
  padding-bottom: 0.5rem;
63
  }
64
 
65
+ /* FIXED: Removed conflicting prediction styles */
66
+ .prediction-card {
67
+ background: white;
68
+ padding: 2rem;
69
+ border-radius: 16px;
70
+ margin: 2rem 0;
71
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
72
+ border: 1px solid #e2e8f0;
73
+ /* Removed all animations and gradients */
74
  }
75
 
76
+ /* FIXED: Stable processing container */
77
  .processing-container {
78
  background: white;
79
  border-radius: 16px;
80
  padding: 2rem;
81
  margin: 2rem 0;
82
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
83
  border: 1px solid #e2e8f0;
84
+ /* Removed animations */
85
  }
86
 
87
+ /* FIXED: Stable LIME container */
88
  .lime-container {
89
  background: white;
90
  border-radius: 16px;
91
  padding: 2rem;
92
  margin: 2rem 0;
93
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
 
 
 
 
94
  border: 1px solid #e2e8f0;
95
+ /* Removed animations */
96
  }
97
 
98
+ /* FIXED: Stable button styling */
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  .stDownloadButton > button {
100
+ background: #3b82f6;
101
  color: white;
102
  border: none;
103
  border-radius: 12px;
104
  padding: 0.75rem 1.5rem;
105
  font-weight: 600;
106
  font-size: 1rem;
 
107
  width: 100%;
108
  margin-top: 1rem;
109
+ /* Removed all hover animations and transitions */
110
  }
111
 
112
+ /* FIXED: Stable upload instructions */
 
 
 
 
 
 
 
 
 
 
 
113
  .upload-instructions {
114
+ background: #f0f9ff;
115
+ border: 2px solid #3b82f6;
116
  border-radius: 12px;
117
  padding: 3rem;
118
  text-align: center;
119
  margin: 2rem 0;
120
+ /* Removed gradient */
121
  }
122
 
123
  .upload-instructions h3 {
 
131
  margin-bottom: 1rem;
132
  }
133
 
134
+ /* FIXED: Stable feature grid */
135
  .feature-grid {
136
  display: grid;
137
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
 
145
  padding: 1.5rem;
146
  text-align: center;
147
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
148
+ border: 1px solid #e2e8f0;
149
+ /* Removed hover animations */
 
 
 
 
150
  }
151
 
152
  .feature-icon {
 
166
  font-size: 0.9rem;
167
  }
168
 
169
+ /* FIXED: Stable confidence bar */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  .confidence-bar {
171
  background: #e2e8f0;
172
  border-radius: 10px;
 
180
  height: 100%;
181
  border-radius: 10px;
182
  position: relative;
183
+ /* Removed transitions */
184
  }
185
 
186
  .confidence-fill.high {
187
+ background: #16a34a;
188
  }
189
 
190
  .confidence-fill.medium {
191
+ background: #f59e0b;
192
  }
193
 
194
  .confidence-fill.low {
195
+ background: #ef4444;
196
  }
197
 
198
+ /* FIXED: Stable sidebar */
199
+ .sidebar-content {
200
  background: white;
201
+ border-radius: 12px;
202
+ padding: 1rem;
203
+ margin: 1rem 0;
 
204
  border: 1px solid #e2e8f0;
205
  }
206
 
207
+ /* FIXED: Stable image container */
208
+ .image-container {
209
+ background: white;
210
+ border-radius: 12px;
211
+ padding: 1rem;
212
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
213
+ border: 1px solid #e2e8f0;
 
 
 
 
 
 
 
 
214
  }
215
 
216
+ /* FIXED: Stable metrics */
217
  .metrics-row {
218
  display: flex;
219
  justify-content: space-around;
 
241
  text-transform: uppercase;
242
  letter-spacing: 0.1em;
243
  }
244
+
245
+ /* FIXED: Stable typography */
246
+ .prediction-title {
247
+ font-size: 1.75rem;
248
+ font-weight: 700;
249
+ color: #1e40af;
250
+ margin-bottom: 1rem;
251
+ text-align: center;
252
+ }
253
+
254
+ .confidence-text {
255
+ font-size: 1.2rem;
256
+ font-weight: 600;
257
+ color: #374151;
258
+ text-align: center;
259
+ margin-top: 0.5rem;
260
+ }
261
+
262
+ /* FIXED: Stable processing steps */
263
+ .processing-step {
264
+ background: white;
265
+ border-radius: 8px;
266
+ padding: 1rem;
267
+ margin: 0.5rem 0;
268
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
269
+ border-left: 3px solid #3b82f6;
270
+ }
271
+
272
+ /* FIXED: Stable message styling */
273
+ .stSuccess {
274
+ background: #f0fdf4;
275
+ border-left: 4px solid #22c55e;
276
+ border-radius: 8px;
277
+ }
278
+
279
+ .stWarning {
280
+ background: #fffbeb;
281
+ border-left: 4px solid #f59e0b;
282
+ border-radius: 8px;
283
+ }
284
+
285
+ .stError {
286
+ background: #fef2f2;
287
+ border-left: 4px solid #ef4444;
288
+ border-radius: 8px;
289
+ }
290
+
291
+ /* FIXED: Remove any potential animation triggers */
292
+ * {
293
+ transition: none !important;
294
+ animation: none !important;
295
+ transform: none !important;
296
+ }
297
+
298
+ /* FIXED: Ensure stable viewport */
299
+ .block-container {
300
+ padding-top: 1rem;
301
+ padding-bottom: 1rem;
302
+ }
303
  </style>
304
  """,
305
  unsafe_allow_html=True,
 
317
  lambda cls, config, *a, **k: original_dw({k: v for k, v in config.items() if k != "groups"}, *a, **k)
318
  )
319
 
320
+ # --- FIXED: Simplified background function (no dynamic changes) ---
321
+ def set_background():
322
+ """Set a stable, consistent background"""
323
+ st.markdown("""
324
+ <style>
325
+ .stApp {
326
+ background: #f8fafc !important;
327
+ }
328
+ [data-testid="stSidebar"] > div:first-child {
329
+ background: white !important;
330
+ border-radius: 0 15px 15px 0;
331
+ }
332
+ </style>
333
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
+ # Apply stable background
336
+ set_background()
337
 
338
  # --- Constants ---
339
  IMG_SIZE = (224, 224)
 
369
  else:
370
  return preds
371
 
372
+ # --- FIXED: Stable preprocessing with consistent styling ---
373
  def preprocess_with_steps(img):
374
  h, w = img.shape[:2]
375
  center, radius = (w // 2, h // 2), min(w, h) // 2
 
389
  sharp = cv2.addWeighted(clahe_img, 4, cv2.GaussianBlur(clahe_img, (0, 0), 10), -4, 128)
390
  resized = cv2.resize(sharp, IMG_SIZE) / 255.0
391
 
392
+ # FIXED: Stable visualization with consistent styling
393
  fig, axs = plt.subplots(1, 4, figsize=(16, 4))
394
+ fig.patch.set_facecolor('white') # Fixed to white background
395
 
396
  for ax, image, title in zip(
397
  axs, [img, circ, clahe_img, resized],
 
400
  ax.imshow(image)
401
  ax.set_title(title, fontsize=14, fontweight='bold', color='#1e40af')
402
  ax.axis("off")
 
 
 
 
403
 
404
  plt.tight_layout()
405
  st.pyplot(fig)
406
  plt.close(fig)
407
  return resized
408
 
409
+ # FIXED: Stable explanation text (no dynamic styling)
410
  explanation_text = {
411
  'Normal': """
412
  <div class="medical-card">
 
505
  """
506
  }
507
 
508
+ # --- FIXED: Stable LIME Display ---
509
  def show_lime(img, model, pred_idx, pred_label, all_probs):
510
  with st.spinner("🔬 Generating LIME explanation..."):
511
  explanation = LIME_EXPLAINER.explain_instance(
 
525
  buf.seek(0)
526
  lime_data = buf.getvalue()
527
 
528
+ # FIXED: Stable layout
 
 
529
  col1, col2 = st.columns(2)
530
  with col1:
531
  st.markdown("""
 
544
  with col2:
545
  st.markdown(explanation_text.get(pred_label, "<p>No explanation available.</p>"), unsafe_allow_html=True)
546
 
547
+ # --- FIXED: Stable confidence display ---
 
 
548
  def show_confidence(confidence, pred_label):
549
+ # FIXED: Determine confidence level without dynamic styling
550
  if confidence >= 80:
 
551
  icon = "🎯"
552
+ level = "high"
553
  elif confidence >= 60:
 
554
  icon = "⚠️"
555
+ level = "medium"
556
  else:
 
557
  icon = "🔍"
558
+ level = "low"
559
 
560
  st.markdown(f"""
561
+ <div class="prediction-card">
562
  <h2 style="margin:0; color:#1e40af;">{icon} Diagnosis: <strong>{pred_label}</strong></h2>
563
  <div class="confidence-bar">
564
+ <div class="confidence-fill {level}" style="width:{confidence}%"></div>
565
  </div>
566
  <p style="margin:0.5rem 0 0 0; font-size:18px; font-weight:bold;">
567
  Confidence: {confidence:.1f}%
 
569
  </div>
570
  """, unsafe_allow_html=True)
571
 
572
+ # --- FIXED: Stable Streamlit App UI ---
573
  st.set_page_config(
574
  page_title="👁️ Retina AI Classifier",
575
  layout="wide",
576
  initial_sidebar_state="expanded"
577
  )
578
 
579
+ # FIXED: Stable main header
580
  st.markdown("""
581
  <div class="main-header">
582
  <h1 style="margin:0; font-size:2.5rem;">👁️ Retina Disease Classifier</h1>
583
+ <p style="margin:0.5rem 0 0 0; font-size:1.2rem;">
584
  AI-Powered Retinal Analysis with LIME Explainability
585
  </p>
586
  </div>
 
588
 
589
  model = load_model()
590
 
591
+ # FIXED: Stable sidebar
592
  with st.sidebar:
593
  st.markdown("""
594
  <div class="sidebar-content">
 
620
  help="Select which image to analyze with LIME"
621
  )
622
 
623
+ # FIXED: Stable main content area
624
  if uploaded_files and selected_filename:
625
  file = next(f for f in uploaded_files if f.name == selected_filename)
626
  file.seek(0)
627
  bgr = cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR)
628
  rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
629
 
630
+ # FIXED: Stable processing steps section
631
  st.markdown("""
632
  <div class="processing-container">
633
  <h3 style="color:#1e40af; margin-top:0; font-size:1.5rem;">🔬 Image Preprocessing Pipeline</h3>
 
646
  pred_label = CLASS_NAMES[pred_idx]
647
  confidence = np.max(preds) * 100
648
 
649
+ # FIXED: Stable prediction display
650
  show_confidence(confidence, pred_label)
651
 
652
+ # FIXED: Stable LIME explanation section
653
  st.markdown("""
654
  <div class="lime-container">
655
  <h3 style="color:#1e40af; margin-top:0; font-size:1.5rem;">🧠 AI Explanation & Clinical Insights</h3>
 
663
  show_lime(preprocessed, model, pred_idx, pred_label, preds)
664
 
665
  else:
666
+ # FIXED: Stable welcome screen
667
  st.markdown("""
668
  <div class="upload-instructions">
669
  <h3>Welcome to the Retina AI Classifier</h3>
 
672
  </div>
673
  """, unsafe_allow_html=True)
674
 
675
+ # FIXED: Stable feature grid
676
  st.markdown("""
677
  <div class="feature-grid">
678
  <div class="feature-card">