blob: b85b996e88c672a4a80b7b50da8ad2c41df1083e [file] [log] [blame]
Ravago Jones6d460fe2021-07-03 16:59:55 -07001import gi
2gi.require_version('Gtk', '3.0')
3from gi.repository import Gtk
4import numpy as np
Ravago Jones56941a52021-07-31 14:42:38 -07005import queue
6import threading
7import copy
Ravago Jonesfa8da562022-07-02 18:10:22 -07008from multispline import Multispline
John Park91e69732019-03-03 13:12:43 -08009from libspline import Spline, DistanceSpline, Trajectory
10
Ravago Jones6d460fe2021-07-03 16:59:55 -070011from matplotlib.backends.backend_gtk3agg import (FigureCanvasGTK3Agg as
12 FigureCanvas)
13from matplotlib.figure import Figure
John Park91e69732019-03-03 13:12:43 -080014
Ravago Jones8da89c42022-07-17 19:34:06 -070015
Ravago Jones6d460fe2021-07-03 16:59:55 -070016class 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 Jonese9584792022-05-28 16:16:46 -070021 self.canvas = FigureCanvas(fig) # a Gtk.DrawingArea
22 self.canvas.set_vexpand(True)
23 self.canvas.set_size_request(800, 250)
Ravago Jones0a1d4092022-06-03 12:47:32 -070024 self.callback_id = self.canvas.mpl_connect('motion_notify_event',
25 self.on_mouse_move)
Ravago Jonese9584792022-05-28 16:16:46 -070026 self.add(self.canvas)
Ravago Jones56941a52021-07-31 14:42:38 -070027
Ravago Jones0a1d4092022-06-03 12:47:32 -070028 # 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 Jones56941a52021-07-31 14:42:38 -070041 thread = threading.Thread(target=self.worker)
42 thread.daemon = True
43 thread.start()
44
Ravago Jones0a1d4092022-06-03 12:47:32 -070045 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)
Ravago Jonesac952dd2022-07-29 21:44:12 -070050 if cursor_index > self.data.size:
51 return None
Ravago Jones0a1d4092022-06-03 12:47:32 -070052 # use the time to index into the position data
53 distance_at_cursor = self.data[0][cursor_index - 1]
Ravago Jones291f5592022-07-07 20:40:37 -070054 multispline_index = int(self.data[5][cursor_index - 1])
55 return (multispline_index, distance_at_cursor)
Ravago Jones0a1d4092022-06-03 12:47:32 -070056
Ravago Jones291f5592022-07-07 20:40:37 -070057 def place_cursor(self, multispline_index, distance):
Ravago Jones0a1d4092022-06-03 12:47:32 -070058 """Places the cursor at a certain distance along the spline"""
59 if self.data is None:
60 return
Ravago Jones291f5592022-07-07 20:40:37 -070061
62 # find the section that is the current multispline
63 start_of_multispline = np.searchsorted(self.data[5],
64 multispline_index,
65 side='left')
66 end_of_multispline = np.searchsorted(self.data[5],
67 multispline_index,
68 side='right')
69 multispline_region = self.data[0][
70 start_of_multispline:end_of_multispline]
71
72 # convert distance along this multispline to time along trajectory
73 index = np.searchsorted(multispline_region, distance,
74 side='left') + start_of_multispline
Ravago Jones0a1d4092022-06-03 12:47:32 -070075 time = index * self.dt
Ravago Jones291f5592022-07-07 20:40:37 -070076
Ravago Jones0a1d4092022-06-03 12:47:32 -070077 self.cursor = time
78 self.redraw_cursor()
79
80 def on_mouse_move(self, event):
81 """Updates the cursor and all the canvases that watch it on mouse move"""
Ravago Jones291f5592022-07-07 20:40:37 -070082
Ravago Jones0a1d4092022-06-03 12:47:32 -070083 if self.data is None:
84 return
85 total_steps_taken = self.data.shape[1]
86 total_time = self.dt * total_steps_taken
87 if event.xdata is not None:
88 # clip the position if still on the canvas, but off the graph
89 self.cursor = np.clip(event.xdata, 0, total_time)
90
91 self.redraw_cursor()
92
93 # tell the field to update too
94 if self.cursor_watcher is not None:
95 self.cursor_watcher.queue_draw()
96
97 def redraw_cursor(self):
98 """Redraws the cursor line"""
99 # TODO: This redraws the entire graph and isn't very snappy
100 if self.cursor_line: self.cursor_line.remove()
101 self.cursor_line = self.axis.axvline(self.cursor)
102 self.canvas.draw_idle()
103
Ravago Jones291f5592022-07-07 20:40:37 -0700104 def schedule_recalculate(self, multisplines):
Ravago Jones0a1d4092022-06-03 12:47:32 -0700105 """Submits points to be graphed
106
107 Can be superseded by newer points if an old one isn't finished processing.
108 """
Ravago Jonesfa8da562022-07-02 18:10:22 -0700109
Ravago Jones291f5592022-07-07 20:40:37 -0700110 new_copy = copy.deepcopy(multisplines)
Ravago Jones56941a52021-07-31 14:42:38 -0700111
112 # empty the queue
113 try:
114 self.queue.get_nowait()
115 except queue.Empty:
Ravago Jones8da89c42022-07-17 19:34:06 -0700116 pass # was already empty
Ravago Jones56941a52021-07-31 14:42:38 -0700117
118 # replace with new request
119 self.queue.put_nowait(new_copy)
120
121 def worker(self):
122 while True:
123 self.recalculate_graph(self.queue.get())
John Park91e69732019-03-03 13:12:43 -0800124
Ravago Jones291f5592022-07-07 20:40:37 -0700125 def recalculate_graph(self, multisplines):
126 if len(multisplines) == 0: return
John Park91e69732019-03-03 13:12:43 -0800127
Ravago Jones6d460fe2021-07-03 16:59:55 -0700128 # call C++ wrappers to calculate the trajectory
Ravago Jones291f5592022-07-07 20:40:37 -0700129 full_data = None
130
131 for multispline_index, multispline in enumerate(multisplines):
132 multispline.update_lib_spline()
133 if len(multispline.getLibsplines()) == 0: continue
134 distanceSpline = DistanceSpline(multispline.getLibsplines())
135 traj = Trajectory(distanceSpline)
136 multispline.addConstraintsToTrajectory(traj)
137 traj.Plan()
138 XVA = traj.GetPlanXVA(self.dt)
139 if XVA is None: continue
140 position, _, _ = XVA
141
142 voltages = np.transpose([traj.Voltage(x) for x in position])
143
144 data = np.append(XVA, voltages, axis=0)
145
146 indicies = np.full((1, XVA.shape[1]), multispline_index, dtype=int)
147 data = np.append(data, indicies, axis=0)
148
149 if full_data is not None:
150 full_data = np.append(full_data, data, axis=1)
151 else:
152 full_data = data
153
154 if full_data is None: return
155 self.data = full_data
John Park91e69732019-03-03 13:12:43 -0800156
Ravago Jones6d460fe2021-07-03 16:59:55 -0700157 # extract values to be graphed
Ravago Jones291f5592022-07-07 20:40:37 -0700158 total_steps_taken = full_data.shape[1]
Ravago Jones0a1d4092022-06-03 12:47:32 -0700159 total_time = self.dt * total_steps_taken
160 times = np.linspace(0, total_time, num=total_steps_taken)
Ravago Jones291f5592022-07-07 20:40:37 -0700161 position, velocity, acceleration, left_voltage, right_voltage, _ = full_data
John Park91e69732019-03-03 13:12:43 -0800162
Ravago Jones6d460fe2021-07-03 16:59:55 -0700163 # update graph
164 self.axis.clear()
Ravago Jones0a1d4092022-06-03 12:47:32 -0700165 self.axis.plot(times, velocity)
166 self.axis.plot(times, acceleration)
167 self.axis.plot(times, left_voltage)
168 self.axis.plot(times, right_voltage)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700169 self.axis.legend(
170 ["Velocity", "Acceleration", "Left Voltage", "Right Voltage"])
171 self.axis.xaxis.set_label_text("Time (sec)")
John Park91e69732019-03-03 13:12:43 -0800172
Ravago Jones6d460fe2021-07-03 16:59:55 -0700173 # renumber the x-axis to include the last point,
174 # the total time to drive the spline
175 self.axis.xaxis.set_ticks(np.linspace(0, total_time, num=8))
Ravago Jones128fb992021-07-31 13:56:58 -0700176
Ravago Jonese9584792022-05-28 16:16:46 -0700177 # redraw
Ravago Jones0a1d4092022-06-03 12:47:32 -0700178 self.canvas.draw_idle()