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(""" """, 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("

🎨 SVD Image Compression Studio

", unsafe_allow_html=True) st.markdown("

Explore the power of linear algebra in image compression using Singular Value Decomposition

", 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("""

👋 Welcome to SVD Image Compression Studio!

This tool uses Singular Value Decomposition, a powerful linear algebra technique, to compress images while maintaining visual quality.

🎯 What You'll Discover:

🚀 Getting Started:

Simply upload an image using the file uploader above to begin exploring!

""", unsafe_allow_html=True) # Show demo information col1, col2, col3 = st.columns(3) with col1: st.markdown("""
🔥

Max Compression

~50:1 ratio
Heavy compression
Lower quality

""", unsafe_allow_html=True) with col2: st.markdown("""
⚖️

Balanced

~10:1 ratio
Good compression
Balanced quality

""", unsafe_allow_html=True) with col3: st.markdown("""

High Quality

~5:1 ratio
Light compression
High quality

""", 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("""

💡 Tip: The rank determines how many singular values are used. Lower rank = higher compression but lower quality. Higher rank = better quality but less compression.

""", 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("

⚡ Quick Presets:

", 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("""

📌 Original Image

""", 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("""

🔧 Compressed Image (Rank {})

""".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("""

📈 Key Performance Indicators

""", 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("""

📏 Image Dimensions

{} × {} pixels

""".format(image_width, image_height), unsafe_allow_html=True) st.markdown("""

📂 Uncompressed Size

{:,} values

""".format(uncompressed_size), unsafe_allow_html=True) st.markdown("""

🎯 Singular Values Used

{} / {}

""".format(rank, max_rank), unsafe_allow_html=True) with stat_col2: st.markdown("""

🔢 Total Pixels

{:,}

""".format(num_pixels), unsafe_allow_html=True) st.markdown("""

🗜️ Compressed Size

{:,} values

""".format(compressed_size), unsafe_allow_html=True) st.markdown("""

💾 Space Saved

{:.1f}%

""".format(space_saved), unsafe_allow_html=True) # Highlight the compression ratio st.markdown("""

🚀 Compression Ratio

{:.2f}:1

""".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("""

📥 Ready to download? Save your compressed image to your device.

""", 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(""" """, unsafe_allow_html=True)