Add common usage examples to docs

This commit is contained in:
Marko Durkovic 2022-01-31 13:35:42 +01:00
parent af4830c647
commit f5d3494cbb
13 changed files with 382 additions and 0 deletions

View File

@ -0,0 +1,42 @@
"""Example: Edit timestamps."""
from __future__ import annotations
from typing import TYPE_CHECKING
from rosbags.rosbag2 import Reader, Writer
from rosbags.serde import deserialize_cdr, serialize_cdr
if TYPE_CHECKING:
from pathlib import Path
def offset_timestamps(src: Path, dst: Path, offset: int) -> None:
"""Offset timestamps.
Args:
src: Source path.
dst: Destination path.
offset: Amount of nanoseconds to offset timestamps.
"""
with Reader(src) as reader, Writer(dst) as writer:
conn_map = {}
for conn in reader.connections.values():
conn_map[conn.id] = writer.add_connection(
conn.topic,
conn.msgtype,
conn.serialization_format,
conn.offered_qos_profiles,
)
for conn, timestamp, data in reader.messages():
# Adjust header timestamps, too
msg = deserialize_cdr(data, conn.msgtype)
if head := getattr(msg, 'header', None):
headstamp = head.stamp.sec * 10**9 + head.stamp.nanosec + offset
head.stamp.sec = headstamp // 10**9
head.stamp.nanosec = headstamp % 10**9
data = serialize_cdr(msg, conn.msgtype)
writer.write(conn_map[conn.id], timestamp + offset, data)

View File

@ -0,0 +1,36 @@
"""Example: Remove topic."""
from __future__ import annotations
from typing import TYPE_CHECKING
from rosbags.rosbag2 import Reader, Writer
if TYPE_CHECKING:
from pathlib import Path
def remove_topic(src: Path, dst: Path, topic: str) -> None:
"""Remove topic from rosbag2.
Args:
src: Source path.
dst: Destination path.
topic: Name of topic to remove.
"""
with Reader(src) as reader, Writer(dst) as writer:
conn_map = {}
for conn in reader.connections.values():
if conn.topic == topic:
continue
conn_map[conn.id] = writer.add_connection(
conn.topic,
conn.msgtype,
conn.serialization_format,
conn.offered_qos_profiles,
)
rconns = [reader.connections[x] for x in conn_map]
for conn, timestamp, data in reader.messages(connections=rconns):
writer.write(conn_map[conn.id], timestamp, data)

View File

@ -0,0 +1,16 @@
Edit rosbags
============
Rosbags does not support opening files in read-write mode, but implicitly enforces copy-on-write semantics. Apart from the mapping of reader to writer connections the process is fairly straightforward.
Remove topic
------------
.. literalinclude:: ./edit_rosbags_remove_topic.py
Edit timestamps
---------------
.. literalinclude:: ./edit_rosbags_edit_timestamps.py

View File

@ -0,0 +1,22 @@
Register custom message types
=============================
Out of the box rosbags only supports the message types that ship with a default ROS2 distribution. If you want to (de)serialize custom messages you need to add them to the type system manually.
From rosbag1
------------
.. literalinclude:: ./register_types_rosbag1.py
From definition string
----------------------
.. literalinclude:: ./register_types_string.py
From multiple files
-------------------
.. literalinclude:: ./register_types_files.py

View File

@ -0,0 +1,24 @@
"""Example: Register types from msg files."""
from pathlib import Path
from rosbags.typesys import get_types_from_msg, register_types
add_types = {}
msgdef = Path('/path/to/custom_msgs/msg/Speed.msg').read_text(encoding='utf-8')
add_types.update(get_types_from_msg(msgdef, 'custom_msgs/msg/Speed.msg'))
msgdef = Path('/path/to/custom_msgs/msg/Accel.msg').read_text(encoding='utf-8')
add_types.update(get_types_from_msg(msgdef, 'custom_msgs/msg/Accel.msg'))
register_types(add_types)
# Type import works only after the register_types call,
# the classname is derived from the msgtype names above
# pylint: disable=no-name-in-module,wrong-import-position
from rosbags.typesys.types import custom_msgs__msg__Speed as Speed # type: ignore # noqa
from rosbags.typesys.types import custom_msgs__msg__Accel as Accel # type: ignore # noqa
# pylint: enable=no-name-in-module,wrong-import-position

View File

@ -0,0 +1,29 @@
"""Example: Register rosbag1 types."""
from __future__ import annotations
from typing import TYPE_CHECKING
from rosbags.rosbag1 import Reader
from rosbags.typesys import get_types_from_msg, register_types
if TYPE_CHECKING:
from pathlib import Path
def process_bag(src: Path) -> None:
"""Register contained messages types before processing bag.
Args:
src: Bag to process.
"""
with Reader(src) as reader:
typs = {}
for conn in reader.connections.values():
typs.update(get_types_from_msg(conn.msgdef, conn.msgtype))
register_types(typs)
# Now all message types used in the bag are registered
# for conn, timestamp, data in reader.messages():
# ...

View File

@ -0,0 +1,25 @@
"""Example: Register type from definition string."""
from rosbags.serde import serialize_cdr
from rosbags.typesys import get_types_from_msg, register_types
# Your custom message definition
STRIDX_MSG = """
string string
uint32 index
"""
register_types(get_types_from_msg(STRIDX_MSG, 'custom_msgs/msg/StrIdx'))
# Type import works only after the register_types call,
# the classname is derived from the msgtype name above
# pylint: disable=no-name-in-module,wrong-import-position
from rosbags.typesys.types import custom_msgs__msg__StrIdx as StrIdx # type: ignore # noqa
# pylint: enable=no-name-in-module,wrong-import-position
message = StrIdx(string='foo', index=42)
# Rawdata that can be passed to rosbag2.Writer.write
rawdata = serialize_cdr(message, message.__msgtype__)

View File

@ -0,0 +1,16 @@
Save images as rosbag
=====================
The following examples show how to create new ROS bags from images.
Save rosbag1
------------
.. literalinclude:: ./save_images_rosbag1.py
Save rosbag2
------------
.. literalinclude:: ./save_images_rosbag2.py

View File

@ -0,0 +1,46 @@
"""Example: Save images as rosbag1."""
import numpy
from rosbags.rosbag1 import Writer
from rosbags.serde import cdr_to_ros1, serialize_cdr
from rosbags.typesys.types import builtin_interfaces__msg__Time as Time
from rosbags.typesys.types import sensor_msgs__msg__CompressedImage as CompressedImage
from rosbags.typesys.types import std_msgs__msg__Header as Header
TOPIC = '/camera'
FRAMEID = 'map'
# Contains filenames and their timestamps
IMAGES = [
('homer.jpg', 42),
('marge.jpg', 43),
]
def save_images() -> None:
"""Iterate over IMAGES and save to output bag."""
with Writer('output.bag') as writer:
conn = writer.add_connection(TOPIC, CompressedImage.__msgtype__)
for path, timestamp in IMAGES:
message = CompressedImage(
Header(
stamp=Time(
sec=int(timestamp // 10**9),
nanosec=int(timestamp % 10**9),
),
frame_id=FRAMEID,
),
format='jpeg', # could also be 'png'
data=numpy.fromfile(path, dtype=numpy.uint8), # type: ignore
)
writer.write(
conn,
timestamp,
cdr_to_ros1(
serialize_cdr(message, CompressedImage.__msgtype__),
CompressedImage.__msgtype__,
),
)

View File

@ -0,0 +1,43 @@
"""Save multiple images in rosbag2."""
import numpy
from rosbags.rosbag2 import Writer
from rosbags.serde import serialize_cdr
from rosbags.typesys.types import builtin_interfaces__msg__Time as Time
from rosbags.typesys.types import sensor_msgs__msg__CompressedImage as CompressedImage
from rosbags.typesys.types import std_msgs__msg__Header as Header
TOPIC = '/camera'
FRAMEID = 'map'
# Contains filenames and their timestamps
IMAGES = [
('homer.jpg', 42),
('marge.jpg', 43),
]
def save_images() -> None:
"""Iterate over IMAGES and save to output bag."""
with Writer('output') as writer:
conn = writer.add_connection(TOPIC, CompressedImage.__msgtype__, 'cdr', '')
for path, timestamp in IMAGES:
message = CompressedImage(
Header(
stamp=Time(
sec=int(timestamp // 10**9),
nanosec=int(timestamp % 10**9),
),
frame_id=FRAMEID,
),
format='jpeg', # could also be 'png'
data=numpy.fromfile(path, dtype=numpy.uint8), # type: ignore
)
writer.write(
conn,
timestamp,
serialize_cdr(message, message.__msgtype__),
)

View File

@ -0,0 +1,66 @@
"""Example: Message instance conversion."""
from __future__ import annotations
import importlib
from typing import TYPE_CHECKING
import numpy
if TYPE_CHECKING:
from typing import Any
NATIVE_CLASSES: dict[str, Any] = {}
def to_native(msg: Any) -> Any:
"""Convert rosbags message to native message.
Args:
msg: Rosbags message.
Returns:
Native message.
"""
msgtype: str = msg.__msgtype__
if msgtype not in NATIVE_CLASSES:
pkg, name = msgtype.rsplit('/', 1)
NATIVE_CLASSES[msgtype] = getattr(importlib.import_module(pkg.replace('/', '.')), name)
fields = {}
for name, field in msg.__dataclass_fields__.items():
if 'ClassVar' in field.type:
continue
value = getattr(msg, name)
if '__msg__' in field.type:
value = to_native(value)
elif isinstance(value, numpy.ndarray):
value = value.tolist()
fields[name] = value
return NATIVE_CLASSES[msgtype](**fields)
if __name__ == '__main__':
from rosbags.typesys.types import (
builtin_interfaces__msg__Time,
sensor_msgs__msg__Image,
std_msgs__msg__Header,
)
image = sensor_msgs__msg__Image(
std_msgs__msg__Header(
builtin_interfaces__msg__Time(42, 666),
'/frame',
),
4,
4,
'rgb8',
False,
4 * 3,
numpy.zeros(4 * 4 * 3, dtype=numpy.uint8),
)
native_image = to_native(image)
# native_image can now be passed to the ROS stack

View File

@ -0,0 +1,10 @@
Use with native stack
=====================
Messages read with rosbags are simple dataclasses that mimic the native ROS2 interface. If you want to pass those messages to the native ROS2 stack, you need to convert them into native objects first.
Message instance conversion
---------------------------
.. literalinclude:: ./use_with_native.py

View File

@ -16,6 +16,13 @@
topics/rosbag1
topics/convert
.. toctree::
:caption: Usage examples
:maxdepth: 0
:hidden:
:glob:
examples/*
.. toctree::
:caption: API