├── src ├── ros │ ├── CMakeLists.txt │ └── thread │ │ ├── BasicThread.cpp │ │ ├── RecordBagThread.hpp │ │ ├── PCDsToBagThread.hpp │ │ ├── EditBagThread.hpp │ │ ├── DummyBagThread.hpp │ │ ├── BagToPCDsThread.hpp │ │ ├── BagToVideoThread.hpp │ │ ├── VideoToBagThread.hpp │ │ ├── BagToImagesThread.hpp │ │ ├── MergeBagsThread.hpp │ │ ├── SendTF2Thread.hpp │ │ ├── PublishImagesThread.hpp │ │ ├── BasicThread.hpp │ │ ├── ChangeCompressionBagThread.hpp │ │ ├── PublishVideoThread.hpp │ │ ├── MergeBagsThread.cpp │ │ ├── SendTF2Thread.cpp │ │ ├── ChangeCompressionBagThread.cpp │ │ ├── RecordBagThread.cpp │ │ ├── PCDsToBagThread.cpp │ │ ├── VideoToBagThread.cpp │ │ ├── PublishVideoThread.cpp │ │ ├── TF2ToFileThread.hpp │ │ ├── PublishImagesThread.cpp │ │ └── TF2ToFileThread.cpp ├── CMakeLists.txt ├── ui │ ├── settings │ │ ├── input │ │ │ ├── RGBSettings.hpp │ │ │ ├── VideoSettings.hpp │ │ │ ├── PublishSettings.hpp │ │ │ ├── AdvancedSettings.hpp │ │ │ ├── DummyBagSettings.hpp │ │ │ ├── EditBagSettings.hpp │ │ │ ├── RecordBagSettings.hpp │ │ │ ├── TF2ToFileSettings.hpp │ │ │ ├── PCDsToBagSettings.hpp │ │ │ ├── VideoToBagSettings.hpp │ │ │ ├── MergeBagsSettings.hpp │ │ │ ├── BagToVideoSettings.hpp │ │ │ ├── BasicSettings.hpp │ │ │ ├── CompressBagSettings.hpp │ │ │ ├── BagToImagesSettings.hpp │ │ │ ├── SendTF2Settings.hpp │ │ │ ├── DeleteSourceSettings.hpp │ │ │ ├── VideoSettings.cpp │ │ │ ├── RGBSettings.cpp │ │ │ ├── PCDsToBagSettings.cpp │ │ │ ├── VideoToBagSettings.cpp │ │ │ ├── BasicSettings.cpp │ │ │ ├── DeleteSourceSettings.cpp │ │ │ ├── CompressBagSettings.cpp │ │ │ ├── TF2ToFileSettings.cpp │ │ │ ├── BagToVideoSettings.cpp │ │ │ ├── AdvancedSettings.cpp │ │ │ ├── PublishSettings.cpp │ │ │ ├── BagToImagesSettings.cpp │ │ │ ├── MergeBagsSettings.cpp │ │ │ ├── DummyBagSettings.cpp │ │ │ ├── RecordBagSettings.cpp │ │ │ ├── CMakeLists.txt │ │ │ ├── SendTF2Settings.cpp │ │ │ └── EditBagSettings.cpp │ │ ├── CMakeLists.txt │ │ ├── DialogSettings.cpp │ │ └── DialogSettings.hpp │ ├── SettingsDialog.hpp │ ├── InputWidgets │ │ ├── BagToPCDsWidget.hpp │ │ ├── TopicsServicesInfoWidget.hpp │ │ ├── BagInfoWidget.hpp │ │ ├── BagToImagesWidget.hpp │ │ ├── BagToVideoWidget.hpp │ │ ├── ChangeCompressionWidget.hpp │ │ ├── PCDsToBagWidget.hpp │ │ ├── TF2ToFileWidget.hpp │ │ ├── RecordBagWidget.hpp │ │ ├── MergeBagsWidget.hpp │ │ ├── VideoToBagWidget.hpp │ │ ├── BagToPCDsWidget.cpp │ │ ├── PublishWidget.hpp │ │ ├── SendTF2Widget.hpp │ │ ├── EditBagWidget.hpp │ │ ├── DummyBagWidget.hpp │ │ ├── CMakeLists.txt │ │ ├── AdvancedInputWidget.hpp │ │ └── BasicInputWidget.hpp │ ├── CMakeLists.txt │ ├── HelperWidgets │ │ ├── LowDiskSpaceWidget.hpp │ │ ├── CMakeLists.txt │ │ ├── MessageCountWidget.hpp │ │ ├── TopicComboBoxWidget.hpp │ │ ├── TopicWidget.hpp │ │ ├── MessageCountWidget.cpp │ │ ├── BasicBagWidget.hpp │ │ ├── TopicListingInputWidget.hpp │ │ ├── LowDiskSpaceWidget.cpp │ │ ├── TopicComboBoxWidget.cpp │ │ └── TopicWidget.cpp │ ├── ProgressWidget.hpp │ ├── MainWindow.hpp │ └── StartWidget.hpp ├── utils │ ├── NodeWrapper.hpp │ ├── UtilsGeneral.hpp │ ├── UtilsGeneral.cpp │ ├── VideoEncoder.cpp │ ├── VideoEncoder.hpp │ ├── CMakeLists.txt │ ├── UtilsUI.hpp │ └── UtilsROS.hpp ├── main.cpp └── cli │ └── CMakeLists.txt ├── resources ├── gifs │ ├── merging_black.gif │ ├── merging_white.gif │ ├── recording_black.gif │ ├── recording_white.gif │ ├── compressing_black.gif │ ├── compressing_white.gif │ ├── publishing_black.gif │ ├── publishing_white.gif │ ├── sending_tf2_black.gif │ ├── sending_tf2_white.gif │ ├── decompressing_black.gif │ └── decompressing_white.gif ├── icons │ ├── minus_black.svg │ ├── minus_white.svg │ ├── plus_black.svg │ ├── plus_white.svg │ ├── warning.svg │ ├── info_tools_black.svg │ ├── info_tools_white.svg │ ├── bag_tools_black.svg │ ├── bag_tools_white.svg │ ├── publishing_tools_black.svg │ ├── publishing_tools_white.svg │ ├── bag_info_black.svg │ ├── bag_info_white.svg │ ├── send_tf2_black.svg │ ├── send_tf2_white.svg │ ├── topics_services_info_black.svg │ ├── topics_services_info_white.svg │ ├── edit_bag_black.svg │ ├── edit_bag_white.svg │ ├── dummy_bag_black.svg │ ├── dummy_bag_white.svg │ ├── bag_to_video_black.svg │ ├── bag_to_video_white.svg │ ├── video_to_bag_black.svg │ ├── video_to_bag_white.svg │ ├── compress_bag_black.svg │ ├── compress_bag_white.svg │ ├── decompress_bag_black.svg │ ├── decompress_bag_white.svg │ ├── publish_video_black.svg │ ├── publish_video_white.svg │ ├── gear_white.svg │ ├── record_bag_black.svg │ ├── record_bag_white.svg │ ├── gear_black.svg │ ├── main.svg │ ├── merge_bags_black.svg │ ├── merge_bags_white.svg │ ├── tf2_to_file_black.svg │ ├── tf2_to_file_white.svg │ ├── publish_images_black.svg │ └── publish_images_white.svg └── resources.qrc ├── test ├── main.cpp ├── CMakeLists.txt └── utils │ └── UtilsUITest.cpp ├── package.xml └── .github └── workflows ├── jazzy.yml ├── humble.yml ├── kilted.yml └── rolling.yml /src/ros/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(thread) 2 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(cli) 2 | add_subdirectory(ros) 3 | add_subdirectory(ui) 4 | add_subdirectory(utils) 5 | -------------------------------------------------------------------------------- /resources/gifs/merging_black.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/merging_black.gif -------------------------------------------------------------------------------- /resources/gifs/merging_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/merging_white.gif -------------------------------------------------------------------------------- /resources/gifs/recording_black.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/recording_black.gif -------------------------------------------------------------------------------- /resources/gifs/recording_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/recording_white.gif -------------------------------------------------------------------------------- /resources/gifs/compressing_black.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/compressing_black.gif -------------------------------------------------------------------------------- /resources/gifs/compressing_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/compressing_white.gif -------------------------------------------------------------------------------- /resources/gifs/publishing_black.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/publishing_black.gif -------------------------------------------------------------------------------- /resources/gifs/publishing_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/publishing_white.gif -------------------------------------------------------------------------------- /resources/gifs/sending_tf2_black.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/sending_tf2_black.gif -------------------------------------------------------------------------------- /resources/gifs/sending_tf2_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/sending_tf2_white.gif -------------------------------------------------------------------------------- /resources/gifs/decompressing_black.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/decompressing_black.gif -------------------------------------------------------------------------------- /resources/gifs/decompressing_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxFleur/ros2_utils_tool/HEAD/resources/gifs/decompressing_white.gif -------------------------------------------------------------------------------- /src/ros/thread/BasicThread.cpp: -------------------------------------------------------------------------------- 1 | #include "BasicThread.hpp" 2 | 3 | BasicThread::BasicThread(const QString& sourceDirectory, 4 | const QString& topicName, 5 | QObject* parent) : 6 | QThread(parent), m_sourceDirectory(sourceDirectory.toStdString()), m_topicName(topicName.toStdString()) 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /resources/icons/minus_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /resources/icons/minus_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_RUNNER 2 | #include "catch_ros2/catch_ros2.hpp" 3 | 4 | #include 5 | 6 | #include "rclcpp/rclcpp.hpp" 7 | 8 | int 9 | main(int argc, char **argv) 10 | { 11 | setenv("QT_QPA_PLATFORM", "offscreen", 0); 12 | 13 | QApplication app(argc, argv); 14 | rclcpp::init(argc, argv); 15 | 16 | return Catch::Session().run(argc, argv); 17 | } 18 | -------------------------------------------------------------------------------- /resources/icons/plus_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/icons/plus_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/ui/settings/input/RGBSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedSettings.hpp" 4 | 5 | // Store rgb settings 6 | class RGBSettings : public AdvancedSettings { 7 | public: 8 | RGBSettings(Parameters::RGBParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | protected: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::RGBParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/VideoSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "RGBSettings.hpp" 4 | 5 | // Store video settings 6 | class VideoSettings : public RGBSettings { 7 | public: 8 | VideoSettings(Parameters::VideoParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | protected: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::VideoParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(input) 2 | 3 | add_library (rt_settings INTERFACE) 4 | 5 | target_include_directories (rt_settings 6 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 7 | ) 8 | 9 | target_sources(rt_settings INTERFACE 10 | ${CMAKE_CURRENT_LIST_DIR}/GeneralSettings.hpp 11 | ${CMAKE_CURRENT_LIST_DIR}/DialogSettings.cpp 12 | ${CMAKE_CURRENT_LIST_DIR}/DialogSettings.hpp 13 | ) 14 | 15 | target_link_libraries(rt_settings 16 | INTERFACE Qt::Widgets 17 | ) 18 | -------------------------------------------------------------------------------- /src/ui/settings/input/PublishSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VideoSettings.hpp" 4 | 5 | // Store publishing parameters 6 | class PublishSettings : public VideoSettings { 7 | public: 8 | PublishSettings(Parameters::PublishParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::PublishParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/AdvancedSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicSettings.hpp" 4 | 5 | // Store advanced settings 6 | class AdvancedSettings : public BasicSettings { 7 | public: 8 | AdvancedSettings(Parameters::AdvancedParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | protected: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::AdvancedParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/NodeWrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rclcpp/rclcpp.hpp" 4 | 5 | // Wrapper for a ROS node to keep ROS includes out of the UI code 6 | class NodeWrapper { 7 | public: 8 | explicit 9 | NodeWrapper(const std::string& nodeName) 10 | { 11 | m_node = std::make_shared(nodeName); 12 | } 13 | 14 | std::shared_ptr 15 | getNode() 16 | { 17 | return m_node; 18 | } 19 | 20 | private: 21 | std::shared_ptr m_node; 22 | }; 23 | -------------------------------------------------------------------------------- /src/ui/settings/input/DummyBagSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicSettings.hpp" 4 | 5 | // Store dummy bag creation parameters 6 | class DummyBagSettings : public BasicSettings { 7 | public: 8 | DummyBagSettings(Parameters::DummyBagParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::DummyBagParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/EditBagSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DeleteSourceSettings.hpp" 4 | 5 | // Store bag editing parameters 6 | class EditBagSettings : public DeleteSourceSettings { 7 | public: 8 | EditBagSettings(Parameters::EditBagParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::EditBagParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/UtilsGeneral.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Util functions for all sorts of things 6 | namespace Utils::General 7 | { 8 | // Returns available drive space in gigabytes 9 | [[nodiscard]] float 10 | getAvailableDriveSpace(const QString& path); 11 | 12 | // Get a file extension 13 | [[nodiscard]] const QString 14 | getFileExtension(const QString& path); 15 | 16 | static constexpr float MINIMUM_RECOMMENDED_DRIVE_SPACE = 10.737f; 17 | static constexpr long GIGABYTE_IN_BYTES = 1073741824; 18 | } 19 | -------------------------------------------------------------------------------- /src/ui/settings/input/RecordBagSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicSettings.hpp" 4 | 5 | // Store bag recording parameters 6 | class RecordBagSettings : public BasicSettings { 7 | public: 8 | RecordBagSettings(Parameters::RecordBagParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::RecordBagParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/TF2ToFileSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedSettings.hpp" 4 | 5 | // Store tf2 to json parameters 6 | class TF2ToFileSettings : public AdvancedSettings { 7 | public: 8 | TF2ToFileSettings(Parameters::TF2ToFileParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::TF2ToFileParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/PCDsToBagSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedSettings.hpp" 4 | 5 | // Store bag creation parameters 6 | class PCDsToBagSettings : public AdvancedSettings { 7 | public: 8 | PCDsToBagSettings(Parameters::PCDsToBagParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::PCDsToBagParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/VideoToBagSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VideoSettings.hpp" 4 | 5 | // Store bag creation parameters 6 | class VideoToBagSettings : public VideoSettings { 7 | public: 8 | VideoToBagSettings(Parameters::VideoToBagParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::VideoToBagParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ros/thread/RecordBagThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread used to record a bag file 7 | class RecordBagThread : public BasicThread { 8 | Q_OBJECT 9 | public: 10 | explicit 11 | RecordBagThread(const Parameters::RecordBagParameters& parameters, 12 | QObject* parent = nullptr); 13 | 14 | void 15 | run() override; 16 | 17 | private: 18 | const Parameters::RecordBagParameters& m_parameters; 19 | }; 20 | -------------------------------------------------------------------------------- /src/ui/settings/input/MergeBagsSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DeleteSourceSettings.hpp" 4 | 5 | // Store bag editing parameters 6 | class MergeBagsSettings : public DeleteSourceSettings { 7 | public: 8 | MergeBagsSettings(Parameters::MergeBagsParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::MergeBagsParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/BagToVideoSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "VideoSettings.hpp" 4 | 5 | // Store video out of ROS bag creation parameters 6 | class BagToVideoSettings : public VideoSettings { 7 | public: 8 | BagToVideoSettings(Parameters::BagToVideoParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::BagToVideoParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/BasicSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GeneralSettings.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Store parameters used by all input widgets 7 | class BasicSettings : public GeneralSettings { 8 | public: 9 | BasicSettings(Parameters::BasicParameters& parameters, 10 | const QString& groupName); 11 | 12 | bool 13 | write() override; 14 | 15 | protected: 16 | bool 17 | read() override; 18 | 19 | private: 20 | Parameters::BasicParameters& m_parameters; 21 | }; 22 | -------------------------------------------------------------------------------- /src/ui/settings/input/CompressBagSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DeleteSourceSettings.hpp" 4 | 5 | // Store bag editing parameters 6 | class CompressBagSettings : public DeleteSourceSettings { 7 | public: 8 | CompressBagSettings(Parameters::CompressBagParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::CompressBagParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/settings/input/BagToImagesSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "RGBSettings.hpp" 4 | 5 | // Store parameters for image sequence out of ROS bag creation 6 | class BagToImagesSettings : public RGBSettings { 7 | public: 8 | BagToImagesSettings(Parameters::BagToImagesParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | private: 15 | bool 16 | read() override; 17 | 18 | private: 19 | Parameters::BagToImagesParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ros/thread/PCDsToBagThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread used to write a set of pcd files to a bag file 7 | class PCDsToBagThread : public BasicThread { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit 12 | PCDsToBagThread(const Parameters::PCDsToBagParameters& parameters, 13 | QObject* parent = nullptr); 14 | 15 | void 16 | run() override; 17 | 18 | private: 19 | const Parameters::PCDsToBagParameters& m_parameters; 20 | }; 21 | -------------------------------------------------------------------------------- /src/ui/SettingsDialog.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DialogSettings.hpp" 4 | #include "Parameters.hpp" 5 | 6 | #include 7 | 8 | // Dialog used to modify settings 9 | class SettingsDialog : public QDialog { 10 | Q_OBJECT 11 | 12 | public: 13 | SettingsDialog(Parameters::DialogParameters& parameters, 14 | QWidget* parent = 0); 15 | 16 | private: 17 | void 18 | storeParametersCheckStateChanged(); 19 | 20 | private: 21 | Parameters::DialogParameters& m_parameters; 22 | 23 | DialogSettings m_settings; 24 | }; 25 | -------------------------------------------------------------------------------- /resources/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ui/MainWindow.hpp" 2 | 3 | #include "rclcpp/rclcpp.hpp" 4 | 5 | #include 6 | 7 | int 8 | main(int argc, char* argv[]) 9 | { 10 | // Initialize ROS and Qt 11 | rclcpp::init(argc, argv); 12 | QApplication app(argc, argv); 13 | app.setWindowIcon(QIcon(":/icons/main.svg")); 14 | 15 | MainWindow mainWindow; 16 | mainWindow.show(); 17 | 18 | while (rclcpp::ok()) { 19 | app.processEvents(); 20 | std::this_thread::sleep_for(std::chrono::milliseconds(20)); 21 | } 22 | 23 | rclcpp::shutdown(); 24 | return EXIT_SUCCESS; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/UtilsGeneral.cpp: -------------------------------------------------------------------------------- 1 | #include "UtilsGeneral.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Utils::General 8 | { 9 | [[nodiscard]] float 10 | getAvailableDriveSpace(const QString& path) 11 | { 12 | std::error_code errorCode; 13 | const auto spaceInfo = std::filesystem::space(path.toStdString(), errorCode); 14 | 15 | return (float) spaceInfo.available / (float) Utils::General::GIGABYTE_IN_BYTES; 16 | } 17 | 18 | 19 | const QString 20 | getFileExtension(const QString& path) 21 | { 22 | QFileInfo fileInfo(path); 23 | return fileInfo.suffix(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/BagToPCDsWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedSettings.hpp" 4 | #include "Parameters.hpp" 5 | #include "TopicComboBoxWidget.hpp" 6 | 7 | class QComboBox; 8 | class QLineEdit; 9 | 10 | // The widget used to manage writing pcd files out of a ROS bag 11 | class BagToPCDsWidget : public TopicComboBoxWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | BagToPCDsWidget(Parameters::AdvancedParameters& parameters, 17 | QWidget* parent = 0); 18 | 19 | private: 20 | Parameters::AdvancedParameters& m_parameters; 21 | 22 | AdvancedSettings m_settings; 23 | }; 24 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/TopicsServicesInfoWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicInputWidget.hpp" 4 | 5 | #include 6 | #include 7 | 8 | class QTreeWidget; 9 | 10 | // The widget showing topics and services info data 11 | class TopicsServicesInfoWidget : public BasicInputWidget 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit 16 | TopicsServicesInfoWidget(QWidget* parent = 0); 17 | 18 | private: 19 | void 20 | fillTree() const; 21 | 22 | private: 23 | QPointer m_treeWidget; 24 | 25 | static constexpr int COL_NAME = 0; 26 | static constexpr int COL_TYPE = 1; 27 | }; 28 | -------------------------------------------------------------------------------- /src/ui/settings/input/SendTF2Settings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicSettings.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Store sending tf2 parameters 7 | class SendTF2Settings : public BasicSettings { 8 | public: 9 | SendTF2Settings(Parameters::SendTF2Parameters& parameters, 10 | const QString& groupName); 11 | 12 | bool 13 | write() override; 14 | 15 | private: 16 | bool 17 | read() override; 18 | 19 | private: 20 | Parameters::SendTF2Parameters& m_parameters; 21 | 22 | static constexpr int TRANSLATION_SIZE = 3; 23 | static constexpr int ROTATION_SIZE = 4; 24 | }; 25 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/BagInfoWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicInputWidget.hpp" 4 | 5 | #include 6 | #include 7 | 8 | class QTreeWidget; 9 | 10 | // The widget showing bag info data 11 | class BagInfoWidget : public BasicInputWidget 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit 16 | BagInfoWidget(QWidget* parent = 0); 17 | 18 | private slots: 19 | void 20 | displayBagInfo(); 21 | 22 | private: 23 | // Main tree used to display bag info 24 | QPointer m_infoTreeWidget; 25 | 26 | static constexpr int COL_DESCRIPTION = 0; 27 | static constexpr int COL_INFORMATION = 1; 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/VideoEncoder.cpp: -------------------------------------------------------------------------------- 1 | #include "VideoEncoder.hpp" 2 | 3 | VideoEncoder::VideoEncoder(int fourcc) : 4 | m_fourcc(fourcc) 5 | { 6 | } 7 | 8 | 9 | bool 10 | VideoEncoder::setVideoWriter(const std::string& directory, int fps, int width, int height, bool useHardwareAcceleration, bool useBWImages) 11 | { 12 | m_videoWriter = cv::VideoWriter(directory, m_fourcc, fps, cv::Size(width, height), { 13 | cv::VIDEOWRITER_PROP_HW_ACCELERATION, useHardwareAcceleration ? cv::VIDEO_ACCELERATION_ANY : cv::VIDEO_ACCELERATION_NONE, 14 | cv::VIDEOWRITER_PROP_IS_COLOR, !useBWImages 15 | }); 16 | 17 | return m_videoWriter.isOpened(); 18 | } 19 | -------------------------------------------------------------------------------- /src/ros/thread/EditBagThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread used to write an edited ROS bag file 7 | class EditBagThread : public BasicThread { 8 | Q_OBJECT 9 | public: 10 | explicit 11 | EditBagThread(const Parameters::EditBagParameters& parameters, 12 | unsigned int numberOfThreads, 13 | QObject* parent = nullptr); 14 | 15 | void 16 | run() override; 17 | 18 | private: 19 | const Parameters::EditBagParameters& m_parameters; 20 | 21 | const unsigned int m_numberOfThreads; 22 | }; 23 | -------------------------------------------------------------------------------- /resources/icons/info_tools_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/icons/info_tools_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/ros/thread/DummyBagThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Dummy bag thread, used to create a bag file with dummy data 7 | class DummyBagThread : public BasicThread { 8 | Q_OBJECT 9 | public: 10 | explicit 11 | DummyBagThread(const Parameters::DummyBagParameters& parameters, 12 | unsigned int numberOfThreads, 13 | QObject* parent = nullptr); 14 | 15 | void 16 | run() override; 17 | 18 | private: 19 | const Parameters::DummyBagParameters& m_parameters; 20 | 21 | const unsigned int m_numberOfThreads; 22 | }; 23 | -------------------------------------------------------------------------------- /src/ros/thread/BagToPCDsThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread used to write a bag images topic to a set of pcd files 7 | class BagToPCDsThread : public BasicThread { 8 | Q_OBJECT 9 | public: 10 | explicit 11 | BagToPCDsThread(const Parameters::AdvancedParameters& parameters, 12 | unsigned int numberOfThreads, 13 | QObject* parent = nullptr); 14 | 15 | void 16 | run() override; 17 | 18 | private: 19 | const Parameters::AdvancedParameters& m_parameters; 20 | 21 | const unsigned int m_numberOfThreads; 22 | }; 23 | -------------------------------------------------------------------------------- /src/ros/thread/BagToVideoThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread writing a bag images topic to a video file 7 | class BagToVideoThread : public BasicThread { 8 | Q_OBJECT 9 | public: 10 | explicit 11 | BagToVideoThread(const Parameters::BagToVideoParameters& parameters, 12 | bool useHardwareAcceleration, 13 | QObject* parent = nullptr); 14 | 15 | void 16 | run() override; 17 | 18 | private: 19 | const Parameters::BagToVideoParameters& m_parameters; 20 | 21 | const bool m_useHardwareAcceleration; 22 | }; 23 | -------------------------------------------------------------------------------- /src/ros/thread/VideoToBagThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread used to write a video to a bag file 7 | class VideoToBagThread : public BasicThread { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit 12 | VideoToBagThread(const Parameters::VideoToBagParameters& parameters, 13 | bool useHardwareAcceleration, 14 | QObject* parent = nullptr); 15 | 16 | void 17 | run() override; 18 | 19 | private: 20 | const Parameters::VideoToBagParameters& m_parameters; 21 | 22 | const bool m_useHardwareAcceleration; 23 | }; 24 | -------------------------------------------------------------------------------- /src/ros/thread/BagToImagesThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread used to write a bag images topic to a set of images 7 | class BagToImagesThread : public BasicThread { 8 | Q_OBJECT 9 | public: 10 | explicit 11 | BagToImagesThread(const Parameters::BagToImagesParameters& parameters, 12 | unsigned int numberOfThreads, 13 | QObject* parent = nullptr); 14 | 15 | void 16 | run() override; 17 | 18 | private: 19 | const Parameters::BagToImagesParameters& m_parameters; 20 | 21 | const unsigned int m_numberOfThreads; 22 | }; 23 | -------------------------------------------------------------------------------- /src/ui/settings/input/DeleteSourceSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedSettings.hpp" 4 | 5 | // Store bag editing parameters 6 | class DeleteSourceSettings : public AdvancedSettings { 7 | public: 8 | DeleteSourceSettings(Parameters::DeleteSourceParameters& parameters, 9 | const QString& groupName); 10 | 11 | bool 12 | write() override; 13 | 14 | protected: 15 | bool 16 | read() override; 17 | 18 | void 19 | setDefaultValueToTrue() 20 | { 21 | m_isDefaultValueTrue = true; 22 | } 23 | 24 | private: 25 | Parameters::DeleteSourceParameters& m_parameters; 26 | 27 | bool m_isDefaultValueTrue = false; 28 | }; 29 | -------------------------------------------------------------------------------- /src/ui/settings/input/VideoSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "VideoSettings.hpp" 2 | 3 | VideoSettings::VideoSettings(Parameters::VideoParameters& parameters, const QString& groupName) : 4 | RGBSettings(parameters, groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | VideoSettings::write() 12 | { 13 | if (!RGBSettings::write()) { 14 | return false; 15 | } 16 | 17 | writeParameter(m_groupName, "fps", m_parameters.fps); 18 | 19 | return true; 20 | } 21 | 22 | 23 | bool 24 | VideoSettings::read() 25 | { 26 | if (!RGBSettings::read()) { 27 | return false; 28 | } 29 | 30 | m_parameters.fps = readParameter(m_groupName, "fps", 30); 31 | 32 | return true; 33 | } 34 | -------------------------------------------------------------------------------- /src/ros/thread/MergeBagsThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread used to merge a bag file using two input bag files 7 | // Makes use of the rosbag2_transport API for simplicity and performance 8 | class MergeBagsThread : public BasicThread { 9 | Q_OBJECT 10 | public: 11 | explicit 12 | MergeBagsThread(const Parameters::MergeBagsParameters& parameters, 13 | unsigned int numberOfThreads, 14 | QObject* parent = nullptr); 15 | 16 | void 17 | run() override; 18 | 19 | private: 20 | const Parameters::MergeBagsParameters& m_parameters; 21 | 22 | const unsigned int m_numberOfThreads; 23 | }; 24 | -------------------------------------------------------------------------------- /resources/icons/bag_tools_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/icons/bag_tools_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/utils/VideoEncoder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // OpenCV video encoder used to write videos 6 | class VideoEncoder { 7 | public: 8 | explicit 9 | VideoEncoder(int fourcc); 10 | 11 | bool 12 | setVideoWriter(const std::string& directory, 13 | int fps, 14 | int width, 15 | int height, 16 | bool useHardwareAcceleration, 17 | bool useBWImages); 18 | 19 | inline void 20 | writeImageToVideo(const cv::Mat& mat) 21 | { 22 | m_videoWriter.write(mat); 23 | } 24 | 25 | private: 26 | int m_fourcc; 27 | cv::VideoWriter m_videoWriter; 28 | }; 29 | -------------------------------------------------------------------------------- /src/ui/settings/input/RGBSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "RGBSettings.hpp" 2 | 3 | RGBSettings::RGBSettings(Parameters::RGBParameters& parameters, const QString& groupName) : 4 | AdvancedSettings(parameters, groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | RGBSettings::write() 12 | { 13 | if (!AdvancedSettings::write()) { 14 | return false; 15 | } 16 | 17 | writeParameter(m_groupName, "switch_red_blue", m_parameters.exchangeRedBlueValues); 18 | 19 | return true; 20 | } 21 | 22 | 23 | bool 24 | RGBSettings::read() 25 | { 26 | if (!AdvancedSettings::read()) { 27 | return false; 28 | } 29 | 30 | m_parameters.exchangeRedBlueValues = readParameter(m_groupName, "switch_red_blue", false); 31 | 32 | return true; 33 | } 34 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ros2_utils_tool 4 | 0.14.0 5 | The ros2_utils_tool package 6 | 7 | maxime 8 | EUPLv1.2 9 | 10 | cv_bridge 11 | geometry_msgs 12 | pcl_conversions 13 | rclcpp 14 | rosbag2_cpp 15 | rosbag2_transport 16 | sensor_msgs 17 | tf2_msgs 18 | tf2_ros 19 | 20 | catch_ros2 21 | 22 | ament_cmake 23 | 24 | 25 | ament_cmake 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/ros/thread/SendTF2Thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | #include "rclcpp/rclcpp.hpp" 7 | #include "tf2_ros/transform_broadcaster.h" 8 | 9 | // Thread used to publish transformations. Runs using a ROS node to enable publishing 10 | class SendTF2Thread : public BasicThread { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit 15 | SendTF2Thread(const Parameters::SendTF2Parameters& parameters, 16 | QObject* parent = nullptr); 17 | 18 | void 19 | run() override; 20 | 21 | private: 22 | std::shared_ptr m_node; 23 | std::shared_ptr m_broadcaster; 24 | 25 | const Parameters::SendTF2Parameters& m_parameters; 26 | 27 | static constexpr int PROGRESS = 0; 28 | }; 29 | -------------------------------------------------------------------------------- /src/ui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(InputWidgets) 2 | add_subdirectory(HelperWidgets) 3 | add_subdirectory(settings) 4 | 5 | add_library (rt_ui INTERFACE) 6 | 7 | target_include_directories (rt_ui 8 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 9 | ) 10 | 11 | target_sources(rt_ui INTERFACE 12 | ${CMAKE_CURRENT_LIST_DIR}/MainWindow.cpp 13 | ${CMAKE_CURRENT_LIST_DIR}/MainWindow.hpp 14 | ${CMAKE_CURRENT_LIST_DIR}/ProgressWidget.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/ProgressWidget.hpp 16 | ${CMAKE_CURRENT_LIST_DIR}/SettingsDialog.cpp 17 | ${CMAKE_CURRENT_LIST_DIR}/SettingsDialog.hpp 18 | ${CMAKE_CURRENT_LIST_DIR}/StartWidget.cpp 19 | ${CMAKE_CURRENT_LIST_DIR}/StartWidget.hpp 20 | ) 21 | 22 | target_link_libraries(rt_ui 23 | INTERFACE Qt::Widgets rt_all_threads rt_input_widgets rt_settings rt_utils 24 | ) 25 | -------------------------------------------------------------------------------- /src/ui/settings/input/PCDsToBagSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "PCDsToBagSettings.hpp" 2 | 3 | PCDsToBagSettings::PCDsToBagSettings(Parameters::PCDsToBagParameters& parameters, 4 | const QString& groupName) : 5 | AdvancedSettings(parameters, groupName), m_parameters(parameters) 6 | { 7 | read(); 8 | } 9 | 10 | 11 | bool 12 | PCDsToBagSettings::write() 13 | { 14 | if (!AdvancedSettings::write()) { 15 | return false; 16 | } 17 | 18 | writeParameter(m_groupName, "rate", m_parameters.rate); 19 | 20 | return true; 21 | } 22 | 23 | 24 | bool 25 | PCDsToBagSettings::read() 26 | { 27 | if (!AdvancedSettings::read()) { 28 | return false; 29 | } 30 | 31 | m_parameters.rate = readParameter(m_groupName, "rate", 5); 32 | 33 | return true; 34 | } 35 | -------------------------------------------------------------------------------- /src/ui/settings/input/VideoToBagSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "VideoToBagSettings.hpp" 2 | 3 | VideoToBagSettings::VideoToBagSettings(Parameters::VideoToBagParameters& parameters, 4 | const QString& groupName) : 5 | VideoSettings(parameters, groupName), m_parameters(parameters) 6 | { 7 | read(); 8 | } 9 | 10 | 11 | bool 12 | VideoToBagSettings::write() 13 | { 14 | if (!VideoSettings::write()) { 15 | return false; 16 | } 17 | 18 | writeParameter(m_groupName, "custom_fps", m_parameters.useCustomFPS); 19 | 20 | return true; 21 | } 22 | 23 | 24 | bool 25 | VideoToBagSettings::read() 26 | { 27 | if (!VideoSettings::read()) { 28 | return false; 29 | } 30 | 31 | m_parameters.useCustomFPS = readParameter(m_groupName, "custom_fps", false); 32 | 33 | return true; 34 | } 35 | -------------------------------------------------------------------------------- /src/ui/settings/input/BasicSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "BasicSettings.hpp" 2 | 3 | #include "DialogSettings.hpp" 4 | 5 | BasicSettings::BasicSettings(Parameters::BasicParameters& parameters, const QString& groupName) : 6 | GeneralSettings(groupName), m_parameters(parameters) 7 | { 8 | } 9 | 10 | 11 | bool 12 | BasicSettings::write() 13 | { 14 | if (!DialogSettings::getStaticParameter("save_parameters", false)) { 15 | return false; 16 | } 17 | 18 | writeParameter(m_groupName, "source_dir", m_parameters.sourceDirectory); 19 | 20 | return true; 21 | } 22 | 23 | 24 | bool 25 | BasicSettings::read() 26 | { 27 | if (!DialogSettings::getStaticParameter("save_parameters", false)) { 28 | return false; 29 | } 30 | 31 | m_parameters.sourceDirectory = readParameter(m_groupName, "source_dir", QString("")); 32 | 33 | return true; 34 | } 35 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/LowDiskSpaceWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QLabel; 7 | 8 | // Used to display a warning icon of low disk space along with the available disk space 9 | class LowDiskSpaceWidget : public QWidget 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | LowDiskSpaceWidget(QWidget* parent = 0); 15 | 16 | public: 17 | void 18 | setVisibility(const QString& path); 19 | 20 | [[nodiscard]] bool 21 | isDiskSpaceSpaceSufficient() 22 | { 23 | return m_isDiskSpaceSufficient; 24 | } 25 | 26 | private: 27 | void 28 | setPixmapLabelIcon() const; 29 | 30 | bool 31 | event(QEvent *event); 32 | 33 | private: 34 | QPointer m_warningIconLabel; 35 | QPointer m_diskSpaceLabel; 36 | 37 | bool m_isDiskSpaceSufficient; 38 | 39 | static constexpr int ICON_SIZE = 25; 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (rt_utils INTERFACE) 2 | 3 | target_include_directories (rt_utils 4 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 5 | ) 6 | 7 | target_sources(rt_utils INTERFACE 8 | ${CMAKE_CURRENT_LIST_DIR}/NodeWrapper.hpp 9 | ${CMAKE_CURRENT_LIST_DIR}/Parameters.hpp 10 | ${CMAKE_CURRENT_LIST_DIR}/UtilsCLI.cpp 11 | ${CMAKE_CURRENT_LIST_DIR}/UtilsCLI.hpp 12 | ${CMAKE_CURRENT_LIST_DIR}/UtilsGeneral.cpp 13 | ${CMAKE_CURRENT_LIST_DIR}/UtilsGeneral.hpp 14 | ${CMAKE_CURRENT_LIST_DIR}/UtilsROS.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/UtilsROS.hpp 16 | ${CMAKE_CURRENT_LIST_DIR}/UtilsUI.cpp 17 | ${CMAKE_CURRENT_LIST_DIR}/UtilsUI.hpp 18 | ${CMAKE_CURRENT_LIST_DIR}/VideoEncoder.cpp 19 | ${CMAKE_CURRENT_LIST_DIR}/VideoEncoder.hpp 20 | ) 21 | 22 | target_link_libraries(rt_utils 23 | INTERFACE ${OpenCV_LIBS} Qt::Widgets rt_settings tf2_ros::tf2_ros 24 | ) 25 | -------------------------------------------------------------------------------- /src/ros/thread/PublishImagesThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | #include "rclcpp/rclcpp.hpp" 7 | #include "sensor_msgs/msg/image.hpp" 8 | 9 | // Thread used to publish image sequences as a ROS topic 10 | // This thread also runs as a separate ROS node to enable the image messages publishing 11 | class PublishImagesThread : public BasicThread { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit 16 | PublishImagesThread(const Parameters::PublishParameters& parameters, 17 | QObject* parent = nullptr); 18 | 19 | void 20 | run() override; 21 | 22 | private: 23 | std::shared_ptr m_node; 24 | rclcpp::Publisher::SharedPtr m_publisher; 25 | 26 | const Parameters::PublishParameters& m_parameters; 27 | 28 | static constexpr int PROGRESS = 0; 29 | }; 30 | -------------------------------------------------------------------------------- /src/ui/settings/input/DeleteSourceSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "DeleteSourceSettings.hpp" 2 | 3 | DeleteSourceSettings::DeleteSourceSettings(Parameters::DeleteSourceParameters& parameters, 4 | const QString& groupName) : 5 | AdvancedSettings(parameters, groupName), m_parameters(parameters) 6 | { 7 | read(); 8 | } 9 | 10 | 11 | bool 12 | DeleteSourceSettings::write() 13 | { 14 | if (!AdvancedSettings::write()) { 15 | return false; 16 | } 17 | 18 | writeParameter(m_groupName, "delete_source", m_parameters.deleteSource); 19 | 20 | return true; 21 | } 22 | 23 | 24 | bool 25 | DeleteSourceSettings::read() 26 | { 27 | if (!AdvancedSettings::read()) { 28 | return false; 29 | } 30 | 31 | m_parameters.deleteSource = readParameter(m_groupName, "delete_source", m_isDefaultValueTrue); 32 | 33 | return true; 34 | } 35 | -------------------------------------------------------------------------------- /src/cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (rt_cli INTERFACE) 2 | 3 | target_include_directories (rt_cli 4 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 5 | ) 6 | 7 | target_sources(rt_cli INTERFACE 8 | ${CMAKE_CURRENT_LIST_DIR}/BagToImages.cpp 9 | ${CMAKE_CURRENT_LIST_DIR}/BagToPCDs.cpp 10 | ${CMAKE_CURRENT_LIST_DIR}/BagToVideo.cpp 11 | ${CMAKE_CURRENT_LIST_DIR}/CompressBag.cpp 12 | ${CMAKE_CURRENT_LIST_DIR}/DecompressBag.cpp 13 | ${CMAKE_CURRENT_LIST_DIR}/DummyBag.cpp 14 | ${CMAKE_CURRENT_LIST_DIR}/MergeBags.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/PCDsToBag.cpp 16 | ${CMAKE_CURRENT_LIST_DIR}/PublishImages.cpp 17 | ${CMAKE_CURRENT_LIST_DIR}/PublishVideo.cpp 18 | ${CMAKE_CURRENT_LIST_DIR}/SendTF2.cpp 19 | ${CMAKE_CURRENT_LIST_DIR}/TF2ToFile.cpp 20 | ${CMAKE_CURRENT_LIST_DIR}/VideoToBag.cpp 21 | ) 22 | 23 | target_link_libraries(rt_cli 24 | INTERFACE Qt::Widgets rt_thread rt_utils 25 | ) 26 | -------------------------------------------------------------------------------- /src/ros/thread/BasicThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Basic thread class, overridden by custom classes 6 | class BasicThread : public QThread { 7 | Q_OBJECT 8 | public: 9 | explicit 10 | BasicThread(const QString& sourceDirectory, 11 | const QString& topicName, 12 | QObject* parent = nullptr); 13 | 14 | signals: 15 | void 16 | informOfGatheringData(); 17 | 18 | // Update progress displayal in widget 19 | void 20 | progressChanged(const QString& progressString, 21 | int progress); 22 | 23 | void 24 | finished(); 25 | 26 | void 27 | processing(); 28 | 29 | // Might fail in some cases (CV instance opening failed, invalid input params...) 30 | void 31 | failed(); 32 | 33 | protected: 34 | const std::string m_sourceDirectory; 35 | const std::string m_topicName; 36 | }; 37 | -------------------------------------------------------------------------------- /src/ros/thread/ChangeCompressionBagThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | // Thread used to write a compressed to an uncompressed file (or vice versa) 7 | // Makes use of the rosbag2_transport API for simplicity and performance 8 | class ChangeCompressionBagThread : public BasicThread { 9 | Q_OBJECT 10 | public: 11 | explicit 12 | ChangeCompressionBagThread(const Parameters::CompressBagParameters& parameters, 13 | int numberOfThreads, 14 | bool compress, 15 | QObject* parent = nullptr); 16 | 17 | void 18 | run() override; 19 | 20 | private: 21 | const Parameters::CompressBagParameters& m_parameters; 22 | 23 | int m_numberOfThreads; 24 | bool m_compress; 25 | }; 26 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/BagToImagesWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BagToImagesSettings.hpp" 4 | #include "Parameters.hpp" 5 | #include "TopicComboBoxWidget.hpp" 6 | 7 | class QCheckBox; 8 | class QFormLayout; 9 | class QSlider; 10 | 11 | // The widget used to manage writing images out of a ROS bag 12 | class BagToImagesWidget : public TopicComboBoxWidget 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | BagToImagesWidget(Parameters::BagToImagesParameters& parameters, 18 | QWidget* parent = 0); 19 | 20 | private slots: 21 | void 22 | adjustWidgetsToChangedFormat(const QString& text); 23 | 24 | private: 25 | QPointer m_qualitySlider; 26 | QPointer m_optimizeOrBilevelCheckBox; 27 | 28 | QPointer m_advancedOptionsFormLayout; 29 | 30 | Parameters::BagToImagesParameters& m_parameters; 31 | 32 | BagToImagesSettings m_settings; 33 | }; 34 | -------------------------------------------------------------------------------- /src/ui/settings/input/CompressBagSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "CompressBagSettings.hpp" 2 | 3 | CompressBagSettings::CompressBagSettings(Parameters::CompressBagParameters& parameters, 4 | const QString& groupName) : 5 | DeleteSourceSettings(parameters, groupName), m_parameters(parameters) 6 | { 7 | setDefaultValueToTrue(); 8 | read(); 9 | } 10 | 11 | 12 | bool 13 | CompressBagSettings::write() 14 | { 15 | if (!DeleteSourceSettings::write()) { 16 | return false; 17 | } 18 | 19 | writeParameter(m_groupName, "compress_per_message", m_parameters.compressPerMessage); 20 | 21 | return true; 22 | } 23 | 24 | 25 | bool 26 | CompressBagSettings::read() 27 | { 28 | if (!DeleteSourceSettings::read()) { 29 | return false; 30 | } 31 | 32 | m_parameters.compressPerMessage = readParameter(m_groupName, "compress_per_message", false); 33 | 34 | return true; 35 | } 36 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/BagToVideoWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BagToVideoSettings.hpp" 4 | #include "Parameters.hpp" 5 | #include "TopicComboBoxWidget.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class QCheckBox; 11 | class QComboBox; 12 | class QFormLayout; 13 | 14 | // Widget used to configure a video encoding out of a ros bag 15 | class BagToVideoWidget : public TopicComboBoxWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | BagToVideoWidget(Parameters::BagToVideoParameters& parameters, 21 | QWidget* parent = 0); 22 | 23 | private slots: 24 | void 25 | formatComboBoxTextChanged(const QString& text); 26 | 27 | private: 28 | QPointer m_formatComboBox; 29 | QPointer m_advancedOptionsFormLayout; 30 | QPointer m_useLosslessCheckBox; 31 | 32 | Parameters::BagToVideoParameters& m_parameters; 33 | 34 | BagToVideoSettings m_settings; 35 | }; 36 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/ChangeCompressionWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedInputWidget.hpp" 4 | #include "CompressBagSettings.hpp" 5 | #include "Parameters.hpp" 6 | 7 | // The widget used to manage compressing a bag file 8 | class ChangeCompressionWidget : public AdvancedInputWidget 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | ChangeCompressionWidget(Parameters::CompressBagParameters& parameters, 14 | bool compress, 15 | QWidget* parent = 0); 16 | 17 | private slots: 18 | void 19 | findSourceButtonPressed() override; 20 | 21 | void 22 | okButtonPressed() const override; 23 | 24 | private: 25 | [[nodiscard]] bool 26 | isBagFileValid(const QString& bagDirectory) const; 27 | 28 | private: 29 | Parameters::CompressBagParameters& m_parameters; 30 | 31 | CompressBagSettings m_settings; 32 | 33 | bool m_compress; 34 | }; 35 | -------------------------------------------------------------------------------- /.github/workflows/jazzy.yml: -------------------------------------------------------------------------------- 1 | name: jazzy 2 | 3 | on: [push] 4 | 5 | env: 6 | BUILD_TYPE: Release 7 | 8 | jobs: 9 | run_jazzy: 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - uses: ros-tooling/setup-ros@v0.7 16 | with: 17 | required-ros-distributions: jazzy 18 | 19 | - name: Install dependencies 20 | run: sudo apt update && sudo apt install -y qt6-base-dev ros-jazzy-catch-ros2 21 | 22 | - name: Create workspace and copy repo there 23 | run: mkdir -p ros2_ws/src/ros2_utils_tool && cp -r resources src test CMakeLists.txt package.xml ros2_ws/src/ros2_utils_tool 24 | 25 | - name: Source ROS and build ws 26 | run: cd ros2_ws && source /opt/ros/jazzy/setup.bash && colcon build --packages-select ros2_utils_tool 27 | 28 | - name: Source ws and run tests 29 | run: cd ros2_ws && source install/setup.bash && ros2 run ros2_utils_tool tool_tests 30 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/PCDsToBagWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "PCDsToBagSettings.hpp" 4 | #include "Parameters.hpp" 5 | #include "TopicComboBoxWidget.hpp" 6 | 7 | #include 8 | #include 9 | 10 | // Widget used to write pcd files to a ROS bag file 11 | class PCDsToBagWidget : public TopicComboBoxWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | PCDsToBagWidget(Parameters::PCDsToBagParameters& parameters, 17 | bool usePredefinedTopicName, 18 | bool warnROS2NameConvention, 19 | QWidget* parent = 0); 20 | 21 | private slots: 22 | void 23 | findSourceButtonPressed() override; 24 | 25 | void 26 | okButtonPressed() const override; 27 | 28 | private: 29 | Parameters::PCDsToBagParameters& m_parameters; 30 | 31 | PCDsToBagSettings m_settings; 32 | 33 | const bool m_warnROS2NameConvention; 34 | }; 35 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/TF2ToFileWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TF2ToFileSettings.hpp" 4 | #include "TopicComboBoxWidget.hpp" 5 | #include "Parameters.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class QComboBox; 11 | class QFormLayout; 12 | class QRadioButton; 13 | 14 | // Widget used to configure sending tf2 messages 15 | class TF2ToFileWidget : public TopicComboBoxWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | TF2ToFileWidget(Parameters::TF2ToFileParameters& parameters, 21 | QWidget* parent = 0); 22 | 23 | void 24 | formatComboBoxTextChanged(bool switched); 25 | 26 | private: 27 | QPointer m_advancedOptionsFormLayout; 28 | QPointer m_compactRadioButton; 29 | QPointer m_indentedRadioButton; 30 | QPointer m_formatComboBox; 31 | 32 | Parameters::TF2ToFileParameters& m_parameters; 33 | 34 | TF2ToFileSettings m_settings; 35 | }; 36 | -------------------------------------------------------------------------------- /.github/workflows/humble.yml: -------------------------------------------------------------------------------- 1 | name: humble 2 | 3 | on: [push] 4 | 5 | env: 6 | BUILD_TYPE: Release 7 | 8 | jobs: 9 | run_humble: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - uses: ros-tooling/setup-ros@v0.7 16 | with: 17 | required-ros-distributions: humble 18 | 19 | - name: Install dependencies 20 | run: sudo apt update && sudo apt install -y qt6-base-dev ros-humble-catch-ros2 21 | 22 | - name: Create workspace and copy repo there 23 | run: mkdir -p ros2_ws/src/ros2_utils_tool && cp -r resources src test CMakeLists.txt package.xml ros2_ws/src/ros2_utils_tool 24 | 25 | - name: Source ROS and build ws 26 | run: cd ros2_ws && source /opt/ros/humble/setup.bash && colcon build --packages-select ros2_utils_tool 27 | 28 | - name: Source ws and run tests 29 | run: cd ros2_ws && source install/setup.bash && ros2 run ros2_utils_tool tool_tests 30 | -------------------------------------------------------------------------------- /.github/workflows/kilted.yml: -------------------------------------------------------------------------------- 1 | name: kilted 2 | 3 | on: [push] 4 | 5 | env: 6 | BUILD_TYPE: Release 7 | 8 | jobs: 9 | run_kilted: 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - uses: ros-tooling/setup-ros@v0.7 16 | with: 17 | required-ros-distributions: kilted 18 | 19 | - name: Install dependencies 20 | run: sudo apt update && sudo apt install -y qt6-base-dev ros-kilted-catch-ros2 21 | 22 | - name: Create workspace and copy repo there 23 | run: mkdir -p ros2_ws/src/ros2_utils_tool && cp -r resources src test CMakeLists.txt package.xml ros2_ws/src/ros2_utils_tool 24 | 25 | - name: Source ROS and build ws 26 | run: cd ros2_ws && source /opt/ros/kilted/setup.bash && colcon build --packages-select ros2_utils_tool 27 | 28 | - name: Source ws and run tests 29 | run: cd ros2_ws && source install/setup.bash && ros2 run ros2_utils_tool tool_tests 30 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (rt_helper_widgets INTERFACE) 2 | 3 | target_include_directories (rt_helper_widgets 4 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 5 | ) 6 | 7 | target_sources(rt_helper_widgets INTERFACE 8 | ${CMAKE_CURRENT_LIST_DIR}/BasicBagWidget.cpp 9 | ${CMAKE_CURRENT_LIST_DIR}/BasicBagWidget.hpp 10 | ${CMAKE_CURRENT_LIST_DIR}/LowDiskSpaceWidget.cpp 11 | ${CMAKE_CURRENT_LIST_DIR}/LowDiskSpaceWidget.hpp 12 | ${CMAKE_CURRENT_LIST_DIR}/TopicWidget.cpp 13 | ${CMAKE_CURRENT_LIST_DIR}/TopicWidget.hpp 14 | ${CMAKE_CURRENT_LIST_DIR}/TopicComboBoxWidget.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/TopicComboBoxWidget.hpp 16 | ${CMAKE_CURRENT_LIST_DIR}/MessageCountWidget.cpp 17 | ${CMAKE_CURRENT_LIST_DIR}/MessageCountWidget.hpp 18 | ${CMAKE_CURRENT_LIST_DIR}/TopicListingInputWidget.cpp 19 | ${CMAKE_CURRENT_LIST_DIR}/TopicListingInputWidget.hpp 20 | ) 21 | 22 | target_link_libraries(rt_helper_widgets 23 | INTERFACE Qt::Widgets 24 | ) 25 | -------------------------------------------------------------------------------- /.github/workflows/rolling.yml: -------------------------------------------------------------------------------- 1 | name: rolling 2 | 3 | on: [push] 4 | 5 | env: 6 | BUILD_TYPE: Release 7 | 8 | jobs: 9 | run_rolling: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - uses: ros-tooling/setup-ros@v0.7 16 | with: 17 | required-ros-distributions: rolling 18 | 19 | - name: Install dependencies 20 | run: sudo apt update && sudo apt install -y qt6-base-dev ros-rolling-catch-ros2 21 | 22 | - name: Create workspace and copy repo there 23 | run: mkdir -p ros2_ws/src/ros2_utils_tool && cp -r resources src test CMakeLists.txt package.xml ros2_ws/src/ros2_utils_tool 24 | 25 | - name: Source ROS and build ws 26 | run: cd ros2_ws && source /opt/ros/rolling/setup.bash && colcon build --packages-select ros2_utils_tool 27 | 28 | - name: Source ws and run tests 29 | run: cd ros2_ws && source install/setup.bash && ros2 run ros2_utils_tool tool_tests 30 | -------------------------------------------------------------------------------- /resources/icons/publishing_tools_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /resources/icons/publishing_tools_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/RecordBagWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Parameters.hpp" 4 | #include "RecordBagSettings.hpp" 5 | #include "TopicListingInputWidget.hpp" 6 | 7 | #include 8 | 9 | class QLineEdit; 10 | class QWidget; 11 | 12 | // Widget used to manage recording a bag file 13 | class RecordBagWidget : public TopicListingInputWidget 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | RecordBagWidget(Parameters::RecordBagParameters& parameters, 19 | QWidget* parent = 0); 20 | 21 | private slots: 22 | void 23 | removeLineEdit(int row); 24 | 25 | void 26 | createNewTopicLineEdit(const QString& topicName, 27 | int index); 28 | 29 | private: 30 | std::optional 31 | areTopicsValid() const override; 32 | 33 | private: 34 | QVector > m_topicLineEdits; 35 | 36 | Parameters::RecordBagParameters& m_parameters; 37 | 38 | RecordBagSettings m_settings; 39 | }; 40 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/MessageCountWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // Display the lower and upper messages which should be extracted out of an existing ROS bag 8 | // into a newly edited one 9 | class MessageCountWidget : public QWidget 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | MessageCountWidget(int minimum, 15 | int maximum, 16 | int currentMaximumValue, 17 | QWidget* parent = 0); 18 | 19 | signals: 20 | void 21 | lowerValueChanged(int value); 22 | 23 | void 24 | upperValueChanged(int value); 25 | 26 | public: 27 | int 28 | getLowerValue() const 29 | { 30 | return m_lowerBox->value(); 31 | } 32 | 33 | int 34 | getHigherValue() const 35 | { 36 | return m_upperBox->value(); 37 | } 38 | 39 | private: 40 | QPointer m_lowerBox; 41 | QPointer m_upperBox; 42 | }; 43 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(catch_ros2 REQUIRED) 2 | 3 | add_executable(tool_tests 4 | ${CMAKE_CURRENT_LIST_DIR}/main.cpp 5 | 6 | ${CMAKE_CURRENT_LIST_DIR}/ros/threads/ThreadsTest.cpp 7 | 8 | ${CMAKE_CURRENT_LIST_DIR}/ui/settings/SettingsTest.cpp 9 | 10 | ${CMAKE_CURRENT_LIST_DIR}/utils/UtilsCLITest.cpp 11 | ${CMAKE_CURRENT_LIST_DIR}/utils/UtilsROSTest.cpp 12 | ${CMAKE_CURRENT_LIST_DIR}/utils/UtilsUITest.cpp 13 | ) 14 | 15 | target_link_libraries(tool_tests 16 | rclcpp::rclcpp rosbag2_cpp::rosbag2_cpp rosbag2_transport::rosbag2_transport 17 | cv_bridge::cv_bridge ${geometry_msgs_TARGETS} ${sensor_msgs_TARGETS} ${std_msgs_TARGETS} ${tf2_msgs_TARGETS} 18 | catch_ros2::catch_ros2_with_main Qt::Widgets rt_all_threads rt_settings_input rt_utils 19 | ) 20 | 21 | if($ENV{ROS_DISTRO} STREQUAL "kilted") 22 | target_link_libraries(tool_tests 23 | pcl_conversions::pcl_conversions 24 | ) 25 | else() 26 | ament_target_dependencies(tool_tests pcl_conversions) 27 | endif() 28 | 29 | -------------------------------------------------------------------------------- /src/ros/thread/PublishVideoThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | #include "rclcpp/rclcpp.hpp" 7 | #include "sensor_msgs/msg/image.hpp" 8 | 9 | // Thread used to publish a video as ROS topic 10 | // This thread also runs as a separate ROS node to enable the image messages publishing 11 | class PublishVideoThread : public BasicThread { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit 16 | PublishVideoThread(const Parameters::PublishParameters& parameters, 17 | bool useHardwareAcceleration, 18 | QObject* parent = nullptr); 19 | 20 | void 21 | run() override; 22 | 23 | private: 24 | std::shared_ptr m_node; 25 | rclcpp::Publisher::SharedPtr m_publisher; 26 | 27 | const Parameters::PublishParameters& m_parameters; 28 | 29 | const bool m_useHardwareAcceleration; 30 | 31 | static constexpr int PROGRESS = 0; 32 | }; 33 | -------------------------------------------------------------------------------- /src/ui/settings/input/TF2ToFileSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "TF2ToFileSettings.hpp" 2 | 3 | TF2ToFileSettings::TF2ToFileSettings(Parameters::TF2ToFileParameters& parameters, 4 | const QString& groupName) : 5 | AdvancedSettings(parameters, groupName), m_parameters(parameters) 6 | { 7 | read(); 8 | } 9 | 10 | 11 | bool 12 | TF2ToFileSettings::write() 13 | { 14 | if (!AdvancedSettings::write()) { 15 | return false; 16 | } 17 | 18 | writeParameter(m_groupName, "keep_timestamps", m_parameters.keepTimestamps); 19 | writeParameter(m_groupName, "compact_output", m_parameters.compactOutput); 20 | 21 | return true; 22 | } 23 | 24 | 25 | bool 26 | TF2ToFileSettings::read() 27 | { 28 | if (!AdvancedSettings::read()) { 29 | return false; 30 | } 31 | 32 | m_parameters.keepTimestamps = readParameter(m_groupName, "keep_timestamps", true); 33 | m_parameters.compactOutput = readParameter(m_groupName, "compact_output", true); 34 | 35 | return true; 36 | } 37 | -------------------------------------------------------------------------------- /resources/icons/bag_info_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /resources/icons/bag_info_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/TopicComboBoxWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedInputWidget.hpp" 4 | 5 | class QComboBox; 6 | 7 | // Augments the advanced input widget with an additional topic combo box 8 | class TopicComboBoxWidget : public AdvancedInputWidget 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | TopicComboBoxWidget(Parameters::AdvancedParameters& parameters, 14 | const QString& headerText, 15 | const QString& iconPath, 16 | const QString& sourceFormLayoutName, 17 | const QString& targetFormLayoutName, 18 | const QString& settingsIdentifier, 19 | int outputFormat, 20 | QWidget* parent = 0); 21 | 22 | protected slots: 23 | void 24 | findSourceButtonPressed() override; 25 | 26 | protected: 27 | QPointer m_topicNameComboBox; 28 | }; 29 | -------------------------------------------------------------------------------- /src/ui/settings/input/BagToVideoSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "BagToVideoSettings.hpp" 2 | 3 | BagToVideoSettings::BagToVideoSettings(Parameters::BagToVideoParameters& parameters, const QString& groupName) : 4 | VideoSettings(parameters, groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | BagToVideoSettings::write() 12 | { 13 | if (!VideoSettings::write()) { 14 | return false; 15 | } 16 | 17 | writeParameter(m_groupName, "format", m_parameters.format); 18 | writeParameter(m_groupName, "bw_images", m_parameters.useBWImages); 19 | writeParameter(m_groupName, "lossless_images", m_parameters.lossless); 20 | 21 | return true; 22 | } 23 | 24 | 25 | bool 26 | BagToVideoSettings::read() 27 | { 28 | if (!VideoSettings::read()) { 29 | return false; 30 | } 31 | 32 | m_parameters.format = readParameter(m_groupName, "format", QString("mp4")); 33 | m_parameters.useBWImages = readParameter(m_groupName, "bw_images", false); 34 | m_parameters.lossless = readParameter(m_groupName, "lossless_images", false); 35 | 36 | return true; 37 | } 38 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/TopicWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class QToolButton; 8 | 9 | // Small helper widget used to select a topic type for the ROS bag dummy creation 10 | class TopicWidget : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | TopicWidget(bool isDummyWidget, 16 | bool addRemoveButton, 17 | const QString& topicTypeText = "", 18 | const QString& topicNameText = "", 19 | QWidget* parent = 0); 20 | 21 | const QString 22 | getTopicName() const 23 | { 24 | return m_topicNameLineEdit->text(); 25 | } 26 | 27 | signals: 28 | void 29 | topicTypeChanged(QString type); 30 | 31 | void 32 | topicNameChanged(QString name); 33 | 34 | void 35 | topicRemoveButtonClicked(); 36 | 37 | private: 38 | void 39 | setPixmapLabelIcon() const; 40 | 41 | bool 42 | event(QEvent *event); 43 | 44 | private: 45 | QPointer m_topicNameLineEdit; 46 | QPointer m_removeTopicButton; 47 | }; 48 | -------------------------------------------------------------------------------- /src/ui/settings/input/AdvancedSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "AdvancedSettings.hpp" 2 | 3 | AdvancedSettings::AdvancedSettings(Parameters::AdvancedParameters& parameters, const QString& groupName) : 4 | BasicSettings(parameters, groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | AdvancedSettings::write() 12 | { 13 | if (!BasicSettings::write()) { 14 | return false; 15 | } 16 | 17 | writeParameter(m_groupName, "target_dir", m_parameters.targetDirectory); 18 | writeParameter(m_groupName, "topic_name", m_parameters.topicName); 19 | writeParameter(m_groupName, "show_advanced", m_parameters.showAdvancedOptions); 20 | 21 | return true; 22 | } 23 | 24 | 25 | bool 26 | AdvancedSettings::read() 27 | { 28 | if (!BasicSettings::read()) { 29 | return false; 30 | } 31 | 32 | m_parameters.targetDirectory = readParameter(m_groupName, "target_dir", QString("")); 33 | m_parameters.topicName = readParameter(m_groupName, "topic_name", QString("")); 34 | m_parameters.showAdvancedOptions = readParameter(m_groupName, "show_advanced", false); 35 | 36 | return true; 37 | } 38 | -------------------------------------------------------------------------------- /src/ui/ProgressWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Parameters.hpp" 4 | #include "UtilsUI.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class BasicThread; 11 | 12 | // Base widget showing overall progress 13 | // The progress widget will access the main thread used to perform the corresponding operation. 14 | // If the user presses the Cancel button, the thread will be cancelled and we'll return to the main window. 15 | class ProgressWidget : public QWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | ProgressWidget(const QString& headerLabelText, 21 | Parameters::BasicParameters& parameters, 22 | const Utils::UI::TOOL_ID threadTypeId, 23 | QWidget* parent = 0); 24 | 25 | ~ProgressWidget(); 26 | 27 | void 28 | startThread(); 29 | 30 | signals: 31 | void 32 | progressStopped(); 33 | 34 | void 35 | finished(); 36 | 37 | private: 38 | bool 39 | event(QEvent *event) override; 40 | 41 | private: 42 | QPointer m_thread; 43 | 44 | QPointer m_movie; 45 | }; 46 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/MergeBagsWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicBagWidget.hpp" 4 | #include "MergeBagsSettings.hpp" 5 | #include "Parameters.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | // Widget for editing a bag file 12 | class MergeBagsWidget : public BasicBagWidget 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit 17 | MergeBagsWidget(Parameters::MergeBagsParameters& mergeBagParameters, 18 | QWidget* parent = 0); 19 | 20 | private slots: 21 | void 22 | findSourceButtonPressed() override; 23 | 24 | void 25 | createTopicTree(bool resetTopicsParameter); 26 | 27 | void 28 | itemCheckStateChanged(QTreeWidgetItem* item, 29 | int column) override; 30 | 31 | void 32 | okButtonPressed() const override; 33 | 34 | private: 35 | QPointer m_secondSourceLineEdit; 36 | 37 | QPointer m_sufficientSpaceLabel; 38 | 39 | Parameters::MergeBagsParameters& m_parameters; 40 | 41 | MergeBagsSettings m_settings; 42 | 43 | bool m_secondSourceButtonClicked { false }; 44 | }; 45 | -------------------------------------------------------------------------------- /src/ui/settings/input/PublishSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "PublishSettings.hpp" 2 | 3 | PublishSettings::PublishSettings(Parameters::PublishParameters& parameters, const QString& groupName) : 4 | VideoSettings(parameters, groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | PublishSettings::write() 12 | { 13 | if (!VideoSettings::write()) { 14 | return false; 15 | } 16 | 17 | writeParameter(m_groupName, "loop", m_parameters.loop); 18 | writeParameter(m_groupName, "scale", m_parameters.scale); 19 | writeParameter(m_groupName, "width", m_parameters.width); 20 | writeParameter(m_groupName, "height", m_parameters.height); 21 | 22 | return true; 23 | } 24 | 25 | 26 | bool 27 | PublishSettings::read() 28 | { 29 | if (!VideoSettings::read()) { 30 | return false; 31 | } 32 | 33 | m_parameters.loop = readParameter(m_groupName, "loop", false); 34 | m_parameters.scale = readParameter(m_groupName, "scale", false); 35 | m_parameters.width = readParameter(m_groupName, "width", 1280); 36 | m_parameters.height = readParameter(m_groupName, "height", 720); 37 | 38 | return true; 39 | } 40 | -------------------------------------------------------------------------------- /resources/icons/send_tf2_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/icons/send_tf2_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/VideoToBagWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Parameters.hpp" 4 | #include "TopicComboBoxWidget.hpp" 5 | #include "VideoToBagSettings.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class QFormLayout; 11 | class QSpinBox; 12 | 13 | // Widget used to write a video file to a ROS bag file 14 | class VideoToBagWidget : public TopicComboBoxWidget 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | VideoToBagWidget(Parameters::VideoToBagParameters& parameters, 20 | bool usePredefinedTopicName, 21 | bool warnROS2NameConvention, 22 | QWidget* parent = 0); 23 | 24 | private slots: 25 | void 26 | findSourceButtonPressed() override; 27 | 28 | void 29 | useCustomFPSCheckBoxPressed(int state); 30 | 31 | void 32 | okButtonPressed() const override; 33 | 34 | private: 35 | QPointer m_advancedOptionsFormLayout; 36 | QPointer m_fpsSpinBox; 37 | 38 | Parameters::VideoToBagParameters& m_parameters; 39 | 40 | VideoToBagSettings m_settings; 41 | 42 | const bool m_warnROS2NameConvention; 43 | }; 44 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/BagToPCDsWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "BagToPCDsWidget.hpp" 2 | 3 | #include "UtilsUI.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | BagToPCDsWidget::BagToPCDsWidget(Parameters::AdvancedParameters& parameters, QWidget *parent) : 10 | TopicComboBoxWidget(parameters, "Bag to PCD Files", ":/icons/bag_to_pcd", "Bag File:", "PCDs Location:", "bag_to_pcds", OUTPUT_PCDS, parent), 11 | m_parameters(parameters), m_settings(parameters, "bag_to_pcds") 12 | { 13 | m_sourceLineEdit->setToolTip("The source bag file directory."); 14 | m_topicNameComboBox->setToolTip("The point cloud bag topic.\nIf the bag contains multiple point cloud topics, you can choose one of them."); 15 | m_targetLineEdit->setToolTip("The target point cloud files directory."); 16 | 17 | m_basicOptionsFormLayout->insertRow(1, "Topic Name:", m_topicNameComboBox); 18 | 19 | m_controlsLayout->addStretch(); 20 | 21 | // Generally, only enable this if the source bag, topic name and target dir line edit contain text 22 | enableOkButton(!m_parameters.sourceDirectory.isEmpty() && 23 | !m_topicNameComboBox->currentText().isEmpty() && !m_parameters.targetDirectory.isEmpty()); 24 | } 25 | -------------------------------------------------------------------------------- /resources/icons/topics_services_info_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/icons/topics_services_info_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/icons/edit_bag_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /resources/icons/edit_bag_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/PublishWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicInputWidget.hpp" 4 | #include "Parameters.hpp" 5 | #include "PublishSettings.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class QFormLayout; 11 | class QLineEdit; 12 | class QSpinBox; 13 | 14 | // Widget used to configure publishing a video OR image sequence as ROS messages 15 | class PublishWidget : public BasicInputWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | PublishWidget(Parameters::PublishParameters& parameters, 21 | bool usePredefinedTopicName, 22 | bool warnROS2NameConvention, 23 | bool publishVideo, 24 | QWidget* parent = 0); 25 | 26 | private slots: 27 | void 28 | searchButtonPressed(); 29 | 30 | void 31 | scaleCheckBoxPressed(int state); 32 | 33 | void 34 | okButtonPressed() const; 35 | 36 | private: 37 | QPointer m_topicNameLineEdit; 38 | QPointer m_advancedOptionsFormLayout; 39 | QPointer m_widthSpinBox; 40 | QPointer m_heightSpinBox; 41 | 42 | Parameters::PublishParameters& m_parameters; 43 | 44 | PublishSettings m_settings; 45 | 46 | const bool m_warnROS2NameConvention; 47 | const bool m_publishVideo; 48 | }; 49 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/SendTF2Widget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicInputWidget.hpp" 4 | #include "Parameters.hpp" 5 | #include "SendTF2Settings.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class NodeWrapper; 11 | 12 | class QCheckBox; 13 | class QFormLayout; 14 | class QLabel; 15 | class QSpinBox; 16 | class QTimer; 17 | 18 | // Widget used to configure sending a TF2 message 19 | class SendTF2Widget : public BasicInputWidget 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | SendTF2Widget(Parameters::SendTF2Parameters& parameters, 25 | QWidget* parent = 0); 26 | 27 | private slots: 28 | void 29 | staticCheckBoxPressed(int state); 30 | 31 | void 32 | okButtonPressed() const; 33 | 34 | private: 35 | QPointer m_isStaticCheckBox; 36 | QPointer m_rateSpinBox; 37 | QPointer m_formLayout; 38 | 39 | QPointer m_transformSentLabel; 40 | QPointer m_timer; 41 | 42 | std::shared_ptr m_nodeWrapper; 43 | 44 | Parameters::SendTF2Parameters& m_parameters; 45 | 46 | SendTF2Settings m_settings; 47 | 48 | static constexpr double SPINBOX_LOWER_RANGE = -1000.0; 49 | static constexpr double SPINBOX_UPPER_RANGE = 1000.0; 50 | static constexpr int NUMBER_OF_DECIMALS = 5; 51 | static constexpr int LABEL_SHOWN_DURATION = 1500; 52 | }; 53 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/EditBagWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicBagWidget.hpp" 4 | #include "EditBagSettings.hpp" 5 | #include "Parameters.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class QCheckBox; 11 | class QLabel; 12 | 13 | // Widget for editing a bag file 14 | class EditBagWidget : public BasicBagWidget 15 | { 16 | Q_OBJECT 17 | public: 18 | explicit 19 | EditBagWidget(Parameters::EditBagParameters& parameters, 20 | bool warnROS2NameConvention, 21 | QWidget* parent = 0); 22 | 23 | private slots: 24 | void 25 | findSourceButtonPressed() override; 26 | 27 | void 28 | createTopicTree(); 29 | 30 | void 31 | itemCheckStateChanged(QTreeWidgetItem* item, 32 | int column) override; 33 | 34 | void 35 | okButtonPressed() const override; 36 | 37 | private: 38 | QPointer m_editLabel; 39 | QPointer m_differentDirsLabel; 40 | 41 | QPointer m_updateTimestampsCheckBox; 42 | 43 | Parameters::EditBagParameters& m_parameters; 44 | 45 | EditBagSettings m_settings; 46 | 47 | const bool m_warnROS2NameConvention; 48 | 49 | static constexpr int COL_MESSAGE_COUNT = 3; 50 | static constexpr int COL_RENAMING = 4; 51 | 52 | static constexpr int BUFFER_SPACE = 200; 53 | }; 54 | -------------------------------------------------------------------------------- /src/ui/settings/input/BagToImagesSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "BagToImagesSettings.hpp" 2 | 3 | BagToImagesSettings::BagToImagesSettings(Parameters::BagToImagesParameters& parameters, const QString& groupName) : 4 | RGBSettings(parameters, groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | BagToImagesSettings::write() 12 | { 13 | if (!RGBSettings::write()) { 14 | return false; 15 | } 16 | 17 | writeParameter(m_groupName, "format", m_parameters.format); 18 | writeParameter(m_groupName, "quality", m_parameters.quality); 19 | writeParameter(m_groupName, "bw_images", m_parameters.useBWImages); 20 | writeParameter(m_groupName, "jpg_optimize", m_parameters.jpgOptimize); 21 | writeParameter(m_groupName, "png_bilevel", m_parameters.pngBilevel); 22 | 23 | return true; 24 | } 25 | 26 | 27 | bool 28 | BagToImagesSettings::read() 29 | { 30 | if (!RGBSettings::read()) { 31 | return false; 32 | } 33 | 34 | m_parameters.format = readParameter(m_groupName, "format", QString("jpg")); 35 | m_parameters.quality = readParameter(m_groupName, "quality", 8); 36 | m_parameters.useBWImages = readParameter(m_groupName, "bw_images", false); 37 | m_parameters.jpgOptimize = readParameter(m_groupName, "jpg_optimize", false); 38 | m_parameters.pngBilevel = readParameter(m_groupName, "png_bilevel", false); 39 | 40 | return true; 41 | } 42 | -------------------------------------------------------------------------------- /resources/icons/dummy_bag_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/icons/dummy_bag_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/icons/bag_to_video_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/icons/bag_to_video_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/icons/video_to_bag_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/icons/video_to_bag_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/icons/compress_bag_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/icons/compress_bag_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/icons/decompress_bag_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/icons/decompress_bag_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/MessageCountWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "MessageCountWidget.hpp" 2 | 3 | #include 4 | #include 5 | 6 | MessageCountWidget::MessageCountWidget(int minimum, int maximum, int currentMaximumValue, QWidget *parent) : 7 | QWidget(parent) 8 | { 9 | m_lowerBox = new QSpinBox; 10 | // Start with a value of 0 instead one 1, most users should be able to handle that :-P 11 | m_lowerBox->setRange(0, maximum); 12 | m_lowerBox->setValue(minimum); 13 | 14 | m_upperBox = new QSpinBox; 15 | m_upperBox->setRange(1, maximum); 16 | m_upperBox->setValue(currentMaximumValue); 17 | 18 | auto* const rangeDifferenceLabel = new QLabel("-"); 19 | 20 | auto* const mainLayout = new QHBoxLayout; 21 | mainLayout->addWidget(m_lowerBox); 22 | mainLayout->addStretch(); 23 | mainLayout->addWidget(rangeDifferenceLabel); 24 | mainLayout->addStretch(); 25 | mainLayout->addWidget(m_upperBox); 26 | // Set these margins because we are going to integrate the widget into existing widgets 27 | // where any extra widgets would create undesired extra space 28 | mainLayout->setContentsMargins(0, 0, 0, 0); 29 | 30 | setLayout(mainLayout); 31 | 32 | connect(m_lowerBox, QOverload::of(&QSpinBox::valueChanged), this, [this] (int value) { 33 | emit lowerValueChanged(value); 34 | }); 35 | connect(m_upperBox, QOverload::of(&QSpinBox::valueChanged), this, [this] (int value) { 36 | emit upperValueChanged(value); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /resources/icons/publish_video_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resources/icons/publish_video_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/DummyBagWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DummyBagSettings.hpp" 4 | #include "Parameters.hpp" 5 | #include "TopicListingInputWidget.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class QSpinBox; 11 | 12 | // Widget used to manage creating a ROS bag with dummy data 13 | class DummyBagWidget : public TopicListingInputWidget 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | DummyBagWidget(Parameters::DummyBagParameters& parameters, 19 | bool warnROS2NameConvention, 20 | QWidget* parent = 0); 21 | 22 | private slots: 23 | void 24 | removeDummyTopicWidget(int row); 25 | 26 | void 27 | createNewDummyTopicWidget(const Parameters::DummyBagParameters::DummyBagTopic& topics, 28 | int index, 29 | bool isCtor = true); 30 | 31 | void 32 | useCustomRateCheckBoxPressed(int state); 33 | 34 | private: 35 | std::optional 36 | areTopicsValid() const override; 37 | 38 | private: 39 | QPointer m_rateSpinBox; 40 | 41 | Parameters::DummyBagParameters& m_parameters; 42 | 43 | DummyBagSettings m_settings; 44 | 45 | const bool m_warnROS2NameConvention; 46 | 47 | static constexpr int TOPIC_WIDGET_OFFSET_CTOR = 3; 48 | static constexpr int TOPIC_WIDGET_OFFSET = 4; 49 | static constexpr int MAXIMUM_NUMBER_OF_TOPICS = 5; 50 | }; 51 | -------------------------------------------------------------------------------- /resources/icons/gear_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /resources/icons/record_bag_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/icons/record_bag_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/icons/gear_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ui/settings/DialogSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "DialogSettings.hpp" 2 | 3 | DialogSettings::DialogSettings(Parameters::DialogParameters& parameters, const QString& groupName) : 4 | GeneralSettings(groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | DialogSettings::write() 12 | { 13 | writeParameter(m_groupName, "max_threads", m_parameters.maxNumberOfThreads); 14 | writeParameter(m_groupName, "hw_acc", m_parameters.useHardwareAcceleration); 15 | writeParameter(m_groupName, "save_parameters", m_parameters.saveParameters); 16 | writeParameter(m_groupName, "predefined_topic_names", m_parameters.usePredefinedTopicNames); 17 | writeParameter(m_groupName, "warn_ros2_name_convention", m_parameters.warnROS2NameConvention); 18 | writeParameter(m_groupName, "warn_target_overwrite", m_parameters.warnTargetOverwrite); 19 | writeParameter(m_groupName, "warn_low_disk_space", m_parameters.warnLowDiskSpace); 20 | 21 | return true; 22 | } 23 | 24 | 25 | bool 26 | DialogSettings::read() 27 | { 28 | m_parameters.maxNumberOfThreads = readParameter(m_groupName, "max_threads", std::thread::hardware_concurrency()); 29 | m_parameters.useHardwareAcceleration = readParameter(m_groupName, "hw_acc", false); 30 | m_parameters.saveParameters = readParameter(m_groupName, "save_parameters", false); 31 | m_parameters.usePredefinedTopicNames = readParameter(m_groupName, "predefined_topic_names", true); 32 | m_parameters.warnROS2NameConvention = readParameter(m_groupName, "warn_ros2_name_convention", false); 33 | m_parameters.warnTargetOverwrite = readParameter(m_groupName, "warn_target_overwrite", true); 34 | m_parameters.warnLowDiskSpace = readParameter(m_groupName, "warn_low_disk_space", true); 35 | 36 | return true; 37 | } 38 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/BasicBagWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedInputWidget.hpp" 4 | #include "DeleteSourceSettings.hpp" 5 | #include "Parameters.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class QCheckBox; 11 | class QHBoxLayout; 12 | class QTreeWidget; 13 | class QTreeWidgetItem; 14 | 15 | // Widget for displaying bag contents for manipulation 16 | class BasicBagWidget : public AdvancedInputWidget 17 | { 18 | Q_OBJECT 19 | public: 20 | explicit 21 | BasicBagWidget(Parameters::DeleteSourceParameters& parameters, 22 | const QString& titleText, 23 | const QString& iconText, 24 | const QString& settingsIdentifierText, 25 | const int outputFormat, 26 | QWidget* parent = 0); 27 | 28 | protected slots: 29 | virtual void 30 | itemCheckStateChanged(QTreeWidgetItem* item, 31 | int column) = 0; 32 | 33 | protected: 34 | [[nodiscard]] bool 35 | areIOParametersValid(int topicSize, 36 | int topicSizeWithoutDuplicates, 37 | const QString& secondSourceParameter = QString()) const; 38 | 39 | protected: 40 | QPointer m_treeWidget; 41 | 42 | QPointer m_deleteSourceCheckBox; 43 | 44 | QPointer m_diskSpaceLayout; 45 | 46 | QPointer m_findTargetWidget; 47 | 48 | static constexpr int COL_CHECKBOXES = 0; 49 | static constexpr int COL_TOPIC_NAME = 1; 50 | static constexpr int COL_TOPIC_TYPE = 2; 51 | 52 | private: 53 | Parameters::DeleteSourceParameters& m_parameters; 54 | 55 | DeleteSourceSettings m_settings; 56 | 57 | bool m_isDiskSpaceSufficient{ true }; 58 | }; 59 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/TopicListingInputWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicInputWidget.hpp" 4 | #include "BasicSettings.hpp" 5 | #include "Parameters.hpp" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | class TopicWidget; 13 | 14 | class QFormLayout; 15 | class QHBoxLayout; 16 | class QLabel; 17 | class QToolButton; 18 | class QVBoxLayout; 19 | 20 | // A widget showing widgets to add and remove topics 21 | class TopicListingInputWidget : public BasicInputWidget 22 | { 23 | Q_OBJECT 24 | public: 25 | explicit 26 | TopicListingInputWidget(Parameters::BasicParameters& parameters, 27 | const QString& titleText, 28 | const QString& iconText, 29 | const QString& settingsIdentifierText, 30 | QWidget* parent = 0); 31 | 32 | protected slots: 33 | void 34 | sourceButtonPressed(); 35 | 36 | void 37 | okButtonPressed() const; 38 | 39 | protected: 40 | virtual std::optional 41 | areTopicsValid() const = 0; 42 | 43 | // Have to overwrite this one because we are using more additional icons then just the top one 44 | void 45 | setPixmapLabelIcon() const; 46 | 47 | bool 48 | event(QEvent *event); 49 | 50 | protected: 51 | // A QFormLayout provides no easy access to layout labels, so we keep them 52 | // in an extra vector for renaming after a row was removed 53 | QVector > m_topicLabels; 54 | QVector > m_topicWidgets; 55 | 56 | QPointer m_addTopicButton; 57 | 58 | QPointer m_formLayout; 59 | QPointer m_topicButtonLayout; 60 | QPointer m_controlsLayout; 61 | 62 | int m_numberOfTopics = 0; 63 | 64 | private: 65 | Parameters::BasicParameters& m_parameters; 66 | 67 | BasicSettings m_settings; 68 | }; 69 | -------------------------------------------------------------------------------- /src/ui/settings/input/MergeBagsSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "MergeBagsSettings.hpp" 2 | 3 | MergeBagsSettings::MergeBagsSettings(Parameters::MergeBagsParameters& parameters, 4 | const QString& groupName) : 5 | DeleteSourceSettings(parameters, groupName), m_parameters(parameters) 6 | { 7 | read(); 8 | } 9 | 10 | 11 | bool 12 | MergeBagsSettings::write() 13 | { 14 | if (!DeleteSourceSettings::write()) { 15 | return false; 16 | } 17 | 18 | QSettings settings; 19 | settings.beginGroup(m_groupName); 20 | 21 | settings.beginWriteArray("topics"); 22 | for (auto i = 0; i < m_parameters.topics.size(); ++i) { 23 | settings.setArrayIndex(i); 24 | writeParameter(settings, "name", m_parameters.topics.at(i).name); 25 | writeParameter(settings, "dir", m_parameters.topics.at(i).bagDir); 26 | writeParameter(settings, "is_selected", m_parameters.topics.at(i).isSelected); 27 | } 28 | settings.endArray(); 29 | settings.endGroup(); 30 | 31 | writeParameter(m_groupName, "second_source", m_parameters.secondSourceDirectory); 32 | 33 | return true; 34 | } 35 | 36 | 37 | bool 38 | MergeBagsSettings::read() 39 | { 40 | if (!DeleteSourceSettings::read()) { 41 | return false; 42 | } 43 | 44 | QSettings settings; 45 | settings.beginGroup(m_groupName); 46 | m_parameters.topics.clear(); 47 | 48 | const auto size = settings.beginReadArray("topics"); 49 | for (auto i = 0; i < size; ++i) { 50 | settings.setArrayIndex(i); 51 | m_parameters.topics.append({ readParameter(settings, "name", QString("")), readParameter(settings, "dir", QString("")), 52 | readParameter(settings, "is_selected", false) }); 53 | } 54 | settings.endArray(); 55 | settings.endGroup(); 56 | 57 | m_parameters.secondSourceDirectory = readParameter(m_groupName, "second_source", QString("")); 58 | 59 | return true; 60 | } 61 | -------------------------------------------------------------------------------- /src/ui/settings/input/DummyBagSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "DummyBagSettings.hpp" 2 | 3 | DummyBagSettings::DummyBagSettings(Parameters::DummyBagParameters& parameters, const QString& groupName) : 4 | BasicSettings(parameters, groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | DummyBagSettings::write() 12 | { 13 | if (!BasicSettings::write()) { 14 | return false; 15 | } 16 | 17 | QSettings settings; 18 | settings.beginGroup(m_groupName); 19 | 20 | settings.beginWriteArray("topics"); 21 | for (auto i = 0; i < m_parameters.topics.size(); ++i) { 22 | settings.setArrayIndex(i); 23 | writeParameter(settings, "type", m_parameters.topics.at(i).type); 24 | writeParameter(settings, "name", m_parameters.topics.at(i).name); 25 | } 26 | settings.endArray(); 27 | settings.endGroup(); 28 | 29 | writeParameter(m_groupName, "msg_count", m_parameters.messageCount); 30 | writeParameter(m_groupName, "rate", m_parameters.rate); 31 | writeParameter(m_groupName, "use_custom_rate", m_parameters.useCustomRate); 32 | 33 | return true; 34 | } 35 | 36 | 37 | bool 38 | DummyBagSettings::read() 39 | { 40 | if (!BasicSettings::read()) { 41 | return false; 42 | } 43 | 44 | QSettings settings; 45 | 46 | settings.beginGroup(m_groupName); 47 | m_parameters.topics.clear(); 48 | 49 | const auto size = settings.beginReadArray("topics"); 50 | for (auto i = 0; i < size; ++i) { 51 | settings.setArrayIndex(i); 52 | m_parameters.topics.append({ readParameter(settings, "type", QString("")), readParameter(settings, "name", QString("")) }); 53 | } 54 | settings.endArray(); 55 | settings.endGroup(); 56 | 57 | m_parameters.messageCount = readParameter(m_groupName, "msg_count", 100); 58 | m_parameters.rate = readParameter(m_groupName, "rate", 10); 59 | m_parameters.useCustomRate = readParameter(m_groupName, "use_custom_rate", false); 60 | 61 | return true; 62 | } 63 | -------------------------------------------------------------------------------- /src/ros/thread/MergeBagsThread.cpp: -------------------------------------------------------------------------------- 1 | #include "MergeBagsThread.hpp" 2 | 3 | #include "rosbag2_transport/bag_rewrite.hpp" 4 | 5 | #include 6 | 7 | MergeBagsThread::MergeBagsThread(const Parameters::MergeBagsParameters& parameters, 8 | unsigned int numberOfThreads, QObject* parent) : 9 | BasicThread(parameters.sourceDirectory, "", parent), 10 | m_parameters(parameters), m_numberOfThreads(numberOfThreads) 11 | { 12 | } 13 | 14 | 15 | void 16 | MergeBagsThread::run() 17 | { 18 | const auto targetDirectoryStd = m_parameters.targetDirectory.toStdString(); 19 | if (std::filesystem::exists(targetDirectoryStd)) { 20 | std::filesystem::remove_all(targetDirectoryStd); 21 | } 22 | 23 | // Setup input parameters 24 | rosbag2_storage::StorageOptions inputStorageFirstBag; 25 | inputStorageFirstBag.uri = m_sourceDirectory; 26 | rosbag2_storage::StorageOptions inputStorageSecondBag; 27 | inputStorageSecondBag.uri = m_parameters.secondSourceDirectory.toStdString(); 28 | 29 | // Output parameters 30 | rosbag2_storage::StorageOptions outputStorage; 31 | outputStorage.uri = targetDirectoryStd; 32 | 33 | rosbag2_transport::RecordOptions outputRecord; 34 | for (const auto& topic : m_parameters.topics) { 35 | if (!topic.isSelected) { 36 | continue; 37 | } 38 | 39 | outputRecord.topics.push_back(topic.name.toStdString()); 40 | } 41 | 42 | std::vector > outputBags; 43 | outputBags.push_back({ outputStorage, outputRecord }); 44 | 45 | emit processing(); 46 | // Do merge 47 | rosbag2_transport::bag_rewrite({ inputStorageFirstBag, inputStorageSecondBag }, outputBags); 48 | 49 | if (m_parameters.deleteSource) { 50 | std::filesystem::remove_all(m_sourceDirectory); 51 | std::filesystem::remove_all(m_parameters.secondSourceDirectory.toStdString()); 52 | } 53 | emit finished(); 54 | } 55 | -------------------------------------------------------------------------------- /resources/icons/main.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 2 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/ros/thread/SendTF2Thread.cpp: -------------------------------------------------------------------------------- 1 | #include "SendTF2Thread.hpp" 2 | 3 | SendTF2Thread::SendTF2Thread(const Parameters::SendTF2Parameters& parameters, 4 | QObject* parent) : 5 | BasicThread(parameters.sourceDirectory, "", parent), 6 | m_parameters(parameters) 7 | { 8 | m_node = std::make_shared("tf_node"); 9 | m_broadcaster = std::make_shared(m_node); 10 | } 11 | 12 | 13 | void 14 | SendTF2Thread::run() 15 | { 16 | const int rate = ((1000 / (float) m_parameters.rate) * 1000); 17 | geometry_msgs::msg::TransformStamped transformStamped; 18 | 19 | // Send the transformation using the specified rate 20 | auto timer = m_node->create_wall_timer(std::chrono::microseconds(rate), [this, &transformStamped] { 21 | if (isInterruptionRequested()) { 22 | return; 23 | } 24 | 25 | transformStamped.header.stamp = m_node->get_clock()->now(); 26 | transformStamped.header.frame_id = "world"; 27 | transformStamped.child_frame_id = m_parameters.childFrameName.toStdString(); 28 | 29 | transformStamped.transform.translation.x = m_parameters.translation[0]; 30 | transformStamped.transform.translation.y = m_parameters.translation[1]; 31 | transformStamped.transform.translation.z = m_parameters.translation[2]; 32 | 33 | transformStamped.transform.rotation.x = m_parameters.rotation[0]; 34 | transformStamped.transform.rotation.y = m_parameters.rotation[1]; 35 | transformStamped.transform.rotation.z = m_parameters.rotation[2]; 36 | transformStamped.transform.rotation.w = m_parameters.rotation[3]; 37 | 38 | m_broadcaster->sendTransform(transformStamped); 39 | }); 40 | 41 | auto executor = std::make_shared(); 42 | executor->add_node(m_node); 43 | 44 | while (!isInterruptionRequested()) { 45 | executor->spin_once(); 46 | } 47 | 48 | timer->cancel(); 49 | emit finished(); 50 | } 51 | -------------------------------------------------------------------------------- /src/ui/settings/DialogSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GeneralSettings.hpp" 4 | #include "Parameters.hpp" 5 | 6 | template 7 | concept DialogSettingsParameter = std::same_as || std::same_as; 8 | 9 | // Settings modified from settings dialog 10 | class DialogSettings : public GeneralSettings { 11 | public: 12 | DialogSettings(Parameters::DialogParameters& parameters, 13 | const QString& groupName); 14 | 15 | // Make these static because we need to access and modify some values from many different places 16 | // without having to use the entire dialog parameter instance 17 | template 18 | requires DialogSettingsParameter 19 | static T 20 | getStaticParameter(const QString& identifier, 21 | T defaultValue) 22 | { 23 | QSettings settings; 24 | settings.beginGroup("dialog"); 25 | T staticParameter; 26 | 27 | if constexpr (std::is_same_v) { 28 | staticParameter = settings.value(identifier).isValid() ? settings.value(identifier).toInt() : defaultValue; 29 | } else { 30 | staticParameter = settings.value(identifier).isValid() ? settings.value(identifier).toBool() : defaultValue; 31 | } 32 | 33 | settings.endGroup(); 34 | return staticParameter; 35 | } 36 | 37 | template 38 | requires DialogSettingsParameter 39 | static void 40 | writeStaticParameter(const QString& identifier, 41 | T value) 42 | { 43 | QSettings settings; 44 | settings.beginGroup("dialog"); 45 | 46 | if (settings.value(identifier).value() == value) { 47 | return; 48 | } 49 | 50 | settings.setValue(identifier, value); 51 | } 52 | 53 | bool 54 | write() override; 55 | 56 | private: 57 | bool 58 | read() override; 59 | 60 | private: 61 | Parameters::DialogParameters& m_parameters; 62 | }; 63 | -------------------------------------------------------------------------------- /resources/icons/merge_bags_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/icons/merge_bags_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/ros/thread/ChangeCompressionBagThread.cpp: -------------------------------------------------------------------------------- 1 | #include "ChangeCompressionBagThread.hpp" 2 | 3 | #include "rosbag2_transport/bag_rewrite.hpp" 4 | 5 | #include 6 | 7 | ChangeCompressionBagThread::ChangeCompressionBagThread(const Parameters::CompressBagParameters& parameters, 8 | int numberOfThreads, bool compress, QObject* parent) : 9 | BasicThread(parameters.sourceDirectory, parameters.topicName, parent), 10 | m_parameters(parameters), m_numberOfThreads(numberOfThreads), m_compress(compress) 11 | { 12 | } 13 | 14 | 15 | void 16 | ChangeCompressionBagThread::run() 17 | { 18 | const auto targetDirectoryStd = m_parameters.targetDirectory.toStdString(); 19 | if (std::filesystem::exists(targetDirectoryStd)) { 20 | std::filesystem::remove_all(targetDirectoryStd); 21 | } 22 | 23 | // Input params 24 | rosbag2_storage::StorageOptions inputStorage; 25 | inputStorage.uri = m_sourceDirectory; 26 | 27 | // Output params 28 | rosbag2_storage::StorageOptions outputStorage; 29 | outputStorage.uri = targetDirectoryStd; 30 | 31 | rosbag2_transport::RecordOptions outputRecord; 32 | #ifdef ROS_HUMBLE 33 | outputRecord.all = true; 34 | #else 35 | outputRecord.all_topics = true; 36 | #endif 37 | if (m_compress) { 38 | outputRecord.compression_format = "zstd"; 39 | outputRecord.compression_mode = m_parameters.compressPerMessage ? "message" : "file"; 40 | outputRecord.compression_threads = m_numberOfThreads; 41 | // Need to set this so no messages are dropped 42 | outputRecord.compression_queue_size = 0; 43 | } 44 | 45 | std::vector > outputBags; 46 | outputBags.push_back({ outputStorage, outputRecord }); 47 | 48 | emit processing(); 49 | // Main rewrite 50 | rosbag2_transport::bag_rewrite({ inputStorage }, outputBags); 51 | 52 | if (m_parameters.deleteSource) { 53 | std::filesystem::remove_all(m_sourceDirectory); 54 | } 55 | emit finished(); 56 | } 57 | -------------------------------------------------------------------------------- /src/ui/settings/input/RecordBagSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "RecordBagSettings.hpp" 2 | 3 | RecordBagSettings::RecordBagSettings(Parameters::RecordBagParameters& parameters, const QString& groupName) : 4 | BasicSettings(parameters, groupName), m_parameters(parameters) 5 | { 6 | read(); 7 | } 8 | 9 | 10 | bool 11 | RecordBagSettings::write() 12 | { 13 | if (!BasicSettings::write()) { 14 | return false; 15 | } 16 | 17 | QSettings settings; 18 | settings.beginGroup(m_groupName); 19 | 20 | settings.beginWriteArray("topics"); 21 | for (auto i = 0; i < m_parameters.topics.size(); ++i) { 22 | settings.setArrayIndex(i); 23 | writeParameter(settings, "name", m_parameters.topics.at(i)); 24 | } 25 | settings.endArray(); 26 | settings.endGroup(); 27 | 28 | writeParameter(m_groupName, "all_topics", m_parameters.allTopics); 29 | writeParameter(m_groupName, "show_advanced", m_parameters.showAdvancedOptions); 30 | writeParameter(m_groupName, "include_hidden_topics", m_parameters.includeHiddenTopics); 31 | writeParameter(m_groupName, "include_unpublished_topics", m_parameters.includeUnpublishedTopics); 32 | 33 | return true; 34 | } 35 | 36 | 37 | bool 38 | RecordBagSettings::read() 39 | { 40 | if (!BasicSettings::read()) { 41 | return false; 42 | } 43 | 44 | QSettings settings; 45 | 46 | settings.beginGroup(m_groupName); 47 | m_parameters.topics.clear(); 48 | 49 | const auto size = settings.beginReadArray("topics"); 50 | for (auto i = 0; i < size; ++i) { 51 | settings.setArrayIndex(i); 52 | m_parameters.topics.append({ readParameter(settings, "name", QString("")) }); 53 | } 54 | settings.endArray(); 55 | settings.endGroup(); 56 | 57 | m_parameters.allTopics = readParameter(m_groupName, "all_topics", true); 58 | m_parameters.showAdvancedOptions = readParameter(m_groupName, "show_advanced", false); 59 | m_parameters.includeHiddenTopics = readParameter(m_groupName, "include_hidden_topics", false); 60 | m_parameters.includeUnpublishedTopics = readParameter(m_groupName, "include_unpublished_topics", false); 61 | 62 | return true; 63 | } 64 | -------------------------------------------------------------------------------- /src/ros/thread/RecordBagThread.cpp: -------------------------------------------------------------------------------- 1 | #include "RecordBagThread.hpp" 2 | 3 | #include "rclcpp/rclcpp.hpp" 4 | #include "rosbag2_transport/recorder.hpp" 5 | 6 | #include 7 | 8 | RecordBagThread::RecordBagThread(const Parameters::RecordBagParameters& parameters, QObject* parent) : 9 | BasicThread(parameters.sourceDirectory, "", parent), m_parameters(parameters) 10 | { 11 | } 12 | 13 | 14 | void 15 | RecordBagThread::run() 16 | { 17 | const auto targetDirectoryStd = m_parameters.sourceDirectory.toStdString(); 18 | if (std::filesystem::exists(targetDirectoryStd)) { 19 | std::filesystem::remove_all(targetDirectoryStd); 20 | } 21 | 22 | rosbag2_storage::StorageOptions storageOptions; 23 | storageOptions.uri = targetDirectoryStd; 24 | 25 | rosbag2_transport::RecordOptions recordOptions; 26 | if (m_parameters.allTopics) { 27 | #ifdef ROS_HUMBLE 28 | recordOptions.all = true; 29 | #else 30 | recordOptions.all_topics = true; 31 | #endif 32 | } else { 33 | for (const auto& topic : m_parameters.topics) { 34 | recordOptions.topics.push_back(topic.toStdString()); 35 | } 36 | } 37 | recordOptions.rmw_serialization_format = "cdr"; 38 | recordOptions.include_hidden_topics = m_parameters.includeHiddenTopics; 39 | recordOptions.include_unpublished_topics = m_parameters.includeUnpublishedTopics; 40 | 41 | auto writer = std::make_unique(); 42 | auto recorder = std::make_shared(std::move(writer), storageOptions, recordOptions); 43 | 44 | // Initialize recorder 45 | recorder->record(); 46 | auto executor = std::make_shared(); 47 | executor->add_node(recorder); 48 | 49 | // Need to spin it in an extra thread for writing messages (no idea why, though) 50 | auto spinThread = std::thread([executor] { 51 | executor->spin(); 52 | }); 53 | 54 | rclcpp::Rate rate(50); 55 | while (!isInterruptionRequested()) { 56 | rate.sleep(); 57 | } 58 | 59 | recorder->stop(); 60 | executor->cancel(); 61 | spinThread.join(); 62 | 63 | emit finished(); 64 | } 65 | -------------------------------------------------------------------------------- /src/ui/settings/input/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (rt_settings_input INTERFACE) 2 | 3 | target_include_directories (rt_settings_input 4 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 5 | ) 6 | 7 | target_sources(rt_settings_input INTERFACE 8 | ${CMAKE_CURRENT_LIST_DIR}/AdvancedSettings.cpp 9 | ${CMAKE_CURRENT_LIST_DIR}/AdvancedSettings.hpp 10 | ${CMAKE_CURRENT_LIST_DIR}/BagToImagesSettings.cpp 11 | ${CMAKE_CURRENT_LIST_DIR}/BagToImagesSettings.hpp 12 | ${CMAKE_CURRENT_LIST_DIR}/BagToVideoSettings.cpp 13 | ${CMAKE_CURRENT_LIST_DIR}/BagToVideoSettings.hpp 14 | ${CMAKE_CURRENT_LIST_DIR}/BasicSettings.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/BasicSettings.hpp 16 | ${CMAKE_CURRENT_LIST_DIR}/CompressBagSettings.cpp 17 | ${CMAKE_CURRENT_LIST_DIR}/CompressBagSettings.hpp 18 | ${CMAKE_CURRENT_LIST_DIR}/DeleteSourceSettings.cpp 19 | ${CMAKE_CURRENT_LIST_DIR}/DeleteSourceSettings.hpp 20 | ${CMAKE_CURRENT_LIST_DIR}/DummyBagSettings.cpp 21 | ${CMAKE_CURRENT_LIST_DIR}/DummyBagSettings.hpp 22 | ${CMAKE_CURRENT_LIST_DIR}/EditBagSettings.cpp 23 | ${CMAKE_CURRENT_LIST_DIR}/EditBagSettings.hpp 24 | ${CMAKE_CURRENT_LIST_DIR}/MergeBagsSettings.cpp 25 | ${CMAKE_CURRENT_LIST_DIR}/MergeBagsSettings.hpp 26 | ${CMAKE_CURRENT_LIST_DIR}/PCDsToBagSettings.cpp 27 | ${CMAKE_CURRENT_LIST_DIR}/PCDsToBagSettings.hpp 28 | ${CMAKE_CURRENT_LIST_DIR}/PublishSettings.cpp 29 | ${CMAKE_CURRENT_LIST_DIR}/PublishSettings.hpp 30 | ${CMAKE_CURRENT_LIST_DIR}/RecordBagSettings.cpp 31 | ${CMAKE_CURRENT_LIST_DIR}/RecordBagSettings.hpp 32 | ${CMAKE_CURRENT_LIST_DIR}/RGBSettings.cpp 33 | ${CMAKE_CURRENT_LIST_DIR}/RGBSettings.hpp 34 | ${CMAKE_CURRENT_LIST_DIR}/SendTF2Settings.cpp 35 | ${CMAKE_CURRENT_LIST_DIR}/SendTF2Settings.hpp 36 | ${CMAKE_CURRENT_LIST_DIR}/TF2ToFileSettings.cpp 37 | ${CMAKE_CURRENT_LIST_DIR}/TF2ToFileSettings.hpp 38 | ${CMAKE_CURRENT_LIST_DIR}/VideoSettings.cpp 39 | ${CMAKE_CURRENT_LIST_DIR}/VideoSettings.hpp 40 | ${CMAKE_CURRENT_LIST_DIR}/VideoToBagSettings.cpp 41 | ${CMAKE_CURRENT_LIST_DIR}/VideoToBagSettings.hpp 42 | ) 43 | 44 | target_link_libraries(rt_settings_input 45 | INTERFACE Qt::Widgets rt_settings 46 | ) 47 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (rt_input_widgets INTERFACE) 2 | 3 | target_include_directories (rt_input_widgets 4 | INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} 5 | ) 6 | 7 | target_sources(rt_input_widgets INTERFACE 8 | ${CMAKE_CURRENT_LIST_DIR}/AdvancedInputWidget.cpp 9 | ${CMAKE_CURRENT_LIST_DIR}/AdvancedInputWidget.hpp 10 | ${CMAKE_CURRENT_LIST_DIR}/BagInfoWidget.cpp 11 | ${CMAKE_CURRENT_LIST_DIR}/BagInfoWidget.hpp 12 | ${CMAKE_CURRENT_LIST_DIR}/BagToPCDsWidget.cpp 13 | ${CMAKE_CURRENT_LIST_DIR}/BagToPCDsWidget.hpp 14 | ${CMAKE_CURRENT_LIST_DIR}/BagToImagesWidget.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/BagToImagesWidget.hpp 16 | ${CMAKE_CURRENT_LIST_DIR}/BagToVideoWidget.cpp 17 | ${CMAKE_CURRENT_LIST_DIR}/BagToVideoWidget.hpp 18 | ${CMAKE_CURRENT_LIST_DIR}/BasicInputWidget.cpp 19 | ${CMAKE_CURRENT_LIST_DIR}/BasicInputWidget.hpp 20 | ${CMAKE_CURRENT_LIST_DIR}/ChangeCompressionWidget.cpp 21 | ${CMAKE_CURRENT_LIST_DIR}/ChangeCompressionWidget.hpp 22 | ${CMAKE_CURRENT_LIST_DIR}/DummyBagWidget.cpp 23 | ${CMAKE_CURRENT_LIST_DIR}/DummyBagWidget.hpp 24 | ${CMAKE_CURRENT_LIST_DIR}/EditBagWidget.cpp 25 | ${CMAKE_CURRENT_LIST_DIR}/EditBagWidget.hpp 26 | ${CMAKE_CURRENT_LIST_DIR}/MergeBagsWidget.cpp 27 | ${CMAKE_CURRENT_LIST_DIR}/MergeBagsWidget.hpp 28 | ${CMAKE_CURRENT_LIST_DIR}/PCDsToBagWidget.cpp 29 | ${CMAKE_CURRENT_LIST_DIR}/PCDsToBagWidget.hpp 30 | ${CMAKE_CURRENT_LIST_DIR}/PublishWidget.cpp 31 | ${CMAKE_CURRENT_LIST_DIR}/PublishWidget.hpp 32 | ${CMAKE_CURRENT_LIST_DIR}/RecordBagWidget.cpp 33 | ${CMAKE_CURRENT_LIST_DIR}/RecordBagWidget.hpp 34 | ${CMAKE_CURRENT_LIST_DIR}/SendTF2Widget.cpp 35 | ${CMAKE_CURRENT_LIST_DIR}/SendTF2Widget.hpp 36 | ${CMAKE_CURRENT_LIST_DIR}/TF2ToFileWidget.cpp 37 | ${CMAKE_CURRENT_LIST_DIR}/TF2ToFileWidget.hpp 38 | ${CMAKE_CURRENT_LIST_DIR}/TopicsServicesInfoWidget.cpp 39 | ${CMAKE_CURRENT_LIST_DIR}/TopicsServicesInfoWidget.hpp 40 | ${CMAKE_CURRENT_LIST_DIR}/VideoToBagWidget.cpp 41 | ${CMAKE_CURRENT_LIST_DIR}/VideoToBagWidget.hpp 42 | ) 43 | 44 | target_link_libraries(rt_input_widgets 45 | INTERFACE Qt::Widgets rt_helper_widgets rt_settings_input rt_utils 46 | ) 47 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/LowDiskSpaceWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "LowDiskSpaceWidget.hpp" 2 | 3 | #include "UtilsGeneral.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | LowDiskSpaceWidget::LowDiskSpaceWidget(QWidget *parent) : 12 | QWidget(parent) 13 | { 14 | m_warningIconLabel = new QLabel; 15 | m_diskSpaceLabel = new QLabel; 16 | 17 | auto labelFont = m_diskSpaceLabel->font(); 18 | labelFont.setBold(true); 19 | m_diskSpaceLabel->setFont(labelFont); 20 | 21 | auto palette = m_diskSpaceLabel->palette(); 22 | palette.setColor(QPalette::WindowText, Qt::red); 23 | m_diskSpaceLabel->setPalette(palette); 24 | 25 | auto* const mainLayout = new QHBoxLayout; 26 | mainLayout->addWidget(m_warningIconLabel); 27 | mainLayout->addWidget(m_diskSpaceLabel); 28 | mainLayout->addStretch(); 29 | // Will be integrated into other widgets, so remove the extra space 30 | mainLayout->setContentsMargins(0, 0, 0, 0); 31 | setLayout(mainLayout); 32 | 33 | setPixmapLabelIcon(); 34 | } 35 | 36 | 37 | void 38 | LowDiskSpaceWidget::setVisibility(const QString& path) 39 | { 40 | const auto diskSpace = Utils::General::getAvailableDriveSpace(path); 41 | m_isDiskSpaceSufficient = diskSpace > Utils::General::MINIMUM_RECOMMENDED_DRIVE_SPACE; 42 | 43 | if (!m_isDiskSpaceSufficient) { 44 | m_diskSpaceLabel->setText("Free available space is only " + QString::number(diskSpace) + " GiB!"); 45 | } 46 | m_warningIconLabel->setVisible(!m_isDiskSpaceSufficient); 47 | m_diskSpaceLabel->setVisible(!m_isDiskSpaceSufficient); 48 | } 49 | 50 | 51 | void 52 | LowDiskSpaceWidget::setPixmapLabelIcon() const 53 | { 54 | const auto visibility = m_warningIconLabel->isVisible(); 55 | 56 | m_warningIconLabel->setPixmap(QIcon(":/icons/warning.svg").pixmap(QSize(ICON_SIZE, ICON_SIZE))); 57 | m_warningIconLabel->setVisible(visibility); 58 | } 59 | 60 | 61 | bool 62 | LowDiskSpaceWidget::event(QEvent *event) 63 | { 64 | [[unlikely]] if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::PaletteChange) { 65 | setPixmapLabelIcon(); 66 | } 67 | return QWidget::event(event); 68 | } 69 | -------------------------------------------------------------------------------- /resources/icons/tf2_to_file_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resources/icons/tf2_to_file_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/ui/settings/input/SendTF2Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "SendTF2Settings.hpp" 2 | 3 | SendTF2Settings::SendTF2Settings(Parameters::SendTF2Parameters& parameters, 4 | const QString& groupName) : 5 | BasicSettings(parameters, groupName), m_parameters(parameters) 6 | { 7 | read(); 8 | } 9 | 10 | 11 | bool 12 | SendTF2Settings::write() 13 | { 14 | if (!BasicSettings::write()) { 15 | return false; 16 | } 17 | 18 | writeParameter(m_groupName, "translation_x", m_parameters.translation[0]); 19 | writeParameter(m_groupName, "translation_y", m_parameters.translation[1]); 20 | writeParameter(m_groupName, "translation_z", m_parameters.translation[2]); 21 | writeParameter(m_groupName, "rotation_x", m_parameters.rotation[0]); 22 | writeParameter(m_groupName, "rotation_y", m_parameters.rotation[1]); 23 | writeParameter(m_groupName, "rotation_z", m_parameters.rotation[2]); 24 | writeParameter(m_groupName, "rotation_w", m_parameters.rotation[3]); 25 | writeParameter(m_groupName, "name", m_parameters.childFrameName); 26 | writeParameter(m_groupName, "rate", m_parameters.rate); 27 | writeParameter(m_groupName, "is_static", m_parameters.isStatic); 28 | 29 | return true; 30 | } 31 | 32 | 33 | bool 34 | SendTF2Settings::read() 35 | { 36 | if (!BasicSettings::read()) { 37 | return false; 38 | } 39 | 40 | m_parameters.translation[0] = readParameter(m_groupName, "translation_x", 0.0); 41 | m_parameters.translation[1] = readParameter(m_groupName, "translation_y", 0.0); 42 | m_parameters.translation[2] = readParameter(m_groupName, "translation_z", 0.0); 43 | m_parameters.rotation[0] = readParameter(m_groupName, "rotation_x", 0.0); 44 | m_parameters.rotation[1] = readParameter(m_groupName, "rotation_y", 0.0); 45 | m_parameters.rotation[2] = readParameter(m_groupName, "rotation_z", 0.0); 46 | m_parameters.rotation[3] = readParameter(m_groupName, "rotation_w", 0.0); 47 | m_parameters.childFrameName = readParameter(m_groupName, "name", QString("tf_test")); 48 | m_parameters.rate = readParameter(m_groupName, "rate", 1); 49 | m_parameters.isStatic = readParameter(m_groupName, "is_static", true); 50 | 51 | return true; 52 | } 53 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/AdvancedInputWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AdvancedSettings.hpp" 4 | #include "BasicInputWidget.hpp" 5 | #include "Parameters.hpp" 6 | 7 | class QFormLayout; 8 | class QLineEdit; 9 | class QVBoxLayout; 10 | 11 | // Derived from basic input, provides functions to search for an input bag or a target directory 12 | class AdvancedInputWidget : public BasicInputWidget 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | AdvancedInputWidget(Parameters::AdvancedParameters& parameters, 18 | const QString& headerText, 19 | const QString& iconPath, 20 | const QString& sourceFormLayoutName, 21 | const QString& targetFormLayoutName, 22 | const QString& settingsIdentifier, 23 | int outputFormat, 24 | QWidget* parent = 0); 25 | 26 | protected slots: 27 | virtual void 28 | findSourceButtonPressed(); 29 | 30 | void 31 | findTargetButtonPressed(); 32 | 33 | virtual void 34 | okButtonPressed() const; 35 | 36 | void 37 | setFileFormat(const QString& fileFormat) 38 | { 39 | m_fileFormat = fileFormat; 40 | } 41 | 42 | protected: 43 | void 44 | fillTargetLineEdit(); 45 | 46 | protected: 47 | QPointer m_targetLineEdit; 48 | 49 | QPointer m_basicOptionsFormLayout; 50 | QPointer m_controlsLayout; 51 | 52 | Parameters::AdvancedParameters& m_parameters; 53 | 54 | AdvancedSettings m_settings; 55 | 56 | const int m_outputFormat; 57 | 58 | static constexpr int OUTPUT_VIDEO = 0; 59 | static constexpr int OUTPUT_IMAGES = 1; 60 | static constexpr int OUTPUT_PCDS = 2; 61 | static constexpr int OUTPUT_TF_TO_FILE = 3; 62 | static constexpr int OUTPUT_BAG = 4; 63 | static constexpr int OUTPUT_BAG_EDITED = 5; 64 | static constexpr int OUTPUT_BAG_MERGED = 6; 65 | static constexpr int OUTPUT_BAG_COMPRESSED = 7; 66 | static constexpr int OUTPUT_BAG_DECOMPRESSED = 8; 67 | 68 | private: 69 | QString m_fileFormat; 70 | }; 71 | -------------------------------------------------------------------------------- /src/ros/thread/PCDsToBagThread.cpp: -------------------------------------------------------------------------------- 1 | #include "PCDsToBagThread.hpp" 2 | 3 | #include 4 | 5 | #include "rosbag2_cpp/writer.hpp" 6 | 7 | #include "sensor_msgs/msg/point_cloud.hpp" 8 | 9 | #include 10 | 11 | PCDsToBagThread::PCDsToBagThread(const Parameters::PCDsToBagParameters& parameters, QObject* parent) : 12 | BasicThread(parameters.sourceDirectory, parameters.topicName, parent), 13 | m_parameters(parameters) 14 | { 15 | } 16 | 17 | 18 | void 19 | PCDsToBagThread::run() 20 | { 21 | const auto targetDirectoryStd = m_parameters.targetDirectory.toStdString(); 22 | if (std::filesystem::exists(targetDirectoryStd)) { 23 | std::filesystem::remove_all(targetDirectoryStd); 24 | } 25 | 26 | emit informOfGatheringData(); 27 | 28 | auto frameCount = 0; 29 | // It is faster to first store all valid pcd file paths and then iterate over those 30 | std::set sortedPCDsSet; 31 | for (auto const& entry : std::filesystem::directory_iterator(m_sourceDirectory)) { 32 | if (entry.path().extension() != ".pcd") { 33 | continue; 34 | } 35 | 36 | sortedPCDsSet.insert(entry.path()); 37 | frameCount++; 38 | } 39 | 40 | pcl::PCLPointCloud2 cloud; 41 | sensor_msgs::msg::PointCloud2 message; 42 | pcl::PCDReader reader; 43 | 44 | auto writer = std::make_unique(); 45 | writer->open(targetDirectoryStd); 46 | 47 | auto iterationCount = 1; 48 | auto timeStamp = rclcpp::Clock(RCL_ROS_TIME).now(); 49 | const auto duration = rclcpp::Duration::from_seconds(1.0f / (float) m_parameters.rate); 50 | 51 | while (true) { 52 | if (isInterruptionRequested() || sortedPCDsSet.empty()) { 53 | break; 54 | } 55 | 56 | // Read pcd from file 57 | reader.read(*sortedPCDsSet.begin(), cloud); 58 | timeStamp += duration; 59 | // Write 60 | pcl_conversions::fromPCL(cloud, message); 61 | writer->write(message, m_topicName, timeStamp); 62 | 63 | sortedPCDsSet.erase(sortedPCDsSet.begin()); 64 | emit progressChanged("Writing pcd file " + QString::number(iterationCount) + " of " + QString::number(frameCount) + "...", 65 | ((float) iterationCount / (float) frameCount) * 100); 66 | iterationCount++; 67 | } 68 | 69 | emit finished(); 70 | } 71 | -------------------------------------------------------------------------------- /src/ui/settings/input/EditBagSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "EditBagSettings.hpp" 2 | 3 | EditBagSettings::EditBagSettings(Parameters::EditBagParameters& parameters, 4 | const QString& groupName) : 5 | DeleteSourceSettings(parameters, groupName), m_parameters(parameters) 6 | { 7 | read(); 8 | } 9 | 10 | 11 | bool 12 | EditBagSettings::write() 13 | { 14 | if (!DeleteSourceSettings::write()) { 15 | return false; 16 | } 17 | 18 | QSettings settings; 19 | settings.beginGroup(m_groupName); 20 | 21 | settings.beginWriteArray("topics"); 22 | for (auto i = 0; i < m_parameters.topics.size(); ++i) { 23 | settings.setArrayIndex(i); 24 | writeParameter(settings, "renamed_name", m_parameters.topics.at(i).renamedTopicName); 25 | writeParameter(settings, "original_name", m_parameters.topics.at(i).originalTopicName); 26 | writeParameter(settings, "is_selected", m_parameters.topics.at(i).isSelected); 27 | writeParameter(settings, "lower_boundary", m_parameters.topics.at(i).lowerBoundary); 28 | writeParameter(settings, "upper_boundary", m_parameters.topics.at(i).upperBoundary); 29 | } 30 | settings.endArray(); 31 | settings.endGroup(); 32 | 33 | writeParameter(m_groupName, "update_timestamps", m_parameters.updateTimestamps); 34 | 35 | return true; 36 | } 37 | 38 | 39 | bool 40 | EditBagSettings::read() 41 | { 42 | if (!DeleteSourceSettings::read()) { 43 | return false; 44 | } 45 | 46 | QSettings settings; 47 | settings.beginGroup(m_groupName); 48 | m_parameters.topics.clear(); 49 | 50 | const auto size = settings.beginReadArray("topics"); 51 | for (auto i = 0; i < size; ++i) { 52 | settings.setArrayIndex(i); 53 | m_parameters.topics.append({ readParameter(settings, "renamed_name", QString("")), readParameter(settings, "original_name", QString("")), 54 | static_cast(readParameter(settings, "lower_boundary", 0)), 55 | static_cast(readParameter(settings, "upper_boundary", 0)), 56 | readParameter(settings, "is_selected", false) }); 57 | } 58 | settings.endArray(); 59 | settings.endGroup(); 60 | 61 | m_parameters.updateTimestamps = readParameter(m_groupName, "update_timestamps", false); 62 | 63 | return true; 64 | } 65 | -------------------------------------------------------------------------------- /src/ui/MainWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Parameters.hpp" 4 | #include "UtilsUI.hpp" 5 | 6 | #include 7 | 8 | // Main window displaying the main user interface. The basic workflow is as follows: 9 | // The main window uses the start widget to show all availabe tools. If the start widget is called, 10 | // this will call a corresponding input widget where a user can modify all necessary parameters. 11 | // If done, the input widget will be replaced with a progress widget showing the current progress. 12 | // The progress widget calls a separate thread performing the main operation in the background. 13 | class MainWindow : public QMainWindow 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | MainWindow(); 19 | 20 | private slots: 21 | // Used to switch between start, input and progress widget 22 | void 23 | setStartWidget(); 24 | 25 | void 26 | setInputWidget(Utils::UI::TOOL_ID mode); 27 | 28 | void 29 | setProgressWidget(Utils::UI::TOOL_ID mode); 30 | 31 | private: 32 | void 33 | closeEvent(QCloseEvent *event) override; 34 | 35 | private: 36 | // Parameters storing all configurations done by a user in the input widgets. 37 | // The parameters are transferred to the progress widget and their thread. 38 | Parameters::BagToVideoParameters m_parametersBagToVideo; 39 | Parameters::VideoToBagParameters m_parametersVideoToBag; 40 | Parameters::AdvancedParameters m_parametersBagToPCDs; 41 | Parameters::PCDsToBagParameters m_parametersPCDsToBag; 42 | Parameters::BagToImagesParameters m_parametersBagToImages; 43 | Parameters::TF2ToFileParameters m_parametersTF2ToFile; 44 | Parameters::EditBagParameters m_parametersEditBag; 45 | Parameters::MergeBagsParameters m_parametersMergeBags; 46 | Parameters::RecordBagParameters m_parametersRecordBag; 47 | Parameters::DummyBagParameters m_parametersDummyBag; 48 | Parameters::CompressBagParameters m_parametersCompressBag; 49 | Parameters::CompressBagParameters m_parametersDecompressBag; 50 | 51 | Parameters::PublishParameters m_parametersPublishVideo; 52 | Parameters::PublishParameters m_parametersPublishImages; 53 | 54 | Parameters::SendTF2Parameters m_parametersSendTF2; 55 | // Parameters for settings dialog 56 | Parameters::DialogParameters m_dialogParameters; 57 | 58 | static constexpr int DEFAULT_WIDTH = 450; 59 | static constexpr int DEFAULT_HEIGHT = 600; 60 | }; 61 | -------------------------------------------------------------------------------- /src/utils/UtilsUI.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // Util functions for user interface related things 13 | namespace Utils::UI 14 | { 15 | // Create a larger font for some buttons and widget headers 16 | void 17 | setWidgetFontSize(QWidget* widget, 18 | bool isButton = false); 19 | 20 | [[maybe_unused]] bool 21 | fillComboBoxWithTopics(QPointer comboBox, 22 | const QString& bagDirectory, 23 | const QString& topicType); 24 | 25 | [[nodiscard]] QCheckBox* 26 | createCheckBox(const QString& toolTipText, 27 | bool checkState); 28 | 29 | // Creates a layout of a lineedit along with a tool button 30 | [[nodiscard]] QHBoxLayout* 31 | createLineEditButtonLayout(QPointer lineEdit, 32 | QPointer toolButton); 33 | 34 | // Create a messagebox asking if a user should continue with invalid ROS2 names 35 | [[nodiscard]] bool 36 | continueWithInvalidROS2Names(); 37 | 38 | // Create a messagebox asking if a user should continue if a target file is already existing 39 | bool 40 | continueForExistingTarget(const QString& targetDirectory, 41 | const QString& headerTextBeginning, 42 | const QString& targetIdentifier); 43 | 44 | [[nodiscard]] QCheckBox* 45 | createMessageBoxCheckBox(const QString& optionsIdentifier); 46 | 47 | // Creates a messagebox informing of a critical error 48 | void 49 | createCriticalMessageBox(const QString& headerText, 50 | const QString& mainText); 51 | 52 | // Replaces a text appendix with another one 53 | const QString 54 | replaceTextAppendix(const QString& inputText, 55 | const QString& newAppendix); 56 | 57 | // Checks if the application is in dark mode 58 | [[nodiscard]] bool 59 | isDarkMode(); 60 | 61 | enum class TOOL_ID { 62 | BAG_TO_VIDEO, 63 | VIDEO_TO_BAG, 64 | BAG_TO_PCDS, 65 | PCDS_TO_BAG, 66 | BAG_TO_IMAGES, 67 | TF2_TO_FILE, 68 | EDIT_BAG, 69 | MERGE_BAGS, 70 | RECORD_BAG, 71 | DUMMY_BAG, 72 | COMPRESS_BAG, 73 | DECOMPRESS_BAG, 74 | PUBLISH_VIDEO, 75 | PUBLISH_IMAGES, 76 | SEND_TF2, 77 | TOPICS_SERVICES_INFO, 78 | BAG_INFO 79 | }; 80 | 81 | static constexpr int FONT_SIZE_HEADER = 16; 82 | static constexpr int FONT_SIZE_BUTTON = 14; 83 | } 84 | -------------------------------------------------------------------------------- /test/utils/UtilsUITest.cpp: -------------------------------------------------------------------------------- 1 | #include "catch_ros2/catch_ros2.hpp" 2 | 3 | #include "UtilsUI.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include "rclcpp/rclcpp.hpp" 9 | 10 | #include "rosbag2_cpp/writer.hpp" 11 | 12 | #include "sensor_msgs/msg/image.hpp" 13 | 14 | #include 15 | 16 | TEST_CASE("Utils UI Testing", "[utils]") { 17 | SECTION("Font size test") { 18 | auto* const widget = new QWidget; 19 | 20 | Utils::UI::setWidgetFontSize(widget); 21 | auto font = widget->font(); 22 | REQUIRE(font.pointSize() == 16); 23 | 24 | Utils::UI::setWidgetFontSize(widget, true); 25 | font = widget->font(); 26 | REQUIRE(font.pointSize() == 14); 27 | } 28 | SECTION("Number of topics test") { 29 | const auto bagDirectory = std::filesystem::path("test_bag_file"); 30 | std::filesystem::remove_all(bagDirectory); 31 | 32 | rosbag2_cpp::Writer writer; 33 | writer.open(bagDirectory); 34 | 35 | for (auto i = 0; i < 5; i++) { 36 | sensor_msgs::msg::Image imageMessage; 37 | imageMessage.width = 1; 38 | imageMessage.height = 1; 39 | writer.write(imageMessage, "/topic_image_one", rclcpp::Clock().now()); 40 | writer.write(imageMessage, "/topic_image_two", rclcpp::Clock().now()); 41 | } 42 | writer.close(); 43 | 44 | auto* const comboBox = new QComboBox; 45 | Utils::UI::fillComboBoxWithTopics(comboBox, QString::fromStdString(bagDirectory), "sensor_msgs/msg/Image"); 46 | 47 | REQUIRE(comboBox->count() == 2); 48 | std::filesystem::remove_all(bagDirectory); 49 | } 50 | SECTION("Checkbox test") { 51 | auto* checkBox = Utils::UI::createCheckBox("This is a tooltip", true); 52 | REQUIRE(checkBox->toolTip() == "This is a tooltip"); 53 | REQUIRE(checkBox->checkState() == Qt::Checked); 54 | 55 | checkBox = Utils::UI::createCheckBox("Another tooltip", false); 56 | REQUIRE(checkBox->toolTip() == "Another tooltip"); 57 | REQUIRE(checkBox->checkState() == Qt::Unchecked); 58 | } 59 | SECTION("New appendix test") { 60 | const auto textSingleAppendix = "text_with_a_format.mp4"; 61 | const auto& newTextSingleAppendix = Utils::UI::replaceTextAppendix(textSingleAppendix, "mkv"); 62 | 63 | REQUIRE(QString::compare(newTextSingleAppendix, "text_with_a_format.mkv") == 0); 64 | 65 | const auto textMultipleAppendices = "text_with_a_format.mp4.mkv"; 66 | const auto& newTextMultipleAppendices = Utils::UI::replaceTextAppendix(textMultipleAppendices, "avi"); 67 | REQUIRE(QString::compare(newTextMultipleAppendices, "text_with_a_format.mp4.avi") == 0); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/TopicComboBoxWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "TopicComboBoxWidget.hpp" 2 | 3 | #include "UtilsUI.hpp" 4 | 5 | #include 6 | 7 | TopicComboBoxWidget::TopicComboBoxWidget(Parameters::AdvancedParameters& parameters, const QString& headerText, 8 | const QString& iconPath, const QString& sourceFormLayoutName, const QString& targetFormLayoutName, 9 | const QString& settingsIdentifier, int outputFormat, QWidget *parent) : 10 | AdvancedInputWidget(parameters, headerText, iconPath, sourceFormLayoutName, targetFormLayoutName, settingsIdentifier, outputFormat, parent) 11 | { 12 | m_topicNameComboBox = new QComboBox; 13 | m_topicNameComboBox->setMinimumWidth(200); 14 | 15 | if (!m_parameters.sourceDirectory.isEmpty()) { 16 | QString topicType; 17 | 18 | switch (outputFormat) { 19 | case OUTPUT_VIDEO: 20 | case OUTPUT_IMAGES: 21 | topicType = "sensor_msgs/msg/Image"; 22 | break; 23 | case OUTPUT_PCDS: 24 | topicType = "sensor_msgs/msg/PointCloud2"; 25 | break; 26 | case OUTPUT_TF_TO_FILE: 27 | topicType = "tf2_msgs/msg/TFMessage"; 28 | break; 29 | default: 30 | break; 31 | } 32 | 33 | Utils::UI::fillComboBoxWithTopics(m_topicNameComboBox, m_parameters.sourceDirectory, topicType); 34 | 35 | if (!m_parameters.topicName.isEmpty()) { 36 | m_topicNameComboBox->setCurrentText(m_parameters.topicName); 37 | } 38 | } 39 | 40 | connect(m_topicNameComboBox, &QComboBox::currentTextChanged, this, [this] (const QString& text) { 41 | writeParameterToSettings(m_parameters.topicName, text, m_settings); 42 | });; 43 | } 44 | 45 | 46 | void 47 | TopicComboBoxWidget::findSourceButtonPressed() 48 | { 49 | AdvancedInputWidget::findSourceButtonPressed(); 50 | 51 | QString topicType; 52 | switch (m_outputFormat) { 53 | case OUTPUT_VIDEO: 54 | topicType = "sensor_msgs/msg/Image"; 55 | break; 56 | case OUTPUT_IMAGES: 57 | topicType = "sensor_msgs/msg/Image"; 58 | break; 59 | case OUTPUT_PCDS: 60 | topicType = "sensor_msgs/msg/PointCloud2"; 61 | break; 62 | case OUTPUT_TF_TO_FILE: 63 | topicType = "tf2_msgs/msg/TFMessage"; 64 | break; 65 | default: 66 | break; 67 | } 68 | 69 | if (const auto containsTopics = Utils::UI::fillComboBoxWithTopics(m_topicNameComboBox, m_sourceLineEdit->text(), topicType); !containsTopics) { 70 | Utils::UI::createCriticalMessageBox("Topic not found!", "The bag file does not contain any corresponding topics!"); 71 | return; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /resources/icons/publish_images_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /resources/icons/publish_images_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/ui/InputWidgets/BasicInputWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicSettings.hpp" 4 | 5 | #include 6 | #include 7 | 8 | class LowDiskSpaceWidget; 9 | 10 | class QDialogButtonBox; 11 | class QHBoxLayout; 12 | class QLabel; 13 | class QLineEdit; 14 | class QPushButton; 15 | class QToolButton; 16 | 17 | template 18 | concept InputParameterSetting = (std::same_as && (std::same_as || std::same_as || 19 | std::same_as || std::same_as || std::same_as)); 20 | 21 | // The basic input widget all other input widgets derive from 22 | // Each input widget uses a corresponding parameter and settings member. 23 | // The parameters are used to store all input for reusing if the widget is closed and opened again, 24 | // while the settings are used to write the parameters to file in case the parameters should be 25 | // used after closing and restarting the application. 26 | 27 | // The basic widget provides a few members and functions used by all derived members. 28 | class BasicInputWidget : public QWidget 29 | { 30 | Q_OBJECT 31 | 32 | public: 33 | // Set the header text and top icon automatically 34 | BasicInputWidget(const QString& headerText, 35 | const QString& iconPath, 36 | QWidget* parent = 0); 37 | 38 | signals: 39 | void 40 | back() const; 41 | 42 | void 43 | okPressed() const; 44 | 45 | protected: 46 | void 47 | enableOkButton(bool enable) const; 48 | 49 | void 50 | setPixmapLabelIcon() const; 51 | 52 | void 53 | setLowDiskSpaceWidgetVisibility(const QString& path); 54 | 55 | [[nodiscard]] bool 56 | showLowDiskSpaceMessageBox() const; 57 | 58 | bool 59 | event(QEvent *event) override; 60 | 61 | template 62 | requires InputParameterSetting 63 | void 64 | writeParameterToSettings(T& settingsParameter, 65 | const U& newValue, 66 | BasicSettings& inputSettings) const 67 | { 68 | settingsParameter = newValue; 69 | inputSettings.write(); 70 | } 71 | 72 | protected: 73 | QPointer m_lowDiskSpaceWidget; 74 | 75 | QPointer m_headerLabel; 76 | QPointer m_headerPixmapLabel; 77 | // All input widgets need some sort of source file, provide it here 78 | QPointer m_sourceLineEdit; 79 | QPointer m_findSourceButton; 80 | // Also need an ok and cancel button 81 | QPointer m_backButton; 82 | QPointer m_okButton; 83 | 84 | QPointer m_findSourceLayout; 85 | QPointer m_buttonLayout; 86 | QPointer m_dialogButtonBox; 87 | 88 | QString m_iconPath; 89 | // Need to store this so we can possibly show it in the messagebox 90 | float m_remainingSpace; 91 | }; 92 | -------------------------------------------------------------------------------- /src/ui/StartWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Parameters.hpp" 4 | #include "UtilsUI.hpp" 5 | 6 | #include 7 | #include 8 | 9 | class QLabel; 10 | class QPushButton; 11 | class QToolButton; 12 | class QVBoxLayout; 13 | 14 | // The starting widget showing all available ui tools 15 | class StartWidget : public QWidget 16 | { 17 | Q_OBJECT 18 | public: 19 | explicit 20 | StartWidget(Parameters::DialogParameters& dialogParameters, 21 | QWidget* parent = 0); 22 | 23 | signals: 24 | void 25 | toolRequested(Utils::UI::TOOL_ID id); 26 | 27 | private slots: 28 | void 29 | openSettingsDialog(); 30 | 31 | private: 32 | void 33 | replaceWidgets(QWidget* fromWidget, 34 | QWidget* toWidget, 35 | int widgetIdentifier, 36 | bool otherItemVisibility); 37 | 38 | QPointer 39 | createToolButton(const QString& buttonText, 40 | const QString& tooltipText = "") const; 41 | 42 | void 43 | setButtonIcons(); 44 | 45 | bool 46 | event(QEvent *event) override; 47 | 48 | private: 49 | QPointer m_headerLabel; 50 | 51 | // Buttons for tools 52 | QPointer m_conversionToolsButton; 53 | QPointer m_bagToolsButton; 54 | QPointer m_publishingToolsButton; 55 | QPointer m_infoToolsButton; 56 | 57 | QPointer m_bagToVideoPushButton; 58 | QPointer m_videoToBagPushButton; 59 | QPointer m_bagToPCDsPushButton; 60 | QPointer m_PCDsToBagPushButton; 61 | QPointer m_bagToImagesPushButton; 62 | QPointer m_tf2ToFilePushButton; 63 | 64 | QPointer m_editBagButton; 65 | QPointer m_mergeBagsButton; 66 | QPointer m_recordBagButton; 67 | QPointer m_dummyBagButton; 68 | QPointer m_compressBagButton; 69 | QPointer m_decompressBagButton; 70 | 71 | QPointer m_publishVideoButton; 72 | QPointer m_publishImagesButton; 73 | QPointer m_sendTF2Button; 74 | 75 | QPointer m_topicServiceInfoButton; 76 | QPointer m_bagInfoButton; 77 | 78 | // Widgets for other elements 79 | QPointer m_settingsButton; 80 | QPointer m_backButton; 81 | QPointer m_versionLabel; 82 | 83 | QPointer m_mainLayout; 84 | 85 | Parameters::DialogParameters& m_dialogParameters; 86 | 87 | // Used to remember which widget was active when we switch to the input widget, but cancel 88 | inline static int m_widgetOnInstantiation = 0; 89 | 90 | static constexpr int WIDGET_OVERALL = 0; 91 | static constexpr int WIDGET_CONVERSION = 1; 92 | static constexpr int WIDGET_BAG = 2; 93 | static constexpr int WIDGET_PUBLISHING = 3; 94 | static constexpr int WIDGET_INFO = 4; 95 | }; 96 | -------------------------------------------------------------------------------- /src/ros/thread/VideoToBagThread.cpp: -------------------------------------------------------------------------------- 1 | #include "VideoToBagThread.hpp" 2 | 3 | #include 4 | 5 | #include "rclcpp/rclcpp.hpp" 6 | #include "rosbag2_cpp/writer.hpp" 7 | #include "sensor_msgs/msg/image.hpp" 8 | 9 | #include 10 | 11 | #ifdef ROS_HUMBLE 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | VideoToBagThread::VideoToBagThread(const Parameters::VideoToBagParameters& parameters, bool useHardwareAcceleration, 18 | QObject* parent) : 19 | BasicThread(parameters.sourceDirectory, parameters.topicName, parent), 20 | m_parameters(parameters), m_useHardwareAcceleration(useHardwareAcceleration) 21 | { 22 | } 23 | 24 | 25 | void 26 | VideoToBagThread::run() 27 | { 28 | auto videoCapture = cv::VideoCapture(m_sourceDirectory, cv::CAP_ANY, { 29 | cv::CAP_PROP_HW_ACCELERATION, m_useHardwareAcceleration ? cv::VIDEO_ACCELERATION_ANY : cv::VIDEO_ACCELERATION_NONE 30 | }); 31 | if (!videoCapture.isOpened()) { 32 | emit failed(); 33 | return; 34 | } 35 | 36 | const auto targetDirectoryStd = m_parameters.targetDirectory.toStdString(); 37 | if (std::filesystem::exists(targetDirectoryStd)) { 38 | std::filesystem::remove_all(targetDirectoryStd); 39 | } 40 | 41 | const auto frameCount = videoCapture.get(cv::CAP_PROP_FRAME_COUNT); 42 | const auto finalFPS = m_parameters.useCustomFPS ? m_parameters.fps : videoCapture.get(cv::CAP_PROP_FPS); 43 | auto timeStamp = rclcpp::Clock(RCL_ROS_TIME).now(); 44 | const auto duration = rclcpp::Duration::from_seconds(1.0f / (float) finalFPS); 45 | 46 | auto writer = std::make_unique(); 47 | writer->open(targetDirectoryStd); 48 | auto iterationCount = 0; 49 | 50 | cv::Mat frame; 51 | std_msgs::msg::Header header; 52 | sensor_msgs::msg::Image message; 53 | 54 | cv_bridge::CvImage cvBridge; 55 | cvBridge.header = header; 56 | cvBridge.encoding = sensor_msgs::image_encodings::BGR8; 57 | 58 | while (true) { 59 | if (isInterruptionRequested()) { 60 | writer->close(); 61 | return; 62 | } 63 | 64 | // Capture image 65 | videoCapture >> frame; 66 | if (frame.empty()) { 67 | break; 68 | } 69 | 70 | iterationCount++; 71 | if (m_parameters.exchangeRedBlueValues) { 72 | cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); 73 | } 74 | 75 | // Create empty sensor message 76 | timeStamp += duration; 77 | header.stamp = timeStamp; 78 | 79 | // Convert and write image 80 | cvBridge.image = frame; 81 | cvBridge.toImageMsg(message); 82 | writer->write(message, m_topicName, timeStamp); 83 | 84 | emit progressChanged("Writing message " + QString::number(iterationCount) + " of " + QString::number(frameCount) + "...", 85 | ((float) iterationCount / (float) frameCount) * 100); 86 | } 87 | 88 | writer->close(); 89 | emit finished(); 90 | } 91 | -------------------------------------------------------------------------------- /src/ros/thread/PublishVideoThread.cpp: -------------------------------------------------------------------------------- 1 | #include "PublishVideoThread.hpp" 2 | 3 | #include 4 | 5 | #ifdef ROS_HUMBLE 6 | #include 7 | #else 8 | #include 9 | #endif 10 | 11 | PublishVideoThread::PublishVideoThread(const Parameters::PublishParameters& parameters, bool useHardwareAcceleration, 12 | QObject* parent) : 13 | BasicThread(parameters.sourceDirectory, parameters.topicName, parent), 14 | m_parameters(parameters), m_useHardwareAcceleration(useHardwareAcceleration) 15 | { 16 | m_node = std::make_shared("publish_video"); 17 | m_publisher = m_node->create_publisher(m_topicName, 10); 18 | } 19 | 20 | 21 | void 22 | PublishVideoThread::run() 23 | { 24 | auto videoCapture = cv::VideoCapture(m_sourceDirectory, cv::CAP_ANY, { 25 | cv::CAP_PROP_HW_ACCELERATION, m_useHardwareAcceleration ? cv::VIDEO_ACCELERATION_ANY : cv::VIDEO_ACCELERATION_NONE 26 | }); 27 | if (!videoCapture.isOpened()) { 28 | emit failed(); 29 | return; 30 | } 31 | 32 | cv::Mat frame; 33 | sensor_msgs::msg::Image message; 34 | 35 | std_msgs::msg::Header header; 36 | cv_bridge::CvImage cvBridge; 37 | cvBridge.header = header; 38 | cvBridge.encoding = sensor_msgs::image_encodings::BGR8; 39 | 40 | const int rate = ((1000 / (float) videoCapture.get(cv::CAP_PROP_FPS)) * 1000); 41 | auto iterator = 0; 42 | const auto frameCount = videoCapture.get(cv::CAP_PROP_FRAME_COUNT); 43 | 44 | auto timer = m_node->create_wall_timer(std::chrono::microseconds(rate), 45 | [this, &iterator, &videoCapture, &frame, &message, &cvBridge, frameCount] { 46 | if (isInterruptionRequested()) { 47 | return; 48 | } 49 | 50 | // Loop if set 51 | if (iterator == frameCount && m_parameters.loop) { 52 | videoCapture.set(cv::CAP_PROP_POS_FRAMES, 0); 53 | iterator = 0; 54 | } 55 | 56 | // Capture image 57 | videoCapture >> frame; 58 | if (frame.empty()) { 59 | return; 60 | } 61 | 62 | if (m_parameters.exchangeRedBlueValues) { 63 | cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); 64 | } 65 | if (m_parameters.scale) { 66 | cv::resize(frame, frame, cv::Size(m_parameters.width, m_parameters.height), 0, 0); 67 | } 68 | 69 | // Convert and write image 70 | cvBridge.image = frame; 71 | cvBridge.toImageMsg(message); 72 | 73 | m_publisher->publish(message); 74 | 75 | emit progressChanged("Publishing image " + QString::number(iterator + 1) + " of " + QString::number(frameCount) + "...", PROGRESS); 76 | iterator++; 77 | }); 78 | 79 | auto executor = std::make_shared(); 80 | executor->add_node(m_node); 81 | 82 | while (!isInterruptionRequested()) { 83 | executor->spin_once(); 84 | } 85 | 86 | timer->cancel(); 87 | emit finished(); 88 | } 89 | -------------------------------------------------------------------------------- /src/ros/thread/TF2ToFileThread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicThread.hpp" 4 | #include "Parameters.hpp" 5 | 6 | #include 7 | 8 | #include "tf2_msgs/msg/tf_message.hpp" 9 | 10 | #include "yaml-cpp/yaml.h" 11 | 12 | template 13 | concept NodeParameter = std::same_as || std::same_as; 14 | 15 | // Used to send nonstatic transformations at a custom rate 16 | class TF2ToFileThread : public BasicThread { 17 | Q_OBJECT 18 | public: 19 | explicit 20 | TF2ToFileThread(const Parameters::TF2ToFileParameters& parameters, 21 | QObject* parent = nullptr); 22 | 23 | void 24 | run() override; 25 | 26 | private: 27 | // Both YAML node and QJsonObject are very similar in terms of array handling, so 28 | // templatize to avoid messy boilerplate code for json and yaml handling 29 | template 30 | requires NodeParameter 31 | void 32 | fillNode(std::shared_ptr tfMessage, 33 | T& finalNode) const 34 | { 35 | for (size_t i = 0; i < tfMessage->transforms.size(); i++) { 36 | const auto& currentTransform = tfMessage->transforms.at(i); 37 | T iterationNode; 38 | 39 | T translation; 40 | translation["x"] = currentTransform.transform.translation.x; 41 | translation["y"] = currentTransform.transform.translation.y; 42 | translation["z"] = currentTransform.transform.translation.z; 43 | 44 | T rotation; 45 | rotation["x"] = currentTransform.transform.rotation.x; 46 | rotation["y"] = currentTransform.transform.rotation.y; 47 | rotation["z"] = currentTransform.transform.rotation.z; 48 | rotation["w"] = currentTransform.transform.rotation.w; 49 | 50 | iterationNode["translation"] = translation; 51 | iterationNode["rotation"] = rotation; 52 | 53 | if constexpr (std::is_same_v) { 54 | iterationNode["header_frame_id"] = QString::fromStdString(currentTransform.header.frame_id); 55 | iterationNode["child_frame_id"] = QString::fromStdString(currentTransform.child_frame_id); 56 | if (m_parameters.keepTimestamps) { 57 | iterationNode["header_stamp"] = QString::number(currentTransform.header.stamp.nanosec / 1e9, 'f', 6); 58 | } 59 | 60 | finalNode["transform_" + QString::number(i)] = iterationNode; 61 | } else { 62 | iterationNode["header_frame_id"] = currentTransform.header.frame_id; 63 | iterationNode["child_frame_id"] = currentTransform.child_frame_id; 64 | if (m_parameters.keepTimestamps) { 65 | iterationNode["header_stamp"] = currentTransform.header.stamp.nanosec / 1e9; 66 | } 67 | 68 | finalNode["transform_" + std::to_string(i)] = iterationNode; 69 | } 70 | } 71 | } 72 | 73 | private: 74 | const Parameters::TF2ToFileParameters& m_parameters; 75 | }; 76 | -------------------------------------------------------------------------------- /resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | gifs/compressing_black.gif 4 | gifs/compressing_white.gif 5 | gifs/decompressing_black.gif 6 | gifs/decompressing_white.gif 7 | gifs/merging_black.gif 8 | gifs/merging_white.gif 9 | gifs/publishing_black.gif 10 | gifs/publishing_white.gif 11 | gifs/recording_black.gif 12 | gifs/recording_white.gif 13 | gifs/sending_tf2_black.gif 14 | gifs/sending_tf2_white.gif 15 | 16 | icons/bag_info_black.svg 17 | icons/bag_info_white.svg 18 | icons/bag_to_pcd_black.svg 19 | icons/bag_to_pcd_white.svg 20 | icons/bag_to_images_black.svg 21 | icons/bag_to_images_white.svg 22 | icons/bag_to_video_black.svg 23 | icons/bag_to_video_white.svg 24 | icons/bag_tools_black.svg 25 | icons/bag_tools_white.svg 26 | icons/compress_bag_black.svg 27 | icons/compress_bag_white.svg 28 | icons/conversion_tools_black.svg 29 | icons/conversion_tools_white.svg 30 | icons/decompress_bag_black.svg 31 | icons/decompress_bag_white.svg 32 | icons/dummy_bag_black.svg 33 | icons/dummy_bag_white.svg 34 | icons/edit_bag_black.svg 35 | icons/edit_bag_white.svg 36 | icons/gear_black.svg 37 | icons/gear_white.svg 38 | icons/info_tools_black.svg 39 | icons/info_tools_white.svg 40 | icons/main.svg 41 | icons/merge_bags_black.svg 42 | icons/merge_bags_white.svg 43 | icons/minus_black.svg 44 | icons/minus_white.svg 45 | icons/pcd_to_bag_black.svg 46 | icons/pcd_to_bag_white.svg 47 | icons/plus_black.svg 48 | icons/plus_white.svg 49 | icons/publishing_tools_black.svg 50 | icons/publishing_tools_white.svg 51 | icons/publish_images_black.svg 52 | icons/publish_images_white.svg 53 | icons/publish_video_black.svg 54 | icons/publish_video_white.svg 55 | icons/record_bag_black.svg 56 | icons/record_bag_white.svg 57 | icons/send_tf2_black.svg 58 | icons/send_tf2_white.svg 59 | icons/topics_services_info_black.svg 60 | icons/topics_services_info_white.svg 61 | icons/tf2_to_file_black.svg 62 | icons/tf2_to_file_white.svg 63 | icons/video_to_bag_black.svg 64 | icons/video_to_bag_white.svg 65 | icons/warning.svg 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/utils/UtilsROS.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NodeWrapper.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include "rclcpp/rclcpp.hpp" 9 | #include "rosbag2_cpp/writer.hpp" 10 | #include "rosbag2_storage/bag_metadata.hpp" 11 | 12 | #include "std_msgs/msg/int32.hpp" 13 | #include "std_msgs/msg/string.hpp" 14 | 15 | #include 16 | 17 | template 18 | concept WriteMessageParameter = (std::same_as && std::same_as) || 19 | (std::same_as && std::same_as); 20 | 21 | // ROS related util functions 22 | namespace Utils::ROS 23 | { 24 | // Sends a static transformation using tf broadcaster 25 | // @NOTE: For whatever reason, just creating and spinning a local node 26 | // does not work here, we have to spin a global node 27 | void 28 | sendStaticTransformation(const std::array& translation, 29 | const std::array& rotation, 30 | std::shared_ptr nodeWrapper); 31 | 32 | // If a directory contains a valid ROS bag 33 | [[nodiscard]] bool 34 | doesDirectoryContainBagFile(const QString& bagDirectory); 35 | 36 | // If a directory contains a valid compressed ROS bag 37 | [[nodiscard]] bool 38 | doesDirectoryContainCompressedBagFile(const QString& bagDirectory); 39 | 40 | // Show all current topic names and types 41 | std::vector > > 42 | getTopicInformation(); 43 | 44 | // Show all current service names and types 45 | std::map > 46 | getServiceNamesAndTypes(); 47 | 48 | // Returns the metadata stored for a ROS bag 49 | [[nodiscard]] rosbag2_storage::BagMetadata 50 | getBagMetadata(const QString& bagDirectory); 51 | 52 | // Returns a topic in a bag file, if existent 53 | std::optional 54 | getTopicInBag(const QString& bagDirectory, 55 | const QString& topicName); 56 | 57 | // If a ROS bag contains a certain topic 58 | [[nodiscard]] bool 59 | doesBagContainTopicName(const QString& bagDirectory, 60 | const QString& topicName); 61 | 62 | // Message count for a ROS bag topic 63 | [[nodiscard]] std::optional 64 | getTopicMessageCount(const QString& bagDirectory, 65 | const QString& topicName); 66 | 67 | [[nodiscard]] std::optional 68 | getTopicMessageCount(const std::string& bagDirectory, 69 | const std::string& topicName); 70 | 71 | // Get a ROS bag topic's type 72 | [[nodiscard]] std::optional 73 | getTopicType(const QString& bagDirectory, 74 | const QString& topicName); 75 | 76 | // Returns the first topic in a bag file with a certain type 77 | [[nodiscard]] std::optional 78 | getFirstTopicWithCertainType(const QString& bagDirectory, 79 | const QString& typeName); 80 | 81 | // Returns all video bag topics stored in a ROS bag file 82 | [[nodiscard]] QVector 83 | getBagTopics(const QString& bagDirectory, 84 | const QString& topicType); 85 | 86 | // Returns if a topic name follows the ROS2 naming convention 87 | [[nodiscard]] bool 88 | isNameROS2Conform(const QString& topicName); 89 | } 90 | -------------------------------------------------------------------------------- /src/ui/HelperWidgets/TopicWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "TopicWidget.hpp" 2 | 3 | #include "UtilsROS.hpp" 4 | #include "UtilsUI.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | TopicWidget::TopicWidget(bool isDummyWidget, bool addRemoveButton, const QString& topicTypeText, 14 | const QString& topicNameText, QWidget *parent) : 15 | QWidget(parent) 16 | { 17 | m_topicNameLineEdit = new QLineEdit(topicNameText); 18 | m_topicNameLineEdit->setPlaceholderText("Enter Topic Name..."); 19 | 20 | m_removeTopicButton = new QToolButton; 21 | m_removeTopicButton->setToolTip("Remove the topic on the left."); 22 | auto sizePolicy = m_removeTopicButton->sizePolicy(); 23 | sizePolicy.setRetainSizeWhenHidden(true); 24 | m_removeTopicButton->setSizePolicy(sizePolicy); 25 | 26 | auto* const mainLayout = new QHBoxLayout; 27 | if (isDummyWidget) { 28 | auto* const topicTypeComboBox = new QComboBox; 29 | topicTypeComboBox->addItem("String", 0); 30 | topicTypeComboBox->addItem("Integer", 1); 31 | topicTypeComboBox->addItem("Image", 2); 32 | topicTypeComboBox->addItem("Point Cloud", 3); 33 | topicTypeComboBox->addItem("TF2", 4); 34 | if (!topicTypeText.isEmpty()) { 35 | topicTypeComboBox->setCurrentText(topicTypeText); 36 | } 37 | 38 | mainLayout->addWidget(topicTypeComboBox); 39 | 40 | connect(topicTypeComboBox, &QComboBox::currentTextChanged, this, [this] (const QString& text) { 41 | emit topicTypeChanged(text); 42 | }); 43 | } else { 44 | const auto& currentTopicsAndTypes = Utils::ROS::getTopicInformation(); 45 | QStringList wordList; 46 | for (const auto& currentTopic : currentTopicsAndTypes) { 47 | wordList.append(QString::fromStdString(currentTopic.first)); 48 | } 49 | 50 | auto* const completer = new QCompleter(wordList); 51 | completer->setCaseSensitivity(Qt::CaseInsensitive); 52 | m_topicNameLineEdit->setCompleter(completer); 53 | } 54 | 55 | mainLayout->addWidget(m_topicNameLineEdit); 56 | mainLayout->addWidget(m_removeTopicButton); 57 | m_removeTopicButton->setVisible(addRemoveButton); 58 | 59 | setLayout(mainLayout); 60 | // This widget will be integrated into other widgets where extra space would look bad 61 | // So remove all space around the widget itself 62 | mainLayout->setSpacing(0); 63 | mainLayout->setContentsMargins(0, 0, 0, 0); 64 | 65 | connect(m_removeTopicButton, &QPushButton::clicked, this, [this] { 66 | emit topicRemoveButtonClicked(); 67 | }); 68 | connect(m_topicNameLineEdit, &QLineEdit::textChanged, this, [this] (const QString& text) { 69 | emit topicNameChanged(text); 70 | }); 71 | 72 | setPixmapLabelIcon(); 73 | } 74 | 75 | 76 | void 77 | TopicWidget::setPixmapLabelIcon() const 78 | { 79 | if (!m_removeTopicButton) { 80 | return; 81 | } 82 | 83 | const auto isDarkMode = Utils::UI::isDarkMode(); 84 | m_removeTopicButton->setIcon(QIcon(isDarkMode ? ":/icons/minus_white.svg" : ":/icons/minus_black.svg")); 85 | } 86 | 87 | 88 | bool 89 | TopicWidget::event(QEvent *event) 90 | { 91 | [[unlikely]] if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::PaletteChange) { 92 | setPixmapLabelIcon(); 93 | } 94 | return QWidget::event(event); 95 | } 96 | -------------------------------------------------------------------------------- /src/ros/thread/PublishImagesThread.cpp: -------------------------------------------------------------------------------- 1 | #include "PublishImagesThread.hpp" 2 | 3 | #include 4 | 5 | #ifdef ROS_HUMBLE 6 | #include 7 | #else 8 | #include 9 | #endif 10 | 11 | #include 12 | 13 | PublishImagesThread::PublishImagesThread(const Parameters::PublishParameters& parameters, 14 | QObject* parent) : 15 | BasicThread(parameters.sourceDirectory, parameters.topicName, parent), 16 | m_parameters(parameters) 17 | { 18 | m_node = std::make_shared("publish_images"); 19 | m_publisher = m_node->create_publisher(m_topicName, 10); 20 | } 21 | 22 | 23 | void 24 | PublishImagesThread::run() 25 | { 26 | std::set sortedImagesSet; 27 | auto frameCount = 0; 28 | 29 | emit informOfGatheringData(); 30 | // It is faster to first store all valid image file paths and then iterate over those 31 | for (auto const& entry : std::filesystem::directory_iterator(m_sourceDirectory)) { 32 | if (entry.path().extension() != ".jpg" && entry.path().extension() != ".png" && entry.path().extension() != ".bmp") { 33 | continue; 34 | } 35 | 36 | sortedImagesSet.insert(entry.path()); 37 | frameCount++; 38 | } 39 | auto setIterator = sortedImagesSet.begin(); 40 | 41 | cv::Mat frame; 42 | sensor_msgs::msg::Image message; 43 | 44 | cv_bridge::CvImage cvBridge; 45 | cvBridge.encoding = sensor_msgs::image_encodings::BGR8; 46 | 47 | const int rate = ((1000 / (float) m_parameters.fps) * 1000); 48 | auto iterator = 0; 49 | auto timer = m_node->create_wall_timer(std::chrono::microseconds(rate), 50 | [this, &iterator, &setIterator, &frame, &cvBridge, &message, sortedImagesSet, frameCount] { 51 | if (isInterruptionRequested()) { 52 | return; 53 | } 54 | 55 | // Loop if set 56 | if (iterator == frameCount) { 57 | if (m_parameters.loop) { 58 | setIterator = sortedImagesSet.begin(); 59 | iterator = 0; 60 | } else { 61 | return; 62 | } 63 | } 64 | 65 | // Read image from file 66 | frame = cv::imread(*setIterator, cv::IMREAD_COLOR); 67 | if (frame.empty()) { 68 | iterator++; 69 | setIterator++; 70 | return; 71 | } 72 | 73 | if (m_parameters.exchangeRedBlueValues) { 74 | cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); 75 | } 76 | if (m_parameters.scale) { 77 | cv::resize(frame, frame, cv::Size(m_parameters.width, m_parameters.height), 0, 0); 78 | } 79 | 80 | // Convert and write image 81 | cvBridge.image = frame; 82 | cvBridge.toImageMsg(message); 83 | 84 | m_publisher->publish(message); 85 | 86 | emit progressChanged("Publishing image " + QString::number(iterator + 1) + " of " + QString::number(frameCount) + "...", PROGRESS); 87 | iterator++; 88 | setIterator++; 89 | }); 90 | 91 | auto executor = std::make_shared(); 92 | executor->add_node(m_node); 93 | 94 | while (!isInterruptionRequested()) { 95 | executor->spin_once(); 96 | } 97 | 98 | timer->cancel(); 99 | emit finished(); 100 | } 101 | -------------------------------------------------------------------------------- /src/ros/thread/TF2ToFileThread.cpp: -------------------------------------------------------------------------------- 1 | #include "TF2ToFileThread.hpp" 2 | 3 | #include "UtilsGeneral.hpp" 4 | #include "UtilsROS.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "geometry_msgs/msg/transform_stamped.hpp" 11 | #include "rosbag2_cpp/reader.hpp" 12 | 13 | #include 14 | #include 15 | 16 | TF2ToFileThread::TF2ToFileThread(const Parameters::TF2ToFileParameters& parameters, QObject* parent) : 17 | BasicThread(parameters.sourceDirectory, parameters.topicName, parent), m_parameters(parameters) 18 | { 19 | } 20 | 21 | 22 | void 23 | TF2ToFileThread::run() 24 | { 25 | const auto& targetDirectory = m_parameters.targetDirectory; 26 | if (std::filesystem::exists(targetDirectory.toStdString())) { 27 | std::filesystem::remove_all(targetDirectory.toStdString()); 28 | } 29 | 30 | const auto isFormatJson = Utils::General::getFileExtension(targetDirectory) == "json"; 31 | const auto messageCount = Utils::ROS::getTopicMessageCount(m_sourceDirectory, m_topicName); 32 | auto iterationCount = 0; 33 | 34 | auto reader = std::make_shared(); 35 | reader->open(m_sourceDirectory); 36 | 37 | rclcpp::Serialization serializiation; 38 | auto tfMessage = std::make_shared(); 39 | 40 | QJsonArray messagesArray; 41 | YAML::Node transformsNode; 42 | 43 | const auto writeMessageToJson = [this, tfMessage, &messagesArray, &iterationCount] { 44 | QJsonObject transformsObject; 45 | fillNode(tfMessage, transformsObject); 46 | 47 | QJsonObject messageObject; 48 | messageObject["message_" + QString::number(iterationCount)] = transformsObject; 49 | messagesArray.append(messageObject); 50 | }; 51 | 52 | const auto writeMessageToYaml = [this, tfMessage, &transformsNode, &iterationCount] { 53 | YAML::Node transformNode; 54 | fillNode(tfMessage, transformNode); 55 | 56 | transformsNode["message_" + std::to_string(iterationCount)] = transformNode; 57 | }; 58 | 59 | while (reader->has_next()) { 60 | if (isInterruptionRequested()) { 61 | break; 62 | } 63 | 64 | auto bagMessage = reader->read_next(); 65 | if (bagMessage->topic_name != m_topicName) { 66 | continue; 67 | } 68 | 69 | rclcpp::SerializedMessage serializedMsg(*bagMessage->serialized_data); 70 | serializiation.deserialize_message(&serializedMsg, tfMessage.get()); 71 | 72 | isFormatJson ? writeMessageToJson() : writeMessageToYaml(); 73 | 74 | iterationCount++; 75 | emit progressChanged("Writing message " + QString::number(iterationCount) + " of " + QString::number(*messageCount) + "...", 76 | ((float) iterationCount / (float) *messageCount) * 100); 77 | } 78 | 79 | if (isFormatJson) { 80 | QJsonDocument doc(messagesArray); 81 | QFile outFile(targetDirectory); 82 | 83 | if (!outFile.open(QIODevice::WriteOnly)) { 84 | emit failed(); 85 | } 86 | 87 | outFile.write(doc.toJson(m_parameters.compactOutput ? QJsonDocument::Compact : QJsonDocument::Indented)); 88 | outFile.close(); 89 | } else { 90 | std::ofstream fout(targetDirectory.toStdString()); 91 | 92 | try { 93 | fout << transformsNode; 94 | } catch (std::ofstream::failure& /* exeption */) { 95 | emit failed(); 96 | } 97 | } 98 | 99 | emit finished(); 100 | } 101 | --------------------------------------------------------------------------------