From b46676b6de612c79303844e9eed98595887cd5c9 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 26 Dec 2023 16:13:59 +0100 Subject: [PATCH 01/75] Added the basics of the connect button in the menu --- robot_log_visualizer/ui/autogenerated/visualizer.py | 9 +++++++++ robot_log_visualizer/ui/gui.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/robot_log_visualizer/ui/autogenerated/visualizer.py b/robot_log_visualizer/ui/autogenerated/visualizer.py index 787b153..070e54a 100644 --- a/robot_log_visualizer/ui/autogenerated/visualizer.py +++ b/robot_log_visualizer/ui/autogenerated/visualizer.py @@ -238,15 +238,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 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") + 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) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index a581b4f..3f6c66f 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -177,6 +177,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): # connect action self.ui.actionQuit.triggered.connect(self.close) self.ui.actionOpen.triggered.connect(self.open_mat_file) + self.ui.actionConnect.triggered.connect(self.connect_realtime_logger) self.ui.actionAbout.triggered.connect(self.open_about) self.ui.actionSet_Robot_Model.triggered.connect(self.open_set_robot_model) @@ -636,6 +637,9 @@ def open_mat_file(self): if file_name: self.__load_mat_file(file_name) + def connect_realtime_logger(self): + print("Now connecting for real-time logging") + def open_about(self): self.about.show() From 6f1ec87dec235ee4540d403aa10a7300fb6d2f5e Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 29 Dec 2023 14:00:39 +0100 Subject: [PATCH 02/75] Added dummy data to test formatting and gui representation --- .../file_reader/signal_provider.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index d7ba996..328fc49 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -138,6 +138,39 @@ def __populate_numerical_data(self, file_object): return data + # TODO: + # Make a dummy self.data which populates with data you choose to + # understand how the plot works + # Then you can understand how to send the data and then serialize it + # Once done, try appending data to self.data periodically + # To symbolize how data would work coming in + # After send the data from the logger and visualize it right there + def establish_connection(self): + self.t = {'x': 1} + key = 'l_arm_ft' + self.data = {'robot_logger_device': {'FTs': {'l_arm_ft': {'data': np.array([[16.04658911], [8.32923841], [41.25904926]]), 'timestamps': np.array([1.70194892e+09, 1.70194893e+09, 1.70194894e+09,]), 'elements_names': np.array(['f_x'])} } } } + """ if self.data[key]["timestamps"][0] < self.initial_time: + self.timestamps = self.data[key]["timestamps"] + self.initial_time = self.timestamps[0] + + if self.data[key]["timestamps"][-1] > self.end_time: + self.timestamps = self.data[key]["timestamps"] + self.end_time = self.timestamps[-1] + + + """ + if self.data['robot_logger_device']['FTs'][key]["timestamps"][0] < self.initial_time: + self.timestamps = self.data['robot_logger_device']['FTs'][key]["timestamps"] + self.initial_time = self.timestamps[0] + + if self.data['robot_logger_device']['FTs'][key]["timestamps"][-1] > self.end_time: + self.timestamps = self.data['robot_logger_device']['FTs'][key]["timestamps"] + self.end_time = self.timestamps[-1] + +# self.data = self.__populate_numerical_data(self.data) + print("Data from connection") + print(self.data) + def open_mat_file(self, file_name: str): with h5py.File(file_name, "r") as file: root_variable = file.get(self.root_name) From cfd0ede3f97cd441573acb3c78f4bdf89e4c0598 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 29 Dec 2023 14:01:05 +0100 Subject: [PATCH 03/75] Added a real-time logger button to the GUI --- robot_log_visualizer/ui/gui.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 3f6c66f..0c2d130 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -639,6 +639,46 @@ def open_mat_file(self): def connect_realtime_logger(self): print("Now connecting for real-time logging") + self.signal_provider.establish_connection() + root = list(self.signal_provider.data.keys())[0] + root_item = QTreeWidgetItem([root]) + root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) + items = self.__populate_variable_tree_widget( + self.signal_provider.data[root], root_item + ) + self.ui.variableTreeWidget.insertTopLevelItems(0, [items]) + + # populate text logging tree + if self.signal_provider.text_logging_data: + root = list(self.signal_provider.text_logging_data.keys())[0] + root_item = QTreeWidgetItem([root]) + root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) + items = self.__populate_text_logging_tree_widget( + self.signal_provider.text_logging_data[root], root_item + ) + self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) + + # spawn the console + self.pyconsole.push_local_ns("data", self.signal_provider.data) + + self.ui.timeSlider.setMaximum(self.signal_size) + self.ui.startButton.setEnabled(True) + self.ui.timeSlider.setEnabled(True) + # for now will hard code the device name to: /testLoggerOutput + """ + import yarp + yarp.Network.init() + loggingInput = yarp.BufferedPortBottle() + loggingInput.open("/visualizerInput") + yarp.Network.connect("/testLoggerOutput", "/visualizerInput") + success = loggingInput.read() + if not success: + print("Failed") + else: + print("Success") + yarp.Network.fini() + """ + def open_about(self): self.about.show() From f20c65582ec1152139c579efb2e42b3797d756ec Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 29 Dec 2023 14:06:59 +0100 Subject: [PATCH 04/75] Added another test data set --- robot_log_visualizer/file_reader/signal_provider.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 328fc49..3af1eec 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -148,7 +148,10 @@ def __populate_numerical_data(self, file_object): def establish_connection(self): self.t = {'x': 1} key = 'l_arm_ft' - self.data = {'robot_logger_device': {'FTs': {'l_arm_ft': {'data': np.array([[16.04658911], [8.32923841], [41.25904926]]), 'timestamps': np.array([1.70194892e+09, 1.70194893e+09, 1.70194894e+09,]), 'elements_names': np.array(['f_x'])} } } } + self.data = {'robot_logger_device': + {'FTs': + {'l_arm_ft': + {'data': np.array([[16.04658911, 0.0], [8.32923841, 5.0], [41.25904926, 10.0]]), 'timestamps': np.array([1.70194892e+09, 1.70194893e+09, 1.70194894e+09,]), 'elements_names': np.array(['f_x', 'f_y'])}} } } """ if self.data[key]["timestamps"][0] < self.initial_time: self.timestamps = self.data[key]["timestamps"] self.initial_time = self.timestamps[0] From 5b700c5342cd2d629fccd126771f6e7189d0518f Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 31 Dec 2023 10:02:05 +0100 Subject: [PATCH 05/75] initial test of reading form YARP port passed --- .../file_reader/signal_provider.py | 87 ++++++++---- .../plotter/matplotlib_viewer_canvas.py | 31 +++-- robot_log_visualizer/ui/gui.py | 129 +++++++++++++++++- robot_log_visualizer/ui/plot_item.py | 6 + 4 files changed, 210 insertions(+), 43 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 3af1eec..144fd2a 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -9,6 +9,10 @@ from PyQt5.QtCore import pyqtSignal, QThread, QMutex, QMutexLocker from robot_log_visualizer.utils.utils import PeriodicThreadState +# for real-time logging +import yarp +import json + class TextLoggingMsg: def __init__(self, level, text): @@ -57,6 +61,9 @@ def __init__(self, period: float): self._current_time = 0 + # for networking with the real-time logger + self.networkInit = False + def __populate_text_logging_data(self, file_object): data = {} for key, value in file_object.items(): @@ -109,8 +116,10 @@ def __populate_numerical_data(self, file_object): if not isinstance(value, h5py._hl.group.Group): continue if key == "#refs#": + print("Skipping for refs") continue if key == "log": + print("Skipping for log") continue if "data" in value.keys(): data[key] = {} @@ -133,6 +142,7 @@ 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) @@ -146,38 +156,59 @@ def __populate_numerical_data(self, file_object): # To symbolize how data would work coming in # After send the data from the logger and visualize it right there def establish_connection(self): - self.t = {'x': 1} - key = 'l_arm_ft' - self.data = {'robot_logger_device': - {'FTs': - {'l_arm_ft': - {'data': np.array([[16.04658911, 0.0], [8.32923841, 5.0], [41.25904926, 10.0]]), 'timestamps': np.array([1.70194892e+09, 1.70194893e+09, 1.70194894e+09,]), 'elements_names': np.array(['f_x', 'f_y'])}} } } - """ if self.data[key]["timestamps"][0] < self.initial_time: - self.timestamps = self.data[key]["timestamps"] - self.initial_time = self.timestamps[0] - - if self.data[key]["timestamps"][-1] > self.end_time: - self.timestamps = self.data[key]["timestamps"] - self.end_time = self.timestamps[-1] - - - """ - if self.data['robot_logger_device']['FTs'][key]["timestamps"][0] < self.initial_time: - self.timestamps = self.data['robot_logger_device']['FTs'][key]["timestamps"] - self.initial_time = self.timestamps[0] - - if self.data['robot_logger_device']['FTs'][key]["timestamps"][-1] > self.end_time: - self.timestamps = self.data['robot_logger_device']['FTs'][key]["timestamps"] - self.end_time = self.timestamps[-1] - -# self.data = self.__populate_numerical_data(self.data) - print("Data from connection") - print(self.data) + key = "l_arm_ft" + if not self.networkInit: + yarp.Network.init() + self.loggingInput = yarp.BufferedPortBottle() + self.loggingInput.open("/visualizerInput") + yarp.Network.connect("/testLoggerOutput", "/visualizerInput") + self.data = {'robot_realtime': {'FTs': {key: {'data': np.array([np.array([])]), 'timestamps': np.array([])}}}} + self.networkInit = True + success = self.loggingInput.read() + if not success: + print("Failed") + else: + rawInput = str(success.toString()) + # json.loads is done twice, the 1st time is to remove \\ character + # the 2nd time actually converts the string to the dictionary + input = json.loads(json.loads(rawInput)) + + self.data['robot_realtime']['FTs'][key]["data"] = np.append(self.data['robot_realtime']['FTs'][key]["data"], np.array(input['robot_realtime']['FTs'][key]["data"])).reshape(-1,1) + self.data['robot_realtime']['FTs'][key]["timestamps"] = np.append(self.data['robot_realtime']['FTs'][key]["timestamps"], input['robot_realtime']['FTs'][key]["timestamps"]) + print(self.data) + # if (len(self.data['robot_realtime']['FTs'][key]["data"])) + + + """ + self.t = {'x': 1} + key = 'l_arm_ft' + self.data = {'robot_logger_device': + {'FTs': + {'l_arm_ft': + {'data': np.array([[16.04658911, 0.0], [8.32923841, 5.0], [41.25904926, 10.0]]), 'timestamps': np.array([1.70194892e+09, 1.70194893e+09, 1.70194894e+09,]), 'elements_names': np.array(['f_x', 'f_y'])}} } } + """ + + if self.data['robot_realtime']['FTs'][key]["timestamps"][0] < self.initial_time: + self.timestamps = self.data['robot_realtime']['FTs'][key]["timestamps"] + self.initial_time = self.timestamps[0] + + if self.data['robot_realtime']['FTs'][key]["timestamps"][-1] > self.end_time: + self.timestamps = self.data['robot_realtime']['FTs'][key]["timestamps"] + self.end_time = self.timestamps[-1] + def open_mat_file(self, file_name: str): with h5py.File(file_name, "r") as file: + # print("mat file items") + # print(file.items()) root_variable = file.get(self.root_name) self.data = self.__populate_numerical_data(file) + # print("Root Variable:") + # print(root_variable) + # print("MAT file keys:") + # print(file.keys()) + # print("Root name keys:") + # print(root_variable.keys()) if "log" in root_variable.keys(): self.text_logging_data["log"] = self.__populate_text_logging_data( @@ -200,6 +231,8 @@ def open_mat_file(self, file_name: str): except: pass self.index = 0 + print("Data:") + print(self.data) def __len__(self): return self.timestamps.shape[0] diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 92a385a..d1a8213 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -69,21 +69,22 @@ def update_plots(self, 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"][:] - - timestamps = data["timestamps"] - self.signal_provider.initial_time - - (self.active_paths[path_string],) = self.axes.plot( - timestamps, datapoints, label=legend_string - ) + #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"][:] + + timestamps = data["timestamps"] - self.signal_provider.initial_time + + self.axes.cla() + (self.active_paths[path_string],) = self.axes.plot( + timestamps, datapoints, label=legend_string + ) paths_to_be_canceled = [] for active_path in self.active_paths.keys(): diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 0c2d130..7973b6d 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -3,6 +3,7 @@ # Released under the terms of the BSD 3-Clause License # PyQt5 +import threading from PyQt5 import QtWidgets, QtGui from PyQt5.QtCore import QUrl from PyQt5.QtCore import pyqtSlot, Qt, QMutex, QMutexLocker @@ -43,6 +44,8 @@ from pyqtconsole.console import PythonConsole import pyqtconsole.highlighter as hl +import time + class SetRobotModelDialog(QtWidgets.QDialog): def __init__( @@ -115,12 +118,50 @@ def get_icon(icon_name): ) return icon +class NetworkThread(threading.Thread): + def __init__(self, thread_name, thread_ID, data): + threading.Thread.__init__(self) + self.thread_name = thread_name + self.thread_ID = thread_ID + self.data = data + + # helper function to execute the threads + def run(self): + print("Now running network thread") + import yarp + yarp.Network.init() + loggingInput = yarp.BufferedPortBottle() + loggingInput.open("/visualizerInput") + yarp.Network.connect("/testLoggerOutput", "/visualizerInput") + success = loggingInput.read() + if not success: + print("Failed") + else: + import json + while True: + output = str(success.toString()) + # json.loads is done twice, the 1st time is to remove \\ character + # the 2nd time actually converts the string to the dictionary + dicOutput = json.loads(json.loads(output)) + print("Output Type:") + print(type(dicOutput)) + print("Result dictionary:") + print(dicOutput) + print(dicOutput["robot_realtime"]["FTs"]) + yarp.Network.fini() + print("Thread completed") + + class RobotViewerMainWindow(QtWidgets.QMainWindow): def __init__(self, signal_provider, meshcat_provider, animation_period): # call QMainWindow constructor super().__init__() + # for realtime logging + self.realtimeLoggerActive = False + self.realtimePlotUpdaterThreadActive = False + self.animation_period = animation_period # set up the user interface @@ -224,6 +265,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): self.ui.pythonWidgetLayout.addWidget(self.pyconsole) self.pyconsole.eval_in_thread() + # self.media_player = QMediaPlayer(None, QMediaPlayer.VideoSurface) # self.media_player.setVideoOutput(self.ui.webcamView) # self.media_loaded = False @@ -288,6 +330,12 @@ def keyPressEvent(self, event): self.ui.timeSlider.setValue(new_index) self.slider_pressed = False + def realtimeUpdatePlots(self): + while True: + print("In plotter thread") + for plot in self.plot_items: + plot.updatePlotItem(signal_provider=self.signal_provider, period=self.animation_period) + def toolButton_on_click(self): self.plot_items.append( PlotItem(signal_provider=self.signal_provider, period=self.animation_period) @@ -299,6 +347,13 @@ def toolButton_on_click(self): else: self.ui.tabPlotWidget.setTabsClosable(True) + """if self.realtimeLoggerActive and not self.realtimePlotUpdaterThreadActive: + print("Now launching plotter thread") + self.plotUpdater = threading.Thread(target=self.realtimeUpdatePlots) + self.plotUpdater.start() + self.realtimePlotUpdaterThreadActive = True + """ + def timeSlider_on_pressed(self): self.slider_pressed = True @@ -390,9 +445,15 @@ def variableTreeWidget_on_click(self): paths.append(path) legends.append(legend) + print("Adding item to plot") + print("Paths:") + print(paths) + print("Legends:") + print(legends) self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( paths, legends ) + print(self.plot_items) def find_text_log_index(self, path): current_time = self.signal_provider.current_time @@ -637,9 +698,70 @@ def open_mat_file(self): if file_name: self.__load_mat_file(file_name) + def maintain_connection(self): + root = None + initConnection = False + plotCounter = 10 + init = False + counter = 0 + while True: + self.signal_provider.establish_connection() + # only display one root in the gui + if not initConnection: + root = list(self.signal_provider.data.keys())[0] + root_item = QTreeWidgetItem([root]) + root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) + initConnection = True + items = self.__populate_variable_tree_widget( + self.signal_provider.data[root], root_item + ) + self.ui.variableTreeWidget.insertTopLevelItems(0, [items]) + + # populate text logging tree + if self.signal_provider.text_logging_data: + root = list(self.signal_provider.text_logging_data.keys())[0] + root_item = QTreeWidgetItem([root]) + root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) + items = self.__populate_text_logging_tree_widget( + self.signal_provider.text_logging_data[root], root_item + ) + self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) + + # spawn the console + self.pyconsole.push_local_ns("data", self.signal_provider.data) + + self.ui.timeSlider.setMaximum(self.signal_size) + self.ui.startButton.setEnabled(True) + self.ui.timeSlider.setEnabled(True) + + + if counter == plotCounter: + # self.plotTabBar_currentChanged(0) + # if not init: + self.plot_items[0].canvas.update_plots( + np.array([['robot_realtime', 'FTs', 'l_arm_ft', '0']]), np.array([['robot_realtime', 'FTs', 'l_arm_ft', 'Element 0']]) + ) + # init = True + #self.plot_items[0].canvas.draw() + #self.plotTabBar_currentChanged(0) + print("Size of plot items") + print(len(self.plot_items)) + #self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.show() + + counter = 0 + time.sleep(0.01) + counter = counter + 1 + + #for plot in self.plot_items: + # plot.updatePlotItem(signal_provider=self.signal_provider, period=self.animation_period) + def connect_realtime_logger(self): + self.realtimeLoggerActive = True print("Now connecting for real-time logging") - self.signal_provider.establish_connection() + self.networkThread = threading.Thread(target=self.maintain_connection) + self.networkThread.start() + + """self.signal_provider.establish_connection() root = list(self.signal_provider.data.keys())[0] root_item = QTreeWidgetItem([root]) root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) @@ -665,6 +787,11 @@ def connect_realtime_logger(self): self.ui.startButton.setEnabled(True) self.ui.timeSlider.setEnabled(True) # for now will hard code the device name to: /testLoggerOutput + + connectionThread = NetworkThread("NetworkThread", 1, self.signal_provider.data) + connectionThread.start() + # connectionThread.join() + """ """ import yarp yarp.Network.init() diff --git a/robot_log_visualizer/ui/plot_item.py b/robot_log_visualizer/ui/plot_item.py index b9ada3e..d4eda1a 100644 --- a/robot_log_visualizer/ui/plot_item.py +++ b/robot_log_visualizer/ui/plot_item.py @@ -18,3 +18,9 @@ def __init__(self, signal_provider, period): parent=self, period=period, signal_provider=signal_provider ) self.ui.plotLayout.addWidget(self.canvas) + + def updatePlotItem(self, signal_provider, period): + self.canvas = MatplotlibViewerCanvas( + parent=self, period=period, signal_provider=signal_provider + ) + self.ui.plotLayout.addWidget(self.canvas) From f8153d4ca40426373599dd1289d5e2961b36a4c8 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 2 Jan 2024 09:24:12 +0100 Subject: [PATCH 06/75] Added more plot support for real-time data --- .../file_reader/signal_provider.py | 81 +++++++++++++++++-- .../plotter/matplotlib_viewer_canvas.py | 2 +- robot_log_visualizer/ui/gui.py | 31 +++++-- 3 files changed, 100 insertions(+), 14 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 144fd2a..d992ef3 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -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 @@ -12,6 +13,7 @@ # for real-time logging import yarp import json +import mergedeep class TextLoggingMsg: @@ -148,6 +150,40 @@ def __populate_numerical_data(self, file_object): return data + def convertToNP(self, rawData, input): + data = {} + for key, value in input.items(): + #print() + #print(input.items()) + #print() + if "data" in value.keys() and "timestamps" in value.keys(): + data[key] = {} + rawData[key]["data"] = np.append(rawData[key]["data"], np.array(value["data"])).reshape(-1, 6) + rawData[key]["timestamps"] = np.append(rawData[key]["timestamps"], np.array(value["timestamps"]))#.reshape(-1,1) + + if rawData[key]["timestamps"][0] < self.initial_time: + self.timestamps = rawData[key]["timestamps"] + self.initial_time = self.timestamps[0] + + if rawData[key]["timestamps"][-1] > self.end_time: + self.timestamps = rawData[key]["timestamps"] + self.end_time = self.timestamps[-1] + + if "elements_names" in value.keys(): + #elements_names_ref = value["elements_names"] + #data[key]["elements_names"] = [ + # "".join(chr(c[0]) for c in value[ref]) + # for ref in elements_names_ref[0] + #] + rawData[key]["elements_names"] = value["elements_names"] + + + else: + data[key] = self.convertToNP(rawData=rawData[key],input=value) + + return data + + # TODO: # Make a dummy self.data which populates with data you choose to # understand how the plot works @@ -157,24 +193,54 @@ def __populate_numerical_data(self, file_object): # After send the data from the logger and visualize it right there def establish_connection(self): key = "l_arm_ft" + self.initalFrame = True if not self.networkInit: yarp.Network.init() self.loggingInput = yarp.BufferedPortBottle() self.loggingInput.open("/visualizerInput") yarp.Network.connect("/testLoggerOutput", "/visualizerInput") - self.data = {'robot_realtime': {'FTs': {key: {'data': np.array([np.array([])]), 'timestamps': np.array([])}}}} + self.data = {'robot_realtime': {'FTs': + {'l_arm_ft': + {'data': np.array([np.array([])]), 'timestamps': np.array([])}, + 'r_arm_ft': + {'data': np.array([np.array([])]), 'timestamps': np.array([])}, + 'l_jet_ft': + {'data': np.array([np.array([])]), 'timestamps': np.array([])}, + 'r_jet_ft': + {'data': np.array([np.array([])]), 'timestamps': np.array([])}, + 'l_foot_front_ft': + {'data': np.array([np.array([])]), 'timestamps': np.array([])}, + 'r_foot_front_ft': + {'data': np.array([np.array([])]), 'timestamps': np.array([])}, + 'l_foot_rear_ft': + {'data': np.array([np.array([])]), 'timestamps': np.array([])}, + 'r_foot_rear_ft': + {'data': np.array([np.array([])]), 'timestamps': np.array([])}, + }}} + #self.data = {'robot_realtime': {}} self.networkInit = True success = self.loggingInput.read() if not success: - print("Failed") + print("Failed to read from YARP port") + sys.exit(1) else: rawInput = str(success.toString()) # json.loads is done twice, the 1st time is to remove \\ character # the 2nd time actually converts the string to the dictionary input = json.loads(json.loads(rawInput)) - - self.data['robot_realtime']['FTs'][key]["data"] = np.append(self.data['robot_realtime']['FTs'][key]["data"], np.array(input['robot_realtime']['FTs'][key]["data"])).reshape(-1,1) - self.data['robot_realtime']['FTs'][key]["timestamps"] = np.append(self.data['robot_realtime']['FTs'][key]["timestamps"], input['robot_realtime']['FTs'][key]["timestamps"]) + #print("Raw input Received:") + #print(input) + + + self.convertToNP(self.data, input) + # print("Data before") + # print(self.data) + # print("Input") + # print(input) + # mergedeep.merge(self.data, input, strategy=mergedeep.Strategy.ADDITIVE) + # self.data['robot_realtime']['FTs'][key]["data"] = np.append(self.data['robot_realtime']['FTs'][key]["data"], np.array(input['robot_realtime']['FTs'][key]["data"])).reshape(-1,1) + # self.data['robot_realtime']['FTs'][key]["timestamps"] = np.append(self.data['robot_realtime']['FTs'][key]["timestamps"], input['robot_realtime']['FTs'][key]["timestamps"]) + print("Data after") print(self.data) # if (len(self.data['robot_realtime']['FTs'][key]["data"])) @@ -188,14 +254,15 @@ def establish_connection(self): {'data': np.array([[16.04658911, 0.0], [8.32923841, 5.0], [41.25904926, 10.0]]), 'timestamps': np.array([1.70194892e+09, 1.70194893e+09, 1.70194894e+09,]), 'elements_names': np.array(['f_x', 'f_y'])}} } } """ - if self.data['robot_realtime']['FTs'][key]["timestamps"][0] < self.initial_time: + """if self.data['robot_realtime']['FTs'][key]["timestamps"][0] < self.initial_time: self.timestamps = self.data['robot_realtime']['FTs'][key]["timestamps"] self.initial_time = self.timestamps[0] if self.data['robot_realtime']['FTs'][key]["timestamps"][-1] > self.end_time: self.timestamps = self.data['robot_realtime']['FTs'][key]["timestamps"] self.end_time = self.timestamps[-1] - + """ + def open_mat_file(self, file_name: str): with h5py.File(file_name, "r") as file: diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index d1a8213..4341f42 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -128,4 +128,4 @@ def update_vertical_line(self, _): # Draw vertical line at current index self.vertical_line.set_data([current_time, current_time], self.axes.get_ylim()) - return self.vertical_line, *(self.active_paths.values()) + return self.vertical_line, *(self.active_paths.values()) \ No newline at end of file diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 7973b6d..a780d80 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -68,6 +68,7 @@ def __init__( self.ui.robotModelToolButton.clicked.connect(self.open_urdf_file) self.ui.packageDirToolButton.clicked.connect(self.open_package_directory) + def open_urdf_file(self): file_name, _ = QFileDialog.getOpenFileName( self, "Open urdf file", ".", filter="*.urdf" @@ -161,6 +162,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): # for realtime logging self.realtimeLoggerActive = False self.realtimePlotUpdaterThreadActive = False + self.plotData = {} self.animation_period = animation_period @@ -414,6 +416,7 @@ def pauseButton_on_click(self): def plotTabCloseButton_on_click(self, index): self.ui.tabPlotWidget.removeTab(index) self.plot_items[index].canvas.quit_animation() + del self.plotData[index] del self.plot_items[index] if self.ui.tabPlotWidget.count() == 1: @@ -450,6 +453,7 @@ def variableTreeWidget_on_click(self): print(paths) print("Legends:") print(legends) + self.plotData[self.ui.tabPlotWidget.currentIndex()] = {"paths": paths, "legends": legends} self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( paths, legends ) @@ -582,8 +586,11 @@ def __populate_variable_tree_widget(self, obj, parent) -> QTreeWidgetItem: n_cols = 1 # In yarp telemetry v0.4.0 the elements_names was saved. + print("About to check element names") if "elements_names" in obj.keys(): + print("There is an element name") for name in obj["elements_names"]: + print(name) item = QTreeWidgetItem([name]) parent.addChild(item) else: @@ -738,17 +745,29 @@ def maintain_connection(self): if counter == plotCounter: # self.plotTabBar_currentChanged(0) # if not init: - self.plot_items[0].canvas.update_plots( - np.array([['robot_realtime', 'FTs', 'l_arm_ft', '0']]), np.array([['robot_realtime', 'FTs', 'l_arm_ft', 'Element 0']]) - ) + #self.plotData[self.ui.tabPlotWidget.currentIndex()] + if len(self.plotData) > 0 and len(self.plotData) > self.ui.tabPlotWidget.currentIndex(): + print("Length of plot_items: " + str(len(self.plot_items))) + print(self.plot_items) + self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( + self.plotData[self.ui.tabPlotWidget.currentIndex()]["paths"], + self.plotData[self.ui.tabPlotWidget.currentIndex()]["legends"] + ) + else: + print("Current index: " + str(self.ui.tabPlotWidget.currentIndex())) + counter = 0 + #self.plot_items[0].canvas.update_plots( + # np.array([['robot_realtime', 'FTs', 'l_arm_ft', '0']]), np.array([['robot_realtime', 'FTs', 'l_arm_ft', 'Element 0']]) + #) + #self.plot_items[0].canvas.refresh_plot() # init = True #self.plot_items[0].canvas.draw() #self.plotTabBar_currentChanged(0) - print("Size of plot items") - print(len(self.plot_items)) + # print(self.plot_items[0].canvas.refresh_plot()) + # print("Size of plot items") + # print(len(self.plot_items)) #self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.show() - counter = 0 time.sleep(0.01) counter = counter + 1 From a716e59907f3f7ff5b6efbc5cdbd069317b0ec5b Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 2 Jan 2024 09:31:52 +0100 Subject: [PATCH 07/75] Fixed small bug in plotting regarding mutiple plots at the same time --- robot_log_visualizer/plotter/matplotlib_viewer_canvas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 4341f42..831c1f1 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -65,6 +65,7 @@ def quit_animation(self): self.vertical_line_anim._stop() def update_plots(self, paths, legends): + self.axes.cla() for path, legend in zip(paths, legends): path_string = "/".join(path) legend_string = "/".join(legend[1:]) @@ -81,7 +82,6 @@ def update_plots(self, paths, legends): timestamps = data["timestamps"] - self.signal_provider.initial_time - self.axes.cla() (self.active_paths[path_string],) = self.axes.plot( timestamps, datapoints, label=legend_string ) From ff33dc2f7cf94cdfe7a835c4ed93a400352cc1f8 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 2 Jan 2024 10:28:11 +0100 Subject: [PATCH 08/75] Fixed race condition with graphs and bug with plot indexes --- robot_log_visualizer/ui/gui.py | 43 ++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index a780d80..fecf9a7 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -163,6 +163,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): self.realtimeLoggerActive = False self.realtimePlotUpdaterThreadActive = False self.plotData = {} + self.plottingLock = threading.Lock() self.animation_period = animation_period @@ -414,13 +415,24 @@ def pauseButton_on_click(self): self.logger.write_to_log("Dataset paused.") def plotTabCloseButton_on_click(self, index): + print("About to acquire the lock for closing a plot") + self.plottingLock.acquire() + print("Lock acquired for closing the plot") self.ui.tabPlotWidget.removeTab(index) self.plot_items[index].canvas.quit_animation() - del self.plotData[index] + # Update the indexes of plotData before deletion + print("Index: " + str(index)) + print("Length of keys: " + str(len(self.plotData.keys()))) + for i in range(index, len(self.plotData.keys()) - 1): + self.plotData[i] = self.plotData[i + 1] + # Remove the last key + del self.plotData[list(self.plotData.keys())[-1]] del self.plot_items[index] if self.ui.tabPlotWidget.count() == 1: self.ui.tabPlotWidget.setTabsClosable(False) + self.plottingLock.release() + print("Lock released for closing the plot") def plotTabBar_on_doubleClick(self, index): dlg, plot_title = build_plot_title_box_dialog() @@ -448,16 +460,16 @@ def variableTreeWidget_on_click(self): paths.append(path) legends.append(legend) - print("Adding item to plot") - print("Paths:") - print(paths) - print("Legends:") - print(legends) + #print("Adding item to plot") + #print("Paths:") + #print(paths) + #print("Legends:") + #print(legends) self.plotData[self.ui.tabPlotWidget.currentIndex()] = {"paths": paths, "legends": legends} self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( paths, legends ) - print(self.plot_items) + #print(self.plot_items) def find_text_log_index(self, path): current_time = self.signal_provider.current_time @@ -586,9 +598,9 @@ def __populate_variable_tree_widget(self, obj, parent) -> QTreeWidgetItem: n_cols = 1 # In yarp telemetry v0.4.0 the elements_names was saved. - print("About to check element names") + #print("About to check element names") if "elements_names" in obj.keys(): - print("There is an element name") + #print("There is an element name") for name in obj["elements_names"]: print(name) item = QTreeWidgetItem([name]) @@ -746,16 +758,21 @@ def maintain_connection(self): # self.plotTabBar_currentChanged(0) # if not init: #self.plotData[self.ui.tabPlotWidget.currentIndex()] + #print("About to aquire lock for updating the plot") + self.plottingLock.acquire() + #print("Lock aquired for updating the plot") if len(self.plotData) > 0 and len(self.plotData) > self.ui.tabPlotWidget.currentIndex(): - print("Length of plot_items: " + str(len(self.plot_items))) - print(self.plot_items) + #print("Length of plot_items: " + str(len(self.plot_items))) + #print(self.plotData) self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( self.plotData[self.ui.tabPlotWidget.currentIndex()]["paths"], self.plotData[self.ui.tabPlotWidget.currentIndex()]["legends"] ) - else: - print("Current index: " + str(self.ui.tabPlotWidget.currentIndex())) + #else: + #print("Current index: " + str(self.ui.tabPlotWidget.currentIndex())) counter = 0 + self.plottingLock.release() + #print("Lock released for updating the plot") #self.plot_items[0].canvas.update_plots( # np.array([['robot_realtime', 'FTs', 'l_arm_ft', '0']]), np.array([['robot_realtime', 'FTs', 'l_arm_ft', 'Element 0']]) #) From 2de1dcdfd07eb594641a090086a9b378ee12c931 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 2 Jan 2024 11:58:55 +0100 Subject: [PATCH 09/75] Removed the need to pre-inform the logger about the data coming in --- robot_log_visualizer/file_reader/signal_provider.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index d992ef3..483223e 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -153,6 +153,8 @@ def __populate_numerical_data(self, file_object): def convertToNP(self, rawData, input): data = {} for key, value in input.items(): + if key not in rawData.keys(): + rawData[key] = value #print() #print(input.items()) #print() @@ -199,7 +201,7 @@ def establish_connection(self): self.loggingInput = yarp.BufferedPortBottle() self.loggingInput.open("/visualizerInput") yarp.Network.connect("/testLoggerOutput", "/visualizerInput") - self.data = {'robot_realtime': {'FTs': + """self.data = {'robot_realtime': {'FTs': {'l_arm_ft': {'data': np.array([np.array([])]), 'timestamps': np.array([])}, 'r_arm_ft': @@ -217,6 +219,8 @@ def establish_connection(self): 'r_foot_rear_ft': {'data': np.array([np.array([])]), 'timestamps': np.array([])}, }}} + """ + #self.data = {'robot_realtime': {}} self.networkInit = True success = self.loggingInput.read() From 6a3840d59f77a6ac5b8c943141096fa15bc31dc4 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 2 Jan 2024 11:59:11 +0100 Subject: [PATCH 10/75] Fixed race condition bug --- robot_log_visualizer/ui/gui.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index fecf9a7..ac62e80 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -736,30 +736,32 @@ def maintain_connection(self): ) self.ui.variableTreeWidget.insertTopLevelItems(0, [items]) + # populate text logging tree - if self.signal_provider.text_logging_data: - root = list(self.signal_provider.text_logging_data.keys())[0] - root_item = QTreeWidgetItem([root]) - root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) - items = self.__populate_text_logging_tree_widget( - self.signal_provider.text_logging_data[root], root_item - ) - self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) - # spawn the console - self.pyconsole.push_local_ns("data", self.signal_provider.data) - self.ui.timeSlider.setMaximum(self.signal_size) - self.ui.startButton.setEnabled(True) - self.ui.timeSlider.setEnabled(True) + if counter == plotCounter: + self.plottingLock.acquire() + if self.signal_provider.text_logging_data: + root = list(self.signal_provider.text_logging_data.keys())[0] + root_item = QTreeWidgetItem([root]) + root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) + items = self.__populate_text_logging_tree_widget( + self.signal_provider.text_logging_data[root], root_item + ) + self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) + + # spawn the console + self.pyconsole.push_local_ns("data", self.signal_provider.data) + self.ui.timeSlider.setMaximum(self.signal_size) + self.ui.startButton.setEnabled(True) + self.ui.timeSlider.setEnabled(True) - if counter == plotCounter: # self.plotTabBar_currentChanged(0) # if not init: #self.plotData[self.ui.tabPlotWidget.currentIndex()] #print("About to aquire lock for updating the plot") - self.plottingLock.acquire() #print("Lock aquired for updating the plot") if len(self.plotData) > 0 and len(self.plotData) > self.ui.tabPlotWidget.currentIndex(): #print("Length of plot_items: " + str(len(self.plot_items))) From aa331dd3401312b41bd7d49f8ae5e76bcefe607c Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 2 Jan 2024 15:03:08 +0100 Subject: [PATCH 11/75] Removed hard-coded value for the length of incoming data --- robot_log_visualizer/file_reader/signal_provider.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 483223e..b09f38d 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -160,8 +160,11 @@ def convertToNP(self, rawData, input): #print() if "data" in value.keys() and "timestamps" in value.keys(): data[key] = {} - rawData[key]["data"] = np.append(rawData[key]["data"], np.array(value["data"])).reshape(-1, 6) - rawData[key]["timestamps"] = np.append(rawData[key]["timestamps"], np.array(value["timestamps"]))#.reshape(-1,1) + print("value[data]: ") + print(value["data"]) + print(len(value["data"])) + rawData[key]["data"] = np.append(rawData[key]["data"], np.array(value["data"])).reshape(-1, len(value["data"])) + rawData[key]["timestamps"] = np.append(rawData[key]["timestamps"], np.array(value["timestamps"])) if rawData[key]["timestamps"][0] < self.initial_time: self.timestamps = rawData[key]["timestamps"] From 3b8612356f62649cf760db584117563442ddc322 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 4 Jan 2024 16:52:02 +0100 Subject: [PATCH 12/75] Can now stream all data (except mesh data) in real-time --- .../file_reader/signal_provider.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index b09f38d..c41b431 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -152,17 +152,27 @@ def __populate_numerical_data(self, file_object): def convertToNP(self, rawData, input): data = {} + print() + print("input items:") + print(input.items()) for key, value in input.items(): if key not in rawData.keys(): rawData[key] = value #print() #print(input.items()) #print() + print("Value:") + print(value) + if value is None: + continue + print("Value.keys") + print(value.keys()) + print() if "data" in value.keys() and "timestamps" in value.keys(): data[key] = {} - print("value[data]: ") - print(value["data"]) - print(len(value["data"])) + # print("value[data]: ") + # print(value["data"]) + # print(len(value["data"])) rawData[key]["data"] = np.append(rawData[key]["data"], np.array(value["data"])).reshape(-1, len(value["data"])) rawData[key]["timestamps"] = np.append(rawData[key]["timestamps"], np.array(value["timestamps"])) @@ -247,8 +257,8 @@ def establish_connection(self): # mergedeep.merge(self.data, input, strategy=mergedeep.Strategy.ADDITIVE) # self.data['robot_realtime']['FTs'][key]["data"] = np.append(self.data['robot_realtime']['FTs'][key]["data"], np.array(input['robot_realtime']['FTs'][key]["data"])).reshape(-1,1) # self.data['robot_realtime']['FTs'][key]["timestamps"] = np.append(self.data['robot_realtime']['FTs'][key]["timestamps"], input['robot_realtime']['FTs'][key]["timestamps"]) - print("Data after") - print(self.data) + # print("Data after") + # print(self.data) # if (len(self.data['robot_realtime']['FTs'][key]["data"])) From 886a45bb7c2c93847f523ca9a63e85b39fc791be Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 4 Jan 2024 17:12:16 +0100 Subject: [PATCH 13/75] Cleaned up the code a bit --- .../file_reader/signal_provider.py | 82 +---------- robot_log_visualizer/ui/gui.py | 128 +----------------- 2 files changed, 3 insertions(+), 207 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index c41b431..b12e5f8 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -152,27 +152,15 @@ def __populate_numerical_data(self, file_object): def convertToNP(self, rawData, input): data = {} - print() - print("input items:") - print(input.items()) for key, value in input.items(): if key not in rawData.keys(): rawData[key] = value - #print() - #print(input.items()) - #print() - print("Value:") - print(value) + # TODO: Understand why this check below is needed if value is None: continue - print("Value.keys") - print(value.keys()) - print() + if "data" in value.keys() and "timestamps" in value.keys(): data[key] = {} - # print("value[data]: ") - # print(value["data"]) - # print(len(value["data"])) rawData[key]["data"] = np.append(rawData[key]["data"], np.array(value["data"])).reshape(-1, len(value["data"])) rawData[key]["timestamps"] = np.append(rawData[key]["timestamps"], np.array(value["timestamps"])) @@ -185,11 +173,6 @@ def convertToNP(self, rawData, input): self.end_time = self.timestamps[-1] if "elements_names" in value.keys(): - #elements_names_ref = value["elements_names"] - #data[key]["elements_names"] = [ - # "".join(chr(c[0]) for c in value[ref]) - # for ref in elements_names_ref[0] - #] rawData[key]["elements_names"] = value["elements_names"] @@ -199,13 +182,6 @@ def convertToNP(self, rawData, input): return data - # TODO: - # Make a dummy self.data which populates with data you choose to - # understand how the plot works - # Then you can understand how to send the data and then serialize it - # Once done, try appending data to self.data periodically - # To symbolize how data would work coming in - # After send the data from the logger and visualize it right there def establish_connection(self): key = "l_arm_ft" self.initalFrame = True @@ -214,27 +190,7 @@ def establish_connection(self): self.loggingInput = yarp.BufferedPortBottle() self.loggingInput.open("/visualizerInput") yarp.Network.connect("/testLoggerOutput", "/visualizerInput") - """self.data = {'robot_realtime': {'FTs': - {'l_arm_ft': - {'data': np.array([np.array([])]), 'timestamps': np.array([])}, - 'r_arm_ft': - {'data': np.array([np.array([])]), 'timestamps': np.array([])}, - 'l_jet_ft': - {'data': np.array([np.array([])]), 'timestamps': np.array([])}, - 'r_jet_ft': - {'data': np.array([np.array([])]), 'timestamps': np.array([])}, - 'l_foot_front_ft': - {'data': np.array([np.array([])]), 'timestamps': np.array([])}, - 'r_foot_front_ft': - {'data': np.array([np.array([])]), 'timestamps': np.array([])}, - 'l_foot_rear_ft': - {'data': np.array([np.array([])]), 'timestamps': np.array([])}, - 'r_foot_rear_ft': - {'data': np.array([np.array([])]), 'timestamps': np.array([])}, - }}} - """ - #self.data = {'robot_realtime': {}} self.networkInit = True success = self.loggingInput.read() if not success: @@ -245,41 +201,7 @@ def establish_connection(self): # json.loads is done twice, the 1st time is to remove \\ character # the 2nd time actually converts the string to the dictionary input = json.loads(json.loads(rawInput)) - #print("Raw input Received:") - #print(input) - - self.convertToNP(self.data, input) - # print("Data before") - # print(self.data) - # print("Input") - # print(input) - # mergedeep.merge(self.data, input, strategy=mergedeep.Strategy.ADDITIVE) - # self.data['robot_realtime']['FTs'][key]["data"] = np.append(self.data['robot_realtime']['FTs'][key]["data"], np.array(input['robot_realtime']['FTs'][key]["data"])).reshape(-1,1) - # self.data['robot_realtime']['FTs'][key]["timestamps"] = np.append(self.data['robot_realtime']['FTs'][key]["timestamps"], input['robot_realtime']['FTs'][key]["timestamps"]) - # print("Data after") - # print(self.data) - # if (len(self.data['robot_realtime']['FTs'][key]["data"])) - - - """ - self.t = {'x': 1} - key = 'l_arm_ft' - self.data = {'robot_logger_device': - {'FTs': - {'l_arm_ft': - {'data': np.array([[16.04658911, 0.0], [8.32923841, 5.0], [41.25904926, 10.0]]), 'timestamps': np.array([1.70194892e+09, 1.70194893e+09, 1.70194894e+09,]), 'elements_names': np.array(['f_x', 'f_y'])}} } } - """ - - """if self.data['robot_realtime']['FTs'][key]["timestamps"][0] < self.initial_time: - self.timestamps = self.data['robot_realtime']['FTs'][key]["timestamps"] - self.initial_time = self.timestamps[0] - - if self.data['robot_realtime']['FTs'][key]["timestamps"][-1] > self.end_time: - self.timestamps = self.data['robot_realtime']['FTs'][key]["timestamps"] - self.end_time = self.timestamps[-1] - """ - def open_mat_file(self, file_name: str): with h5py.File(file_name, "r") as file: diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index ac62e80..a2e201f 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -119,41 +119,6 @@ def get_icon(icon_name): ) return icon -class NetworkThread(threading.Thread): - def __init__(self, thread_name, thread_ID, data): - threading.Thread.__init__(self) - self.thread_name = thread_name - self.thread_ID = thread_ID - self.data = data - - # helper function to execute the threads - def run(self): - print("Now running network thread") - import yarp - yarp.Network.init() - loggingInput = yarp.BufferedPortBottle() - loggingInput.open("/visualizerInput") - yarp.Network.connect("/testLoggerOutput", "/visualizerInput") - success = loggingInput.read() - if not success: - print("Failed") - else: - import json - while True: - output = str(success.toString()) - # json.loads is done twice, the 1st time is to remove \\ character - # the 2nd time actually converts the string to the dictionary - dicOutput = json.loads(json.loads(output)) - print("Output Type:") - print(type(dicOutput)) - print("Result dictionary:") - print(dicOutput) - print(dicOutput["robot_realtime"]["FTs"]) - yarp.Network.fini() - print("Thread completed") - - - class RobotViewerMainWindow(QtWidgets.QMainWindow): def __init__(self, signal_provider, meshcat_provider, animation_period): # call QMainWindow constructor @@ -332,13 +297,6 @@ def keyPressEvent(self, event): self.ui.timeSlider.setValue(new_index) self.slider_pressed = False - - def realtimeUpdatePlots(self): - while True: - print("In plotter thread") - for plot in self.plot_items: - plot.updatePlotItem(signal_provider=self.signal_provider, period=self.animation_period) - def toolButton_on_click(self): self.plot_items.append( PlotItem(signal_provider=self.signal_provider, period=self.animation_period) @@ -350,13 +308,6 @@ def toolButton_on_click(self): else: self.ui.tabPlotWidget.setTabsClosable(True) - """if self.realtimeLoggerActive and not self.realtimePlotUpdaterThreadActive: - print("Now launching plotter thread") - self.plotUpdater = threading.Thread(target=self.realtimeUpdatePlots) - self.plotUpdater.start() - self.realtimePlotUpdaterThreadActive = True - """ - def timeSlider_on_pressed(self): self.slider_pressed = True @@ -460,16 +411,11 @@ def variableTreeWidget_on_click(self): paths.append(path) legends.append(legend) - #print("Adding item to plot") - #print("Paths:") - #print(paths) - #print("Legends:") - #print(legends) + self.plotData[self.ui.tabPlotWidget.currentIndex()] = {"paths": paths, "legends": legends} self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( paths, legends ) - #print(self.plot_items) def find_text_log_index(self, path): current_time = self.signal_provider.current_time @@ -598,9 +544,7 @@ def __populate_variable_tree_widget(self, obj, parent) -> QTreeWidgetItem: n_cols = 1 # In yarp telemetry v0.4.0 the elements_names was saved. - #print("About to check element names") if "elements_names" in obj.keys(): - #print("There is an element name") for name in obj["elements_names"]: print(name) item = QTreeWidgetItem([name]) @@ -758,93 +702,23 @@ def maintain_connection(self): self.ui.startButton.setEnabled(True) self.ui.timeSlider.setEnabled(True) - # self.plotTabBar_currentChanged(0) - # if not init: - #self.plotData[self.ui.tabPlotWidget.currentIndex()] - #print("About to aquire lock for updating the plot") - #print("Lock aquired for updating the plot") if len(self.plotData) > 0 and len(self.plotData) > self.ui.tabPlotWidget.currentIndex(): - #print("Length of plot_items: " + str(len(self.plot_items))) - #print(self.plotData) self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( self.plotData[self.ui.tabPlotWidget.currentIndex()]["paths"], self.plotData[self.ui.tabPlotWidget.currentIndex()]["legends"] ) - #else: - #print("Current index: " + str(self.ui.tabPlotWidget.currentIndex())) counter = 0 self.plottingLock.release() - #print("Lock released for updating the plot") - #self.plot_items[0].canvas.update_plots( - # np.array([['robot_realtime', 'FTs', 'l_arm_ft', '0']]), np.array([['robot_realtime', 'FTs', 'l_arm_ft', 'Element 0']]) - #) - #self.plot_items[0].canvas.refresh_plot() - # init = True - #self.plot_items[0].canvas.draw() - #self.plotTabBar_currentChanged(0) - # print(self.plot_items[0].canvas.refresh_plot()) - # print("Size of plot items") - # print(len(self.plot_items)) - #self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.show() time.sleep(0.01) counter = counter + 1 - #for plot in self.plot_items: - # plot.updatePlotItem(signal_provider=self.signal_provider, period=self.animation_period) - def connect_realtime_logger(self): self.realtimeLoggerActive = True print("Now connecting for real-time logging") self.networkThread = threading.Thread(target=self.maintain_connection) self.networkThread.start() - """self.signal_provider.establish_connection() - root = list(self.signal_provider.data.keys())[0] - root_item = QTreeWidgetItem([root]) - root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) - items = self.__populate_variable_tree_widget( - self.signal_provider.data[root], root_item - ) - self.ui.variableTreeWidget.insertTopLevelItems(0, [items]) - - # populate text logging tree - if self.signal_provider.text_logging_data: - root = list(self.signal_provider.text_logging_data.keys())[0] - root_item = QTreeWidgetItem([root]) - root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) - items = self.__populate_text_logging_tree_widget( - self.signal_provider.text_logging_data[root], root_item - ) - self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) - - # spawn the console - self.pyconsole.push_local_ns("data", self.signal_provider.data) - - self.ui.timeSlider.setMaximum(self.signal_size) - self.ui.startButton.setEnabled(True) - self.ui.timeSlider.setEnabled(True) - # for now will hard code the device name to: /testLoggerOutput - - connectionThread = NetworkThread("NetworkThread", 1, self.signal_provider.data) - connectionThread.start() - # connectionThread.join() - """ - """ - import yarp - yarp.Network.init() - loggingInput = yarp.BufferedPortBottle() - loggingInput.open("/visualizerInput") - yarp.Network.connect("/testLoggerOutput", "/visualizerInput") - success = loggingInput.read() - if not success: - print("Failed") - else: - print("Success") - yarp.Network.fini() - """ - - def open_about(self): self.about.show() From a1a07990707e5e239d8b7bc0341ff8872b4ec201 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 5 Jan 2024 12:29:34 +0100 Subject: [PATCH 14/75] Added mesh support, cleaned up the code a bit --- .../file_reader/signal_provider.py | 24 +++---- .../robot_visualizer/meshcat_provider.py | 15 +++++ robot_log_visualizer/ui/gui.py | 62 +++++++++++-------- 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index b12e5f8..b29f08c 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -150,7 +150,7 @@ def __populate_numerical_data(self, file_object): return data - def convertToNP(self, rawData, input): + def __populateRealtimeLoggerData(self, rawData, input): data = {} for key, value in input.items(): if key not in rawData.keys(): @@ -177,13 +177,13 @@ def convertToNP(self, rawData, input): else: - data[key] = self.convertToNP(rawData=rawData[key],input=value) + data[key] = self.__populateRealtimeLoggerData(rawData=rawData[key],input=value) return data def establish_connection(self): - key = "l_arm_ft" + self.root_name = "robot_realtime" self.initalFrame = True if not self.networkInit: yarp.Network.init() @@ -194,27 +194,21 @@ def establish_connection(self): self.networkInit = True success = self.loggingInput.read() if not success: - print("Failed to read from YARP port") + print("Failed to read realtime YARP port, closing") sys.exit(1) else: rawInput = str(success.toString()) - # json.loads is done twice, the 1st time is to remove \\ character + + # json.loads is done twice, the 1st time is to remove escape characters # the 2nd time actually converts the string to the dictionary input = json.loads(json.loads(rawInput)) - self.convertToNP(self.data, input) + self.__populateRealtimeLoggerData(self.data, input) def open_mat_file(self, file_name: str): with h5py.File(file_name, "r") as file: - # print("mat file items") - # print(file.items()) + root_variable = file.get(self.root_name) self.data = self.__populate_numerical_data(file) - # print("Root Variable:") - # print(root_variable) - # print("MAT file keys:") - # print(file.keys()) - # print("Root name keys:") - # print(root_variable.keys()) if "log" in root_variable.keys(): self.text_logging_data["log"] = self.__populate_text_logging_data( @@ -237,8 +231,6 @@ def open_mat_file(self, file_name: str): except: pass self.index = 0 - print("Data:") - print(self.data) def __len__(self): return self.timestamps.shape[0] diff --git a/robot_log_visualizer/robot_visualizer/meshcat_provider.py b/robot_log_visualizer/robot_visualizer/meshcat_provider.py index 2a58dcf..0a24cc5 100644 --- a/robot_log_visualizer/robot_visualizer/meshcat_provider.py +++ b/robot_log_visualizer/robot_visualizer/meshcat_provider.py @@ -164,3 +164,18 @@ def run(self): if self.state == PeriodicThreadState.closed: return + + def updateMesh(self): + base_rotation = np.eye(3) + base_position = np.array([0.0, 0.0, 0.0]) + + self._signal_provider.index = len(self._signal_provider.timestamps) - 1 + # These are the robot measured joint positions in radians + self.meshcat_visualizer.set_multibody_system_state( + base_position, + base_rotation, + joint_value=self._signal_provider.get_joints_position_at_index( + self._signal_provider.index + )[self.model_joints_index], + model_name="robot", + ) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index a2e201f..2b84132 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -348,7 +348,7 @@ def startButton_on_click(self): self.ui.startButton.setEnabled(False) self.ui.pauseButton.setEnabled(True) self.signal_provider.state = PeriodicThreadState.running - # self.meshcat_provider.state = PeriodicThreadState.running + self.meshcat_provider.state = PeriodicThreadState.running self.logger.write_to_log("Dataset started.") @@ -361,19 +361,16 @@ def pauseButton_on_click(self): video_item.media_player.pause() self.signal_provider.state = PeriodicThreadState.pause - # self.meshcat_provider.state = PeriodicThreadState.pause + self.meshcat_provider.state = PeriodicThreadState.pause self.logger.write_to_log("Dataset paused.") def plotTabCloseButton_on_click(self, index): - print("About to acquire the lock for closing a plot") self.plottingLock.acquire() - print("Lock acquired for closing the plot") self.ui.tabPlotWidget.removeTab(index) self.plot_items[index].canvas.quit_animation() + # Update the indexes of plotData before deletion - print("Index: " + str(index)) - print("Length of keys: " + str(len(self.plotData.keys()))) for i in range(index, len(self.plotData.keys()) - 1): self.plotData[i] = self.plotData[i + 1] # Remove the last key @@ -383,7 +380,6 @@ def plotTabCloseButton_on_click(self, index): if self.ui.tabPlotWidget.count() == 1: self.ui.tabPlotWidget.setTabsClosable(False) self.plottingLock.release() - print("Lock released for closing the plot") def plotTabBar_on_doubleClick(self, index): dlg, plot_title = build_plot_title_box_dialog() @@ -546,7 +542,6 @@ def __populate_variable_tree_widget(self, obj, parent) -> QTreeWidgetItem: # In yarp telemetry v0.4.0 the elements_names was saved. if "elements_names" in obj.keys(): for name in obj["elements_names"]: - print(name) item = QTreeWidgetItem([name]) parent.addChild(item) else: @@ -661,29 +656,15 @@ def open_mat_file(self): if file_name: self.__load_mat_file(file_name) - def maintain_connection(self): - root = None - initConnection = False + def maintain_connection(self, root): + # root = None + # initConnection = False plotCounter = 10 - init = False counter = 0 while True: self.signal_provider.establish_connection() - # only display one root in the gui - if not initConnection: - root = list(self.signal_provider.data.keys())[0] - root_item = QTreeWidgetItem([root]) - root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) - initConnection = True - items = self.__populate_variable_tree_widget( - self.signal_provider.data[root], root_item - ) - self.ui.variableTreeWidget.insertTopLevelItems(0, [items]) - # populate text logging tree - - if counter == plotCounter: self.plottingLock.acquire() if self.signal_provider.text_logging_data: @@ -712,11 +693,40 @@ def maintain_connection(self): time.sleep(0.01) counter = counter + 1 + self.meshcat_provider.updateMesh() def connect_realtime_logger(self): self.realtimeLoggerActive = True print("Now connecting for real-time logging") - self.networkThread = threading.Thread(target=self.maintain_connection) + + # Do initial connection to populate the necessary data + self.signal_provider.establish_connection() + # only display one root in the gui + root = list(self.signal_provider.data.keys())[0] + root_item = QTreeWidgetItem([root]) + root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) + items = self.__populate_variable_tree_widget( + self.signal_provider.data[root], root_item + ) + self.ui.variableTreeWidget.insertTopLevelItems(0, [items]) + + + # load the model + self.signal_provider.joints_name = ['neck_pitch', 'neck_roll', 'neck_yaw', 'torso_pitch', 'torso_roll', 'torso_yaw', 'l_shoulder_pitch', 'l_shoulder_roll', 'l_shoulder_yaw', 'l_elbow', 'r_shoulder_pitch', 'r_shoulder_roll', 'r_shoulder_yaw', 'r_elbow', 'l_hip_pitch', 'l_hip_roll', 'l_hip_yaw', 'l_knee', 'l_ankle_pitch', 'l_ankle_roll', 'r_hip_pitch', 'r_hip_roll', 'r_hip_yaw', 'r_knee', 'r_ankle_pitch', 'r_ankle_roll'] + self.signal_provider.robot_name = "iRonCub-Mk3_Gazebo" + if not self.meshcat_provider.load_model( + self.signal_provider.joints_name, self.signal_provider.robot_name + ): + # if not loaded we print an error but we continue + msg = "Unable to load the model: " + if self.meshcat_provider.custom_model_path: + msg = msg + self.meshcat_provider.custom_model_path + else: + msg = msg + self.signal_provider.robot_name + + self.logger.write_to_log(msg) + + self.networkThread = threading.Thread(target=self.maintain_connection, args=(root,)) self.networkThread.start() def open_about(self): From e412bf4581db51e9f47760ab239231a69c02f114 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 5 Jan 2024 15:12:07 +0100 Subject: [PATCH 15/75] Cleaned up the code, removed hard-coded values --- .../file_reader/signal_provider.py | 7 +++++++ robot_log_visualizer/ui/gui.py | 14 +++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index b29f08c..b890742 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -60,6 +60,7 @@ def __init__(self, period: float): self.robot_name = "" self.root_name = "robot_logger_device" + #self.root_name = "robot_realtime" self._current_time = 0 @@ -155,7 +156,12 @@ def __populateRealtimeLoggerData(self, rawData, input): for key, value in input.items(): if key not in rawData.keys(): rawData[key] = value + elif key == "description_list" or key == "yarp_robot_name": + continue # TODO: Understand why this check below is needed + # print("Value:") + # print(value) + # print() if value is None: continue @@ -203,6 +209,7 @@ def establish_connection(self): # the 2nd time actually converts the string to the dictionary input = json.loads(json.loads(rawInput)) self.__populateRealtimeLoggerData(self.data, input) + self.joints_name = self.data["robot_realtime"]["description_list"] def open_mat_file(self, file_name: str): with h5py.File(file_name, "r") as file: diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 2b84132..7c7427b 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -125,10 +125,10 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): super().__init__() # for realtime logging - self.realtimeLoggerActive = False self.realtimePlotUpdaterThreadActive = False self.plotData = {} self.plottingLock = threading.Lock() + self.realtimeConnectionEnabled = False self.animation_period = animation_period @@ -527,6 +527,8 @@ def closeEvent(self, event): self.signal_provider.wait() event.accept() + self.realtimeConnectionEnabled = False + self.networkThread.join() def __populate_variable_tree_widget(self, obj, parent) -> QTreeWidgetItem: if not isinstance(obj, dict): @@ -657,11 +659,9 @@ def open_mat_file(self): self.__load_mat_file(file_name) def maintain_connection(self, root): - # root = None - # initConnection = False plotCounter = 10 counter = 0 - while True: + while self.realtimeConnectionEnabled: self.signal_provider.establish_connection() # populate text logging tree @@ -696,7 +696,7 @@ def maintain_connection(self, root): self.meshcat_provider.updateMesh() def connect_realtime_logger(self): - self.realtimeLoggerActive = True + self.realtimeConnectionEnabled = True print("Now connecting for real-time logging") # Do initial connection to populate the necessary data @@ -712,8 +712,8 @@ def connect_realtime_logger(self): # load the model - self.signal_provider.joints_name = ['neck_pitch', 'neck_roll', 'neck_yaw', 'torso_pitch', 'torso_roll', 'torso_yaw', 'l_shoulder_pitch', 'l_shoulder_roll', 'l_shoulder_yaw', 'l_elbow', 'r_shoulder_pitch', 'r_shoulder_roll', 'r_shoulder_yaw', 'r_elbow', 'l_hip_pitch', 'l_hip_roll', 'l_hip_yaw', 'l_knee', 'l_ankle_pitch', 'l_ankle_roll', 'r_hip_pitch', 'r_hip_roll', 'r_hip_yaw', 'r_knee', 'r_ankle_pitch', 'r_ankle_roll'] - self.signal_provider.robot_name = "iRonCub-Mk3_Gazebo" + self.signal_provider.joints_name = self.signal_provider.data["robot_realtime"]["description_list"] + self.signal_provider.robot_name = self.signal_provider.data["robot_realtime"]["yarp_robot_name"][0] if not self.meshcat_provider.load_model( self.signal_provider.joints_name, self.signal_provider.robot_name ): From e19c626545297e3590060afa0c8c5dcf17ea3375 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 9 Jan 2024 09:57:47 +0100 Subject: [PATCH 16/75] Cleaned up the code some more --- robot_log_visualizer/file_reader/signal_provider.py | 6 +----- robot_log_visualizer/robot_visualizer/meshcat_provider.py | 1 + robot_log_visualizer/ui/gui.py | 1 + 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index b890742..20c2b64 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -13,7 +13,6 @@ # for real-time logging import yarp import json -import mergedeep class TextLoggingMsg: @@ -158,10 +157,7 @@ def __populateRealtimeLoggerData(self, rawData, input): rawData[key] = value elif key == "description_list" or key == "yarp_robot_name": continue - # TODO: Understand why this check below is needed - # print("Value:") - # print(value) - # print() + if value is None: continue diff --git a/robot_log_visualizer/robot_visualizer/meshcat_provider.py b/robot_log_visualizer/robot_visualizer/meshcat_provider.py index 0a24cc5..6319948 100644 --- a/robot_log_visualizer/robot_visualizer/meshcat_provider.py +++ b/robot_log_visualizer/robot_visualizer/meshcat_provider.py @@ -165,6 +165,7 @@ def run(self): if self.state == PeriodicThreadState.closed: return + # For the real-time logger def updateMesh(self): base_rotation = np.eye(3) base_position = np.array([0.0, 0.0, 0.0]) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 7c7427b..c8be772 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -729,6 +729,7 @@ def connect_realtime_logger(self): self.networkThread = threading.Thread(target=self.maintain_connection, args=(root,)) self.networkThread.start() + def open_about(self): self.about.show() From 835a17fad771e6079fbf97d77abf6cebf4e75254 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 9 Jan 2024 09:58:50 +0100 Subject: [PATCH 17/75] Added sliding window feature and icon for connection --- .../file_reader/signal_provider.py | 13 +++- .../plotter/matplotlib_viewer_canvas.py | 16 +++-- robot_log_visualizer/ui/gui.py | 68 +++++++++---------- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 20c2b64..68b71aa 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -59,10 +59,11 @@ def __init__(self, period: float): self.robot_name = "" self.root_name = "robot_logger_device" - #self.root_name = "robot_realtime" self._current_time = 0 + self.realtimeBufferReached = False + # for networking with the real-time logger self.networkInit = False @@ -174,6 +175,16 @@ def __populateRealtimeLoggerData(self, rawData, input): self.timestamps = rawData[key]["timestamps"] self.end_time = self.timestamps[-1] + if self.end_time - self.initial_time >= 20: + self.realtimeBufferReached = True + tempInitialTime = self.initial_time + tempEndTime = self.end_time + while tempEndTime - tempInitialTime >= 20: + rawData[key]["data"] = np.delete(rawData[key]["data"], 0, axis=0) + rawData[key]["timestamps"] = np.delete(rawData[key]["timestamps"], 0) + tempInitialTime = rawData[key]["timestamps"][0] + tempEndTime = rawData[key]["timestamps"][-1] + if "elements_names" in value.keys(): rawData[key]["elements_names"] = value["elements_names"] diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 831c1f1..5f91fa4 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -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 @@ -36,6 +37,8 @@ def __init__(self, parent, signal_provider, period): self.axes.set_ylabel("value") self.axes.grid(True) + self.realtimeFixedXMax = 20 + # start the vertical line animation (self.vertical_line,) = self.axes.plot([], [], "-", lw=1, c="k") @@ -64,14 +67,14 @@ def quit_animation(self): if self.vertical_line_anim: self.vertical_line_anim._stop() - def update_plots(self, paths, legends): + def update_plots(self, paths, legends, realtimePlot=False): self.axes.cla() 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 + data = self.signal_provider.data.copy() for key in path[:-1]: data = data[key] try: @@ -97,9 +100,12 @@ 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.set_xlim(1, self.realtimeFixedXMax) + 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 diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index c8be772..a4fd684 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -6,7 +6,7 @@ import threading from PyQt5 import QtWidgets, QtGui from PyQt5.QtCore import QUrl -from PyQt5.QtCore import pyqtSlot, Qt, QMutex, QMutexLocker +from PyQt5.QtCore import pyqtSlot, Qt, QMutex, QMutexLocker, QThread from PyQt5.QtWidgets import ( QFileDialog, QTreeWidgetItem, @@ -129,6 +129,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): self.plotData = {} self.plottingLock = threading.Lock() self.realtimeConnectionEnabled = False + self.sleepPeriodBuffer = 0.02 self.animation_period = animation_period @@ -149,6 +150,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): self.ui.actionQuit.setIcon(get_icon("close-circle-outline.svg")) self.ui.actionQuit.setIcon(get_icon("close-circle-outline.svg")) + self.ui.actionConnect.setIcon(get_icon("connection-outline.png")) self.ui.actionOpen.setIcon(get_icon("folder-open-outline.svg")) self.ui.actionSet_Robot_Model.setIcon(get_icon("body-outline.svg")) self.setWindowIcon(get_icon("icon.png")) @@ -408,10 +410,12 @@ def variableTreeWidget_on_click(self): paths.append(path) legends.append(legend) + self.plottingLock.acquire() self.plotData[self.ui.tabPlotWidget.currentIndex()] = {"paths": paths, "legends": legends} self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( - paths, legends + paths, legends, self.realtimeConnectionEnabled ) + self.plottingLock.release() def find_text_log_index(self, path): current_time = self.signal_provider.current_time @@ -527,8 +531,9 @@ def closeEvent(self, event): self.signal_provider.wait() event.accept() - self.realtimeConnectionEnabled = False - self.networkThread.join() + if self.realtimeConnectionEnabled: + self.realtimeConnectionEnabled = False + self.networkThread.join() def __populate_variable_tree_widget(self, obj, parent) -> QTreeWidgetItem: if not isinstance(obj, dict): @@ -659,40 +664,35 @@ def open_mat_file(self): self.__load_mat_file(file_name) def maintain_connection(self, root): - plotCounter = 10 - counter = 0 while self.realtimeConnectionEnabled: self.signal_provider.establish_connection() # populate text logging tree - if counter == plotCounter: - self.plottingLock.acquire() - if self.signal_provider.text_logging_data: - root = list(self.signal_provider.text_logging_data.keys())[0] - root_item = QTreeWidgetItem([root]) - root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) - items = self.__populate_text_logging_tree_widget( - self.signal_provider.text_logging_data[root], root_item - ) - self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) - - # spawn the console - self.pyconsole.push_local_ns("data", self.signal_provider.data) - - self.ui.timeSlider.setMaximum(self.signal_size) - self.ui.startButton.setEnabled(True) - self.ui.timeSlider.setEnabled(True) - - if len(self.plotData) > 0 and len(self.plotData) > self.ui.tabPlotWidget.currentIndex(): - self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( - self.plotData[self.ui.tabPlotWidget.currentIndex()]["paths"], - self.plotData[self.ui.tabPlotWidget.currentIndex()]["legends"] - ) - counter = 0 - self.plottingLock.release() - - time.sleep(0.01) - counter = counter + 1 + self.plottingLock.acquire() + if self.signal_provider.text_logging_data: + root = list(self.signal_provider.text_logging_data.keys())[0] + root_item = QTreeWidgetItem([root]) + root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) + items = self.__populate_text_logging_tree_widget( + self.signal_provider.text_logging_data[root], root_item + ) + self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) + + # spawn the console + self.pyconsole.push_local_ns("data", self.signal_provider.data) + + self.ui.timeSlider.setMaximum(self.signal_size) + self.ui.startButton.setEnabled(True) + self.ui.timeSlider.setEnabled(True) + + if len(self.plotData) > 0 and len(self.plotData) > self.ui.tabPlotWidget.currentIndex(): + self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( + self.plotData[self.ui.tabPlotWidget.currentIndex()]["paths"], + self.plotData[self.ui.tabPlotWidget.currentIndex()]["legends"], + self.realtimeConnectionEnabled) + self.plottingLock.release() + + time.sleep(self.animation_period + self.sleepPeriodBuffer) self.meshcat_provider.updateMesh() def connect_realtime_logger(self): From 0bf05254ec4ba4e89acd00edd77a8677f4b87b84 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 9 Jan 2024 09:59:58 +0100 Subject: [PATCH 18/75] Fixed bug with real time logging window --- robot_log_visualizer/file_reader/signal_provider.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 68b71aa..7f25fcd 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -216,6 +216,11 @@ def establish_connection(self): # the 2nd time actually converts the string to the dictionary input = json.loads(json.loads(rawInput)) self.__populateRealtimeLoggerData(self.data, input) + if self.realtimeBufferReached: + self.initial_time = self.timestamps[0] + self.end_time = self.timestamps[-1] + self.timestamps = np.delete(self.timestamps, 0) + self.realtimeBufferReached = False self.joints_name = self.data["robot_realtime"]["description_list"] def open_mat_file(self, file_name: str): From 872b3648736eb20764a761b560472e68fddb26f1 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 9 Jan 2024 10:51:44 +0100 Subject: [PATCH 19/75] removed hardcoded value for fixed plot window --- robot_log_visualizer/file_reader/signal_provider.py | 5 +++-- robot_log_visualizer/plotter/matplotlib_viewer_canvas.py | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 7f25fcd..7e2024d 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -63,6 +63,7 @@ def __init__(self, period: float): self._current_time = 0 self.realtimeBufferReached = False + self.realtimeFixedPlotWindow = 20 # for networking with the real-time logger self.networkInit = False @@ -175,11 +176,11 @@ def __populateRealtimeLoggerData(self, rawData, input): self.timestamps = rawData[key]["timestamps"] self.end_time = self.timestamps[-1] - if self.end_time - self.initial_time >= 20: + if self.end_time - self.initial_time >= self.realtimeFixedPlotWindow: self.realtimeBufferReached = True tempInitialTime = self.initial_time tempEndTime = self.end_time - while tempEndTime - tempInitialTime >= 20: + while tempEndTime - tempInitialTime >= self.realtimeFixedPlotWindow: rawData[key]["data"] = np.delete(rawData[key]["data"], 0, axis=0) rawData[key]["timestamps"] = np.delete(rawData[key]["timestamps"], 0) tempInitialTime = rawData[key]["timestamps"][0] diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 5f91fa4..61ec1c3 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -37,8 +37,6 @@ def __init__(self, parent, signal_provider, period): self.axes.set_ylabel("value") self.axes.grid(True) - self.realtimeFixedXMax = 20 - # start the vertical line animation (self.vertical_line,) = self.axes.plot([], [], "-", lw=1, c="k") @@ -101,7 +99,7 @@ def update_plots(self, paths, legends, realtimePlot=False): self.active_paths.pop(path) if realtimePlot: - self.axes.set_xlim(1, self.realtimeFixedXMax) + 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 From 36c9f3b62a2486bd5189ef6b0c4b3dab32f829b1 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 9 Jan 2024 10:57:25 +0100 Subject: [PATCH 20/75] Moved intialization values outside of the conneciton loop --- robot_log_visualizer/file_reader/signal_provider.py | 2 -- robot_log_visualizer/ui/gui.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 7e2024d..50d2cbb 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -197,8 +197,6 @@ def __populateRealtimeLoggerData(self, rawData, input): def establish_connection(self): - self.root_name = "robot_realtime" - self.initalFrame = True if not self.networkInit: yarp.Network.init() self.loggingInput = yarp.BufferedPortBottle() diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index a4fd684..4a7ba47 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -697,6 +697,7 @@ def maintain_connection(self, root): def connect_realtime_logger(self): self.realtimeConnectionEnabled = True + self.signal_provider.root_name = "robot_realtime" print("Now connecting for real-time logging") # Do initial connection to populate the necessary data From 031c401be7a8200f9364f3aebfefae622375d8b9 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 9 Jan 2024 10:58:26 +0100 Subject: [PATCH 21/75] Fixed blocking when connection closed, changed the YARP port name --- robot_log_visualizer/file_reader/signal_provider.py | 9 +++++---- robot_log_visualizer/ui/gui.py | 10 +++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 50d2cbb..6743713 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -200,14 +200,14 @@ def establish_connection(self): if not self.networkInit: yarp.Network.init() self.loggingInput = yarp.BufferedPortBottle() - self.loggingInput.open("/visualizerInput") - yarp.Network.connect("/testLoggerOutput", "/visualizerInput") + self.loggingInput.open("/visualizerInput:i") + yarp.Network.connect("/YARPRobotLoggerRT:o", "/visualizerInput:i") self.networkInit = True - success = self.loggingInput.read() + success = self.loggingInput.read(shouldWait=False) if not success: print("Failed to read realtime YARP port, closing") - sys.exit(1) + return False else: rawInput = str(success.toString()) @@ -221,6 +221,7 @@ def establish_connection(self): self.timestamps = np.delete(self.timestamps, 0) self.realtimeBufferReached = False self.joints_name = self.data["robot_realtime"]["description_list"] + return True def open_mat_file(self, file_name: str): with h5py.File(file_name, "r") as file: diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 4a7ba47..767d5bf 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -46,6 +46,8 @@ import time +import yarp + class SetRobotModelDialog(QtWidgets.QDialog): def __init__( @@ -664,8 +666,9 @@ def open_mat_file(self): self.__load_mat_file(file_name) def maintain_connection(self, root): - while self.realtimeConnectionEnabled: - self.signal_provider.establish_connection() + while self.realtimeConnectionEnabled and yarp.Network.exists("/YARPRobotLoggerRT:o"): + if not self.signal_provider.establish_connection(): + continue # populate text logging tree self.plottingLock.acquire() @@ -701,7 +704,8 @@ def connect_realtime_logger(self): print("Now connecting for real-time logging") # Do initial connection to populate the necessary data - self.signal_provider.establish_connection() + while not self.signal_provider.establish_connection(): + time.sleep(0.1) # only display one root in the gui root = list(self.signal_provider.data.keys())[0] root_item = QTreeWidgetItem([root]) From de5a54a227df28c7f9c0ca112ef05f468d556b2c Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 9 Jan 2024 10:58:41 +0100 Subject: [PATCH 22/75] Added label to rt connection option in GUI --- robot_log_visualizer/ui/autogenerated/visualizer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/robot_log_visualizer/ui/autogenerated/visualizer.py b/robot_log_visualizer/ui/autogenerated/visualizer.py index 070e54a..f12548d 100644 --- a/robot_log_visualizer/ui/autogenerated/visualizer.py +++ b/robot_log_visualizer/ui/autogenerated/visualizer.py @@ -283,6 +283,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")) self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O")) self.actionAbout.setText(_translate("MainWindow", "About")) self.actionSet_Robot_Model.setText(_translate("MainWindow", "Set Robot Model")) From be1a6672a18aec28774401747639210244206ec3 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Tue, 9 Jan 2024 15:58:36 +0100 Subject: [PATCH 23/75] Removed Trailing Whitespace --- robot_log_visualizer/file_reader/signal_provider.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 686d8be..1b25050 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -153,7 +153,7 @@ 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) @@ -166,7 +166,7 @@ def __populateRealtimeLoggerData(self, rawData, input): rawData[key] = value elif key == "description_list" or key == "yarp_robot_name": continue - + if value is None: continue @@ -201,7 +201,7 @@ def __populateRealtimeLoggerData(self, rawData, input): data[key] = self.__populateRealtimeLoggerData(rawData=rawData[key],input=value) return data - + def establish_connection(self): if not self.networkInit: @@ -209,7 +209,7 @@ def establish_connection(self): self.loggingInput = yarp.BufferedPortBottle() self.loggingInput.open("/visualizerInput:i") yarp.Network.connect("/YARPRobotLoggerRT:o", "/visualizerInput:i") - + self.networkInit = True success = self.loggingInput.read(shouldWait=False) if not success: From 82eb5e55d580a06a7bb860ca66aa940ed9501a28 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Tue, 9 Jan 2024 15:59:32 +0100 Subject: [PATCH 24/75] Removed whitespace --- robot_log_visualizer/ui/gui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index d68529f..9231406 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -430,10 +430,10 @@ def variableTreeWidget_on_click(self): # if there is no selection we do nothing if not paths: return - + self.plottingLock.acquire() self.plotData[self.ui.tabPlotWidget.currentIndex()] = {"paths": paths, "legends": legends} - + self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( paths, legends, self.realtimeConnectionEnabled ) @@ -750,7 +750,7 @@ def connect_realtime_logger(self): msg = msg + self.signal_provider.robot_name self.logger.write_to_log(msg) - + self.networkThread = threading.Thread(target=self.maintain_connection, args=(root,)) self.networkThread.start() From 83dbebcc85c19560a703cf3c27f941e0fdf9269e Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Tue, 9 Jan 2024 16:00:31 +0100 Subject: [PATCH 25/75] Removed Whitespace --- robot_log_visualizer/ui/plot_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/ui/plot_item.py b/robot_log_visualizer/ui/plot_item.py index d4eda1a..5bc7ca5 100644 --- a/robot_log_visualizer/ui/plot_item.py +++ b/robot_log_visualizer/ui/plot_item.py @@ -18,7 +18,7 @@ def __init__(self, signal_provider, period): parent=self, period=period, signal_provider=signal_provider ) self.ui.plotLayout.addWidget(self.canvas) - + def updatePlotItem(self, signal_provider, period): self.canvas = MatplotlibViewerCanvas( parent=self, period=period, signal_provider=signal_provider From b117936785a22b2c1a9cc55a71805a8af593026e Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 12 Jan 2024 10:02:06 +0100 Subject: [PATCH 26/75] Fixed issues with merge --- .../robot_visualizer/meshcat_provider.py | 19 +++++++++++-------- robot_log_visualizer/ui/gui.py | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/robot_log_visualizer/robot_visualizer/meshcat_provider.py b/robot_log_visualizer/robot_visualizer/meshcat_provider.py index 52f7707..1aae8ca 100644 --- a/robot_log_visualizer/robot_visualizer/meshcat_provider.py +++ b/robot_log_visualizer/robot_visualizer/meshcat_provider.py @@ -36,6 +36,8 @@ def __init__(self, signal_provider, period): self.env_list = ["GAZEBO_MODEL_PATH", "ROS_PACKAGE_PATH", "AMENT_PREFIX_PATH"] self._registered_3d_points = set() + self._realtimeMeshUpdate = False + @property def state(self): locker = QMutexLocker(self.state_lock) @@ -162,7 +164,9 @@ def run(self): while True: start = time.time() - if self.state == PeriodicThreadState.running and self._is_model_loaded: + if self._realtimeMeshUpdate: + return + elif self.state == PeriodicThreadState.running and self._is_model_loaded: robot_state = self._signal_provider.get_robot_state_at_index( self._signal_provider.index ) @@ -196,17 +200,16 @@ def run(self): return # For the real-time logger - def updateMesh(self): + def updateMeshRealtime(self): base_rotation = np.eye(3) base_position = np.array([0.0, 0.0, 0.0]) self._signal_provider.index = len(self._signal_provider.timestamps) - 1 + robot_state = self._signal_provider.get_robot_state_at_index(self._signal_provider.index) # These are the robot measured joint positions in radians - self.meshcat_visualizer.set_multibody_system_state( - base_position, - base_rotation, - joint_value=self._signal_provider.get_joints_position_at_index( - self._signal_provider.index - )[self.model_joints_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", ) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 9231406..2077bfb 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -716,7 +716,7 @@ def maintain_connection(self, root): self.plottingLock.release() time.sleep(self.animation_period + self.sleepPeriodBuffer) - self.meshcat_provider.updateMesh() + self.meshcat_provider.updateMeshRealtime() def connect_realtime_logger(self): self.realtimeConnectionEnabled = True @@ -726,6 +726,7 @@ def connect_realtime_logger(self): # Do initial connection to populate the necessary data while not self.signal_provider.establish_connection(): time.sleep(0.1) + self.meshcat_provider._realtimeMeshUpdate = True # only display one root in the gui root = list(self.signal_provider.data.keys())[0] root_item = QTreeWidgetItem([root]) From 228e84f20ccf17b00ec03fcf54fbd3111369080e Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 12 Jan 2024 10:33:54 +0100 Subject: [PATCH 27/75] Fixed freeze when connection stops --- robot_log_visualizer/ui/gui.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 2077bfb..8bcb188 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -136,6 +136,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): self.plotData = {} self.plottingLock = threading.Lock() self.realtimeConnectionEnabled = False + self.timeoutAttempts = 20 self.sleepPeriodBuffer = 0.02 self.animation_period = animation_period @@ -724,8 +725,13 @@ def connect_realtime_logger(self): print("Now connecting for real-time logging") # Do initial connection to populate the necessary data - while not self.signal_provider.establish_connection(): + connectionCounter = 0 + while not self.signal_provider.establish_connection() and connectionCounter < self.timeoutAttempts: time.sleep(0.1) + connectionCounter = connectionCounter + 1 + if connectionCounter == self.timeoutAttempts: + print("Failed to connect, connection timeout") + return self.meshcat_provider._realtimeMeshUpdate = True # only display one root in the gui root = list(self.signal_provider.data.keys())[0] From 4cd3a855bc048f7216a04ef9d796e6415246e073 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 15 Jan 2024 12:06:37 +0100 Subject: [PATCH 28/75] Initial test complete of receiving RT data from Vector Collection Server --- .../file_reader/signal_provider.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 1b25050..25c9df1 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -11,6 +11,8 @@ from robot_log_visualizer.utils.utils import PeriodicThreadState, RobotStatePath import idyntree.swig as idyn +import bipedal_locomotion_framework.bindings as blf + # for real-time logging import yarp import json @@ -206,21 +208,33 @@ def __populateRealtimeLoggerData(self, rawData, input): def establish_connection(self): if not self.networkInit: yarp.Network.init() - self.loggingInput = yarp.BufferedPortBottle() - self.loggingInput.open("/visualizerInput:i") - yarp.Network.connect("/YARPRobotLoggerRT:o", "/visualizerInput:i") + #self.loggingInput = yarp.BufferedPortBottle() + #self.loggingInput.open("/visualizerInput:i") + #yarp.Network.connect("/YARPRobotLoggerRT:o", "/visualizerInput:i") + + vectorCollectionsClient = blf.yarp_utilities.VectorsCollectionClient() + param_handler = blf.parameters_handler.YarpParametersHandler() + param_handler.set_parameter_string("remote", "/testVectorCollections") # you must have some local port as well + param_handler.set_parameter_string("local", "/visualizerInput:i") # remote must match the server + param_handler.set_parameter_string("carrier", "udp") + print("Just set the parameter string") + vectorCollectionsClient.initialize(param_handler) + print("just initialized the vectorCollectionsClient") + + vectorCollectionsClient.connect() + print("Just tried to connect to the vector Collections Client") + self.networkInit = True - success = self.loggingInput.read(shouldWait=False) - if not success: + print("About to read data from the vectors colleciton client") + input = vectorCollectionsClient.readData(True) + print("Successfully read the data: " + str(input)) + if not input: print("Failed to read realtime YARP port, closing") return False else: - rawInput = str(success.toString()) - # json.loads is done twice, the 1st time is to remove escape characters # the 2nd time actually converts the string to the dictionary - input = json.loads(json.loads(rawInput)) self.__populateRealtimeLoggerData(self.data, input) if self.realtimeBufferReached: self.initial_time = self.timestamps[0] From 6d13f85c8b272848f8036fe849a8bd76b3924549 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 16 Jan 2024 15:19:02 +0100 Subject: [PATCH 29/75] Got joint data streaming over the vector collection network --- .../file_reader/signal_provider.py | 111 +++++++++--------- .../plotter/matplotlib_viewer_canvas.py | 6 + robot_log_visualizer/ui/gui.py | 4 +- 3 files changed, 64 insertions(+), 57 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 25c9df1..6fa825d 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -72,10 +72,12 @@ def __init__(self, period: float): self._current_time = 0 self.realtimeBufferReached = False + self.initMetadata = False self.realtimeFixedPlotWindow = 20 # for networking with the real-time logger - self.networkInit = False + self.realtimeNetworkInit = False + self.vectorCollectionsClient = blf.yarp_utilities.VectorsCollectionClient() def __populate_text_logging_data(self, file_object): data = {} @@ -161,87 +163,80 @@ def __populate_numerical_data(self, file_object): return data - def __populateRealtimeLoggerData(self, rawData, input): - data = {} - for key, value in input.items(): - if key not in rawData.keys(): - rawData[key] = value - elif key == "description_list" or key == "yarp_robot_name": - continue - - if value is None: - continue - - if "data" in value.keys() and "timestamps" in value.keys(): - data[key] = {} - rawData[key]["data"] = np.append(rawData[key]["data"], np.array(value["data"])).reshape(-1, len(value["data"])) - rawData[key]["timestamps"] = np.append(rawData[key]["timestamps"], np.array(value["timestamps"])) - - if rawData[key]["timestamps"][0] < self.initial_time: - self.timestamps = rawData[key]["timestamps"] - self.initial_time = self.timestamps[0] - - if rawData[key]["timestamps"][-1] > self.end_time: - self.timestamps = rawData[key]["timestamps"] - self.end_time = self.timestamps[-1] - - if self.end_time - self.initial_time >= self.realtimeFixedPlotWindow: - self.realtimeBufferReached = True - tempInitialTime = self.initial_time - tempEndTime = self.end_time - while tempEndTime - tempInitialTime >= self.realtimeFixedPlotWindow: - rawData[key]["data"] = np.delete(rawData[key]["data"], 0, axis=0) - rawData[key]["timestamps"] = np.delete(rawData[key]["timestamps"], 0) - tempInitialTime = rawData[key]["timestamps"][0] - tempEndTime = rawData[key]["timestamps"][-1] - - if "elements_names" in value.keys(): - rawData[key]["elements_names"] = value["elements_names"] - - - else: - data[key] = self.__populateRealtimeLoggerData(rawData=rawData[key],input=value) - - return data + def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): + if keys[0] not in rawData: + rawData[keys[0]] = {} + + if len(keys) == 1: + if "data" not in rawData[keys[0]]: + rawData[keys[0]]["data"] = np.array([]) + rawData[keys[0]]["data"] = np.append(rawData[keys[0]]["data"], value).reshape(-1, len(value)) + if "timestamps" not in rawData[keys[0]]: + rawData[keys[0]]["timestamps"] = np.array([]) + rawData[keys[0]]["timestamps"] = np.append(rawData[keys[0]]["timestamps"], recentTimestamp) + + else: + self.__populateRealtimeLoggerData(rawData[keys[0]], keys[1:], value, recentTimestamp) + + def __populateRealtimeLoggerMetadata(self, rawData, keys, value): + if keys[0] not in rawData: + rawData[keys[0]] = {} + + if len(keys) == 1: + if "elements_names" not in rawData[keys[0]]: + rawData[keys[0]]["elements_names"] = np.array([]) + #print(np.append(rawData[keys[0]]["elements_names"], value)) + rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value)#.reshape(-1, len(rawData[keys[0]])) + else: + self.__populateRealtimeLoggerMetadata(rawData[keys[0]], keys[1:], value) def establish_connection(self): - if not self.networkInit: + if not self.realtimeNetworkInit: yarp.Network.init() #self.loggingInput = yarp.BufferedPortBottle() #self.loggingInput.open("/visualizerInput:i") #yarp.Network.connect("/YARPRobotLoggerRT:o", "/visualizerInput:i") - - vectorCollectionsClient = blf.yarp_utilities.VectorsCollectionClient() + param_handler = blf.parameters_handler.YarpParametersHandler() param_handler.set_parameter_string("remote", "/testVectorCollections") # you must have some local port as well param_handler.set_parameter_string("local", "/visualizerInput:i") # remote must match the server param_handler.set_parameter_string("carrier", "udp") - print("Just set the parameter string") - vectorCollectionsClient.initialize(param_handler) - print("just initialized the vectorCollectionsClient") + self.vectorCollectionsClient.initialize(param_handler) - vectorCollectionsClient.connect() - print("Just tried to connect to the vector Collections Client") + self.vectorCollectionsClient.connect() + self.realtimeNetworkInit = True + input = self.vectorCollectionsClient.readData(True) + metadata = self.vectorCollectionsClient.getMetadata() - self.networkInit = True - print("About to read data from the vectors colleciton client") - input = vectorCollectionsClient.readData(True) - print("Successfully read the data: " + str(input)) if not input: print("Failed to read realtime YARP port, closing") return False else: # json.loads is done twice, the 1st time is to remove escape characters # the 2nd time actually converts the string to the dictionary - self.__populateRealtimeLoggerData(self.data, input) + if not self.initMetadata: + for keyString, value in metadata.items(): + keys = keyString.split("::") + self.__populateRealtimeLoggerMetadata(self.data, keys, value) + self.initMetadata = True + + recentTimestamp = input["robot_realtime::timestamps"][0] + for keyString, value in input.items(): + keys = keyString.split("::") + self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) + self.timestamps = np.append(self.timestamps, recentTimestamp) if self.realtimeBufferReached: self.initial_time = self.timestamps[0] self.end_time = self.timestamps[-1] self.timestamps = np.delete(self.timestamps, 0) self.realtimeBufferReached = False + else: + self.initial_time = self.timestamps[0] + self.end_time = self.timestamps[-1] self.joints_name = self.data["robot_realtime"]["description_list"] + #print(self.data) return True def open_mat_file(self, file_name: str): @@ -346,6 +341,10 @@ def get_item_from_path_at_index(self, path, index, default_path=None): if data is None: return None closest_index = np.argmin(np.abs(timestamps - self.timestamps[index])) + # if the realtime network is running + if self.realtimeNetworkInit: + return data + return data[closest_index, :] def get_robot_state_at_index(self, index): diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 61ec1c3..a447110 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -81,8 +81,14 @@ def update_plots(self, paths, legends, realtimePlot=False): # This happens in the case the variable is a scalar. datapoints = data["data"][:] + print(data["timestamps"]) timestamps = data["timestamps"] - self.signal_provider.initial_time + print("Timestamps:") + print(timestamps) + print("Datapoints:") + print(datapoints) + (self.active_paths[path_string],) = self.axes.plot( timestamps, datapoints, label=legend_string ) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 8bcb188..b8d9f39 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -745,7 +745,9 @@ def connect_realtime_logger(self): # load the model self.signal_provider.joints_name = self.signal_provider.data["robot_realtime"]["description_list"] - self.signal_provider.robot_name = self.signal_provider.data["robot_realtime"]["yarp_robot_name"][0] + print("Now printing the robot names") + print(self.signal_provider.data["robot_realtime"]["yarp_robot_name"]["elements_names"][0]) + self.signal_provider.robot_name = self.signal_provider.data["robot_realtime"]["yarp_robot_name"]["elements_names"][0] if not self.meshcat_provider.load_model( self.signal_provider.joints_name, self.signal_provider.robot_name ): From 97d2a036baca0256d9e8d5ee5cb414121fa07e87 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 17 Jan 2024 12:12:30 +0100 Subject: [PATCH 30/75] Got mesh updating in realtime, cleaned up the code --- robot_log_visualizer/file_reader/signal_provider.py | 12 ++---------- .../plotter/matplotlib_viewer_canvas.py | 6 ------ .../robot_visualizer/meshcat_provider.py | 5 +---- robot_log_visualizer/ui/gui.py | 4 +--- 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 6fa825d..b54b814 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -185,8 +185,7 @@ def __populateRealtimeLoggerMetadata(self, rawData, keys, value): if len(keys) == 1: if "elements_names" not in rawData[keys[0]]: rawData[keys[0]]["elements_names"] = np.array([]) - #print(np.append(rawData[keys[0]]["elements_names"], value)) - rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value)#.reshape(-1, len(rawData[keys[0]])) + rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value) else: self.__populateRealtimeLoggerMetadata(rawData[keys[0]], keys[1:], value) @@ -194,9 +193,6 @@ def __populateRealtimeLoggerMetadata(self, rawData, keys, value): def establish_connection(self): if not self.realtimeNetworkInit: yarp.Network.init() - #self.loggingInput = yarp.BufferedPortBottle() - #self.loggingInput.open("/visualizerInput:i") - #yarp.Network.connect("/YARPRobotLoggerRT:o", "/visualizerInput:i") param_handler = blf.parameters_handler.YarpParametersHandler() param_handler.set_parameter_string("remote", "/testVectorCollections") # you must have some local port as well @@ -235,8 +231,7 @@ def establish_connection(self): else: self.initial_time = self.timestamps[0] self.end_time = self.timestamps[-1] - self.joints_name = self.data["robot_realtime"]["description_list"] - #print(self.data) + self.joints_name = self.data["robot_realtime"]["description_list"]["elements_names"] return True def open_mat_file(self, file_name: str): @@ -341,9 +336,6 @@ def get_item_from_path_at_index(self, path, index, default_path=None): if data is None: return None closest_index = np.argmin(np.abs(timestamps - self.timestamps[index])) - # if the realtime network is running - if self.realtimeNetworkInit: - return data return data[closest_index, :] diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index a447110..61ec1c3 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -81,14 +81,8 @@ def update_plots(self, paths, legends, realtimePlot=False): # This happens in the case the variable is a scalar. datapoints = data["data"][:] - print(data["timestamps"]) timestamps = data["timestamps"] - self.signal_provider.initial_time - print("Timestamps:") - print(timestamps) - print("Datapoints:") - print(datapoints) - (self.active_paths[path_string],) = self.axes.plot( timestamps, datapoints, label=legend_string ) diff --git a/robot_log_visualizer/robot_visualizer/meshcat_provider.py b/robot_log_visualizer/robot_visualizer/meshcat_provider.py index 1aae8ca..293b520 100644 --- a/robot_log_visualizer/robot_visualizer/meshcat_provider.py +++ b/robot_log_visualizer/robot_visualizer/meshcat_provider.py @@ -201,12 +201,9 @@ def run(self): # For the real-time logger def updateMeshRealtime(self): - base_rotation = np.eye(3) - base_position = np.array([0.0, 0.0, 0.0]) - self._signal_provider.index = len(self._signal_provider.timestamps) - 1 robot_state = self._signal_provider.get_robot_state_at_index(self._signal_provider.index) - # These are the robot measured joint positions in radians + self._meshcat_visualizer.set_multibody_system_state( base_position=robot_state["base_position"], base_rotation=robot_state["base_orientation"], diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index b8d9f39..31c109b 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -744,9 +744,7 @@ def connect_realtime_logger(self): # load the model - self.signal_provider.joints_name = self.signal_provider.data["robot_realtime"]["description_list"] - print("Now printing the robot names") - print(self.signal_provider.data["robot_realtime"]["yarp_robot_name"]["elements_names"][0]) + self.signal_provider.joints_name = self.signal_provider.data["robot_realtime"]["description_list"]["elements_names"].tolist() self.signal_provider.robot_name = self.signal_provider.data["robot_realtime"]["yarp_robot_name"]["elements_names"][0] if not self.meshcat_provider.load_model( self.signal_provider.joints_name, self.signal_provider.robot_name From 56c69edc636935cdc7d84d025ad5a576f6a10a2c Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 17 Jan 2024 13:00:04 +0100 Subject: [PATCH 31/75] Removed unnecessary values from the tree window --- robot_log_visualizer/file_reader/signal_provider.py | 11 +++++++++-- robot_log_visualizer/ui/gui.py | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index b54b814..c78333a 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -212,13 +212,20 @@ def establish_connection(self): else: # json.loads is done twice, the 1st time is to remove escape characters # the 2nd time actually converts the string to the dictionary + recentTimestamp = input["robot_realtime::timestamps"][0] + del metadata["robot_realtime::timestamps"] + del input["robot_realtime::timestamps"] if not self.initMetadata: for keyString, value in metadata.items(): keys = keyString.split("::") self.__populateRealtimeLoggerMetadata(self.data, keys, value) + self.joints_name = self.data["robot_realtime"]["description_list"]["elements_names"].tolist() + self.robot_name = self.data["robot_realtime"]["yarp_robot_name"]["elements_names"][0] + del self.data["robot_realtime"]["description_list"] + del self.data["robot_realtime"]["yarp_robot_name"] self.initMetadata = True + - recentTimestamp = input["robot_realtime::timestamps"][0] for keyString, value in input.items(): keys = keyString.split("::") self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) @@ -231,7 +238,7 @@ def establish_connection(self): else: self.initial_time = self.timestamps[0] self.end_time = self.timestamps[-1] - self.joints_name = self.data["robot_realtime"]["description_list"]["elements_names"] + return True def open_mat_file(self, file_name: str): diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 31c109b..aaf339b 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -744,8 +744,8 @@ def connect_realtime_logger(self): # load the model - self.signal_provider.joints_name = self.signal_provider.data["robot_realtime"]["description_list"]["elements_names"].tolist() - self.signal_provider.robot_name = self.signal_provider.data["robot_realtime"]["yarp_robot_name"]["elements_names"][0] + # self.signal_provider.joints_name = self.signal_provider.data["robot_realtime"]["description_list"]["elements_names"].tolist() + # self.signal_provider.robot_name = self.signal_provider.data["robot_realtime"]["yarp_robot_name"]["elements_names"][0] if not self.meshcat_provider.load_model( self.signal_provider.joints_name, self.signal_provider.robot_name ): From da334196858baaa52c8c9a031ff227300d1f5321 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 17 Jan 2024 16:56:01 +0100 Subject: [PATCH 32/75] plot scaling now updating correctly --- .../file_reader/signal_provider.py | 22 +++++++++++++------ .../plotter/matplotlib_viewer_canvas.py | 1 + 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index c78333a..c39b106 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -174,6 +174,15 @@ def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): if "timestamps" not in rawData[keys[0]]: rawData[keys[0]]["timestamps"] = np.array([]) 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) @@ -213,6 +222,7 @@ def establish_connection(self): # json.loads is done twice, the 1st time is to remove escape characters # the 2nd time actually converts the string to the dictionary recentTimestamp = input["robot_realtime::timestamps"][0] + self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) del metadata["robot_realtime::timestamps"] del input["robot_realtime::timestamps"] if not self.initMetadata: @@ -229,15 +239,13 @@ def establish_connection(self): for keyString, value in input.items(): keys = keyString.split("::") self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) - self.timestamps = np.append(self.timestamps, recentTimestamp) - if self.realtimeBufferReached: - self.initial_time = self.timestamps[0] - self.end_time = self.timestamps[-1] - self.timestamps = np.delete(self.timestamps, 0) - self.realtimeBufferReached = False - else: + + 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] return True diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 61ec1c3..c0f7a6d 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -99,6 +99,7 @@ def update_plots(self, paths, legends, realtimePlot=False): self.active_paths.pop(path) if realtimePlot: + #self.axes.autoscale() self.axes.set_xlim(0, self.signal_provider.realtimeFixedPlotWindow) else: self.axes.set_xlim( From 27eb8587c6c594065be8ff5236f275fdaebd8111 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 17 Jan 2024 19:18:54 +0100 Subject: [PATCH 33/75] Flight data streaming --- robot_log_visualizer/file_reader/signal_provider.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index c39b106..ee5c714 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -192,7 +192,9 @@ def __populateRealtimeLoggerMetadata(self, rawData, keys, value): rawData[keys[0]] = {} if len(keys) == 1: - if "elements_names" not in rawData[keys[0]]: + if len(rawData[keys[0]]) == 0: + return + elif "elements_names" not in rawData[keys[0]]: rawData[keys[0]]["elements_names"] = np.array([]) rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value) else: @@ -214,6 +216,7 @@ def establish_connection(self): input = self.vectorCollectionsClient.readData(True) metadata = self.vectorCollectionsClient.getMetadata() + print(metadata) if not input: print("Failed to read realtime YARP port, closing") @@ -226,11 +229,15 @@ def establish_connection(self): del metadata["robot_realtime::timestamps"] del input["robot_realtime::timestamps"] if not self.initMetadata: + self.joints_name = metadata["robot_realtime::description_list"] + self.robot_name = metadata["robot_realtime::yarp_robot_name"][0] for keyString, value in metadata.items(): keys = keyString.split("::") self.__populateRealtimeLoggerMetadata(self.data, keys, value) - self.joints_name = self.data["robot_realtime"]["description_list"]["elements_names"].tolist() - self.robot_name = self.data["robot_realtime"]["yarp_robot_name"]["elements_names"][0] + print("Joints name:") + print(self.joints_name) + print("robot name:") + print(self.robot_name) del self.data["robot_realtime"]["description_list"] del self.data["robot_realtime"]["yarp_robot_name"] self.initMetadata = True From 0681e5acdc989e4c91c7c20094801cb4e83c5c08 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 18 Jan 2024 12:16:59 +0100 Subject: [PATCH 34/75] Got naming corrected --- .../file_reader/signal_provider.py | 43 +++++++++++-------- robot_log_visualizer/ui/gui.py | 6 +++ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index ee5c714..2df9688 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -126,6 +126,7 @@ def __populate_text_logging_data(self, file_object): return data def __populate_numerical_data(self, file_object): + print("Populating data!!!") data = {} for key, value in file_object.items(): if not isinstance(value, h5py._hl.group.Group): @@ -192,9 +193,10 @@ def __populateRealtimeLoggerMetadata(self, rawData, keys, value): rawData[keys[0]] = {} if len(keys) == 1: - if len(rawData[keys[0]]) == 0: + if len(value) == 0: + del rawData[keys[0]] return - elif "elements_names" not in rawData[keys[0]]: + if "elements_names" not in rawData[keys[0]]: rawData[keys[0]]["elements_names"] = np.array([]) rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value) else: @@ -205,18 +207,35 @@ def establish_connection(self): if not self.realtimeNetworkInit: yarp.Network.init() + print("Initializing the yarp network") param_handler = blf.parameters_handler.YarpParametersHandler() param_handler.set_parameter_string("remote", "/testVectorCollections") # you must have some local port as well - param_handler.set_parameter_string("local", "/visualizerInput:i") # remote must match the server + param_handler.set_parameter_string("local", "/visualizerInput") # remote must match the server param_handler.set_parameter_string("carrier", "udp") + print("About to initialize the client") self.vectorCollectionsClient.initialize(param_handler) + print("client initialized") self.vectorCollectionsClient.connect() + print("client connected") self.realtimeNetworkInit = True + metadata = self.vectorCollectionsClient.getMetadata() + if not metadata: + print("Failed to read realtime YARP port, closing") + return False + + self.joints_name = metadata["robot_realtime::description_list"] + self.robot_name = metadata["robot_realtime::yarp_robot_name"][0] + for keyString, value in metadata.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"] + + print("About to read input data") input = self.vectorCollectionsClient.readData(True) - metadata = self.vectorCollectionsClient.getMetadata() - print(metadata) + print("input data was read") if not input: print("Failed to read realtime YARP port, closing") @@ -226,21 +245,7 @@ def establish_connection(self): # the 2nd time actually converts the string to the dictionary recentTimestamp = input["robot_realtime::timestamps"][0] self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) - del metadata["robot_realtime::timestamps"] del input["robot_realtime::timestamps"] - if not self.initMetadata: - self.joints_name = metadata["robot_realtime::description_list"] - self.robot_name = metadata["robot_realtime::yarp_robot_name"][0] - for keyString, value in metadata.items(): - keys = keyString.split("::") - self.__populateRealtimeLoggerMetadata(self.data, keys, value) - print("Joints name:") - print(self.joints_name) - print("robot name:") - print(self.robot_name) - del self.data["robot_realtime"]["description_list"] - del self.data["robot_realtime"]["yarp_robot_name"] - self.initMetadata = True for keyString, value in input.items(): diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index aaf339b..20eb175 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -687,8 +687,13 @@ def open_mat_file(self): self.__load_mat_file(file_name) def maintain_connection(self, root): + timeoutCounter = 0 + print("Start are maintain connection") while self.realtimeConnectionEnabled and yarp.Network.exists("/YARPRobotLoggerRT:o"): if not self.signal_provider.establish_connection(): + print("counter increasing") + time.sleep(0.1) + timeoutCounter = timeoutCounter + 1 continue # populate text logging tree @@ -718,6 +723,7 @@ def maintain_connection(self, root): time.sleep(self.animation_period + self.sleepPeriodBuffer) self.meshcat_provider.updateMeshRealtime() + print("Outside of while loop") def connect_realtime_logger(self): self.realtimeConnectionEnabled = True From 1631469c16dfebab558dc369d8390f8f92d23848 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 18 Jan 2024 12:28:13 +0100 Subject: [PATCH 35/75] Fixed freezing when server is not running --- robot_log_visualizer/ui/gui.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 20eb175..bd222ba 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -691,10 +691,7 @@ def maintain_connection(self, root): print("Start are maintain connection") while self.realtimeConnectionEnabled and yarp.Network.exists("/YARPRobotLoggerRT:o"): if not self.signal_provider.establish_connection(): - print("counter increasing") - time.sleep(0.1) - timeoutCounter = timeoutCounter + 1 - continue + break # populate text logging tree self.plottingLock.acquire() @@ -731,12 +728,8 @@ def connect_realtime_logger(self): print("Now connecting for real-time logging") # Do initial connection to populate the necessary data - connectionCounter = 0 - while not self.signal_provider.establish_connection() and connectionCounter < self.timeoutAttempts: - time.sleep(0.1) - connectionCounter = connectionCounter + 1 - if connectionCounter == self.timeoutAttempts: - print("Failed to connect, connection timeout") + if not self.signal_provider.establish_connection(): + print("Could not connect to YARP server, closing") return self.meshcat_provider._realtimeMeshUpdate = True # only display one root in the gui From 8d969480da8d5ebb2c2aa7c6513f7d99db012a2e Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 18 Jan 2024 15:40:59 +0100 Subject: [PATCH 36/75] Cleaned up the code a bit --- robot_log_visualizer/file_reader/signal_provider.py | 7 ------- robot_log_visualizer/ui/gui.py | 6 ++---- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 2df9688..49affab 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -126,7 +126,6 @@ def __populate_text_logging_data(self, file_object): return data def __populate_numerical_data(self, file_object): - print("Populating data!!!") data = {} for key, value in file_object.items(): if not isinstance(value, h5py._hl.group.Group): @@ -207,17 +206,13 @@ def establish_connection(self): if not self.realtimeNetworkInit: yarp.Network.init() - print("Initializing the yarp network") param_handler = blf.parameters_handler.YarpParametersHandler() param_handler.set_parameter_string("remote", "/testVectorCollections") # you must have some local port as well param_handler.set_parameter_string("local", "/visualizerInput") # remote must match the server param_handler.set_parameter_string("carrier", "udp") - print("About to initialize the client") self.vectorCollectionsClient.initialize(param_handler) - print("client initialized") self.vectorCollectionsClient.connect() - print("client connected") self.realtimeNetworkInit = True metadata = self.vectorCollectionsClient.getMetadata() if not metadata: @@ -233,9 +228,7 @@ def establish_connection(self): del self.data["robot_realtime"]["yarp_robot_name"] - print("About to read input data") input = self.vectorCollectionsClient.readData(True) - print("input data was read") if not input: print("Failed to read realtime YARP port, closing") diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index bd222ba..2c857ca 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -687,10 +687,9 @@ def open_mat_file(self): self.__load_mat_file(file_name) def maintain_connection(self, root): - timeoutCounter = 0 - print("Start are maintain connection") - while self.realtimeConnectionEnabled and yarp.Network.exists("/YARPRobotLoggerRT:o"): + while self.realtimeConnectionEnabled: if not self.signal_provider.establish_connection(): + self.realtimeConnectionEnabled = False break # populate text logging tree @@ -720,7 +719,6 @@ def maintain_connection(self, root): time.sleep(self.animation_period + self.sleepPeriodBuffer) self.meshcat_provider.updateMeshRealtime() - print("Outside of while loop") def connect_realtime_logger(self): self.realtimeConnectionEnabled = True From a8538d6f58246124cbfa0264361167734ae7ac80 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 18 Jan 2024 15:41:13 +0100 Subject: [PATCH 37/75] Added a grid to the plot --- robot_log_visualizer/plotter/matplotlib_viewer_canvas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index c0f7a6d..d804a5e 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -86,7 +86,7 @@ def update_plots(self, paths, legends, realtimePlot=False): (self.active_paths[path_string],) = self.axes.plot( timestamps, datapoints, label=legend_string ) - + self.axes.grid() paths_to_be_canceled = [] for active_path in self.active_paths.keys(): path = active_path.split("/") From 6c6028849a512a448596d1ce8eefd3a3b5a2d8cf Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Thu, 18 Jan 2024 16:04:30 +0100 Subject: [PATCH 38/75] Removed whitespace from signal_provider.py --- robot_log_visualizer/file_reader/signal_provider.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index dacd31e..3489a77 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -170,7 +170,7 @@ def __populate_numerical_data(self, file_object): def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): if keys[0] not in rawData: rawData[keys[0]] = {} - + if len(keys) == 1: if "data" not in rawData[keys[0]]: rawData[keys[0]]["data"] = np.array([]) @@ -187,14 +187,13 @@ def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): 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] not in rawData: rawData[keys[0]] = {} - + if len(keys) == 1: if len(value) == 0: del rawData[keys[0]] @@ -209,7 +208,7 @@ def __populateRealtimeLoggerMetadata(self, rawData, keys, value): def establish_connection(self): if not self.realtimeNetworkInit: yarp.Network.init() - + param_handler = blf.parameters_handler.YarpParametersHandler() param_handler.set_parameter_string("remote", "/testVectorCollections") # you must have some local port as well param_handler.set_parameter_string("local", "/visualizerInput") # remote must match the server @@ -222,7 +221,7 @@ def establish_connection(self): if not metadata: print("Failed to read realtime YARP port, closing") return False - + self.joints_name = metadata["robot_realtime::description_list"] self.robot_name = metadata["robot_realtime::yarp_robot_name"][0] for keyString, value in metadata.items(): @@ -230,7 +229,7 @@ def establish_connection(self): self.__populateRealtimeLoggerMetadata(self.data, keys, value) del self.data["robot_realtime"]["description_list"] del self.data["robot_realtime"]["yarp_robot_name"] - + input = self.vectorCollectionsClient.readData(True) @@ -243,7 +242,7 @@ def establish_connection(self): recentTimestamp = input["robot_realtime::timestamps"][0] self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) del input["robot_realtime::timestamps"] - + for keyString, value in input.items(): keys = keyString.split("::") From 32a05b74b24e2edc28f085d69b4aedf71c61528a Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 22 Jan 2024 16:18:23 +0100 Subject: [PATCH 39/75] Changed realtime port name --- robot_log_visualizer/file_reader/signal_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 49affab..fff9a49 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -207,7 +207,7 @@ def establish_connection(self): yarp.Network.init() param_handler = blf.parameters_handler.YarpParametersHandler() - param_handler.set_parameter_string("remote", "/testVectorCollections") # you must have some local port as well + param_handler.set_parameter_string("remote", "/rtLoggingVectorCollections") # you must have some local port as well param_handler.set_parameter_string("local", "/visualizerInput") # remote must match the server param_handler.set_parameter_string("carrier", "udp") self.vectorCollectionsClient.initialize(param_handler) From 336db8ed446b5ccea743fec4ab3611cd4264794e Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 5 Feb 2024 18:22:42 +0100 Subject: [PATCH 40/75] Changes naming of python functions --- robot_log_visualizer/file_reader/signal_provider.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 8eac7a8..3f8a041 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -217,7 +217,7 @@ def establish_connection(self): self.vectorCollectionsClient.connect() self.realtimeNetworkInit = True - metadata = self.vectorCollectionsClient.getMetadata() + metadata = self.vectorCollectionsClient.get_metadata().getVectors() if not metadata: print("Failed to read realtime YARP port, closing") return False @@ -231,7 +231,7 @@ def establish_connection(self): del self.data["robot_realtime"]["yarp_robot_name"] - input = self.vectorCollectionsClient.readData(True) + input = self.vectorCollectionsClient.read_data(True) if not input: print("Failed to read realtime YARP port, closing") From ad228f1b001fa93c4c67be82f5c66bb7bdaa2f12 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 8 Feb 2024 12:09:42 +0100 Subject: [PATCH 41/75] Edited flag for closing thread properly --- robot_log_visualizer/ui/gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 2cf70df..a5c4a8b 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -729,6 +729,7 @@ def connect_realtime_logger(self): # Do initial connection to populate the necessary data if not self.signal_provider.establish_connection(): print("Could not connect to YARP server, closing") + self.realtimeConnectionEnabled = False return self.meshcat_provider._realtimeMeshUpdate = True # only display one root in the gui From e10efaa562cc95fbda6b8e378a1d20a27136244b Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 9 Feb 2024 10:42:25 +0100 Subject: [PATCH 42/75] Moved enable features outside the loop --- robot_log_visualizer/ui/gui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index a5c4a8b..6602499 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -708,8 +708,6 @@ def maintain_connection(self, root): self.pyconsole.push_local_ns("data", self.signal_provider.data) self.ui.timeSlider.setMaximum(self.signal_size) - self.ui.startButton.setEnabled(True) - self.ui.timeSlider.setEnabled(True) if len(self.plotData) > 0 and len(self.plotData) > self.ui.tabPlotWidget.currentIndex(): self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( @@ -757,6 +755,9 @@ def connect_realtime_logger(self): self.logger.write_to_log(msg) + # Disable these buttons for RT communication + self.ui.startButton.setEnabled(False) + self.ui.timeSlider.setEnabled(False) self.networkThread = threading.Thread(target=self.maintain_connection, args=(root,)) self.networkThread.start() From bfce38b2e295762cb6712279018ece2594d0d49a Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 9 Feb 2024 11:55:44 +0100 Subject: [PATCH 43/75] Removed timestamps from RT view --- robot_log_visualizer/file_reader/signal_provider.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 3f8a041..36dd9a7 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -191,6 +191,8 @@ def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): 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]] = {} From 3b7e98ebccabbca01e0ae9c9166c4b32044aa48c Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 9 Feb 2024 12:00:45 +0100 Subject: [PATCH 44/75] Removed whitespace --- robot_log_visualizer/ui/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 6602499..ada1c95 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -755,7 +755,7 @@ def connect_realtime_logger(self): self.logger.write_to_log(msg) - # Disable these buttons for RT communication + # Disable these buttons for RT communication self.ui.startButton.setEnabled(False) self.ui.timeSlider.setEnabled(False) self.networkThread = threading.Thread(target=self.maintain_connection, args=(root,)) From cadd9e05816925e1a3213498fb70bf7b7219b3e4 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 13 Feb 2024 16:19:49 +0100 Subject: [PATCH 45/75] External signals can now be added at any time --- .../file_reader/signal_provider.py | 28 +++++++++++++++---- robot_log_visualizer/ui/gui.py | 10 +++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 36dd9a7..07a49ef 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -82,6 +82,9 @@ def __init__(self, period: float): self.realtimeNetworkInit = False self.vectorCollectionsClient = blf.yarp_utilities.VectorsCollectionClient() self.trajectory_span = 200 + self.rtMetadataDict = {} + self.updateMetadataVal = 0 + self.updateMetadata = False def __populate_text_logging_data(self, file_object): data = {} @@ -174,9 +177,12 @@ def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): if len(keys) == 1: if "data" not in rawData[keys[0]]: rawData[keys[0]]["data"] = np.array([]) + starterValue = [0.0] * len(value) + rawData[keys[0]]["data"] = np.append(rawData[keys[0]]["data"], starterValue).reshape(-1, len(value)) rawData[keys[0]]["data"] = np.append(rawData[keys[0]]["data"], value).reshape(-1, len(value)) if "timestamps" not in rawData[keys[0]]: rawData[keys[0]]["timestamps"] = np.array([]) + rawData[keys[0]]["timestamps"] = np.append(rawData[keys[0]]["timestamps"], recentTimestamp) rawData[keys[0]]["timestamps"] = np.append(rawData[keys[0]]["timestamps"], recentTimestamp) tempInitialTime = rawData[keys[0]]["timestamps"][0] @@ -219,20 +225,19 @@ def establish_connection(self): self.vectorCollectionsClient.connect() self.realtimeNetworkInit = True - metadata = self.vectorCollectionsClient.get_metadata().getVectors() - if not metadata: + self.rtMetadataDict = self.vectorCollectionsClient.get_metadata().getVectors() + if not self.rtMetadataDict: print("Failed to read realtime YARP port, closing") return False - self.joints_name = metadata["robot_realtime::description_list"] - self.robot_name = metadata["robot_realtime::yarp_robot_name"][0] - for keyString, value in metadata.items(): + 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"] - input = self.vectorCollectionsClient.read_data(True) if not input: @@ -245,10 +250,21 @@ def establish_connection(self): self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) del input["robot_realtime::timestamps"] + print(input["robot_realtime::newMetadata"]) for keyString, value in input.items(): keys = keyString.split("::") self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) + + if int(self.data["robot_realtime"]["newMetadata"]["data"][-1]) != self.updateMetadataVal: + self.updateMetadataVal = int(self.data["robot_realtime"]["newMetadata"]["data"][-1]) + self.updateMetadata = True + metadata = self.vectorCollectionsClient.get_metadata().getVectors() + difference = { k : metadata[k] for k in set(metadata) - set(self.rtMetadataDict) } + for keyString, value in difference.items(): + keys = keyString.split("::") + self.__populateRealtimeLoggerMetadata(self.data, keys, value) + while recentTimestamp - self.timestamps[0] > self.realtimeFixedPlotWindow: self.initial_time = self.timestamps[0] diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index ada1c95..1c818fe 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -703,6 +703,16 @@ def maintain_connection(self, root): self.signal_provider.text_logging_data[root], root_item ) self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) + elif self.signal_provider.updateMetadata: + self.signal_provider.updateMetadata = False + root = list(self.signal_provider.data.keys())[0] + root_item = QTreeWidgetItem([root]) + root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) + items = self.__populate_variable_tree_widget( + self.signal_provider.data[root], root_item + ) + self.ui.variableTreeWidget.clear() + self.ui.variableTreeWidget.insertTopLevelItems(0, [items]) # spawn the console self.pyconsole.push_local_ns("data", self.signal_provider.data) From 0458de23e1c2a98e7b6662736188e390485f6dfa Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 13 Feb 2024 16:25:18 +0100 Subject: [PATCH 46/75] Cleaned up the code --- robot_log_visualizer/file_reader/signal_provider.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 07a49ef..9708e3a 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -250,8 +250,6 @@ def establish_connection(self): self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) del input["robot_realtime::timestamps"] - print(input["robot_realtime::newMetadata"]) - for keyString, value in input.items(): keys = keyString.split("::") self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) From db95f5e20b3c5a238bec107bbae0b1cc70d4535b Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 13 Feb 2024 16:32:18 +0100 Subject: [PATCH 47/75] Removed whitespace --- robot_log_visualizer/file_reader/signal_provider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 9708e3a..33cfaae 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -253,7 +253,6 @@ def establish_connection(self): for keyString, value in input.items(): keys = keyString.split("::") self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) - if int(self.data["robot_realtime"]["newMetadata"]["data"][-1]) != self.updateMetadataVal: self.updateMetadataVal = int(self.data["robot_realtime"]["newMetadata"]["data"][-1]) self.updateMetadata = True From ac368763ea240cbf9d3fcdde7a18b756509c26a7 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 13 Feb 2024 16:57:40 +0100 Subject: [PATCH 48/75] Fixed issue of adding mutiple exogenous signals --- robot_log_visualizer/file_reader/signal_provider.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 33cfaae..ec9a9e2 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -197,7 +197,7 @@ def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): self.__populateRealtimeLoggerData(rawData[keys[0]], keys[1:], value, recentTimestamp) def __populateRealtimeLoggerMetadata(self, rawData, keys, value): - if keys[0] == "timestamps": + if keys[0] == "timestamps" or keys[0] == "newMetadata": return if keys[0] not in rawData: rawData[keys[0]] = {} @@ -258,6 +258,7 @@ def establish_connection(self): self.updateMetadata = True metadata = self.vectorCollectionsClient.get_metadata().getVectors() difference = { k : metadata[k] for k in set(metadata) - set(self.rtMetadataDict) } + self.rtMetadataDict = metadata for keyString, value in difference.items(): keys = keyString.split("::") self.__populateRealtimeLoggerMetadata(self.data, keys, value) From 82b5353b4cf61414b89a8b66c0e4b1d3dec39c2f Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 13 Feb 2024 17:14:10 +0100 Subject: [PATCH 49/75] Removed new metadata flag from being displayed --- robot_log_visualizer/file_reader/signal_provider.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index ec9a9e2..920a0bd 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -197,7 +197,7 @@ def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): self.__populateRealtimeLoggerData(rawData[keys[0]], keys[1:], value, recentTimestamp) def __populateRealtimeLoggerMetadata(self, rawData, keys, value): - if keys[0] == "timestamps" or keys[0] == "newMetadata": + if keys[0] == "timestamps": return if keys[0] not in rawData: rawData[keys[0]] = {} @@ -237,6 +237,7 @@ def establish_connection(self): self.__populateRealtimeLoggerMetadata(self.data, keys, value) del self.data["robot_realtime"]["description_list"] del self.data["robot_realtime"]["yarp_robot_name"] + del self.data["robot_realtime"]["newMetadata"] input = self.vectorCollectionsClient.read_data(True) @@ -250,12 +251,16 @@ def establish_connection(self): self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) del input["robot_realtime::timestamps"] + newMetadataInputVal = input["robot_realtime::newMetadata"][0] + self.updateMetadata = newMetadataInputVal != self.updateMetadataVal + del input["robot_realtime::newMetadata"] + for keyString, value in input.items(): keys = keyString.split("::") self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) - if int(self.data["robot_realtime"]["newMetadata"]["data"][-1]) != self.updateMetadataVal: - self.updateMetadataVal = int(self.data["robot_realtime"]["newMetadata"]["data"][-1]) - self.updateMetadata = True + if self.updateMetadata: #int(self.data["robot_realtime"]["newMetadata"]["data"][-1]) != self.updateMetadataVal: + self.updateMetadataVal = newMetadataInputVal + # self.updateMetadata = True metadata = self.vectorCollectionsClient.get_metadata().getVectors() difference = { k : metadata[k] for k in set(metadata) - set(self.rtMetadataDict) } self.rtMetadataDict = metadata From 7b6f231fc31822d827524c9c2bddb63953a4f15b Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 13 Feb 2024 19:03:44 +0100 Subject: [PATCH 50/75] Cleaned up the code --- robot_log_visualizer/file_reader/signal_provider.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 920a0bd..e65ecdf 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -258,9 +258,8 @@ def establish_connection(self): for keyString, value in input.items(): keys = keyString.split("::") self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) - if self.updateMetadata: #int(self.data["robot_realtime"]["newMetadata"]["data"][-1]) != self.updateMetadataVal: + if self.updateMetadata: self.updateMetadataVal = newMetadataInputVal - # self.updateMetadata = True metadata = self.vectorCollectionsClient.get_metadata().getVectors() difference = { k : metadata[k] for k in set(metadata) - set(self.rtMetadataDict) } self.rtMetadataDict = metadata From 6056056c7bda253e9c6acb00489bfae02b68fe4f Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 16 Feb 2024 17:28:33 +0100 Subject: [PATCH 51/75] Might have fixed element names issue --- robot_log_visualizer/file_reader/signal_provider.py | 13 +++++++------ robot_log_visualizer/ui/gui.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index e65ecdf..162cc03 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -251,6 +251,13 @@ def establish_connection(self): self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) del input["robot_realtime::timestamps"] + 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] + newMetadataInputVal = input["robot_realtime::newMetadata"][0] self.updateMetadata = newMetadataInputVal != self.updateMetadataVal del input["robot_realtime::newMetadata"] @@ -268,12 +275,6 @@ def establish_connection(self): self.__populateRealtimeLoggerMetadata(self.data, keys, value) - 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] return True diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 1c818fe..b204334 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -703,7 +703,7 @@ def maintain_connection(self, root): self.signal_provider.text_logging_data[root], root_item ) self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) - elif self.signal_provider.updateMetadata: + if self.signal_provider.updateMetadata: self.signal_provider.updateMetadata = False root = list(self.signal_provider.data.keys())[0] root_item = QTreeWidgetItem([root]) From 00c9011d16498facc91c56ee77794554fdc1d56a Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Thu, 22 Feb 2024 11:44:35 +0100 Subject: [PATCH 52/75] Fixed other issue regarding element_names --- .../file_reader/signal_provider.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 162cc03..4cac61c 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -175,14 +175,7 @@ def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): rawData[keys[0]] = {} if len(keys) == 1: - if "data" not in rawData[keys[0]]: - rawData[keys[0]]["data"] = np.array([]) - starterValue = [0.0] * len(value) - rawData[keys[0]]["data"] = np.append(rawData[keys[0]]["data"], starterValue).reshape(-1, len(value)) rawData[keys[0]]["data"] = np.append(rawData[keys[0]]["data"], value).reshape(-1, len(value)) - if "timestamps" not in rawData[keys[0]]: - rawData[keys[0]]["timestamps"] = np.array([]) - rawData[keys[0]]["timestamps"] = np.append(rawData[keys[0]]["timestamps"], recentTimestamp) rawData[keys[0]]["timestamps"] = np.append(rawData[keys[0]]["timestamps"], recentTimestamp) tempInitialTime = rawData[keys[0]]["timestamps"][0] @@ -208,6 +201,13 @@ def __populateRealtimeLoggerMetadata(self, rawData, keys, value): return if "elements_names" not in rawData[keys[0]]: rawData[keys[0]]["elements_names"] = np.array([]) + + rawData[keys[0]]["data"] = np.array([]) + # starterValue = [0.0] * len(value) + # rawData[keys[0]]["data"] = np.append(rawData[keys[0]]["data"], starterValue).reshape(-1, len(value)) + rawData[keys[0]]["timestamps"] = np.array([]) + # rawData[keys[0]]["timestamps"] = np.append(rawData[keys[0]]["timestamps"], recentTimestamp) + rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value) else: self.__populateRealtimeLoggerMetadata(rawData[keys[0]], keys[1:], value) @@ -245,12 +245,12 @@ def establish_connection(self): print("Failed to read realtime YARP port, closing") return False else: - # json.loads is done twice, the 1st time is to remove escape characters - # the 2nd time actually converts the string to the dictionary + # Update the timestamps recentTimestamp = input["robot_realtime::timestamps"][0] self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) del 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] @@ -258,13 +258,12 @@ def establish_connection(self): self.initial_time = self.timestamps[0] self.end_time = self.timestamps[-1] + # Check if new metadata arrived for a new signal newMetadataInputVal = input["robot_realtime::newMetadata"][0] self.updateMetadata = newMetadataInputVal != self.updateMetadataVal del input["robot_realtime::newMetadata"] - for keyString, value in input.items(): - keys = keyString.split("::") - self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) + # If there is new metadata, populate it if self.updateMetadata: self.updateMetadataVal = newMetadataInputVal metadata = self.vectorCollectionsClient.get_metadata().getVectors() @@ -274,7 +273,10 @@ def establish_connection(self): keys = keyString.split("::") self.__populateRealtimeLoggerMetadata(self.data, keys, value) - + # Store the new data that comes in + for keyString, value in input.items(): + keys = keyString.split("::") + self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) return True From f633d6dce066b3d7b720ea72d5dbd70da41f0eae Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Mon, 4 Mar 2024 11:06:57 +0100 Subject: [PATCH 53/75] Removed old comments --- robot_log_visualizer/file_reader/signal_provider.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 4cac61c..2036864 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -201,12 +201,8 @@ def __populateRealtimeLoggerMetadata(self, rawData, keys, value): return if "elements_names" not in rawData[keys[0]]: rawData[keys[0]]["elements_names"] = np.array([]) - rawData[keys[0]]["data"] = np.array([]) - # starterValue = [0.0] * len(value) - # rawData[keys[0]]["data"] = np.append(rawData[keys[0]]["data"], starterValue).reshape(-1, len(value)) rawData[keys[0]]["timestamps"] = np.array([]) - # rawData[keys[0]]["timestamps"] = np.append(rawData[keys[0]]["timestamps"], recentTimestamp) rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value) else: From 0d2408cf2d67e2edceb309d74c69b4ffacbe2e9c Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Mon, 4 Mar 2024 15:33:42 +0100 Subject: [PATCH 54/75] Simplified for first part of the PR --- .../file_reader/signal_provider.py | 18 ------------------ robot_log_visualizer/ui/gui.py | 11 ----------- 2 files changed, 29 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 2036864..b9547c2 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -83,8 +83,6 @@ def __init__(self, period: float): self.vectorCollectionsClient = blf.yarp_utilities.VectorsCollectionClient() self.trajectory_span = 200 self.rtMetadataDict = {} - self.updateMetadataVal = 0 - self.updateMetadata = False def __populate_text_logging_data(self, file_object): data = {} @@ -233,7 +231,6 @@ def establish_connection(self): self.__populateRealtimeLoggerMetadata(self.data, keys, value) del self.data["robot_realtime"]["description_list"] del self.data["robot_realtime"]["yarp_robot_name"] - del self.data["robot_realtime"]["newMetadata"] input = self.vectorCollectionsClient.read_data(True) @@ -254,21 +251,6 @@ def establish_connection(self): self.initial_time = self.timestamps[0] self.end_time = self.timestamps[-1] - # Check if new metadata arrived for a new signal - newMetadataInputVal = input["robot_realtime::newMetadata"][0] - self.updateMetadata = newMetadataInputVal != self.updateMetadataVal - del input["robot_realtime::newMetadata"] - - # If there is new metadata, populate it - if self.updateMetadata: - self.updateMetadataVal = newMetadataInputVal - metadata = self.vectorCollectionsClient.get_metadata().getVectors() - difference = { k : metadata[k] for k in set(metadata) - set(self.rtMetadataDict) } - self.rtMetadataDict = metadata - for keyString, value in difference.items(): - keys = keyString.split("::") - self.__populateRealtimeLoggerMetadata(self.data, keys, value) - # Store the new data that comes in for keyString, value in input.items(): keys = keyString.split("::") diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index b204334..8519369 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -703,17 +703,6 @@ def maintain_connection(self, root): self.signal_provider.text_logging_data[root], root_item ) self.ui.yarpTextLogTreeWidget.insertTopLevelItems(0, [items]) - if self.signal_provider.updateMetadata: - self.signal_provider.updateMetadata = False - root = list(self.signal_provider.data.keys())[0] - root_item = QTreeWidgetItem([root]) - root_item.setFlags(root_item.flags() & ~Qt.ItemIsSelectable) - items = self.__populate_variable_tree_widget( - self.signal_provider.data[root], root_item - ) - self.ui.variableTreeWidget.clear() - self.ui.variableTreeWidget.insertTopLevelItems(0, [items]) - # spawn the console self.pyconsole.push_local_ns("data", self.signal_provider.data) From 296ee3814d8eae8a0183c0aa849d50d4a25706de Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Wed, 6 Mar 2024 15:08:23 +0100 Subject: [PATCH 55/75] Changed to account for python bindings --- robot_log_visualizer/file_reader/signal_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index b9547c2..02662d1 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -219,7 +219,7 @@ def establish_connection(self): self.vectorCollectionsClient.connect() self.realtimeNetworkInit = True - self.rtMetadataDict = self.vectorCollectionsClient.get_metadata().getVectors() + self.rtMetadataDict = self.vectorCollectionsClient.get_metadata().vectors if not self.rtMetadataDict: print("Failed to read realtime YARP port, closing") return False From d8b573bf10eeab1d13bfc5720122a4c96524f7c6 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Wed, 6 Mar 2024 15:08:39 +0100 Subject: [PATCH 56/75] Changed variable name --- robot_log_visualizer/file_reader/signal_provider.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 02662d1..66ab2b5 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -232,16 +232,16 @@ def establish_connection(self): del self.data["robot_realtime"]["description_list"] del self.data["robot_realtime"]["yarp_robot_name"] - input = self.vectorCollectionsClient.read_data(True) + vc_input = self.vectorCollectionsClient.read_data(True) - if not input: + if not vc_input: print("Failed to read realtime YARP port, closing") return False else: # Update the timestamps - recentTimestamp = input["robot_realtime::timestamps"][0] + recentTimestamp = vc_input["robot_realtime::timestamps"][0] self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) - del input["robot_realtime::timestamps"] + del vc_input["robot_realtime::timestamps"] # Keep the data within the fixed time interval while recentTimestamp - self.timestamps[0] > self.realtimeFixedPlotWindow: @@ -252,7 +252,7 @@ def establish_connection(self): self.end_time = self.timestamps[-1] # Store the new data that comes in - for keyString, value in input.items(): + for keyString, value in vc_input.items(): keys = keyString.split("::") self.__populateRealtimeLoggerData(self.data, keys, value, recentTimestamp) From 059c6d6c8d741f06842d21e4c01166f55a0a33e4 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Wed, 13 Mar 2024 17:23:56 +0100 Subject: [PATCH 57/75] Changed function names to make more sense --- robot_log_visualizer/file_reader/signal_provider.py | 2 +- robot_log_visualizer/ui/gui.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 66ab2b5..9e6410a 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -207,7 +207,7 @@ def __populateRealtimeLoggerMetadata(self, rawData, keys, value): self.__populateRealtimeLoggerMetadata(rawData[keys[0]], keys[1:], value) - def establish_connection(self): + def maintain_connection(self): if not self.realtimeNetworkInit: yarp.Network.init() diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 617bd0a..bb22b89 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -694,9 +694,9 @@ def open_mat_file(self): if file_name: self.__load_mat_file(file_name) - def maintain_connection(self, root): + def establish_connection(self, root): while self.realtimeConnectionEnabled: - if not self.signal_provider.establish_connection(): + if not self.signal_provider.maintain_connection(): self.realtimeConnectionEnabled = False break @@ -731,7 +731,7 @@ def connect_realtime_logger(self): print("Now connecting for real-time logging") # Do initial connection to populate the necessary data - if not self.signal_provider.establish_connection(): + if not self.signal_provider.maintain_connection(): print("Could not connect to YARP server, closing") self.realtimeConnectionEnabled = False return @@ -764,7 +764,7 @@ def connect_realtime_logger(self): # Disable these buttons for RT communication self.ui.startButton.setEnabled(False) self.ui.timeSlider.setEnabled(False) - self.networkThread = threading.Thread(target=self.maintain_connection, args=(root,)) + self.networkThread = threading.Thread(target=self.establish_connection, args=(root,)) self.networkThread.start() From 621ce36875f252a615bdd75bed7c0f5d7653b325 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Mon, 8 Apr 2024 17:39:45 +0200 Subject: [PATCH 58/75] Rebased for recent changes --- .../plotter/matplotlib_viewer_canvas.py | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 2e90ecf..0793902 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -185,24 +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() + 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, @@ -211,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("/") @@ -232,8 +243,10 @@ def update_plots(self, paths, legends): # 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() From b98edec639ecd73e9b059b9f18d717be94ff8c31 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 14:21:05 +0200 Subject: [PATCH 59/75] Removed print statements --- robot_log_visualizer/file_reader/signal_provider.py | 2 -- robot_log_visualizer/ui/gui.py | 1 - 2 files changed, 3 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 9e6410a..692dcbd 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -136,10 +136,8 @@ def __populate_numerical_data(self, file_object): if not isinstance(value, h5py._hl.group.Group): continue if key == "#refs#": - print("Skipping for refs") continue if key == "log": - print("Skipping for log") continue if "data" in value.keys(): data[key] = {} diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index bb22b89..427fb91 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -728,7 +728,6 @@ def establish_connection(self, root): def connect_realtime_logger(self): self.realtimeConnectionEnabled = True self.signal_provider.root_name = "robot_realtime" - print("Now connecting for real-time logging") # Do initial connection to populate the necessary data if not self.signal_provider.maintain_connection(): From 221711ac085da94314833c52b3903712436815b5 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 14:21:52 +0200 Subject: [PATCH 60/75] Removed whitespace Co-authored-by: Giulio Romualdi --- robot_log_visualizer/robot_visualizer/meshcat_provider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/robot_log_visualizer/robot_visualizer/meshcat_provider.py b/robot_log_visualizer/robot_visualizer/meshcat_provider.py index 42592b4..5d41427 100644 --- a/robot_log_visualizer/robot_visualizer/meshcat_provider.py +++ b/robot_log_visualizer/robot_visualizer/meshcat_provider.py @@ -177,7 +177,6 @@ def run(self): index = self._signal_provider.index if self.state == PeriodicThreadState.running and self._is_model_loaded: robot_state = self._signal_provider.get_robot_state_at_index(index) - self.meshcat_visualizer_mutex.lock() # These are the robot measured joint positions in radians self._meshcat_visualizer.set_multibody_system_state( From 49880bfdf103aa6ba4ce011ac934853ea07ac610 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 14:24:40 +0200 Subject: [PATCH 61/75] Edited to only clear plot if rt is enabled --- robot_log_visualizer/plotter/matplotlib_viewer_canvas.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 0793902..89b0621 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -186,7 +186,8 @@ def on_pick(self, event): ) def update_plots(self, paths, legends, realtimePlot): - self.axes.cla() + if realtimePlot: + self.axes.cla() realtimeColorIndex = 0 for path, legend in zip(paths, legends): path_string = "/".join(path) From 8cb0209881cca66585919aca7debce6232e8ebbe Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 14:43:11 +0200 Subject: [PATCH 62/75] Removed prints, moved some prints to integrated console screen --- robot_log_visualizer/file_reader/signal_provider.py | 2 -- robot_log_visualizer/ui/gui.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 692dcbd..cd7c2d3 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -219,7 +219,6 @@ def maintain_connection(self): self.realtimeNetworkInit = True self.rtMetadataDict = self.vectorCollectionsClient.get_metadata().vectors if not self.rtMetadataDict: - print("Failed to read realtime YARP port, closing") return False self.joints_name = self.rtMetadataDict["robot_realtime::description_list"] @@ -233,7 +232,6 @@ def maintain_connection(self): vc_input = self.vectorCollectionsClient.read_data(True) if not vc_input: - print("Failed to read realtime YARP port, closing") return False else: # Update the timestamps diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 427fb91..414f50d 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -731,7 +731,7 @@ def connect_realtime_logger(self): # Do initial connection to populate the necessary data if not self.signal_provider.maintain_connection(): - print("Could not connect to YARP server, closing") + self.text_logger.add_entry("Could not connect to YARP server", time.time()) self.realtimeConnectionEnabled = False return self.meshcat_provider._realtimeMeshUpdate = True From 989426c520e327423d3f9a3cab16e4dd69ee344e Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 14:45:41 +0200 Subject: [PATCH 63/75] Removed unused function --- robot_log_visualizer/ui/plot_item.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/robot_log_visualizer/ui/plot_item.py b/robot_log_visualizer/ui/plot_item.py index 5bc7ca5..b9ada3e 100644 --- a/robot_log_visualizer/ui/plot_item.py +++ b/robot_log_visualizer/ui/plot_item.py @@ -18,9 +18,3 @@ def __init__(self, signal_provider, period): parent=self, period=period, signal_provider=signal_provider ) self.ui.plotLayout.addWidget(self.canvas) - - def updatePlotItem(self, signal_provider, period): - self.canvas = MatplotlibViewerCanvas( - parent=self, period=period, signal_provider=signal_provider - ) - self.ui.plotLayout.addWidget(self.canvas) From 40f406bb70f9b4b043306ae0f33f3534598d7f65 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 14:48:20 +0200 Subject: [PATCH 64/75] Removed unused variables/imports --- robot_log_visualizer/file_reader/signal_provider.py | 2 -- robot_log_visualizer/robot_visualizer/meshcat_provider.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index cd7c2d3..552a059 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -2,7 +2,6 @@ # 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 @@ -15,7 +14,6 @@ # for real-time logging import yarp -import json class TextLoggingMsg: diff --git a/robot_log_visualizer/robot_visualizer/meshcat_provider.py b/robot_log_visualizer/robot_visualizer/meshcat_provider.py index 5d41427..fe08297 100644 --- a/robot_log_visualizer/robot_visualizer/meshcat_provider.py +++ b/robot_log_visualizer/robot_visualizer/meshcat_provider.py @@ -37,8 +37,6 @@ def __init__(self, signal_provider, period): self._registered_3d_points = set() self._registered_3d_trajectories = dict() - self._realtimeMeshUpdate = False - @property def state(self): locker = QMutexLocker(self.state_lock) From fa4282f9254f65e58a67bfbdc7549f33645cf087 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 15:11:14 +0200 Subject: [PATCH 65/75] Fixed bug with text logging --- robot_log_visualizer/ui/gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 414f50d..95f86e1 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -731,8 +731,8 @@ def connect_realtime_logger(self): # Do initial connection to populate the necessary data if not self.signal_provider.maintain_connection(): - self.text_logger.add_entry("Could not connect to YARP server", time.time()) - self.realtimeConnectionEnabled = False + self.logger.write_to_log("Could not connect to the real-time logger.") + self.realtime_connection_enabled = False return self.meshcat_provider._realtimeMeshUpdate = True # only display one root in the gui From 0b033baad293d2508cf47ab421e2a9a77ab4ea82 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 15:11:31 +0200 Subject: [PATCH 66/75] Converted to snake_case --- .../file_reader/signal_provider.py | 90 +++++++++---------- .../plotter/matplotlib_viewer_canvas.py | 8 +- .../robot_visualizer/meshcat_provider.py | 2 +- robot_log_visualizer/ui/gui.py | 24 ++--- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 552a059..527a6d8 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -74,13 +74,13 @@ def __init__(self, period: float): self.realtimeBufferReached = False self.initMetadata = False - self.realtimeFixedPlotWindow = 20 + self.realtime_fixed_plot_window = 20 # for networking with the real-time logger - self.realtimeNetworkInit = False - self.vectorCollectionsClient = blf.yarp_utilities.VectorsCollectionClient() + self.realtime_network_init = False + self.vector_collections_client = blf.yarp_utilities.VectorsCollectionClient() self.trajectory_span = 200 - self.rtMetadataDict = {} + self.rt_metadata_dict = {} def __populate_text_logging_data(self, file_object): data = {} @@ -164,81 +164,81 @@ def __populate_numerical_data(self, file_object): return data - def __populateRealtimeLoggerData(self, rawData, keys, value, recentTimestamp): - if keys[0] not in rawData: - rawData[keys[0]] = {} + 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: - 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) + 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) - 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] + 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.__populateRealtimeLoggerData(rawData[keys[0]], keys[1:], value, recentTimestamp) + self.__populate_realtime_logger_data(raw_data[keys[0]], keys[1:], value, recent_timestamp) - def __populateRealtimeLoggerMetadata(self, rawData, keys, value): + def __populate_realtime_logger_metadata(self, raw_data, keys, value): if keys[0] == "timestamps": return - if keys[0] not in rawData: - rawData[keys[0]] = {} + if keys[0] not in raw_data: + raw_data[keys[0]] = {} if len(keys) == 1: if len(value) == 0: - del rawData[keys[0]] + del raw_data[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([]) + 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([]) - rawData[keys[0]]["elements_names"] = np.append(rawData[keys[0]]["elements_names"], value) + raw_data[keys[0]]["elements_names"] = np.append(raw_data[keys[0]]["elements_names"], value) else: - self.__populateRealtimeLoggerMetadata(rawData[keys[0]], keys[1:], value) + self.__populate_realtime_logger_metadata(raw_data[keys[0]], keys[1:], value) def maintain_connection(self): - if not self.realtimeNetworkInit: + if not self.realtime_network_init: yarp.Network.init() param_handler = blf.parameters_handler.YarpParametersHandler() param_handler.set_parameter_string("remote", "/rtLoggingVectorCollections") # you must have some local port as well 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.vector_collections_client.initialize(param_handler) - self.vectorCollectionsClient.connect() - self.realtimeNetworkInit = True - self.rtMetadataDict = self.vectorCollectionsClient.get_metadata().vectors - if not self.rtMetadataDict: + self.vector_collections_client.connect() + self.realtime_network_init = True + self.rt_metadata_dict = self.vector_collections_client.get_metadata().vectors + if not self.rt_metadata_dict: 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) + 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.vectorCollectionsClient.read_data(True) + vc_input = self.vector_collections_client.read_data(True) if not vc_input: return False else: # Update the timestamps - recentTimestamp = vc_input["robot_realtime::timestamps"][0] - self.timestamps = np.append(self.timestamps, recentTimestamp).reshape(-1) + 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 recentTimestamp - self.timestamps[0] > self.realtimeFixedPlotWindow: + 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) @@ -246,9 +246,9 @@ def maintain_connection(self): 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) + 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 diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 89b0621..1a42fe6 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -185,8 +185,8 @@ def on_pick(self, event): blit=True, ) - def update_plots(self, paths, legends, realtimePlot): - if realtimePlot: + def update_plots(self, paths, legends, realtime_plot): + if realtime_plot: self.axes.cla() realtimeColorIndex = 0 for path, legend in zip(paths, legends): @@ -204,7 +204,7 @@ def update_plots(self, paths, legends, realtimePlot): timestamps = data["timestamps"] - self.signal_provider.initial_time - if realtimePlot: + if realtime_plot: (self.active_paths[path_string],) = self.axes.plot( timestamps, datapoints, @@ -234,7 +234,7 @@ def update_plots(self, paths, legends, realtimePlot): self.active_paths[path].remove() self.active_paths.pop(path) - if realtimePlot: + if realtime_plot: #self.axes.autoscale() self.axes.set_xlim(0, self.signal_provider.realtimeFixedPlotWindow) else: diff --git a/robot_log_visualizer/robot_visualizer/meshcat_provider.py b/robot_log_visualizer/robot_visualizer/meshcat_provider.py index fe08297..a723d17 100644 --- a/robot_log_visualizer/robot_visualizer/meshcat_provider.py +++ b/robot_log_visualizer/robot_visualizer/meshcat_provider.py @@ -226,7 +226,7 @@ def run(self): return # For the real-time logger - def updateMeshRealtime(self): + 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) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 95f86e1..6dde3ac 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -135,7 +135,7 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): self.realtimePlotUpdaterThreadActive = False self.plotData = {} self.plottingLock = threading.Lock() - self.realtimeConnectionEnabled = False + self.realtime_connection_enabled = False self.timeoutAttempts = 20 self.sleepPeriodBuffer = 0.02 @@ -437,7 +437,7 @@ def variableTreeWidget_on_click(self): self.plotData[self.ui.tabPlotWidget.currentIndex()] = {"paths": paths, "legends": legends} self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( - paths, legends, self.realtimeConnectionEnabled + paths, legends, self.realtime_connection_enabled ) self.plottingLock.release() @@ -562,9 +562,9 @@ def closeEvent(self, event): self.signal_provider.wait() event.accept() - if self.realtimeConnectionEnabled: - self.realtimeConnectionEnabled = False - self.networkThread.join() + if self.realtime_connection_enabled: + self.realtime_connection_enabled = False + self.network_thread.join() def __populate_variable_tree_widget(self, obj, parent) -> QTreeWidgetItem: if not isinstance(obj, dict): @@ -695,9 +695,9 @@ def open_mat_file(self): self.__load_mat_file(file_name) def establish_connection(self, root): - while self.realtimeConnectionEnabled: + while self.realtime_connection_enabled: if not self.signal_provider.maintain_connection(): - self.realtimeConnectionEnabled = False + self.realtime_connection_enabled = False break # populate text logging tree @@ -719,14 +719,14 @@ def establish_connection(self, root): self.plot_items[self.ui.tabPlotWidget.currentIndex()].canvas.update_plots( self.plotData[self.ui.tabPlotWidget.currentIndex()]["paths"], self.plotData[self.ui.tabPlotWidget.currentIndex()]["legends"], - self.realtimeConnectionEnabled) + self.realtime_connection_enabled) self.plottingLock.release() time.sleep(self.animation_period + self.sleepPeriodBuffer) - self.meshcat_provider.updateMeshRealtime() + self.meshcat_provider.update_mesh_realtime() def connect_realtime_logger(self): - self.realtimeConnectionEnabled = True + self.realtime_connection_enabled = True self.signal_provider.root_name = "robot_realtime" # Do initial connection to populate the necessary data @@ -763,8 +763,8 @@ def connect_realtime_logger(self): # Disable these buttons for RT communication self.ui.startButton.setEnabled(False) self.ui.timeSlider.setEnabled(False) - self.networkThread = threading.Thread(target=self.establish_connection, args=(root,)) - self.networkThread.start() + self.network_thread = threading.Thread(target=self.establish_connection, args=(root,)) + self.network_thread.start() def open_about(self): From f8b88fab1f0898a6ebcbe8d74165af6a6f4ff085 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 15:43:53 +0200 Subject: [PATCH 67/75] Added exception for if BLF is not installed --- robot_log_visualizer/file_reader/signal_provider.py | 13 ++++++++++--- robot_log_visualizer/ui/autogenerated/visualizer.py | 8 +++++++- robot_log_visualizer/ui/gui.py | 4 +++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 527a6d8..9c6d877 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -10,7 +10,6 @@ from robot_log_visualizer.utils.utils import PeriodicThreadState, RobotStatePath import idyntree.swig as idyn -import bipedal_locomotion_framework.bindings as blf # for real-time logging import yarp @@ -40,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() @@ -78,7 +84,8 @@ def __init__(self, period: float): # for networking with the real-time logger self.realtime_network_init = False - self.vector_collections_client = blf.yarp_utilities.VectorsCollectionClient() + if self.blfInstalled: + self.vector_collections_client = blf.yarp_utilities.VectorsCollectionClient() self.trajectory_span = 200 self.rt_metadata_dict = {} @@ -207,7 +214,7 @@ def maintain_connection(self): if not self.realtime_network_init: yarp.Network.init() - param_handler = blf.parameters_handler.YarpParametersHandler() + param_handler = self.blf.parameters_handler.YarpParametersHandler() param_handler.set_parameter_string("remote", "/rtLoggingVectorCollections") # you must have some local port as well param_handler.set_parameter_string("local", "/visualizerInput") # remote must match the server param_handler.set_parameter_string("carrier", "udp") diff --git a/robot_log_visualizer/ui/autogenerated/visualizer.py b/robot_log_visualizer/ui/autogenerated/visualizer.py index 44519f6..73672bf 100644 --- a/robot_log_visualizer/ui/autogenerated/visualizer.py +++ b/robot_log_visualizer/ui/autogenerated/visualizer.py @@ -248,6 +248,7 @@ def setupUi(self, MainWindow): # Add a GUI action for connecting to the YARP port # for real-time logging + self.actionConnect = QtWidgets.QAction(MainWindow) self.actionConnect.setObjectName("actionConnect") @@ -284,8 +285,13 @@ 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")) + try: + import bipedal_locomotion_framework.bindings as blf + self.actionConnect.setText(_translate("MainWindow", "Realtime Connect")) + except ImportError: + self.actionConnect.setText(_translate("MainWindow", "Install BLF for RT Connect Functionality")) self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O")) self.actionAbout.setText(_translate("MainWindow", "About")) self.actionSet_Robot_Model.setText(_translate("MainWindow", "Set Robot Model")) + from PyQt5 import QtWebEngineWidgets diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 6dde3ac..ebd4e77 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -199,7 +199,9 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): # connect action self.ui.actionQuit.triggered.connect(self.close) self.ui.actionOpen.triggered.connect(self.open_mat_file) - self.ui.actionConnect.triggered.connect(self.connect_realtime_logger) + + if self.signal_provider.blfInstalled: + self.ui.actionConnect.triggered.connect(self.connect_realtime_logger) self.ui.actionAbout.triggered.connect(self.open_about) self.ui.actionSet_Robot_Model.triggered.connect(self.open_set_robot_model) From 59953ef8e056b78244633f551aa3940ac87bb568 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 16:26:20 +0200 Subject: [PATCH 68/75] Fixed bug regarding naming --- robot_log_visualizer/plotter/matplotlib_viewer_canvas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 1a42fe6..32345fb 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -236,7 +236,7 @@ def update_plots(self, paths, legends, realtime_plot): if realtime_plot: #self.axes.autoscale() - self.axes.set_xlim(0, self.signal_provider.realtimeFixedPlotWindow) + 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 From ad96b952dfb9c55ac477d866cbb4424bf5141fe1 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Fri, 17 May 2024 16:30:46 +0200 Subject: [PATCH 69/75] Fixed small bug with the grid --- robot_log_visualizer/plotter/matplotlib_viewer_canvas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index 32345fb..b577b69 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -247,7 +247,7 @@ def update_plots(self, paths, legends, realtime_plot): self.vertical_line_anim._stop() self.axes.legend() - self.axes.grid() + self.axes.grid(True) if not self.frame_legend: self.frame_legend = self.axes.legend().get_frame() From 76b31295731d0c3701eafaf15f687bedf980b838 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Mon, 2 Sep 2024 15:25:00 +0200 Subject: [PATCH 70/75] Updated to support latest python binding for vectors collection --- robot_log_visualizer/file_reader/signal_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index 9c6d877..e52180b 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -234,7 +234,7 @@ def maintain_connection(self): del self.data["robot_realtime"]["description_list"] del self.data["robot_realtime"]["yarp_robot_name"] - vc_input = self.vector_collections_client.read_data(True) + vc_input = self.vector_collections_client.read_data(True).vectors if not vc_input: return False From 767c83cf3871de14a142f80af445ca87987e647d Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Tue, 3 Sep 2024 14:16:18 +0200 Subject: [PATCH 71/75] Fixed bug displaying data offline --- .../plotter/matplotlib_viewer_canvas.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py index b577b69..5e4986f 100644 --- a/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py +++ b/robot_log_visualizer/plotter/matplotlib_viewer_canvas.py @@ -186,9 +186,8 @@ def on_pick(self, event): ) def update_plots(self, paths, legends, realtime_plot): - if realtime_plot: - self.axes.cla() - realtimeColorIndex = 0 + self.axes.cla() + colorIndex = 0 for path, legend in zip(paths, legends): path_string = "/".join(path) legend_string = "/".join(legend[1:]) @@ -210,17 +209,18 @@ def update_plots(self, paths, legends, realtime_plot): datapoints, label=legend_string, picker=True, - color=self.color_palette.get_color(realtimeColorIndex), + color=self.color_palette.get_color(colorIndex), ) - realtimeColorIndex = realtimeColorIndex + 1 + colorIndex = colorIndex + 1 else: (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 paths_to_be_canceled = [] From 2cc7277fecda4a1d463f507912bbd9e3b37c95ac Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Wed, 25 Sep 2024 13:14:41 +0200 Subject: [PATCH 72/75] Added better exception handling --- robot_log_visualizer/file_reader/signal_provider.py | 10 ++++++---- robot_log_visualizer/ui/gui.py | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/robot_log_visualizer/file_reader/signal_provider.py b/robot_log_visualizer/file_reader/signal_provider.py index e52180b..1f46281 100644 --- a/robot_log_visualizer/file_reader/signal_provider.py +++ b/robot_log_visualizer/file_reader/signal_provider.py @@ -221,11 +221,14 @@ def maintain_connection(self): self.vector_collections_client.initialize(param_handler) self.vector_collections_client.connect() - self.realtime_network_init = True - self.rt_metadata_dict = self.vector_collections_client.get_metadata().vectors - if not self.rt_metadata_dict: + 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(): @@ -261,7 +264,6 @@ def maintain_connection(self): 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) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index ebd4e77..5c4ed96 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -201,7 +201,9 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): self.ui.actionOpen.triggered.connect(self.open_mat_file) if self.signal_provider.blfInstalled: - self.ui.actionConnect.triggered.connect(self.connect_realtime_logger) + self.ui.actionRealtime_Connect.triggered.connect(self.connect_realtime_logger) + else: + self.ui.actionRealtime_Connect.setText("Install BLF for RT Connect Functionality") self.ui.actionAbout.triggered.connect(self.open_about) self.ui.actionSet_Robot_Model.triggered.connect(self.open_set_robot_model) @@ -735,6 +737,7 @@ def connect_realtime_logger(self): if not self.signal_provider.maintain_connection(): self.logger.write_to_log("Could not connect to the real-time logger.") self.realtime_connection_enabled = False + self.signal_provider.root_name = "robot_logger_device" return self.meshcat_provider._realtimeMeshUpdate = True # only display one root in the gui From f5b4a6599825601b2c7a9bf35140fe0e9d979106 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Wed, 25 Sep 2024 13:16:40 +0200 Subject: [PATCH 73/75] Removed unused icon --- robot_log_visualizer/ui/gui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/robot_log_visualizer/ui/gui.py b/robot_log_visualizer/ui/gui.py index 5c4ed96..51ba323 100644 --- a/robot_log_visualizer/ui/gui.py +++ b/robot_log_visualizer/ui/gui.py @@ -158,7 +158,6 @@ def __init__(self, signal_provider, meshcat_provider, animation_period): self.ui.actionQuit.setIcon(get_icon("close-circle-outline.svg")) self.ui.actionQuit.setIcon(get_icon("close-circle-outline.svg")) - self.ui.actionConnect.setIcon(get_icon("connection-outline.png")) self.ui.actionOpen.setIcon(get_icon("folder-open-outline.svg")) self.ui.actionSet_Robot_Model.setIcon(get_icon("body-outline.svg")) self.setWindowIcon(get_icon("icon.png")) From be77f988f36b1c23a81ba762de8916dab6b1a1c4 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Wed, 25 Sep 2024 13:17:18 +0200 Subject: [PATCH 74/75] Added gui changes to the visualizer.ui --- robot_log_visualizer/ui/misc/visualizer.ui | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/robot_log_visualizer/ui/misc/visualizer.ui b/robot_log_visualizer/ui/misc/visualizer.ui index c3dff71..9696a61 100644 --- a/robot_log_visualizer/ui/misc/visualizer.ui +++ b/robot_log_visualizer/ui/misc/visualizer.ui @@ -503,6 +503,7 @@ &File + @@ -556,12 +557,20 @@ Set Robot Model + + + Realtime Connect + + + Ctrl+R + + QWebEngineView QWidget -
PyQt5.QtWebEngineWidgets
+
QtWebEngineWidgets/QWebEngineView
1
From 5df2f8850940ed46b7d93caa9523aa21b5d0c964 Mon Sep 17 00:00:00 2001 From: Nick Tremaroli Date: Wed, 25 Sep 2024 13:23:51 +0200 Subject: [PATCH 75/75] updates to autogenerated files from qt creator --- .../ui/autogenerated/about.py | 5 ++-- .../ui/autogenerated/plot_tab.py | 5 ++-- .../ui/autogenerated/set_robot_model.py | 9 ++++--- .../ui/autogenerated/video_tab.py | 5 ++-- .../ui/autogenerated/visualizer.py | 26 ++++++------------- 5 files changed, 22 insertions(+), 28 deletions(-) diff --git a/robot_log_visualizer/ui/autogenerated/about.py b/robot_log_visualizer/ui/autogenerated/about.py index fedcf29..61a927f 100644 --- a/robot_log_visualizer/ui/autogenerated/about.py +++ b/robot_log_visualizer/ui/autogenerated/about.py @@ -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 diff --git a/robot_log_visualizer/ui/autogenerated/plot_tab.py b/robot_log_visualizer/ui/autogenerated/plot_tab.py index dc3fd67..1b1d496 100644 --- a/robot_log_visualizer/ui/autogenerated/plot_tab.py +++ b/robot_log_visualizer/ui/autogenerated/plot_tab.py @@ -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 diff --git a/robot_log_visualizer/ui/autogenerated/set_robot_model.py b/robot_log_visualizer/ui/autogenerated/set_robot_model.py index f49c134..6100115 100644 --- a/robot_log_visualizer/ui/autogenerated/set_robot_model.py +++ b/robot_log_visualizer/ui/autogenerated/set_robot_model.py @@ -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 @@ -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): diff --git a/robot_log_visualizer/ui/autogenerated/video_tab.py b/robot_log_visualizer/ui/autogenerated/video_tab.py index 0a0c3c0..f627445 100644 --- a/robot_log_visualizer/ui/autogenerated/video_tab.py +++ b/robot_log_visualizer/ui/autogenerated/video_tab.py @@ -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 diff --git a/robot_log_visualizer/ui/autogenerated/visualizer.py b/robot_log_visualizer/ui/autogenerated/visualizer.py index 73672bf..373bfaa 100644 --- a/robot_log_visualizer/ui/autogenerated/visualizer.py +++ b/robot_log_visualizer/ui/autogenerated/visualizer.py @@ -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 @@ -239,25 +240,18 @@ 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 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") - 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.actionRealtime_Connect = QtWidgets.QAction(MainWindow) + self.actionRealtime_Connect.setObjectName("actionRealtime_Connect") self.menuFile.addAction(self.actionOpen) - self.menuFile.addAction(self.actionConnect) + self.menuFile.addAction(self.actionRealtime_Connect) self.menuFile.addSeparator() self.menuFile.addAction(self.actionQuit) self.menuHelp.addAction(self.actionAbout) @@ -285,13 +279,9 @@ def retranslateUi(self, MainWindow): self.actionQuit.setText(_translate("MainWindow", "&Quit")) self.actionQuit.setShortcut(_translate("MainWindow", "Ctrl+Q")) self.actionOpen.setText(_translate("MainWindow", "&Open")) - try: - import bipedal_locomotion_framework.bindings as blf - self.actionConnect.setText(_translate("MainWindow", "Realtime Connect")) - except ImportError: - self.actionConnect.setText(_translate("MainWindow", "Install BLF for RT Connect Functionality")) 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