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

fix: file size mismatch, download filename change

Browse files
Files changed (1) hide show
  1. app.py +79 -54
app.py CHANGED
@@ -3,6 +3,7 @@ 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:
@@ -14,69 +15,93 @@ def compress_kmeans(image: np.ndarray, k: int) -> np.ndarray:
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=[
 
3
  from sklearn.cluster import KMeans
4
  from PIL import Image
5
  import os
6
+ import traceback
7
  from io import BytesIO
8
 
9
  def compress_kmeans(image: np.ndarray, k: int) -> np.ndarray:
 
15
  return centres[labels].reshape(image.shape)
16
 
17
  def compress_and_report(filepath, k):
18
+ if filepath is None:
19
+ return None, "<div style='padding: 20px; color: #64748b;'>Upload an image to view the report.</div>"
 
20
 
21
+ try:
22
+ # Safely extract original filename
23
+ if isinstance(filepath, str):
24
+ base = os.path.basename(filepath)
25
+ else:
26
+ base = os.path.basename(str(filepath))
27
+ orig_filename = os.path.splitext(base)[0]
28
 
29
+ orig_pil = Image.open(filepath).convert("RGB")
30
+ orig_np = np.array(orig_pil)
 
31
 
32
+ comp_np = compress_kmeans(orig_np, k)
33
+ comp_pil = Image.fromarray(comp_np)
 
34
 
35
+ # Measure actual PNG byte sizes
36
+ buf_orig = BytesIO()
37
+ orig_pil.save(buf_orig, format="PNG")
38
+ orig_size = len(buf_orig.getvalue())
39
 
40
+ buf_comp = BytesIO()
41
+ comp_pil.save(buf_comp, format="PNG")
42
+ comp_size = len(buf_comp.getvalue())
 
43
 
44
+ # Metrics
45
+ mse = np.mean((orig_np.astype(np.float64) - comp_np.astype(np.float64)) ** 2)
46
+ psnr = float("inf") if mse == 0 else 20.0 * np.log10(255.0 / np.sqrt(mse))
47
+ ratio = orig_size / comp_size
48
+ saved_pct = (1.0 - (comp_size / orig_size)) * 100.0
49
+ orig_kb = orig_size / 1024
50
+ comp_kb = comp_size / 1024
51
 
52
+ # Quality badge
53
+ if psnr > 35:
54
+ badge = '<span style="color:#16a34a;">Excellent</span>'
55
+ elif psnr > 28:
56
+ badge = '<span style="color:#eab308;">Good</span>'
57
+ else:
58
+ badge = '<span style="color:#dc2626;">Acceptable</span>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ # HTML Report
61
+ report_html = f"""
62
+ <div style="background: #f8f9fa; padding: 16px; border-radius: 8px; border-left: 4px solid #2563eb; font-family: sans-serif;">
63
+ <h4 style="margin: 0 0 12px 0; color: #1e293b;">Compression Report</h4>
64
+ <table style="width: 100%; border-collapse: collapse; font-size: 14px;">
65
+ <tr style="border-bottom: 1px solid #e2e8f0;">
66
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">PSNR</td>
67
+ <td style="padding: 6px 0; color: #0f172a;">{psnr:.2f} dB</td>
68
+ <td style="padding: 6px 0;">{badge}</td>
69
+ </tr>
70
+ <tr style="border-bottom: 1px solid #e2e8f0;">
71
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">MSE</td>
72
+ <td style="padding: 6px 0; color: #0f172a;" colspan="2">{mse:.2f}</td>
73
+ </tr>
74
+ <tr style="border-bottom: 1px solid #e2e8f0;">
75
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">Compression Ratio</td>
76
+ <td style="padding: 6px 0; color: #0f172a;" colspan="2">{ratio:.2f}x</td>
77
+ </tr>
78
+ <tr style="border-bottom: 1px solid #e2e8f0;">
79
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">Original Size</td>
80
+ <td style="padding: 6px 0; color: #0f172a;" colspan="2">{orig_kb:.2f} KB</td>
81
+ </tr>
82
+ <tr>
83
+ <td style="padding: 6px 0; font-weight: 600; color: #475569;">Compressed Size</td>
84
+ <td style="padding: 6px 0; color: #0f172a;">{comp_kb:.2f} KB</td>
85
+ <td style="padding: 6px 0; color: #2563eb; font-weight: 600;">↓ {saved_pct:.1f}%</td>
86
+ </tr>
87
+ </table>
88
+ </div>
89
+ """
90
 
91
+ download_name = f"{orig_filename}_compressed_{int(comp_kb)}KB.png"
92
+ return gr.update(value=comp_np, download_name=download_name), report_html
93
+
94
+ except Exception as e:
95
+ error_trace = traceback.format_exc()
96
+ error_html = f"""
97
+ <div style="background: #fee2e2; padding: 16px; border-radius: 8px; border-left: 4px solid #dc2626; font-family: monospace; font-size: 13px;">
98
+ <strong style="color: #991b1b;">Runtime Error</strong>
99
+ <pre style="white-space: pre-wrap; word-wrap: break-word; margin-top: 8px;">{error_trace}</pre>
100
+ </div>
101
+ """
102
+ return None, error_html
103
+
104
+ # Gradio Interface
105
  iface = gr.Interface(
106
  fn=compress_and_report,
107
  inputs=[