Skip to content

Commit 996e0d4

Browse files
Merge pull request #76 from ami-iit/draw_3d_trajectory
✨ Add the possibility to draw 3d trajectory in meshcat-visualizer
2 parents 2a26abf + b073ce7 commit 996e0d4

File tree

4 files changed

+121
-16
lines changed

4 files changed

+121
-16
lines changed

robot_log_visualizer/file_reader/signal_provider.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ def __init__(self, period: float):
4848
self._3d_points_path = {}
4949
self._3d_points_path_lock = QMutex()
5050

51+
self._3d_trajectories_path = {}
52+
self._3d_trajectories_path_lock = QMutex()
53+
5154
self.period = period
5255

5356
self.data = {}
@@ -64,6 +67,8 @@ def __init__(self, period: float):
6467

6568
self._current_time = 0
6669

70+
self.trajectory_span = 200
71+
6772
def __populate_text_logging_data(self, file_object):
6873
data = {}
6974
for key, value in file_object.items():
@@ -241,12 +246,18 @@ def get_item_from_path(self, path, default_path=None):
241246

242247
return data["data"], data["timestamps"]
243248

244-
def get_item_from_path_at_index(self, path, index, default_path=None):
249+
def get_item_from_path_at_index(self, path, index, default_path=None, neighbor=0):
245250
data, timestamps = self.get_item_from_path(path, default_path)
246251
if data is None:
247252
return None
248253
closest_index = np.argmin(np.abs(timestamps - self.timestamps[index]))
249-
return data[closest_index, :]
254+
255+
if neighbor == 0:
256+
return data[closest_index, :]
257+
258+
initial_index = max(0, closest_index - neighbor)
259+
end_index = min(len(timestamps), closest_index + neighbor + 1)
260+
return data[initial_index:end_index, :]
250261

251262
def get_robot_state_at_index(self, index):
252263
robot_state = {}
@@ -308,6 +319,31 @@ def get_3d_point_at_index(self, index):
308319

309320
return points
310321

322+
def get_3d_trajectory_at_index(self, index):
323+
trajectories = {}
324+
325+
self._3d_trajectories_path_lock.lock()
326+
327+
for key, value in self._3d_trajectories_path.items():
328+
trajectories[key] = self.get_item_from_path_at_index(
329+
value, index, neighbor=self.trajectory_span
330+
)
331+
# force the size of the points to be 3 if less than 3 we assume that the point is a 2d point and we add a 0 as z coordinate
332+
if trajectories[key].shape[1] < 3:
333+
trajectories[key] = np.concatenate(
334+
(
335+
trajectories[key],
336+
np.zeros(
337+
(trajectories[key].shape[0], 3 - trajectories[key].shape[1])
338+
),
339+
),
340+
axis=1,
341+
)
342+
343+
self._3d_trajectories_path_lock.unlock()
344+
345+
return trajectories
346+
311347
def register_3d_point(self, key, points_path):
312348
self._3d_points_path_lock.lock()
313349
self._3d_points_path[key] = points_path
@@ -318,6 +354,16 @@ def unregister_3d_point(self, key):
318354
del self._3d_points_path[key]
319355
self._3d_points_path_lock.unlock()
320356

357+
def register_3d_trajectory(self, key, trajectory_path):
358+
self._3d_trajectories_path_lock.lock()
359+
self._3d_trajectories_path[key] = trajectory_path
360+
self._3d_trajectories_path_lock.unlock()
361+
362+
def unregister_3d_trajectory(self, key):
363+
self._3d_trajectories_path_lock.lock()
364+
del self._3d_trajectories_path[key]
365+
self._3d_trajectories_path_lock.unlock()
366+
321367
def run(self):
322368
while True:
323369
start = time.time()

robot_log_visualizer/robot_visualizer/meshcat_provider.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(self, signal_provider, period):
3535
self.custom_package_dir = ""
3636
self.env_list = ["GAZEBO_MODEL_PATH", "ROS_PACKAGE_PATH", "AMENT_PREFIX_PATH"]
3737
self._registered_3d_points = set()
38+
self._registered_3d_trajectories = dict()
3839

3940
@property
4041
def state(self):
@@ -55,11 +56,20 @@ def register_3d_point(self, point_path, color):
5556
radius=radius, color=color, shape_name=point_path
5657
)
5758

59+
def register_3d_trajectory(self, trajectory_path, color):
60+
locker = QMutexLocker(self.meshcat_visualizer_mutex)
61+
self._registered_3d_trajectories[trajectory_path] = (False, color)
62+
5863
def unregister_3d_point(self, point_path):
5964
locker = QMutexLocker(self.meshcat_visualizer_mutex)
6065
self._registered_3d_points.remove(point_path)
6166
self._meshcat_visualizer.delete(shape_name=point_path)
6267

68+
def unregister_3d_trajectory(self, trajectory_path):
69+
locker = QMutexLocker(self.meshcat_visualizer_mutex)
70+
self._registered_3d_trajectories.pop(trajectory_path, None)
71+
self._meshcat_visualizer.delete(shape_name=trajectory_path)
72+
6373
def load_model(self, considered_joints, model_name):
6474
def get_model_path_from_envs(env_list):
6575
return [
@@ -162,11 +172,9 @@ def run(self):
162172
while True:
163173
start = time.time()
164174

175+
index = self._signal_provider.index
165176
if self.state == PeriodicThreadState.running and self._is_model_loaded:
166-
robot_state = self._signal_provider.get_robot_state_at_index(
167-
self._signal_provider.index
168-
)
169-
177+
robot_state = self._signal_provider.get_robot_state_at_index(index)
170178
self.meshcat_visualizer_mutex.lock()
171179
# These are the robot measured joint positions in radians
172180
self._meshcat_visualizer.set_multibody_system_state(
@@ -177,7 +185,7 @@ def run(self):
177185
)
178186

179187
for points_path, points in self._signal_provider.get_3d_point_at_index(
180-
self._signal_provider.index
188+
index
181189
).items():
182190
if points_path not in self._registered_3d_points:
183191
continue
@@ -186,6 +194,28 @@ def run(self):
186194
position=points, rotation=identity, shape_name=points_path
187195
)
188196

197+
for (
198+
trajectory_path,
199+
trajectory,
200+
) in self._signal_provider.get_3d_trajectory_at_index(index).items():
201+
if trajectory_path not in self._registered_3d_trajectories.keys():
202+
continue
203+
204+
if self._registered_3d_trajectories[trajectory_path][0]:
205+
self._meshcat_visualizer.delete(shape_name=trajectory_path)
206+
else:
207+
self._registered_3d_trajectories[trajectory_path] = (
208+
True,
209+
self._registered_3d_trajectories[trajectory_path][1],
210+
)
211+
212+
self._meshcat_visualizer.load_line(
213+
vertices=trajectory.T,
214+
linewidth=5.0,
215+
shape_name=trajectory_path,
216+
color=self._registered_3d_trajectories[trajectory_path][1],
217+
)
218+
189219
self.meshcat_visualizer_mutex.unlock()
190220

191221
sleep_time = self._period - (time.time() - start)

robot_log_visualizer/ui/gui.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period):
165165
self.plot_items = []
166166
self.video_items = []
167167
self.visualized_3d_points = set()
168+
self.visualized_3d_trajectories = set()
168169
self.visualized_3d_points_colors_palette = ColorPalette()
169170

170171
self.toolButton_on_click()
@@ -722,7 +723,9 @@ def variableTreeWidget_on_right_click(self, item_position):
722723
menu = QtWidgets.QMenu()
723724

724725
add_3d_point_str = "Show as a 3D point"
726+
add_3d_trajectory_str = "Show as a 3D trajectory"
725727
remove_3d_point_str = "Remove the 3D point"
728+
remove_3d_trajectory_str = "Remove the 3D trajectory"
726729
use_as_base_position_str = "Use as base position"
727730
use_as_base_orientation_str = "Use as base orientation"
728731
dont_use_as_base_position_str = "Don't use as base position"
@@ -734,8 +737,14 @@ def variableTreeWidget_on_right_click(self, item_position):
734737
if item_size == 2:
735738
if item_key in self.visualized_3d_points:
736739
menu.addAction(remove_3d_point_str)
737-
else:
740+
if item_key in self.visualized_3d_trajectories:
741+
menu.addAction(remove_3d_trajectory_str)
742+
if (
743+
item_key not in self.visualized_3d_points
744+
and item_key not in self.visualized_3d_trajectories
745+
):
738746
menu.addAction(add_3d_point_str)
747+
menu.addAction(add_3d_trajectory_str)
739748

740749
# in this case we can use the item as base position, base orientation or 3d point
741750
if item_size == 3:
@@ -753,8 +762,14 @@ def variableTreeWidget_on_right_click(self, item_position):
753762

754763
if item_key in self.visualized_3d_points:
755764
menu.addAction(remove_3d_point_str)
756-
else:
765+
if item_key in self.visualized_3d_trajectories:
766+
menu.addAction(remove_3d_trajectory_str)
767+
if (
768+
item_key not in self.visualized_3d_points
769+
and item_key not in self.visualized_3d_trajectories
770+
):
757771
menu.addAction(add_3d_point_str)
772+
menu.addAction(add_3d_trajectory_str)
758773

759774
if item_size == 4:
760775
if item_path == self.robot_state_path.base_orientation_path:
@@ -769,22 +784,36 @@ def variableTreeWidget_on_right_click(self, item_position):
769784

770785
item_path = self.get_item_path(item)
771786

772-
if action.text() == add_3d_point_str:
787+
if action.text() == add_3d_point_str or action.text() == add_3d_trajectory_str:
773788
color = next(self.visualized_3d_points_colors_palette)
774789

775790
item.setForeground(0, QtGui.QBrush(QtGui.QColor(color.as_hex())))
776-
self.meshcat_provider.register_3d_point(
777-
item_key, list(color.as_normalized_rgb())
778-
)
779-
self.signal_provider.register_3d_point(item_key, item_path)
780-
self.visualized_3d_points.add(item_key)
791+
792+
if action.text() == add_3d_point_str:
793+
self.meshcat_provider.register_3d_point(
794+
item_key, list(color.as_normalized_rgb())
795+
)
796+
self.signal_provider.register_3d_point(item_key, item_path)
797+
self.visualized_3d_points.add(item_key)
798+
else:
799+
self.meshcat_provider.register_3d_trajectory(
800+
item_key, list(color.as_normalized_rgb())
801+
)
802+
self.signal_provider.register_3d_trajectory(item_key, item_path)
803+
self.visualized_3d_trajectories.add(item_key)
781804

782805
if action.text() == remove_3d_point_str:
783806
self.meshcat_provider.unregister_3d_point(item_key)
784807
self.signal_provider.unregister_3d_point(item_key)
785808
self.visualized_3d_points.remove(item_key)
786809
item.setForeground(0, QtGui.QBrush(QtGui.QColor(0, 0, 0)))
787810

811+
if action.text() == remove_3d_trajectory_str:
812+
self.meshcat_provider.unregister_3d_trajectory(item_key)
813+
self.signal_provider.unregister_3d_trajectory(item_key)
814+
self.visualized_3d_trajectories.remove(item_key)
815+
item.setForeground(0, QtGui.QBrush(QtGui.QColor(0, 0, 0)))
816+
788817
if (
789818
use_as_base_orientation_str in action.text()
790819
or action.text() == use_as_base_position_str

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ classifiers =
4040
packages = find:
4141
python_requires = >=3.8
4242
install_requires =
43-
idyntree >= 8.0.0
43+
idyntree >= 10.2.0
4444
meshcat
4545
numpy
4646
PyQt5

0 commit comments

Comments
 (0)