-
Notifications
You must be signed in to change notification settings - Fork 5
RT Capabilities Part 1 - robot-log-visualizer #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 64 commits
b46676b
6f1ec87
cfd0ede
f20c655
5b700c5
f8153d4
a716e59
ff33dc2
2de1dcd
6a3840d
aa331dd
3b86123
886a45b
a1a0799
e412bf4
e19c626
835a17f
0bf0525
872b364
36c9f3b
031c401
de5a54a
be3d77f
be1a667
82eb5e5
83dbebc
b117936
228e84f
4cd3a85
6d13f85
97d2a03
56c69ed
da33419
27eb858
0681e5a
1631469
8d96948
a8538d6
4b203ab
6c60288
32a05b7
3604f62
336db8e
ad228f1
e10efaa
bfce38b
3b7e98e
cadd9e0
0458de2
db95f5e
ac36876
82b5353
7b6f231
6056056
00c9011
f633d6d
0d2408c
4eacfa2
296ee38
d8b573b
ecdf600
059c6d6
a02f24e
621ce36
b98edec
221711a
49880bf
8cb0209
989426c
40f406b
fa4282f
0b033ba
f8b88fa
59953ef
ad96b95
76b3129
767c83c
2cc7277
f5b4a65
be77f98
5df2f88
5461cbe
05f2a9b
ca2088c
e6a4f67
b469e6c
d0cd098
7c04af1
b41f386
ccfc2f0
b6e372d
42b935b
2222eaa
dd9ec7c
7a51724
f79308e
9c09faa
d43cb37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
# This software may be modified and distributed under the terms of the | ||
# Released under the terms of the BSD 3-Clause License | ||
|
||
import sys | ||
import time | ||
import math | ||
import h5py | ||
|
@@ -10,6 +11,12 @@ | |
from robot_log_visualizer.utils.utils import PeriodicThreadState, RobotStatePath | ||
import idyntree.swig as idyn | ||
|
||
import bipedal_locomotion_framework.bindings as blf | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# for real-time logging | ||
import yarp | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @GiulioRomualdi should we lazy load also yarp or we just need to lazy load blf? fyi @nicktrem |
||
import json | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class TextLoggingMsg: | ||
def __init__(self, level, text): | ||
|
@@ -67,7 +74,15 @@ def __init__(self, period: float): | |
|
||
self._current_time = 0 | ||
|
||
self.realtimeBufferReached = False | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.initMetadata = False | ||
self.realtimeFixedPlotWindow = 20 | ||
|
||
# for networking with the real-time logger | ||
self.realtimeNetworkInit = False | ||
self.vectorCollectionsClient = blf.yarp_utilities.VectorsCollectionClient() | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.trajectory_span = 200 | ||
self.rtMetadataDict = {} | ||
|
||
def __populate_text_logging_data(self, file_object): | ||
data = {} | ||
|
@@ -121,8 +136,10 @@ def __populate_numerical_data(self, file_object): | |
if not isinstance(value, h5py._hl.group.Group): | ||
continue | ||
if key == "#refs#": | ||
print("Skipping for refs") | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue | ||
if key == "log": | ||
print("Skipping for log") | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue | ||
if "data" in value.keys(): | ||
data[key] = {} | ||
|
@@ -145,13 +162,105 @@ def __populate_numerical_data(self, file_object): | |
"".join(chr(c[0]) for c in value[ref]) | ||
for ref in elements_names_ref[0] | ||
] | ||
|
||
else: | ||
data[key] = self.__populate_numerical_data(file_object=value) | ||
|
||
return data | ||
|
||
def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if keys[0] not in rawData: | ||
rawData[keys[0]] = {} | ||
|
||
if len(keys) == 1: | ||
rawData[keys[0]]["data"] = np.append(rawData[keys[0]]["data"], value).reshape(-1, len(value)) | ||
rawData[keys[0]]["timestamps"] = np.append(rawData[keys[0]]["timestamps"], recentTimestamp) | ||
|
||
tempInitialTime = rawData[keys[0]]["timestamps"][0] | ||
tempEndTime = rawData[keys[0]]["timestamps"][-1] | ||
while tempEndTime - tempInitialTime > self.realtimeFixedPlotWindow: | ||
rawData[keys[0]]["data"] = np.delete(rawData[keys[0]]["data"], 0, axis=0) | ||
rawData[keys[0]]["timestamps"] = np.delete(rawData[keys[0]]["timestamps"], 0) | ||
tempInitialTime = rawData[keys[0]]["timestamps"][0] | ||
tempEndTime = rawData[keys[0]]["timestamps"][-1] | ||
|
||
else: | ||
self.__populateRealtimeLoggerData(rawData[keys[0]], keys[1:], value, recentTimestamp) | ||
|
||
def __populateRealtimeLoggerMetadata(self, rawData, keys, value): | ||
if keys[0] == "timestamps": | ||
return | ||
if keys[0] not in rawData: | ||
rawData[keys[0]] = {} | ||
|
||
if len(keys) == 1: | ||
if len(value) == 0: | ||
del rawData[keys[0]] | ||
return | ||
if "elements_names" not in rawData[keys[0]]: | ||
rawData[keys[0]]["elements_names"] = np.array([]) | ||
rawData[keys[0]]["data"] = np.array([]) | ||
rawData[keys[0]]["timestamps"] = np.array([]) | ||
|
||
rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value) | ||
else: | ||
self.__populateRealtimeLoggerMetadata(rawData[keys[0]], keys[1:], value) | ||
|
||
|
||
def maintain_connection(self): | ||
if not self.realtimeNetworkInit: | ||
yarp.Network.init() | ||
|
||
param_handler = blf.parameters_handler.YarpParametersHandler() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine to have config files. However, I would keep blf deps relegated only for the real-time logging and use qt config |
||
param_handler.set_parameter_string("remote", "/rtLoggingVectorCollections") # you must have some local port as well | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the remote should be set from the gui perhaps this can be the default value |
||
param_handler.set_parameter_string("local", "/visualizerInput") # remote must match the server | ||
param_handler.set_parameter_string("carrier", "udp") | ||
self.vectorCollectionsClient.initialize(param_handler) | ||
|
||
self.vectorCollectionsClient.connect() | ||
self.realtimeNetworkInit = True | ||
self.rtMetadataDict = self.vectorCollectionsClient.get_metadata().vectors | ||
if not self.rtMetadataDict: | ||
print("Failed to read realtime YARP port, closing") | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return False | ||
|
||
self.joints_name = self.rtMetadataDict["robot_realtime::description_list"] | ||
self.robot_name = self.rtMetadataDict["robot_realtime::yarp_robot_name"][0] | ||
for keyString, value in self.rtMetadataDict.items(): | ||
keys = keyString.split("::") | ||
self.__populateRealtimeLoggerMetadata(self.data, keys, value) | ||
del self.data["robot_realtime"]["description_list"] | ||
del self.data["robot_realtime"]["yarp_robot_name"] | ||
|
||
vc_input = self.vectorCollectionsClient.read_data(True) | ||
|
||
if not vc_input: | ||
print("Failed to read realtime YARP port, closing") | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return False | ||
else: | ||
# Update the timestamps | ||
recentTimestamp = vc_input["robot_realtime::timestamps"][0] | ||
self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) | ||
del vc_input["robot_realtime::timestamps"] | ||
|
||
# Keep the data within the fixed time interval | ||
while recentTimestamp - self.timestamps[0] > self.realtimeFixedPlotWindow: | ||
self.initial_time = self.timestamps[0] | ||
self.end_time = self.timestamps[-1] | ||
self.timestamps = np.delete(self.timestamps, 0).reshape(-1) | ||
self.initial_time = self.timestamps[0] | ||
self.end_time = self.timestamps[-1] | ||
|
||
# Store the new data that comes in | ||
for keyString, value in vc_input.items(): | ||
keys = keyString.split("::") | ||
self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) | ||
|
||
return True | ||
|
||
def open_mat_file(self, file_name: str): | ||
with h5py.File(file_name, "r") as file: | ||
|
||
root_variable = file.get(self.root_name) | ||
self.data = self.__populate_numerical_data(file) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
# Released under the terms of the BSD 3-Clause License | ||
|
||
# PyQt | ||
import numpy as np | ||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas | ||
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar | ||
from matplotlib.figure import Figure | ||
|
@@ -184,23 +185,34 @@ def on_pick(self, event): | |
blit=True, | ||
) | ||
|
||
def update_plots(self, paths, legends): | ||
def update_plots(self, paths, legends, realtimePlot): | ||
self.axes.cla() | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
realtimeColorIndex = 0 | ||
for path, legend in zip(paths, legends): | ||
path_string = "/".join(path) | ||
legend_string = "/".join(legend[1:]) | ||
|
||
if path_string not in self.active_paths.keys(): | ||
data = self.signal_provider.data | ||
for key in path[:-1]: | ||
data = data[key] | ||
try: | ||
datapoints = data["data"][:, int(path[-1])] | ||
except IndexError: | ||
# This happens in the case the variable is a scalar. | ||
datapoints = data["data"][:] | ||
data = self.signal_provider.data.copy() | ||
for key in path[:-1]: | ||
data = data[key] | ||
try: | ||
datapoints = data["data"][:, int(path[-1])] | ||
except IndexError: | ||
# This happens in the case the variable is a scalar. | ||
datapoints = data["data"][:] | ||
|
||
timestamps = data["timestamps"] - self.signal_provider.initial_time | ||
timestamps = data["timestamps"] - self.signal_provider.initial_time | ||
|
||
if realtimePlot: | ||
(self.active_paths[path_string],) = self.axes.plot( | ||
timestamps, | ||
datapoints, | ||
label=legend_string, | ||
picker=True, | ||
color=self.color_palette.get_color(realtimeColorIndex), | ||
) | ||
realtimeColorIndex = realtimeColorIndex + 1 | ||
else: | ||
(self.active_paths[path_string],) = self.axes.plot( | ||
timestamps, | ||
datapoints, | ||
|
@@ -209,6 +221,7 @@ def update_plots(self, paths, legends): | |
color=next(self.color_palette), | ||
) | ||
|
||
|
||
paths_to_be_canceled = [] | ||
for active_path in self.active_paths.keys(): | ||
path = active_path.split("/") | ||
|
@@ -220,14 +233,20 @@ def update_plots(self, paths, legends): | |
self.active_paths[path].remove() | ||
self.active_paths.pop(path) | ||
|
||
self.axes.set_xlim( | ||
0, self.signal_provider.end_time - self.signal_provider.initial_time | ||
) | ||
if realtimePlot: | ||
#self.axes.autoscale() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is not needed we can remove it. |
||
self.axes.set_xlim(0, self.signal_provider.realtimeFixedPlotWindow) | ||
else: | ||
self.axes.set_xlim( | ||
0, self.signal_provider.end_time - self.signal_provider.initial_time | ||
) | ||
|
||
# Since a new plot has been added/removed we delete the old animation and we create a new one | ||
# TODO: this part could be optimized | ||
|
||
self.vertical_line_anim._stop() | ||
self.axes.legend() | ||
self.axes.grid() | ||
|
||
if not self.frame_legend: | ||
self.frame_legend = self.axes.legend().get_frame() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -239,15 +239,24 @@ def setupUi(self, MainWindow): | |
icon = QtGui.QIcon.fromTheme("exit") | ||
self.actionQuit.setIcon(icon) | ||
self.actionQuit.setObjectName("actionQuit") | ||
|
||
# Add the GUI components for the open action | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an autogenerated file and cannot be touched the correct approach here is to modify the UI file directly with qtcreator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in be77f98 |
||
self.actionOpen = QtWidgets.QAction(MainWindow) | ||
icon = QtGui.QIcon.fromTheme("document-open") | ||
self.actionOpen.setIcon(icon) | ||
self.actionOpen.setObjectName("actionOpen") | ||
|
||
# Add a GUI action for connecting to the YARP port | ||
# for real-time logging | ||
self.actionConnect = QtWidgets.QAction(MainWindow) | ||
self.actionConnect.setObjectName("actionConnect") | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
self.actionAbout = QtWidgets.QAction(MainWindow) | ||
self.actionAbout.setObjectName("actionAbout") | ||
self.actionSet_Robot_Model = QtWidgets.QAction(MainWindow) | ||
self.actionSet_Robot_Model.setObjectName("actionSet_Robot_Model") | ||
self.menuFile.addAction(self.actionOpen) | ||
self.menuFile.addAction(self.actionConnect) | ||
self.menuFile.addSeparator() | ||
self.menuFile.addAction(self.actionQuit) | ||
self.menuHelp.addAction(self.actionAbout) | ||
|
@@ -275,6 +284,7 @@ def retranslateUi(self, MainWindow): | |
self.actionQuit.setText(_translate("MainWindow", "&Quit")) | ||
self.actionQuit.setShortcut(_translate("MainWindow", "Ctrl+Q")) | ||
self.actionOpen.setText(_translate("MainWindow", "&Open")) | ||
self.actionConnect.setText(_translate("MainWindow", "Realtime Connect")) | ||
nicktrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O")) | ||
self.actionAbout.setText(_translate("MainWindow", "About")) | ||
self.actionSet_Robot_Model.setText(_translate("MainWindow", "Set Robot Model")) | ||
|
Uh oh!
There was an error while loading. Please reload this page.