StackGPArt / app.py
guptaru1's picture
Upload app.py
b99b73b verified
Raw
History Blame Contribute Delete
9.17 kB
import gradio as gr
import matplotlib.pyplot as plt
from matplotlib.colors import hsv_to_rgb
import numpy as np
import time
import inspect
# Import from your V2 script (make sure these functions are not hidden inside 'if __name__ == "__main__":')
from gp_art_v2 import evolve_two_trees, render_samila, make_point_grid, safe_eval, norm01, evolve_two_trees_stream
def decode_raw_stack(model):
"""Translates the StackGP array into readable algebra."""
if not model or len(model) < 2: return "Empty"
ops = model[0]
vars_stack = model[1]
stack1 = vars_stack[:]
stack3 = []
probe = ["x", "y", "r", "theta", "theta*r", "sin(theta)*r"]
def resolve_var(v):
if callable(v):
try: return str(v(probe))
except:
try: return str(round(float(v()), 3))
except: return "c"
else:
return str(round(float(v), 3))
for op in ops:
if op == "pop":
if len(stack1) > 0:
stack3.insert(0, resolve_var(stack1[0]))
stack1 = stack1[1:]
else:
stack3.insert(0, "c")
elif hasattr(op, '__name__'):
try: patt = len(inspect.signature(op).parameters)
except ValueError: patt = 1
while patt > len(stack3):
if len(stack1) > 0:
stack3.insert(0, resolve_var(stack1[0]))
stack1 = stack1[1:]
else:
stack3.insert(0, "c")
args = stack3[:patt]
stack3 = stack3[patt:]
args.reverse()
op_name = op.__name__
if op_name == "add": op_str = f"({args[0]} + {args[1]})"
elif op_name == "sub": op_str = f"({args[0]} - {args[1]})"
elif op_name == "mult": op_str = f"({args[0]} * {args[1]})"
elif op_name in ["div_safe", "protectDiv"]: op_str = f"({args[0]} / {args[1]})"
elif op_name == "sqrd": op_str = f"({args[0]}^2)"
elif patt == 1: op_str = f"{op_name}({args[0]})"
elif patt == 2: op_str = f"{op_name}({args[0]}, {args[1]})"
else: op_str = f"{op_name}(...)"
stack3.insert(0, op_str)
return stack3[0] if len(stack3) > 0 else "Empty"
# --- NEW: The Classic Pixel Grid Renderer (from gp_art.py) ---
def render_pixel_grid(model_f1, model_f2, mode, resolution, figsize=(8, 8)):
"""Evaluates the equations directly into a static pixel grid."""
grid = make_point_grid(resolution)
f1 = safe_eval(model_f1, grid['input'])
f1n = norm01(f1).reshape(resolution, resolution)
fig, ax = plt.subplots(figsize=figsize, facecolor='#050505')
ax.axis('off')
if mode == "PIXEL_MONO":
# Matches your gp_art.py monochrome olive/green style
hue = 0.17
sat = 0.45
hsv = np.stack([
np.full_like(f1n, hue),
np.full_like(f1n, sat),
f1n # F1 equation drives the brightness/value
], axis=-1)
rgb = hsv_to_rgb(hsv)
ax.imshow(rgb, interpolation='bilinear')
elif mode == "PIXEL_COLOR":
# Full HSV evolution: F1 drives Hue, F2 drives Saturation
f2 = safe_eval(model_f2, grid['input'])
f2n = norm01(f2).reshape(resolution, resolution)
hsv = np.stack([
f1n, # Hue
np.clip(f2n + 0.2, 0, 1), # Saturation
np.clip(f1n + 0.5, 0, 1) # Value (Brightness)
], axis=-1)
rgb = hsv_to_rgb(hsv)
ax.imshow(rgb, interpolation='bilinear')
plt.tight_layout(pad=0)
return fig, ax
def live_evolution_stream(pop_size, total_gens, mode, cmap):
yield None, "Initializing Population...", "Starting evolutionary engine..."
current_gen = 0
# Loop over your TRUE intermediate equations as they evolve!
for model_f1, model_f2 in evolve_two_trees_stream(resolution=80, generations=total_gens, pop_size=pop_size):
current_gen += 1
# Decode the actual equations for THIS specific generation
eq1 = decode_raw_stack(model_f1)
eq2 = decode_raw_stack(model_f2)
# Only render to the UI every 5 generations to keep the browser from crashing
if current_gen % 5 == 0 or current_gen == total_gens:
if mode.startswith("PIXEL"):
fig, ax = render_pixel_grid(model_f1, model_f2, mode, resolution=150)
else:
fig, ax = render_samila(
model_f1, model_f2,
mode=mode,
resolution=150,
cmap=cmap,
point_size=1.5,
alpha=0.85,
color_by='f1',
bgcolor='#050505',
figsize=(8, 8)
)
stats = f"**Generation:** {current_gen} / {total_gens} | **Status:** Actively Evolving..."
eq_text = f"**F1 (Equation 1):**\n`{eq1}`\n\n**F2 (Equation 2):**\n`{eq2}`"
# Stream the actual intermediate math and image to the screen
yield fig, stats, eq_text
# Final completion state
final_stats = f"**Generation:** {total_gens} (Complete) | **F1 Complexity:** {model_f1[2][1]} | **F2 Complexity:** {model_f2[2][1]}"
yield fig, final_stats, eq_text
# --- The Generator Function ---
# def live_evolution_stream(pop_size, total_gens, mode, cmap):
# yield None, "Initializing Population...", "Starting evolutionary engine..."
# # Trigger StackGP backend
# model_f1, model_f2 = evolve_two_trees(
# resolution=64,
# generations=total_gens,
# pop_size=pop_size
# )
# eq1 = decode_raw_stack(model_f1)
# eq2 = decode_raw_stack(model_f2)
# # Stream the rendering to visualize emergence
# steps = 5
# for step in range(1, steps + 1):
# current_gen = int((total_gens / steps) * step)
# # Branch based on Vector Displacement vs Pixel Grid
# if mode.startswith("PIXEL"):
# current_res = int(128 + (64 * step)) # Pixel grids need higher res
# fig, ax = render_pixel_grid(model_f1, model_f2, mode, current_res)
# else:
# current_res = int(80 + (40 * step))
# fig, ax = render_samila(
# model_f1, model_f2,
# mode=mode,
# resolution=current_res,
# cmap=cmap,
# point_size=1.5,
# alpha=0.4 + (0.1 * step),
# color_by='f1',
# bgcolor='#050505',
# figsize=(8, 8)
# )
# stats = f"**Generation:** {current_gen} / {total_gens} | **Status:** Evolving Geometry..."
# eq_text = f"**F1 (Equation 1):**\n`{eq1}`\n\n**F2 (Equation 2):**\n`{eq2}`"
# yield fig, stats, eq_text
# time.sleep(0.3)
# final_stats = f"**Generation:** {total_gens} (Complete) | **F1 Complexity:** {model_f1[2][1]} | **F2 Complexity:** {model_f2[2][1]}"
# yield fig, final_stats, eq_text
# =============================================================================
# GRADIO UI ARCHITECTURE
# =============================================================================
with gr.Blocks(theme=gr.themes.Monochrome(text_size="lg")) as demo:
gr.Markdown("# StackGPArt: Live Evolution Engine")
gr.Markdown("*Evolving generative structures using pure Stack-based Genetic Programming.*")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 1. Evolution Parameters")
pop_slider = gr.Slider(minimum=20, maximum=300, value=100, step=10, label="Population Size")
gen_slider = gr.Slider(minimum=10, maximum=300, value=80, step=10, label="Target Generations")
# The Unified Dropdown!
mode_dropdown = gr.Dropdown(
choices=[
"F1_VS_X1", "F1_VS_F2", "POLAR", "X2_VS_F2", # Vector Modes (gp_art_v2)
"PIXEL_MONO", "PIXEL_COLOR" # Pixel Modes (gp_art)
],
value="F1_VS_X1",
label="Rendering Paradigm"
)
cmap_dropdown = gr.Dropdown(
choices=["turbo", "plasma", "magma", "viridis"],
value="turbo",
label="Colormap (Ignored for Pixel Modes)"
)
evolve_btn = gr.Button("🧬 Start Evolution", variant="primary")
gr.Markdown("### 2. Live Telemetry")
stats_display = gr.Markdown("**Generation:** 0 | **Status:** Idle")
eq_display = gr.Markdown("**F1:** —\n\n**F2:** —")
with gr.Column(scale=2):
art_output = gr.Plot(label="Emergent Geometry")
evolve_btn.click(
fn=live_evolution_stream,
inputs=[pop_slider, gen_slider, mode_dropdown, cmap_dropdown],
outputs=[art_output, stats_display, eq_display]
)
if __name__ == "__main__":
demo.queue().launch()