Skip to content
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

RT Capabilities Part 1 - robot-log-visualizer #80

Open
wants to merge 81 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
b46676b
Added the basics of the connect button in the menu
nicktrem Dec 26, 2023
6f1ec87
Added dummy data to test formatting and gui representation
nicktrem Dec 29, 2023
cfd0ede
Added a real-time logger button to the GUI
nicktrem Dec 29, 2023
f20c655
Added another test data set
nicktrem Dec 29, 2023
5b700c5
initial test of reading form YARP port passed
nicktrem Dec 31, 2023
f8153d4
Added more plot support for real-time data
nicktrem Jan 2, 2024
a716e59
Fixed small bug in plotting regarding mutiple plots at the same time
nicktrem Jan 2, 2024
ff33dc2
Fixed race condition with graphs and bug with plot indexes
nicktrem Jan 2, 2024
2de1dcd
Removed the need to pre-inform the logger about the data coming in
nicktrem Jan 2, 2024
6a3840d
Fixed race condition bug
nicktrem Jan 2, 2024
aa331dd
Removed hard-coded value for the length of incoming data
nicktrem Jan 2, 2024
3b86123
Can now stream all data (except mesh data) in real-time
nicktrem Jan 4, 2024
886a45b
Cleaned up the code a bit
nicktrem Jan 4, 2024
a1a0799
Added mesh support, cleaned up the code a bit
nicktrem Jan 5, 2024
e412bf4
Cleaned up the code, removed hard-coded values
nicktrem Jan 5, 2024
e19c626
Cleaned up the code some more
nicktrem Jan 9, 2024
835a17f
Added sliding window feature and icon for connection
nicktrem Jan 9, 2024
0bf0525
Fixed bug with real time logging window
nicktrem Jan 9, 2024
872b364
removed hardcoded value for fixed plot window
nicktrem Jan 9, 2024
36c9f3b
Moved intialization values outside of the conneciton loop
nicktrem Jan 9, 2024
031c401
Fixed blocking when connection closed, changed the YARP port name
nicktrem Jan 9, 2024
de5a54a
Added label to rt connection option in GUI
nicktrem Jan 9, 2024
be3d77f
Merge branch 'main' into YARP-port-client
nicktrem Jan 9, 2024
be1a667
Removed Trailing Whitespace
nicktrem Jan 9, 2024
82eb5e5
Removed whitespace
nicktrem Jan 9, 2024
83dbebc
Removed Whitespace
nicktrem Jan 9, 2024
b117936
Fixed issues with merge
nicktrem Jan 12, 2024
228e84f
Fixed freeze when connection stops
nicktrem Jan 12, 2024
4cd3a85
Initial test complete of receiving RT data from Vector Collection Server
nicktrem Jan 15, 2024
6d13f85
Got joint data streaming over the vector collection network
nicktrem Jan 16, 2024
97d2a03
Got mesh updating in realtime, cleaned up the code
nicktrem Jan 17, 2024
56c69ed
Removed unnecessary values from the tree window
nicktrem Jan 17, 2024
da33419
plot scaling now updating correctly
nicktrem Jan 17, 2024
27eb858
Flight data streaming
nicktrem Jan 17, 2024
0681e5a
Got naming corrected
nicktrem Jan 18, 2024
1631469
Fixed freezing when server is not running
nicktrem Jan 18, 2024
8d96948
Cleaned up the code a bit
nicktrem Jan 18, 2024
a8538d6
Added a grid to the plot
nicktrem Jan 18, 2024
4b203ab
Merge branch 'main' into vectorCollectionClientRT
nicktrem Jan 18, 2024
6c60288
Removed whitespace from signal_provider.py
nicktrem Jan 18, 2024
32a05b7
Changed realtime port name
nicktrem Jan 22, 2024
3604f62
Merge branch 'vectorCollectionClientRT' of https://github.com/nicktre…
nicktrem Jan 22, 2024
336db8e
Changes naming of python functions
nicktrem Feb 5, 2024
ad228f1
Edited flag for closing thread properly
nicktrem Feb 8, 2024
e10efaa
Moved enable features outside the loop
nicktrem Feb 9, 2024
bfce38b
Removed timestamps from RT view
nicktrem Feb 9, 2024
3b7e98e
Removed whitespace
nicktrem Feb 9, 2024
cadd9e0
External signals can now be added at any time
nicktrem Feb 13, 2024
0458de2
Cleaned up the code
nicktrem Feb 13, 2024
db95f5e
Removed whitespace
nicktrem Feb 13, 2024
ac36876
Fixed issue of adding mutiple exogenous signals
nicktrem Feb 13, 2024
82b5353
Removed new metadata flag from being displayed
nicktrem Feb 13, 2024
7b6f231
Cleaned up the code
nicktrem Feb 13, 2024
6056056
Might have fixed element names issue
nicktrem Feb 16, 2024
00c9011
Fixed other issue regarding element_names
nicktrem Feb 22, 2024
f633d6d
Removed old comments
nicktrem Mar 4, 2024
0d2408c
Simplified for first part of the PR
nicktrem Mar 4, 2024
4eacfa2
Merge branch 'main' into rtCapabilitiesP1Visualizer
nicktrem Mar 4, 2024
296ee38
Changed to account for python bindings
nicktrem Mar 6, 2024
d8b573b
Changed variable name
nicktrem Mar 6, 2024
ecdf600
Fixed merge
nicktrem Mar 6, 2024
059c6d6
Changed function names to make more sense
nicktrem Mar 13, 2024
a02f24e
Merge branch 'main' into rtCapabilitiesP1Visualizer
nicktrem Apr 8, 2024
621ce36
Rebased for recent changes
nicktrem Apr 8, 2024
b98edec
Removed print statements
nicktrem May 17, 2024
221711a
Removed whitespace
nicktrem May 17, 2024
49880bf
Edited to only clear plot if rt is enabled
nicktrem May 17, 2024
8cb0209
Removed prints, moved some prints to integrated console screen
nicktrem May 17, 2024
989426c
Removed unused function
nicktrem May 17, 2024
40f406b
Removed unused variables/imports
nicktrem May 17, 2024
fa4282f
Fixed bug with text logging
nicktrem May 17, 2024
0b033ba
Converted to snake_case
nicktrem May 17, 2024
f8b88fa
Added exception for if BLF is not installed
nicktrem May 17, 2024
59953ef
Fixed bug regarding naming
nicktrem May 17, 2024
ad96b95
Fixed small bug with the grid
nicktrem May 17, 2024
76b3129
Updated to support latest python binding for vectors collection
nicktrem Sep 2, 2024
767c83c
Fixed bug displaying data offline
nicktrem Sep 3, 2024
2cc7277
Added better exception handling
nicktrem Sep 25, 2024
f5b4a65
Removed unused icon
nicktrem Sep 25, 2024
be77f98
Added gui changes to the visualizer.ui
nicktrem Sep 25, 2024
5df2f88
updates to autogenerated files from qt creator
nicktrem Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions robot_log_visualizer/file_reader/signal_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
import idyntree.swig as idyn


# for real-time logging
import yarp
Copy link
Contributor

Choose a reason for hiding this comment

The 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



class TextLoggingMsg:
def __init__(self, level, text):
self.level = level
Expand All @@ -35,6 +39,13 @@ class SignalProvider(QThread):
def __init__(self, period: float):
QThread.__init__(self)

self.blfInstalled = True
try:
import bipedal_locomotion_framework.bindings as blf
self.blf = blf
except ImportError:
self.blfInstalled = False

# set device state
self._state = PeriodicThreadState.pause
self.state_lock = QMutex()
Expand Down Expand Up @@ -67,7 +78,16 @@ 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.realtime_fixed_plot_window = 20

# for networking with the real-time logger
self.realtime_network_init = False
if self.blfInstalled:
self.vector_collections_client = blf.yarp_utilities.VectorsCollectionClient()
self.trajectory_span = 200
self.rt_metadata_dict = {}

def __populate_text_logging_data(self, file_object):
data = {}
Expand Down Expand Up @@ -145,11 +165,103 @@ 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 __populate_realtime_logger_data(self, raw_data, keys, value, recent_timestamp):
if keys[0] not in raw_data:
raw_data[keys[0]] = {}

if len(keys) == 1:
raw_data[keys[0]]["data"] = np.append(raw_data[keys[0]]["data"], value).reshape(-1, len(value))
raw_data[keys[0]]["timestamps"] = np.append(raw_data[keys[0]]["timestamps"], recent_timestamp)

temp_initial_time = raw_data[keys[0]]["timestamps"][0]
temp_end_time = raw_data[keys[0]]["timestamps"][-1]
while temp_end_time - temp_initial_time > self.realtime_fixed_plot_window:
raw_data[keys[0]]["data"] = np.delete(raw_data[keys[0]]["data"], 0, axis=0)
raw_data[keys[0]]["timestamps"] = np.delete(raw_data[keys[0]]["timestamps"], 0)
temp_initial_time = raw_data[keys[0]]["timestamps"][0]
temp_end_time = raw_data[keys[0]]["timestamps"][-1]

else:
self.__populate_realtime_logger_data(raw_data[keys[0]], keys[1:], value, recent_timestamp)

def __populate_realtime_logger_metadata(self, raw_data, keys, value):
if keys[0] == "timestamps":
return
if keys[0] not in raw_data:
raw_data[keys[0]] = {}

if len(keys) == 1:
if len(value) == 0:
del raw_data[keys[0]]
return
if "elements_names" not in raw_data[keys[0]]:
raw_data[keys[0]]["elements_names"] = np.array([])
raw_data[keys[0]]["data"] = np.array([])
raw_data[keys[0]]["timestamps"] = np.array([])

raw_data[keys[0]]["elements_names"] = np.append(raw_data[keys[0]]["elements_names"], value)
else:
self.__populate_realtime_logger_metadata(raw_data[keys[0]], keys[1:], value)


def maintain_connection(self):
if not self.realtime_network_init:
yarp.Network.init()

param_handler = self.blf.parameters_handler.YarpParametersHandler()
param_handler.set_parameter_string("remote", "/rtLoggingVectorCollections") # you must have some local port as well
Copy link
Member

Choose a reason for hiding this comment

The 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.vector_collections_client.initialize(param_handler)

self.vector_collections_client.connect()
try:
self.rt_metadata_dict = self.vector_collections_client.get_metadata().vectors
except ValueError:
print("Error in retreiving the metadata from the logger")
print("Check if the logger is running and configured for realtime connection")
return False

self.realtime_network_init = True
self.joints_name = self.rt_metadata_dict["robot_realtime::description_list"]
self.robot_name = self.rt_metadata_dict["robot_realtime::yarp_robot_name"][0]
for key_string, value in self.rt_metadata_dict.items():
keys = key_string.split("::")
self.__populate_realtime_logger_metadata(self.data, keys, value)
del self.data["robot_realtime"]["description_list"]
del self.data["robot_realtime"]["yarp_robot_name"]

vc_input = self.vector_collections_client.read_data(True).vectors

if not vc_input:
return False
else:
# Update the timestamps
recent_timestamp = vc_input["robot_realtime::timestamps"][0]
self.timestamps = np.append(self.timestamps, recent_timestamp).reshape(-1)
del vc_input["robot_realtime::timestamps"]

# Keep the data within the fixed time interval
while recent_timestamp - self.timestamps[0] > self.realtime_fixed_plot_window:
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 key_string, value in vc_input.items():
keys = key_string.split("::")
self.__populate_realtime_logger_data(self.data, keys, value, recent_timestamp)

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)
Expand Down
50 changes: 35 additions & 15 deletions robot_log_visualizer/plotter/matplotlib_viewer_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -184,30 +185,43 @@ def on_pick(self, event):
blit=True,
)

def update_plots(self, paths, legends):
def update_plots(self, paths, legends, realtime_plot):
self.axes.cla()
nicktrem marked this conversation as resolved.
Show resolved Hide resolved
colorIndex = 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 realtime_plot:
(self.active_paths[path_string],) = self.axes.plot(
timestamps,
datapoints,
label=legend_string,
picker=True,
color=next(self.color_palette),
color=self.color_palette.get_color(colorIndex),
)
colorIndex = colorIndex + 1
else:
(self.active_paths[path_string],) = self.axes.plot(
timestamps,
datapoints,
label=legend_string,
picker=True,
color=self.color_palette.get_color(colorIndex),
)
colorIndex = colorIndex + 1


paths_to_be_canceled = []
for active_path in self.active_paths.keys():
Expand All @@ -220,14 +234,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 realtime_plot:
#self.axes.autoscale()
Copy link
Contributor

Choose a reason for hiding this comment

The 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.realtime_fixed_plot_window)
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(True)

if not self.frame_legend:
self.frame_legend = self.axes.legend().get_frame()
Expand Down
12 changes: 12 additions & 0 deletions robot_log_visualizer/robot_visualizer/meshcat_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,15 @@ def run(self):

if self.state == PeriodicThreadState.closed:
return

# For the real-time logger
def update_mesh_realtime(self):
self._signal_provider.index = len(self._signal_provider.timestamps) - 1
robot_state = self._signal_provider.get_robot_state_at_index(self._signal_provider.index)

self._meshcat_visualizer.set_multibody_system_state(
base_position=robot_state["base_position"],
base_rotation=robot_state["base_orientation"],
joint_value=robot_state["joints_position"][self.model_joints_index],
model_name="robot",
)
5 changes: 3 additions & 2 deletions robot_log_visualizer/ui/autogenerated/about.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

# Form implementation generated from reading ui file 'robot_log_visualizer/ui/misc/about.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets
Expand Down
5 changes: 3 additions & 2 deletions robot_log_visualizer/ui/autogenerated/plot_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

# Form implementation generated from reading ui file 'robot_log_visualizer/ui/misc/plot_tab.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets
Expand Down
9 changes: 5 additions & 4 deletions robot_log_visualizer/ui/autogenerated/set_robot_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

# Form implementation generated from reading ui file 'robot_log_visualizer/ui/misc/set_robot_model.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets
Expand Down Expand Up @@ -61,8 +62,8 @@ def setupUi(self, setRobotModelDialog):
self.gridLayout.addItem(spacerItem, 2, 0, 1, 1)

self.retranslateUi(setRobotModelDialog)
self.buttonBox.accepted.connect(setRobotModelDialog.accept)
self.buttonBox.rejected.connect(setRobotModelDialog.reject)
self.buttonBox.accepted.connect(setRobotModelDialog.accept) # type: ignore
self.buttonBox.rejected.connect(setRobotModelDialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(setRobotModelDialog)

def retranslateUi(self, setRobotModelDialog):
Expand Down
5 changes: 3 additions & 2 deletions robot_log_visualizer/ui/autogenerated/video_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

# Form implementation generated from reading ui file 'robot_log_visualizer/ui/misc/video_tab.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets
Expand Down
10 changes: 8 additions & 2 deletions robot_log_visualizer/ui/autogenerated/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

# Form implementation generated from reading ui file 'robot_log_visualizer/ui/misc/visualizer.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING! All changes made in this file will be lost!
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets
Expand Down Expand Up @@ -247,7 +248,10 @@ def setupUi(self, MainWindow):
self.actionAbout.setObjectName("actionAbout")
self.actionSet_Robot_Model = QtWidgets.QAction(MainWindow)
self.actionSet_Robot_Model.setObjectName("actionSet_Robot_Model")
self.actionRealtime_Connect = QtWidgets.QAction(MainWindow)
self.actionRealtime_Connect.setObjectName("actionRealtime_Connect")
self.menuFile.addAction(self.actionOpen)
self.menuFile.addAction(self.actionRealtime_Connect)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionQuit)
self.menuHelp.addAction(self.actionAbout)
Expand Down Expand Up @@ -278,4 +282,6 @@ def retranslateUi(self, MainWindow):
self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O"))
self.actionAbout.setText(_translate("MainWindow", "About"))
self.actionSet_Robot_Model.setText(_translate("MainWindow", "Set Robot Model"))
self.actionRealtime_Connect.setText(_translate("MainWindow", "Realtime Connect"))
self.actionRealtime_Connect.setShortcut(_translate("MainWindow", "Ctrl+R"))
from PyQt5 import QtWebEngineWidgets
Loading
Loading