Spaces:
Sleeping
Sleeping
File size: 6,232 Bytes
8555a26 20d4c28 8555a26 0315d87 20d4c28 8555a26 4e956a8 8555a26 4e956a8 8555a26 4e956a8 8555a26 4e956a8 8555a26 4e956a8 8555a26 288dabe 8555a26 7ddfcb1 8555a26 4e956a8 8555a26 288dabe 20d4c28 8555a26 288dabe 7123d8f 288dabe 8555a26 20d4c28 288dabe 8555a26 288dabe 8555a26 288dabe 8555a26 288dabe 4e956a8 20d4c28 8555a26 288dabe 8555a26 288dabe | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | import gradio as gr
import numpy as np
import cv2
from PIL import Image
import sys
import os
def get_size_in_mb(array):
return array.nbytes / (1024 * 1024)
def load_default_image():
script_dir = os.path.dirname(os.path.abspath(__file__))
default_image_path = os.path.join(script_dir, "kittens_cute.jpg")
if os.path.exists(default_image_path):
return Image.open(default_image_path)
return None
def perform_svd(image, r):
if image is None:
return None, "Please upload an image first."
try:
# Convert PIL Image to numpy array
image_array = np.array(image)
# Convert to grayscale if not already
if len(image_array.shape) == 3:
image_array = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY)
# Store original info
original_size_mb = get_size_in_mb(image_array)
# Perform SVD
U, S, Vt = np.linalg.svd(image_array, full_matrices=False)
# Ensure r is not larger than the minimum dimension
max_r = min(image_array.shape)
r = min(r, max_r)
# Reconstruct the image using the top r singular values
S_r = np.diag(S[:r])
reconstructed = np.dot(U[:, :r], np.dot(S_r, Vt[:r, :]))
# Clip values to valid range and convert to uint8
reconstructed = np.clip(reconstructed, 0, 255).astype(np.uint8)
height, width = image_array.shape
decomposed_elements = (height * r) + r + (width * r)
decomposed_bytes = decomposed_elements * image_array.itemsize
original_bytes = image_array.size * image_array.itemsize
def bytes_to_human_readable(num_bytes):
for unit in ["B", "KB", "MB", "GB", "TB"]:
if num_bytes < 1024:
return f"{num_bytes:.2f} {unit}"
num_bytes /= 1024
return f"{num_bytes:.2f} TB"
decomposed_hr = bytes_to_human_readable(decomposed_bytes)
original_hr = bytes_to_human_readable(original_bytes)
info = f"Original Image: {height}x{width} => {image_array.size} elements => {original_hr}\n" \
f"Decomposed Image: ({height}*{r} + {r} + {width}*{r}) => {decomposed_elements} => {decomposed_hr}\n" \
f"Energy Retained: {(np.sum(S[:r]) / np.sum(S) * 100):.2f}%"
return Image.fromarray(reconstructed), info
except Exception as e:
return None, f"Error processing image: {str(e)}"
def clear_outputs():
return None, "Waiting for generation..."
def get_image_svd_info(image):
if image is None:
return gr.update(value=50, maximum=100)
arr = np.array(image)
if len(arr.shape) == 3:
arr = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
# Set maximum R to min dimension of the image
max_r = min(arr.shape)
initial_r = min(max_r, 50) # Default to 50 if the dimension is larger
return gr.update(value=initial_r, maximum=max_r)
# Gradio interface
def svd_interface(image, r):
return perform_svd(image, r)
with gr.Blocks(theme=gr.themes.Soft(), css="footer {visibility: hidden !important;}") as demo:
gr.Markdown("""# Image Compression using SVD
Upload an image and adjust the number of singular values (R), then click 'Generate' to see the decomposed image.
""")
with gr.Row():
with gr.Column(scale=1):
# Load default image and get its dimensions for initial R slider setup
default_img = load_default_image()
image_input = gr.Image(
type="pil",
label="Upload Image",
height=450,
width=450,
sources=["upload", "webcam"],
#sources=[],
value=default_img
)
with gr.Column():
initial_max = 100
if default_img is not None:
img_array = np.array(default_img)
if len(img_array.shape) == 3:
img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
initial_max = min(img_array.shape)
r_slider = gr.Slider(
minimum=1,
maximum=initial_max,
step=5,
value=min(50, initial_max),
label="Number of Singular Values (R)"
)
# Row specifically for the button, placed after the slider
with gr.Row():
# Left spacer column (takes up (1 - 0.3) / 2 = 35% of the width)
gr.Column(scale=2, min_width=0)
# Middle column for the button (takes up 30% of the width)
with gr.Column(scale=6, min_width=0):
# Button takes full width of this middle column
submit_btn = gr.Button("Generate", variant="primary", scale=1)
# Right spacer column (takes up 35% of the width)
gr.Column(scale=2, min_width=0)
with gr.Column(scale=1):
output_image = gr.Image(
label="Reconstructed Image",
height=450,
width=450
)
info_output = gr.Textbox(
label="Image Statistics",
interactive=False,
lines=4,
value="Click Generate to see the decomposition"
)
# Link inputs and outputs for real-time updates
image_input.change(
fn=get_image_svd_info,
inputs=[image_input],
outputs=[r_slider],
queue=False
).then(
fn=clear_outputs,
inputs=[],
outputs=[output_image, info_output],
queue=False
)
# Link submit button to SVD processing
submit_btn.click(
fn=svd_interface,
inputs=[image_input, r_slider],
outputs=[output_image, info_output]
)
gr.Markdown("""
### Tips:
- Try different R values and click Generate to see the effect
- Higher R preserves more details but results in larger file size
- The compression ratio shows how much smaller the compressed version is
""")
demo.launch()
|