Spaces:
Sleeping
Sleeping
| 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() |