blob: 7d73a799131daa79b376603f1d734d7f44a4efad [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)
50 # use the time to index into the position data
51 distance_at_cursor = self.data[0][cursor_index - 1]
Ravago Jones291f5592022-07-07 20:40:37 -070052 multispline_index = int(self.data[5][cursor_index - 1])
53 return (multispline_index, distance_at_cursor)
Ravago Jones0a1d4092022-06-03 12:47:32 -070054
Ravago Jones291f5592022-07-07 20:40:37 -070055 def place_cursor(self, multispline_index, distance):
Ravago Jones0a1d4092022-06-03 12:47:32 -070056 """Places the cursor at a certain distance along the spline"""
57 if self.data is None:
58 return
Ravago Jones291f5592022-07-07 20:40:37 -070059
60 # find the section that is the current multispline
61 start_of_multispline = np.searchsorted(self.data[5],
62 multispline_index,
63 side='left')
64 end_of_multispline = np.searchsorted(self.data[5],
65 multispline_index,
66 side='right')
67 multispline_region = self.data[0][
68 start_of_multispline:end_of_multispline]
69
70 # convert distance along this multispline to time along trajectory
71 index = np.searchsorted(multispline_region, distance,
72 side='left') + start_of_multispline
Ravago Jones0a1d4092022-06-03 12:47:32 -070073 time = index * self.dt
Ravago Jones291f5592022-07-07 20:40:37 -070074
Ravago Jones0a1d4092022-06-03 12:47:32 -070075 self.cursor = time
76 self.redraw_cursor()
77
78 def on_mouse_move(self, event):
79 """Updates the cursor and all the canvases that watch it on mouse move"""
Ravago Jones291f5592022-07-07 20:40:37 -070080
Ravago Jones0a1d4092022-06-03 12:47:32 -070081 if self.data is None:
82 return
83 total_steps_taken = self.data.shape[1]
84 total_time = self.dt * total_steps_taken
85 if event.xdata is not None:
86 # clip the position if still on the canvas, but off the graph
87 self.cursor = np.clip(event.xdata, 0, total_time)
88
89 self.redraw_cursor()
90
91 # tell the field to update too
92 if self.cursor_watcher is not None:
93 self.cursor_watcher.queue_draw()
94
95 def redraw_cursor(self):
96 """Redraws the cursor line"""
97 # TODO: This redraws the entire graph and isn't very snappy
98 if self.cursor_line: self.cursor_line.remove()
99 self.cursor_line = self.axis.axvline(self.cursor)
100 self.canvas.draw_idle()
101
Ravago Jones291f5592022-07-07 20:40:37 -0700102 def schedule_recalculate(self, multisplines):
Ravago Jones0a1d4092022-06-03 12:47:32 -0700103 """Submits points to be graphed
104
105 Can be superseded by newer points if an old one isn't finished processing.
106 """
Ravago Jonesfa8da562022-07-02 18:10:22 -0700107
Ravago Jones291f5592022-07-07 20:40:37 -0700108 new_copy = copy.deepcopy(multisplines)
Ravago Jones56941a52021-07-31 14:42:38 -0700109
110 # empty the queue
111 try:
112 self.queue.get_nowait()
113 except queue.Empty:
Ravago Jones8da89c42022-07-17 19:34:06 -0700114 pass # was already empty
Ravago Jones56941a52021-07-31 14:42:38 -0700115
116 # replace with new request
117 self.queue.put_nowait(new_copy)
118
119 def worker(self):
120 while True:
121 self.recalculate_graph(self.queue.get())
John Park91e69732019-03-03 13:12:43 -0800122
Ravago Jones291f5592022-07-07 20:40:37 -0700123 def recalculate_graph(self, multisplines):
124 if len(multisplines) == 0: return
John Park91e69732019-03-03 13:12:43 -0800125
Ravago Jones6d460fe2021-07-03 16:59:55 -0700126 # call C++ wrappers to calculate the trajectory
Ravago Jones291f5592022-07-07 20:40:37 -0700127 full_data = None
128
129 for multispline_index, multispline in enumerate(multisplines):
130 multispline.update_lib_spline()
131 if len(multispline.getLibsplines()) == 0: continue
132 distanceSpline = DistanceSpline(multispline.getLibsplines())
133 traj = Trajectory(distanceSpline)
134 multispline.addConstraintsToTrajectory(traj)
135 traj.Plan()
136 XVA = traj.GetPlanXVA(self.dt)
137 if XVA is None: continue
138 position, _, _ = XVA
139
140 voltages = np.transpose([traj.Voltage(x) for x in position])
141
142 data = np.append(XVA, voltages, axis=0)
143
144 indicies = np.full((1, XVA.shape[1]), multispline_index, dtype=int)
145 data = np.append(data, indicies, axis=0)
146
147 if full_data is not None:
148 full_data = np.append(full_data, data, axis=1)
149 else:
150 full_data = data
151
152 if full_data is None: return
153 self.data = full_data
John Park91e69732019-03-03 13:12:43 -0800154
Ravago Jones6d460fe2021-07-03 16:59:55 -0700155 # extract values to be graphed
Ravago Jones291f5592022-07-07 20:40:37 -0700156 total_steps_taken = full_data.shape[1]
Ravago Jones0a1d4092022-06-03 12:47:32 -0700157 total_time = self.dt * total_steps_taken
158 times = np.linspace(0, total_time, num=total_steps_taken)
Ravago Jones291f5592022-07-07 20:40:37 -0700159 position, velocity, acceleration, left_voltage, right_voltage, _ = full_data
John Park91e69732019-03-03 13:12:43 -0800160
Ravago Jones6d460fe2021-07-03 16:59:55 -0700161 # update graph
162 self.axis.clear()
Ravago Jones0a1d4092022-06-03 12:47:32 -0700163 self.axis.plot(times, velocity)
164 self.axis.plot(times, acceleration)
165 self.axis.plot(times, left_voltage)
166 self.axis.plot(times, right_voltage)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700167 self.axis.legend(
168 ["Velocity", "Acceleration", "Left Voltage", "Right Voltage"])
169 self.axis.xaxis.set_label_text("Time (sec)")
John Park91e69732019-03-03 13:12:43 -0800170
Ravago Jones6d460fe2021-07-03 16:59:55 -0700171 # renumber the x-axis to include the last point,
172 # the total time to drive the spline
173 self.axis.xaxis.set_ticks(np.linspace(0, total_time, num=8))
Ravago Jones128fb992021-07-31 13:56:58 -0700174
Ravago Jonese9584792022-05-28 16:16:46 -0700175 # redraw
Ravago Jones0a1d4092022-06-03 12:47:32 -0700176 self.canvas.draw_idle()