From 77b9eaf0386a851bd0036e930306022d878f9c8c Mon Sep 17 00:00:00 2001 From: AnthonyCvn Date: Wed, 13 Mar 2024 21:41:22 +0100 Subject: [PATCH] first draft --- .../img/ros-reductstore-example.drawio | 79 +++++++ .../img/ros-reductstore-example.webp | Bin 0 -> 33660 bytes blog/2024-03-15-ROS-integration/index.md | 195 ++++++++++++++++++ 3 files changed, 274 insertions(+) create mode 100644 blog/2024-03-15-ROS-integration/img/ros-reductstore-example.drawio create mode 100644 blog/2024-03-15-ROS-integration/img/ros-reductstore-example.webp create mode 100644 blog/2024-03-15-ROS-integration/index.md diff --git a/blog/2024-03-15-ROS-integration/img/ros-reductstore-example.drawio b/blog/2024-03-15-ROS-integration/img/ros-reductstore-example.drawio new file mode 100644 index 00000000..f3d4ae9c --- /dev/null +++ b/blog/2024-03-15-ROS-integration/img/ros-reductstore-example.drawio @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/blog/2024-03-15-ROS-integration/img/ros-reductstore-example.webp b/blog/2024-03-15-ROS-integration/img/ros-reductstore-example.webp new file mode 100644 index 0000000000000000000000000000000000000000..6f547eda05433ca7949a00ae221e940c5432ec5f GIT binary patch literal 33660 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 literal 0 HcmV?d00001 diff --git a/blog/2024-03-15-ROS-integration/index.md b/blog/2024-03-15-ROS-integration/index.md new file mode 100644 index 00000000..9bd9fe4f --- /dev/null +++ b/blog/2024-03-15-ROS-integration/index.md @@ -0,0 +1,195 @@ +--- +title: "Optimal Image Storage Solutions for ROS-Based Computer Vision" +description: "This blog post will guide you through setting up ROS 2 with ReductStore—a time-series database optimized for unstructured data. More specifically, we'll go through a practical example to show how to capture and store raw camera images from a ROS topic in ReductStore." +authors: anthony +tags: [tutorials, ros, computer-vision] +slug: tutorials/ros/optimal-image-storage-solutions-for-ros-based-computer-vision +date: 2024-03-15 +image: ./img/ros-reductstore-example.webp +--- + +![ROS with ReductStore](./img/ros-reductstore-example.webp) + +The Robot Operating System (ROS) stands as a versatile framework for developing sophisticated robotic applications with various sensors, including cameras. These cameras are relatively inexpensive and widely used as they can provide a wealth of information about the robot's environment. + +Processing camera output with computer vision requires efficient solutions to handle massive amounts of data in real time. ROS 2 is designed with this in mind, but it is a communication middleware and does not provide a built-in solution for storing and managing large volumes of image data. + +Addressing this challenge, this blog post will guide you through setting up ROS 2 with ReductStore—a time-series database for unstructured data optimized for edge computing, ensuring your robotic applications can process and store camera outputs effectively. + + + +## ROS 2 Distributions +To install ROS 2, you'll need to select a distribution that aligns with your project requirements and system compatibility. As of now, multiple distributions are available for ROS 2 [**here**](). + +Among them, two distributions are currently actively maintained : + +- **Iron Irwini** (Release date: May 23rd, 2023; End of life: November 2024) + +- **Humble Hawksbill** (Release date: May 23rd, 2022; End of life: May 2027) + + + + +We recommend the "Humble Hawksbill" distribution for its long-term support until May 2027. This is the eighth release of ROS 2 and caters to various platforms including Ubuntu Linux, Windows, RHEL (Red Hat Enterprise Linux), or macOS. + +To install the Humble Hawksbill distribution on your preferred operating system, follow the instructions provided in their [**installation guide**](). The installation process involves a series of commands specific to each platform and may require certain prerequisites like Python or C++ dependencies depending on your OS. + +## Understanding the Advantages of Using ReductStore with ROS +Integrating ReductStore with ROS provides many benefits for robotic applications. + +- **Best performance**: ReductStore's time-series design is tailored to the sequential nature of robotic applications and optimized for unstructured data, such as images, audio, and sensor readings. + +- **Real-time data management**: ReductStore provides real-time First In First Out (FIFO) quota management, which is critical for maintaining a balance between storage space and continuous data flow on edge devices. + +- **Metadata association**: It supports the association of labels or AI-generated analytics results directly with each stored image, enriching the dataset and facilitating subsequent processing or machine learning tasks. + +- **Replication**: ReductStore offers the ability to replicate data across a distributed network based on user-defined filters, to copy important data to a central server or cloud storage for further analysis. + + + + +## Example to Capture and Store Raw Camera Images +To capture and store raw camera images in a ROS-based system, you need to create a node that subscribes to the image topic, processes the image, and stores it in ReductStore. + +You can use the [**reduct-ros-example**]() as a guideline. This example demonstrates how to subscribe to a ROS topic, such as "/image\_raw", serialize the message in binary format, and store it in a bucket called "ros-bucket" under the entry "image-raw". + +### Setting Up Your Node: Integrating ReductStore with a ROS Client Instance +To set up your node for integrating ReductStore with a ROS client instance, you will need to create a custom ROS 2 Node that listens to image messages and uses ReductStore client for data storage. + +Below is an example demonstrating this integration within a Python class: + +```python +import asyncio +from reduct import Client, Bucket +from sensor_msgs.msg import Image +from rclpy.node import Node + +class ImageListener(Node): + """Node for listening to image messages and storing them in ReductStore.""" + def __init__(self, reduct_client: Client, loop: asyncio.AbstractEventLoop) -> None: + """ + Initialize the image listener node. + + :param reduct_client: Client instance for interacting with ReductStore. + :param loop: The asyncio event loop. + """ + super().__init__("image_listener") + self.reduct_client: Client = reduct_client + self.loop: asyncio.AbstractEventLoop = loop + self.bucket: Bucket = None + self.subscription = self.create_subscription( + Image, "/image_raw", self.image_callback, 10 + ) +``` + +In this example `ImageListener` is a subclass of `Node`, which is part of ROS 2's client library (`rclpy`). It sets up a subscription to listen for incoming images from the `/image_raw` topic. When an image message is received by the node via `self.subscription`, it triggers `image_callback`. + +In this callback function, each received frame is stored in the designated bucket in ReductStore using Python's built-in `asyncio` module, more on this later. + +### Initialize a New ReductStore Bucket +This process involves setting up configuration parameters such as the bucket name and its storage quota settings. + +In our example, we create a bucket named "ros-bucket" with a FIFO quota type, which is suitable for making sure that the disk doesn't run out of space. + +The `exist_ok` parameter ensures that if the bucket already exists, it won't raise an exception but rather reuse the existing one. + +Here's how you can define this initialization within our Python class: + +```python +from reduct_py import BucketSettings, QuotaType + +class ImageListener(Node): + # ... [other parts of ImageListener class] ... + + async def init_bucket(self) -> None: + """Asynchronously initialize the Reduct bucket for storing images.""" + self.get_logger().info("Initializing Reduct bucket") + self.bucket = await self.reduct_client.create_bucket( + "ros-bucket", + BucketSettings(quota_type=QuotaType.FIFO, quota_size=1_000_000_000), + exist_ok=True, + ) +``` + +This code snippet should be called within your existing `ImageListener` class during the node's initialization or before storing the first image. This ensures that the storage bucket is ready. + +### Handling Images in Callbacks +When an image message from a ROS topic is received, it triggers the `image_callback` method. This method's role is to serialize the image data and organize its storage without blocking the main thread. + +Serialization converts the ROS message format into a simpler binary representation that can be stored or transmitted more easily. + +Here's an example code snippet demonstrating how to handle images in callbacks for storing them in ReductStore: + +```python +from rclpy.serialization import serialize_message + +class ImageListener(Node): + # ... [previous parts of ImageListener class] ... + + def image_callback(self, msg: Image) -> None: + """ + Handle incoming image messages by scheduling storage. + + This callback is triggered by ROS message processing. It schedules + the image storage coroutine to be executed in the asyncio event loop. + """ + self.get_logger().info("Received an image") + timestamp = self.get_timestamp(msg) + image_data = serialize_message(msg) + asyncio.run_coroutine_threadsafe( + self.store_data(timestamp, image_data), self.loop + ) +``` + +In this context, `serialize_message` is used to convert the `Image` message object into a byte stream that can subsequently be passed along for storage. + +Following serialization, an asynchronous coroutine (`store_data`) is scheduled on the event loop (`self.loop`) using `asyncio.run_coroutine_threadsafe`. + +This function is particularly useful for integrating asynchronous operations within primarily synchronous ROS 2 callback handlers, ensuring that the processing doesn't block the executor. + +### Store Images in a ReductStore Bucket Entry +The `store_data` method is designed to receive timestamped image data and write it into a specific Reduct bucket. + +Here's an explanation of how this method works: + +- **Timestamp Parameter**: The `timestamp` argument ensures that each piece of data can be associated with the exact time it was captured. + +- **Data Serialization**: The `data` parameter expects a byte stream, which implies that image messages from ROS need to be serialized before being passed to this function. + + + + +With these considerations in mind, here's how you can define the `store_data` method within the `ImageListener` class: + +```python +class ImageListener(Node): + # ... [previous parts of ImageListener class] ... + + async def store_data(self, timestamp: int, data: bytes) -> None: + """ + Store unstructured data in the Reduct bucket. + + :param timestamp: The timestamp for the data. + :param data: The serialized data. + """ + if not self.bucket: + await self.init_bucket() + readable_timestamp = self.format_timestamp(timestamp) + self.get_logger().info(f"Storing data at {readable_timestamp}") + await self.bucket.write("image-raw", data, timestamp) +``` + +When the `store_data` method is called, it first checks if the bucket has been initialized. If not, it calls the `init_bucket` method to create the bucket. Then, it writes the serialized data to the bucket entry "image-raw" with the provided timestamp. + +As you can see, the `store_data` method is designed to be non-blocking, ensuring that the main thread can continue processing other tasks without waiting for the data to be stored. + + + +## Conclusion +In conclusion, this blog post has demonstrated how to capture and store raw camera images from a ROS topic in ReductStore. The provided code snippets serve as a practical guide for setting up such a system, highlighting the importance of non-blocking operations and proper serialization to maintain system performance. + +Using [**ReductStore**]() is straightforward to deploy and provides a robust solution for managing large volumes of image data in real time. The FIFO quota management, metadata association, and replication features make it an ideal choice for managing unstructured data in ROS-based computer vision applications. + +--- + +I hope this tutorial has been helpful. If you have any questions or feedback, don’t hesitate to reach out in [**Discord**](https://discord.gg/8wPtPGJYsn) or by opening a discussion on [**GitHub**](https://github.com/reductstore/reductstore/discussions). \ No newline at end of file