csabhay commited on
Commit
1178f23
·
1 Parent(s): 9ef7694

fix: file size mismatch, download filename change

Browse files
Files changed (1) hide show
  1. app.py +61 -38
app.py CHANGED
@@ -2,9 +2,10 @@ import gradio as gr
2
  import numpy as np
3
  from sklearn.cluster import KMeans
4
  from PIL import Image
 
 
5
 
6
  def compress_kmeans(image: np.ndarray, k: int) -> np.ndarray:
7
- """Reduce image palette to k colours using K-Means vector quantisation."""
8
  pixels = image.reshape(-1, 3)
9
  model = KMeans(n_clusters=k, n_init="auto", random_state=42)
10
  model.fit(pixels)
@@ -12,57 +13,79 @@ def compress_kmeans(image: np.ndarray, k: int) -> np.ndarray:
12
  labels = model.predict(pixels)
13
  return centres[labels].reshape(image.shape)
14
 
15
- def compute_psnr_mse(original: np.ndarray, compressed: np.ndarray):
16
- """Return (MSE, PSNR) between two images."""
17
- mse = np.mean((original.astype(np.float64) - compressed.astype(np.float64)) ** 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  psnr = float("inf") if mse == 0 else 20.0 * np.log10(255.0 / np.sqrt(mse))
19
- return mse, psnr
20
 
21
- def compute_metrics(image: np.ndarray, k: int):
22
- """Estimate compression ratio and file sizes (theoretical)."""
23
- h, w, c = image.shape
24
- original_bits = h * w * c * 8
25
- compressed_bits = (k * 24) + (h * w * int(np.ceil(np.log2(k))))
26
- ratio = original_bits / compressed_bits
27
- return {
28
- "ratio": ratio,
29
- "original_kb": original_bits / (8 * 1024),
30
- "compressed_kb": compressed_bits / (8 * 1024),
31
- "saved_percent": (1.0 - (1.0 / ratio)) * 100.0,
32
- }
33
 
34
- def compress_and_report(image, k):
35
- """Gradio entry point: compress, compute metrics, return image + report."""
36
- if isinstance(image, np.ndarray):
37
- orig = image[:, :, :3] if image.shape[-1] == 4 else image
38
  else:
39
- orig = np.array(image.convert("RGB"))
40
 
41
- compressed = compress_kmeans(orig, k)
42
- mse, psnr = compute_psnr_mse(orig, compressed)
43
- metrics = compute_metrics(orig, k)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- report = (
46
- f"COMPRESSION REPORT\n"
47
- f"{'-' * 40}\n"
48
- f"PSNR : {psnr:.2f} dB\n"
49
- f"MSE : {mse:.2f}\n"
50
- f"Compression Ratio : {metrics['ratio']:.2f}x\n"
51
- f"Original Size (KB) : {metrics['original_kb']:.2f}\n"
52
- f"Compressed Size (KB): {metrics['compressed_kb']:.2f}\n"
53
- f"Space Saved (%) : {metrics['saved_percent']:.1f}"
54
- )
55
- return compressed, report
56
 
57
  iface = gr.Interface(
58
  fn=compress_and_report,
59
  inputs=[
60
- gr.Image(type="pil", label="Upload Image"),
61
  gr.Slider(minimum=2, maximum=64, step=2, value=16, label="Number of Colors (k)"),
62
  ],
63
  outputs=[
64
  gr.Image(type="numpy", label="Compressed Image"),
65
- gr.Textbox(label="Metrics Report", lines=10),
66
  ],
67
  title="Image Compression using K-Means",
68
  description="Upload an image and adjust the colour palette size to see lossy compression in action.",
 
2
  import numpy as np
3
  from sklearn.cluster import KMeans
4
  from PIL import Image
5
+ import os
6
+ from io import BytesIO
7
 
8
  def compress_kmeans(image: np.ndarray, k: int) -> np.ndarray:
 
9
  pixels = image.reshape(-1, 3)
10
  model = KMeans(n_clusters=k, n_init="auto", random_state=42)
11
  model.fit(pixels)
 
13
  labels = model.predict(pixels)
14
  return centres[labels].reshape(image.shape)
15
 
16
+ def compress_and_report(filepath, k):
17
+ orig_filename = os.path.splitext(os.path.basename(filepath))[0]
18
+ orig_pil = Image.open(filepath).convert("RGB")
19
+ orig_np = np.array(orig_pil)
20
+
21
+ comp_np = compress_kmeans(orig_np, k)
22
+ comp_pil = Image.fromarray(comp_np)
23
+
24
+ buf_orig = BytesIO()
25
+ orig_pil.save(buf_orig, format="PNG")
26
+ orig_size = len(buf_orig.getvalue())
27
+
28
+ buf_comp = BytesIO()
29
+ comp_pil.save(buf_comp, format="PNG")
30
+ comp_size = len(buf_comp.getvalue())
31
+
32
+ mse = np.mean((orig_np.astype(np.float64) - comp_np.astype(np.float64)) ** 2)
33
  psnr = float("inf") if mse == 0 else 20.0 * np.log10(255.0 / np.sqrt(mse))
 
34
 
35
+ ratio = orig_size / comp_size
36
+ saved_pct = (1.0 - (comp_size / orig_size)) * 100.0
37
+ orig_kb = orig_size / 1024
38
+ comp_kb = comp_size / 1024
 
 
 
 
 
 
 
 
39
 
40
+ if psnr > 35:
41
+ quality_badge = '<span style="color:#16a34a;">Excellent</span>'
42
+ elif psnr > 28:
43
+ quality_badge = '<span style="color:#eab308;">Good</span>'
44
  else:
45
+ quality_badge = '<span style="color:#dc2626;">Acceptable</span>'
46
 
47
+ report_html = f"""
48
+ <div style="background: #f8f9fa; padding: 16px; border-radius: 8px; border-left: 4px solid #2563eb; font-family: sans-serif;">
49
+ <h4 style="margin: 0 0 12px 0; color: #1e293b;">Compression Report</h4>
50
+ <table style="width: 100%; border-collapse: collapse; font-size: 14px;">
51
+ <tr style="border-bottom: 1px solid #e2e8f0;">
52
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">PSNR</td>
53
+ <td style="padding: 6px 0; color: #0f172a;">{psnr:.2f} dB</td>
54
+ <td style="padding: 6px 0;">{quality_badge}</td>
55
+ </tr>
56
+ <tr style="border-bottom: 1px solid #e2e8f0;">
57
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">MSE</td>
58
+ <td style="padding: 6px 0; color: #0f172a;" colspan="2">{mse:.2f}</td>
59
+ </tr>
60
+ <tr style="border-bottom: 1px solid #e2e8f0;">
61
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">Compression Ratio</td>
62
+ <td style="padding: 6px 0; color: #0f172a;" colspan="2">{ratio:.2f}x</td>
63
+ </tr>
64
+ <tr style="border-bottom: 1px solid #e2e8f0;">
65
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">Original Size</td>
66
+ <td style="padding: 6px 0; color: #0f172a;" colspan="2">{orig_kb:.2f} KB</td>
67
+ </tr>
68
+ <tr>
69
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">Compressed Size</td>
70
+ <td style="padding: 6px 0; color: #0f172a;">{comp_kb:.2f} KB</td>
71
+ <td style="padding: 6px 0; color: #2563eb; font-weight: 600;">↓ {saved_pct:.1f}%</td>
72
+ </tr>
73
+ </table>
74
+ </div>
75
+ """
76
 
77
+ download_name = f"{orig_filename}_compressed_{int(comp_kb)}KB.png"
78
+ return gr.update(value=comp_np, download_name=download_name), report_html
 
 
 
 
 
 
 
 
 
79
 
80
  iface = gr.Interface(
81
  fn=compress_and_report,
82
  inputs=[
83
+ gr.Image(type="filepath", label="Upload Image"),
84
  gr.Slider(minimum=2, maximum=64, step=2, value=16, label="Number of Colors (k)"),
85
  ],
86
  outputs=[
87
  gr.Image(type="numpy", label="Compressed Image"),
88
+ gr.Markdown(label="Metrics Report"),
89
  ],
90
  title="Image Compression using K-Means",
91
  description="Upload an image and adjust the colour palette size to see lossy compression in action.",