Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 1 | import gi |
| 2 | gi.require_version('Gtk', '3.0') |
| 3 | from gi.repository import Gtk |
| 4 | import numpy as np |
Ravago Jones | 56941a5 | 2021-07-31 14:42:38 -0700 | [diff] [blame] | 5 | import queue |
| 6 | import threading |
| 7 | import copy |
Ravago Jones | fa8da56 | 2022-07-02 18:10:22 -0700 | [diff] [blame^] | 8 | from multispline import Multispline |
John Park | 91e6973 | 2019-03-03 13:12:43 -0800 | [diff] [blame] | 9 | from libspline import Spline, DistanceSpline, Trajectory |
| 10 | |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 11 | from matplotlib.backends.backend_gtk3agg import (FigureCanvasGTK3Agg as |
| 12 | FigureCanvas) |
| 13 | from matplotlib.figure import Figure |
John Park | 91e6973 | 2019-03-03 13:12:43 -0800 | [diff] [blame] | 14 | |
Ravago Jones | 8da89c4 | 2022-07-17 19:34:06 -0700 | [diff] [blame] | 15 | |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 16 | class Graph(Gtk.Bin): |
| 17 | def __init__(self): |
| 18 | super(Graph, self).__init__() |
| 19 | fig = Figure(figsize=(5, 4), dpi=100) |
| 20 | self.axis = fig.add_subplot(111) |
Ravago Jones | e958479 | 2022-05-28 16:16:46 -0700 | [diff] [blame] | 21 | self.canvas = FigureCanvas(fig) # a Gtk.DrawingArea |
| 22 | self.canvas.set_vexpand(True) |
| 23 | self.canvas.set_size_request(800, 250) |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 24 | self.callback_id = self.canvas.mpl_connect('motion_notify_event', |
| 25 | self.on_mouse_move) |
Ravago Jones | e958479 | 2022-05-28 16:16:46 -0700 | [diff] [blame] | 26 | self.add(self.canvas) |
Ravago Jones | 56941a5 | 2021-07-31 14:42:38 -0700 | [diff] [blame] | 27 | |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 28 | # The current graph data |
| 29 | self.data = None |
| 30 | # The size of a timestep |
| 31 | self.dt = 0.00505 |
| 32 | # The position of the cursor in seconds |
| 33 | self.cursor = 0 |
| 34 | |
| 35 | # Reference to the parent gtk Widget that wants to get redrawn |
| 36 | # when user moves the cursor |
| 37 | self.cursor_watcher = None |
| 38 | self.cursor_line = None |
| 39 | |
| 40 | self.queue = queue.Queue(maxsize=1) |
Ravago Jones | 56941a5 | 2021-07-31 14:42:38 -0700 | [diff] [blame] | 41 | thread = threading.Thread(target=self.worker) |
| 42 | thread.daemon = True |
| 43 | thread.start() |
| 44 | |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 45 | def find_cursor(self): |
| 46 | """Gets the cursor position as a distance along the spline""" |
| 47 | if self.data is None: |
| 48 | return None |
| 49 | cursor_index = int(self.cursor / self.dt) |
| 50 | # use the time to index into the position data |
| 51 | distance_at_cursor = self.data[0][cursor_index - 1] |
| 52 | return distance_at_cursor |
| 53 | |
| 54 | def place_cursor(self, distance): |
| 55 | """Places the cursor at a certain distance along the spline""" |
| 56 | if self.data is None: |
| 57 | return |
| 58 | # convert distance along spline to time along trajectory |
| 59 | index = np.searchsorted(self.data[0], distance, side='left') |
| 60 | time = index * self.dt |
| 61 | self.cursor = time |
| 62 | self.redraw_cursor() |
| 63 | |
| 64 | def on_mouse_move(self, event): |
| 65 | """Updates the cursor and all the canvases that watch it on mouse move""" |
| 66 | if self.data is None: |
| 67 | return |
| 68 | total_steps_taken = self.data.shape[1] |
| 69 | total_time = self.dt * total_steps_taken |
| 70 | if event.xdata is not None: |
| 71 | # clip the position if still on the canvas, but off the graph |
| 72 | self.cursor = np.clip(event.xdata, 0, total_time) |
| 73 | |
| 74 | self.redraw_cursor() |
| 75 | |
| 76 | # tell the field to update too |
| 77 | if self.cursor_watcher is not None: |
| 78 | self.cursor_watcher.queue_draw() |
| 79 | |
| 80 | def redraw_cursor(self): |
| 81 | """Redraws the cursor line""" |
| 82 | # TODO: This redraws the entire graph and isn't very snappy |
| 83 | if self.cursor_line: self.cursor_line.remove() |
| 84 | self.cursor_line = self.axis.axvline(self.cursor) |
| 85 | self.canvas.draw_idle() |
| 86 | |
Ravago Jones | 56941a5 | 2021-07-31 14:42:38 -0700 | [diff] [blame] | 87 | def schedule_recalculate(self, points): |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 88 | """Submits points to be graphed |
| 89 | |
| 90 | Can be superseded by newer points if an old one isn't finished processing. |
| 91 | """ |
Ravago Jones | fa8da56 | 2022-07-02 18:10:22 -0700 | [diff] [blame^] | 92 | |
| 93 | # TODO: Draw all multisplines |
| 94 | points = points[0] |
| 95 | |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 96 | if not points.getLibsplines(): return |
Ravago Jones | 56941a5 | 2021-07-31 14:42:38 -0700 | [diff] [blame] | 97 | new_copy = copy.deepcopy(points) |
| 98 | |
| 99 | # empty the queue |
| 100 | try: |
| 101 | self.queue.get_nowait() |
| 102 | except queue.Empty: |
Ravago Jones | 8da89c4 | 2022-07-17 19:34:06 -0700 | [diff] [blame] | 103 | pass # was already empty |
Ravago Jones | 56941a5 | 2021-07-31 14:42:38 -0700 | [diff] [blame] | 104 | |
| 105 | # replace with new request |
| 106 | self.queue.put_nowait(new_copy) |
| 107 | |
| 108 | def worker(self): |
| 109 | while True: |
| 110 | self.recalculate_graph(self.queue.get()) |
John Park | 91e6973 | 2019-03-03 13:12:43 -0800 | [diff] [blame] | 111 | |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 112 | def recalculate_graph(self, points): |
| 113 | if not points.getLibsplines(): return |
John Park | 91e6973 | 2019-03-03 13:12:43 -0800 | [diff] [blame] | 114 | |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 115 | # call C++ wrappers to calculate the trajectory |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 116 | distance_spline = DistanceSpline(points.getLibsplines()) |
| 117 | traj = Trajectory(distance_spline) |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 118 | points.addConstraintsToTrajectory(traj) |
| 119 | traj.Plan() |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 120 | self.data = traj.GetPlanXVA(self.dt) |
| 121 | if self.data is None: return |
John Park | 91e6973 | 2019-03-03 13:12:43 -0800 | [diff] [blame] | 122 | |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 123 | # extract values to be graphed |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 124 | total_steps_taken = self.data.shape[1] |
| 125 | total_time = self.dt * total_steps_taken |
| 126 | times = np.linspace(0, total_time, num=total_steps_taken) |
| 127 | position, velocity, acceleration = self.data |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 128 | left_voltage, right_voltage = zip(*(traj.Voltage(x) for x in position)) |
John Park | 91e6973 | 2019-03-03 13:12:43 -0800 | [diff] [blame] | 129 | |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 130 | # update graph |
| 131 | self.axis.clear() |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 132 | self.axis.plot(times, velocity) |
| 133 | self.axis.plot(times, acceleration) |
| 134 | self.axis.plot(times, left_voltage) |
| 135 | self.axis.plot(times, right_voltage) |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 136 | self.axis.legend( |
| 137 | ["Velocity", "Acceleration", "Left Voltage", "Right Voltage"]) |
| 138 | self.axis.xaxis.set_label_text("Time (sec)") |
John Park | 91e6973 | 2019-03-03 13:12:43 -0800 | [diff] [blame] | 139 | |
Ravago Jones | 6d460fe | 2021-07-03 16:59:55 -0700 | [diff] [blame] | 140 | # renumber the x-axis to include the last point, |
| 141 | # the total time to drive the spline |
| 142 | self.axis.xaxis.set_ticks(np.linspace(0, total_time, num=8)) |
Ravago Jones | 128fb99 | 2021-07-31 13:56:58 -0700 | [diff] [blame] | 143 | |
Ravago Jones | e958479 | 2022-05-28 16:16:46 -0700 | [diff] [blame] | 144 | # redraw |
Ravago Jones | 0a1d409 | 2022-06-03 12:47:32 -0700 | [diff] [blame] | 145 | self.canvas.draw_idle() |