Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: python bindings for image_transport and publish #323

Merged
merged 27 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c4d8434
feat: python bindings for image_transport and publish
tfoldi Sep 23, 2024
ac72a10
style: reformat code
tfoldi Sep 24, 2024
4f95097
style: missing headers, copyright year
tfoldi Sep 24, 2024
ff4ffd7
build: add tests
tfoldi Sep 24, 2024
8a4250c
build: remove pluginlib (not used)
tfoldi Sep 24, 2024
898d875
style: reformat with rolling ament config
tfoldi Sep 24, 2024
4ceb2ec
WIP: before moving spin to custom class
tfoldi Sep 26, 2024
591c0fe
doc: fix repository and bugtracker url
tfoldi Sep 26, 2024
2eddcbf
feat: camera_info and subscriber support
tfoldi Sep 26, 2024
4ba43b5
Merge branch 'feat_image_transport_py' of https://github.com/tfoldi/i…
tfoldi Sep 26, 2024
1d331ab
doc: create initial version of the README.md file
tfoldi Sep 26, 2024
74e32f1
doc: move README.md to the right location
tfoldi Sep 26, 2024
f3ece7d
feat: add image_transport parameter
tfoldi Sep 26, 2024
98042aa
doc: complete documentation
tfoldi Sep 27, 2024
1cc8b23
style: remove too long line
tfoldi Sep 27, 2024
03b8e9a
style: reformat
tfoldi Sep 27, 2024
d2983f8
style: fix documentation formating
tfoldi Sep 27, 2024
6b615d2
fix: CameraSubscriber class name
tfoldi Oct 1, 2024
0f0f3c7
build: move pybind11 to build only dep
tfoldi Oct 2, 2024
9246ea6
chore: implement review suggestions
tfoldi Oct 4, 2024
e6428ae
build: add <2.9 pybind11 compatibility
tfoldi Oct 8, 2024
6b802dd
Merge branch 'feat_image_transport_py' of https://github.com/tfoldi/i…
tfoldi Oct 8, 2024
e6e71f0
build: add <2.9 pybind11 compatibility
tfoldi Oct 8, 2024
2424f26
build: add <2.9 pybind11 compatibility
tfoldi Oct 8, 2024
817ed6c
build: ament_cppcheck(LANGUAGE "c++") after ament_lint_auto_find_test…
tfoldi Oct 8, 2024
7c1ea65
build: cpp check after test dependencies
tfoldi Oct 8, 2024
bf4eae9
build: skip cppcheck on Windows
tfoldi Oct 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions image_transport/include/image_transport/image_transport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ class ImageTransport
IMAGE_TRANSPORT_PUBLIC
explicit ImageTransport(rclcpp::Node::SharedPtr node);

IMAGE_TRANSPORT_PUBLIC
ImageTransport(const ImageTransport & other);

IMAGE_TRANSPORT_PUBLIC
ImageTransport & operator=(const ImageTransport & other);

IMAGE_TRANSPORT_PUBLIC
~ImageTransport();

Expand Down Expand Up @@ -364,6 +370,11 @@ class ImageTransport
std::unique_ptr<Impl> impl_;
};

struct ImageTransport::Impl
{
rclcpp::Node::SharedPtr node_;
};

} // namespace image_transport

#endif // IMAGE_TRANSPORT__IMAGE_TRANSPORT_HPP_
6 changes: 2 additions & 4 deletions image_transport/src/image_transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,8 @@ std::vector<std::string> getLoadableTransports()
return loadableTransports;
}

struct ImageTransport::Impl
{
rclcpp::Node::SharedPtr node_;
};
ImageTransport::ImageTransport(const ImageTransport & other)
: impl_(std::make_unique<Impl>(*other.impl_)) {}

ImageTransport::ImageTransport(rclcpp::Node::SharedPtr node)
: impl_(std::make_unique<ImageTransport::Impl>())
Expand Down
3 changes: 3 additions & 0 deletions image_transport_py/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Changelog for package image_transport_py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75 changes: 75 additions & 0 deletions image_transport_py/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
cmake_minimum_required(VERSION 3.5)
project(image_transport_py)

# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()

# Default to C++17
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# Figure out Python3 debug/release before anything else can find_package it
if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
find_package(python_cmake_module REQUIRED)
find_package(PythonExtra REQUIRED)

# Force FindPython3 to use the debug interpreter where ROS 2 expects it
set(Python3_EXECUTABLE "${PYTHON_EXECUTABLE_DEBUG}")
endif()
tfoldi marked this conversation as resolved.
Show resolved Hide resolved

find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(ament_cmake_ros REQUIRED)
find_package(image_transport REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)

# Find python before pybind11
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)

find_package(pybind11_vendor REQUIRED)
find_package(pybind11 REQUIRED)

ament_python_install_package(${PROJECT_NAME})

pybind11_add_module(_image_transport MODULE
src/image_transport_py/pybind_image_transport.cpp
)

target_include_directories(_image_transport PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include/${PROJECT_NAME}>")

target_link_libraries(_image_transport PUBLIC
image_transport::image_transport
rclcpp::rclcpp
${sensor_msgs_TARGETS}
)

# Install cython modules as sub-modules of the project
install(
TARGETS
_image_transport
DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}"
)

if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)

list(APPEND AMENT_LINT_AUTO_EXCLUDE
ament_cmake_cppcheck
)
ament_lint_auto_find_test_dependencies()

tfoldi marked this conversation as resolved.
Show resolved Hide resolved
ament_cppcheck(LANGUAGE "c++")
endif()

ament_package()
203 changes: 203 additions & 0 deletions image_transport_py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# image_transport_py: Python Bindings for ROS 2 Image Transport
tfoldi marked this conversation as resolved.
Show resolved Hide resolved

## Introduction

`image_transport_py` is a Python package that provides bindings for `image_transport`. It enables efficient publishing and subscribing of images in Python, leveraging various transport plugins (e.g., `raw`, `compressed`).
The package allows developers to handle image topics more efficiently and with less overhead than using standard ROS 2 topics.

## Usage

### Publishing Images

To publish images using `image_transport_py`, you create an `ImageTransport` object and use it to advertise an image topic. The first parameter for `ImageTransport` is the image transport
node's name which needs to be unique in the namespace.

Steps:

1. Import Necessary Modules:

```python
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from image_transport_py import ImageTransport
```

2. Initialize the Node and ImageTransport:

```python
class ImagePublisher(Node):
def __init__(self):
super().__init__('image_publisher')
self.image_transport = ImageTransport("imagetransport_pub", image_transport="raw")
self.publisher = self.image_transport.advertise('camera/image', 10)

# read images at 10Hz frequency
self.timer = self.create_timer(0.1, self.publish_image)
```

3. Publish Images in the Callback:
```python
def publish_image(self):
image_msg = .. # Read image from your devices

image_msg.header.stamp = self.get_clock().now().to_msg()
self.publisher.publish(image_msg)
```

`advertise_camera` can publish `CameraInfo` along with `Image` message.

### Subscribing to Images

To subscribe to images, use `ImageTransport` to create a subscription to the image topic.

Steps:

1. Import Necessary Modules:

```python
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from image_transport_py import ImageTransport
```

2. Initialize the Node and ImageTransport:
```python
class ImageSubscriber(Node):
def __init__(self):
super().__init__('image_subscriber')
self.image_transport = ImageTransport("imagetransport_sub", image_transport="raw")
self.subscription = self.image_transport.subscribe('camera/image', 10, self.image_callback)
```

3. Handle Incoming Images:
```python
def image_callback(self, msg):
# do something with msg (= sensor_msgs.msg.Image type)
```

`subscribe_camera` will add `CameraInfo` along with `Image` message for the callback.

### Transport Selection

By default, `image_transport` uses the `raw` transport. You can specify a different transport by passing `image_transport` parameter to `ImageTransport`. Alternatively,
you can use your own ROS2 parameter file for the imagetransport node via `launch_params_filepath` parameter.

## Classes

### Publisher

A publisher for images.

#### Methods

- `get_topic()`

Returns the base image topic.

- `get_num_subscribers()`

Returns the number of subscribers this publisher is connected to.

- `shutdown()`

Unsubscribe the callback associated with this Publisher.

- `publish(img)`

Publish an image on the topics associated with this Publisher.

### CameraPublisher

A publisher for images with camera info.

#### Methods

- `get_topic()`

Returns the base (image) topic of this CameraPublisher.

- `get_num_subscribers()`

Returns the number of subscribers this camera publisher is connected to.

- `shutdown()`

Unsubscribe the callback associated with this CameraPublisher.

- `publish(img, info)`

Publish an image and camera info on the topics associated with this Publisher.

### ImageTransport

An object for image transport operations.

#### Constructor

- `__init__(node_name, image_transport="", launch_params_filepath="")`

Initialize an ImageTransport object with its node name, `image_transport` and launch params file path. If no `image_transport` specified, the default `raw` plugin will be initialized.

#### Methods

- `advertise(base_topic, queue_size, latch=False)`

Advertise an image topic.

- `advertise_camera(base_topic, queue_size, latch=False)`

Advertise an image topic with camera info.

- `subscribe(base_topic, queue_size, callback)`

Subscribe to an image topic.

- `subscribe_camera(base_topic, queue_size, callback)`

Subscribe to an image topic with camera info.

### Subscriber

A subscriber for images.

#### Methods

- `get_topic()`

Returns the base image topic.

- `get_num_publishers()`

Returns the number of publishers this subscriber is connected to.

- `get_transport()`

Returns the name of the transport being used.

- `shutdown()`

Unsubscribe the callback associated with this Subscriber.

### CameraSubscriber

A subscriber for images with camera info.

#### Methods

- `get_topic()`

Returns the base image topic.

- `get_num_publishers()`

Returns the number of publishers this subscriber is connected to.

- `get_transport()`

Returns the name of the transport being used.

- `shutdown()`

Unsubscribe the callback associated with this CameraSubscriber.
36 changes: 36 additions & 0 deletions image_transport_py/image_transport_py/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2024 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 rpyutils import add_dll_directories_from_env

# Since Python 3.8, on Windows we should ensure DLL directories are explicitly added
# to the search path.
# See https://docs.python.org/3/whatsnew/3.8.html#bpo-36085-whatsnew
with add_dll_directories_from_env('PATH'):
from image_transport_py._image_transport import (
ImageTransport,
Publisher,
Subscriber,
CameraPublisher,
CameraSubscriber,
)


__all__ = [
'ImageTransport',
'Publisher',
'Subscriber',
'CameraPublisher',
'CameraSubscriber',
]
Loading