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()