Formula Student Autonomous Systems
The code for the main driverless system
Loading...
Searching...
No Matches
scenario_generator.py
Go to the documentation of this file.
1import matplotlib.pyplot as plt
2import os
3import math
4import numpy as np
5
7 """
8 A class to interactively track and mark points on a matplotlib plot.
9
10 The tool supports:
11 - Marking yellow and blue cones (track positions).
12 - Setting an initial position.
13 - Defining a target for orientation.
14 - Selecting two points for a final rectangular box.
15
16 Data is saved to a file when the user finishes.
17 """
18
19 def __init__(self):
20 # Initialize figure and axes.
21 self.fig, self.ax = plt.subplots(figsize=(10, 6))
22
23 # Cone point lists and current color.
24 self.x_points = []
25 self.y_points = []
26 self.colors = []
27 self.current_color = None
28 self.scatter = None
29
30 # Initial position and orientation variables.
31 self.init_pos = None
32 self.temp_target = None
33 self.init_scatter = None
34 self.target_scatter = None
35 self.orientation_line = None
36 self.theta = None
37
38 # Final box variables.
40 self.final_box_plot = None
41
42 # Disable default key handlers.
43 self.fig.canvas.mpl_disconnect(self.fig.canvas.manager.key_press_handler_id)
44
45 # Configure plot and connect events.
46 self.configure_plot()
47 self.setup_events()
48 self.mode = 'none'
49 self.fig.canvas.draw()
50
51 def configure_plot(self):
52 """Configure the matplotlib plot with title, labels, gridlines, and limits."""
53 title = ('Track Setup Tool\n'
54 'Y: Yellow Cones, B: Blue Cones\n'
55 'P: Initial Position, T: Target for Orientation\n'
56 'F: Final Box Points\n'
57 'Right-click to Finish')
58 self.ax.set_title(title)
59 self.ax.set_xlabel('X Coordinate')
60 self.ax.set_ylabel('Y Coordinate')
61 self.ax.grid(True)
62 self.ax.minorticks_on()
63 self.ax.grid(which='minor', linestyle=':', linewidth=0.5)
64 self.ax.grid(which='major', linestyle='-', linewidth=1)
65 self.ax.set_xlim(-5, 40)
66 self.ax.set_ylim(-5, 40)
67 self.ax.set_autoscale_on(False)
68
69 def setup_events(self):
70 """Connect mouse and key events to their handlers."""
71 self.fig.canvas.mpl_connect('button_press_event', self.on_clickon_click)
72 self.fig.canvas.mpl_connect('key_press_event', self.on_keyon_key)
73
74 def on_key(self, event):
75 """Delegate key press actions to helper methods."""
76 key = event.key.lower()
77 if key == 'y':
78 self.set_cone_mode('yellow')
79 elif key == 'b':
80 self.set_cone_mode('blue')
81 elif key == 'p':
83 elif key == 't' and self.init_pos:
84 self.set_target_mode()
85 elif key == 'f':
86 self.start_final_box()
87 elif event.key == 'ctrl+z':
88 self.undo_last_cone()
89 self.fig.canvas.draw()
90
91 def set_cone_mode(self, color):
92 """Set mode for adding cones of a given color."""
93 self.mode = color
94 self.current_color = f"{color}_cone"
95 title_color = "Yellow" if color == 'yellow' else "Blue"
96 self.ax.set_title(f"Adding {title_color} Cones\nLeft-click to add, Right-click to finish\nCtrl+Z to undo")
97
99 """Reset the initial position and clear any orientation markers."""
100 self.mode = 'init_pos'
101 self.remove_marker('init_scatter')
102 self.remove_marker('orientation_line')
103 self.init_pos = None
104 self.temp_target = None
105 self.theta = None
106 self.ax.set_title('Select Initial Position')
107
109 """Prepare to select a target point for orientation."""
110 self.mode = 'target'
111 self.remove_marker('target_scatter')
112 self.remove_marker('orientation_line')
113 self.temp_target = None
114 self.ax.set_title('Select Target Point for Orientation')
115
117 """Initialize final box mode and clear any previous box."""
118 self.mode = 'final_box'
119 self.final_box_points = []
120 if self.final_box_plot:
121 self.final_box_plot.remove()
122 self.final_box_plot = None
123 self.ax.set_title('Select First Point of Final Box')
124
125 def undo_last_cone(self):
126 """Remove the last added cone point and update the display."""
127 if self.x_points:
128 self.x_points.pop()
129 self.y_points.pop()
130 self.colors.pop()
131 if self.scatter:
132 self.scatter.remove()
133 self.scatter = None
134 if len(self.x_points) > 0:
135 display_colors = ['yellow' if c == 'yellow_cone' else 'blue' for c in self.colors]
136 self.scatter = self.ax.scatter(
137 self.x_points,
138 self.y_points,
139 c=display_colors,
140 edgecolors='black',
141 linewidth=1
142 )
143
144 def remove_marker(self, marker_name):
145 """Remove a marker attribute if it exists."""
146 marker = getattr(self, marker_name, None)
147 if marker:
148 marker.remove()
149 setattr(self, marker_name, None)
150
151 def on_click(self, event):
152 """Route mouse click events based on button and current mode."""
153 if event.inaxes != self.ax:
154 return
155 if event.button == 1:
156 self.handle_left_click(event)
157 elif event.button == 3:
158 self.handle_right_click()
159 self.fig.canvas.draw()
160
161 def handle_left_click(self, event):
162 """Process a left-click event depending on the current mode."""
163 if self.mode in ['yellow', 'blue']:
164 self.add_cone(event)
165 elif self.mode == 'init_pos':
166 self.set_initial_position(event)
167 elif self.mode == 'target' and self.init_pos:
168 self.set_target(event)
169 elif self.mode == 'final_box':
170 self.add_final_box_point(event)
171
173 """On right-click, save data (if any) and close the plot."""
174 if self.x_points or self.init_pos or self.final_box_points:
175 self.save_points()
176 plt.close(self.fig)
177
178 def add_cone(self, event):
179 """Add a cone point and update the scatter plot."""
180 self.x_points.append(event.xdata)
181 self.y_points.append(event.ydata)
182 self.colors.append(self.current_color)
183 if self.scatter:
184 self.scatter.remove()
185 display_colors = ['yellow' if c == 'yellow_cone' else 'blue' for c in self.colors]
186 self.scatter = self.ax.scatter(
187 self.x_points,
188 self.y_points,
189 c=display_colors,
190 edgecolors='black',
191 linewidth=1
192 )
193
194 def set_initial_position(self, event):
195 """Set the initial position and mark it on the plot."""
196 self.init_pos = (event.xdata, event.ydata)
197 if self.init_scatter:
198 self.init_scatter.remove()
199 self.init_scatter = self.ax.scatter(
200 [self.init_pos[0]],
201 [self.init_pos[1]],
202 c='green',
203 marker='*',
204 s=200,
205 label='Initial Position'
206 )
207 self.ax.set_title('Press F to set orientation')
208
209 def set_target(self, event):
210 """Set the target point, compute orientation, and update markers."""
211 self.temp_target = (event.xdata, event.ydata)
212 self.theta = self.calculate_theta(self.init_pos, self.temp_target)
213 if self.target_scatter:
214 self.target_scatter.remove()
215 if self.orientation_line:
216 self.orientation_line.remove()
217 self.target_scatter = self.ax.scatter(
218 [self.temp_target[0]],
219 [self.temp_target[1]],
220 c='red',
221 marker='x',
222 s=100
223 )
224 self.orientation_line = self.ax.plot(
225 [self.init_pos[0], self.temp_target[0]],
226 [self.init_pos[1], self.temp_target[1]],
227 'r--'
228 )[0]
229
230 def add_final_box_point(self, event):
231 """Collect final box points and draw the rectangle if two points are set."""
232 self.final_box_points.append((event.xdata, event.ydata))
233 if len(self.final_box_points) == 2:
234 self.draw_final_box()
235
236 def draw_final_box(self):
237 """Draw the rectangle representing the final box."""
238 if self.final_box_plot:
239 self.final_box_plot.remove()
240 x1 = min(self.final_box_points[0][0], self.final_box_points[1][0])
241 x2 = max(self.final_box_points[0][0], self.final_box_points[1][0])
242 y1 = min(self.final_box_points[0][1], self.final_box_points[1][1])
243 y2 = max(self.final_box_points[0][1], self.final_box_points[1][1])
244 self.final_box_plot = plt.Rectangle(
245 (x1, y1),
246 x2 - x1,
247 y2 - y1,
248 fill=False,
249 color='purple',
250 linewidth=2
251 )
252 self.ax.add_patch(self.final_box_plot)
253 self.mode = 'none'
254
255 def calculate_theta(self, p1, p2):
256 """
257 Calculate the angle (in radians) between two points relative to the horizontal axis.
258 """
259 dx = p2[0] - p1[0]
260 dy = p2[1] - p1[1]
261 return math.atan2(dy, dx)
262
263 def save_points(self):
264 """
265 Save the configuration (initial position, orientation, final box, and cones)
266 to a text file.
267 """
268 filename = 'cones.txt'
269 filepath = os.path.join("/home/ws/src/planning/test/integration_tests/tests/", filename)
270 with open(filepath, 'w') as f:
271 if self.init_pos and self.theta is not None:
272 f.write(f'P {self.init_pos[0]:.1f} {self.init_pos[1]:.1f} {self.theta:.6f}\n')
273 if len(self.final_box_points) == 2:
274 x1 = min(self.final_box_points[0][0], self.final_box_points[1][0])
275 x2 = max(self.final_box_points[0][0], self.final_box_points[1][0])
276 y1 = min(self.final_box_points[0][1], self.final_box_points[1][1])
277 y2 = max(self.final_box_points[0][1], self.final_box_points[1][1])
278 f.write(f'F {x1:.1f} {x2:.1f} {y1:.1f} {y2:.1f}\n')
279 for x, y, color in zip(self.x_points, self.y_points, self.colors):
280 f.write(f'C {x:.1f} {y:.1f} {color}\n')
281 print(f"Data saved to {filepath}")
282
283def main():
284 """
285 Main function to display instructions and run the Track Setup Tool.
286 """
287 print("Track Setup Tool")
288 print("1. Press 'Y' for yellow cones")
289 print("2. Press 'B' for blue cones")
290 print("3. Press 'P' to set initial position")
291 print("4. Press 'T' to set orientation target")
292 print("5. Press 'F' to set final box (2 points)")
293 print("6. Left-click to add points")
294 print("7. Right-click to finish and save")
295
296 _ = PointTracker()
297 plt.show()
298
299if __name__ == '__main__':
300 main()
Definition main.py:1