kaitongg commited on
Commit
21ee15d
·
verified ·
1 Parent(s): 39ff40e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +247 -0
app.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import random
5
+
6
+ # --- Configure Matplotlib ---
7
+ plt.style.use('ggplot')
8
+ plt.rcParams['figure.figsize'] = [10, 10]
9
+
10
+ # --- Core Data Structure: Circle Class ---
11
+ class Circle:
12
+ """Represents a circle in the drawing and its state"""
13
+ def __init__(self, x, y, radius, color):
14
+ self.x = x
15
+ self.y = y
16
+ self.radius = radius
17
+ self.color = color
18
+
19
+ def __repr__(self):
20
+ return f"Circle(Center:({self.x:.2f}, {self.y:.2f}), R:{self.radius}, Color:{self.color})"
21
+
22
+ # --- Shape Grammar Core Function ---
23
+ def run_shape_grammar(N_iterations):
24
+ """
25
+ Runs the shape grammar iterative process N times and yields the state after each iteration.
26
+ """
27
+ # 1. Initial Condition (Start)
28
+ circles = [Circle(0, 0, 2, 'blue')]
29
+ lines = [] # Stores all line segments for plotting
30
+
31
+ current_iteration = 0
32
+ print(f"--- Starting Shape Grammar: Target Iterations N = {N_iterations} ---")
33
+
34
+ # Yield initial state before any iterations
35
+ yield circles, lines
36
+
37
+ while current_iteration < N_iterations:
38
+ # Filter out non-red circles
39
+ active_circles = [c for c in circles if c.color != 'red']
40
+
41
+ # 2. Stop Condition Check
42
+ if not active_circles:
43
+ print(f"\n✅ Stop: All circles have turned red. Total iterations: {current_iteration}")
44
+ break
45
+
46
+ # 3. Randomly select an original circle (Pick a random circle)
47
+ C_original = random.choice(active_circles)
48
+
49
+ # 4. Determine Direction and Line Segment (Direction and Line)
50
+
51
+ # Random angle (multiple of 45°)
52
+ angle_deg = random.choice([0, 45, 90, 135, 180, 225, 270, 315])
53
+ angle_rad = np.deg2rad(angle_deg)
54
+
55
+ # Random line length
56
+ L_length = random.choice([6, 8, 10])
57
+
58
+ # Randomly choose starting point (center or tangent point)
59
+ start_from_center = random.choice([True, False])
60
+
61
+ if start_from_center:
62
+ # Option A: Start from the center
63
+ P_start_x, P_start_y = C_original.x, C_original.y
64
+ else:
65
+ # Option B: Start from a tangent point on the circumference
66
+ # The tangent point is the center translated by R in the opposite direction of 'angle_rad'
67
+ P_start_x = C_original.x + C_original.radius * np.cos(angle_rad + np.pi)
68
+ P_start_y = C_original.y + C_original.radius * np.sin(angle_rad + np.pi)
69
+
70
+ # Calculate the end point of the line segment
71
+ P_end_x = P_start_x + L_length * np.cos(angle_rad)
72
+ P_end_y = P_start_y + L_length * np.sin(angle_rad)
73
+
74
+ # Store the line segment
75
+ lines.append(((P_start_x, P_start_y), (P_end_x, P_end_y)))
76
+
77
+ # 5. Create New Circle (New Circle)
78
+
79
+ # Random radius and color
80
+ R_new = random.choice([1, 2, 3, 4])
81
+ # Color selection: Kept uniformly random for faster generation
82
+ Color_new = random.choice(['red', 'green', 'blue'])
83
+
84
+ # Random placement position
85
+ placement_option = random.choice(['tangential_left', 'tangential_right', 'centered'])
86
+
87
+ if placement_option == 'centered':
88
+ # Option C: Centered at the end point of the line segment
89
+ C_new_x, C_new_y = P_end_x, P_end_y
90
+ else:
91
+ # Option A/B: Tangential to the line segment
92
+ # Normal direction: Angle plus/minus 90 degrees
93
+ # Tangential to the line (L) left or right
94
+ normal_angle = angle_rad + (np.pi/2 if placement_option == 'tangential_left' else -np.pi/2)
95
+
96
+ # The center of the new circle is located at the end point P_end, moved by R_new distance along the normal direction
97
+ C_new_x = P_end_x + R_new * np.cos(normal_angle)
98
+ C_new_y = P_new_y + R_new * np.sin(normal_angle) # Changed C_new_y to use P_new_y instead of P_end_y
99
+
100
+ C_new = Circle(C_new_x, C_new_y, R_new, Color_new)
101
+ circles.append(C_new)
102
+
103
+ # 6. Color Updates (Color Updates)
104
+ if C_original.color == 'blue':
105
+ C_original.color = 'green'
106
+ elif C_original.color == 'green':
107
+ C_original.color = 'red'
108
+ # If it's already red, it remains red (although it should be skipped in the selection phase)
109
+
110
+ current_iteration += 1
111
+ print(f"Iteration {current_iteration}/{N_iterations}: Original circle turned {C_original.color}, New circle {C_new.color}, Total circles: {len(circles)}")
112
+
113
+ # Yield the current state
114
+ yield circles, lines
115
+
116
+
117
+ if current_iteration == N_iterations:
118
+ print(f"\n✅ Stop: Maximum number of iterations N = {N_iterations} reached.")
119
+
120
+
121
+ # --- Plotting Function ---
122
+ def plot_grammar_result(circles, lines):
123
+ """
124
+ Plots the generated shape using Matplotlib and returns the figure.
125
+ """
126
+ fig, ax = plt.subplots()
127
+
128
+ # Plot all circles
129
+ for c in circles:
130
+ # Plot using matplotlib.patches.Circle
131
+ circle_patch = plt.Circle((c.x, c.y), c.radius,
132
+ color=c.color,
133
+ alpha=0.6, # Transparency
134
+ fill=True,
135
+ linewidth=1,
136
+ edgecolor='black')
137
+ ax.add_patch(circle_patch)
138
+
139
+ # Plot all line segments
140
+ for (start, end) in lines:
141
+ ax.plot([start[0], end[0]], [start[1], end[1]],
142
+ color='gray',
143
+ linestyle='-',
144
+ linewidth=0.5,
145
+ zorder=-1) # Place below circles
146
+
147
+ # Set axes and limits
148
+ if circles:
149
+ all_x = [c.x for c in circles]
150
+ all_y = [c.y for c in circles]
151
+ # Automatically calculate boundaries to ensure all circles are visible
152
+ x_min, x_max = min(all_x), max(all_x)
153
+ y_min, y_max = min(all_y), max(all_y)
154
+
155
+ padding = 10 # Additional padding
156
+ ax.set_xlim(x_min - padding, x_max + padding)
157
+ ax.set_ylim(y_min - padding, y_max + padding)
158
+
159
+ ax.set_aspect('equal', adjustable='box') # Maintain equal aspect ratio
160
+ ax.set_title(f"Shape Grammar Generation Result (Total {len(circles)} circles)")
161
+ ax.set_xlabel("X Coordinate")
162
+ ax.set_ylabel("Y Coordinate")
163
+
164
+ # Don't call plt.show() here, return the figure object
165
+ return fig
166
+
167
+ # Store the generated plots
168
+ generated_plots = []
169
+
170
+ def run_shape_grammar_and_generate_plots(num_iterations):
171
+ """
172
+ Runs the shape grammar and generates plots for each iteration.
173
+ Returns a list of Matplotlib figure objects.
174
+ """
175
+ global generated_plots
176
+ generated_plots = [] # Clear previous plots
177
+
178
+ # Ensure the input is a positive integer
179
+ if num_iterations is None or num_iterations <= 0:
180
+ num_iterations = 50 # Default to 50 if invalid
181
+
182
+ # Run the generation process and iterate through yielded results
183
+ for i, (circles, lines) in enumerate(run_shape_grammar(int(num_iterations))):
184
+ # Plot the result for the current step and get the figure object
185
+ fig = plot_grammar_result(circles, lines)
186
+ generated_plots.append(fig)
187
+ # Close the figure to free up memory
188
+ plt.close(fig)
189
+
190
+ return generated_plots
191
+
192
+ def display_iteration(iteration_index):
193
+ """
194
+ Displays the plot for a specific iteration based on the slider value.
195
+ """
196
+ global generated_plots
197
+ if generated_plots and 0 <= iteration_index < len(generated_plots):
198
+ return generated_plots[int(iteration_index)] # Ensure index is integer
199
+ else:
200
+ # Return an empty plot or a placeholder if no plots are available or index is out of range
201
+ fig, ax = plt.subplots()
202
+ ax.text(0.5, 0.5, "No plot available", horizontalalignment='center', verticalalignment='center')
203
+ ax.set_title("Error")
204
+ return fig
205
+
206
+
207
+ # Create the Gradio interface
208
+ with gr.Blocks() as demo:
209
+ gr.Markdown("## Shape Grammar Generator with Iteration Slider")
210
+ gr.Markdown("Generate abstract shapes using a simple shape grammar and explore each step of the process.")
211
+
212
+ with gr.Row():
213
+ num_iterations_input = gr.Number(label="Number of Iterations", value=50, precision=0)
214
+ generate_button = gr.Button("Generate")
215
+
216
+ # Output area for the plots
217
+ plot_output = gr.Plot()
218
+
219
+ # Slider to control the displayed iteration
220
+ iteration_slider = gr.Slider(minimum=0, maximum=0, step=1, label="Iteration")
221
+
222
+ # Link the generate button to the function that runs the grammar and generates plots
223
+ # Update the slider's maximum value after generation
224
+ generate_button.click(
225
+ fn=run_shape_grammar_and_generate_plots,
226
+ inputs=num_iterations_input,
227
+ outputs=None # We don't directly output here, we just generate and store plots
228
+ ).then(
229
+ fn=lambda: gr.update(maximum=len(generated_plots)-1, value=0), # Update slider max and reset to 0
230
+ inputs=None, # No direct input needed, accessing global variable
231
+ outputs=iteration_slider
232
+ ).then(
233
+ fn=lambda: generated_plots[0] if generated_plots else None, # Display the first plot initially
234
+ inputs=None, # No direct input needed, accessing global variable
235
+ outputs=plot_output
236
+ )
237
+
238
+ # Link the slider to the function that displays the selected iteration's plot
239
+ iteration_slider.change(
240
+ fn=display_iteration,
241
+ inputs=iteration_slider,
242
+ outputs=plot_output
243
+ )
244
+
245
+ # Launch the interface for Hugging Face Spaces
246
+ if __name__ == "__main__":
247
+ demo.launch()