priyadip commited on
Commit
2c187d3
·
verified ·
1 Parent(s): 8b01c9f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +126 -89
app.py CHANGED
@@ -3,10 +3,26 @@ import numpy as np
3
  import matplotlib
4
  matplotlib.use('Agg')
5
  import matplotlib.pyplot as plt
 
6
 
7
  COLORS = ['#00d4ff','#7c3aed','#f59e0b','#10b981','#f43f5e',
8
  '#a78bfa','#34d399','#fb923c','#60a5fa','#e879f9']
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  def parse_points(text):
11
  points, errors = [], []
12
  for i, line in enumerate([l.strip() for l in text.strip().split('\n') if l.strip()]):
@@ -20,83 +36,122 @@ def parse_points(text):
20
  errors.append(f"Line {i+1}: invalid numbers")
21
  return (np.array(points) if points else None), errors
22
 
23
- def run_pca(points_text):
24
- points, errors = parse_points(points_text)
25
- if errors:
26
- return "❌ Input Errors:\n" + "\n".join(errors), None, None, None
27
- if points is None or len(points) < 6:
28
- n = len(points) if points is not None else 0
29
- return f"❌ Need at least 6 points (you entered {n})", None, None, None
30
-
31
- n = len(points)
32
- mean = np.mean(points, axis=0)
33
- centered = points - mean
34
- cov = np.cov(centered.T)
35
- eigenvalues, eigenvectors = np.linalg.eigh(cov)
36
- idx = np.argsort(eigenvalues)[::-1]
37
- eigenvalues = eigenvalues[idx]
38
- eigenvectors = eigenvectors[:, idx]
39
- W = eigenvectors[:, :2]
40
- projected = centered @ W
41
- var_exp = eigenvalues / np.sum(eigenvalues) * 100
42
 
43
- sep = "=" * 58
44
- thin = "-" * 58
45
- L = []
46
  L.append(sep)
47
- L.append(" PCA | 3D to 2D DIMENSIONALITY REDUCTION")
48
  L.append(sep)
49
 
50
- L.append("\n > STEP 1 | INPUT POINTS")
 
51
  L.append(" " + thin)
52
- L.append(f" {'#':<6} {'X':>10} {'Y':>10} {'Z':>10}")
53
  L.append(" " + thin)
54
  for i, p in enumerate(points):
55
- L.append(f" P{i+1:<5} {p[0]:>10.4f} {p[1]:>10.4f} {p[2]:>10.4f}")
56
 
57
- L.append("\n > STEP 2 | MEAN & CENTERED POINTS")
 
58
  L.append(" " + thin)
59
- L.append(f" Mean = ({mean[0]:.4f}, {mean[1]:.4f}, {mean[2]:.4f})\n")
60
- L.append(f" {'#':<6} {'X-mx':>10} {'Y-my':>10} {'Z-mz':>10}")
61
  L.append(" " + thin)
62
  for i, p in enumerate(centered):
63
- L.append(f" P{i+1:<5} {p[0]:>10.4f} {p[1]:>10.4f} {p[2]:>10.4f}")
64
 
65
- L.append("\n > STEP 3 | COVARIANCE MATRIX (3x3)")
 
66
  L.append(" " + thin)
67
- L.append(f" {'':>6} {'X':>10} {'Y':>10} {'Z':>10}")
68
  for i, lbl in enumerate(['X','Y','Z']):
69
- L.append(f" {lbl:<6}" + "".join(f"{cov[i,j]:>10.4f}" for j in range(3)))
70
 
71
- L.append("\n > STEP 4 | EIGENVALUES & EIGENVECTORS")
 
72
  L.append(" " + thin)
73
  for i in range(3):
74
  ev = eigenvectors[:, i]
75
- L.append(f" L{i+1} = {eigenvalues[i]:>10.6f} | v{i+1} = [{ev[0]:>8.4f}, {ev[1]:>8.4f}, {ev[2]:>8.4f}]")
76
-
77
- L.append("\n > STEP 5 | TOP 2 PRINCIPAL COMPONENTS")
 
 
 
 
 
78
  L.append(" " + thin)
79
- L.append(f" PC1 L={eigenvalues[0]:.4f} -> [{W[0,0]:>7.4f}, {W[1,0]:>7.4f}, {W[2,0]:>7.4f}]")
80
- L.append(f" PC2 L={eigenvalues[1]:.4f} -> [{W[0,1]:>7.4f}, {W[1,1]:>7.4f}, {W[2,1]:>7.4f}]")
81
- L.append(f"\n Variance: PC1={var_exp[0]:.2f}% PC2={var_exp[1]:.2f}% Total={var_exp[0]+var_exp[1]:.2f}%")
82
-
83
- L.append("\n > STEP 6 | PROJECTION MATRIX W (3x2)")
 
 
 
 
 
 
 
 
84
  L.append(" " + thin)
85
- L.append(f" {'':>4} {'PC1':>10} {'PC2':>10}")
86
  for i, lbl in enumerate(['x','y','z']):
87
- L.append(f" {lbl:<4} {W[i,0]:>10.4f} {W[i,1]:>10.4f}")
88
 
89
- L.append("\n > STEP 7 | REDUCED 2D COORDINATES")
 
90
  L.append(" " + thin)
91
- L.append(f" {'#':<6} {'3D (X, Y, Z)':<32} 2D (PC1, PC2)")
92
  L.append(" " + thin)
93
- for i in range(n):
94
- orig = f"({points[i,0]:.2f}, {points[i,1]:.2f}, {points[i,2]:.2f})"
95
- red = f"({projected[i,0]:.4f}, {projected[i,1]:.4f})"
96
- L.append(f" P{i+1:<5} {orig:<32} {red}")
 
 
 
 
 
 
 
97
 
98
  L.append("\n" + sep)
99
- output_text = "\n".join(L)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  fig1 = make_3d_plot(points, mean)
102
  fig2 = make_2d_plot(projected, var_exp)
@@ -114,7 +169,7 @@ def _style_ax(ax):
114
 
115
  def make_3d_plot(points, mean):
116
  fig = plt.figure(figsize=(6, 5), facecolor='#0a0e1a')
117
- ax = fig.add_subplot(111, projection='3d')
118
  ax.set_facecolor('#111827')
119
  for pane in [ax.xaxis.pane, ax.yaxis.pane, ax.zaxis.pane]:
120
  pane.fill = False
@@ -154,9 +209,8 @@ def make_2d_plot(projected, var_exp):
154
 
155
  def make_var_plot(var_exp):
156
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), facecolor='#0a0e1a')
157
- labels = ['PC1','PC2','PC3']
158
  bar_colors = ['#00d4ff','#7c3aed','#f59e0b']
159
-
160
  _style_ax(ax1)
161
  bars = ax1.bar(labels, var_exp, color=bar_colors, edgecolor='#0a0e1a', linewidth=1.5, width=0.5)
162
  for bar, val in zip(bars, var_exp):
@@ -165,7 +219,6 @@ def make_var_plot(var_exp):
165
  ax1.set_title('Variance per PC', color='#e2e8f0', fontsize=10, fontweight='bold', pad=8)
166
  ax1.set_ylabel('Variance (%)', color='#64748b', fontsize=9)
167
  ax1.set_ylim(0, max(var_exp)*1.2)
168
-
169
  _style_ax(ax2)
170
  cum = np.cumsum(var_exp)
171
  ax2.plot(labels, cum, 'o-', color='#10b981', lw=2.5, markersize=8,
@@ -178,23 +231,18 @@ def make_var_plot(var_exp):
178
  ax2.set_ylabel('Cumulative (%)', color='#64748b', fontsize=9)
179
  ax2.set_ylim(0, 115)
180
  ax2.grid(True, color='#1e293b', lw=0.5)
181
-
182
  fig.tight_layout(pad=2)
183
  return fig
184
 
185
 
186
- # No @import, no external fonts — all inline, loads instantly
187
  css = """
188
  * { box-sizing: border-box; }
189
-
190
  body, .gradio-container {
191
  background: #0a0e1a !important;
192
  color: #e2e8f0 !important;
193
  font-family: ui-monospace, 'Cascadia Code', 'Segoe UI Mono', monospace !important;
194
  }
195
-
196
  .gradio-container { max-width: 1200px !important; margin: 0 auto !important; }
197
-
198
  .header {
199
  background: #111827;
200
  border: 1px solid #1e293b;
@@ -203,17 +251,8 @@ body, .gradio-container {
203
  padding: 28px 36px;
204
  margin-bottom: 20px;
205
  }
206
-
207
- .header h1 {
208
- font-size: 1.9rem;
209
- font-weight: 800;
210
- color: #00d4ff;
211
- margin: 0 0 6px 0;
212
- letter-spacing: -0.02em;
213
- }
214
-
215
- .header p { color: #64748b; font-size: 0.82rem; margin: 0; }
216
-
217
  .hint {
218
  background: rgba(0,212,255,0.04);
219
  border: 1px solid rgba(0,212,255,0.15);
@@ -224,9 +263,7 @@ body, .gradio-container {
224
  line-height: 1.8;
225
  margin-top: 10px;
226
  }
227
-
228
  .hint strong { color: #00d4ff; }
229
-
230
  textarea {
231
  background: #060a12 !important;
232
  border: 1px solid #1e293b !important;
@@ -235,16 +272,8 @@ textarea {
235
  font-size: 0.8rem !important;
236
  border-radius: 8px !important;
237
  }
238
-
239
- textarea:focus {
240
- border-color: #00d4ff !important;
241
- outline: none !important;
242
- }
243
-
244
- button {
245
- font-weight: 700 !important;
246
- border-radius: 8px !important;
247
- }
248
  """
249
 
250
  EXAMPLE = """1 2 3
@@ -268,14 +297,22 @@ with gr.Blocks(title="PCA 3D to 2D") as demo:
268
  points_input = gr.Textbox(
269
  label="3D Points (one per line: X Y Z)",
270
  placeholder="1 2 3\n4 5 6\n...",
271
- lines=12,
272
  value=EXAMPLE
273
  )
 
 
 
 
 
 
 
274
  gr.HTML("""<div class="hint">
275
- <strong>Format:</strong> X Y Z &nbsp;(space or comma separated)<br>
276
- <strong>Minimum:</strong> 6 points required<br>
277
- <strong>Example:</strong> &nbsp;1 2 3 &nbsp;or&nbsp; 1,2,3
278
  </div>""")
 
279
  with gr.Row():
280
  run_btn = gr.Button("Run PCA", variant="primary")
281
  clear_btn = gr.Button("Clear", variant="secondary")
@@ -286,7 +323,7 @@ with gr.Blocks(title="PCA 3D to 2D") as demo:
286
  steps_out = gr.Textbox(
287
  label="Full PCA Computation",
288
  interactive=False,
289
- lines=34
290
  )
291
  with gr.Tab("3D Input Plot"):
292
  plot_3d = gr.Plot(label="Original 3D Points")
@@ -297,7 +334,7 @@ with gr.Blocks(title="PCA 3D to 2D") as demo:
297
 
298
  run_btn.click(
299
  fn=run_pca,
300
- inputs=points_input,
301
  outputs=[steps_out, plot_3d, plot_2d, plot_var]
302
  )
303
  clear_btn.click(
 
3
  import matplotlib
4
  matplotlib.use('Agg')
5
  import matplotlib.pyplot as plt
6
+ from fractions import Fraction
7
 
8
  COLORS = ['#00d4ff','#7c3aed','#f59e0b','#10b981','#f43f5e',
9
  '#a78bfa','#34d399','#fb923c','#60a5fa','#e879f9']
10
 
11
+ def to_frac(val, max_denom=64):
12
+ f = Fraction(val).limit_denominator(max_denom)
13
+ return str(f)
14
+
15
+ def fmt(val, mode):
16
+ if mode == "Fraction":
17
+ return f"{to_frac(val):>14}"
18
+ elif mode == "Both":
19
+ return f"{val:>10.4f} ({to_frac(val):>10})"
20
+ else:
21
+ return f"{val:>10.4f}"
22
+
23
+ def col_width(mode):
24
+ return 24 if mode == "Both" else 14 if mode == "Fraction" else 10
25
+
26
  def parse_points(text):
27
  points, errors = [], []
28
  for i, line in enumerate([l.strip() for l in text.strip().split('\n') if l.strip()]):
 
36
  errors.append(f"Line {i+1}: invalid numbers")
37
  return (np.array(points) if points else None), errors
38
 
39
+ def build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, mode):
40
+ sep = "=" * 66
41
+ thin = "-" * 66
42
+ cw = col_width(mode)
43
+ L = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ title_mode = f"({mode})"
 
 
46
  L.append(sep)
47
+ L.append(f" PCA | 3D to 2D DIMENSIONALITY REDUCTION {title_mode}")
48
  L.append(sep)
49
 
50
+ # STEP 1
51
+ L.append(f"\n > STEP 1 | INPUT POINTS")
52
  L.append(" " + thin)
53
+ L.append(f" {'#':<6} {'X':>{cw}} {'Y':>{cw}} {'Z':>{cw}}")
54
  L.append(" " + thin)
55
  for i, p in enumerate(points):
56
+ L.append(f" P{i+1:<5} {fmt(p[0],mode)} {fmt(p[1],mode)} {fmt(p[2],mode)}")
57
 
58
+ # STEP 2
59
+ L.append(f"\n > STEP 2 | MEAN & CENTERED POINTS")
60
  L.append(" " + thin)
61
+ L.append(f" Mean = ( {fmt(mean[0],mode).strip()}, {fmt(mean[1],mode).strip()}, {fmt(mean[2],mode).strip()} )\n")
62
+ L.append(f" {'#':<6} {'X-mx':>{cw}} {'Y-my':>{cw}} {'Z-mz':>{cw}}")
63
  L.append(" " + thin)
64
  for i, p in enumerate(centered):
65
+ L.append(f" P{i+1:<5} {fmt(p[0],mode)} {fmt(p[1],mode)} {fmt(p[2],mode)}")
66
 
67
+ # STEP 3
68
+ L.append(f"\n > STEP 3 | COVARIANCE MATRIX (3x3)")
69
  L.append(" " + thin)
70
+ L.append(f" {'':>6} {'X':>{cw}} {'Y':>{cw}} {'Z':>{cw}}")
71
  for i, lbl in enumerate(['X','Y','Z']):
72
+ L.append(f" {lbl:<6}" + "".join(f"{fmt(cov[i,j],mode)}" for j in range(3)))
73
 
74
+ # STEP 4
75
+ L.append(f"\n > STEP 4 | EIGENVALUES & EIGENVECTORS")
76
  L.append(" " + thin)
77
  for i in range(3):
78
  ev = eigenvectors[:, i]
79
+ lv = fmt(eigenvalues[i], mode).strip()
80
+ v0 = fmt(ev[0], mode).strip()
81
+ v1 = fmt(ev[1], mode).strip()
82
+ v2 = fmt(ev[2], mode).strip()
83
+ L.append(f" L{i+1} = {lv:<20} | v{i+1} = [ {v0}, {v1}, {v2} ]")
84
+
85
+ # STEP 5
86
+ L.append(f"\n > STEP 5 | TOP 2 PRINCIPAL COMPONENTS")
87
  L.append(" " + thin)
88
+ for ci, label in enumerate(['PC1','PC2']):
89
+ lv = fmt(eigenvalues[ci], mode).strip()
90
+ w0 = fmt(W[0,ci], mode).strip()
91
+ w1 = fmt(W[1,ci], mode).strip()
92
+ w2 = fmt(W[2,ci], mode).strip()
93
+ L.append(f" {label} L={lv:<20} -> [ {w0}, {w1}, {w2} ]")
94
+ v1s = fmt(var_exp[0], mode).strip()
95
+ v2s = fmt(var_exp[1], mode).strip()
96
+ tot = fmt(var_exp[0]+var_exp[1], mode).strip()
97
+ L.append(f"\n Variance: PC1={v1s}% PC2={v2s}% Total={tot}%")
98
+
99
+ # STEP 6
100
+ L.append(f"\n > STEP 6 | PROJECTION MATRIX W (3x2)")
101
  L.append(" " + thin)
102
+ L.append(f" {'':>4} {'PC1':>{cw}} {'PC2':>{cw}}")
103
  for i, lbl in enumerate(['x','y','z']):
104
+ L.append(f" {lbl:<4} {fmt(W[i,0],mode)} {fmt(W[i,1],mode)}")
105
 
106
+ # STEP 7
107
+ L.append(f"\n > STEP 7 | REDUCED 2D COORDINATES")
108
  L.append(" " + thin)
109
+ L.append(f" {'#':<6} {'3D (X, Y, Z)':<36} 2D (PC1, PC2)")
110
  L.append(" " + thin)
111
+ for i in range(len(points)):
112
+ if mode == "Fraction":
113
+ orig = f"({to_frac(points[i,0])}, {to_frac(points[i,1])}, {to_frac(points[i,2])})"
114
+ red = f"({to_frac(projected[i,0])}, {to_frac(projected[i,1])})"
115
+ elif mode == "Both":
116
+ orig = f"({points[i,0]:.2f}, {points[i,1]:.2f}, {points[i,2]:.2f})"
117
+ red = f"({projected[i,0]:.4f} ({to_frac(projected[i,0])}), {projected[i,1]:.4f} ({to_frac(projected[i,1])}))"
118
+ else:
119
+ orig = f"({points[i,0]:.2f}, {points[i,1]:.2f}, {points[i,2]:.2f})"
120
+ red = f"({projected[i,0]:.4f}, {projected[i,1]:.4f})"
121
+ L.append(f" P{i+1:<5} {orig:<36} {red}")
122
 
123
  L.append("\n" + sep)
124
+ return "\n".join(L)
125
+
126
+
127
+ def run_pca(points_text, output_mode):
128
+ points, errors = parse_points(points_text)
129
+ if errors:
130
+ return "❌ Input Errors:\n" + "\n".join(errors), None, None, None
131
+ if points is None or len(points) < 6:
132
+ n = len(points) if points is not None else 0
133
+ return f"❌ Need at least 6 points (you entered {n})", None, None, None
134
+
135
+ mean = np.mean(points, axis=0)
136
+ centered = points - mean
137
+ cov = np.cov(centered.T)
138
+ eigenvalues, eigenvectors = np.linalg.eigh(cov)
139
+ idx = np.argsort(eigenvalues)[::-1]
140
+ eigenvalues = eigenvalues[idx]
141
+ eigenvectors = eigenvectors[:, idx]
142
+ W = eigenvectors[:, :2]
143
+ projected = centered @ W
144
+ var_exp = eigenvalues / np.sum(eigenvalues) * 100
145
+
146
+ if output_mode == "Both (Decimal + Fraction)":
147
+ # Show both sections separated
148
+ dec_block = build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, "Decimal")
149
+ frac_block = build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, "Fraction")
150
+ output_text = dec_block + "\n\n\n" + frac_block
151
+ elif output_mode == "Fraction only":
152
+ output_text = build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, "Fraction")
153
+ else:
154
+ output_text = build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, "Decimal")
155
 
156
  fig1 = make_3d_plot(points, mean)
157
  fig2 = make_2d_plot(projected, var_exp)
 
169
 
170
  def make_3d_plot(points, mean):
171
  fig = plt.figure(figsize=(6, 5), facecolor='#0a0e1a')
172
+ ax = fig.add_subplot(111, projection='3d')
173
  ax.set_facecolor('#111827')
174
  for pane in [ax.xaxis.pane, ax.yaxis.pane, ax.zaxis.pane]:
175
  pane.fill = False
 
209
 
210
  def make_var_plot(var_exp):
211
  fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), facecolor='#0a0e1a')
212
+ labels = ['PC1','PC2','PC3']
213
  bar_colors = ['#00d4ff','#7c3aed','#f59e0b']
 
214
  _style_ax(ax1)
215
  bars = ax1.bar(labels, var_exp, color=bar_colors, edgecolor='#0a0e1a', linewidth=1.5, width=0.5)
216
  for bar, val in zip(bars, var_exp):
 
219
  ax1.set_title('Variance per PC', color='#e2e8f0', fontsize=10, fontweight='bold', pad=8)
220
  ax1.set_ylabel('Variance (%)', color='#64748b', fontsize=9)
221
  ax1.set_ylim(0, max(var_exp)*1.2)
 
222
  _style_ax(ax2)
223
  cum = np.cumsum(var_exp)
224
  ax2.plot(labels, cum, 'o-', color='#10b981', lw=2.5, markersize=8,
 
231
  ax2.set_ylabel('Cumulative (%)', color='#64748b', fontsize=9)
232
  ax2.set_ylim(0, 115)
233
  ax2.grid(True, color='#1e293b', lw=0.5)
 
234
  fig.tight_layout(pad=2)
235
  return fig
236
 
237
 
 
238
  css = """
239
  * { box-sizing: border-box; }
 
240
  body, .gradio-container {
241
  background: #0a0e1a !important;
242
  color: #e2e8f0 !important;
243
  font-family: ui-monospace, 'Cascadia Code', 'Segoe UI Mono', monospace !important;
244
  }
 
245
  .gradio-container { max-width: 1200px !important; margin: 0 auto !important; }
 
246
  .header {
247
  background: #111827;
248
  border: 1px solid #1e293b;
 
251
  padding: 28px 36px;
252
  margin-bottom: 20px;
253
  }
254
+ .header h1 { font-size: 1.9rem; font-weight: 800; color: #00d4ff; margin: 0 0 6px 0; }
255
+ .header p { color: #64748b; font-size: 0.82rem; margin: 0; }
 
 
 
 
 
 
 
 
 
256
  .hint {
257
  background: rgba(0,212,255,0.04);
258
  border: 1px solid rgba(0,212,255,0.15);
 
263
  line-height: 1.8;
264
  margin-top: 10px;
265
  }
 
266
  .hint strong { color: #00d4ff; }
 
267
  textarea {
268
  background: #060a12 !important;
269
  border: 1px solid #1e293b !important;
 
272
  font-size: 0.8rem !important;
273
  border-radius: 8px !important;
274
  }
275
+ textarea:focus { border-color: #00d4ff !important; outline: none !important; }
276
+ button { font-weight: 700 !important; border-radius: 8px !important; }
 
 
 
 
 
 
 
 
277
  """
278
 
279
  EXAMPLE = """1 2 3
 
297
  points_input = gr.Textbox(
298
  label="3D Points (one per line: X Y Z)",
299
  placeholder="1 2 3\n4 5 6\n...",
300
+ lines=10,
301
  value=EXAMPLE
302
  )
303
+
304
+ output_mode = gr.Radio(
305
+ choices=["Decimal only", "Fraction only", "Both (Decimal + Fraction)"],
306
+ value="Both (Decimal + Fraction)",
307
+ label="Output Format"
308
+ )
309
+
310
  gr.HTML("""<div class="hint">
311
+ <strong>Decimal:</strong> 0.3333<br>
312
+ <strong>Fraction:</strong> 1/3<br>
313
+ <strong>Both:</strong> shows decimal then fraction section
314
  </div>""")
315
+
316
  with gr.Row():
317
  run_btn = gr.Button("Run PCA", variant="primary")
318
  clear_btn = gr.Button("Clear", variant="secondary")
 
323
  steps_out = gr.Textbox(
324
  label="Full PCA Computation",
325
  interactive=False,
326
+ lines=36
327
  )
328
  with gr.Tab("3D Input Plot"):
329
  plot_3d = gr.Plot(label="Original 3D Points")
 
334
 
335
  run_btn.click(
336
  fn=run_pca,
337
+ inputs=[points_input, output_mode],
338
  outputs=[steps_out, plot_3d, plot_2d, plot_var]
339
  )
340
  clear_btn.click(