From 6503eb0c532a7146d14dbd5c8eb09c2db5f91e74 Mon Sep 17 00:00:00 2001 From: Mabel Zhang Date: Thu, 13 Aug 2020 17:19:27 -0400 Subject: [PATCH 1/2] rosbag2_py reader and writer (#308) * Initialize new package rosbag2_py Signed-off-by: Jacob Perron (cherry picked from commit f2ec0b7f3780b51c4b34c24ffc215344eea80d36) * Proof-of-concept implementation using pybind11 Expose the sequential reader to iterate over messages from Python. Signed-off-by: Jacob Perron (cherry picked from commit dc6889481c31f7a62778182101a965e22c5464f0) * pybind StorageOptions and ConverterOptions; linter fixes Signed-off-by: Mabel Zhang (cherry picked from commit 91f67634a6fca7114af9d962a466bbdc5c44b21c) * return timestamp Signed-off-by: Mabel Zhang (cherry picked from commit deafbf8984d9dfa74457a30a080daf69b29226f5) * simplify binding of structs Signed-off-by: Mabel Zhang (cherry picked from commit da561ccf2215bc90c37058892943e014e77c3ea9) * pybind TopicMetadata Signed-off-by: Mabel Zhang (cherry picked from commit 4a4e1b83188300c2ebf2cc7d93019d3fd4ad3cee) * namespace rosbag2 -> rosbag2_cpp Signed-off-by: Mabel Zhang * small fixes Signed-off-by: Mabel Zhang * initial pytests Signed-off-by: Mabel Zhang * wrap Reader instead of SequentialReader Signed-off-by: Mabel Zhang * small fixes for PR comments Signed-off-by: Mabel Zhang * change workflow to autotest feature branch Signed-off-by: Mabel Zhang * revert workflow Signed-off-by: Mabel Zhang * added writer and writer tests Signed-off-by: Mabel Zhang * fix test path setup Signed-off-by: Mabel Zhang * fix and make more rigorous writer test Signed-off-by: Mabel Zhang * wrapper and test for topic filter Signed-off-by: Mabel Zhang * linters. change pybind package Signed-off-by: Mabel Zhang * simplify verbose import Signed-off-by: Mabel Zhang * tidy up Signed-off-by: Mabel Zhang * explicit subclasses of Reader and Writer Signed-off-by: Mabel Zhang * Use template specialization instead of inheritence This makes the implementation slightly more compact. And adding a new specialization should be more straight forward as we only have to modify the cpp file in one place. Signed-off-by: Jacob Perron * Remove temporary variable Signed-off-by: Jacob Perron * Remove unused member variable Signed-off-by: Jacob Perron * Move to initializer list Signed-off-by: Jacob Perron * refactor into reader writer storage Signed-off-by: Mabel Zhang * cleanup includes Signed-off-by: Mabel Zhang * switch back to pybind11_vendor Signed-off-by: Mabel Zhang * Packages dependency for CI Signed-off-by: Mabel Zhang * printout to debug CI test failure Signed-off-by: Mabel Zhang * use tempfile for writer test, add exec_depend Signed-off-by: Mabel Zhang * remove exec_depend Signed-off-by: Mabel Zhang * use pytest fixture tmp_path; add rosbag2_py to rosbag2 Signed-off-by: Mabel Zhang * add makedirs back to see CI result Signed-off-by: Mabel Zhang * fix import error Signed-off-by: Mabel Zhang * cleanup Signed-off-by: Mabel Zhang * add modules explicitly for windows CI Signed-off-by: Mabel Zhang * cleanup Signed-off-by: Mabel Zhang * try more s for mac test failure Signed-off-by: Mabel Zhang * wrap structs with named arguments for Python keyword params Signed-off-by: Mabel Zhang * add pytest.ini Signed-off-by: Mabel Zhang * add rosbag db3-shm and db3-wal files for macOS test Signed-off-by: Mabel Zhang Co-authored-by: Jacob Perron Co-authored-by: Andreas Klintberg Signed-off-by: Alexander Gutenkunst --- rosbag2/package.xml | 1 + rosbag2_py/CMakeLists.txt | 88 +++++++++++++++ rosbag2_py/package.xml | 38 +++++++ rosbag2_py/pytest.ini | 2 + rosbag2_py/resources/talker/metadata.yaml | 31 ++++++ rosbag2_py/resources/talker/talker.db3 | Bin 0 -> 16384 bytes rosbag2_py/resources/talker/talker.db3-shm | Bin 0 -> 32768 bytes rosbag2_py/resources/talker/talker.db3-wal | 0 rosbag2_py/rosbag2_py/__init__.py | 42 ++++++++ rosbag2_py/src/rosbag2_py/_reader.cpp | 120 +++++++++++++++++++++ rosbag2_py/src/rosbag2_py/_storage.cpp | 83 ++++++++++++++ rosbag2_py/src/rosbag2_py/_writer.cpp | 103 ++++++++++++++++++ rosbag2_py/test/common.py | 25 +++++ rosbag2_py/test/test_sequential_reader.py | 70 ++++++++++++ rosbag2_py/test/test_sequential_writer.py | 85 +++++++++++++++ 15 files changed, 688 insertions(+) create mode 100644 rosbag2_py/CMakeLists.txt create mode 100644 rosbag2_py/package.xml create mode 100644 rosbag2_py/pytest.ini create mode 100644 rosbag2_py/resources/talker/metadata.yaml create mode 100644 rosbag2_py/resources/talker/talker.db3 create mode 100644 rosbag2_py/resources/talker/talker.db3-shm create mode 100644 rosbag2_py/resources/talker/talker.db3-wal create mode 100644 rosbag2_py/rosbag2_py/__init__.py create mode 100644 rosbag2_py/src/rosbag2_py/_reader.cpp create mode 100644 rosbag2_py/src/rosbag2_py/_storage.cpp create mode 100644 rosbag2_py/src/rosbag2_py/_writer.cpp create mode 100644 rosbag2_py/test/common.py create mode 100644 rosbag2_py/test/test_sequential_reader.py create mode 100644 rosbag2_py/test/test_sequential_writer.py diff --git a/rosbag2/package.xml b/rosbag2/package.xml index 6cdf1be2a2..29c2ca6228 100644 --- a/rosbag2/package.xml +++ b/rosbag2/package.xml @@ -14,6 +14,7 @@ rosbag2_compression rosbag2_converter_default_plugins rosbag2_cpp + rosbag2_py rosbag2_storage rosbag2_storage_default_plugins rosbag2_transport diff --git a/rosbag2_py/CMakeLists.txt b/rosbag2_py/CMakeLists.txt new file mode 100644 index 0000000000..98b7fd6385 --- /dev/null +++ b/rosbag2_py/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.5) +project(rosbag2_py) + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic -Werror) +endif() + +find_package(ament_cmake REQUIRED) +find_package(ament_cmake_ros REQUIRED) +find_package(pybind11 REQUIRED) +find_package(python_cmake_module REQUIRED) +find_package(rosbag2_compression REQUIRED) +find_package(rosbag2_cpp REQUIRED) +find_package(rosbag2_storage REQUIRED) + +ament_python_install_package(${PROJECT_NAME}) + +pybind11_add_module(_reader SHARED + src/rosbag2_py/_reader.cpp +) +ament_target_dependencies(_reader PUBLIC + "rosbag2_compression" + "rosbag2_cpp" +) + +pybind11_add_module(_storage SHARED + src/rosbag2_py/_storage.cpp +) +ament_target_dependencies(_storage PUBLIC + "rosbag2_cpp" + "rosbag2_storage" +) + +pybind11_add_module(_writer SHARED + src/rosbag2_py/_writer.cpp +) +ament_target_dependencies(_writer PUBLIC + "rosbag2_compression" + "rosbag2_cpp" +) + +# Install cython modules as sub-modules of the project +install( + TARGETS + _reader + _storage + _writer + DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}" +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + find_package(ament_cmake_pytest REQUIRED) + + set(_PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}") + if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(_PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE_DEBUG}") + endif() + set(pythonpath "${CMAKE_CURRENT_BINARY_DIR}/rosbag2_py;${CMAKE_CURRENT_SOURCE_DIR}") + if(NOT WIN32) + string(REPLACE ";" ":" pythonpath "${pythonpath}") + endif() + ament_add_pytest_test(test_sequential_reader_py "test/test_sequential_reader.py" + PYTHON_EXECUTABLE "${_PYTHON_EXECUTABLE}" + APPEND_ENV "PYTHONPATH=${pythonpath}" + APPEND_LIBRARY_DIRS "${_append_library_dirs}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/resources" + ) + ament_add_pytest_test(test_sequential_writer_py "test/test_sequential_writer.py" + PYTHON_EXECUTABLE "${_PYTHON_EXECUTABLE}" + APPEND_ENV "PYTHONPATH=${pythonpath}" + APPEND_LIBRARY_DIRS "${_append_library_dirs}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/resources" + ) +endif() + +ament_package() diff --git a/rosbag2_py/package.xml b/rosbag2_py/package.xml new file mode 100644 index 0000000000..3f940523ac --- /dev/null +++ b/rosbag2_py/package.xml @@ -0,0 +1,38 @@ + + + + rosbag2_py + 0.2.4 + Python API for rosbag2 + Jacob Perron + ROS Tooling Working Group + Apache License 2.0 + + Mabel Zhang + + ament_cmake_ros + + pybind11_vendor + python_cmake_module + rosbag2_compression + rosbag2_cpp + rosbag2_storage + + rpyutils + + ament_lint_auto + ament_lint_common + python3-pytest + rcl_interfaces + rclpy + rosbag2_converter_default_plugins + rosbag2_storage_default_plugins + rosbag2_storage + rosidl_runtime_py + rpyutils + std_msgs + + + ament_cmake + + diff --git a/rosbag2_py/pytest.ini b/rosbag2_py/pytest.ini new file mode 100644 index 0000000000..fe55d2ed64 --- /dev/null +++ b/rosbag2_py/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +junit_family=xunit2 diff --git a/rosbag2_py/resources/talker/metadata.yaml b/rosbag2_py/resources/talker/metadata.yaml new file mode 100644 index 0000000000..35db28401c --- /dev/null +++ b/rosbag2_py/resources/talker/metadata.yaml @@ -0,0 +1,31 @@ +rosbag2_bagfile_information: + version: 4 + storage_identifier: sqlite3 + relative_file_paths: + - talker.db3 + duration: + nanoseconds: 4531096768 + starting_time: + nanoseconds_since_epoch: 1585866235112411371 + message_count: 20 + topics_with_message_count: + - topic_metadata: + name: /topic + type: std_msgs/msg/String + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 2147483647\n nsec: 4294967295\n lifespan:\n sec: 2147483647\n nsec: 4294967295\n liveliness: 1\n liveliness_lease_duration:\n sec: 2147483647\n nsec: 4294967295\n avoid_ros_namespace_conventions: false" + message_count: 10 + - topic_metadata: + name: /rosout + type: rcl_interfaces/msg/Log + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 2147483647\n nsec: 4294967295\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 2147483647\n nsec: 4294967295\n avoid_ros_namespace_conventions: false" + message_count: 10 + - topic_metadata: + name: /parameter_events + type: rcl_interfaces/msg/ParameterEvent + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 2147483647\n nsec: 4294967295\n lifespan:\n sec: 2147483647\n nsec: 4294967295\n liveliness: 1\n liveliness_lease_duration:\n sec: 2147483647\n nsec: 4294967295\n avoid_ros_namespace_conventions: false" + message_count: 0 + compression_format: "" + compression_mode: "" diff --git a/rosbag2_py/resources/talker/talker.db3 b/rosbag2_py/resources/talker/talker.db3 new file mode 100644 index 0000000000000000000000000000000000000000..3af211094541ccbfce88f9bc4ee33cff589d52f6 GIT binary patch literal 16384 zcmeI2ZD<@t7{~YSlDpg`xlNO+F=%vzk{7sUb1!*qsiy5xORu>!No^vw%jR}2S=qb2 z?rz=$Z4zTYh@c=yOQGTy6$L4ZDB2H#QVCcpf)w(}Pp#sY1cXR6)@SZAyQZFEIXS4# z!rbnE=Goc#{b$1MJeQGU3Dv?Vts5EHLNTt6bGy065aKw_4W$AKJv?ynNA02we{*(o z9S>frg#b_R6vrP8egr3WFb9|e%mL;AbAUO(9AFMG2bcrQ0p`Ge&VdsiZ)0n-YhKMN zc+OHY*tFzKPEwUQxHRQSY*ri?76(Q}ba?QfIEJ=`pyVK;VW`b+LIWcQ+7}z#zQ*Qe z*W$b-PiUBi(X(f(*U^Ff3DNGPO;zAIqv9cP7!3^{J~}Wwj*f`qVN1`cDHxR9IG7wo zgU1tzu=7xQpWTEM*^<%zL~=i(b?u9tE?;9)lWS>DepLB>&BCX9+lUHve>I-0oWW>R z9D_mF1DVg=S!H5Fl{NJl*;4haw6%fXewUt3V*@MFY2B1^hMrb6Y;NzkJ-;`vd6)}c zsZGO)9n1md0CRvjz#L!>Fb9|e%mL=W|JQ+~gB~B(6l+*c3R}AwMar0yrT`zph-IkR z$&_O3MpG*6bBy_Z6bm4v;G8wpk2>Mfz?v#gsG4d)TZFVt8@7(-V`W8Cv$!9w5i)VA zA4Mbao_KGpJKmG8%jPTN(Y|iEmQ5^? zp^?4RUG*x@=&B+au)!j`HW*Y2ODR1&gR`V7cvM=}OuTf`?c;!EcCiD`PYJTbBe`l#i$R7qm zg>_A?ot0Srl}YyhA@2>2f0SR|MbV@=b;XjV5gzQZD$SY#*7VcU@P;SAOJKfIz;p2P2qftRcz;Rda>Ab;C1`SH zLXkV5k*kLO^c;qlR^4c8=eQQoE%gp)?DY@Vi-Yz8=ne-ocK*2)3hFU0c}oE8EgNW^ z1G@8@8;6R6_5djFfbRV2`2`aA8_ekU*Gihvp0a@kZP2x!-HGePLAwF8)&^bM*nGuH zLHCT7G^5>R1FdmDS9>?#Dh}EOpw$lOYUJyeN#G{T=($fyn$fPZfd(AV%TI^CEe;w7 zP`?9uIrYN}6!gSwNi!NR8)%gSdhx;+vYHB#2MpK(a(#X(NaC!@7LXkCQ9&|IURyvmsgeqknyIh_q+dK#kX(kl+T-;D J_8Q4o{0@Nyz1;u+ literal 0 HcmV?d00001 diff --git a/rosbag2_py/resources/talker/talker.db3-shm b/rosbag2_py/resources/talker/talker.db3-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r3 +#include + +#include +#include +#include + +#include "rosbag2_compression/sequential_compression_reader.hpp" +#include "rosbag2_cpp/converter_options.hpp" +#include "rosbag2_cpp/readers/sequential_reader.hpp" +#include "rosbag2_cpp/reader.hpp" +#include "rosbag2_cpp/storage_options.hpp" +#include "rosbag2_storage/storage_filter.hpp" +#include "rosbag2_storage/topic_metadata.hpp" + +namespace rosbag2_py +{ + +template +class Reader +{ +public: + Reader() + : reader_(std::make_unique(std::make_unique())) + { + } + + void open( + rosbag2_cpp::StorageOptions & storage_options, + rosbag2_cpp::ConverterOptions & converter_options) + { + reader_->open(storage_options, converter_options); + } + + bool has_next() + { + return reader_->has_next(); + } + + /// Return a tuple containing the topic name, the serialized ROS message, and + /// the timestamp. + pybind11::tuple read_next() + { + const auto next = reader_->read_next(); + rcutils_uint8_array_t rcutils_data = *next->serialized_data.get(); + std::string serialized_data(rcutils_data.buffer, + rcutils_data.buffer + rcutils_data.buffer_length); + return pybind11::make_tuple( + next->topic_name, pybind11::bytes(serialized_data), next->time_stamp); + } + + /// Return a mapping from topic name to topic type. + std::vector get_all_topics_and_types() + { + return reader_->get_all_topics_and_types(); + } + + void set_filter(const rosbag2_storage::StorageFilter & storage_filter) + { + return reader_->set_filter(storage_filter); + } + + void reset_filter() + { + reader_->reset_filter(); + } + +protected: + std::unique_ptr reader_; +}; +} // namespace rosbag2_py + +PYBIND11_MODULE(_reader, m) { + m.doc() = "Python wrapper of the rosbag2_cpp reader API"; + + pybind11::class_>( + m, "SequentialReader") + .def(pybind11::init()) + .def("open", &rosbag2_py::Reader::open) + .def("read_next", &rosbag2_py::Reader::read_next) + .def("has_next", &rosbag2_py::Reader::has_next) + .def( + "get_all_topics_and_types", + &rosbag2_py::Reader::get_all_topics_and_types) + .def("set_filter", &rosbag2_py::Reader::set_filter) + .def("reset_filter", &rosbag2_py::Reader::reset_filter); + + pybind11::class_>( + m, "SequentialCompressionReader") + .def(pybind11::init()) + .def("open", &rosbag2_py::Reader::open) + .def( + "read_next", &rosbag2_py::Reader::read_next) + .def("has_next", &rosbag2_py::Reader::has_next) + .def( + "get_all_topics_and_types", + &rosbag2_py::Reader< + rosbag2_compression::SequentialCompressionReader + >::get_all_topics_and_types) + .def( + "set_filter", + &rosbag2_py::Reader::set_filter) + .def( + "reset_filter", + &rosbag2_py::Reader::reset_filter); +} diff --git a/rosbag2_py/src/rosbag2_py/_storage.cpp b/rosbag2_py/src/rosbag2_py/_storage.cpp new file mode 100644 index 0000000000..bbd536dc51 --- /dev/null +++ b/rosbag2_py/src/rosbag2_py/_storage.cpp @@ -0,0 +1,83 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include + +#include "rosbag2_cpp/converter_options.hpp" +#include "rosbag2_cpp/storage_options.hpp" +#include "rosbag2_storage/storage_filter.hpp" +#include "rosbag2_storage/topic_metadata.hpp" + +PYBIND11_MODULE(_storage, m) { + m.doc() = "Python wrapper of the rosbag2 utilities API"; + + pybind11::class_(m, "ConverterOptions") + .def( + pybind11::init(), + pybind11::arg("input_serialization_format"), + pybind11::arg("output_serialization_format")) + .def_readwrite( + "input_serialization_format", + &rosbag2_cpp::ConverterOptions::input_serialization_format) + .def_readwrite( + "output_serialization_format", + &rosbag2_cpp::ConverterOptions::output_serialization_format); + + pybind11::class_(m, "StorageOptions") + .def( + pybind11::init(), + pybind11::arg("uri"), + pybind11::arg("storage_id"), + pybind11::arg("max_bagfile_size") = 0, + pybind11::arg("max_bagfile_duration") = 0, + pybind11::arg("max_cache_size") = 0) + .def_readwrite("uri", &rosbag2_cpp::StorageOptions::uri) + .def_readwrite("storage_id", &rosbag2_cpp::StorageOptions::storage_id) + .def_readwrite( + "max_bagfile_size", + &rosbag2_cpp::StorageOptions::max_bagfile_size) + .def_readwrite( + "max_bagfile_duration", + &rosbag2_cpp::StorageOptions::max_bagfile_duration) + .def_readwrite( + "max_cache_size", + &rosbag2_cpp::StorageOptions::max_cache_size); + + pybind11::class_(m, "StorageFilter") + .def( + pybind11::init>(), + pybind11::arg("topics")) + .def_readwrite("topics", &rosbag2_storage::StorageFilter::topics); + + pybind11::class_(m, "TopicMetadata") + .def( + pybind11::init(), + pybind11::arg("name"), + pybind11::arg("type"), + pybind11::arg("serialization_format"), + pybind11::arg("offered_qos_profiles") = "") + .def_readwrite("name", &rosbag2_storage::TopicMetadata::name) + .def_readwrite("type", &rosbag2_storage::TopicMetadata::type) + .def_readwrite( + "serialization_format", + &rosbag2_storage::TopicMetadata::serialization_format) + .def_readwrite( + "offered_qos_profiles", + &rosbag2_storage::TopicMetadata::offered_qos_profiles) + .def("equals", &rosbag2_storage::TopicMetadata::operator==); +} diff --git a/rosbag2_py/src/rosbag2_py/_writer.cpp b/rosbag2_py/src/rosbag2_py/_writer.cpp new file mode 100644 index 0000000000..ecae08a344 --- /dev/null +++ b/rosbag2_py/src/rosbag2_py/_writer.cpp @@ -0,0 +1,103 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include + +#include "rosbag2_compression/sequential_compression_writer.hpp" +#include "rosbag2_cpp/converter_options.hpp" +#include "rosbag2_cpp/storage_options.hpp" +#include "rosbag2_cpp/writer.hpp" +#include "rosbag2_cpp/writers/sequential_writer.hpp" +#include "rosbag2_storage/ros_helper.hpp" +#include "rosbag2_storage/storage_filter.hpp" +#include "rosbag2_storage/topic_metadata.hpp" + +namespace rosbag2_py +{ + +template +class Writer +{ +public: + Writer() + : writer_(std::make_unique(std::make_unique())) + { + } + + void open( + rosbag2_cpp::StorageOptions & storage_options, + rosbag2_cpp::ConverterOptions & converter_options) + { + writer_->open(storage_options, converter_options); + } + + void create_topic(const rosbag2_storage::TopicMetadata & topic_with_type) + { + writer_->create_topic(topic_with_type); + } + + void remove_topic(const rosbag2_storage::TopicMetadata & topic_with_type) + { + writer_->remove_topic(topic_with_type); + } + + /// Write a serialized message to a bag file + void write( + const std::string & topic_name, const std::string & message, + const rcutils_time_point_value_t & time_stamp) + { + auto bag_message = + std::make_shared(); + + bag_message->topic_name = topic_name; + bag_message->serialized_data = + rosbag2_storage::make_serialized_message(message.c_str(), message.length()); + bag_message->time_stamp = time_stamp; + + writer_->write(bag_message); + } + +protected: + std::unique_ptr writer_; +}; + +} // namespace rosbag2_py + +PYBIND11_MODULE(_writer, m) { + m.doc() = "Python wrapper of the rosbag2_cpp writer API"; + + pybind11::class_>( + m, "SequentialWriter") + .def(pybind11::init()) + .def("open", &rosbag2_py::Writer::open) + .def("write", &rosbag2_py::Writer::write) + .def("remove_topic", &rosbag2_py::Writer::remove_topic) + .def("create_topic", &rosbag2_py::Writer::create_topic); + + pybind11::class_>( + m, "SequentialCompressionWriter") + .def(pybind11::init()) + .def("open", &rosbag2_py::Writer::open) + .def("write", &rosbag2_py::Writer::write) + .def( + "remove_topic", + &rosbag2_py::Writer::remove_topic) + .def( + "create_topic", + &rosbag2_py::Writer::create_topic); +} diff --git a/rosbag2_py/test/common.py b/rosbag2_py/test/common.py new file mode 100644 index 0000000000..cec02bea31 --- /dev/null +++ b/rosbag2_py/test/common.py @@ -0,0 +1,25 @@ +# Copyright 2020 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import rosbag2_py + + +def get_rosbag_options(path, serialization_format='cdr'): + storage_options = rosbag2_py.StorageOptions(uri=path, storage_id='sqlite3') + + converter_options = rosbag2_py.ConverterOptions( + input_serialization_format=serialization_format, + output_serialization_format=serialization_format) + + return storage_options, converter_options diff --git a/rosbag2_py/test/test_sequential_reader.py b/rosbag2_py/test/test_sequential_reader.py new file mode 100644 index 0000000000..fc2be12c2b --- /dev/null +++ b/rosbag2_py/test/test_sequential_reader.py @@ -0,0 +1,70 @@ +# Copyright 2020 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +from common import get_rosbag_options +from rcl_interfaces.msg import Log +from rclpy.serialization import deserialize_message +import rosbag2_py +from rosidl_runtime_py.utilities import get_message +from std_msgs.msg import String + + +def test_sequential_reader(): + bag_path = str(Path(__file__).parent.parent / 'resources' / 'talker') + storage_options, converter_options = get_rosbag_options(bag_path) + + reader = rosbag2_py.SequentialReader() + reader.open(storage_options, converter_options) + + topic_types = reader.get_all_topics_and_types() + + # Create a map for quicker lookup + type_map = {topic_types[i].name: topic_types[i].type for i in range(len(topic_types))} + + # Set filter for topic of string type + storage_filter = rosbag2_py.StorageFilter(topics=['/topic']) + reader.set_filter(storage_filter) + + msg_counter = 0 + + while reader.has_next(): + (topic, data, t) = reader.read_next() + msg_type = get_message(type_map[topic]) + msg = deserialize_message(data, msg_type) + + assert isinstance(msg, String) + assert msg.data == f'Hello, world! {msg_counter}' + + msg_counter += 1 + + # No filter + reader.reset_filter() + + reader = rosbag2_py.SequentialReader() + reader.open(storage_options, converter_options) + + msg_counter = 0 + + while reader.has_next(): + (topic, data, t) = reader.read_next() + msg_type = get_message(type_map[topic]) + msg = deserialize_message(data, msg_type) + + assert isinstance(msg, Log) or isinstance(msg, String) + + if isinstance(msg, String): + assert msg.data == f'Hello, world! {msg_counter}' + msg_counter += 1 diff --git a/rosbag2_py/test/test_sequential_writer.py b/rosbag2_py/test/test_sequential_writer.py new file mode 100644 index 0000000000..e1a12f4e53 --- /dev/null +++ b/rosbag2_py/test/test_sequential_writer.py @@ -0,0 +1,85 @@ +# Copyright 2020 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from common import get_rosbag_options +from rclpy.serialization import deserialize_message, serialize_message +import rosbag2_py +from rosidl_runtime_py.utilities import get_message +from std_msgs.msg import String + + +def create_topic(writer, topic_name, topic_type, serialization_format='cdr'): + """ + Create a new topic. + + :param writer: writer instance + :param topic_name: + :param topic_type: + :param serialization_format: + :return: + """ + topic_name = topic_name + topic = rosbag2_py.TopicMetadata(name=topic_name, type=topic_type, + serialization_format=serialization_format) + + writer.create_topic(topic) + + +def test_sequential_writer(tmp_path): + """ + Test for sequential writer. + + :return: + """ + bag_path = str(tmp_path / 'tmp_write_test') + + storage_options, converter_options = get_rosbag_options(bag_path) + + writer = rosbag2_py.SequentialWriter() + writer.open(storage_options, converter_options) + + # create topic + topic_name = '/chatter' + create_topic(writer, topic_name, 'std_msgs/msg/String') + + for i in range(10): + msg = String() + msg.data = f'Hello, world! {str(i)}' + time_stamp = i * 100 + + writer.write(topic_name, serialize_message(msg), time_stamp) + + # close bag and create new storage instance + del writer + storage_options, converter_options = get_rosbag_options(bag_path) + + reader = rosbag2_py.SequentialReader() + reader.open(storage_options, converter_options) + + topic_types = reader.get_all_topics_and_types() + + # Create a map for quicker lookup + type_map = {topic_types[i].name: topic_types[i].type for i in range(len(topic_types))} + + msg_counter = 0 + while reader.has_next(): + topic, data, t = reader.read_next() + msg_type = get_message(type_map[topic]) + msg_deserialized = deserialize_message(data, msg_type) + + assert isinstance(msg_deserialized, String) + assert msg_deserialized.data == f'Hello, world! {msg_counter}' + assert t == msg_counter * 100 + + msg_counter += 1 From 593f52681279d6973568011494ad608b81de9bfc Mon Sep 17 00:00:00 2001 From: Mabel Zhang Date: Fri, 14 Aug 2020 20:12:09 -0400 Subject: [PATCH 2/2] AMENT_IGNORE rosbag2_py for now (#509) * AMENT_IGNORE rosbag2_py Signed-off-by: Mabel Zhang * remove rosbag2_py from meta package Signed-off-by: Mabel Zhang Signed-off-by: Alexander Gutenkunst --- rosbag2/package.xml | 1 - rosbag2_py/AMENT_IGNORE | 0 2 files changed, 1 deletion(-) create mode 100644 rosbag2_py/AMENT_IGNORE diff --git a/rosbag2/package.xml b/rosbag2/package.xml index 29c2ca6228..6cdf1be2a2 100644 --- a/rosbag2/package.xml +++ b/rosbag2/package.xml @@ -14,7 +14,6 @@ rosbag2_compression rosbag2_converter_default_plugins rosbag2_cpp - rosbag2_py rosbag2_storage rosbag2_storage_default_plugins rosbag2_transport diff --git a/rosbag2_py/AMENT_IGNORE b/rosbag2_py/AMENT_IGNORE new file mode 100644 index 0000000000..e69de29bb2