Jishan2019 commited on
Commit
d627d5a
Β·
verified Β·
1 Parent(s): 4843268

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +724 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,726 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ from PIL import Image, ImageFile
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import io
6
+
7
+ # ------------------------------------------------------------------------
8
+ # Page configuration and custom styling
9
+ st.set_page_config(
10
+ page_title="SVD Image Compression",
11
+ layout="wide",
12
+ page_icon="🎨",
13
+ initial_sidebar_state="expanded"
14
+ )
15
+
16
+ st.markdown("""
17
+ <style>
18
+ /* Import Google Fonts */
19
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
20
+
21
+ /* Global Styling */
22
+ * {
23
+ font-family: 'Inter', sans-serif;
24
+ }
25
+
26
+ /* Hide Streamlit default elements */
27
+ #MainMenu {visibility: hidden;}
28
+ footer {visibility: hidden;}
29
+ .stDeployButton {display: none;}
30
+
31
+ /* Main Background with Gradient */
32
+ .main {
33
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
34
+ background-attachment: fixed;
35
+ }
36
+
37
+ /* Content Container */
38
+ .block-container {
39
+ padding-top: 2rem;
40
+ padding-bottom: 2rem;
41
+ background: rgba(255, 255, 255, 0.95);
42
+ border-radius: 20px;
43
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
44
+ }
45
+
46
+ /* Sidebar Styling */
47
+ [data-testid="stSidebar"] {
48
+ background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
49
+ }
50
+
51
+ [data-testid="stSidebar"] * {
52
+ color: white !important;
53
+ }
54
+
55
+ [data-testid="stSidebar"] h2 {
56
+ color: white !important;
57
+ font-weight: 700;
58
+ margin-top: 0;
59
+ }
60
+
61
+ [data-testid="stSidebar"] h4 {
62
+ color: #ffd700 !important;
63
+ font-weight: 600;
64
+ margin-top: 1.5rem;
65
+ }
66
+
67
+ /* Main Title */
68
+ h1 {
69
+ text-align: center;
70
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
71
+ -webkit-background-clip: text;
72
+ -webkit-text-fill-color: transparent;
73
+ background-clip: text;
74
+ font-weight: 700;
75
+ font-size: 3rem;
76
+ margin-bottom: 0.5rem;
77
+ }
78
+
79
+ /* Subtitle */
80
+ .subtitle {
81
+ text-align: center;
82
+ color: #666;
83
+ font-size: 1.1rem;
84
+ margin-bottom: 2rem;
85
+ }
86
+
87
+ /* Section Headers */
88
+ h3 {
89
+ color: #667eea;
90
+ font-weight: 700;
91
+ border-left: 4px solid #667eea;
92
+ padding-left: 12px;
93
+ margin-top: 2rem;
94
+ margin-bottom: 1rem;
95
+ }
96
+
97
+ /* File Uploader Styling */
98
+ [data-testid="stFileUploader"] {
99
+ background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
100
+ border: 2px dashed #667eea;
101
+ border-radius: 15px;
102
+ padding: 2rem;
103
+ transition: all 0.3s ease;
104
+ }
105
+
106
+ [data-testid="stFileUploader"]:hover {
107
+ border-color: #764ba2;
108
+ background: linear-gradient(135deg, #667eea25 0%, #764ba225 100%);
109
+ }
110
+
111
+ /* Info Box */
112
+ .stAlert {
113
+ background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
114
+ border-left: 4px solid #667eea;
115
+ border-radius: 10px;
116
+ }
117
+
118
+ /* Slider Styling */
119
+ .stSlider {
120
+ padding: 1rem 0;
121
+ }
122
+
123
+ /* Compression Stats Card */
124
+ .stats-card {
125
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
126
+ padding: 2rem;
127
+ border-radius: 15px;
128
+ color: white;
129
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
130
+ margin: 2rem 0;
131
+ }
132
+
133
+ .stats-card h4 {
134
+ color: white;
135
+ margin-top: 0;
136
+ font-size: 1.3rem;
137
+ font-weight: 700;
138
+ border-bottom: 2px solid rgba(255, 255, 255, 0.3);
139
+ padding-bottom: 0.8rem;
140
+ margin-bottom: 1.5rem;
141
+ }
142
+
143
+ .stat-row {
144
+ display: flex;
145
+ justify-content: space-between;
146
+ align-items: center;
147
+ padding: 0.8rem 0;
148
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
149
+ }
150
+
151
+ .stat-row:last-child {
152
+ border-bottom: none;
153
+ }
154
+
155
+ .stat-label {
156
+ font-weight: 600;
157
+ font-size: 1rem;
158
+ }
159
+
160
+ .stat-value {
161
+ font-weight: 700;
162
+ font-size: 1.1rem;
163
+ background: rgba(255, 255, 255, 0.2);
164
+ padding: 0.3rem 1rem;
165
+ border-radius: 20px;
166
+ }
167
+
168
+ .compression-highlight {
169
+ background: #ffd700;
170
+ color: #764ba2;
171
+ padding: 0.5rem 1.5rem;
172
+ border-radius: 25px;
173
+ font-size: 1.3rem;
174
+ font-weight: 700;
175
+ text-align: center;
176
+ margin-top: 1rem;
177
+ box-shadow: 0 4px 15px rgba(255, 215, 0, 0.4);
178
+ }
179
+
180
+ /* Image Container */
181
+ .image-container {
182
+ background: white;
183
+ padding: 1.5rem;
184
+ border-radius: 15px;
185
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
186
+ margin: 1rem 0;
187
+ }
188
+
189
+ /* Subheader in columns */
190
+ .stColumn h4 {
191
+ color: #667eea;
192
+ font-weight: 700;
193
+ text-align: center;
194
+ margin-bottom: 1rem;
195
+ }
196
+
197
+ /* Progress Bar */
198
+ .stProgress > div > div {
199
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
200
+ }
201
+
202
+ /* Footer */
203
+ .footer {
204
+ text-align: center;
205
+ margin-top: 3rem;
206
+ padding: 2rem;
207
+ background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
208
+ border-radius: 15px;
209
+ color: #667eea;
210
+ font-weight: 600;
211
+ }
212
+
213
+ /* Metric Cards */
214
+ [data-testid="stMetricValue"] {
215
+ font-size: 2rem;
216
+ color: #667eea;
217
+ font-weight: 700;
218
+ }
219
+
220
+ /* Buttons */
221
+ .stButton > button {
222
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
223
+ color: white;
224
+ font-weight: 600;
225
+ border: none;
226
+ border-radius: 10px;
227
+ padding: 0.5rem 2rem;
228
+ transition: all 0.3s ease;
229
+ }
230
+
231
+ .stButton > button:hover {
232
+ transform: translateY(-2px);
233
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
234
+ }
235
+ </style>
236
+ """, unsafe_allow_html=True)
237
+
238
+ # Allow large image processing
239
+ ImageFile.LOAD_TRUNCATED_IMAGES = True
240
+ Image.MAX_IMAGE_PIXELS = None
241
+
242
+ # ------------------------------------------------------------------------
243
+ # Sidebar: Information and Instructions
244
+ with st.sidebar:
245
+ st.markdown("## πŸ“Œ About This App")
246
+ st.write(
247
+ "This application demonstrates **Singular Value Decomposition (SVD)** for image compression. "
248
+ "Upload any image and adjust the rank slider to see real-time compression effects."
249
+ )
250
+
251
+ st.divider()
252
+
253
+ st.markdown("#### πŸ“– What is SVD?")
254
+ st.write(
255
+ "SVD decomposes a matrix into three components: **U**, **Ξ£**, and **V^T**. "
256
+ "By retaining only the largest singular values, we can approximate the original image "
257
+ "while significantly reducing storage requirements."
258
+ )
259
+
260
+ st.divider()
261
+
262
+ st.markdown("#### 🎯 How to Use")
263
+ st.markdown("""
264
+ 1. **Upload** an image file (PNG, JPG, JPEG, BMP, or TIFF)
265
+ 2. **Adjust** the rank slider to control compression level
266
+ 3. **Compare** original vs. reconstructed images side-by-side
267
+ 4. **Analyze** compression statistics and ratio
268
+ 5. **Download** your compressed image
269
+ """)
270
+
271
+ st.divider()
272
+
273
+ st.markdown("#### πŸ’‘ Pro Tips")
274
+ st.info("""
275
+ β€’ **Lower rank** = higher compression but lower quality
276
+
277
+ β€’ **Higher rank** = better quality but less compression
278
+
279
+ β€’ **Sweet spot**: Usually around 25-50% of max rank
280
+
281
+ β€’ Use **Quick Presets** for common compression levels
282
+ """)
283
+
284
+ st.divider()
285
+
286
+ st.markdown("#### πŸ”¬ Educational Value")
287
+ st.write(
288
+ "This tool helps students and professionals understand how linear algebra "
289
+ "concepts apply to real-world data compression problems."
290
+ )
291
+
292
+ # ------------------------------------------------------------------------
293
+ # App Title and Header
294
+ st.markdown("<h1>🎨 SVD Image Compression Studio</h1>", unsafe_allow_html=True)
295
+ st.markdown("<p class='subtitle'>Explore the power of linear algebra in image compression using Singular Value Decomposition</p>", unsafe_allow_html=True)
296
+
297
+ # ------------------------------------------------------------------------
298
+ # Image Upload
299
+ st.markdown("### πŸ“€ Step 1: Upload Your Image")
300
+
301
+ uploaded_file = st.file_uploader(
302
+ "Choose an image file to compress",
303
+ type=["png", "jpg", "jpeg", "bmp", "tiff"],
304
+ help="Supported formats: PNG, JPG, JPEG, BMP, TIFF | Max recommended size: 1024Γ—1024 pixels"
305
+ )
306
+
307
+ if uploaded_file is None:
308
+ # Welcome screen with instructions
309
+ st.markdown("""
310
+ <div style='background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
311
+ padding: 2rem; border-radius: 15px; margin: 2rem 0;
312
+ border-left: 5px solid #667eea;'>
313
+ <h3 style='color: #667eea; margin-top: 0;'>πŸ‘‹ Welcome to SVD Image Compression Studio!</h3>
314
+ <p style='font-size: 1.1rem; margin-bottom: 1.5rem;'>
315
+ This tool uses <strong>Singular Value Decomposition</strong>, a powerful linear algebra technique,
316
+ to compress images while maintaining visual quality.
317
+ </p>
318
+ <h4 style='color: #764ba2;'>🎯 What You'll Discover:</h4>
319
+ <ul style='font-size: 1rem; line-height: 1.8;'>
320
+ <li>Real-time image compression using SVD</li>
321
+ <li>Visual comparison between original and compressed images</li>
322
+ <li>Detailed compression statistics and ratios</li>
323
+ <li>Interactive control over compression level</li>
324
+ <li>Download capability for compressed images</li>
325
+ </ul>
326
+ <h4 style='color: #764ba2; margin-top: 1.5rem;'>πŸš€ Getting Started:</h4>
327
+ <p style='font-size: 1rem;'>
328
+ Simply <strong>upload an image</strong> using the file uploader above to begin exploring!
329
+ </p>
330
+ </div>
331
+ """, unsafe_allow_html=True)
332
+
333
+ # Show demo information
334
+ col1, col2, col3 = st.columns(3)
335
+
336
+ with col1:
337
+ st.markdown("""
338
+ <div style='background: white; padding: 1.5rem; border-radius: 12px;
339
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); text-align: center;'>
340
+ <div style='font-size: 3rem; margin-bottom: 0.5rem;'>πŸ”₯</div>
341
+ <h4 style='color: #667eea; margin: 0.5rem 0;'>Max Compression</h4>
342
+ <p style='font-size: 0.9rem; color: #666; margin: 0;'>
343
+ ~50:1 ratio<br/>
344
+ Heavy compression<br/>
345
+ Lower quality
346
+ </p>
347
+ </div>
348
+ """, unsafe_allow_html=True)
349
+
350
+ with col2:
351
+ st.markdown("""
352
+ <div style='background: white; padding: 1.5rem; border-radius: 12px;
353
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); text-align: center;'>
354
+ <div style='font-size: 3rem; margin-bottom: 0.5rem;'>βš–οΈ</div>
355
+ <h4 style='color: #667eea; margin: 0.5rem 0;'>Balanced</h4>
356
+ <p style='font-size: 0.9rem; color: #666; margin: 0;'>
357
+ ~10:1 ratio<br/>
358
+ Good compression<br/>
359
+ Balanced quality
360
+ </p>
361
+ </div>
362
+ """, unsafe_allow_html=True)
363
+
364
+ with col3:
365
+ st.markdown("""
366
+ <div style='background: white; padding: 1.5rem; border-radius: 12px;
367
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); text-align: center;'>
368
+ <div style='font-size: 3rem; margin-bottom: 0.5rem;'>✨</div>
369
+ <h4 style='color: #667eea; margin: 0.5rem 0;'>High Quality</h4>
370
+ <p style='font-size: 0.9rem; color: #666; margin: 0;'>
371
+ ~5:1 ratio<br/>
372
+ Light compression<br/>
373
+ High quality
374
+ </p>
375
+ </div>
376
+ """, unsafe_allow_html=True)
377
+
378
+ st.stop()
379
+
380
+ # Load Image
381
+ try:
382
+ image = Image.open(uploaded_file)
383
+ st.success(f"βœ… Image loaded successfully: **{uploaded_file.name}**")
384
+ except Exception as e:
385
+ st.error(f"❌ Error loading image: {e}")
386
+ st.stop()
387
+
388
+ # Resize image if too large
389
+ max_dimensions = (1024, 1024)
390
+ if image.width > max_dimensions[0] or image.height > max_dimensions[1]:
391
+ st.warning("⚠️ Resizing large image for optimal processing performance...")
392
+ try:
393
+ resample_filter = Image.Resampling.LANCZOS if hasattr(Image, "Resampling") else Image.LANCZOS
394
+ image.thumbnail(max_dimensions, resample_filter)
395
+ except Exception as e:
396
+ st.error(f"❌ Error resizing image: {e}")
397
+ st.stop()
398
+
399
+ # Convert image to grayscale numpy array
400
+ image_np = np.array(image)
401
+ gray_image = np.array(image.convert("L"))
402
+
403
+ # ------------------------------------------------------------------------
404
+ # Compute image properties
405
+ image_height, image_width = gray_image.shape
406
+ num_pixels = image_height * image_width
407
+
408
+ # Determine max rank
409
+ max_rank = min(image_height, image_width)
410
+ default_rank = min(50, max_rank)
411
+
412
+ # ------------------------------------------------------------------------
413
+ # Rank Selection with better UX
414
+ st.markdown("### 🎚️ Step 2: Adjust Compression Level")
415
+
416
+ # Info box about rank selection
417
+ st.markdown("""
418
+ <div style='background: #f8f9fa; padding: 1rem; border-radius: 10px;
419
+ border-left: 4px solid #667eea; margin-bottom: 1.5rem;'>
420
+ <p style='margin: 0; color: #666;'>
421
+ <strong>πŸ’‘ Tip:</strong> The rank determines how many singular values are used.
422
+ Lower rank = higher compression but lower quality. Higher rank = better quality but less compression.
423
+ </p>
424
+ </div>
425
+ """, unsafe_allow_html=True)
426
+
427
+ # Initialize session state for rank if not exists
428
+ if 'rank' not in st.session_state:
429
+ st.session_state.rank = default_rank
430
+
431
+ # Quick presets with better styling
432
+ st.markdown("<p style='text-align: center; font-weight: 600; margin: 0 0 0.5rem 0;'>⚑ Quick Presets:</p>", unsafe_allow_html=True)
433
+
434
+ preset_col1, preset_col2, preset_col3, preset_col4 = st.columns(4)
435
+
436
+ with preset_col1:
437
+ if st.button("πŸ”₯ Max Compression", use_container_width=True, help="~5% of max rank"):
438
+ st.session_state.rank = max(1, max_rank // 20)
439
+ st.rerun()
440
+
441
+ with preset_col2:
442
+ if st.button("βš–οΈ Balanced", use_container_width=True, help="25% of max rank"):
443
+ st.session_state.rank = max_rank // 4
444
+ st.rerun()
445
+
446
+ with preset_col3:
447
+ if st.button("✨ High Quality", use_container_width=True, help="50% of max rank"):
448
+ st.session_state.rank = max_rank // 2
449
+ st.rerun()
450
+
451
+ with preset_col4:
452
+ if st.button("🎯 Ultra HD", use_container_width=True, help="90% of max rank"):
453
+ st.session_state.rank = int(max_rank * 0.9)
454
+ st.rerun()
455
+
456
+ # Slider below presets
457
+ col1, col2, col3 = st.columns([1, 3, 1])
458
+
459
+ with col2:
460
+ rank = st.slider(
461
+ "Select Compression Rank",
462
+ min_value=1,
463
+ max_value=max_rank,
464
+ value=st.session_state.rank,
465
+ step=1,
466
+ help=f"Lower values = more compression. Range: 1 to {max_rank}"
467
+ )
468
+
469
+ # Update session state when slider changes
470
+ st.session_state.rank = rank
471
+
472
+ # Show percentage
473
+ rank_percentage = (rank / max_rank) * 100
474
+ st.caption(f"Current: **{rank}** ({rank_percentage:.1f}% of maximum rank)")
475
+
476
+ # ------------------------------------------------------------------------
477
+ # Perform SVD
478
+ with st.spinner("πŸ”„ Processing SVD compression..."):
479
+ U, S, VT = np.linalg.svd(gray_image, full_matrices=False)
480
+ S_diag = np.diag(S[:rank])
481
+ Xprox = U[:, :rank] @ S_diag @ VT[:rank, :]
482
+
483
+ # ------------------------------------------------------------------------
484
+ # Compute Compression Statistics (moved here before image display)
485
+ uncompressed_size = num_pixels
486
+ compressed_size = rank * (image_width + image_height + 1)
487
+ compression_ratio = uncompressed_size / compressed_size if compressed_size > 0 else float('inf')
488
+ space_saved = ((uncompressed_size - compressed_size) / uncompressed_size) * 100
489
+
490
+ # ------------------------------------------------------------------------
491
+ # Layout: Original and Reconstructed Images
492
+ st.markdown("### πŸ–ΌοΈ Step 3: Visual Comparison")
493
+
494
+ col_original, col_reconstructed = st.columns(2)
495
+
496
+ with col_original:
497
+ st.markdown("""
498
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
499
+ padding: 0.5rem; border-radius: 12px 12px 0 0; text-align: center;'>
500
+ <h4 style='color: white; margin: 0; font-size: 1.1rem;'>πŸ“Œ Original Image</h4>
501
+ </div>
502
+ """, unsafe_allow_html=True)
503
+
504
+ fig1, ax1 = plt.subplots(figsize=(8, 8), facecolor='white')
505
+ ax1.imshow(gray_image, cmap="gray")
506
+ ax1.axis("off")
507
+ plt.tight_layout()
508
+ st.pyplot(fig1)
509
+ plt.close(fig1)
510
+
511
+ st.caption(f"Size: {image_width}Γ—{image_height} | Total: {num_pixels:,} pixels")
512
+
513
+ with col_reconstructed:
514
+ st.markdown("""
515
+ <div style='background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
516
+ padding: 0.5rem; border-radius: 12px 12px 0 0; text-align: center;'>
517
+ <h4 style='color: white; margin: 0; font-size: 1.1rem;'>πŸ”§ Compressed Image (Rank {})</h4>
518
+ </div>
519
+ """.format(rank), unsafe_allow_html=True)
520
+
521
+ fig2, ax2 = plt.subplots(figsize=(8, 8), facecolor='white')
522
+ ax2.imshow(Xprox, cmap="gray")
523
+ ax2.axis("off")
524
+ plt.tight_layout()
525
+ st.pyplot(fig2)
526
+ plt.close(fig2)
527
+
528
+ st.caption(f"Compressed Size: {compressed_size:,} values | Ratio: {compression_ratio:.2f}:1")
529
+
530
+ # ------------------------------------------------------------------------
531
+ # Display Metrics
532
+ st.markdown("### πŸ“Š Step 4: Compression Analytics")
533
+
534
+ # Create a nice header for metrics
535
+ st.markdown("""
536
+ <div style='background: linear-gradient(135deg, #667eea10 0%, #764ba210 100%);
537
+ padding: 1rem; border-radius: 10px; margin-bottom: 1rem;'>
538
+ <p style='margin: 0; text-align: center; color: #666; font-size: 0.95rem;'>
539
+ πŸ“ˆ Key Performance Indicators
540
+ </p>
541
+ </div>
542
+ """, unsafe_allow_html=True)
543
+
544
+ metric_col1, metric_col2, metric_col3, metric_col4 = st.columns(4)
545
+
546
+ with metric_col1:
547
+ st.metric(
548
+ label="πŸ–ΌοΈ Image Size",
549
+ value=f"{image_width}Γ—{image_height}",
550
+ delta=None,
551
+ help=f"Total pixels: {num_pixels:,}"
552
+ )
553
+
554
+ with metric_col2:
555
+ st.metric(
556
+ label="🎚️ Current Rank",
557
+ value=f"{rank}",
558
+ delta=f"{(rank/max_rank)*100:.1f}%",
559
+ help=f"Using {rank} out of {max_rank} possible singular values"
560
+ )
561
+
562
+ with metric_col3:
563
+ st.metric(
564
+ label="⚑ Compression Ratio",
565
+ value=f"{compression_ratio:.2f}:1",
566
+ delta="Higher is better",
567
+ help="How much the image is compressed"
568
+ )
569
+
570
+ with metric_col4:
571
+ st.metric(
572
+ label="πŸ’Ύ Space Saved",
573
+ value=f"{space_saved:.1f}%",
574
+ delta=f"{uncompressed_size - compressed_size:,}",
575
+ help="Reduction in storage requirements"
576
+ )
577
+
578
+ # ------------------------------------------------------------------------
579
+ # Detailed Statistics Card - Using Streamlit Components
580
+ st.markdown("### πŸ“ˆ Detailed Compression Report")
581
+
582
+ # Create a nice container for stats
583
+ stats_container = st.container()
584
+
585
+ with stats_container:
586
+ # Create columns for better layout
587
+ stat_col1, stat_col2 = st.columns(2)
588
+
589
+ with stat_col1:
590
+ st.markdown("""
591
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
592
+ padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'>
593
+ <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>πŸ“ Image Dimensions</p>
594
+ <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{} Γ— {} pixels</p>
595
+ </div>
596
+ """.format(image_width, image_height), unsafe_allow_html=True)
597
+
598
+ st.markdown("""
599
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
600
+ padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'>
601
+ <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>πŸ“‚ Uncompressed Size</p>
602
+ <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{:,} values</p>
603
+ </div>
604
+ """.format(uncompressed_size), unsafe_allow_html=True)
605
+
606
+ st.markdown("""
607
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
608
+ padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'>
609
+ <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>🎯 Singular Values Used</p>
610
+ <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{} / {}</p>
611
+ </div>
612
+ """.format(rank, max_rank), unsafe_allow_html=True)
613
+
614
+ with stat_col2:
615
+ st.markdown("""
616
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
617
+ padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'>
618
+ <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>πŸ”’ Total Pixels</p>
619
+ <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{:,}</p>
620
+ </div>
621
+ """.format(num_pixels), unsafe_allow_html=True)
622
+
623
+ st.markdown("""
624
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
625
+ padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'>
626
+ <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>πŸ—œοΈ Compressed Size</p>
627
+ <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{:,} values</p>
628
+ </div>
629
+ """.format(compressed_size), unsafe_allow_html=True)
630
+
631
+ st.markdown("""
632
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
633
+ padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'>
634
+ <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>πŸ’Ύ Space Saved</p>
635
+ <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{:.1f}%</p>
636
+ </div>
637
+ """.format(space_saved), unsafe_allow_html=True)
638
+
639
+ # Highlight the compression ratio
640
+ st.markdown("""
641
+ <div style='background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
642
+ padding: 1.5rem 2rem; border-radius: 15px;
643
+ text-align: center; margin: 1.5rem 0;
644
+ box-shadow: 0 8px 25px rgba(255, 215, 0, 0.3);'>
645
+ <p style='margin: 0; font-size: 1.1rem; color: #764ba2; font-weight: 600;'>πŸš€ Compression Ratio</p>
646
+ <p style='margin: 0.5rem 0 0 0; font-size: 2.5rem; color: #764ba2; font-weight: bold;'>{:.2f}:1</p>
647
+ </div>
648
+ """.format(compression_ratio), unsafe_allow_html=True)
649
+
650
+ # ------------------------------------------------------------------------
651
+ # Compression Progress Visualization
652
+ st.markdown("### πŸ“‰ Compression Efficiency")
653
+ progress_value = min(1.0, rank / max_rank)
654
+ st.progress(progress_value)
655
+ st.caption(f"Using {rank} out of {max_rank} available singular values ({(rank/max_rank)*100:.1f}%)")
656
+
657
+ # ------------------------------------------------------------------------
658
+ # Additional Insights
659
+ with st.expander("πŸ” View Technical Details"):
660
+ st.write(f"""
661
+ **SVD Decomposition Details:**
662
+ - **U matrix shape**: {U.shape}
663
+ - **Ξ£ (Singular values) shape**: {S.shape}
664
+ - **V^T matrix shape**: {VT.shape}
665
+ - **Reconstructed matrix shape**: {Xprox.shape}
666
+ - **Data type**: {gray_image.dtype}
667
+ - **Rank used**: {rank}
668
+ - **Maximum possible rank**: {max_rank}
669
+
670
+ **Storage Analysis:**
671
+ - Original: {uncompressed_size:,} values
672
+ - Compressed (U): {rank * image_height:,} values
673
+ - Compressed (Ξ£): {rank:,} values
674
+ - Compressed (V^T): {rank * image_width:,} values
675
+ - **Total compressed**: {compressed_size:,} values
676
+ - **Reduction**: {space_saved:.2f}%
677
+ """)
678
+
679
+ # ------------------------------------------------------------------------
680
+ # Download Option
681
+ st.markdown("### πŸ’Ύ Step 5: Export Your Compressed Image")
682
+
683
+ st.markdown("""
684
+ <div style='background: #f8f9fa; padding: 1rem; border-radius: 10px;
685
+ border-left: 4px solid #667eea; margin-bottom: 1rem;'>
686
+ <p style='margin: 0; color: #666;'>
687
+ <strong>πŸ“₯ Ready to download?</strong> Save your compressed image to your device.
688
+ </p>
689
+ </div>
690
+ """, unsafe_allow_html=True)
691
+
692
+ col_d1, col_d2, col_d3 = st.columns([1, 2, 1])
693
+
694
+ with col_d2:
695
+ # Convert reconstructed image to PIL
696
+ reconstructed_pil = Image.fromarray(np.uint8(np.clip(Xprox, 0, 255)))
697
+
698
+ # Save to bytes
699
+ buf = io.BytesIO()
700
+ reconstructed_pil.save(buf, format='PNG')
701
+ byte_im = buf.getvalue()
702
+
703
+ st.download_button(
704
+ label="⬇️ Download Compressed Image",
705
+ data=byte_im,
706
+ file_name=f"compressed_rank_{rank}_{uploaded_file.name}",
707
+ mime="image/png",
708
+ help="Download the reconstructed image as PNG",
709
+ use_container_width=True
710
+ )
711
+
712
+ st.caption(f"πŸ“ Filename: compressed_rank_{rank}_{uploaded_file.name}")
713
 
714
+ # ------------------------------------------------------------------------
715
+ # Footer
716
+ st.markdown("""
717
+ <div class='footer'>
718
+ <p style='font-size: 1.1rem; margin-bottom: 0.5rem;'><strong>πŸ‘¨β€πŸ« Developed by Dr. Jishan Ahmed</strong></p>
719
+ <p style='margin: 0.3rem 0; font-size: 0.95rem;'>Data Science Assistant Professor</p>
720
+ <p style='margin: 0.3rem 0; font-size: 0.95rem;'>Department of Mathematics</p>
721
+ <p style='margin: 0.3rem 0 1rem 0; font-size: 0.95rem;'>Weber State University</p>
722
+ <p style='margin-top: 1rem; font-size: 0.85rem; opacity: 0.7;'>
723
+ Powered by NumPy, Matplotlib, and Streamlit | Linear Algebra in Action πŸš€
724
+ </p>
725
+ </div>
726
+ """, unsafe_allow_html=True)