CHECKING / app.py
priyadip's picture
Update app.py
2368c1a verified
import gradio as gr
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from fractions import Fraction
COLORS = ['#00d4ff','#7c3aed','#f59e0b','#10b981','#f43f5e',
'#a78bfa','#34d399','#fb923c','#60a5fa','#e879f9']
def to_frac(val, max_denom=64):
f = Fraction(val).limit_denominator(max_denom)
return str(f)
def fmt(val, mode):
if mode == "Fraction":
return f"{to_frac(val):>14}"
elif mode == "Both":
return f"{val:>10.4f} ({to_frac(val):>10})"
else:
return f"{val:>10.4f}"
def col_width(mode):
return 24 if mode == "Both" else 14 if mode == "Fraction" else 10
def parse_points(text):
points, errors = [], []
for i, line in enumerate([l.strip() for l in text.strip().split('\n') if l.strip()]):
try:
vals = [float(x) for x in line.replace(',', ' ').split()]
if len(vals) != 3:
errors.append(f"Line {i+1}: need 3 values, got {len(vals)}")
else:
points.append(vals)
except:
errors.append(f"Line {i+1}: invalid numbers")
return (np.array(points) if points else None), errors
def build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, mode):
sep = "=" * 66
thin = "-" * 66
cw = col_width(mode)
L = []
title_mode = f"({mode})"
L.append(sep)
L.append(f" PCA | 3D to 2D DIMENSIONALITY REDUCTION {title_mode}")
L.append(sep)
# STEP 1
L.append(f"\n > STEP 1 | INPUT POINTS")
L.append(" " + thin)
L.append(f" {'#':<6} {'X':>{cw}} {'Y':>{cw}} {'Z':>{cw}}")
L.append(" " + thin)
for i, p in enumerate(points):
L.append(f" P{i+1:<5} {fmt(p[0],mode)} {fmt(p[1],mode)} {fmt(p[2],mode)}")
# STEP 2
L.append(f"\n > STEP 2 | MEAN & CENTERED POINTS")
L.append(" " + thin)
L.append(f" Mean = ( {fmt(mean[0],mode).strip()}, {fmt(mean[1],mode).strip()}, {fmt(mean[2],mode).strip()} )\n")
L.append(f" {'#':<6} {'X-mx':>{cw}} {'Y-my':>{cw}} {'Z-mz':>{cw}}")
L.append(" " + thin)
for i, p in enumerate(centered):
L.append(f" P{i+1:<5} {fmt(p[0],mode)} {fmt(p[1],mode)} {fmt(p[2],mode)}")
# STEP 3
L.append(f"\n > STEP 3 | COVARIANCE MATRIX (3x3)")
L.append(" " + thin)
L.append(f" {'':>6} {'X':>{cw}} {'Y':>{cw}} {'Z':>{cw}}")
for i, lbl in enumerate(['X','Y','Z']):
L.append(f" {lbl:<6}" + "".join(f"{fmt(cov[i,j],mode)}" for j in range(3)))
# STEP 4
L.append(f"\n > STEP 4 | EIGENVALUES & EIGENVECTORS")
L.append(" " + thin)
for i in range(3):
ev = eigenvectors[:, i]
lv = fmt(eigenvalues[i], mode).strip()
v0 = fmt(ev[0], mode).strip()
v1 = fmt(ev[1], mode).strip()
v2 = fmt(ev[2], mode).strip()
L.append(f" L{i+1} = {lv:<20} | v{i+1} = [ {v0}, {v1}, {v2} ]")
# STEP 5
L.append(f"\n > STEP 5 | TOP 2 PRINCIPAL COMPONENTS")
L.append(" " + thin)
for ci, label in enumerate(['PC1','PC2']):
lv = fmt(eigenvalues[ci], mode).strip()
w0 = fmt(W[0,ci], mode).strip()
w1 = fmt(W[1,ci], mode).strip()
w2 = fmt(W[2,ci], mode).strip()
L.append(f" {label} L={lv:<20} -> [ {w0}, {w1}, {w2} ]")
v1s = fmt(var_exp[0], mode).strip()
v2s = fmt(var_exp[1], mode).strip()
tot = fmt(var_exp[0]+var_exp[1], mode).strip()
L.append(f"\n Variance: PC1={v1s}% PC2={v2s}% Total={tot}%")
# STEP 6
L.append(f"\n > STEP 6 | PROJECTION MATRIX W (3x2)")
L.append(" " + thin)
L.append(f" {'':>4} {'PC1':>{cw}} {'PC2':>{cw}}")
for i, lbl in enumerate(['x','y','z']):
L.append(f" {lbl:<4} {fmt(W[i,0],mode)} {fmt(W[i,1],mode)}")
# STEP 7
L.append(f"\n > STEP 7 | REDUCED 2D COORDINATES")
L.append(" " + thin)
L.append(f" {'#':<6} {'3D (X, Y, Z)':<36} 2D (PC1, PC2)")
L.append(" " + thin)
for i in range(len(points)):
if mode == "Fraction":
orig = f"({to_frac(points[i,0])}, {to_frac(points[i,1])}, {to_frac(points[i,2])})"
red = f"({to_frac(projected[i,0])}, {to_frac(projected[i,1])})"
elif mode == "Both":
orig = f"({points[i,0]:.2f}, {points[i,1]:.2f}, {points[i,2]:.2f})"
red = f"({projected[i,0]:.4f} ({to_frac(projected[i,0])}), {projected[i,1]:.4f} ({to_frac(projected[i,1])}))"
else:
orig = f"({points[i,0]:.2f}, {points[i,1]:.2f}, {points[i,2]:.2f})"
red = f"({projected[i,0]:.4f}, {projected[i,1]:.4f})"
L.append(f" P{i+1:<5} {orig:<36} {red}")
L.append("\n" + sep)
return "\n".join(L)
def run_pca(points_text, output_mode):
points, errors = parse_points(points_text)
if errors:
return "❌ Input Errors:\n" + "\n".join(errors), None, None, None
if points is None or len(points) < 6:
n = len(points) if points is not None else 0
return f"❌ Need at least 6 points (you entered {n})", None, None, None
mean = np.mean(points, axis=0)
centered = points - mean
cov = np.cov(centered.T)
eigenvalues, eigenvectors = np.linalg.eigh(cov)
idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]
W = eigenvectors[:, :2]
projected = centered @ W
var_exp = eigenvalues / np.sum(eigenvalues) * 100
if output_mode == "Both (Decimal + Fraction)":
# Show both sections separated
dec_block = build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, "Decimal")
frac_block = build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, "Fraction")
output_text = dec_block + "\n\n\n" + frac_block
elif output_mode == "Fraction only":
output_text = build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, "Fraction")
else:
output_text = build_output(points, mean, centered, cov, eigenvalues, eigenvectors, W, projected, var_exp, "Decimal")
fig1 = make_3d_plot(points, mean)
fig2 = make_2d_plot(projected, var_exp)
fig3 = make_var_plot(var_exp)
return output_text, fig1, fig2, fig3
def _style_ax(ax):
ax.set_facecolor('#111827')
for sp in ax.spines.values():
sp.set_color('#1e293b')
ax.tick_params(colors='#94a3b8', labelsize=8)
def make_3d_plot(points, mean):
fig = plt.figure(figsize=(6, 5), facecolor='#0a0e1a')
ax = fig.add_subplot(111, projection='3d')
ax.set_facecolor('#111827')
for pane in [ax.xaxis.pane, ax.yaxis.pane, ax.zaxis.pane]:
pane.fill = False
pane.set_edgecolor('#1e293b')
ax.tick_params(colors='#94a3b8', labelsize=7)
ax.xaxis.label.set_color('#94a3b8')
ax.yaxis.label.set_color('#94a3b8')
ax.zaxis.label.set_color('#94a3b8')
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z')
for i, p in enumerate(points):
c = COLORS[i % len(COLORS)]
ax.scatter(*p, color=c, s=80, edgecolors='white', linewidths=0.5, zorder=5)
ax.text(p[0], p[1], p[2], f' P{i+1}', color=c, fontsize=7.5, fontweight='bold')
ax.scatter(*mean, color='#f59e0b', s=160, marker='*', edgecolors='white', linewidths=0.8, zorder=10)
ax.set_title('Original 3D Points', color='#e2e8f0', fontsize=11, fontweight='bold', pad=10)
fig.tight_layout()
return fig
def make_2d_plot(projected, var_exp):
fig, ax = plt.subplots(figsize=(6, 5), facecolor='#0a0e1a')
_style_ax(ax)
ax.axhline(0, color='#1e293b', lw=1)
ax.axvline(0, color='#1e293b', lw=1)
ax.grid(True, color='#1e293b', lw=0.5, alpha=0.7)
ax.set_xlabel(f'PC1 ({var_exp[0]:.1f}%)', color='#00d4ff', fontsize=10)
ax.set_ylabel(f'PC2 ({var_exp[1]:.1f}%)', color='#7c3aed', fontsize=10)
for i, p in enumerate(projected):
c = COLORS[i % len(COLORS)]
ax.scatter(*p, color=c, s=100, edgecolors='white', linewidths=0.6, zorder=5)
ax.annotate(f'P{i+1}', p, xytext=(7,4), textcoords='offset points',
color=c, fontsize=8.5, fontweight='bold')
ax.set_title('2D Projection (PCA)', color='#e2e8f0', fontsize=11, fontweight='bold', pad=10)
fig.tight_layout()
return fig
def make_var_plot(var_exp):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), facecolor='#0a0e1a')
labels = ['PC1','PC2','PC3']
bar_colors = ['#00d4ff','#7c3aed','#f59e0b']
_style_ax(ax1)
bars = ax1.bar(labels, var_exp, color=bar_colors, edgecolor='#0a0e1a', linewidth=1.5, width=0.5)
for bar, val in zip(bars, var_exp):
ax1.text(bar.get_x()+bar.get_width()/2, bar.get_height()+0.8,
f'{val:.1f}%', ha='center', color='#e2e8f0', fontsize=9, fontweight='bold')
ax1.set_title('Variance per PC', color='#e2e8f0', fontsize=10, fontweight='bold', pad=8)
ax1.set_ylabel('Variance (%)', color='#64748b', fontsize=9)
ax1.set_ylim(0, max(var_exp)*1.2)
_style_ax(ax2)
cum = np.cumsum(var_exp)
ax2.plot(labels, cum, 'o-', color='#10b981', lw=2.5, markersize=8,
markerfacecolor='#0a0e1a', markeredgecolor='#10b981', markeredgewidth=2.5)
ax2.fill_between(labels, cum, alpha=0.12, color='#10b981')
ax2.axhline(100, color='#f43f5e', lw=1, ls='--', alpha=0.5)
for x, y in zip(labels, cum):
ax2.text(x, y+2, f'{y:.1f}%', ha='center', color='#10b981', fontsize=9, fontweight='bold')
ax2.set_title('Cumulative Variance', color='#e2e8f0', fontsize=10, fontweight='bold', pad=8)
ax2.set_ylabel('Cumulative (%)', color='#64748b', fontsize=9)
ax2.set_ylim(0, 115)
ax2.grid(True, color='#1e293b', lw=0.5)
fig.tight_layout(pad=2)
return fig
css = """
* { box-sizing: border-box; }
body, .gradio-container {
background: #0a0e1a !important;
color: #e2e8f0 !important;
font-family: ui-monospace, 'Cascadia Code', 'Segoe UI Mono', monospace !important;
}
.gradio-container { max-width: 1200px !important; margin: 0 auto !important; }
.header {
background: #111827;
border: 1px solid #1e293b;
border-top: 3px solid #00d4ff;
border-radius: 12px;
padding: 28px 36px;
margin-bottom: 20px;
}
.header h1 { font-size: 1.9rem; font-weight: 800; color: #00d4ff; margin: 0 0 6px 0; }
.header p { color: #64748b; font-size: 0.82rem; margin: 0; }
.hint {
background: rgba(0,212,255,0.04);
border: 1px solid rgba(0,212,255,0.15);
border-radius: 8px;
padding: 12px 16px;
font-size: 0.76rem;
color: #64748b;
line-height: 1.8;
margin-top: 10px;
}
.hint strong { color: #00d4ff; }
textarea {
background: #060a12 !important;
border: 1px solid #1e293b !important;
color: #94a3b8 !important;
font-family: ui-monospace, monospace !important;
font-size: 0.8rem !important;
border-radius: 8px !important;
}
textarea:focus { border-color: #00d4ff !important; outline: none !important; }
button { font-weight: 700 !important; border-radius: 8px !important; }
"""
EXAMPLE = """1 2 3
4 5 6
7 8 9
2 4 1
5 1 8
3 6 2
9 3 7
1 8 4"""
with gr.Blocks(title="PCA 3D to 2D") as demo:
gr.HTML("""
<div class="header">
<h1>&#x25A6; PCA Visualizer</h1>
<p>Principal Component Analysis &nbsp;&middot;&nbsp; 3D &#8594; 2D Dimensionality Reduction</p>
</div>""")
with gr.Row():
with gr.Column(scale=1):
points_input = gr.Textbox(
label="3D Points (one per line: X Y Z)",
placeholder="1 2 3\n4 5 6\n...",
lines=10,
value=EXAMPLE
)
output_mode = gr.Radio(
choices=["Decimal only", "Fraction only", "Both (Decimal + Fraction)"],
value="Both (Decimal + Fraction)",
label="Output Format"
)
gr.HTML("""<div class="hint">
<strong>Decimal:</strong> 0.3333<br>
<strong>Fraction:</strong> 1/3<br>
<strong>Both:</strong> shows decimal then fraction section
</div>""")
with gr.Row():
run_btn = gr.Button("Run PCA", variant="primary")
clear_btn = gr.Button("Clear", variant="secondary")
with gr.Column(scale=2):
with gr.Tabs():
with gr.Tab("Steps & Results"):
steps_out = gr.Textbox(
label="Full PCA Computation",
interactive=False,
lines=36
)
with gr.Tab("3D Input Plot"):
plot_3d = gr.Plot(label="Given 3D Points")
with gr.Tab("2D Projection"):
plot_2d = gr.Plot(label="PCA 2D Projection")
with gr.Tab("Variance Analysis"):
plot_var = gr.Plot(label="Explained Variance")
run_btn.click(
fn=run_pca,
inputs=[points_input, output_mode],
outputs=[steps_out, plot_3d, plot_2d, plot_var]
)
clear_btn.click(
fn=lambda: ("", None, None, None),
outputs=[points_input, plot_3d, plot_2d, plot_var]
)
demo.launch(css=css, ssr_mode=False)