Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from PIL import Image, ImageFile | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import io | |
| # ------------------------------------------------------------------------ | |
| # Page configuration and custom styling | |
| st.set_page_config( | |
| page_title="SVD Image Compression", | |
| layout="wide", | |
| page_icon="π¨", | |
| initial_sidebar_state="expanded" | |
| ) | |
| st.markdown(""" | |
| <style> | |
| /* Import Google Fonts */ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); | |
| /* Global Styling */ | |
| * { | |
| font-family: 'Inter', sans-serif; | |
| } | |
| /* Hide Streamlit default elements */ | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| .stDeployButton {display: none;} | |
| /* Main Background with Gradient */ | |
| .main { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| background-attachment: fixed; | |
| } | |
| /* Content Container */ | |
| .block-container { | |
| padding-top: 2rem; | |
| padding-bottom: 2rem; | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 20px; | |
| box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); | |
| } | |
| /* Sidebar Styling */ | |
| [data-testid="stSidebar"] { | |
| background: linear-gradient(180deg, #667eea 0%, #764ba2 100%); | |
| } | |
| [data-testid="stSidebar"] * { | |
| color: white !important; | |
| } | |
| [data-testid="stSidebar"] h2 { | |
| color: white !important; | |
| font-weight: 700; | |
| margin-top: 0; | |
| } | |
| [data-testid="stSidebar"] h4 { | |
| color: #ffd700 !important; | |
| font-weight: 600; | |
| margin-top: 1.5rem; | |
| } | |
| /* Main Title */ | |
| h1 { | |
| text-align: center; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| font-weight: 700; | |
| font-size: 3rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| /* Subtitle */ | |
| .subtitle { | |
| text-align: center; | |
| color: #666; | |
| font-size: 1.1rem; | |
| margin-bottom: 2rem; | |
| } | |
| /* Section Headers */ | |
| h3 { | |
| color: #667eea; | |
| font-weight: 700; | |
| border-left: 4px solid #667eea; | |
| padding-left: 12px; | |
| margin-top: 2rem; | |
| margin-bottom: 1rem; | |
| } | |
| /* File Uploader Styling */ | |
| [data-testid="stFileUploader"] { | |
| background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); | |
| border: 2px dashed #667eea; | |
| border-radius: 15px; | |
| padding: 2rem; | |
| transition: all 0.3s ease; | |
| } | |
| [data-testid="stFileUploader"]:hover { | |
| border-color: #764ba2; | |
| background: linear-gradient(135deg, #667eea25 0%, #764ba225 100%); | |
| } | |
| /* Info Box */ | |
| .stAlert { | |
| background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); | |
| border-left: 4px solid #667eea; | |
| border-radius: 10px; | |
| } | |
| /* Slider Styling */ | |
| .stSlider { | |
| padding: 1rem 0; | |
| } | |
| /* Compression Stats Card */ | |
| .stats-card { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 2rem; | |
| border-radius: 15px; | |
| color: white; | |
| box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); | |
| margin: 2rem 0; | |
| } | |
| .stats-card h4 { | |
| color: white; | |
| margin-top: 0; | |
| font-size: 1.3rem; | |
| font-weight: 700; | |
| border-bottom: 2px solid rgba(255, 255, 255, 0.3); | |
| padding-bottom: 0.8rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .stat-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 0.8rem 0; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .stat-row:last-child { | |
| border-bottom: none; | |
| } | |
| .stat-label { | |
| font-weight: 600; | |
| font-size: 1rem; | |
| } | |
| .stat-value { | |
| font-weight: 700; | |
| font-size: 1.1rem; | |
| background: rgba(255, 255, 255, 0.2); | |
| padding: 0.3rem 1rem; | |
| border-radius: 20px; | |
| } | |
| .compression-highlight { | |
| background: #ffd700; | |
| color: #764ba2; | |
| padding: 0.5rem 1.5rem; | |
| border-radius: 25px; | |
| font-size: 1.3rem; | |
| font-weight: 700; | |
| text-align: center; | |
| margin-top: 1rem; | |
| box-shadow: 0 4px 15px rgba(255, 215, 0, 0.4); | |
| } | |
| /* Image Container */ | |
| .image-container { | |
| background: white; | |
| padding: 1.5rem; | |
| border-radius: 15px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); | |
| margin: 1rem 0; | |
| } | |
| /* Subheader in columns */ | |
| .stColumn h4 { | |
| color: #667eea; | |
| font-weight: 700; | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| } | |
| /* Progress Bar */ | |
| .stProgress > div > div { | |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| } | |
| /* Footer */ | |
| .footer { | |
| text-align: center; | |
| margin-top: 3rem; | |
| padding: 2rem; | |
| background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); | |
| border-radius: 15px; | |
| color: #667eea; | |
| font-weight: 600; | |
| } | |
| /* Metric Cards */ | |
| [data-testid="stMetricValue"] { | |
| font-size: 2rem; | |
| color: #667eea; | |
| font-weight: 700; | |
| } | |
| /* Buttons */ | |
| .stButton > button { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| font-weight: 600; | |
| border: none; | |
| border-radius: 10px; | |
| padding: 0.5rem 2rem; | |
| transition: all 0.3s ease; | |
| } | |
| .stButton > button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Allow large image processing | |
| ImageFile.LOAD_TRUNCATED_IMAGES = True | |
| Image.MAX_IMAGE_PIXELS = None | |
| # ------------------------------------------------------------------------ | |
| # Sidebar: Information and Instructions | |
| with st.sidebar: | |
| st.markdown("## π About This App") | |
| st.write( | |
| "This application demonstrates **Singular Value Decomposition (SVD)** for image compression. " | |
| "Upload any image and adjust the rank slider to see real-time compression effects." | |
| ) | |
| st.divider() | |
| st.markdown("#### π What is SVD?") | |
| st.write( | |
| "SVD decomposes a matrix into three components: **U**, **Ξ£**, and **V^T**. " | |
| "By retaining only the largest singular values, we can approximate the original image " | |
| "while significantly reducing storage requirements." | |
| ) | |
| st.divider() | |
| st.markdown("#### π― How to Use") | |
| st.markdown(""" | |
| 1. **Upload** an image file (PNG, JPG, JPEG, BMP, or TIFF) | |
| 2. **Adjust** the rank slider to control compression level | |
| 3. **Compare** original vs. reconstructed images side-by-side | |
| 4. **Analyze** compression statistics and ratio | |
| 5. **Download** your compressed image | |
| """) | |
| st.divider() | |
| st.markdown("#### π‘ Pro Tips") | |
| st.info(""" | |
| β’ **Lower rank** = higher compression but lower quality | |
| β’ **Higher rank** = better quality but less compression | |
| β’ **Sweet spot**: Usually around 25-50% of max rank | |
| β’ Use **Quick Presets** for common compression levels | |
| """) | |
| st.divider() | |
| st.markdown("#### π¬ Educational Value") | |
| st.write( | |
| "This tool helps students and professionals understand how linear algebra " | |
| "concepts apply to real-world data compression problems." | |
| ) | |
| # ------------------------------------------------------------------------ | |
| # App Title and Header | |
| st.markdown("<h1>π¨ SVD Image Compression Studio</h1>", unsafe_allow_html=True) | |
| st.markdown("<p class='subtitle'>Explore the power of linear algebra in image compression using Singular Value Decomposition</p>", unsafe_allow_html=True) | |
| # ------------------------------------------------------------------------ | |
| # Image Upload | |
| st.markdown("### π€ Step 1: Upload Your Image") | |
| uploaded_file = st.file_uploader( | |
| "Choose an image file to compress", | |
| type=["png", "jpg", "jpeg", "bmp", "tiff"], | |
| help="Supported formats: PNG, JPG, JPEG, BMP, TIFF | Max recommended size: 1024Γ1024 pixels" | |
| ) | |
| if uploaded_file is None: | |
| # Welcome screen with instructions | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); | |
| padding: 2rem; border-radius: 15px; margin: 2rem 0; | |
| border-left: 5px solid #667eea;'> | |
| <h3 style='color: #667eea; margin-top: 0;'>π Welcome to SVD Image Compression Studio!</h3> | |
| <p style='font-size: 1.1rem; margin-bottom: 1.5rem;'> | |
| This tool uses <strong>Singular Value Decomposition</strong>, a powerful linear algebra technique, | |
| to compress images while maintaining visual quality. | |
| </p> | |
| <h4 style='color: #764ba2;'>π― What You'll Discover:</h4> | |
| <ul style='font-size: 1rem; line-height: 1.8;'> | |
| <li>Real-time image compression using SVD</li> | |
| <li>Visual comparison between original and compressed images</li> | |
| <li>Detailed compression statistics and ratios</li> | |
| <li>Interactive control over compression level</li> | |
| <li>Download capability for compressed images</li> | |
| </ul> | |
| <h4 style='color: #764ba2; margin-top: 1.5rem;'>π Getting Started:</h4> | |
| <p style='font-size: 1rem;'> | |
| Simply <strong>upload an image</strong> using the file uploader above to begin exploring! | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Show demo information | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.markdown(""" | |
| <div style='background: white; padding: 1.5rem; border-radius: 12px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); text-align: center;'> | |
| <div style='font-size: 3rem; margin-bottom: 0.5rem;'>π₯</div> | |
| <h4 style='color: #667eea; margin: 0.5rem 0;'>Max Compression</h4> | |
| <p style='font-size: 0.9rem; color: #666; margin: 0;'> | |
| ~50:1 ratio<br/> | |
| Heavy compression<br/> | |
| Lower quality | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col2: | |
| st.markdown(""" | |
| <div style='background: white; padding: 1.5rem; border-radius: 12px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); text-align: center;'> | |
| <div style='font-size: 3rem; margin-bottom: 0.5rem;'>βοΈ</div> | |
| <h4 style='color: #667eea; margin: 0.5rem 0;'>Balanced</h4> | |
| <p style='font-size: 0.9rem; color: #666; margin: 0;'> | |
| ~10:1 ratio<br/> | |
| Good compression<br/> | |
| Balanced quality | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col3: | |
| st.markdown(""" | |
| <div style='background: white; padding: 1.5rem; border-radius: 12px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); text-align: center;'> | |
| <div style='font-size: 3rem; margin-bottom: 0.5rem;'>β¨</div> | |
| <h4 style='color: #667eea; margin: 0.5rem 0;'>High Quality</h4> | |
| <p style='font-size: 0.9rem; color: #666; margin: 0;'> | |
| ~5:1 ratio<br/> | |
| Light compression<br/> | |
| High quality | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.stop() | |
| # Load Image | |
| try: | |
| image = Image.open(uploaded_file) | |
| st.success(f"β Image loaded successfully: **{uploaded_file.name}**") | |
| except Exception as e: | |
| st.error(f"β Error loading image: {e}") | |
| st.stop() | |
| # Resize image if too large | |
| max_dimensions = (1024, 1024) | |
| if image.width > max_dimensions[0] or image.height > max_dimensions[1]: | |
| st.warning("β οΈ Resizing large image for optimal processing performance...") | |
| try: | |
| resample_filter = Image.Resampling.LANCZOS if hasattr(Image, "Resampling") else Image.LANCZOS | |
| image.thumbnail(max_dimensions, resample_filter) | |
| except Exception as e: | |
| st.error(f"β Error resizing image: {e}") | |
| st.stop() | |
| # Convert image to grayscale numpy array | |
| image_np = np.array(image) | |
| gray_image = np.array(image.convert("L")) | |
| # ------------------------------------------------------------------------ | |
| # Compute image properties | |
| image_height, image_width = gray_image.shape | |
| num_pixels = image_height * image_width | |
| # Determine max rank | |
| max_rank = min(image_height, image_width) | |
| default_rank = min(50, max_rank) | |
| # ------------------------------------------------------------------------ | |
| # Rank Selection with better UX | |
| st.markdown("### ποΈ Step 2: Adjust Compression Level") | |
| # Info box about rank selection | |
| st.markdown(""" | |
| <div style='background: #f8f9fa; padding: 1rem; border-radius: 10px; | |
| border-left: 4px solid #667eea; margin-bottom: 1.5rem;'> | |
| <p style='margin: 0; color: #666;'> | |
| <strong>π‘ Tip:</strong> The rank determines how many singular values are used. | |
| Lower rank = higher compression but lower quality. Higher rank = better quality but less compression. | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Initialize session state for rank if not exists | |
| if 'rank' not in st.session_state: | |
| st.session_state.rank = default_rank | |
| # Quick presets with better styling | |
| st.markdown("<p style='text-align: center; font-weight: 600; margin: 0 0 0.5rem 0;'>β‘ Quick Presets:</p>", unsafe_allow_html=True) | |
| preset_col1, preset_col2, preset_col3, preset_col4 = st.columns(4) | |
| with preset_col1: | |
| if st.button("π₯ Max Compression", use_container_width=True, help="~5% of max rank"): | |
| st.session_state.rank = max(1, max_rank // 20) | |
| st.rerun() | |
| with preset_col2: | |
| if st.button("βοΈ Balanced", use_container_width=True, help="25% of max rank"): | |
| st.session_state.rank = max_rank // 4 | |
| st.rerun() | |
| with preset_col3: | |
| if st.button("β¨ High Quality", use_container_width=True, help="50% of max rank"): | |
| st.session_state.rank = max_rank // 2 | |
| st.rerun() | |
| with preset_col4: | |
| if st.button("π― Ultra HD", use_container_width=True, help="90% of max rank"): | |
| st.session_state.rank = int(max_rank * 0.9) | |
| st.rerun() | |
| # Slider below presets | |
| col1, col2, col3 = st.columns([1, 3, 1]) | |
| with col2: | |
| rank = st.slider( | |
| "Select Compression Rank", | |
| min_value=1, | |
| max_value=max_rank, | |
| value=st.session_state.rank, | |
| step=1, | |
| help=f"Lower values = more compression. Range: 1 to {max_rank}" | |
| ) | |
| # Update session state when slider changes | |
| st.session_state.rank = rank | |
| # Show percentage | |
| rank_percentage = (rank / max_rank) * 100 | |
| st.caption(f"Current: **{rank}** ({rank_percentage:.1f}% of maximum rank)") | |
| # ------------------------------------------------------------------------ | |
| # Perform SVD | |
| with st.spinner("π Processing SVD compression..."): | |
| U, S, VT = np.linalg.svd(gray_image, full_matrices=False) | |
| S_diag = np.diag(S[:rank]) | |
| Xprox = U[:, :rank] @ S_diag @ VT[:rank, :] | |
| # ------------------------------------------------------------------------ | |
| # Compute Compression Statistics (moved here before image display) | |
| uncompressed_size = num_pixels | |
| compressed_size = rank * (image_width + image_height + 1) | |
| compression_ratio = uncompressed_size / compressed_size if compressed_size > 0 else float('inf') | |
| space_saved = ((uncompressed_size - compressed_size) / uncompressed_size) * 100 | |
| # ------------------------------------------------------------------------ | |
| # Layout: Original and Reconstructed Images | |
| st.markdown("### πΌοΈ Step 3: Visual Comparison") | |
| col_original, col_reconstructed = st.columns(2) | |
| with col_original: | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 0.5rem; border-radius: 12px 12px 0 0; text-align: center;'> | |
| <h4 style='color: white; margin: 0; font-size: 1.1rem;'>π Original Image</h4> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| fig1, ax1 = plt.subplots(figsize=(8, 8), facecolor='white') | |
| ax1.imshow(gray_image, cmap="gray") | |
| ax1.axis("off") | |
| plt.tight_layout() | |
| st.pyplot(fig1) | |
| plt.close(fig1) | |
| st.caption(f"Size: {image_width}Γ{image_height} | Total: {num_pixels:,} pixels") | |
| with col_reconstructed: | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); | |
| padding: 0.5rem; border-radius: 12px 12px 0 0; text-align: center;'> | |
| <h4 style='color: white; margin: 0; font-size: 1.1rem;'>π§ Compressed Image (Rank {})</h4> | |
| </div> | |
| """.format(rank), unsafe_allow_html=True) | |
| fig2, ax2 = plt.subplots(figsize=(8, 8), facecolor='white') | |
| ax2.imshow(Xprox, cmap="gray") | |
| ax2.axis("off") | |
| plt.tight_layout() | |
| st.pyplot(fig2) | |
| plt.close(fig2) | |
| st.caption(f"Compressed Size: {compressed_size:,} values | Ratio: {compression_ratio:.2f}:1") | |
| # ------------------------------------------------------------------------ | |
| # Display Metrics | |
| st.markdown("### π Step 4: Compression Analytics") | |
| # Create a nice header for metrics | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea10 0%, #764ba210 100%); | |
| padding: 1rem; border-radius: 10px; margin-bottom: 1rem;'> | |
| <p style='margin: 0; text-align: center; color: #666; font-size: 0.95rem;'> | |
| π Key Performance Indicators | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| metric_col1, metric_col2, metric_col3, metric_col4 = st.columns(4) | |
| with metric_col1: | |
| st.metric( | |
| label="πΌοΈ Image Size", | |
| value=f"{image_width}Γ{image_height}", | |
| delta=None, | |
| help=f"Total pixels: {num_pixels:,}" | |
| ) | |
| with metric_col2: | |
| st.metric( | |
| label="ποΈ Current Rank", | |
| value=f"{rank}", | |
| delta=f"{(rank/max_rank)*100:.1f}%", | |
| help=f"Using {rank} out of {max_rank} possible singular values" | |
| ) | |
| with metric_col3: | |
| st.metric( | |
| label="β‘ Compression Ratio", | |
| value=f"{compression_ratio:.2f}:1", | |
| delta="Higher is better", | |
| help="How much the image is compressed" | |
| ) | |
| with metric_col4: | |
| st.metric( | |
| label="πΎ Space Saved", | |
| value=f"{space_saved:.1f}%", | |
| delta=f"{uncompressed_size - compressed_size:,}", | |
| help="Reduction in storage requirements" | |
| ) | |
| # ------------------------------------------------------------------------ | |
| # Detailed Statistics Card - Using Streamlit Components | |
| st.markdown("### π Detailed Compression Report") | |
| # Create a nice container for stats | |
| stats_container = st.container() | |
| with stats_container: | |
| # Create columns for better layout | |
| stat_col1, stat_col2 = st.columns(2) | |
| with stat_col1: | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'> | |
| <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>π Image Dimensions</p> | |
| <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{} Γ {} pixels</p> | |
| </div> | |
| """.format(image_width, image_height), unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'> | |
| <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>π Uncompressed Size</p> | |
| <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{:,} values</p> | |
| </div> | |
| """.format(uncompressed_size), unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'> | |
| <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>π― Singular Values Used</p> | |
| <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{} / {}</p> | |
| </div> | |
| """.format(rank, max_rank), unsafe_allow_html=True) | |
| with stat_col2: | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'> | |
| <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>π’ Total Pixels</p> | |
| <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{:,}</p> | |
| </div> | |
| """.format(num_pixels), unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'> | |
| <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>ποΈ Compressed Size</p> | |
| <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{:,} values</p> | |
| </div> | |
| """.format(compressed_size), unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 1.5rem; border-radius: 12px; color: white; margin-bottom: 1rem;'> | |
| <p style='margin: 0; font-size: 0.9rem; opacity: 0.9;'>πΎ Space Saved</p> | |
| <p style='margin: 0.3rem 0 0 0; font-size: 1.5rem; font-weight: bold;'>{:.1f}%</p> | |
| </div> | |
| """.format(space_saved), unsafe_allow_html=True) | |
| # Highlight the compression ratio | |
| st.markdown(""" | |
| <div style='background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%); | |
| padding: 1.5rem 2rem; border-radius: 15px; | |
| text-align: center; margin: 1.5rem 0; | |
| box-shadow: 0 8px 25px rgba(255, 215, 0, 0.3);'> | |
| <p style='margin: 0; font-size: 1.1rem; color: #764ba2; font-weight: 600;'>π Compression Ratio</p> | |
| <p style='margin: 0.5rem 0 0 0; font-size: 2.5rem; color: #764ba2; font-weight: bold;'>{:.2f}:1</p> | |
| </div> | |
| """.format(compression_ratio), unsafe_allow_html=True) | |
| # ------------------------------------------------------------------------ | |
| # Compression Progress Visualization | |
| st.markdown("### π Compression Efficiency") | |
| progress_value = min(1.0, rank / max_rank) | |
| st.progress(progress_value) | |
| st.caption(f"Using {rank} out of {max_rank} available singular values ({(rank/max_rank)*100:.1f}%)") | |
| # ------------------------------------------------------------------------ | |
| # Additional Insights | |
| with st.expander("π View Technical Details"): | |
| st.write(f""" | |
| **SVD Decomposition Details:** | |
| - **U matrix shape**: {U.shape} | |
| - **Ξ£ (Singular values) shape**: {S.shape} | |
| - **V^T matrix shape**: {VT.shape} | |
| - **Reconstructed matrix shape**: {Xprox.shape} | |
| - **Data type**: {gray_image.dtype} | |
| - **Rank used**: {rank} | |
| - **Maximum possible rank**: {max_rank} | |
| **Storage Analysis:** | |
| - Original: {uncompressed_size:,} values | |
| - Compressed (U): {rank * image_height:,} values | |
| - Compressed (Ξ£): {rank:,} values | |
| - Compressed (V^T): {rank * image_width:,} values | |
| - **Total compressed**: {compressed_size:,} values | |
| - **Reduction**: {space_saved:.2f}% | |
| """) | |
| # ------------------------------------------------------------------------ | |
| # Download Option | |
| st.markdown("### πΎ Step 5: Export Your Compressed Image") | |
| st.markdown(""" | |
| <div style='background: #f8f9fa; padding: 1rem; border-radius: 10px; | |
| border-left: 4px solid #667eea; margin-bottom: 1rem;'> | |
| <p style='margin: 0; color: #666;'> | |
| <strong>π₯ Ready to download?</strong> Save your compressed image to your device. | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| col_d1, col_d2, col_d3 = st.columns([1, 2, 1]) | |
| with col_d2: | |
| # Convert reconstructed image to PIL | |
| reconstructed_pil = Image.fromarray(np.uint8(np.clip(Xprox, 0, 255))) | |
| # Save to bytes | |
| buf = io.BytesIO() | |
| reconstructed_pil.save(buf, format='PNG') | |
| byte_im = buf.getvalue() | |
| st.download_button( | |
| label="β¬οΈ Download Compressed Image", | |
| data=byte_im, | |
| file_name=f"compressed_rank_{rank}_{uploaded_file.name}", | |
| mime="image/png", | |
| help="Download the reconstructed image as PNG", | |
| use_container_width=True | |
| ) | |
| st.caption(f"π Filename: compressed_rank_{rank}_{uploaded_file.name}") | |
| # ------------------------------------------------------------------------ | |
| # Footer | |
| st.markdown(""" | |
| <div class='footer'> | |
| <p style='font-size: 1.1rem; margin-bottom: 0.5rem;'><strong>π¨βπ« Developed by Dr. Jishan Ahmed</strong></p> | |
| <p style='margin: 0.3rem 0; font-size: 0.95rem;'>Data Science Assistant Professor</p> | |
| <p style='margin: 0.3rem 0; font-size: 0.95rem;'>Department of Mathematics</p> | |
| <p style='margin: 0.3rem 0 1rem 0; font-size: 0.95rem;'>Weber State University</p> | |
| <p style='margin-top: 1rem; font-size: 0.85rem; opacity: 0.7;'> | |
| Powered by NumPy, Matplotlib, and Streamlit | Linear Algebra in Action π | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |