|
1 | 1 | """ |
2 | | -Plotting utilities for mobile manipulation experiments. |
| 2 | +Core plotting functionality for mobile manipulation experiments. |
3 | 3 |
|
4 | | -This module provides plotting functionality organized into logical sections: |
5 | | -- DataPlotter: Core data loading and processing |
6 | | -- Trajectory plotting: Path and tracking visualization |
7 | | -- Utility functions: PDF export, logger construction |
| 4 | +This module provides the main DataPlotter class and utility functions for |
| 5 | +loading and processing simulation data. |
8 | 6 | """ |
9 | 7 |
|
10 | 8 | import copy |
|
18 | 16 | import mm_control.MPC as MPC |
19 | 17 | from mm_utils import math, parsing |
20 | 18 | from mm_utils.math import wrap_pi_array |
| 19 | +from mm_utils.plotting.mpc import MPCPlotterMixin |
| 20 | +from mm_utils.plotting.trajectory import TrajectoryPlotterMixin |
21 | 21 |
|
22 | 22 | # ============================================================================= |
23 | 23 | # UTILITY FUNCTIONS |
@@ -66,194 +66,6 @@ def construct_logger( |
66 | 66 | raise ValueError(f"Unrecognized folder structure in {path_to_folder}.") |
67 | 67 |
|
68 | 68 |
|
69 | | -# ============================================================================= |
70 | | -# MPC PLOTTING MIXIN |
71 | | -# ============================================================================= |
72 | | - |
73 | | - |
74 | | -class MPCPlotterMixin: |
75 | | - """Mixin class containing MPC-specific plotting methods.""" |
76 | | - |
77 | | - def plot_cost(self): |
78 | | - """Plot MPC cost function over time.""" |
79 | | - t_sim = self.data["ts"] |
80 | | - cost_final = self.data.get("mpc_cost_finals") |
81 | | - |
82 | | - if cost_final is None: |
83 | | - print("No cost data found") |
84 | | - return |
85 | | - |
86 | | - f, ax = plt.subplots(1, 1) |
87 | | - |
88 | | - # Convert to numpy array and handle different shapes |
89 | | - cost_final = np.array(cost_final) |
90 | | - if cost_final.ndim == 1: |
91 | | - ax.plot( |
92 | | - t_sim, |
93 | | - cost_final, |
94 | | - ".-", |
95 | | - label=self.data["name"], |
96 | | - linewidth=2, |
97 | | - markersize=8, |
98 | | - ) |
99 | | - else: |
100 | | - # Multi-dimensional cost - plot first dimension |
101 | | - ax.plot( |
102 | | - t_sim, |
103 | | - cost_final[:, 0], |
104 | | - ".-", |
105 | | - label=self.data["name"], |
106 | | - linewidth=2, |
107 | | - markersize=8, |
108 | | - ) |
109 | | - |
110 | | - ax.set_xlabel("Time (s)") |
111 | | - ax.set_ylabel("Cost") |
112 | | - ax.legend() |
113 | | - ax.set_title("MPC Cost") |
114 | | - ax.grid(True) |
115 | | - |
116 | | - def plot_run_time(self): |
117 | | - """Plot controller execution time.""" |
118 | | - t_sim = self.data["ts"] |
119 | | - run_time = self.data.get("controller_run_time") |
120 | | - if run_time is None: |
121 | | - print("Ignore run time") |
122 | | - return |
123 | | - |
124 | | - f, ax = plt.subplots(1, 1) |
125 | | - ax.plot(t_sim, run_time * 1000, label=self.data["name"], linewidth=2) |
126 | | - ax.set_xlabel("Time (s)") |
127 | | - ax.set_ylabel("run time (ms)") |
128 | | - ax.legend() |
129 | | - ax.set_title("Controller Run Time") |
130 | | - |
131 | | - |
132 | | -# ============================================================================= |
133 | | -# TRAJECTORY PLOTTING MIXIN |
134 | | -# ============================================================================= |
135 | | - |
136 | | - |
137 | | -class TrajectoryPlotterMixin: |
138 | | - """Mixin class containing trajectory and path plotting methods.""" |
139 | | - |
140 | | - def plot_ee_tracking(self): |
141 | | - """Plot end-effector position tracking.""" |
142 | | - ts = self.data["ts"] |
143 | | - r_ew_w_ds = self.data.get("r_ew_w_ds", []) |
144 | | - r_ew_ws = self.data.get("r_ew_ws", []) |
145 | | - |
146 | | - if len(r_ew_w_ds) == 0 and len(r_ew_ws) == 0: |
147 | | - return |
148 | | - |
149 | | - _, axes = plt.subplots(1, 1, sharex=True) |
150 | | - legend = self.data["name"] |
151 | | - |
152 | | - if len(r_ew_w_ds) > 0: |
153 | | - axes.plot( |
154 | | - ts, r_ew_w_ds[:, 0], label=legend + "$x_d$", color="r", linestyle="--" |
155 | | - ) |
156 | | - axes.plot( |
157 | | - ts, r_ew_w_ds[:, 1], label=legend + "$y_d$", color="g", linestyle="--" |
158 | | - ) |
159 | | - axes.plot( |
160 | | - ts, r_ew_w_ds[:, 2], label=legend + "$z_d$", color="b", linestyle="--" |
161 | | - ) |
162 | | - if len(r_ew_ws) > 0: |
163 | | - axes.plot(ts, r_ew_ws[:, 0], label=legend + "$x$", color="r") |
164 | | - axes.plot(ts, r_ew_ws[:, 1], label=legend + "$y$", color="g") |
165 | | - axes.plot(ts, r_ew_ws[:, 2], label=legend + "$z$", color="b") |
166 | | - axes.grid() |
167 | | - axes.legend() |
168 | | - axes.set_xlabel("Time (s)") |
169 | | - axes.set_ylabel("Position (m)") |
170 | | - axes.set_title("End effector position tracking") |
171 | | - |
172 | | - return axes |
173 | | - |
174 | | - def plot_base_path(self): |
175 | | - """Plot base path.""" |
176 | | - r_b = self.data.get("r_bw_ws", []) |
177 | | - |
178 | | - if len(r_b) == 0: |
179 | | - return |
180 | | - |
181 | | - _, ax = plt.subplots(1, 1) |
182 | | - |
183 | | - if len(r_b) > 0: |
184 | | - r_b = np.array(r_b) # Convert to numpy array |
185 | | - ax.plot(r_b[:, 0], r_b[:, 1], label=self.data["name"], linewidth=1) |
186 | | - |
187 | | - ax.grid() |
188 | | - ax.legend() |
189 | | - ax.set_xlabel("x (m)") |
190 | | - ax.set_ylabel("y (m)") |
191 | | - ax.set_title("Base Path Tracking") |
192 | | - |
193 | | - def plot_tracking_err(self): |
194 | | - """Plot tracking error.""" |
195 | | - ts = self.data["ts"] |
196 | | - |
197 | | - _, ax = plt.subplots(1, 1) |
198 | | - |
199 | | - ax.plot( |
200 | | - ts, |
201 | | - self.data["err_base"], |
202 | | - label=self.data["name"] |
203 | | - + f" base err, rms={self.data['statistics']['err_base']['rms']:.3f}", |
204 | | - linestyle="--", |
205 | | - ) |
206 | | - ax.plot( |
207 | | - ts, |
208 | | - self.data["err_ee"], |
209 | | - label=self.data["name"] |
210 | | - + f" EE err, rms={self.data['statistics']['err_ee']['rms']:.3f}", |
211 | | - linestyle="-", |
212 | | - ) |
213 | | - |
214 | | - ax.grid() |
215 | | - ax.legend() |
216 | | - ax.set_xlabel("Time (s)") |
217 | | - ax.set_ylabel("Error (m)") |
218 | | - ax.set_title("Tracking Error vs Time") |
219 | | - |
220 | | - def plot_task_performance(self): |
221 | | - """Plot task performance metrics.""" |
222 | | - f, axes = plt.subplots(4, 1, sharex=True) |
223 | | - |
224 | | - legend = self.data["name"] |
225 | | - t_sim = self.data["ts"] |
226 | | - |
227 | | - axes[0].plot( |
228 | | - t_sim, |
229 | | - self.data["constraints_violation"] * 100, |
230 | | - label=f"{legend} mean={self.data['statistics']['constraints_violation']['mean']*100:.1f}%", |
231 | | - ) |
232 | | - axes[0].set_ylabel("Constraints violation (%)") |
233 | | - |
234 | | - axes[1].plot( |
235 | | - t_sim, |
236 | | - self.data["err_ee"], |
237 | | - label=f"{legend} acc={self.data['statistics']['err_ee']['integral']:.3f}", |
238 | | - ) |
239 | | - axes[1].set_ylabel("EE Error (m)") |
240 | | - |
241 | | - axes[2].plot( |
242 | | - t_sim, |
243 | | - self.data["err_base"], |
244 | | - label=f"{legend} acc={self.data['statistics']['err_base']['integral']:.3f}", |
245 | | - ) |
246 | | - axes[2].set_ylabel("Base Error (m)") |
247 | | - |
248 | | - axes[3].plot(t_sim, self.data["arm_manipulability"], label=legend) |
249 | | - axes[3].set_ylabel("Arm Manipulability") |
250 | | - axes[3].set_xlabel("Time (s)") |
251 | | - |
252 | | - for ax in axes: |
253 | | - ax.legend() |
254 | | - ax.grid(True) |
255 | | - |
256 | | - |
257 | 69 | # ============================================================================= |
258 | 70 | # CORE DATAPLOTTER CLASS |
259 | 71 | # ============================================================================= |
|
0 commit comments