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