diff --git a/.gitattributes b/.gitattributes index e2f3e0ca..8d1daa40 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,6 @@ +# Ignore Python files in linguist +*.py linguist-detectable=false + # Images *.gif filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md index 7e97b44e..169afbc0 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,31 @@ -# Isaac ROS Nvblox (Preview) +# Isaac ROS Nvblox -
+
## Overview -Nvblox is a package for building a 3D reconstruction of the environment around your robot from sensor observations in real-time. The reconstruction is intended to be used by path planners to generate collision-free paths. Under the hood, nvblox uses NVIDIA CUDA to accelerate this task to allow operation at real-time rates. This repository contains ROS2 integration for the [nvblox core library](https://github.com/nvidia-isaac/nvblox). +Isaac ROS Nvblox contains ROS 2 packages for 3D reconstruction and cost maps for navigation. `nvblox_nav2` processes depth and pose to reconstruct a 3D scene in real-time and a 2D costmap for [Nav2](https://github.com/ros-planning/navigation2). The costmap is used in planning during navigation as a vision-based solution to avoid obstacles. -Given a stream of depth images and the corresponding pose of the depth sensor, Isaac ROS Nvblox produces both a 2D distance map slice, representing the distance from each point to the nearest reconstructed object, and also a 3D mesh for visualization in RVIZ. An optional RGB stream can also be used to color the reconstruction for visualization. +`nvblox_nav2` is designed to work with stereo cameras, which provide a depth image, and the corresponding pose uses GPU acceleration to compute 3D reconstruction and 2D costmaps using [nvblox](https://github.com/nvidia-isaac/nvblox). -Read more about Nvblox's technical details [here](docs/technical-details.md). +
-The figure below shows a simple system utilizing nvblox for path planning. +Above is a typical graph that uses `nvblox_nav2`. The input color image corresponding to the depth image is processed with `unet`, using the PeopleSemSegNet DNN model to estimate a segmentation mask for persons in the color image. Pose corresponding to the depth image is computed using `visual_slam`. The resulting persons mask and pose is used with the color image and depth image to perform 3D scene reconstruction. The output costmap is provided through a cost map plugin into [Nav2](https://github.com/ros-planning/navigation2), with an optional colorized 3D reconstruction into a Rviz using the mesh visualization plugin. + +
+ +`nvblox_nav2` builds the reconstructed map in the form of a TSDF (Truncated Signed Distance Function) stored in a 3D voxel grid. This approach is similar to 3D occupancy grid mapping approaches in which occupancy probabilities are stored at each voxel. However, TSDF-based approaches like nvblox store the (signed) distance to the closest surface at each voxel. The surface of the environment can then be extracted as the zero-level set of this voxelized function. Typically, TSDF-based reconstructions provide higher quality surface reconstructions. + +In addition to their use in reconstruction, distance fields are also useful for path planning because they provide an immediate means of checking whether potential future robot positions are in collision. + +People are common obstacles for mobile robots, and while part of a costmap, people should not be part of the 3D reconstruction. Planners that provide behavioral awareness by navigating differently depending on their proximity to people, benefit from a costmap for people. Person segmentation is computed using the color image, with the resulting mask applied to the depth image separating depth into scene depth and person depth images. The scene depth image is forwarded to TSDF mapping as explained above, the depth image for people is processed to an occupancy grid map. + +To relax the assumption that occupancy grid maps only capture static objects, Nvblox applies an occupancy decay step. At a fixed frequency, all voxel occupancy probabilities are decayed towards 0.5 over time. This means that the state of the map (occupied or free) becomes less certain after it has fallen out of the field of view, until it becomes unknown (0.5 occupancy probability). -
## Table of Contents -- [Isaac ROS Nvblox (Preview)](#isaac-ros-nvblox-preview) +- [Isaac ROS Nvblox](#isaac-ros-nvblox) - [Overview](#overview) - [Table of Contents](#table-of-contents) - [Latest Update](#latest-update) @@ -26,46 +35,42 @@ The figure below shows a simple system utilizing nvblox for path planning. - [Next Steps](#next-steps) - [Try More Examples](#try-more-examples) - [Customize your Dev Environment](#customize-your-dev-environment) - - [Package Reference](#package-reference) - - [`nvblox_nav2`](#nvblox_nav2) - - [Usage](#usage) - - [ROS Parameters](#ros-parameters) - - [ROS Topics Subscribed](#ros-topics-subscribed) - - [ROS Topics Published](#ros-topics-published) - - [ROS Services Advertised](#ros-services-advertised) + - [Packages Overview](#packages-overview) + - [ROS 2 Parameters](#ros-2-parameters) + - [ROS 2 Topics and Services](#ros-2-topics-and-services) - [Troubleshooting](#troubleshooting) - [Isaac ROS Troubleshooting](#isaac-ros-troubleshooting) - - [`realsense-ros` packages don't build with ROS2 Humble](#realsense-ros-packages-dont-build-with-ros2-humble) + - [*realsense-ros* packages don't build with ROS 2 Humble](#realsense-ros-packages-dont-build-with-ros-2-humble) + - [Troubleshooting the Nvblox Realsense Example](#troubleshooting-the-nvblox-realsense-example) + - [Troubleshooting ROS 2 communication issues](#troubleshooting-ros-2-communication-issues) - [Updates](#updates) ## Latest Update -Update 2022-10-19: Updated OSS licensing +Update 2023-04-05: Human reconstruction and new weighting functions. ## Supported Platforms -This package is designed and tested to be compatible with ROS2 Humble running on [Jetson](https://developer.nvidia.com/embedded-computing) or an x86_64 system with an NVIDIA GPU. +This package is designed and tested to be compatible with ROS 2 Humble running on [Jetson](https://developer.nvidia.com/embedded-computing) or an x86_64 system with an NVIDIA GPU. -> **Note**: Versions of ROS2 earlier than Humble are **not** supported. This package depends on specific ROS2 implementation features that were only introduced beginning with the Humble release. +> **Note**: Versions of ROS 2 earlier than Humble are **not** supported. This package depends on specific ROS 2 implementation features that were only introduced beginning with the Humble release. -| Platform | Hardware | Software | Notes | -| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Jetson | [Jetson Orin](https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-orin/)
[Jetson Xavier](https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-agx-xavier/) | [JetPack 5.0.2](https://developer.nvidia.com/embedded/jetpack) | For best performance, ensure that [power settings](https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance.html) are configured appropriately. | -| x86_64 | NVIDIA GPU | [Ubuntu 20.04+](https://releases.ubuntu.com/20.04/)
[CUDA 11.6.1+](https://developer.nvidia.com/cuda-downloads) | +| Platform | Hardware | Software | Notes | +| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Jetson | [Jetson Orin](https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-orin/)
[Jetson Xavier](https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-agx-xavier/) | [JetPack 5.1.1](https://developer.nvidia.com/embedded/jetpack) | For best performance, ensure that [power settings](https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance.html) are configured appropriately. | +| x86_64 | NVIDIA GPU | [Ubuntu 20.04+](https://releases.ubuntu.com/20.04/)
[CUDA 11.8+](https://developer.nvidia.com/cuda-downloads) | | ### Docker To simplify development, we strongly recommend leveraging the Isaac ROS Dev Docker images by following [these steps](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_common/blob/main/docs/dev-env-setup.md). This will streamline your development environment setup with the correct versions of dependencies on both Jetson and x86_64 platforms. -> **Note:** All Isaac ROS Quickstarts, tutorials, and examples have been designed with the Isaac ROS Docker images as a prerequisite. +> **Note**: All Isaac ROS Quickstarts, tutorials, and examples have been designed with the Isaac ROS Docker images as a prerequisite. ## Quickstart 1. Set up your development environment by following the instructions [here](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_common/blob/main/docs/dev-env-setup.md). -2. Complete the RealSense setup guide [here](https://github.com/NVIDIA-ISAAC-ROS/.github/blob/main/profile/realsense-setup.md). - -3. Clone this repository and its dependencies under `~/workspaces/isaac_ros-dev/src`. +2. Clone this repository and its dependencies under `~/workspaces/isaac_ros-dev/src`. ```bash cd ~/workspaces/isaac_ros-dev/src @@ -80,36 +85,28 @@ To simplify development, we strongly recommend leveraging the Isaac ROS Dev Dock cd isaac_ros_nvblox && git lfs pull ``` -4. Configure the container created by `isaac_ros_common/scripts/run_dev.sh` to include `librealsense`. Create the `.isaac_ros_common-config` file in the `isaac_ros_common/scripts` directory: - - ```bash - cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common/scripts && \ - touch .isaac_ros_common-config && \ - echo CONFIG_IMAGE_KEY=humble.nav2.realsense > .isaac_ros_common-config - ``` - -5. Pull down a ROS Bag of sample data: +3. Pull down a ROS Bag of sample data: ```bash cd ~/workspaces/isaac_ros-dev/src/isaac_ros_nvblox && \ git lfs pull -X "" -I "nvblox_ros/test/test_cases/rosbags/nvblox_pol" ``` -6. Launch the Docker container using the `run_dev.sh` script: +4. Launch the Docker container using the `run_dev.sh` script: ```bash cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ ./scripts/run_dev.sh ``` -7. Inside the container, install package-specific dependencies via `rosdep`: +5. Inside the container, install package-specific dependencies via `rosdep`: ```bash cd /workspaces/isaac_ros-dev/ && \ rosdep install -i -r --from-paths src --rosdistro humble -y --skip-keys "libopencv-dev libopencv-contrib-dev libopencv-imgproc-dev python-opencv python3-opencv nvblox" ``` -8. Build and source the workspace: +6. Build and source the workspace: ```bash cd /workspaces/isaac_ros-dev && \ @@ -117,106 +114,78 @@ To simplify development, we strongly recommend leveraging the Isaac ROS Dev Dock source install/setup.bash ``` -9. (Optional) Run tests to verify complete and correct installation: +8. (Optional) Run tests to verify complete and correct installation: ```bash colcon test --executor sequential ``` -10. In a **current terminal** inside the Docker container, run the launch file for Nvblox with `nav2`: +9. In a **current terminal** inside the Docker container, run the launch file for Nvblox with `nav2`: ```bash source /workspaces/isaac_ros-dev/install/setup.bash && \ - ros2 launch nvblox_nav2 carter_sim.launch.py + ros2 launch nvblox_examples_bringup isaac_sim_example.launch.py ``` -11. Open a **second terminal** inside the docker container: +10. Open a **second terminal** inside the docker container: ```bash cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ ./scripts/run_dev.sh ``` -12. In the **second terminal**, play the ROS Bag: +11. In the **second terminal**, play the ROS Bag: ```bash ros2 bag play src/isaac_ros_nvblox/nvblox_ros/test/test_cases/rosbags/nvblox_pol ``` -You should see the robot reconstructing a mesh, with a costmap overlaid on top. +You should see the robot reconstructing a mesh, with the 2d esdf slice overlaid on top. + +
## Next Steps ### Try More Examples -To continue your exploration, check out the following suggested examples: +The launch files for all examples are available in the `nvblox_examples_bringup` package: +| Launch file | Arguments | Description | +| ------------------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `isaac_sim_example.launch.py` | `run_nav2`, `run_rviz` | Example to run with Isaac Sim ([tutorial](./docs/tutorial-isaac-sim.md)) | +| `isaac_sim_humans_example.launch.py` | `run_nav2`, `run_rviz` | Example to run with Isaac Sim including human reconstruction ([tutorial](./docs/tutorial-human-reconstruction-isaac-sim.md)) | +| `realsense_example.launch.py` | `from_bag`, `bag_path`, `run_rviz` | Example to run with a realsense camera ([tutorial](./docs/tutorial-realsense.md)) | +| `realsense_humans_example.launch.py` | `from_bag`, `bag_path`, `run_rviz` | Example to run with a realsense camera including human reconstruction ([tutorial](./docs/tutorial-human-reconstruction-realsense.md)) | +| `record_realsense.launch.py` | `launch_realsense`, `run_rqt` | Record realsense data to replay with the above examples ([tutorial](./docs/tutorial-realsense-record.md)) | -- [Tutorial with Isaac Sim](./docs/tutorial-isaac-sim.md) -- [Tutorial with realsense, VSLAM and nvblox](./docs/tutorial-nvblox-vslam-realsense.md) ### Customize your Dev Environment To customize your development environment, reference [this guide](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_common/blob/main/docs/modify-dockerfile.md). -## Package Reference - -### `nvblox_nav2` - -#### Usage - -```bash -ros2 launch nvblox_nav2 carter_sim.launch.py -``` - -#### ROS Parameters - -| ROS Parameter | Type | Default | Description | -| --------------------------------------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `voxel_size` | `float` | `0.05` | Voxel size (in meters) to use for the map. | -| `esdf` | `bool` | `true` | Whether to compute the ESDF (map of distances to the nearest obstacle). | -| `esdf_2d` | `bool` | `true` | Whether to compute the ESDF in 2D (true) or 3D (false). | -| `distance_slice` | `bool` | `true` | Whether to output a distance slice of the ESDF to be used for path planning. | -| `mesh` | `bool` | `true` | Whether to output a mesh for visualization in rviz, to be used with `nvblox_rviz_plugin`. | -| `global_frame` | `string` | `map` | The name of the TF frame to be used as the global frame. | -| `slice_height` | `float` | `1.0` | The *output* slice height for the distance slice and ESDF pointcloud. Does not need to be within min and max height below. In units of meters. | -| `min_height` | `float` | `0.0` | The minimum height, in meters, to consider obstacles part of the 2D ESDF slice. | -| `max_height` | `float` | `1.0` | The maximum height, in meters, to consider obstacles part of the 2D ESDF slice. | -| `max_tsdf_update_hz` | `float` | `10.0` | The maximum rate (in Hz) at which to integrate depth images into the TSDF. A value of 0.0 means there is no cap. | -| `max_color_update_hz` | `float` | `5.0` | The maximum rate (in Hz) at which to integrate color images into the color layer. A value of 0.0 means there is no cap. | -| `max_mesh_update_hz` | `float` | `5.0` | The maximum rate (in Hz) at which to update and color the mesh. A value of 0.0 means there is no cap. | -| `max_esdf_update_hz` | `float` | `2.0` | The maximum rate (in Hz) at which to update the ESDF and output the distance slice. A value of 0.0 means there is no cap. | -| `tsdf_integrator_max_integration_distance_m` | `float` | `10.0` | The maximum distance, in meters, to integrate the TSDF up to. | -| `tsdf_integrator_truncation_distance_vox` | `float` | `4.0` | The truncation distance, in units of voxels, for the TSDF. | -| `tsdf_integrator_max_weight` | `float` | `100.0` | Maximum weight for the TSDF. Setting this number higher will lead to higher-quality reconstructions but worse performance in dynamic scenes. | -| `mesh_integrator_min_weight` | `float` | `1e-4` | Minimum weight of the TSDF to consider for inclusion in the mesh. | -| `mesh_integrator_weld_vertices` | `bool` | `false` | Whether to weld identical vertices together in the mesh. Currently reduces the number of vertices by a factor of 5x, but is quite slow so we do not recommend you use this setting. | -| `color_integrator_max_integration_distance_m` | `float` | `10.0` | Maximum distance, in meters, to integrate the color up to. | -| `esdf_integrator_min_weight` | `float` | `1e-4` | Minimum weight of the TSDF to consider for inclusion in the ESDF. | -| `esdf_integrator_min_site_distance_vox` | `float` | `1.0` | Minimum distance to consider a voxel within a surface for the ESDF calculation. | -| `esdf_integrator_max_distance_m` | `float` | `10.0` | Maximum distance to compute the ESDF up to, in meters. | - -#### ROS Topics Subscribed - -| ROS Topic | Interface | Description | -| ------------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `depth/image` | [sensor_msgs/Image](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/Image.msg) | The input depth image to be integrated. Must be paired with a `camera_info` message below. Supports both floating-point (depth in meters) and `uint16` (depth in millimeters, OpenNI format). | -| `depth/camera_info` | [sensor_msgs/CameraInfo](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/CameraInfo.msg) | Required topic along with the depth image; contains intrinsic calibration parameters of the depth camera. | -| `color/image` | [sensor_msgs/Image](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/Image.msg) | Optional input color image to be integrated. Must be paired with a `camera_info` message below. Only used to color the mesh. | -| `color/camera_info` | [sensor_msgs/CameraInfo](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/CameraInfo.msg) | Optional topic along with the color image above, contains intrinsics of the color camera. | - -#### ROS Topics Published - -| ROS Topic | Interface | Description | -| ------------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `mesh` | [nvblox_msgs/Mesh](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/msg/Mesh.msg) | A visualization topic showing the mesh produced from the TSDF in a form that can be seen in RViz using `nvblox_rviz_plugin`. Set ``max_mesh_update_hz`` to control its update rate. | -| `pointcloud` | [sensor_msgs/PointCloud2](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/PointCloud2.msg) | A pointcloud of the 2D ESDF (Euclidean Signed Distance Field), with intensity as the metric distance to the nearest obstacle. Set ``max_esdf_update_hz`` to control its update rate. | -| `map_slice` | [nvblox_msgs/DistanceMapSlice](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/msg/DistanceMapSlice.msg) | A 2D slice of the ESDF, to be consumed by `nvblox_nav2` package for interfacing with Nav2. Set ``max_esdf_update_hz`` to control its update rate. | - -#### ROS Services Advertised - -| ROS Service | Interface | Description | -| ----------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| `save_ply` | [std_srvs/Empty](https://github.com/ros2/common_interfaces/blob/master/std_srvs/srv/Empty.srv) | This service has an empty request and response. Calling this service will write a mesh to disk at the path specified by the `output_dir` parameter. | +## Packages Overview + +* **isaac_ros_nvblox**: A meta-package. +* **nvblox_examples_bringup**: Launch files and configurations for launching the examples. +* **nvblox_image_padding**: Node to pad and crop images for adjusting the image size to the fixed input resolution that is required by the [image segmentation network](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_image_segmentation). +* **nvblox_isaac_sim**: Contains scripts for launching Isaac Sim configured for use with nvblox. +* **realsense_splitter**: Node for using the realsense camera with inbuilt projector. See why this is needed [here](./docs/troubleshooting-nvblox-realsense.md). +* **semantic_label_conversion**: Package for converting semantic labels coming from Isaac Sim to mask images used by nvblox ([readme](./nvblox_examples/semantic_label_conversion/README.md)). +* **nvblox_msgs**: Custom messages for transmitting the output distance map slice and mesh over ROS 2. +* **nvblox_nav2**: Contains a custom plugin that allows ROS 2 Nav2 to consume nvblox distance map outputs. +* **nvblox_performance_measurement**: Multiple packages containing tools for measuring nvblox performance ([readme](./nvblox_performance_measurement/README.md)). +* **nvblox_ros**: The ROS 2 wrapper for the core reconstruction library and the nvblox node. +* **nvblox_ros_common**: Package providing repository wide utility functions. +* **nvblox_rviz_plugin**: A plugin for displaying nvblox's (custom) mesh type in RVIZ. +* **\[submodule\] nvblox**: The core (ROS independent) reconstruction library. + + +## ROS 2 Parameters + +Find all available ROS 2 parameters [here](./docs/parameters.md). + +## ROS 2 Topics and Services + +Find all ROS 2 subscribers, publishers and services [here](./docs/topics-and-services.md). ## Troubleshooting @@ -224,15 +193,25 @@ ros2 launch nvblox_nav2 carter_sim.launch.py For solutions to problems with Isaac ROS, please check [here](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_common/blob/main/docs/troubleshooting.md). -### `realsense-ros` packages don't build with ROS2 Humble +### *realsense-ros* packages don't build with ROS 2 Humble Please follow the workaround [here](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_common/blob/main/docs/troubleshooting.md#realsense-driver-doesnt-work-with-ros2-humble). +### Troubleshooting the Nvblox Realsense Example + +See our troubleshooting page [here](./docs/troubleshooting-nvblox-realsense.md). + +### Troubleshooting ROS 2 communication issues + +If it looks like you are dropping messages or you are not receiving any messages, please consult our troubleshooting page [here](./docs/troubleshooting-nvblox-ros2.md). + ## Updates -| Date | Changes | -| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| 2022-10-19 | Updated OSS licensing | -| 2022-08-31 | Update to be compatible with JetPack 5.0.2. Serialization of nvblox maps to file. Support for 3D LIDAR input and performace improvements. | -| 2022-06-30 | Support for ROS2 Humble and miscellaneous bug fixes. | -| 2022-03-21 | Initial version. | +| Date | Changes | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| 2023-04-05 | Human reconstruction and new weighting functions. | +| 2022-12-10 | Updated documentation. | +| 2022-10-19 | Updated OSS licensing | +| 2022-08-31 | Update to be compatible with JetPack 5.0.2. Serialization of nvblox maps to file. Support for 3D LIDAR input and performance improvements. | +| 2022-06-30 | Support for ROS 2 Humble and miscellaneous bug fixes. | +| 2022-03-21 | Initial version. | diff --git a/ci/Jenkinsfile b/ci/Jenkinsfile deleted file mode 100644 index 8c5e356d..00000000 --- a/ci/Jenkinsfile +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. -// -// NVIDIA CORPORATION and its licensors retain all intellectual property -// and proprietary rights in and to this software, related documentation -// and any modifications thereto. Any use, reproduction, disclosure or -// distribution of this software and related documentation without an express -// license agreement from NVIDIA CORPORATION is strictly prohibited. -@Library('ci-groovkins-lib@9729a1fa623eedf0de3f13be8b3bd484adfdec54') - -import com.nvidia.isaac.ci.utils.WorkflowScriptUtil -import com.nvidia.isaac.ci.isaac_ros.dto.CredentialsConfig -import com.nvidia.isaac.ci.isaac_ros.VarsDef - -import com.nvidia.isaac.ci.isaac_ros.IsaacRosBuildPipeline - -CredentialsConfig credentials_config = CredentialsConfig.from_default() -WorkflowScriptUtil utils = new WorkflowScriptUtil(this, credentials_config) -IsaacRosBuildPipeline pipeline_helper = new IsaacRosBuildPipeline(this, credentials_config, utils) - -pipeline { - - agent { - docker { - label 'isaac-gpu' - image 'nvcr.io/nvstaging/isaac-amr/nvblox_ros_base:latest' - args '-u root -e NVIDIA_VISIBLE_DEVICES=all -e NVIDIA_DRIVER_CAPABILITIES=all' - reuseNode true - } - } - - options { - gitLabConnection('gitlab-master') - ansiColor('xterm') - timestamps() - timeout(time: 2, unit: 'HOURS') - buildDiscarder(logRotator(numToKeepStr: '60', daysToKeepStr: '30')) - } - - triggers { - cron(env.BRANCH_NAME == 'envoy-dev' ? 'H 4 * * *' : '') - gitlab( - triggerOnPush: true, - triggerOnMergeRequest: true, - triggerOpenMergeRequestOnPush: 'never', - triggerOnNoteRequest: true, - noteRegex: 'Jenkins please retry a build', - skipWorkInProgressMergeRequest: true, - ciSkip: false, - setBuildDescription: true, - addNoteOnMergeRequest: true, - addCiMessage: true, - addVoteOnMergeRequest: true, - acceptMergeRequestOnSuccess: false, - branchFilterType: 'All', - pendingBuildName: 'Jenkins', - cancelPendingBuildsOnUpdate: true, - ) - } - - stages { - - stage('build') { - steps { - updateGitlabCommitStatus name: 'build', state: 'running' - script { - utils.run_with_ssh_key("secrets/account/ssh-key", - "/root/.ssh/id_rsa") { - utils.prepare_ssh(VarsDef.SSH_FOLDER) - - sh''' - cd ${WORKSPACE} - git config --global --add safe.directory ${WORKSPACE} - git submodule deinit --all -f - git submodule update --init --recursive - ''' - - sh ''' - mkdir -p /ros/src - ln -sT ${WORKSPACE} /ros/src/isaac_ros_nvblox - ''' - - sh '''#!/bin/bash - source /opt/ros/foxy/setup.bash - cd /ros/ - colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release - ''' - - sh '''#!/bin/bash - source /opt/ros/foxy/setup.bash - cd /ros/ - colcon test --event-handlers console_direct+ - ''' - - sh '''#!/bin/bash - source /opt/ros/foxy/setup.bash && \ - cd /ros/ && \ - ./src/isaac_ros_nvblox/ci/copy_results.sh - ''' - } - } - } - } - - } - - post { - always { - - archiveArtifacts ( - artifacts: 'test_results/*.xml', - fingerprint: true - ) - xunit ( - testTimeMargin: '3000', - thresholdMode: 1, - thresholds: [ - skipped(failureThreshold: '0'), - failed(failureThreshold: '16') - ], - tools: [JUnit( - pattern: 'test_results/*.xunit.xml', - deleteOutputFiles: true, - failIfNotNew: false, - skipNoTestFiles: true, - stopProcessingIfError: true - )] - ) - junit( - allowEmptyResults: true, - testResults: '**/test-reports/*.xml' - ) - utils.prepare_cleanup() - cleanWs disableDeferredWipeout: true, deleteDirs: true - } - failure { - updateGitlabCommitStatus name: 'build', state: 'failed' - } - success { - updateGitlabCommitStatus name: 'build', state: 'success' - } - } - -} diff --git a/ci/copy_results.sh b/ci/copy_results.sh deleted file mode 100755 index 64469e10..00000000 --- a/ci/copy_results.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -mkdir -p ${WORKSPACE}/test_results -colcon test-result --all --result-files-only | xargs -I '{}' cp '{}' -t ${WORKSPACE}/test_results diff --git a/ci/nvblox_nightly.jenkinsfile b/ci/nvblox_nightly.jenkinsfile deleted file mode 100644 index b55295ea..00000000 --- a/ci/nvblox_nightly.jenkinsfile +++ /dev/null @@ -1,147 +0,0 @@ -// ISAAC ROS NVBlox nightly pipeline -// -// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. -// -// NVIDIA CORPORATION and its licensors retain all intellectual property -// and proprietary rights in and to this software, related documentation -// and any modifications thereto. Any use, reproduction, disclosure or -// distribution of this software and related documentation without an express -// license agreement from NVIDIA CORPORATION is strictly prohibited. - -// See: https://gitlab-master.nvidia.com/robotics/isaac-pipeline-library -@Library('ci-groovkins-lib@d3b3733a3ad1de47cfa77ad5d04970ab403da38a') - -import com.nvidia.isaac.ci.utils.WorkflowScriptUtil -import com.nvidia.isaac.ci.isaac_ros.dto.CredentialsConfig - -import com.nvidia.isaac.ci.isaac_ros.IsaacRosBuildPipeline -import com.nvidia.isaac.ci.isaac_ros.VarsDef -import com.nvidia.isaac.ci.utils.Docker -import com.nvidia.isaac.ci.utils.dto.BaseCredentialsConfig - -Docker docker_utils = new Docker(this, - BaseCredentialsConfig.from_default()) - - -CredentialsConfig credentials_config = CredentialsConfig.from_default() -WorkflowScriptUtil utils = new WorkflowScriptUtil(this, credentials_config) - -pipeline { - agent { - label 'isaac && builder' - } - triggers { - // Run at around 1am UTC. - cron('0 1 * * *') - } - environment { - NGC_REGISTRY_CREDENTIALS = 'vault-ngc' - } - options { - ansiColor('xterm') - timestamps() - timeout(time: 2, unit: 'HOURS') - buildDiscarder(logRotator(numToKeepStr: '60', daysToKeepStr: '30')) - skipDefaultCheckout(true) - } - parameters { - string( - name: 'BRANCH_TO_BUILD', - description: 'The branch name or commit in ISAAC ROS NVBLOX repository to build.', - defaultValue: 'envoy-dev', - trim: true - ) - } - - stages { - stage('Clone') { - steps { - cleanWs disableDeferredWipeout: true, deleteDirs: true - script { - if (!params.BRANCH_TO_BUILD) { - error('BRANCH_TO_BUILD pipeline parameter is required, none provided.') - } - - sh ''' - mkdir -p src - ''' - utils.run_with_ssh_key("secrets/account/ssh-key", - "${HOME}/.ssh/id_rsa") { - utils.prepare_ssh("${HOME}/.ssh") - def scmUrl = scm.getUserRemoteConfigs()[0].getUrl() - sh """ - echo Building branch ${params.BRANCH_TO_BUILD} - """ - dir('src/isaac_ros_nvblox') { - checkout([$class: 'GitSCM', branches: [[name: params.BRANCH_TO_BUILD]], extensions: [[$class: 'GitLFSPull'], [$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true, recursiveSubmodules: true, reference: '', trackingSubmodules: false]], userRemoteConfigs: [[url: 'ssh://git@gitlab-master.nvidia.com:12051/isaac_ros/isaac_ros_nvblox.git']]]) - } - - dir ('src/nvblox_tools') { - checkout([$class: 'GitSCM', branches: [[name: 'main']], extensions: [[$class: 'GitLFSPull'], [$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true, recursiveSubmodules: true, reference: '', trackingSubmodules: false]], userRemoteConfigs: [[url: 'ssh://git@gitlab-master.nvidia.com:12051/nvblox/nvblox_tools.git']]]) - } - - sh """ - cp src/nvblox_tools/docker_utils/.dockerignore src/ - """ - } - } - } - } - stage ('Build base docker image') { - steps { - dir('src') { - script { - docker.withRegistry('https://nvcr.io', NGC_REGISTRY_CREDENTIALS) { - def baseImage = docker.build("nvcr.io/nvstaging/isaac-amr/nvblox_ros_base:latest", - "-f ./nvblox_tools/docker_utils/Dockerfile.ros_base .") - baseImage.push() - sh "docker rmi -f ${baseImage.imageName()}" - } - } - } - } - } - stage ('Build isaacsim image') { - steps { - dir ('src') { - script { - docker.withRegistry('https://nvcr.io', NGC_REGISTRY_CREDENTIALS) { - def isaacsimImage = docker.build("nvcr.io/nvstaging/isaac-amr/nvblox_isaacsim:latest", - "-f ./nvblox_tools/docker_utils/Dockerfile.nvblox_isaacsim .") - isaacsimImage.push() - sh "docker rmi -f ${isaacsimImage.imageName()}" - } - } - } - } - } - stage ('Build nvblox image') { - steps { - dir ('src') { - script { - docker.withRegistry('https://nvcr.io', NGC_REGISTRY_CREDENTIALS) { - def nvbloxImage = docker.build("nvcr.io/nvstaging/isaac-amr/nvblox_isaac_ros:latest", - "-f ./nvblox_tools/docker_utils/Dockerfile.nvblox_ros .") - nvbloxImage.push() - sh "docker rmi -f ${nvbloxImage.imageName()}" - } - } - } - } - } - - - } - post { - always { - script { - utils.prepare_cleanup() - } - } - cleanup { - script { - cleanWs disableDeferredWipeout: true, deleteDirs: true - } - } - } -} diff --git a/docs/linux-people-isaac-sim-workaround.md b/docs/linux-people-isaac-sim-workaround.md new file mode 100644 index 00000000..817ae54b --- /dev/null +++ b/docs/linux-people-isaac-sim-workaround.md @@ -0,0 +1,11 @@ +# Workaround for running People Animation on Linux + +Currently, python scripts for animation can only run if the usd is on the same filesystem as the script. This means that it is required to upload the scripts next to where the usd is stored: + +1. To retrieve where the scripts are stored, click Window -> Extensions and search for `omni.anim.people`. Then in the extension details click on the folder icon. This will open a window in your browser showing where the extension is stored. + +
+ +2. You now need to upload the `scripts` folder from the `omni/anim/people` folder into a `people_anim_scripts` folder next to the scene usd. To do so, you can right-click on the *Content* tab on the bottom then select *Upload files and folders* to upload the scripts folder. + +3. This step will need to be done each time a new version of the extension is released. diff --git a/docs/parameters.md b/docs/parameters.md new file mode 100644 index 00000000..3086ab1c --- /dev/null +++ b/docs/parameters.md @@ -0,0 +1,110 @@ +# Parameters + +This page contains all parameters exposed to ROS 2. +For the provided examples in the `nvblox_example_bringup` package, parameters +are set using YAML configuration files under `nvblox_examples/nvblox_examples_bringup/config`. + +The base parameters are set in `nvblox_base.yaml` and there exist multiple specialization files that are overwriting parameters from the base file depending on which example is launched. + +File tree: +```bash +config +├── nvblox +    ├── nvblox_base.yaml +    └── specializations +    ├── nvblox_humans.yaml +    ├── nvblox_isaac_sim.yaml +    └── nvblox_realsense.yaml +``` + +Loaded specializations for each example: +| Launch file | Specializations | +| ---------------------------------- | --------------------------------------------- | +| isaac_sim_example.launch.py | `nvblox_isaac_sim.yaml` | +| isaac_sim_humans_example.launch.py | `nvblox_isaac_sim.yaml`, `nvblox_humans.yaml` | +| realsense_example.launch.py | `nvblox_realsense.yaml` | +| realsense_humans_example.launch.py | `nvblox_realsense.yaml`,`nvblox_humans.yaml` | + +## General Parameters + +| ROS Parameter | Type | Default | Description | +| ----------------------------------------- | -------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `global_frame` | `string` | `odom` | The name of the TF frame to be used as the global frame. For the realsense examples, this parameter is exposed as a launch argument. | +| `voxel_size` | `float` | `0.05` | Voxel size (in meters) to use for the map. | +| `use_tf_transforms` | `bool` | `true` | Whether to use TF transforms at all. If set to false, `use_topic_transforms` must be true and `pose_frame` *needs* to be set. | +| `use_topic_transforms` | `bool` | `false` | Whether to listen to topics for transforms. If set to true, will try to get `global_frame` to `pose_frame` transform from the topics. If set to false, everything will be resolved through TF. | +| `compute_esdf` | `bool` | `true` | Whether to compute the ESDF (map of distances to the nearest obstacle). | +| `esdf_update_rate_hz` | `float` | `2.0` | The rate (in Hz) at which to update the ESDF and output the distance slice. | +| `esdf_2d` | `bool` | `true` | Whether to compute the ESDF in 2D (true) or 3D (false). | +| `esdf_distance_slice` | `bool` | `true` | Whether to output a distance slice of the ESDF to be used for path planning. | +| `esdf_slice_height` | `float` | `1.0` | The *output* slice height for the distance slice and ESDF pointcloud. Does not need to be within min and max height below. In units of meters. | +| `esdf_2d_min_height` | `float` | `0.0` | The minimum height, in meters, to consider obstacles part of the 2D ESDF slice. | +| `esdf_2d_max_height` | `float` | `1.0` | The maximum height, in meters, to consider obstacles part of the 2D ESDF slice. | +| `compute_mesh` | `bool` | `true` | Whether to output a mesh for visualization in rviz, to be used with `nvblox_rviz_plugin`. | +| `mesh_update_rate_hz` | `float` | `5.0` | The rate (in Hz) at which to update and publish the mesh. | +| `use_color` | `bool` | `true` | Whether to integrate color images to color the mesh. | +| `max_color_update_hz` | `float` | `5.0` | The maximum rate (in Hz) at which to integrate color images into the color layer. A value of 0.0 means there is no cap. | +| `use_depth` | `bool` | `true` | Whether to integrate depth images. | +| `max_depth_update_hz` | `float` | `10.0` | The maximum rate (in Hz) at which to integrate depth images. A value of 0.0 means there is no cap. | +| `use_lidar` | `bool` | `true` | Whether to integrate LiDAR scans. | +| `max_lidar_update_hz` | `float` | `10.0` | The maximum rate (in Hz) at which to integrate LiDAR scans. A value of 0.0 means there is no cap. | +| `lidar_width` | `int` | `1800` | Width of the LIDAR scan, in number of beams. Defaults are for the Velodyne VLP16. | +| `lidar_height` | `int` | `16` | Height of the LIDAR scan, in number of beams. Defaults are for the VLP16. | +| `lidar_vertical_fov_rad` | `float` | `30 degrees (in radians)` | The vertical field of view of the LIDAR scan, in radians. Horizontal FoV is assumed to be 360 degrees. This is used to calculate the individual beam angle offsets. | +| `use_static_occupancy_layer` | `float` | `false` | Whether to use the static occupancy layer for projective integration. If this flag is set to false (default), TSDF integration is used. | +| `occupancy_publication_rate_hz` | `float` | `2.0` | The rate (in Hz) at which to publish the static occupancy pointcloud. | +| `max_poll_rate_hz` | `float` | `100.0` | Specifies what rate to poll the color & depth updates at. Will exit as no-op if no new images. Set this higher than you expect images to come in at. | +| `maximum_sensor_message_queue_length` | `int` | `30` | How many messages to store in the sensor messages queues (depth, color, lidar) before deleting oldest messages. | +| `map_clearing_radius_m` | `float` | `-1.0` | Radius around the `map_clearing_frame_id` outside which we clear the map. Note that values <= 0.0 indicate that no clearing is performed. | +| `map_clearing_frame_id` | `string` | `base_link` | The name of the TF frame around which we clear the map. | +| `clear_outside_radius_rate_hz` | `float` | `1.0` | The rate (in Hz) at wich we clear the map outside of the `map_clearing_radius_m`. | +| `depth_qos` | `string` | `SYSTEM_DEFAULT` | ROS 2 QoS string for the depth subscription. | +| `color_qos` | `string` | `SYSTEM_DEFAULT` | ROS 2 QoS string for the color subscription. | +| `pose_frame` | `float` | `base_link` | Only used if `use_topic_transforms` is set to true. Pose and transform messages will be interpreted as being in this pose frame, and the remaining transform to the sensor frame will be looked up on the TF tree. | +| `slice_visualization_attachment_frame_id` | `string` | `base_link` | Frame to which the map slice bounds visualization is centered on the xy-plane. | +| `slice_visualization_side_length` | `float` | `10.0` | Side length of the map slice bounds visualization plane. | + +# Mapper Parameters + +The nvblox node holds a mapper object that takes care of updating all the layers. +Depending on the `use_static_occupancy_layer` parameter above, it builds either a tsdf or occupancy probability grid. +Below is a list of all the parameters to set up this mapper. + +| ROS Parameter | Type | Default | Description | +| --------------------------------------------------------------- | -------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `mapper.projective_integrator_max_integration_distance_m` | `float` | `7.0` | The maximum distance, in meters, to integrate the TSDF or occupancy map up to for depth images. | +| `mapper.projective_integrator_truncation_distance_vox` | `float` | `4.0` | The truncation distance, in units of voxels, for the TSDF or occupancy map. | +| `mapper.lidar_projective_integrator_max_integration_distance_m` | `float` | `10.0` | The maximum distance, in meters, to integrate the TSDF or occupancy map up to for LiDAR scans. | +| `mapper.weighting_mode` | `string` | `inverse_square` | The weighting mode, applied to TSDF and color integrations. Options: [`constant`, `constant_dropoff`, `inverse_square`, `inverse_square_dropoff`]. | +| `mapper.tsdf_integrator_max_weight` | `float` | `100.0` | Maximum weight for the TSDF. Setting this number higher will lead to higher-quality reconstructions but worse performance in dynamic scenes. | +| `mapper.free_region_occupancy_probability` | `float` | `0.3` | The inverse sensor model occupancy probability for voxels observed as free space. | +| `mapper.occupied_region_occupancy_probability` | `float` | `0.7` | The inverse sensor model occupancy probability for voxels observed as occupied. | +| `mapper.unobserved_region_occupancy_probability` | `float` | `0.5` | The inverse sensor model occupancy probability for unobserved voxels. | +| `mapper.occupied_region_half_width_m` | `float` | `0.1` | Half the width of the region which is considered as occupied. | +| `mapper.esdf_integrator_min_weight` | `float` | `1e-4` | Minimum weight of the TSDF to consider for inclusion in the ESDF. | +| `mapper.esdf_integrator_max_distance_m` | `float` | `2.0` | Maximum distance to compute the ESDF up to, in meters. | +| `mapper.esdf_integrator_max_site_distance_vox` | `float` | `1.0` | Maximum distance to consider a voxel within a surface for the ESDF calculation. | +| `mapper.mesh_integrator_min_weight` | `float` | `1e-4` | Minimum weight of the TSDF to consider for inclusion in the mesh. | +| `mapper.mesh_integrator_weld_vertices` | `bool` | `true` | Whether to weld identical vertices together in the mesh. | +| `mapper.color_integrator_max_integration_distance_m` | `float` | `7.0` | Maximum distance, in meters, to integrate the color up to. | + +# Human Reconstruction Parameters + +In case of the `nvblox_human_node`, there are two individual mappers (contained in a multi-mapper object) taking care of updating the layers. +The first mapper is equivalent to the mapper of the `nvblox_node` explained above and is also initialized with the same parameters. + +The second mapper handles dynamic reconstruction of humans in an occupancy layer. +Using the human occupancy layer an esdf layer and costmap are generated. +The parameters of the human mapper for occupancy and esdf integration are duplicates of the parameters for the static mapper. +Only the parent parameter name needs to be changed to `human_mapper` (i.e. `mapper.esdf_integrator_min_weight` -> `human_mapper.esdf_integrator_min_weight`). + +Below you find additional parameters that only exist for the 'nvblox_human_node'. + +| ROS Parameter | Type | Default | Description | +| ------------------------------------------------ | ------- | ------- | ------------------------------------------------------------------------------------------------------------------ | +| `human_esdf_update_rate_hz` | `float` | `10.0` | The rate (in Hz) at which to update the human ESDF, output the distance slice and additional human visualizations. | +| `human_occupancy_decay_rate_hz` | `float` | `10.0` | The rate (in Hz) at which to decay the human occupancy layer. | +| `human_mapper.free_region_decay_probability` | `float` | `0.55` | The decay probability that is applied to the free region on decay. Must be in `[0.5, 1.0]`. | +| `human_mapper.occupied_region_decay_probability` | `float` | `0.4` | The decay probability that is applied to the occupied region on decay. Must be in `[0.0, 0.5]`. | + +> **Note**: Decay of the occupancy layer is needed to handle dynamic objects like humans. Without the decay observed dynamic objects stay in the map even if not seen for a long time period. In that time the object might have moved and the map is therefore invalid. Using the decay we simulate the loss of knowledge about parts of the dynamic map currently not observed over time. diff --git a/docs/technical-details.md b/docs/technical-details.md index 942bbc55..09c6dc97 100644 --- a/docs/technical-details.md +++ b/docs/technical-details.md @@ -3,3 +3,17 @@ Nvblox builds the reconstructed map in the form of a Truncated Signed Distance Function (TSDF) stored in a 3D voxel grid. This approach is similar to 3D occupancy grid mapping approaches in which occupancy probabilities are stored at each voxel. However, TSDF-based approaches like nvblox store the (signed) distance to the closest surface at each voxel. The surface of the environment can then be extracted as the zero-level set of this voxelized function. Typically, TSDF-based reconstructions provide higher quality surface reconstructions. In addition to their use in reconstruction, distance fields are also useful for path planning, because they provide an immediate means of checking whether potential future robot positions are in collision. The dual utility of distance functions for both reconstruction and planning motivates their use in nvblox (a reconstruction library for path planning). + +
+ +The diagram above indicates data and processes in nvblox. In the default configuration nvblox builds TSDF, color, mesh, and ESDF *layers*. Each *layer* is a independent, but aligned and co-located, voxel grid containing data of the appropriate type. For example, voxels on the TSDF layer store distance and weight data, while the color layer voxels store color values. + +# Human Reconstruction + +Additionally to the static TSDF mapping we also provide a human reconstruction and dynamic mapping utility using occupancy grid mapping. + +Human segmentation is applied to each processed color frame with [*Isaac ROS Image Segmentation*](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_image_segmentation). The depth masker module uses the segmentation mask from the color image to separate the depth-map into human and non-human parts. While the non-human labelled part of the depth frame is still forwarded to TSDF mapping as explained above, the human labelled part is processed to an occupancy grid map. + +To relax the assumption that occupancy grid maps only capture static objects we apply an occupancy decay step. At a fixed frequency, all voxel occupancy probabilities are decayed towards `0.5` over time. This means that the state of the map (occupied or free) becomes less certain after it has fallen out of the field of view, until it becomes unknown (`0.5` occupancy probability). + +
diff --git a/docs/topics-and-services.md b/docs/topics-and-services.md new file mode 100644 index 00000000..0d2fe1b5 --- /dev/null +++ b/docs/topics-and-services.md @@ -0,0 +1,65 @@ + +# ROS Topics and Services + +Find all topics and services of the `nvblox_node` and the `nvblox_human_node` below. + +> **Note**: The examples in the [Isaac Sim](./tutorial-human-reconstruction-isaac-sim.md) and [realsense](./tutorial-human-reconstruction-realsense.md) human reconstruction tutorials launch the `nvblox_human_node`, while the standard [Isaac Sim](tutorial-isaac-sim.md) and [realsense](tutorial-realsense.md) tutorials launch the `nvblox_node`. The `nvblox_human_node` inherits from the `nvblox_node` and extends its capabilities. + +## ROS Topics Subscribed + +| ROS Topic | Interface | Description | +|---------------------|-----------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `pointcloud` | [sensor_msgs/PointCloud2](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/PointCloud2.msg) | Input 3D LIDAR pointcloud. Make sure to set the `lidar_height`, `lidar_width`, and `lidar_vertical_fov_rad` parameters if using this input, as it uses those to convert the pointcloud into a depth image. | +| `color/image` | [sensor_msgs/Image](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/Image.msg) | Optional input color image to be integrated. Must be paired with a `camera_info` message below. Only used to color the mesh. | +| `color/camera_info` | [sensor_msgs/CameraInfo](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/CameraInfo.msg) | Optional topic along with the color image above. Contains intrinsics of the color camera. | +| `depth/camera_info` | [sensor_msgs/CameraInfo](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/CameraInfo.msg) | Required topic along with the depth image. Contains intrinsic calibration parameters of the depth camera. | +| `depth/image` | [sensor_msgs/Image](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/Image.msg) | The input depth image to be integrated. Must be paired with a `camera_info` message below. Supports both floating-point (depth in meters) and `uint16` (depth in millimeters, OpenNI format). | +| `transform` | [geometry_message/TransformStamped](https://github.com/ros2/common_interfaces/blob/humble/geometry_msgs/msg/TransformStamped.msg) | Odometry as stamped transform messages. Not required if `use_tf_transforms` is set to true. | +| `pose` | [geometry_message/PoseStamped](https://github.com/ros2/common_interfaces/blob/humble/geometry_msgs/msg/PoseStamped.msg) | Odometry as stamped pose messages. Not required if `use_tf_transforms` is set to true. | + +Additionally subscribed topics by the `nvblox_human_node`: + +| ROS Topic | Interface | Description | +|--------------------|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `mask/image` | [sensor_msgs/Image](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/Image.msg) | The mask image with the human segmentation (non-human pixels = 0 and human pixels = 1). In the [nvblox human examples](./tutorial-human-reconstruction.md) this is published by [*Isaac ROS Image Segmentation*](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_image_segmentation). | +| `mask/camera_info` | [sensor_msgs/CameraInfo](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/CameraInfo.msg) | Required topic along with the mask image. Contains intrinsic calibration parameters of the mask camera. | + +## ROS Topics Published + +| ROS Topic | Interface | Description | +|----------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `~/mesh` | [nvblox_msgs/Mesh](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/msg/Mesh.msg) | A visualization topic showing the mesh produced from the TSDF in a form that can be seen in RViz using `nvblox_rviz_plugin`. Set ``mesh_update_rate_hz`` to control its update rate. | +| `~/esdf_pointcloud` | [sensor_msgs/PointCloud2](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/PointCloud2.msg) | A pointcloud of the static 2D ESDF (Euclidean Signed Distance Field), with intensity as the metric distance to the nearest obstacle. Set ``esdf_update_rate_hz`` to control its update rate. | +| `~/occupancy` | [sensor_msgs/PointCloud2](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/PointCloud2.msg) | A pointcloud of the occupancy map (only voxels with occupation ``probability > 0.5``). Set ``occupancy_publication_rate_hz`` to control its publication rate. | +| `~/map_slice` | [nvblox_msgs/DistanceMapSlice](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/msg/DistanceMapSlice.msg) | A 2D slice of the static ESDF, to be consumed by `nvblox_nav2` package for interfacing with Nav2. Set ``esdf_update_rate_hz`` to control its update rate. | +| `~/map_slice_bounds` | [visualization_msgs/Marker](https://github.com/ros2/common_interfaces/blob/humble/visualization_msgs/msg/Marker.msg) | A visualization topic showing the mesh slice bounds that can be set with the parameters `esdf_2d_min_height` and `esdf_2d_min_height`. | +| `~/mesh_marker` | [visualization_msgs/Marker](https://github.com/ros2/common_interfaces/blob/humble/visualization_msgs/msg/Marker.msg) | A visualization topic showing the mesh using a marker message. | + +Additionally published topics by the `nvblox_human_node`: +| ROS Topic | Interface | Description | +|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `~/human_pointcloud` | [sensor_msgs/PointCloud2](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/PointCloud2.msg) | Pointcloud visualizing the back-projected pixels of the latest human masked depth frame (without temporal fusion). | +| `~/human_voxels` | [visualization_msgs/Marker](https://github.com/ros2/common_interfaces/blob/humble/visualization_msgs/msg/Marker.msg) | The pointcloud published at `~/human_pointcloud` mapped to the corresponding voxels. | +| `~/human_occupancy` | [sensor_msgs/PointCloud2](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/PointCloud2.msg) | A pointcloud of the human occupancy map (only voxels with occupation ``probability > 0.5``). | +| `~/human_esdf_pointcloud` | [sensor_msgs/PointCloud2](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/PointCloud2.msg) | A pointcloud of the human 2D ESDF (Euclidean Signed Distance Field), with intensity as the metric distance to the nearest human. Set ``human_esdf_update_rate_hz`` to control its update rate. | +| `~/combined_esdf_pointcloud` | [sensor_msgs/PointCloud2](https://github.com/ros2/common_interfaces/blob/humble/sensor_msgs/msg/PointCloud2.msg) | A pointcloud of the combined static and human 2D ESDF (minimal distance of both), with intensity as the metric distance to the nearest obstacle or human. Set ``human_esdf_update_rate_hz`` to control its update rate. | +| `~/human_map_slice` | [nvblox_msgs/DistanceMapSlice](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/msg/DistanceMapSlice.msg) | A 2D slice of the human ESDF, to be consumed by `nvblox_nav2` package for interfacing with Nav2. Set ``human_esdf_update_rate_hz`` to control its update rate. | +| `~/combined_map_slice` | [nvblox_msgs/DistanceMapSlice](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/msg/DistanceMapSlice.msg) | A 2D slice of the combined static and human ESDF (minimal distance of both), to be consumed by `nvblox_nav2` package for interfacing with Nav2. Set ``human_esdf_update_rate_hz`` to control its update rate. | +| `~/depth_frame_overlay` | [sensor_msgs/Image](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/Image.msg) | Debug image showing the mask overlaid on the depth image. | +| `~/color_frame_overlay` | [sensor_msgs/Image](https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/msg/Image.msg) | Debug image showing the mask overlaid on the color image. | + + +## ROS Services Advertised + +| ROS Service | Interface | Description | +|--------------|---------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------| +| `~/save_ply` | [nvblox_msgs/FilePath](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/srv/FilePath.srv) | Will save the mesh as the PLY (standard polygon file format, which can be viewed with MeshLab or CloudCompare) at the specified location. | +| `~/save_map` | [nvblox_msgs/FilePath](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/srv/FilePath.srv) | Will serialize the entire map, including TSDF, ESDF, etc., at the given location. | +| `~/load_map` | [nvblox_msgs/FilePath](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox/blob/main/nvblox_msgs/srv/FilePath.srv) | Will overwrite the current map in the node with a map loaded from the given path. | + +Example service calls from the command line: +```bash +ros2 service call /nvblox_node/save_ply nvblox_msgs/srv/FilePath "{file_path: '/home/USERNAME/super_cool_map.ply'}" +ros2 service call /nvblox_node/save_map nvblox_msgs/srv/FilePath "{file_path: '/home/USERNAME/super_cool_map.nvblx'}" +ros2 service call /nvblox_node/load_map nvblox_msgs/srv/FilePath "{file_path: '/home/USERNAME/super_cool_map.nvblx'}" +``` diff --git a/docs/troubleshooting-nvblox-vslam-realsense.md b/docs/troubleshooting-nvblox-realsense.md similarity index 87% rename from docs/troubleshooting-nvblox-vslam-realsense.md rename to docs/troubleshooting-nvblox-realsense.md index 32b859c0..2398b69a 100644 --- a/docs/troubleshooting-nvblox-vslam-realsense.md +++ b/docs/troubleshooting-nvblox-realsense.md @@ -1,12 +1,12 @@ # Troubleshoot for RealSense-based Reconstruction Example -This page contains troubleshooting for the nvblox-VSLAM-realsense example found [here](tutorial-nvblox-vslam-realsense.md). The example combines nvblox-based reconstruction with VSLAM. Both of these processes, nvblox and VSLAM, are driven by the realsense depth camera. +This page contains troubleshooting for the nvblox-VSLAM-realsense example found [here](tutorial-realsense.md). The example combines nvblox-based reconstruction with VSLAM. Both of these processes, nvblox and VSLAM, are driven by the realsense depth camera. The setup for the example is shown below.
-Images (2 x IR images and 1 x Depth image) are published at 60Hz from the realsense ROS2 node. At the time of publishing we are using the `ros2-development` branch of [realsense-ros](https://github.com/IntelRealSense/realsense-ros/tree/ros2-development). This is the branch used by the ROS2 apt packages (viewable on the [ros package index](https://index.ros.org/p/realsense2_camera/github-IntelRealSense-realsense-ros/#humble)). +Images (2 x IR images and 1 x Depth image) are published at 60Hz from the realsense ROS 2 node. At the time of publishing we are using the `ros2-development` branch of [realsense-ros](https://github.com/IntelRealSense/realsense-ros/tree/ros2-development). This is the branch used by the ROS 2 apt packages (viewable on the [ros package index](https://index.ros.org/p/realsense2_camera/github-IntelRealSense-realsense-ros/#humble)). > **Why do we include the realsense splitter?**: The depth output from the realsense is significantly improved when using its inbuilt projector which increases the amount of texture in the scene. However, VSLAM performance is disrupted by the projector. We, therefore, use the realsense's toggling feature such that the projector is switched on and off in interleaved frames (on, off, on, off, on, off,...etc.). The splitter splits this interleaved image stream into two channels, one with the projector on and one with the projector off. IR images with the projector off are passed to VSLAM, while depth images with the projector on are passed to nvblox. This splitting occurs based on the metadata associated with each frame, published by the realsense, which contains a field which states if the projector was on at the time of frame capture. @@ -21,7 +21,7 @@ No reconstruction appears in RVIZ, and the terminal reports: [nvblox_node-3] at line 93 in /opt/ros/humble/src/geometry2/tf2/src/buffer_core.cpp ``` -**Explanation:** This message is repeating because nvblox can't get the camera pose in the odom frame. It can't get the pose because VSLAM is not publishing it. VSLAM is not publishing anything because (likely) it is not receiving input data. +**Explanation**: This message is repeating because nvblox can't get the camera pose in the odom frame. It can't get the pose because VSLAM is not publishing it. VSLAM is not publishing anything because (likely) it is not receiving input data. So the issue here is that VSLAM is not getting data. This is caused either by one of: 1) Images not coming off the camera into Linux. @@ -66,7 +66,7 @@ You're here because your realsense-viewer is operating as expected, i.e. is show Start the example: ```bash -ros2 launch nvblox_examples_bringup nvblox_vslam_realsense.launch.py +ros2 launch nvblox_examples_bringup realsense_example.launch.py ``` In rviz check that the following images can be viewed. @@ -110,16 +110,16 @@ On a supported kernel version, the apt package patches the kernel when you insta If everything is as expected on a supported kernel and you run ```bash -helen@holeynikova-dt:~/workspaces/isaac_ros-dev/src/isaac_ros_common (dev)$ uname -r +USERNAME@HOSTNAME:~/workspaces/isaac_ros-dev/src/isaac_ros_common (dev)$ uname -r 5.8.0-43-generic -helen@holeynikova-dt:~/workspaces/isaac_ros-dev/src/isaac_ros_common (dev)$ dkms status +USERNAME@HOSTNAME:~/workspaces/isaac_ros-dev/src/isaac_ros_common (dev)$ dkms status librealsense2-dkms, 1.3.18, 5.8.0-43-generic, x86_64: installed librealsense2-dkms, 1.3.18, 5.8.0-67-generic, x86_64: installed nvidia, 510.85.02, 5.13.0-40-generic, x86_64: installed nvidia, 510.85.02, 5.8.0-43-generic, x86_64: installed -helen@holeynikova-dt:/lib/udev/rules.d$ modinfo uvcvideo | grep "version:" +USERNAME@HOSTNAME:/lib/udev/rules.d$ modinfo uvcvideo | grep "version:" version: 1.1.2.realsense-1.3.18 srcversion: 964E8D3335D17053B8EEDD2 ``` @@ -131,11 +131,11 @@ The important thing to see here is that the word "realsense" appears: Likely, if you're here, you have a recent kernel and the words realsense do not appear, or appear in the second command next to an old kernel version. Something like: ```bash -shubham@shubham-linuxbox:~$ uname -r +USERNAME@HOSTNAME:~$ uname -r 5.15.0-48-generic -shubham@shubham-linuxbox:~$ dkms status +USERNAME@HOSTNAME:~$ dkms status nvidia, 510.85.02, 5.15.0-48-generic, x86_64: installed -shubham@shubham-linuxbox:~$ modinfo uvcvideo | grep "version:" +USERNAME@HOSTNAME:~$ modinfo uvcvideo | grep "version:" version: 1.1.1 srcversion: 18D809600E7D1E107042647 ``` @@ -149,10 +149,10 @@ In the directory where you downloaded the file run: `sudo apt install ./libreals Now the commands above, on my machine, return: ```bash -alex@shallowinsight:~$ uname -r +USERNAME@HOSTNAME:~$ uname -r 5.15.0-48-generic -alex@shallowinsight:~$ dkms status +USERNAME@HOSTNAME:~$ dkms status librealsense2-dkms, 1.3.14, 5.15.0-48-generic, x86_64: installed librealsense2-dkms, 1.3.14, 5.15.0-50-generic, x86_64: installed nvidia, 510.85.02, 5.15.0-48-generic, x86_64: installed @@ -160,7 +160,7 @@ nvidia, 510.85.02, 5.15.0-50-generic, x86_64: installed nvidia, 510.85.02, 5.4.0-126-generic, x86_64: installed nvidia, 510.85.02, 5.4.0-128-generic, x86_64: installed -alex@shallowinsight:~$ modinfo uvcvideo | grep "version:" +USERNAME@HOSTNAME:~$ modinfo uvcvideo | grep "version:" version: 1.1.2.realsense-1.3.14 srcversion: 26234508927E0F6886C9A48 ``` @@ -177,7 +177,7 @@ Manually build librealsense within the container without CUDA. Run the following /opt/realsense/build-librealsense.sh -n ``` -now rebuild the ROS2 workspace +now rebuild the ROS 2 workspace ```bash rm -rf build/ install/ log/ diff --git a/docs/troubleshooting-nvblox-ros2.md b/docs/troubleshooting-nvblox-ros2.md new file mode 100644 index 00000000..0cc2c7fe --- /dev/null +++ b/docs/troubleshooting-nvblox-ros2.md @@ -0,0 +1,35 @@ +# Troubleshooting ROS 2 communication issues + +If it looks like you are dropping messages or you are not receiving any messages, you might be at the right place here. + +## DDS tuning + +In case you encounter issues with reliability of ROS 2 message delivery, +you could try to increase the maximum receive buffer size: + +```bash +sudo sysctl -w net.core.rmem_max=2147483647 +``` + +For more information please consult the [DDS tuning information of ROS 2](https://docs.ros.org/en/humble/How-To-Guides/DDS-tuning.html). + +## RTPS profile + +You can find the rtps profile of Isaac ROS [here](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_common/blob/main/docker/middleware_profiles/rtps_udp_profile.xml). +If you encounter communication issues when launching many ROS 2 nodes simultaneously you might want to increase the `maxInitialPeersRange` parameter like shown below: + +```xml +NVIDIA Isaac ROS Software License + + + + UdpTransport + UDPv4 + 100 + + + +... +``` + +After saving the modification, make sure to restart you docker container. diff --git a/docs/tutorial-human-reconstruction-isaac-sim.md b/docs/tutorial-human-reconstruction-isaac-sim.md new file mode 100644 index 00000000..752262da --- /dev/null +++ b/docs/tutorial-human-reconstruction-isaac-sim.md @@ -0,0 +1,81 @@ +# Tutorial For Human Reconstruction in Isaac Sim + +
+ +In this tutorial we'll demonstrate how one can perform human reconstruction in nvblox with Isaac Sim. +The detected human is then visible in a separate dynamic costmap that can be used for navigation with Nav2. +If you want to know how human reconstruction works behind the scenes refer to the [technical details](./technical-details.md#human-reconstruction). + +Before continuing this tutorial, make sure that the [Isaac Sim](tutorial-isaac-sim.md) tutorial is working fine. + +## Isaac Sim Human Reconstruction + +In this part, we demonstrate how to use Isaac Sim for human reconstruction in nvblox. We use the +ground truth semantic segmentation coming from the simulation as input to detect humans. This +demonstration relies on the extensions [*omni.anim.people*](https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/ext_omni_anim_people.html) and [*omni.anim.navigation*](https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_navigation-mesh.html) to make +humans navigate in the environment while the robot is moving. + +### First time setup + +Complete steps 1-4 in the [Isaac Sim](tutorial-isaac-sim.md) tutorial to set up Isaac Sim, build the ROS container and the workspace. + + > **Note**: In a linux based system the usd file, behavior scripts, and the command file have to be on the same file system. If you are not using our sample usd scene, you need to apply [this workaround](linux-people-isaac-sim-workaround.md). + + +### Running the demonstration scene + +1. Run the ROS Docker container using the `run_dev.sh` script: + + ```bash + cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ + ./scripts/run_dev.sh + ``` + +2. Start the simulation by running the below command in a terminal **outside the Docker container**: + + ```bash + alias omni_python='~/.local/share/ov/pkg/isaac_sim-2022.2.1/python.sh' && \ + omni_python ~/workspaces/isaac_ros-dev/src/isaac_ros_nvblox/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/start_isaac_sim.py --with_people --scenario_path=/Isaac/Samples/NvBlox/carter_warehouse_navigation_with_people.usd + ``` + + > **Note**: The `start_isaac_sim` script is explained in detail [here](../nvblox_examples/nvblox_isaac_sim/omniverse_scripts/README.md). + + > **Note**: If you are using a different version of Isaac Sim, replace `isaac_sim-2022.2.1` with your version found at `'~/.local/share/ov/pkg/` + + > **Note**: We strongly advise using `isaac_sim-2022.2.1` because our code is tested and requires some extensions introduced on this version. + + > **Note**: Because the animation requires to execute Python scripts, running the scene with the UI will pop a window up asking to confirm that you want to enable script execution. Click Yes to make it possible to start the scene and the human animation. + +
+ +3. Launch nvblox configured for human mapping: + + ```bash + ros2 launch nvblox_examples_bringup isaac_sim_humans_example.launch.py + ``` + +Step 2 launches the default human paths. If you want to have other paths taken by the humans, you can either: + +- change the default human animation file manually directly on the server +- use the randomization options of the above script. To do so: + + ```bash + alias omni_python='~/.local/share/ov/pkg/isaac_sim-2022.2.1/python.sh' && \ + omni_python ~/workspaces/isaac_ros-dev/src/isaac_ros_nvblox/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/start_isaac_sim.py \ + --with_people --scenario_path=/Isaac/Samples/NvBlox/carter_warehouse_navigation_with_people.usd \ + --anim_people_waypoint_dir --with_people --random_command_generation + ``` + + This will launch the scene headless (without visualization) and generate a new `human_cmd_file.txt` in ``. By default, it will generate 5 waypoints per human, but it is possible to change this with the option `--num_waypoints=`. Then you can either manually upload the script file to replace the default one, or use the same command as step 2. by adding `--use_generated_command_file --anim_people_waypoint_dir ` to automatically set the script file. + +### Running on a custom scene + +If you want to test the reconstruction on another scene: + +* Follow the steps from the [setup section](#first-time-setup) to have the scripts available. + +* Make sure you use the same robot usd so that the topic names and ROS publication graphs are correctly set up. + +* Make sure that humans that you add to the scene have the `person` semantic segmentation class. To do so, you can use the Semantics Schema Editor on the top prim of the additional humans. + +* Make sure that all humans are under */World/Humans* for them to be picked up by the randomization. diff --git a/docs/tutorial-human-reconstruction-realsense.md b/docs/tutorial-human-reconstruction-realsense.md new file mode 100644 index 00000000..1babb815 --- /dev/null +++ b/docs/tutorial-human-reconstruction-realsense.md @@ -0,0 +1,197 @@ +# Tutorial For Human Reconstruction with Realsense + +
+ +In this tutorial we'll demonstrate how one can perform dynamic human reconstruction in nvblox using realsense data. +If you want to know how human reconstruction works behind the scenes refer to the [technical details](./technical-details.md#human-reconstruction). + +## Setup *Isaac ROS Image Segmentation* + + > **Note**: This example including nvblox with human reconstruction on RealSense data is not intended to run on a Jetson platform yet. Stay tuned for updates. + + > **Note**: Currently we recommend the heavier `PeopleSemSegNet` over the lighter `PeopleSemSegNet ShuffleSeg` model provided in [*Isaac ROS Image Segmentation*](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_image_segmentation) for better segmentation performance. + +The following steps show you how to run `PeopleSemSegNet` in ROS. +Refer to [this](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_image_segmentation) readme to run the `PeopleSemSegNet ShuffleSeg` network. + +1. Complete all steps of the [RealSense](tutorial-realsense.md) tutorial and make sure it is working fine. + +2. Clone the segmentation repository and its dependencies under `~/workspaces/isaac_ros-dev/src`. + + ```bash + cd ~/workspaces/isaac_ros-dev/src + ``` + + ```bash + git clone https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nitros + ``` + + ```bash + git clone https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_image_segmentation + ``` + + ```bash + git clone https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_dnn_inference + ``` + + ```bash + git clone https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_image_pipeline.git + ``` + +3. Pull down a ROS Bag of sample data: + + ```bash + cd ~/workspaces/isaac_ros-dev/src/isaac_ros_image_segmentation && \ + git lfs pull -X "" -I "resources/rosbags/" + ``` + +4. Launch the Docker container using the `run_dev.sh` script: + + ```bash + cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ + ./scripts/run_dev.sh + ``` + +5. Download the `PeopleSemSegNet` ETLT file and the`int8` inference mode cache file: + + ```bash + mkdir -p /workspaces/isaac_ros-dev/models/peoplesemsegnet/1 + cd /workspaces/isaac_ros-dev/models/peoplesemsegnet + wget 'https://api.ngc.nvidia.com/v2/models/nvidia/tao/peoplesemsegnet/versions/deployable_quantized_vanilla_unet_v2.0/files/peoplesemsegnet_vanilla_unet_dynamic_etlt_int8.cache' + wget 'https://api.ngc.nvidia.com/v2/models/nvidia/tao/peoplesemsegnet/versions/deployable_quantized_vanilla_unet_v2.0/files/peoplesemsegnet_vanilla_unet_dynamic_etlt_int8_fp16.etlt' + ``` + +6. Convert the ETLT file to a TensorRT plan file: + + ```bash + /opt/nvidia/tao/tao-converter -k tlt_encode -d 3,544,960 -p input_1:0,1x3x544x960,1x3x544x960,1x3x544x960 -t int8 -c peoplesemsegnet_vanilla_unet_dynamic_etlt_int8.cache -e /workspaces/isaac_ros-dev/models/peoplesemsegnet/1/model.plan -o argmax_1 peoplesemsegnet_vanilla_unet_dynamic_etlt_int8_fp16.etlt + ``` + +7. Create the triton configuration file called `/workspaces/isaac_ros-dev/models/peoplesemsegnet/config.pbtxt` with the following content: + + ```bash + name: "peoplesemsegnet" + platform: "tensorrt_plan" + max_batch_size: 0 + input [ + { + name: "input_1:0" + data_type: TYPE_FP32 + dims: [ 1, 3, 544, 960 ] + } + ] + output [ + { + name: "argmax_1" + data_type: TYPE_INT32 + dims: [ 1, 544, 960, 1 ] + } + ] + version_policy: { + specific { + versions: [ 1 ] + } + } + ``` + +8. Inside the container, build and source the workspace: + + ```bash + cd /workspaces/isaac_ros-dev && \ + colcon build --symlink-install && \ + source install/setup.bash + ``` + +9. (Optional) Run tests to verify complete and correct installation: + + ```bash + colcon test --executor sequential + ``` + +10. Run this following launch file to get the ROS node running: + + ```bash + ros2 launch isaac_ros_unet isaac_ros_unet_triton.launch.py model_name:=peoplesemsegnet model_repository_paths:=['/workspaces/isaac_ros-dev/models'] input_binding_names:=['input_1:0'] output_binding_names:=['argmax_1'] network_output_type:='argmax' + ``` + +11. Open *two* other terminals, and enter the Docker container in both: + + ```bash + cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ + ./scripts/run_dev.sh + ``` + +12. Play the ROS bag in one of the terminals: + + ```bash + ros2 bag play -l src/isaac_ros_image_segmentation/resources/rosbags/unet_sample_data/ + ``` + + And visualize the output in the other terminal: + + ```bash + ros2 run rqt_image_view rqt_image_view + ``` + +13. Verify that the output looks similar to [this image.](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_image_segmentation/blob/main/resources/peoplesemsegnet_shuffleseg_rqt.png) + +## Example with Realsense Live Data + +1. Complete the [*Isaac ROS Image Segmentation* setup](#setup-isaac-ros-image-segmentation) above. + +2. Connect the RealSense device to your machine. + +3. Run the ROS Docker container using the `run_dev.sh` script: + + ```bash + cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ + ./scripts/run_dev.sh + ``` + +4. Source the workspace: + + ```bash + source /workspaces/isaac_ros-dev/install/setup.bash + ``` + +5. At this point, you can check that the RealSense camera is connected by running realsense-viewer: + + ```bash + realsense-viewer + ``` + +6. If successful, run the launch file to spin up the example: + + ```bash + ros2 launch nvblox_examples_bringup realsense_humans_example.launch.py + ``` + +## Example with Realsense Recorded Data + +Refer to the [RealSense recording tutorial](./tutorial-realsense-record.md) on how to record RealSense data. +Below we show how to run the example on your own recorded ROS bags. + +1. Complete the [*Isaac ROS Image Segmentation* setup](#setup-isaac-ros-image-segmentation) above. + +2. Run the ROS Docker container using the `run_dev.sh` script: + + ```bash + cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ + ./scripts/run_dev.sh + ``` + +3. Source the workspace: + + ```bash + source /workspaces/isaac_ros-dev/install/setup.bash + ``` + +4. If successful, run the launch file to spin up the example: + + ```bash + ros2 launch nvblox_examples_bringup realsense_humans_example.launch.py from_bag:=True bag_path:= + ``` + +## Troubleshooting + +See our troubleshooting page [here](troubleshooting-nvblox-realsense.md). diff --git a/docs/tutorial-isaac-sim.md b/docs/tutorial-isaac-sim.md index 8549c2e6..265d8d10 100644 --- a/docs/tutorial-isaac-sim.md +++ b/docs/tutorial-isaac-sim.md @@ -6,9 +6,11 @@ This tutorial walks you through generating a 3D reconstruction with nvblox using image and LiDAR data from Isaac Sim. The reconstruction is displayed as a 3D mesh in RVIZ. Nvblox also converts the reconstruction into a 2D costmap, which is fed into the Nav2 stack for path planning. +Last validated with [Isaac Sim 2022.1.0](https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/release_notes.html#id10). + ## Tutorial Walkthrough -1. Complete the [Quickstart section](../README.md#quickstart) in the main README. +1. Complete the [quickstart section](../README.md#quickstart) in the main README. 2. Launch the Docker container using the `run_dev.sh` script: ```bash @@ -26,18 +28,19 @@ This tutorial walks you through generating a 3D reconstruction with nvblox using 4. Install Isaac Sim following the steps in the [Isaac ROS Isaac Sim Setup Guide](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_common/blob/main/docs/isaac-sim-sil-setup.md) 5. Start the simulation by running the below command in a terminal **outside the Docker container**: +The start_isaac_sim script is explained in detail [here](../nvblox_examples/nvblox_isaac_sim/omniverse_scripts/README.md). ```bash - alias omni_python='~/.local/share/ov/pkg/isaac_sim-2022.1.1-release.1/python.sh' && \ - omni_python ~/workspaces/isaac_ros-dev/src/isaac_ros_nvblox/nvblox_isaac_sim/omniverse_scripts/carter_warehouse.py + alias omni_python='~/.local/share/ov/pkg/isaac_sim-2022.2.1-rc.3/python.sh' && \ + omni_python ~/workspaces/isaac_ros-dev/src/isaac_ros_nvblox/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/start_isaac_sim.py ``` - > **Note:** If you are using a different version of Isaac Sim, replace `isaac_sim-2022.1.1-release.1` with your version found at `'~/.local/share/ov/pkg/` + > **Note**: If you are using a different version of Isaac Sim, replace `isaac_sim-2022.2.1-rc.3` with your version found at `'~/.local/share/ov/pkg/`. -6. Launch the pre-composed pipeline launchfile: +6. Launch the pre-composed graph launchfile: ```bash - ros2 launch nvblox_nav2 carter_sim.launch.py + ros2 launch nvblox_examples_bringup isaac_sim_example.launch.py ``` 7. Click the **2D Goal Pose** button. You should see the mesh and costmap, as well as the robot moving towards the goal location, as shown at the top of this page. diff --git a/docs/tutorial-nvblox-vslam-realsense.md b/docs/tutorial-nvblox-vslam-realsense.md deleted file mode 100644 index ecbe7a0f..00000000 --- a/docs/tutorial-nvblox-vslam-realsense.md +++ /dev/null @@ -1,80 +0,0 @@ -# Tutorial For RealSense-based Reconstruction - -This tutorial demonstrates how to perform depth-camera based reconstruction using a [Realsense](https://www.intel.com/content/www/us/en/architecture-and-technology/realsense-overview.html) camera, NVIDIA [VSLAM](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_visual_slam), and [nvblox](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox). - -> Note: This tutorial has been tested with a Realsense D455/D435 camera connected to an x86 computer with an NVIDIA graphics card, as well as a Jetson Xavier AGX. - -## Realsense Camera Firmware - -This example is tested and compatible with realsense camera firmware version 5.13.0.50 which is available [here](https://dev.intelrealsense.com/docs/firmware-releases). - -> Note: We experienced issues with the latest realsense firmware (version 5.14 at the time of publishing). It's possible at some point that this starts working, but our recommendation is to install *exactly* 5.13.0.50. - -## Host System Setup - -We have found ROS2 message delivery to be unreliable under high load without some small modifications to the QoS profile (especially on weaker machines). Before running this example run -``` -sudo sysctl -w net.core.rmem_max=8388608 net.core.rmem_default=8388608 -``` -However this only sets these parameters until reboot. To set them permanently run: -``` -echo -e "net.core.rmem_max=8388608\nnet.core.rmem_default=8388608\n" | sudo tee /etc/sysctl.d/60-cyclonedds.conf -``` -More details on DDS tuning can be found [here](https://docs.ros.org/en/rolling/How-To-Guides/DDS-tuning.html). - -## Installing the Dependencies - -1. Complete steps 1, 2, and 3 described in the [quickstart guide](../README.md#quickstart) to clone `isaac_ros_common` and - `isaac_ros_nvblox` and to build [librealsense](https://github.com/IntelRealSense/librealsense) as part of the Docker container. - -2. Complete the [RealSense setup tutorial](https://github.com/NVIDIA-ISAAC-ROS/.github/blob/main/profile/realsense-setup.md). - -3. Download the code for [Isaac ROS Visual SLAM](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_visual_slam.git). - - ```bash - git clone https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_visual_slam.git - ``` - -4. Launch the Docker container using the `run_dev.sh` script: - - ```bash - cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ - ./scripts/run_dev.sh - ``` - -5. Inside the container, install package-specific dependencies via `rosdep`: - - ```bash - cd /workspaces/isaac_ros-dev/ && \ - rosdep install -i -r --from-paths src --rosdistro humble -y --skip-keys "libopencv-dev libopencv-contrib-dev libopencv-imgproc-dev python-opencv python3-opencv nvblox" - ``` - -6. Build and source the workspace: - - ```bash - cd /workspaces/isaac_ros-dev && \ - colcon build --symlink-install && \ - source install/setup.bash - ``` - -## Running the Example - -To run the example, source the workspace: - -```bash -source /workspaces/isaac_ros-dev/install/setup.bash -``` - -Then run the launch file, which launches the example: - -```bash -ros2 launch nvblox_examples_bringup nvblox_vslam_realsense.launch.py -``` - -Here is a few seconds of the result from running the example: - -
- -## Troubleshooting - -See our troubleshooting page [here](troubleshooting-nvblox-vslam-realsense.md). diff --git a/docs/tutorial-realsense-record.md b/docs/tutorial-realsense-record.md new file mode 100644 index 00000000..e08c4694 --- /dev/null +++ b/docs/tutorial-realsense-record.md @@ -0,0 +1,25 @@ +# Tutorial For Realsense Data Recording + +## Record ROS Bag + +To record realsense data for nvblox: + +1. Make sure that all [realsense dependencies](./tutorial-realsense.md) are met. + +2. Connect the camera, start the docker container and source the workspace as explained in the [realsense example tutorial](./tutorial-realsense.md). + +3. Start recording: + + ```bash + ros2 launch nvblox_examples_bringup record_realsense.launch.py + ``` + +4. Stop the recording when done. + +## Start NVBLOX from ROS Bag + +1. Start nvblox from bag: + + ```bash + ros2 launch nvblox_examples_bringup realsense(_humans)_example.launch.py from_bag:=True bag_path:= + ``` diff --git a/docs/tutorial-realsense.md b/docs/tutorial-realsense.md new file mode 100644 index 00000000..0deac06a --- /dev/null +++ b/docs/tutorial-realsense.md @@ -0,0 +1,93 @@ +# Tutorial For Realsense-based Reconstruction + +This tutorial demonstrates how to perform depth-camera based reconstruction using a [realsense](https://www.intel.com/content/www/us/en/architecture-and-technology/realsense-overview.html) camera, NVIDIA [VSLAM](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_visual_slam), and [nvblox](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nvblox). + +> **Note**: This tutorial requires a compatible RealSense camera from the list available [here](https://github.com/NVIDIA-ISAAC-ROS/.github/blob/main/profile/realsense-setup.md#camera-compatibility). + +## Realsense Camera Firmware + +This example is tested and compatible with realsense camera firmware version 5.13.0.50 which is available [here](https://dev.intelrealsense.com/docs/firmware-releases). + +> Note: We experienced issues with the latest realsense firmware (version 5.14 at the time of publishing). It's possible at some point that this starts working, but our recommendation is to install *exactly* 5.13.0.50. + +## Host System Setup + +We have found ROS 2 message delivery to be unreliable under high load without some small modifications to the QoS profile (especially on weaker machines). Before running this example run +```bash +sudo sysctl -w net.core.rmem_max=8388608 net.core.rmem_default=8388608 +``` + +However this only sets these parameters until reboot. To set them permanently run: +```bash +echo -e "net.core.rmem_max=8388608\nnet.core.rmem_default=8388608\n" | sudo tee /etc/sysctl.d/60-cyclonedds.conf +``` +More details on DDS tuning can be found [here](https://docs.ros.org/en/rolling/How-To-Guides/DDS-tuning.html). + +## Installing the Dependencies + +1. Complete steps 1 and 2 described in the [quickstart guide](../README.md#quickstart) to set up your development environment and clone the required repositories. + +2. Stop git tracking the `COLCON_IGNORE` file in the `realsense_splitter` package and remove it. + + ```bash + cd ~/workspaces/isaac_ros-dev/src/isaac_ros_nvblox/nvblox_examples/realsense_splitter && \ + git update-index --assume-unchanged COLCON_IGNORE && \ + rm COLCON_IGNORE + ``` + +> Note: The `COLCON_IGNORE` file was added to remove the dependency to `realsense-ros` for users that do not want to run the RealSense examples. + +3. Complete the [realsense setup tutorial](https://github.com/NVIDIA-ISAAC-ROS/.github/blob/main/profile/realsense-setup.md) to set up `librealsense` *outside of the isaac_ros_common* docker, clone `realsense_ros`, and to configure the container for use with realsense. + +4. Download the code for [Isaac ROS Visual SLAM](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_visual_slam.git). + + ```bash + cd ~/workspaces/isaac_ros-dev/src && \ + git clone https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_visual_slam.git + ``` + +5. Launch the Docker container using the `run_dev.sh` script: + + ```bash + cd ~/workspaces/isaac_ros-dev/src/isaac_ros_common && \ + ./scripts/run_dev.sh + ``` + +6. Inside the container, install package-specific dependencies via `rosdep`: + + ```bash + cd /workspaces/isaac_ros-dev/ && \ + rosdep install -i -r --from-paths src --rosdistro humble -y --skip-keys "libopencv-dev libopencv-contrib-dev libopencv-imgproc-dev python-opencv python3-opencv nvblox" + ``` + +7. Build and source the workspace: + + ```bash + cd /workspaces/isaac_ros-dev && \ + colcon build --symlink-install && \ + source install/setup.bash + ``` + +## Running the Example + +To run the example, source the workspace: + +```bash +source /workspaces/isaac_ros-dev/install/setup.bash +``` + +Then run the launch file, which launches the example: + +```bash +ros2 launch nvblox_examples_bringup realsense_example.launch.py +``` + +Here is a few seconds of the result from running the example: + +
+ +If you want to run the realsense example on recorded data refer to the [realsense recording tutorial](./tutorial-realsense-record.md). + +## Troubleshooting + +See our troubleshooting page [here](troubleshooting-nvblox-realsense.md). diff --git a/isaac_ros_nvblox/package.xml b/isaac_ros_nvblox/package.xml index 31ca657e..3e857202 100644 --- a/isaac_ros_nvblox/package.xml +++ b/isaac_ros_nvblox/package.xml @@ -21,8 +21,8 @@ isaac_ros_nvblox - 0.20.0 - Nvblox ROS2 metapackage + 0.30.0 + Nvblox ROS 2 metapackage Helen Oleynikova Apache-2.0 diff --git a/nvblox b/nvblox index c2aff854..4b013e93 160000 --- a/nvblox +++ b/nvblox @@ -1 +1 @@ -Subproject commit c2aff854b55cc0bad201e7b0f73e926e30209bb9 +Subproject commit 4b013e935b9b51b76ae9cec07bc26ee2c8bc746c diff --git a/nvblox_examples/nvblox_examples_bringup/CMakeLists.txt b/nvblox_examples/nvblox_examples_bringup/CMakeLists.txt index 9ba3da9c..9b51c76d 100644 --- a/nvblox_examples/nvblox_examples_bringup/CMakeLists.txt +++ b/nvblox_examples/nvblox_examples_bringup/CMakeLists.txt @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/nvblox_nav2/params/carter_nav2.yaml b/nvblox_examples/nvblox_examples_bringup/config/nav2/nav2_isaac_sim.yaml similarity index 83% rename from nvblox_nav2/params/carter_nav2.yaml rename to nvblox_examples/nvblox_examples_bringup/config/nav2/nav2_isaac_sim.yaml index 9172314a..523afacd 100644 --- a/nvblox_nav2/params/carter_nav2.yaml +++ b/nvblox_examples/nvblox_examples_bringup/config/nav2/nav2_isaac_sim.yaml @@ -19,7 +19,7 @@ bt_navigator: ros__parameters: use_sim_time: True - global_frame: map + global_frame: odom robot_base_frame: base_link odom_topic: /odom bt_loop_duration: 20 @@ -98,7 +98,7 @@ controller_server: general_goal_checker: plugin: "nav2_controller::SimpleGoalChecker" xy_goal_tolerance: 0.25 - yaw_goal_tolerance: 0.25 + yaw_goal_tolerance: 1.00 stateful: True # DWB parameters FollowPath: @@ -108,18 +108,18 @@ controller_server: min_vel_y: 0.0 max_vel_x: 2.0 max_vel_y: 0.0 - max_vel_theta: 2.0 + max_vel_theta: 1.0 min_speed_xy: 0.0 max_speed_xy: 2.0 min_speed_theta: 0.0 # Add high threshold velocity for turtlebot 3 issue. # https://github.com/ROBOTIS-GIT/turtlebot3_simulations/issues/75 - acc_lim_x: 2.5 + acc_lim_x: 1.0 acc_lim_y: 0.0 - acc_lim_theta: 1.2 - decel_lim_x: -2.5 + acc_lim_theta: 1.0 + decel_lim_x: -1.0 decel_lim_y: 0.0 - decel_lim_theta: -1.2 + decel_lim_theta: -1.0 vx_samples: 30 vy_samples: 10 vtheta_samples: 20 @@ -143,6 +143,22 @@ controller_server: RotateToGoal.slowing_factor: 1.0 RotateToGoal.lookahead_time: -1.0 +# Update velocity smoother default params to match our controller server config +# https://navigation.ros.org/configuration/packages/configuring-velocity-smoother.html +velocity_smoother: + ros__parameters: + smoothing_frequency: 20.0 + scale_velocities: false + feedback: "OPEN_LOOP" + max_velocity: [1.0, 0.0, 2.0] + min_velocity: [0.0, 0.0, -2.0] + deadband_velocity: [0.0, 0.0, 0.0] + velocity_timeout: 1.0 + max_accel: [1.0, 0.0, 2.0] + max_decel: [-1.0, 0.0, -2.0] + odom_topic: "odom" + odom_duration: 0.1 + controller_server_rclcpp_node: ros__parameters: use_sim_time: True @@ -152,7 +168,7 @@ local_costmap: ros__parameters: update_frequency: 10.0 publish_frequency: 10.0 - global_frame: map + global_frame: odom robot_base_frame: base_link use_sim_time: True rolling_window: True @@ -160,12 +176,19 @@ local_costmap: height: 10 resolution: 0.05 robot_radius: 0.4 - plugins: ["nvblox_layer"] - nvblox_layer: + plugins: ["nvblox_base_layer", "nvblox_human_layer"] + nvblox_base_layer: + plugin: "nvblox::nav2::NvbloxCostmapLayer" + enabled: True + max_obstacle_distance: 1.0 + inflation_distance: 0.4 + nvblox_map_slice_topic: "/nvblox_node/map_slice" + nvblox_human_layer: plugin: "nvblox::nav2::NvbloxCostmapLayer" enabled: True max_obstacle_distance: 1.0 inflation_distance: 0.4 + nvblox_map_slice_topic: "/nvblox_human_node/human_map_slice" always_send_full_costmap: True local_costmap_client: ros__parameters: @@ -179,7 +202,7 @@ global_costmap: ros__parameters: update_frequency: 10.0 publish_frequency: 10.0 - global_frame: map + global_frame: odom robot_base_frame: base_link use_sim_time: True rolling_window: True @@ -189,12 +212,19 @@ global_costmap: resolution: 0.1 origin_x: -100.0 origin_y: -100.0 - plugins: ["nvblox_layer"] - nvblox_layer: + plugins: ["nvblox_base_layer", "nvblox_human_layer"] + nvblox_base_layer: + plugin: "nvblox::nav2::NvbloxCostmapLayer" + enabled: True + max_obstacle_distance: 1.0 + inflation_distance: 0.4 + nvblox_map_slice_topic: "/nvblox_node/map_slice" + nvblox_human_layer: plugin: "nvblox::nav2::NvbloxCostmapLayer" enabled: True max_obstacle_distance: 1.0 inflation_distance: 0.4 + nvblox_map_slice_topic: "/nvblox_human_node/human_map_slice" always_send_full_costmap: True global_costmap_client: ros__parameters: @@ -242,14 +272,14 @@ behavior_server: plugin: "nav2_behaviors/DriveOnHeading" wait: plugin: "nav2_behaviors/Wait" - global_frame: map + global_frame: odom robot_base_frame: base_link transform_tolerance: 0.2 use_sim_time: true simulate_ahead_time: 2.0 - max_rotational_vel: 2.0 + max_rotational_vel: 1.0 min_rotational_vel: 0.4 - rotational_acc_lim: 3.2 + rotational_acc_lim: 1.0 robot_state_publisher: ros__parameters: diff --git a/nvblox_examples/nvblox_examples_bringup/config/nvblox.yaml b/nvblox_examples/nvblox_examples_bringup/config/nvblox.yaml deleted file mode 100644 index 64b03a76..00000000 --- a/nvblox_examples/nvblox_examples_bringup/config/nvblox.yaml +++ /dev/null @@ -1,42 +0,0 @@ -%YAML 1.2 -# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# 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. -# -# SPDX-License-Identifier: Apache-2.0 ---- -nvblox_node: - ros__parameters: - global_frame: odom - voxel_size: 0.05 - slice_height: 0.0 - min_height: -0.1 - max_height: 0.1 - esdf: true - esdf_2d: true - mesh: true - distance_slice: true - tsdf_integrator_max_integration_distance_m: 4.0 - tsdf_integrator_truncation_distance_vox: 4.0 - tsdf_integrator_max_weight: 20.0 - esdf_integrator_min_weight: 2.0 - esdf_integrator_max_distance_m: 2.0 - esdf_integrator_max_site_distance_vox: 5.0 - max_tsdf_update_hz: 30.0 - max_color_update_hz: 5.0 - max_mesh_update_hz: 1.0 - max_esdf_update_hz: 10.0 - output_dir: /home/nvidia/data/meshes/ - depth_qos: "SENSOR_DATA" - color_qos: "SENSOR_DATA" diff --git a/nvblox_examples/nvblox_examples_bringup/config/nvblox/nvblox_base.yaml b/nvblox_examples/nvblox_examples_bringup/config/nvblox/nvblox_base.yaml new file mode 100644 index 00000000..c2d785b6 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/config/nvblox/nvblox_base.yaml @@ -0,0 +1,66 @@ +/**: + ros__parameters: + # miscellaneous + voxel_size: 0.05 + use_tf_transforms: true + # esdf settings + compute_esdf: true + esdf_update_rate_hz: 10.0 + esdf_2d: true + esdf_distance_slice: true + esdf_slice_height: 0.4 + esdf_2d_min_height: 0.025 + esdf_2d_max_height: 0.7 + # mesh settings + compute_mesh: true + mesh_update_rate_hz: 5.0 + # color settings + use_color: true + max_color_update_hz: 5.0 + # depth settings + use_depth: true + max_depth_update_hz: 30.0 + # lidar settings + use_lidar: true + max_lidar_update_hz: 100.0 + lidar_width: 1800 + lidar_height: 31 + # static occupancy + use_static_occupancy_layer: false # tsdf if false + occupancy_publication_rate_hz: 2.0 + # Input queues + max_poll_rate_hz: 100.0 + maximum_sensor_message_queue_length: 30 + # Map clearing settings + map_clearing_radius_m: -1.0 # no map clearing if < 0.0 + map_clearing_frame_id: "base_link" + clear_outside_radius_rate_hz: 1.0 + # QoS settings + depth_qos: "SYSTEM_DEFAULT" + color_qos: "SYSTEM_DEFAULT" + # Rviz visualization + slice_visualization_attachment_frame_id: "base_link" + slice_visualization_side_length: 10.0 + + mapper: + # tsdf or occupancy integrator + projective_integrator_max_integration_distance_m: 7.0 + projective_integrator_truncation_distance_vox: 4.0 + lidar_projective_integrator_max_integration_distance_m: 10.0 + # tsdf integrator + weighting_mode: "inverse_square" # applies to color integrator as well + tsdf_integrator_max_weight: 5.0 + # occupancy integrator + free_region_occupancy_probability: 0.45 + occupied_region_occupancy_probability: 0.55 + unobserved_region_occupancy_probability: 0.5 + occupied_region_half_width_m: 0.1 + # esdf integrator + esdf_integrator_min_weight: 0.0001 + esdf_integrator_max_distance_m: 2.0 + esdf_integrator_max_site_distance_vox: 5.0 + # mesh integrator + mesh_integrator_min_weight: 0.0001 + mesh_integrator_weld_vertices: true + # color integrator + color_integrator_max_integration_distance_m: 7.0 diff --git a/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_humans.yaml b/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_humans.yaml new file mode 100644 index 00000000..5812b00e --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_humans.yaml @@ -0,0 +1,23 @@ +/**: + ros__parameters: + # lidar settings + use_lidar: false + # human node specific settings + human_esdf_update_rate_hz: 10.0 + human_occupancy_decay_rate_hz: 10.0 + + human_mapper: + # occupancy integrator + projective_integrator_max_integration_distance_m: 7.0 + projective_integrator_truncation_distance_vox: 4.0 + free_region_occupancy_probability: 0.2 + occupied_region_occupancy_probability: 0.9 + unobserved_region_occupancy_probability: 0.35 + occupied_region_half_width_m: 0.15 + # occupancy decay + free_region_decay_probability: 0.55 + occupied_region_decay_probability: 0.45 + # esdf integrator + esdf_integrator_min_weight: 2.0 + esdf_integrator_max_distance_m: 2.0 + esdf_integrator_max_site_distance_vox: 5.0 diff --git a/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_isaac_sim.yaml b/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_isaac_sim.yaml new file mode 100644 index 00000000..91cc7340 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_isaac_sim.yaml @@ -0,0 +1,3 @@ +/**: + ros__parameters: + use_sim_time: True diff --git a/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_realsense.yaml b/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_realsense.yaml new file mode 100644 index 00000000..5213b804 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/config/nvblox/specializations/nvblox_realsense.yaml @@ -0,0 +1,19 @@ +/**: + ros__parameters: + # esdf settings + esdf_slice_height: 0.0 + esdf_2d_min_height: -0.1 + esdf_2d_max_height: 0.3 + # Lidar settings + use_lidar: false + # QoS settings + depth_qos: "SENSOR_DATA" + color_qos: "SENSOR_DATA" + # Map clearing settings + map_clearing_frame_id: "camera_link" + # Rviz visualization + slice_visualization_attachment_frame_id: "camera_link" + + # Needed because of RS driver bug: + # https://github.com/IntelRealSense/realsense-ros/issues/2583 + is_realsense_data: True diff --git a/nvblox_examples/nvblox_examples_bringup/config/nvblox_vslam_realsense.rviz b/nvblox_examples/nvblox_examples_bringup/config/nvblox_vslam_realsense.rviz deleted file mode 100644 index 43eeb26c..00000000 --- a/nvblox_examples/nvblox_examples_bringup/config/nvblox_vslam_realsense.rviz +++ /dev/null @@ -1,326 +0,0 @@ -Panels: - - Class: rviz_common/Displays - Help Height: 78 - Name: Displays - Property Tree Widget: - Expanded: - - /Global Options1 - - /Status1 - - /TF1/Frames1 - - /PointCloud22/Autocompute Value Bounds1 - - /Image1 - - /Image1/Topic1 - Splitter Ratio: 0.5 - Tree Height: 872 - - Class: rviz_common/Selection - Name: Selection - - Class: rviz_common/Tool Properties - Expanded: - - /2D Goal Pose1 - - /Publish Point1 - Name: Tool Properties - Splitter Ratio: 0.5886790156364441 - - Class: rviz_common/Views - Expanded: - - /Current View1 - Name: Views - Splitter Ratio: 0.5 - - Class: rviz_common/Time - Experimental: false - Name: Time - SyncMode: 0 - SyncSource: PointCloud2 -Visualization Manager: - Class: "" - Displays: - - Alpha: 0.5 - Cell Size: 1 - Class: rviz_default_plugins/Grid - Color: 160; 160; 164 - Enabled: true - Line Style: - Line Width: 0.029999999329447746 - Value: Lines - Name: Grid - Normal Cell Count: 0 - Offset: - X: 0 - Y: 0 - Z: 0 - Plane: XY - Plane Cell Count: 10 - Reference Frame: - Value: true - - Alpha: 1 - Autocompute Intensity Bounds: true - Autocompute Value Bounds: - Max Value: 10 - Min Value: -10 - Value: true - Axis: Z - Channel Name: intensity - Class: rviz_default_plugins/PointCloud2 - Color: 255; 0; 255 - Color Transformer: FlatColor - Decay Time: 0 - Enabled: true - Invert Rainbow: false - Max Color: 255; 255; 255 - Max Intensity: 4096 - Min Color: 0; 0; 0 - Min Intensity: 0 - Name: PointCloud2 - Position Transformer: XYZ - Selectable: true - Size (Pixels): 3 - Size (m): 0.029999999329447746 - Style: Flat Squares - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /visual_slam/vis/observations_cloud - Use Fixed Frame: true - Use rainbow: true - Value: true - - Class: rviz_default_plugins/TF - Enabled: true - Frame Timeout: 15 - Frames: - All Enabled: true - base_link: - Value: true - camera_aligned_depth_to_infra1_frame: - Value: true - camera_color_frame: - Value: true - camera_color_optical_frame: - Value: true - camera_depth_frame: - Value: true - camera_depth_optical_frame: - Value: true - camera_infra1_frame: - Value: true - camera_infra1_optical_frame: - Value: true - camera_infra2_frame: - Value: true - camera_infra2_optical_frame: - Value: true - camera_link: - Value: true - map: - Value: true - odom: - Value: true - Marker Scale: 1 - Name: TF - Show Arrows: true - Show Axes: true - Show Names: false - Tree: - map: - odom: - base_link: - camera_link: - camera_aligned_depth_to_infra1_frame: - camera_infra1_optical_frame: - {} - camera_color_frame: - camera_color_optical_frame: - {} - camera_depth_frame: - camera_depth_optical_frame: - {} - camera_infra1_frame: - {} - camera_infra2_frame: - camera_infra2_optical_frame: - {} - Update Interval: 0 - Value: true - - Alpha: 1 - Autocompute Intensity Bounds: true - Autocompute Value Bounds: - Max Value: 5 - Min Value: -1 - Value: false - Axis: Z - Channel Name: intensity - Class: rviz_default_plugins/PointCloud2 - Color: 255; 255; 255 - Color Transformer: AxisColor - Decay Time: 0 - Enabled: true - Invert Rainbow: false - Max Color: 255; 255; 255 - Max Intensity: 4096 - Min Color: 0; 0; 0 - Min Intensity: 0 - Name: PointCloud2 - Position Transformer: XYZ - Selectable: true - Size (Pixels): 3 - Size (m): 0.019999999552965164 - Style: Flat Squares - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /visual_slam/vis/landmarks_cloud - Use Fixed Frame: true - Use rainbow: true - Value: true - - Ceiling Height: 0 - Class: nvblox_rviz_plugin/NvbloxMesh - Cut Ceiling: true - Enabled: true - Mesh Color: Color + Shading - Name: NvbloxMesh - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /nvblox_node/mesh - Value: true - - Alpha: 1 - Autocompute Intensity Bounds: true - Autocompute Value Bounds: - Max Value: 10 - Min Value: -10 - Value: true - Axis: Z - Channel Name: intensity - Class: rviz_default_plugins/PointCloud2 - Color: 255; 255; 255 - Color Transformer: Intensity - Decay Time: 0 - Enabled: false - Invert Rainbow: false - Max Color: 255; 255; 255 - Max Intensity: 2 - Min Color: 0; 0; 0 - Min Intensity: 0 - Name: PointCloud2 - Position Transformer: XYZ - Selectable: true - Size (Pixels): 3 - Size (m): 0.029999999329447746 - Style: Flat Squares - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /nvblox_node/pointcloud - Use Fixed Frame: true - Use rainbow: true - Value: false - - Class: rviz_default_plugins/Image - Enabled: false - Max Value: 1 - Median window: 5 - Min Value: 0 - Name: Image - Normalize Range: true - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Best Effort - Value: /camera/color/image_raw - Value: false - Enabled: true - Global Options: - Background Color: 48; 48; 48 - Fixed Frame: odom - Frame Rate: 30 - Name: root - Tools: - - Class: rviz_default_plugins/Interact - Hide Inactive Objects: true - - Class: rviz_default_plugins/MoveCamera - - Class: rviz_default_plugins/Select - - Class: rviz_default_plugins/FocusCamera - - Class: rviz_default_plugins/Measure - Line color: 128; 128; 0 - - Class: rviz_default_plugins/SetInitialPose - Covariance x: 0.25 - Covariance y: 0.25 - Covariance yaw: 0.06853891909122467 - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /initialpose - - Class: rviz_default_plugins/SetGoal - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /goal_pose - - Class: rviz_default_plugins/PublishPoint - Single click: true - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /clicked_point - Transformation: - Current: - Class: rviz_default_plugins/TF - Value: true - Views: - Current: - Class: rviz_default_plugins/ThirdPersonFollower - Distance: 6.388955593109131 - Enable Stereo Rendering: - Stereo Eye Separation: 0.05999999865889549 - Stereo Focal Distance: 1 - Swap Stereo Eyes: false - Value: false - Focal Point: - X: -2.7065999507904053 - Y: -0.12273930013179779 - Z: -0.04624328017234802 - Focal Shape Fixed Size: true - Focal Shape Size: 0.05000000074505806 - Invert Z Axis: false - Name: Current View - Near Clip Distance: 0.009999999776482582 - Pitch: 0.6453965902328491 - Target Frame: base_link - Value: ThirdPersonFollower (rviz_default_plugins) - Yaw: 3.02223539352417 - Saved: ~ -Window Geometry: - Displays: - collapsed: false - Height: 1163 - Hide Left Dock: false - Hide Right Dock: false - Image: - collapsed: false - QMainWindow State: 000000ff00000000fd000000040000000000000156000003f1fc0200000009fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b000003f1000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000a0049006d00610067006502000009b8000002ea000001710000011f000000010000013e000003f1fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003b000003f1000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000007380000003efc0100000002fb0000000800540069006d00650100000000000007380000024400fffffffb0000000800540069006d0065010000000000000450000000000000000000000498000003f100000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 - Selection: - collapsed: false - Time: - collapsed: false - Tool Properties: - collapsed: false - Views: - collapsed: false - Width: 1848 - X: 1992 - Y: 0 diff --git a/nvblox_examples/nvblox_examples_bringup/config/rviz/isaac_sim_example.rviz b/nvblox_examples/nvblox_examples_bringup/config/rviz/isaac_sim_example.rviz new file mode 100644 index 00000000..11a1d48f --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/config/rviz/isaac_sim_example.rviz @@ -0,0 +1,487 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /TF1/Frames1 + - /TF1/Tree1 + - /Nvblox1 + - /Sensors1 + - /Sensors1/LiDAR Pointcloud1/Autocompute Value Bounds1 + Splitter Ratio: 0.5256410241127014 + Tree Height: 940 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Class: rviz_default_plugins/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + base_link: + Value: true + camera_left_ROS_frame: + Value: true + camera_right_ROS_frame: + Value: true + camera_sensor_left: + Value: true + camera_sensor_right: + Value: true + carter_lidar: + Value: true + caster_frame_base: + Value: true + caster_swivel_left: + Value: true + caster_swivel_right: + Value: true + caster_wheel_left: + Value: true + caster_wheel_right: + Value: true + chassis_link: + Value: true + odom: + Value: true + wheel_left: + Value: true + wheel_right: + Value: true + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + odom: + base_link: + camera_left_ROS_frame: + {} + camera_right_ROS_frame: + {} + camera_sensor_left: + {} + camera_sensor_right: + {} + carter_lidar: + {} + chassis_link: + caster_frame_base: + caster_swivel_left: + caster_wheel_left: + {} + caster_swivel_right: + caster_wheel_right: + {} + wheel_left: + {} + wheel_right: + {} + Update Interval: 0 + Value: true + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 100 + Reference Frame: + Value: true + - Class: rviz_common/Group + Displays: + - Ceiling Height: 1.5 + Class: nvblox_rviz_plugin/NvbloxMesh + Cut Ceiling: true + Enabled: true + Mesh Color: Color + Shading + Name: NvbloxMesh + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/mesh + Value: true + - Class: rviz_default_plugins/MarkerArray + Enabled: false + Name: Mesh Marker + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/mesh_marker + Value: false + - Class: rviz_default_plugins/Marker + Enabled: false + Name: Map Slice Bounds + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/map_slice_bounds + Value: false + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 2 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Esdf Slice + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/esdf_pointcloud + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 6.700000286102295 + Min Value: -0.3499999940395355 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: AxisColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 1 + Min Color: 0; 0; 0 + Min Intensity: 1 + Name: Occupancy + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.05000000074505806 + Style: Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/occupancy + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Name: Nvblox + - Class: rviz_common/Group + Displays: + - Class: rviz_common/Group + Displays: + - Alpha: 0.699999988079071 + Class: rviz_default_plugins/Map + Color Scheme: costmap + Draw Behind: false + Enabled: false + Name: Global Costmap + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /global_costmap/costmap + Update Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /global_costmap/costmap_updates + Use Timestamp: false + Value: false + - Alpha: 1 + Buffer Length: 1 + Class: rviz_default_plugins/Path + Color: 25; 255; 0 + Enabled: true + Head Diameter: 0.30000001192092896 + Head Length: 0.20000000298023224 + Length: 0.30000001192092896 + Line Style: Billboards + Line Width: 0.10000000149011612 + Name: Global Path + Offset: + X: 0 + Y: 0 + Z: 0 + Pose Color: 255; 85; 255 + Pose Style: None + Radius: 0.029999999329447746 + Shaft Diameter: 0.10000000149011612 + Shaft Length: 0.10000000149011612 + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /plan + Value: true + Enabled: true + Name: Global Navigation + - Class: rviz_common/Group + Displays: + - Alpha: 1 + Buffer Length: 1 + Class: rviz_default_plugins/Path + Color: 239; 41; 41 + Enabled: true + Head Diameter: 0.30000001192092896 + Head Length: 0.20000000298023224 + Length: 0.30000001192092896 + Line Style: Billboards + Line Width: 0.029999999329447746 + Name: Local Path + Offset: + X: 0 + Y: 0 + Z: 0 + Pose Color: 255; 85; 255 + Pose Style: None + Radius: 0.029999999329447746 + Shaft Diameter: 0.10000000149011612 + Shaft Length: 0.10000000149011612 + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /local_plan + Value: true + - Alpha: 0.699999988079071 + Class: rviz_default_plugins/Map + Color Scheme: costmap + Draw Behind: false + Enabled: true + Name: Local Costmap + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /local_costmap/costmap + Update Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /local_costmap/costmap_updates + Use Timestamp: false + Value: true + Enabled: false + Name: Local Navigation + Enabled: true + Name: Nav2 + - Class: rviz_common/Group + Displays: + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 15 + Median window: 5 + Min Value: 0 + Name: Depth Image + Normalize Range: false + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /front/stereo_camera/left/depth + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Color Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /front/stereo_camera/left/rgb + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 0 + Min Value: -0.5 + Value: false + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: AxisColor + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: LiDAR Pointcloud + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /point_cloud + Use Fixed Frame: true + Use rainbow: true + Value: false + Enabled: true + Name: Sensors + Enabled: true + Global Options: + Background Color: 212; 212; 212 + Fixed Frame: odom + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 23.513273239135742 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: -0.5149949789047241 + Y: -3.302676200866699 + Z: -4.94804048538208 + Focal Shape Fixed Size: false + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 1.054795742034912 + Target Frame: base_link + Value: Orbit (rviz) + Yaw: 3.090402603149414 + Saved: ~ +Window Geometry: + Color Image: + collapsed: false + Depth Image: + collapsed: false + Displays: + collapsed: false + Height: 1163 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd00000004000000000000018800000435fc020000000efb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b00000435000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb00000010007200670062005f006c00650066007402000008f4000000fe000003740000026efb0000001400640065007000740068005f006c0065006600740200000c8c000001e70000014600000125fb0000000a0049006d0061006700650200000c900000023f0000025e000001d3fb0000000c00430061006d0065007200610200000c2f00000104000001e600000190fb000000160044006500700074006800200049006d0061006700650300000d5a0000033f0000018800000148fb000000160043006f006c006f007200200049006d0061006700650300000d5c000001fe000001850000013b000000010000010f00000435fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b00000435000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000005f20000043500000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1920 + X: 1920 + Y: 0 diff --git a/nvblox_examples/nvblox_examples_bringup/config/rviz/isaac_sim_humans_example.rviz b/nvblox_examples/nvblox_examples_bringup/config/rviz/isaac_sim_humans_example.rviz new file mode 100644 index 00000000..b619bab5 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/config/rviz/isaac_sim_humans_example.rviz @@ -0,0 +1,654 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /TF1/Frames1 + - /TF1/Tree1 + - /Nvblox1 + - /Humans1 + - /Nav21/Global Navigation1 + Splitter Ratio: 0.545918345451355 + Tree Height: 913 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Class: rviz_default_plugins/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + base_link: + Value: true + camera_left_ROS_frame: + Value: true + camera_right_ROS_frame: + Value: true + camera_sensor_left: + Value: true + camera_sensor_right: + Value: true + carter_lidar: + Value: true + caster_frame_base: + Value: true + caster_swivel_left: + Value: true + caster_swivel_right: + Value: true + caster_wheel_left: + Value: true + caster_wheel_right: + Value: true + chassis_link: + Value: true + odom: + Value: true + wheel_left: + Value: true + wheel_right: + Value: true + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + odom: + base_link: + camera_left_ROS_frame: + {} + camera_right_ROS_frame: + {} + camera_sensor_left: + {} + camera_sensor_right: + {} + carter_lidar: + {} + chassis_link: + caster_frame_base: + caster_swivel_left: + caster_wheel_left: + {} + caster_swivel_right: + caster_wheel_right: + {} + wheel_left: + {} + wheel_right: + {} + Update Interval: 0 + Value: true + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 100 + Reference Frame: + Value: true + - Class: rviz_common/Group + Displays: + - Ceiling Height: 1.5 + Class: nvblox_rviz_plugin/NvbloxMesh + Cut Ceiling: true + Enabled: true + Mesh Color: Color + Shading + Name: NvbloxMesh + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/mesh + Value: true + - Class: rviz_default_plugins/MarkerArray + Enabled: false + Name: Mesh Marker + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/mesh_marker + Value: false + - Class: rviz_default_plugins/Marker + Enabled: false + Name: Map Slice Bounds + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/map_slice_bounds + Value: false + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 2 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Esdf Slice + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/esdf_pointcloud + Use Fixed Frame: true + Use rainbow: true + Value: false + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 6.700000286102295 + Min Value: -0.3499999940395355 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: AxisColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 1 + Min Color: 0; 0; 0 + Min Intensity: 1 + Name: Occupancy + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.05000000074505806 + Style: Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/occupancy + Use Fixed Frame: true + Use rainbow: true + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Color Overlay + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/color_frame_overlay + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Depth Overlay + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/depth_frame_overlay + Value: true + Enabled: true + Name: Nvblox + - Class: rviz_common/Group + Displays: + - Class: rviz_default_plugins/Marker + Enabled: false + Name: Human Voxels + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/human_voxels + Value: false + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 0; 0; 255 + Max Intensity: 0.9878048896789551 + Min Color: 255; 255; 255 + Min Intensity: 0.6216533184051514 + Name: Human Occupancy + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.05000000074505806 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/human_occupancy + Use Fixed Frame: true + Use rainbow: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: false + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 2 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Human Esdf Slice + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/human_esdf_pointcloud + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 2 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Combined Esdf Slice + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/combined_esdf_pointcloud + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Name: Humans + - Class: rviz_common/Group + Displays: + - Class: rviz_common/Group + Displays: + - Alpha: 0.699999988079071 + Class: rviz_default_plugins/Map + Color Scheme: costmap + Draw Behind: false + Enabled: false + Name: Global Costmap + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /global_costmap/costmap + Update Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /global_costmap/costmap_updates + Use Timestamp: false + Value: false + - Alpha: 1 + Buffer Length: 1 + Class: rviz_default_plugins/Path + Color: 25; 255; 0 + Enabled: true + Head Diameter: 0.30000001192092896 + Head Length: 0.20000000298023224 + Length: 0.30000001192092896 + Line Style: Billboards + Line Width: 0.10000000149011612 + Name: Global Path + Offset: + X: 0 + Y: 0 + Z: 0 + Pose Color: 255; 85; 255 + Pose Style: None + Radius: 0.029999999329447746 + Shaft Diameter: 0.10000000149011612 + Shaft Length: 0.10000000149011612 + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /plan + Value: true + Enabled: true + Name: Global Navigation + - Class: rviz_common/Group + Displays: + - Alpha: 1 + Buffer Length: 1 + Class: rviz_default_plugins/Path + Color: 239; 41; 41 + Enabled: true + Head Diameter: 0.30000001192092896 + Head Length: 0.20000000298023224 + Length: 0.30000001192092896 + Line Style: Billboards + Line Width: 0.029999999329447746 + Name: Local Path + Offset: + X: 0 + Y: 0 + Z: 0 + Pose Color: 255; 85; 255 + Pose Style: None + Radius: 0.029999999329447746 + Shaft Diameter: 0.10000000149011612 + Shaft Length: 0.10000000149011612 + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /local_plan + Value: true + - Alpha: 0.699999988079071 + Class: rviz_default_plugins/Map + Color Scheme: costmap + Draw Behind: false + Enabled: true + Name: Local Costmap + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /local_costmap/costmap + Update Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /local_costmap/costmap_updates + Use Timestamp: false + Value: true + Enabled: false + Name: Local Navigation + Enabled: true + Name: Nav2 + - Class: rviz_common/Group + Displays: + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 15 + Median window: 5 + Min Value: 0 + Name: Depth Image + Normalize Range: false + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /front/stereo_camera/right/depth + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Color Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /front/stereo_camera/left/rgb + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Semantic Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /semantic_conversion/front/stereo_camera/left/semantic_colorized + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 0 + Min Value: -0.5 + Value: false + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: AxisColor + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: LiDAR Pointcloud + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /point_cloud + Use Fixed Frame: true + Use rainbow: true + Value: false + Enabled: false + Name: Sensors + Enabled: true + Global Options: + Background Color: 212; 212; 212 + Fixed Frame: odom + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 23.513273239135742 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0.8349463939666748 + Y: -3.4285402297973633 + Z: -3.8615450859069824 + Focal Shape Fixed Size: false + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.8947959542274475 + Target Frame: base_link + Value: Orbit (rviz) + Yaw: 3.0904006958007812 + Saved: ~ +Window Geometry: + Color Image: + collapsed: false + Color Overlay: + collapsed: false + Depth Image: + collapsed: false + Depth Overlay: + collapsed: false + Displays: + collapsed: false + Height: 1136 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd00000004000000000000018a0000041afc0200000012fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b0000041a000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb00000010007200670062005f006c00650066007402000008f4000000fe000003740000026efb0000001400640065007000740068005f006c0065006600740200000c8c000001e70000014600000125fb0000000a0049006d0061006700650200000c900000023f0000025e000001d3fb0000000c00430061006d0065007200610200000c2f00000104000001e600000190fb000000160044006500700074006800200049006d0061006700650200000592000002ea0000018800000148fb000000160043006f006c006f007200200049006d00610067006502000005920000019f000001850000013bfb0000001a0043006f006c006f00720020004f007600650072006c0061007903000005910000019f000001880000013cfb0000001a004400650070007400680020004f007600650072006c006100790300000591000002eb0000018c00000145fb0000000a0049006d0061006700650100000385000000d00000000000000000fb0000001c00530065006d0061006e00740069006300200049006d0061006700650200000410000002ee0000017800000144000000010000010f00000435fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b00000435000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000005a80000041a00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Semantic Image: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1848 + X: 72 + Y: 27 diff --git a/nvblox_examples/nvblox_examples_bringup/config/rviz/realsense_example.rviz b/nvblox_examples/nvblox_examples_bringup/config/rviz/realsense_example.rviz new file mode 100644 index 00000000..9cef7578 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/config/rviz/realsense_example.rviz @@ -0,0 +1,348 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Status1 + - /TF1/Frames1 + - /TF1/Tree1 + - /Nvblox1 + Splitter Ratio: 0.5896806120872498 + Tree Height: 913 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Class: rviz_default_plugins/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + camera_aligned_depth_to_infra1_frame: + Value: true + camera_color_frame: + Value: true + camera_color_optical_frame: + Value: true + camera_depth_frame: + Value: true + camera_depth_optical_frame: + Value: true + camera_infra1_frame: + Value: true + camera_infra1_optical_frame: + Value: true + camera_infra2_frame: + Value: true + camera_infra2_optical_frame: + Value: true + camera_link: + Value: true + odom: + Value: true + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + Update Interval: 0 + Value: true + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 100 + Reference Frame: + Value: true + - Class: rviz_common/Group + Displays: + - Ceiling Height: 1.5 + Class: nvblox_rviz_plugin/NvbloxMesh + Cut Ceiling: true + Enabled: true + Mesh Color: Color + Shading + Name: NvbloxMesh + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/mesh + Value: true + - Class: rviz_default_plugins/MarkerArray + Enabled: false + Name: Mesh Marker + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/mesh_marker + Value: false + - Class: rviz_default_plugins/Marker + Enabled: false + Name: Map Slice Bounds + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/map_slice_bounds + Value: false + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 2 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Esdf Slice + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/esdf_pointcloud + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 6.700000286102295 + Min Value: -0.3499999940395355 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: AxisColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 1 + Min Color: 0; 0; 0 + Min Intensity: 1 + Name: Occupancy + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.05000000074505806 + Style: Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_node/occupancy + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Name: Nvblox + - Class: rviz_common/Group + Displays: + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 0; 255 + Color Transformer: FlatColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Observation Cloud + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.05000000074505806 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /visual_slam/vis/observations_cloud + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Name: Vslam + - Class: rviz_common/Group + Displays: + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Depth Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Best Effort + Value: /camera/realsense_splitter_node/output/depth + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Color Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Best Effort + Value: /camera/color/image_raw + Value: true + Enabled: true + Name: Sensors + Enabled: true + Global Options: + Background Color: 212; 212; 212 + Fixed Frame: odom + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 14.100801467895508 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 3.7574188709259033 + Y: -1.3805701732635498 + Z: -2.184345245361328 + Focal Shape Fixed Size: false + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.9597960710525513 + Target Frame: camera_link + Value: Orbit (rviz) + Yaw: 3.2903997898101807 + Saved: ~ +Window Geometry: + Color Image: + collapsed: false + Depth Image: + collapsed: false + Displays: + collapsed: false + Height: 1136 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000001560000041afc0200000012fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b0000041a000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb00000010007200670062005f006c00650066007402000008f4000000fe000003740000026efb0000001400640065007000740068005f006c0065006600740200000c8c000001e70000014600000125fb0000000a0049006d0061006700650200000c900000023f0000025e000001d3fb0000000c00430061006d0065007200610200000c2f00000104000001e600000190fb000000160044006500700074006800200049006d0061006700650300000592000002ea0000018800000148fb000000160043006f006c006f007200200049006d00610067006503000005920000019f000001850000013bfb0000001a0043006f006c006f00720020004f007600650072006c0061007902000005910000019f000001880000013cfb0000001a004400650070007400680020004f007600650072006c006100790200000591000002eb0000018c00000145fb0000000a0049006d0061006700650100000385000000d00000000000000000fb0000001c00530065006d0061006e00740069006300200049006d0061006700650200000410000002ee0000017800000144000000010000010f00000435fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b00000435000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000005dc0000041a00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1848 + X: 72 + Y: 27 diff --git a/nvblox_examples/nvblox_examples_bringup/config/rviz/realsense_humans_example.rviz b/nvblox_examples/nvblox_examples_bringup/config/rviz/realsense_humans_example.rviz new file mode 100755 index 00000000..7d993886 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/config/rviz/realsense_humans_example.rviz @@ -0,0 +1,550 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Status1 + - /TF1/Frames1 + - /TF1/Tree1 + - /Nvblox1 + - /Humans1 + Splitter Ratio: 0.5896806120872498 + Tree Height: 913 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Class: rviz_default_plugins/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + camera_aligned_depth_to_infra1_frame: + Value: true + camera_color_frame: + Value: true + camera_color_optical_frame: + Value: true + camera_depth_frame: + Value: true + camera_depth_optical_frame: + Value: true + camera_infra1_frame: + Value: true + camera_infra1_optical_frame: + Value: true + camera_infra2_frame: + Value: true + camera_infra2_optical_frame: + Value: true + camera_link: + Value: true + odom: + Value: true + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + Update Interval: 0 + Value: true + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 100 + Reference Frame: + Value: true + - Class: rviz_common/Group + Displays: + - Ceiling Height: 1.5 + Class: nvblox_rviz_plugin/NvbloxMesh + Cut Ceiling: true + Enabled: true + Mesh Color: Color + Shading + Name: NvbloxMesh + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/mesh + Value: true + - Class: rviz_default_plugins/MarkerArray + Enabled: false + Name: Mesh Marker + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/mesh_marker + Value: false + - Class: rviz_default_plugins/Marker + Enabled: false + Name: Map Slice Bounds + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/map_slice_bounds + Value: false + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 2 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Esdf Slice + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/esdf_pointcloud + Use Fixed Frame: true + Use rainbow: true + Value: false + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 6.700000286102295 + Min Value: -0.3499999940395355 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: AxisColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 1 + Min Color: 0; 0; 0 + Min Intensity: 1 + Name: Occupancy + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.05000000074505806 + Style: Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/occupancy + Use Fixed Frame: true + Use rainbow: true + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Color Overlay + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/color_frame_overlay + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Depth Overlay + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/depth_frame_overlay + Value: true + Enabled: true + Name: Nvblox + - Class: rviz_common/Group + Displays: + - Class: rviz_default_plugins/Marker + Enabled: false + Name: Human Voxels + Namespaces: + {} + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/human_voxels + Value: false + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 0; 0; 255 + Max Intensity: 0.9878048896789551 + Min Color: 255; 255; 255 + Min Intensity: 0.5066614151000977 + Name: Human Occupancy + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.05000000074505806 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/human_occupancy + Use Fixed Frame: true + Use rainbow: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: false + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 2 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Human Esdf Slice + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/human_esdf_pointcloud + Use Fixed Frame: true + Use rainbow: true + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 2 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Combined Esdf Slice + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /nvblox_human_node/combined_esdf_pointcloud + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Name: Humans + - Class: rviz_common/Group + Displays: + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 0; 255 + Color Transformer: FlatColor + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: Observation Cloud + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.05000000074505806 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /visual_slam/vis/observations_cloud + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: false + Name: Vslam + - Class: rviz_common/Group + Displays: + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Depth Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Best Effort + Value: /camera/realsense_splitter_node/output/depth + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Color Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Best Effort + Value: /camera/color/image_raw + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Semantic Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /unet/colored_segmentation_mask + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 0 + Min Value: -0.5 + Value: false + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: AxisColor + Decay Time: 0 + Enabled: false + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: LiDAR Pointcloud + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.02500000037252903 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /point_cloud + Use Fixed Frame: true + Use rainbow: true + Value: false + Enabled: false + Name: Sensors + Enabled: true + Global Options: + Background Color: 212; 212; 212 + Fixed Frame: odom + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 14.100801467895508 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 3.7574188709259033 + Y: -1.3805701732635498 + Z: -2.184345245361328 + Focal Shape Fixed Size: false + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.9597960710525513 + Target Frame: camera_link + Value: Orbit (rviz) + Yaw: 3.2903997898101807 + Saved: ~ +Window Geometry: + Color Image: + collapsed: false + Color Overlay: + collapsed: false + Depth Image: + collapsed: false + Depth Overlay: + collapsed: false + Displays: + collapsed: false + Height: 1136 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000001990000041afc0200000012fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b0000041a000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb00000010007200670062005f006c00650066007402000008f4000000fe000003740000026efb0000001400640065007000740068005f006c0065006600740200000c8c000001e70000014600000125fb0000000a0049006d0061006700650200000c900000023f0000025e000001d3fb0000000c00430061006d0065007200610200000c2f00000104000001e600000190fb000000160044006500700074006800200049006d0061006700650200000592000002ea0000018800000148fb000000160043006f006c006f007200200049006d00610067006502000005920000019f000001850000013bfb0000001a0043006f006c006f00720020004f007600650072006c0061007903000005910000019f000001880000013cfb0000001a004400650070007400680020004f007600650072006c006100790300000591000002eb0000018c00000145fb0000000a0049006d0061006700650100000385000000d00000000000000000fb0000001c00530065006d0061006e00740069006300200049006d0061006700650200000410000002ee0000017800000144000000010000010f00000435fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b00000435000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000005990000041a00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Semantic Image: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1848 + X: 72 + Y: 27 diff --git a/nvblox_examples/nvblox_examples_bringup/config/realsense.yaml b/nvblox_examples/nvblox_examples_bringup/config/sensors/realsense.yaml similarity index 100% rename from nvblox_examples/nvblox_examples_bringup/config/realsense.yaml rename to nvblox_examples/nvblox_examples_bringup/config/sensors/realsense.yaml diff --git a/nvblox_examples/nvblox_examples_bringup/launch/isaac_sim_example.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/isaac_sim_example.launch.py new file mode 100644 index 00000000..5077d5fe --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/isaac_sim_example.launch.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription +from launch.conditions import IfCondition +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration + + +def generate_launch_description(): + + bringup_dir = get_package_share_directory('nvblox_examples_bringup') + + # Launch Arguments + run_rviz_arg = DeclareLaunchArgument( + 'run_rviz', default_value='True', + description='Whether to start RVIZ') + run_nav2_arg = DeclareLaunchArgument( + 'run_nav2', default_value='True', + description='Whether to run nav2') + + # Nav2 + nav2_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource(os.path.join( + bringup_dir, 'launch', 'nav2', 'nav2_isaac_sim.launch.py')), + condition=IfCondition(LaunchConfiguration('run_nav2'))) + + # Nvblox + nvblox_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([ + os.path.join(bringup_dir, 'launch', 'nvblox', 'nvblox.launch.py')]), + launch_arguments={'setup_for_isaac_sim': 'True'}.items()) + + # Rviz + rviz_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'rviz', 'rviz.launch.py')]), + launch_arguments={ + 'config_name': 'isaac_sim_example.rviz'}.items(), + condition=IfCondition(LaunchConfiguration('run_rviz'))) + + return LaunchDescription([ + run_rviz_arg, + run_nav2_arg, + nav2_launch, + nvblox_launch, + rviz_launch]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/isaac_sim_humans_example.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/isaac_sim_humans_example.launch.py new file mode 100644 index 00000000..542e2ed9 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/isaac_sim_humans_example.launch.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, GroupAction +from launch.conditions import IfCondition +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import SetRemap + + +def generate_launch_description(): + + bringup_dir = get_package_share_directory('nvblox_examples_bringup') + + # Launch Arguments + run_rviz_arg = DeclareLaunchArgument( + 'run_rviz', default_value='True', + description='Whether to start RVIZ') + run_nav2_arg = DeclareLaunchArgument( + 'run_nav2', default_value='True', + description='Whether to run nav2') + + # Nav2 + nav2_launch = GroupAction([ + SetRemap(src=['/nvblox_node/map_slice'], + dst=['/nvblox_human_node/map_slice']), + IncludeLaunchDescription( + PythonLaunchDescriptionSource(os.path.join( + bringup_dir, 'launch', 'nav2', 'nav2_isaac_sim.launch.py')), + condition=IfCondition(LaunchConfiguration('run_nav2')))]) + + # Semantic label conversion (convert Isaac Sim labels to mask image) + semantic_label_conversion_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + get_package_share_directory('semantic_label_conversion'), 'launch', + 'semantic_label_conversion.launch.py')])) + + # Nvblox with humans + nvblox_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'nvblox', 'nvblox_humans.launch.py')]), + launch_arguments={'setup_for_isaac_sim': 'True'}.items()) + + # Rviz + rviz_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'rviz', 'rviz.launch.py')]), + launch_arguments={ + 'config_name': 'isaac_sim_humans_example.rviz'}.items(), + condition=IfCondition(LaunchConfiguration('run_rviz'))) + + return LaunchDescription([ + run_rviz_arg, + run_nav2_arg, + nav2_launch, + semantic_label_conversion_launch, + nvblox_launch, + rviz_launch]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/nav2/nav2_isaac_sim.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/nav2/nav2_isaac_sim.launch.py new file mode 100644 index 00000000..0c1cba46 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/nav2/nav2_isaac_sim.launch.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource + + +def generate_launch_description(): + + nvblox_bringup_dir = get_package_share_directory('nvblox_examples_bringup') + nav2_bringup_dir = get_package_share_directory('nav2_bringup') + + # Config file + nav2_param_file = os.path.join(nvblox_bringup_dir, + 'config', 'nav2', 'nav2_isaac_sim.yaml') + + # Nav2 launch + nav2_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(nav2_bringup_dir, 'launch', 'navigation_launch.py')), + launch_arguments={'use_sim_time': 'True', + 'params_file': nav2_param_file, + 'autostart': 'True'}.items()) + + return LaunchDescription([nav2_launch]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/nvblox/nvblox.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/nvblox/nvblox.launch.py new file mode 100644 index 00000000..e1326a79 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/nvblox/nvblox.launch.py @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import GroupAction +from launch.conditions import IfCondition +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import (ComposableNodeContainer, SetParameter, + SetParametersFromFile, SetRemap) +from launch_ros.descriptions import ComposableNode + + +def generate_launch_description(): + + bringup_dir = get_package_share_directory('nvblox_examples_bringup') + base_config_dir = os.path.join(bringup_dir, 'config', 'nvblox') + specialization_dir = os.path.join(base_config_dir, 'specializations') + + # Config files + base_config = os.path.join(base_config_dir, 'nvblox_base.yaml') + realsense_config = os.path.join( + specialization_dir, 'nvblox_realsense.yaml') + simulation_config = os.path.join( + specialization_dir, 'nvblox_isaac_sim.yaml') + + # Conditionals for setup + setup_for_isaac_sim = IfCondition( + LaunchConfiguration('setup_for_isaac_sim', default='False')) + setup_for_realsense = IfCondition( + LaunchConfiguration('setup_for_realsense', default='False')) + + # Nvblox node + node = ComposableNode( + name='nvblox_node', + package='nvblox_ros', + plugin='nvblox::NvbloxNode') + + # Nvblox node container + nvblox_container = ComposableNodeContainer( + name='nvblox_container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[node], + output='screen') + + group_action = GroupAction([ + + # Set parameters with specializations + SetParametersFromFile(base_config), + SetParametersFromFile(realsense_config, + condition=setup_for_realsense), + SetParametersFromFile(simulation_config, + condition=setup_for_isaac_sim), + SetParameter(name='global_frame', + value=LaunchConfiguration('global_frame', default='odom')), + + # Remappings for realsense data + SetRemap(src=['depth/image'], + dst=['/camera/realsense_splitter_node/output/depth'], + condition=setup_for_realsense), + SetRemap(src=['depth/camera_info'], + dst=['/camera/depth/camera_info'], + condition=setup_for_realsense), + SetRemap(src=['color/image'], + dst=['/camera/color/image_raw'], + condition=setup_for_realsense), + SetRemap(src=['color/camera_info'], + dst=['/camera/color/camera_info'], + condition=setup_for_realsense), + + # Remappings for isaac sim data + SetRemap(src=['depth/image'], + dst=['/front/stereo_camera/left/depth'], + condition=setup_for_isaac_sim), + SetRemap(src=['depth/camera_info'], + dst=['/front/stereo_camera/left/camera_info'], + condition=setup_for_isaac_sim), + SetRemap(src=['color/image'], + dst=['/front/stereo_camera/left/rgb'], + condition=setup_for_isaac_sim), + SetRemap(src=['color/camera_info'], + dst=['/front/stereo_camera/left/camera_info'], + condition=setup_for_isaac_sim), + SetRemap(src=['pointcloud'], + dst=['/point_cloud'], + condition=setup_for_isaac_sim), + + # Include the node container + nvblox_container + ]) + + return LaunchDescription([group_action]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/nvblox/nvblox_humans.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/nvblox/nvblox_humans.launch.py new file mode 100644 index 00000000..f6747eaa --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/nvblox/nvblox_humans.launch.py @@ -0,0 +1,124 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import GroupAction +from launch.conditions import IfCondition +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import (ComposableNodeContainer, SetParameter, + SetParametersFromFile, SetRemap) +from launch_ros.descriptions import ComposableNode + + +def generate_launch_description(): + + bringup_dir = get_package_share_directory('nvblox_examples_bringup') + base_config_dir = os.path.join(bringup_dir, 'config', 'nvblox') + specialization_dir = os.path.join(base_config_dir, 'specializations') + + # Config files + base_config = os.path.join(base_config_dir, 'nvblox_base.yaml') + humans_config = os.path.join(specialization_dir, 'nvblox_humans.yaml') + realsense_config = os.path.join( + specialization_dir, 'nvblox_realsense.yaml') + simulation_config = os.path.join( + specialization_dir, 'nvblox_isaac_sim.yaml') + + # Conditionals for setup + setup_for_isaac_sim = IfCondition( + LaunchConfiguration('setup_for_isaac_sim', default='False')) + setup_for_realsense = IfCondition( + LaunchConfiguration('setup_for_realsense', default='False')) + + # Nvblox node + node = ComposableNode( + name='nvblox_human_node', + package='nvblox_ros', + plugin='nvblox::NvbloxHumanNode') + + # Nvblox node container + nvblox_human_container = ComposableNodeContainer( + name='nvblox_human_container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[node], + output='screen') + + group_action = GroupAction([ + + # Set parameters with specializations + SetParametersFromFile(base_config), + SetParametersFromFile(humans_config), + SetParametersFromFile(realsense_config, + condition=setup_for_realsense), + SetParametersFromFile(simulation_config, + condition=setup_for_isaac_sim), + SetParameter(name='global_frame', + value=LaunchConfiguration('global_frame', default='odom')), + + # Remappings for realsense data + SetRemap(src=['depth/image'], + dst=['/camera/realsense_splitter_node/output/depth'], + condition=setup_for_realsense), + SetRemap(src=['depth/camera_info'], + dst=['/camera/depth/camera_info'], + condition=setup_for_realsense), + SetRemap(src=['color/image'], + dst=['/camera/color/image_raw'], + condition=setup_for_realsense), + SetRemap(src=['color/camera_info'], + dst=['/camera/color/camera_info'], + condition=setup_for_realsense), + SetRemap(src=['mask/image'], + dst=['/unet/raw_segmentation_mask_depadded'], + condition=setup_for_realsense), + SetRemap(src=['mask/camera_info'], + dst=['/camera/color/camera_info'], + condition=setup_for_realsense), + + # Remappings for isaac sim data + SetRemap(src=['depth/image'], + dst=['/front/stereo_camera/right/depth'], + condition=setup_for_isaac_sim), + SetRemap(src=['depth/camera_info'], + dst=['/front/stereo_camera/right/camera_info'], + condition=setup_for_isaac_sim), + SetRemap(src=['color/image'], + dst=['/front/stereo_camera/left/rgb'], + condition=setup_for_isaac_sim), + SetRemap(src=['color/camera_info'], + dst=['/front/stereo_camera/left/camera_info'], + condition=setup_for_isaac_sim), + SetRemap(src=['mask/image'], + dst=['/semantic_conversion/front/stereo_camera/left/semantic_mono8'], + condition=setup_for_isaac_sim), + SetRemap(src=['mask/camera_info'], + dst=['/front/stereo_camera/left/camera_info'], + condition=setup_for_isaac_sim), + SetRemap(src=['pointcloud'], + dst=['/point_cloud'], + condition=setup_for_isaac_sim), + + # Include the node container + nvblox_human_container + ]) + + return LaunchDescription([group_action]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/nvblox_vslam_realsense.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/nvblox_vslam_realsense.launch.py deleted file mode 100644 index 6ddea6e7..00000000 --- a/nvblox_examples/nvblox_examples_bringup/launch/nvblox_vslam_realsense.launch.py +++ /dev/null @@ -1,173 +0,0 @@ -# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# 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. -# -# SPDX-License-Identifier: Apache-2.0 - -import os - -from ament_index_python.packages import get_package_share_directory - -from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.substitutions import LaunchConfiguration -from launch_ros.actions import Node -from launch_ros.descriptions import ComposableNode -from launch_ros.actions import ComposableNodeContainer - - -def generate_launch_description(): - - # RealSense - realsense_config_file_path = os.path.join( - get_package_share_directory('nvblox_examples_bringup'), - 'config', 'realsense.yaml' - ) - - realsense_node = ComposableNode( - namespace="camera", - package='realsense2_camera', - plugin='realsense2_camera::RealSenseNodeFactory', - parameters=[realsense_config_file_path], - ) - - realsense_splitter_node = ComposableNode( - namespace="camera", - name='realsense_splitter_node', - package='realsense_splitter', - plugin='nvblox::RealsenseSplitterNode', - parameters=[{ - 'input_qos': 'SENSOR_DATA', - 'output_qos': 'SENSOR_DATA' - }], - remappings=[('input/infra_1', '/camera/infra1/image_rect_raw'), - ('input/infra_1_metadata', '/camera/infra1/metadata'), - ('input/infra_2', '/camera/infra2/image_rect_raw'), - ('input/infra_2_metadata', '/camera/infra2/metadata'), - ('input/depth', '/camera/depth/image_rect_raw'), - ('input/depth_metadata', '/camera/depth/metadata'), - ('input/pointcloud', '/camera/depth/color/points'), - ('input/pointcloud_metadata', '/camera/depth/metadata'), - ] - ) - - realsense_container = ComposableNodeContainer( - name='realsense_container', - namespace='', - package='rclcpp_components', - executable='component_container', - composable_node_descriptions=[ - realsense_node, - realsense_splitter_node - ], - output='screen' - ) - - # VSLAM - visual_slam_node = ComposableNode( - name='visual_slam_node', - package='isaac_ros_visual_slam', - plugin='isaac_ros::visual_slam::VisualSlamNode', - parameters=[{ - 'enable_rectified_pose': True, - 'denoise_input_images': False, - 'rectified_images': True, - 'enable_debug_mode': False, - 'debug_dump_path': '/tmp/vslam', - 'enable_slam_visualization': True, - 'enable_landmarks_view': True, - 'enable_observations_view': True, - 'map_frame': 'map', - 'odom_frame': 'odom', - 'base_frame': 'base_link', - 'enable_localization_n_mapping': True, - 'publish_odom_to_base_tf': True, - 'publish_map_to_odom_tf': True, - 'image_qos': 'SENSOR_DATA' - }], - remappings=[('stereo_camera/left/image', '/camera/realsense_splitter_node/output/infra_1'), - ('stereo_camera/left/camera_info', '/camera/infra1/camera_info'), - ('stereo_camera/right/image', '/camera/realsense_splitter_node/output/infra_2'), - ('stereo_camera/right/camera_info', '/camera/infra2/camera_info')] - ) - - vslam_container = ComposableNodeContainer( - name='vslam_container', - namespace='', - package='rclcpp_components', - executable='component_container', - composable_node_descriptions=[ - visual_slam_node - ], - output='screen' - ) - - base_link_tf_node = Node( - package='tf2_ros', - executable='static_transform_publisher', - arguments=[ - '0', '0', '0', '0', '0', '0', '1', - 'base_link', 'camera_link'] - ) - - # Nvblox - nvblox_config = DeclareLaunchArgument( - 'nvblox_config', default_value=os.path.join( - get_package_share_directory( - 'nvblox_examples_bringup'), 'config', 'nvblox.yaml' - ) - ) - - nvblox_node = ComposableNode( - name='nvblox_node', - package='nvblox_ros', - plugin='nvblox::NvbloxNode', - parameters=[LaunchConfiguration('nvblox_config')], - remappings=[ - ("depth/camera_info", "/camera/depth/camera_info"), - ("depth/image", "/camera/realsense_splitter_node/output/depth"), - ("color/camera_info", "/camera/color/camera_info"), - ("color/image", "/camera/color/image_raw") - ] - ) - - nvblox_container = ComposableNodeContainer( - name='nvblox_container', - namespace='', - package='rclcpp_components', - executable='component_container', - composable_node_descriptions=[ - nvblox_node - ], - output='screen' - ) - - # RVIZ - rviz_config_path = os.path.join(get_package_share_directory( - 'nvblox_examples_bringup'), 'config', 'nvblox_vslam_realsense.rviz') - - rviz = Node( - package='rviz2', - executable='rviz2', - arguments=['-d', rviz_config_path], - output='screen') - - return LaunchDescription([ - nvblox_config, - realsense_container, - vslam_container, - nvblox_container, - base_link_tf_node, - rviz - ]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/perception/segmentation.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/perception/segmentation.launch.py new file mode 100644 index 00000000..828d8901 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/perception/segmentation.launch.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import GroupAction, IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.actions import SetRemap, Node + + +def generate_launch_description(): + + segnet_bringup_dir = get_package_share_directory('isaac_ros_unet') + + # Semantic segmentation + segnet_launch = GroupAction([ + SetRemap(src=['image'], + dst=['/camera/color/image_raw_padded']), + + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(segnet_bringup_dir, + 'launch', 'isaac_ros_unet_triton.launch.py')), + launch_arguments={'model_name': 'peoplesemsegnet', + 'model_repository_paths': "['/workspaces/isaac_ros-dev/models']", + 'input_binding_names': "['input_1:0']", + 'output_binding_names': "['argmax_1']", + 'network_output_type': 'argmax'}.items()) + ]) + + image_padding_node = Node( + name='image_padding_node', + package='nvblox_image_padding', + executable='image_padding_cropping_node', + parameters=[{'image_qos': 'SYSTEM_DEFAULT', + 'desired_height': 544, + 'desired_width': 960 + }], + remappings=[('~/image_in', '/camera/color/image_raw'), + ('~/image_out', '/camera/color/image_raw_padded')]) + + image_cropping_node = Node( + name='image_cropping_node', + package='nvblox_image_padding', + executable='image_padding_cropping_node', + parameters=[{'image_qos': 'SYSTEM_DEFAULT', + 'desired_height': 480, + 'desired_width': 640 + }], + remappings=[('~/image_in', '/unet/raw_segmentation_mask'), + ('~/image_out', '/unet/raw_segmentation_mask_depadded')]) + + return LaunchDescription([ + segnet_launch, + image_padding_node, + image_cropping_node]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/perception/vslam.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/perception/vslam.launch.py new file mode 100644 index 00000000..c43152a6 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/perception/vslam.launch.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +from launch import LaunchDescription +from launch_ros.actions import ComposableNodeContainer +from launch_ros.descriptions import ComposableNode +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration + + +def generate_launch_description(): + + output_odom_frame_name_arg = DeclareLaunchArgument( + 'output_odom_frame_name', default_value='odom', + description='The name of the VSLAM output frame') + + # VSLAM node + visual_slam_node = ComposableNode( + name='visual_slam_node', + package='isaac_ros_visual_slam', + plugin='isaac_ros::visual_slam::VisualSlamNode', + parameters=[{ + 'enable_rectified_pose': True, + 'denoise_input_images': False, + 'rectified_images': True, + 'enable_debug_mode': False, + 'debug_dump_path': '/tmp/elbrus', + 'enable_slam_visualization': True, + 'enable_landmarks_view': True, + 'enable_observations_view': True, + 'map_frame': 'map', + 'odom_frame': LaunchConfiguration('output_odom_frame_name'), + 'base_frame': 'camera_link', + 'enable_localization_n_mapping': False, + 'publish_odom_to_base_tf': True, + 'publish_map_to_odom_tf': False, + 'invert_odom_to_base_tf': True, + }], + remappings=[('stereo_camera/left/image', + '/camera/realsense_splitter_node/output/infra_1'), + ('stereo_camera/left/camera_info', + '/camera/infra1/camera_info'), + ('stereo_camera/right/image', + '/camera/realsense_splitter_node/output/infra_2'), + ('stereo_camera/right/camera_info', + '/camera/infra2/camera_info')]) + + # VSLAM container + vslam_container = ComposableNodeContainer( + name='vslam_container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + visual_slam_node + ], + output='screen') + + return LaunchDescription([ + output_odom_frame_name_arg, + vslam_container]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/realsense_example.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/realsense_example.launch.py new file mode 100644 index 00000000..78785e8a --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/realsense_example.launch.py @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import (DeclareLaunchArgument, ExecuteProcess, + IncludeLaunchDescription) +from launch.conditions import IfCondition, UnlessCondition +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration + + +def generate_launch_description(): + + bringup_dir = get_package_share_directory('nvblox_examples_bringup') + + # Launch Arguments + run_rviz_arg = DeclareLaunchArgument( + 'run_rviz', default_value='True', + description='Whether to start RVIZ') + from_bag_arg = DeclareLaunchArgument( + 'from_bag', default_value='False', + description='Whether to run from a bag or live realsense data') + bag_path_arg = DeclareLaunchArgument( + 'bag_path', default_value='rosbag2*', + description='Path of the bag (only used if from_bag == True)') + global_frame = LaunchConfiguration('global_frame', + default='odom') + + # Realsense + realsense_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'sensors', 'realsense.launch.py')]), + condition=UnlessCondition(LaunchConfiguration('from_bag'))) + + # Vslam + vslam_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'perception', 'vslam.launch.py')]), + launch_arguments={'output_odom_frame_name': global_frame}.items()) + + # Nvblox + nvblox_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'nvblox', 'nvblox.launch.py')]), + launch_arguments={'setup_for_realsense': 'True', + 'global_frame': global_frame + }.items()) + + # Ros2 bag + bag_play = ExecuteProcess( + cmd=['ros2', 'bag', 'play', LaunchConfiguration('bag_path')], + shell=True, output='screen', + condition=IfCondition(LaunchConfiguration('from_bag'))) + + # Rviz + rviz_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'rviz', 'rviz.launch.py')]), + launch_arguments={'config_name': 'realsense_example.rviz', + 'global_frame': global_frame}.items(), + condition=IfCondition(LaunchConfiguration('run_rviz'))) + + return LaunchDescription([ + run_rviz_arg, + from_bag_arg, + bag_path_arg, + realsense_launch, + vslam_launch, + nvblox_launch, + bag_play, + rviz_launch]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/realsense_humans_example.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/realsense_humans_example.launch.py new file mode 100644 index 00000000..a9cd1d95 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/realsense_humans_example.launch.py @@ -0,0 +1,95 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import (DeclareLaunchArgument, ExecuteProcess, + IncludeLaunchDescription) +from launch.conditions import IfCondition, UnlessCondition +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration + + +def generate_launch_description(): + + bringup_dir = get_package_share_directory('nvblox_examples_bringup') + + # Launch Arguments + run_rviz_arg = DeclareLaunchArgument( + 'run_rviz', default_value='True', + description='Whether to start RVIZ') + from_bag_arg = DeclareLaunchArgument( + 'from_bag', default_value='False', + description='Whether to run from a bag or live realsense data') + bag_path_arg = DeclareLaunchArgument( + 'bag_path', default_value='rosbag2*', + description='Path of the bag (only used if from_bag == True)') + global_frame = LaunchConfiguration('global_frame', + default='odom') + + # Realsense + realsense_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'sensors', 'realsense.launch.py')]), + condition=UnlessCondition(LaunchConfiguration('from_bag'))) + + # Vslam + vslam_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'perception', 'vslam.launch.py')]), + launch_arguments={'output_odom_frame_name': global_frame}.items()) + + # Segmentation + segmentation_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'perception', 'segmentation.launch.py')])) + + # Nvblox with humans + nvblox_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'nvblox', 'nvblox_humans.launch.py')]), + launch_arguments={'setup_for_realsense': 'True', + 'global_frame': global_frame, + }.items()) + + # Ros2 bag + bag_play = ExecuteProcess( + cmd=['ros2', 'bag', 'play', LaunchConfiguration('bag_path')], + shell=True, output='screen', + condition=IfCondition(LaunchConfiguration('from_bag'))) + + # Rviz + rviz_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'rviz', 'rviz.launch.py')]), + launch_arguments={ + 'config_name': 'realsense_humans_example.rviz', + 'global_frame': global_frame}.items(), + condition=IfCondition(LaunchConfiguration('run_rviz'))) + + return LaunchDescription([ + run_rviz_arg, + from_bag_arg, + bag_path_arg, + realsense_launch, + vslam_launch, + segmentation_launch, + nvblox_launch, + bag_play, + rviz_launch]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/rviz/rviz.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/rviz/rviz.launch.py new file mode 100644 index 00000000..f5521538 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/rviz/rviz.launch.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.substitutions import LaunchConfiguration, PathJoinSubstitution +from launch_ros.actions import Node + + +def generate_launch_description(): + + # Config + config_name = LaunchConfiguration('config_name', default='default.rviz') + config_path = PathJoinSubstitution([get_package_share_directory( + 'nvblox_examples_bringup'), 'config', 'rviz', config_name]) + global_frame = LaunchConfiguration('global_frame', default='odom') + + # Rviz node + rviz = Node( + package='rviz2', + executable='rviz2', + arguments=['-d', config_path, # set the config + '-f', global_frame], # overwrite the global frame + output='screen') + + return LaunchDescription([rviz]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/sensors/realsense.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/sensors/realsense.launch.py new file mode 100644 index 00000000..ba154d0e --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/sensors/realsense.launch.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch_ros.actions import ComposableNodeContainer +from launch_ros.descriptions import ComposableNode + + +def generate_launch_description(): + + # Config file + config_file = os.path.join( + get_package_share_directory('nvblox_examples_bringup'), + 'config', 'sensors', 'realsense.yaml') + + # Realsense driver node + realsense_node = ComposableNode( + namespace="camera", + package='realsense2_camera', + plugin='realsense2_camera::RealSenseNodeFactory', + parameters=[config_file]) + + # Realsense splitter node + realsense_splitter_node = ComposableNode( + namespace="camera", + name='realsense_splitter_node', + package='realsense_splitter', + plugin='nvblox::RealsenseSplitterNode', + parameters=[{ + 'input_qos': 'SENSOR_DATA', + 'output_qos': 'SENSOR_DATA' + }], + remappings=[('input/infra_1', '/camera/infra1/image_rect_raw'), + ('input/infra_1_metadata', '/camera/infra1/metadata'), + ('input/infra_2', '/camera/infra2/image_rect_raw'), + ('input/infra_2_metadata', '/camera/infra2/metadata'), + ('input/depth', '/camera/depth/image_rect_raw'), + ('input/depth_metadata', '/camera/depth/metadata'), + ('input/pointcloud', '/camera/depth/color/points'), + ('input/pointcloud_metadata', '/camera/depth/metadata'), + ]) + + # Container + realsense_container = ComposableNodeContainer( + name='realsense_container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + realsense_node, + realsense_splitter_node + ], + output='screen') + + return LaunchDescription([realsense_container]) diff --git a/nvblox_examples/nvblox_examples_bringup/launch/sensors/record_realsense.launch.py b/nvblox_examples/nvblox_examples_bringup/launch/sensors/record_realsense.launch.py new file mode 100644 index 00000000..d3eea6b1 --- /dev/null +++ b/nvblox_examples/nvblox_examples_bringup/launch/sensors/record_realsense.launch.py @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import (DeclareLaunchArgument, ExecuteProcess, + IncludeLaunchDescription, LogInfo, TimerAction) +from launch.conditions import IfCondition +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node + + +def generate_launch_description(): + + bringup_dir = get_package_share_directory('nvblox_examples_bringup') + + # Launch Arguments + launch_realsense_arg = DeclareLaunchArgument( + 'launch_realsense', default_value='True', + description='Whether to launch the realsense driver') + run_rqt_arg = DeclareLaunchArgument( + 'run_rqt', default_value='True', + description='Whether to start rqt_image_view') + + # Realsense + realsense_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([os.path.join( + bringup_dir, 'launch', 'sensors', 'realsense.launch.py')]), + condition=IfCondition(LaunchConfiguration('launch_realsense'))) + + # Bag recording + realsense_topics = [ + '/tf_static', + '/camera/color/camera_info', + '/camera/color/image_raw', + '/camera/realsense_splitter_node/output/depth', + '/camera/depth/camera_info', + '/camera/realsense_splitter_node/output/infra_1', + '/camera/infra1/camera_info', + '/camera/realsense_splitter_node/output/infra_2', + '/camera/infra2/camera_info' + ] + bag_record = ExecuteProcess( + cmd=['ros2', 'bag', 'record', " ".join(realsense_topics)], + shell=True, output='screen') + + # Rqt + rqt_launch = Node( + package='rqt_image_view', + executable='rqt_image_view', + name='rqt_image_view', + condition=IfCondition(LaunchConfiguration('run_rqt'))) + + recording_started_msg =\ + '''\n\n\n + ----------------------------------------------------- + BAG RECORDING IS STARTING NOW + + (make sure the realsense node is up) + ----------------------------------------------------- + \n\n\n''' + return LaunchDescription([ + launch_realsense_arg, + run_rqt_arg, + + # Start the realsense and rqt image view + realsense_launch, + rqt_launch, + + # Start recording after 10 sec. + TimerAction( + period=10.0, + actions=[ + LogInfo(msg=recording_started_msg), + bag_record + ])]) diff --git a/nvblox_examples/nvblox_examples_bringup/package.xml b/nvblox_examples/nvblox_examples_bringup/package.xml index 02ea8f5b..b1a17061 100644 --- a/nvblox_examples/nvblox_examples_bringup/package.xml +++ b/nvblox_examples/nvblox_examples_bringup/package.xml @@ -21,7 +21,7 @@ nvblox_examples_bringup - 0.20.0 + 0.30.0 Launchfiles for nvblox examples Helen Oleynikova @@ -33,8 +33,6 @@ Alexander Millane ament_cmake - realsense_splitter - realsense2_camera ament_lint_auto ament_lint_common diff --git a/nvblox_examples/nvblox_image_padding/CMakeLists.txt b/nvblox_examples/nvblox_image_padding/CMakeLists.txt new file mode 100644 index 00000000..5c7027f7 --- /dev/null +++ b/nvblox_examples/nvblox_image_padding/CMakeLists.txt @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +cmake_minimum_required(VERSION 3.5) +project(nvblox_image_padding) + +set(CMAKE_CXX_STANDARD 17) + +add_compile_options(-Wall -Wextra -Wpedantic) + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_components REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(cv_bridge REQUIRED) +find_package(nvblox_ros_common REQUIRED) + +############# +# LIBRARIES # +############# +add_library(${PROJECT_NAME}_lib SHARED + src/image_padding_cropping_node.cpp +) +ament_target_dependencies(${PROJECT_NAME}_lib + rclcpp + rclcpp_components + sensor_msgs + cv_bridge + nvblox_ros_common +) +target_include_directories(${PROJECT_NAME}_lib PUBLIC + $ + $) + +rclcpp_components_register_nodes(${PROJECT_NAME}_lib "nvblox::ImagePaddingCroppingNode") + +############ +# BINARIES # +############ +add_executable(image_padding_cropping_node + src/image_padding_cropping_node_main.cpp +) +target_link_libraries(image_padding_cropping_node ${PROJECT_NAME}_lib) + +########### +# INSTALL # +########### + +# includes +install( + DIRECTORY include/ + DESTINATION include +) + +# libs +install( + TARGETS ${PROJECT_NAME}_lib + EXPORT ${PROJECT_NAME}Targets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +# nodes +install( + TARGETS image_padding_cropping_node + DESTINATION lib/${PROJECT_NAME} +) + +########## +# EXPORT # +########## +ament_export_include_directories( + include +) +ament_export_libraries( + ${PROJECT_NAME}_lib +) +ament_export_targets( + ${PROJECT_NAME}Targets +) + +ament_export_dependencies( + rclcpp + sensor_msgs + cv_bridge +) + +ament_package() diff --git a/nvblox_examples/nvblox_image_padding/include/nvblox_image_padding/image_padding_cropping_node.hpp b/nvblox_examples/nvblox_image_padding/include/nvblox_image_padding/image_padding_cropping_node.hpp new file mode 100644 index 00000000..d342a021 --- /dev/null +++ b/nvblox_examples/nvblox_image_padding/include/nvblox_image_padding/image_padding_cropping_node.hpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_IMAGE_PADDING__IMAGE_PADDING_NODE_HPP_ +#define NVBLOX_IMAGE_PADDING__IMAGE_PADDING_NODE_HPP_ + +#include +#include + +#include + +namespace nvblox { + +class ImagePaddingCroppingNode : public rclcpp::Node { + public: + explicit ImagePaddingCroppingNode( + const rclcpp::NodeOptions& options = rclcpp::NodeOptions(), + const std::string& node_name = "image_padding_node"); + virtual ~ImagePaddingCroppingNode() = default; + + // Callbacks + void imageCallback(sensor_msgs::msg::Image::ConstSharedPtr image_ptr); + + private: + // Image subscriber + rclcpp::Subscription::SharedPtr image_sub_; + + // Image publisher + rclcpp::Publisher::SharedPtr image_pub_; + + // The amount to pad (or crop) + int desired_height_ = -1; + int desired_width_ = -1; + + // Default subscription QoS + std::string image_qos_str_ = "SYSTEM_DEFAULT"; +}; + +} // namespace nvblox + +#endif // NVBLOX_IMAGE_PADDING__IMAGE_PADDING_NODE_HPP_ diff --git a/nvblox_examples/nvblox_image_padding/package.xml b/nvblox_examples/nvblox_image_padding/package.xml new file mode 100644 index 00000000..28f30790 --- /dev/null +++ b/nvblox_examples/nvblox_image_padding/package.xml @@ -0,0 +1,52 @@ + + + + + + + nvblox_image_padding + 0.30.0 + Image padding for nvblox_ros + + Alexander Millane + Remo Steiner + Hemal Shah + Apache-2.0 + https://developer.nvidia.com/isaac-ros-gems/ + Alexander Millane + Remo Steiner + Emilie Wirbel + Vikram Ramasamy + + ament_cmake + ament_cmake_auto + + rclcpp + rclcpp_components + sensor_msgs + cv_bridge + nvblox_ros_common + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/nvblox_examples/nvblox_image_padding/src/image_padding_cropping_node.cpp b/nvblox_examples/nvblox_image_padding/src/image_padding_cropping_node.cpp new file mode 100644 index 00000000..db9c5148 --- /dev/null +++ b/nvblox_examples/nvblox_image_padding/src/image_padding_cropping_node.cpp @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include + +#include "nvblox_image_padding/image_padding_cropping_node.hpp" + +namespace nvblox { + +using std::placeholders::_1; + +ImagePaddingCroppingNode::ImagePaddingCroppingNode( + const rclcpp::NodeOptions& options, const std::string& node_name) + : Node(node_name, options) { + RCLCPP_INFO(get_logger(), "Starting up ImagePaddingCroppingNode"); + + // Parameters + image_qos_str_ = declare_parameter("image_qos", image_qos_str_); + desired_height_ = declare_parameter("desired_height", desired_height_); + desired_width_ = declare_parameter("desired_width", desired_width_); + + // Users like feedback! + if (desired_height_ <= 0 || desired_width_ <= 0) { + RCLCPP_FATAL_STREAM( + get_logger(), + "Parameters \"desired_height\" and \"desired_width\" need both to be " + "set and > 0. Currently desired_height=" + << desired_height_ << " and desired_width=" << desired_width_); + exit(1); + } + RCLCPP_INFO_STREAM(get_logger(), + "Cropping/padding to desired_height=" + << desired_height_ + << ", and to desired_width=" << desired_width_); + + // QoS + constexpr size_t kOutputQueueSize = 10; + const auto qos = rclcpp::QoS(rclcpp::KeepLast(kOutputQueueSize), + parseQosString(image_qos_str_)); + + // Subscriptions + image_sub_ = this->create_subscription( + "~/image_in", qos, + std::bind(&ImagePaddingCroppingNode::imageCallback, this, _1)); + + // Publishers + image_pub_ = create_publisher("~/image_out", qos); +} + +void ImagePaddingCroppingNode::imageCallback( + sensor_msgs::msg::Image::ConstSharedPtr image_ptr) { + // Determine if we're cropping or padding. + // NOTE(alexmillane): This is not totally general, if the user requests a pad + // in one dimension and a crop in the other we just fail. Improving this is an + // exercise for the reader. + const bool crop_in_width = + desired_width_ < static_cast(image_ptr->width); + const bool crop_in_height = + desired_height_ < static_cast(image_ptr->height); + assert(crop_in_width == crop_in_height); + const bool crop_if_true_pad_if_false = crop_in_width; + + // Calculate the crop/pad amount + const int left_pixels = std::abs(static_cast( + (desired_width_ - static_cast(image_ptr->width)) / 2.0)); + const int top_pixels = std::abs(static_cast( + (desired_height_ - static_cast(image_ptr->height)) / 2.0)); + + if (crop_if_true_pad_if_false) { + RCLCPP_INFO_STREAM_ONCE(get_logger(), "Cropping left by: " << left_pixels); + RCLCPP_INFO_STREAM_ONCE(get_logger(), "Cropping top by: " << top_pixels); + } else { + RCLCPP_INFO_STREAM_ONCE(get_logger(), "Padding left by: " << left_pixels); + RCLCPP_INFO_STREAM_ONCE(get_logger(), "Padding top by: " << top_pixels); + } + + // Warn if the crop/pad amount is fractional + if (((desired_width_ - image_ptr->width) % 2) != 0) { + RCLCPP_WARN_ONCE( + get_logger(), + "Desired width - Image width not cleanly dividable by two. Extra pixel " + "will be made up on one side."); + } + if (((desired_height_ - image_ptr->height) % 2) != 0) { + RCLCPP_WARN_ONCE( + get_logger(), + "Desired height - Image height not cleanly dividable by two. Extra " + "pixel will be made up on one side."); + } + + // Wrap input image + cv_bridge::CvImageConstPtr input_image_cv_ptr = + cv_bridge::toCvShare(image_ptr, image_ptr->encoding); + + // Prepare empty output image + cv_bridge::CvImage output_image_cv(input_image_cv_ptr->header, + input_image_cv_ptr->encoding); + + // Crop (if that's what we want) + if (crop_if_true_pad_if_false) { + output_image_cv.image = input_image_cv_ptr->image( + cv::Rect(left_pixels, top_pixels, desired_width_, desired_height_)); + } + // Pad (if that's what we want) + else { + output_image_cv.image = cv::Mat::zeros(desired_height_, desired_width_, + input_image_cv_ptr->image.type()); + input_image_cv_ptr->image.copyTo(output_image_cv.image(cv::Rect( + left_pixels, top_pixels, image_ptr->width, image_ptr->height))); + } + // Republish + image_pub_->publish(*output_image_cv.toImageMsg()); +} + +} // namespace nvblox + +// Register the node as a component +#include "rclcpp_components/register_node_macro.hpp" +RCLCPP_COMPONENTS_REGISTER_NODE(nvblox::ImagePaddingCroppingNode) diff --git a/nvblox_examples/nvblox_image_padding/src/image_padding_cropping_node_main.cpp b/nvblox_examples/nvblox_image_padding/src/image_padding_cropping_node_main.cpp new file mode 100644 index 00000000..c6a67b93 --- /dev/null +++ b/nvblox_examples/nvblox_image_padding/src/image_padding_cropping_node_main.cpp @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include + +#include "nvblox_image_padding/image_padding_cropping_node.hpp" + +int main(int argc, char* argv[]) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/nvblox_examples/nvblox_isaac_sim/nvblox_isaac_sim/__init__.py b/nvblox_examples/nvblox_isaac_sim/nvblox_isaac_sim/__init__.py new file mode 100644 index 00000000..6bbcb131 --- /dev/null +++ b/nvblox_examples/nvblox_isaac_sim/nvblox_isaac_sim/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/README.md b/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/README.md new file mode 100644 index 00000000..ea200061 --- /dev/null +++ b/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/README.md @@ -0,0 +1,32 @@ +# Explanation of the start_isaac_sim file to open and play Isaac Sim examples + +start_isaac_sim has mainly three modes which can be used in start_isaac_sim.py + +- Mode 1 + - `with_people` : **False** + - `random_command_generation` : **False** + - `use_generated_command_file` : **False**
+ Launch isaac sim example with no humans in scene +- Mode 2 + - `with_people` : **True** + - `random_command_generation` : **False** + - `use_generated_command_file` : **False/True**
+ Launch isaac sim example with humans in scene. Either use default command file for humans with argument use_generated_command_file as **FALSE** or a custom command file with the argument as **TRUE**. If custom command file is to be used the argument for anim_people_waypoint_dir must be set. +- Mode 3 + - `with_people` : **True** + - `random_command_generation` : **True** + - `use_generated_command_file` : **False**
+ Launch isaac sim headless and generate custom command file for human animation. Argument anim_people_waypoint_dir has to be set. The script generated this command file in the specified directory and then closes the simulation. This command file can be used the next time the script is used to run the example with humans in scene. + +#### **Parameters Explained** + +| Parameter | Default | Description | +|---------------------|------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `scenario_path` | */Isaac/Samples/NvBlox/
carter_warehouse_navigation.usd*| Path of the scenario to launch relative to the nucleus server base path. Scenario must contain a carter robot. If the scene contains animated humans, the script expects to find them under /World/Humans.| +| `environment_prim_path` | */World/WareHouse* | Path to the world to create a navigation mesh.| +| `tick_rate_hz` | *20* | The rate (in hz) that we step the simulation at.| +| `anim_people_waypoint_dir` | | Directory location to save the people animation commands as a text file| +| `with_people` | *False* | To choose whether to have scene with people or not | +| `random_command_generation` | *False* | Choose whether we generate random waypoint or run sim | +| `num_waypoints` | *5* | Number of waypoints to generate for each human in the scene | +| `use_generated_command_file` | *False* | Choose whether to use generated/custom command file or to use the default one to run the people animation | diff --git a/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/__init__.py b/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/__init__.py new file mode 100644 index 00000000..6bbcb131 --- /dev/null +++ b/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/nvblox_isaac_sim/omniverse_scripts/common/scenario.py b/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/common/scenario.py similarity index 96% rename from nvblox_isaac_sim/omniverse_scripts/common/scenario.py rename to nvblox_examples/nvblox_isaac_sim/omniverse_scripts/common/scenario.py index 337f3fed..59cb7fc2 100644 --- a/nvblox_isaac_sim/omniverse_scripts/common/scenario.py +++ b/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/common/scenario.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/start_isaac_sim.py b/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/start_isaac_sim.py new file mode 100644 index 00000000..2afd9160 --- /dev/null +++ b/nvblox_examples/nvblox_isaac_sim/omniverse_scripts/start_isaac_sim.py @@ -0,0 +1,363 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import os +import random +import time + +import omni + +ADDITIONAL_EXTENSIONS_BASE = ['omni.isaac.ros2_bridge-humble'] + +ADDITIONAL_EXTENSIONS_PEOPLE = [ + 'omni.anim.people', 'omni.anim.navigation.bundle', 'omni.anim.timeline', + 'omni.anim.graph.bundle', 'omni.anim.graph.core', 'omni.anim.graph.ui', + 'omni.anim.retarget.bundle', 'omni.anim.retarget.core', + 'omni.anim.retarget.ui', 'omni.kit.scripting'] + + +def enable_extensions_for_sim(with_people: bool = False): + """ + Enable the required extensions for the simulation. + + Args: + with_people (bool, optional): Loads the human animation extensions if the scene with + humans is requested. Defaults to False. + + """ + from omni.isaac.core.utils.extensions import enable_extension + add_exten = ADDITIONAL_EXTENSIONS_BASE + if with_people: + add_exten = ADDITIONAL_EXTENSIONS_BASE + ADDITIONAL_EXTENSIONS_PEOPLE + for idx in range(len(add_exten)): + enable_extension(add_exten[idx]) + + +def rebuild_nav_mesh(): + """ + Rebuild the navmesh with the correct settings. Used for the people to move around. + + Called only when the sim with people is requested. + """ + # Navigation mesh rebake settings + import omni.kit.commands + print('Rebuilding navigation mesh...') + + omni.kit.commands.execute( + 'ChangeSetting', + path='/exts/omni.anim.navigation.core/navMesh/config/height', + value=1.5) + omni.kit.commands.execute( + 'ChangeSetting', + path='/exts/omni.anim.navigation.core/navMesh/config/radius', + value=0.5) + omni.kit.commands.execute( + 'ChangeSetting', + path='/exts/omni.anim.navigation.core/navMesh/config/maxSlope', + value=60.0) + omni.kit.commands.execute( + 'ChangeSetting', + path='/exts/omni.anim.navigation.core/navMesh/config/maxClimb', + value=0.2) + omni.kit.commands.execute( + 'ChangeSetting', + path='/persistent/exts/omni.anim.navigation.core/navMesh/autoRebakeDelaySeconds', + value=4) + omni.kit.commands.execute( + 'ChangeSetting', + path='/persistent/exts/omni.anim.navigation.core/navMesh/viewNavMesh', + value=False) + print('Navigation mesh rebuilt.') + + +def create_people_commands(environment_prim_path: str, + anim_people_command_dir: str, + max_number_tries: int, + num_waypoints_per_human: int): + """ + Create the occupancy grid which returns the free points for the humans to move. + + These points are then randomly sampled and assigned to each person and is saved as a txt file + which can be used as the command file for people animation. It directly gets the context of + the scene once the scene is opened and played in IsaacSim. + + Args: + environment_prim_path (str): The warehouse enviroment prim path + anim_people_command_dir (str): The local directory to be used for storing the command file + max_number_tries (int): The number of times the script attempts to select a new waypoint + for a human. + num_waypoints_per_human (int): The number of waypoints to create per human. + + """ + from omni.isaac.occupancy_map import _occupancy_map + import omni.anim.navigation.core as navcore + from omni.isaac.core.prims import XFormPrim + import carb + navigation_interface = navcore.acquire_interface() + print('Creating randomized paths for people...') + bounding_box = omni.usd.get_context().compute_path_world_bounding_box( + environment_prim_path) + + physx = omni.physx.acquire_physx_interface() + stage_id = omni.usd.get_context().get_stage_id() + + generator = _occupancy_map.Generator(physx, stage_id) + # 0.05m cell size, output buffer will have 0 for occupied cells, 1 for + # unoccupied, and 6 for cells that cannot be seen + # this assumes your usd stage units are in m, and not cm + generator.update_settings(.05, 0, 1, 6) + # Setting the transform for the generator + generator.set_transform( + # The starting location to map from + (0, 0, 0.025), + # The min bound + (bounding_box[0][0], bounding_box[0][1], 0.025), + # The max bound + (bounding_box[1][0], bounding_box[1][1], 0.025)) + generator.generate2d() + # Get locations of free points + free_points = generator.get_free_positions() + # Find out number of humans in scene and create a list with their names + human_names = [] + human_prims = [] + for human in omni.usd.get_context().get_stage().GetPrimAtPath( + '/World/Humans').GetAllChildren(): + human_name = human.GetChildren()[0].GetName() + # To remove biped setup prim from the list + if 'CharacterAnimation' in human_name or 'Biped_Setup' in human_name: + continue + human_names.append(human_name) + human_prims.append(XFormPrim(human.GetPath().pathString)) + + random_waypoints = {} + for human_name, human_prim in zip(human_names, human_prims): + # Get the human initial position. + start_point, _ = human_prim.get_world_pose() + # Initialize target waypoints. + random_waypoints[human_name] = [] + for _ in range(num_waypoints_per_human): + current_tries = 0 + path = None + # Sample for a maximum number of tries then give up. + while path is None and current_tries < max_number_tries: + new_waypoint = random.sample(free_points, 1) + # Check that a path exists between the starting position and the destination + path = navigation_interface.query_navmesh_path( + carb.Float3(start_point), new_waypoint[0]) + current_tries += 1 + if path is not None: + new_waypoint[0].z = 0.0 + random_waypoints[human_name].append(new_waypoint[0]) + print( + f'Found path for {human_name} from {start_point} to {new_waypoint[0]} after \ + {current_tries} tries') + start_point = new_waypoint[0] + else: + print( + f'Could not find path for {human_name} after {max_number_tries}, skipping') + + # Save as command file + command_file_path = os.path.join( + anim_people_command_dir, 'human_cmd_file.txt') + print(f'Saving randomized commands to {command_file_path}') + with open(command_file_path, 'w') as file: + for human_name, waypoints in random_waypoints.items(): + human_command_line = f'{human_name} GoTo ' + for next_waypoint in waypoints: + human_command_line += f'{next_waypoint[0]} {next_waypoint[1]} 0 ' + human_command_line += '_\n' + file.write(human_command_line) + + +def update_people_command_file_path(anim_people_command_dir: str): + """ + Update the command file path settings in the simulation scene to the custom one. + + Args: + anim_people_command_dir (str): The directory where the generated/custom command file is + stored. + + """ + import omni.kit.commands + omni.kit.commands.execute( + 'ChangeSetting', + path='/exts/omni.anim.people/command_settings/command_file_path', + value=anim_people_command_dir + '/human_cmd_file.txt') + + +def main(scenario_path: str, + anim_people_command_dir: str, + environment_prim_path: str, + random_command_generation: bool, + num_waypoints: int, + headless: bool = False, + with_people: bool = True, + use_generated_command_file: bool = False, + tick_rate_hz: float = 20.0): + + # Start up the simulator + from omni.isaac.kit import SimulationApp + simulation_app = SimulationApp({ + 'renderer': 'RayTracedLighting', + 'headless': headless + }) + + import omni.kit.commands + from omni.isaac.core import SimulationContext + from omni.isaac.core.utils.nucleus import get_assets_root_path + + # Enables the simulation extensions + enable_extensions_for_sim(with_people) + + assets_root_path = get_assets_root_path() + if assets_root_path is None: + print( + 'Could not find Isaac Sim assets folder. Make sure you have an up to date local \ + Nucleus server or that you have a proper connection to the internet.' + ) + simulation_app.close() + exit() + + usd_path = assets_root_path + scenario_path + print(f'Opening stage {usd_path}...') + + # Load the stage + omni.usd.get_context().open_stage(usd_path, None) + + # Wait two frames so that stage starts loading + simulation_app.update() + simulation_app.update() + + print('Loading stage...') + from omni.isaac.core.utils.stage import is_stage_loading + + while is_stage_loading(): + simulation_app.update() + print('Loading Complete') + + # If the scene with people is requested we build/rebuild the navmesh + # We also point to the generated/custom command file for people animation if set by user + if with_people: + rebuild_nav_mesh() + if not random_command_generation and use_generated_command_file: + update_people_command_file_path(anim_people_command_dir) + + time_dt = 1.0 / tick_rate_hz + print(f'Running sim at {tick_rate_hz} Hz, with dt of {time_dt}') + # Run physics at 60 Hz and render time at the set frequency to see the sim as real time + simulation_context = SimulationContext(stage_units_in_meters=1.0, + physics_dt=1.0 / 60, + rendering_dt=time_dt) + + simulation_context.play() + simulation_context.step() + + # Physics can only be seen when the scene is played + # If we want to generate the command file for the people simulation we call the + # create_people_commands method, which stores the command file and then we close the + # simulation and point the user on how to use the generated file + if with_people and random_command_generation: + print('Creating human animation file...') + create_people_commands(environment_prim_path, + anim_people_command_dir, 10, num_waypoints) + print( + 'Human command file has been created at {}/human_human_cmd_file.txt' + .format(anim_people_command_dir)) + print( + 'Please restart the simulation with --with_people and \n \ + --use_generated_command_file to use the generated command file in human simulation' + ) + simulation_context.stop() + simulation_app.close() + + # Simulate for a few seconds to warm up sim and let everything settle + for _ in range(2*round(tick_rate_hz)): + simulation_context.step() + + # Run the sim + last_frame_time = time.monotonic() + while simulation_app.is_running(): + simulation_context.step() + current_frame_time = time.monotonic() + if current_frame_time - last_frame_time < time_dt: + time.sleep(time_dt - (current_frame_time - last_frame_time)) + last_frame_time = time.monotonic() + + simulation_context.stop() + simulation_app.close() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Sample app for running Carter in a Warehouse for NVblox.') + parser.add_argument( + '--scenario_path', + help='Path of the scenario to launch relative to the nucleus server ' + 'base path. Scenario must contain a carter robot. If the scene ' + 'contains animated humans, the script expects to find them under /World/Humans. ' + 'Example scenarios are /Isaac/Samples/NvBlox/carter_warehouse_navigation_with_people.usd ' + 'or /Isaac/Samples/NvBlox/carter_warehouse_navigation.usd', + default='/Isaac/Samples/NvBlox/carter_warehouse_navigation.usd') + parser.add_argument('--environment_prim_path', + default='/World/WareHouse', + help='Path to the world to create a navigation mesh.') + parser.add_argument( + '--tick_rate_hz', + type=int, + help='The rate (in hz) that we step the simulation at.', + default=20) + parser.add_argument( + '--anim_people_waypoint_dir', + help='Directory location to save the waypoints in a yaml file') + parser.add_argument('--headless', action='store_true', + help='Run the simulation headless.') + parser.add_argument( + '--with_people', + help='If used, animation extensions for humans will be enabled.', + action='store_true') + parser.add_argument( + '--random_command_generation', + help='Choose whether we generate random waypoint or run sim', + action='store_true') + parser.add_argument('--num_waypoints', type=int, + help='Number of waypoints to generate for each human in the scene.', + default=5) + parser.add_argument( + '--use_generated_command_file', + help='Choose whether to use generated/custom command file or to use the default one to run\ + the people animation', + action='store_true') + # This allows for IsaacSim options to be passed on the SimulationApp. + args, unknown = parser.parse_known_args() + + # If we want to generate the command file then we run the simulation headless + if args.random_command_generation: + args.headless = True + + # Check if the command file directory is given if it has to be generated or used for the + # simulation + if args.random_command_generation or args.use_generated_command_file: + if not args.anim_people_waypoint_dir: + raise ValueError( + 'Input to command file directory required if custom command file has to be \ + generated/used!!' + ) + + main(args.scenario_path, args.anim_people_waypoint_dir, args.environment_prim_path, + args.random_command_generation, args.num_waypoints, args.headless, args.with_people, + args.use_generated_command_file, args.tick_rate_hz) diff --git a/nvblox_isaac_sim/package.xml b/nvblox_examples/nvblox_isaac_sim/package.xml similarity index 98% rename from nvblox_isaac_sim/package.xml rename to nvblox_examples/nvblox_isaac_sim/package.xml index 0f7bb453..a1a47a05 100644 --- a/nvblox_isaac_sim/package.xml +++ b/nvblox_examples/nvblox_isaac_sim/package.xml @@ -21,7 +21,7 @@ nvblox_isaac_sim - 0.20.0 + 0.30.0 Launch files for working with nvblox_ros and Isaac Sim Helen Oleynikova diff --git a/nvblox_isaac_sim/resource/nvblox_isaac_sim b/nvblox_examples/nvblox_isaac_sim/resource/nvblox_isaac_sim similarity index 100% rename from nvblox_isaac_sim/resource/nvblox_isaac_sim rename to nvblox_examples/nvblox_isaac_sim/resource/nvblox_isaac_sim diff --git a/nvblox_isaac_sim/setup.cfg b/nvblox_examples/nvblox_isaac_sim/setup.cfg similarity index 100% rename from nvblox_isaac_sim/setup.cfg rename to nvblox_examples/nvblox_isaac_sim/setup.cfg diff --git a/nvblox_isaac_sim/setup.py b/nvblox_examples/nvblox_isaac_sim/setup.py similarity index 92% rename from nvblox_isaac_sim/setup.py rename to nvblox_examples/nvblox_isaac_sim/setup.py index f046d903..67c84803 100644 --- a/nvblox_isaac_sim/setup.py +++ b/nvblox_examples/nvblox_isaac_sim/setup.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ setup( name=package_name, - version='0.20.0', + version='0.30.0', packages=[package_name], data_files=[ ('share/ament_index/resource_index/packages', diff --git a/nvblox_isaac_sim/test/test_flake8.py b/nvblox_examples/nvblox_isaac_sim/test/test_flake8.py similarity index 100% rename from nvblox_isaac_sim/test/test_flake8.py rename to nvblox_examples/nvblox_isaac_sim/test/test_flake8.py diff --git a/nvblox_isaac_sim/test/test_pep257.py b/nvblox_examples/nvblox_isaac_sim/test/test_pep257.py similarity index 92% rename from nvblox_isaac_sim/test/test_pep257.py rename to nvblox_examples/nvblox_isaac_sim/test/test_pep257.py index b234a384..2ff83693 100644 --- a/nvblox_isaac_sim/test/test_pep257.py +++ b/nvblox_examples/nvblox_isaac_sim/test/test_pep257.py @@ -19,5 +19,5 @@ @pytest.mark.linter @pytest.mark.pep257 def test_pep257(): - rc = main(argv=['.', 'test']) + rc = main(argv=['.', 'test', '--add-ignore', 'D407']) assert rc == 0, 'Found code style errors / warnings' diff --git a/nvblox_examples/realsense_splitter/CMakeLists.txt b/nvblox_examples/realsense_splitter/CMakeLists.txt index f4af67ea..96bb3d34 100644 --- a/nvblox_examples/realsense_splitter/CMakeLists.txt +++ b/nvblox_examples/realsense_splitter/CMakeLists.txt @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ cmake_minimum_required(VERSION 3.5) project(realsense_splitter) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) add_compile_options(-Wall -Wextra -Wpedantic) # find dependencies @@ -29,6 +29,7 @@ find_package(rclcpp_components REQUIRED) find_package(sensor_msgs REQUIRED) find_package(message_filters REQUIRED) find_package(realsense2_camera_msgs REQUIRED) +find_package(nvblox_ros_common REQUIRED) ############# @@ -36,7 +37,6 @@ find_package(realsense2_camera_msgs REQUIRED) ############# add_library(realsense_splitter_component src/realsense_splitter_node.cpp - src/qos.cpp ) target_compile_definitions(realsense_splitter_component PRIVATE "COMPOSITION_BUILDING_DLL") @@ -46,6 +46,7 @@ ament_target_dependencies(realsense_splitter_component sensor_msgs message_filters realsense2_camera_msgs + nvblox_ros_common ) target_include_directories(realsense_splitter_component PUBLIC $ diff --git a/nvblox_isaac_sim/nvblox_isaac_sim/__init__.py b/nvblox_examples/realsense_splitter/COLCON_IGNORE similarity index 100% rename from nvblox_isaac_sim/nvblox_isaac_sim/__init__.py rename to nvblox_examples/realsense_splitter/COLCON_IGNORE diff --git a/nvblox_examples/realsense_splitter/include/realsense_splitter/qos.hpp b/nvblox_examples/realsense_splitter/include/realsense_splitter/qos.hpp index f5ebc737..6c41aa1d 100644 --- a/nvblox_examples/realsense_splitter/include/realsense_splitter/qos.hpp +++ b/nvblox_examples/realsense_splitter/include/realsense_splitter/qos.hpp @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // // 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 +// 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, diff --git a/nvblox_examples/realsense_splitter/include/realsense_splitter/realsense_splitter_node.hpp b/nvblox_examples/realsense_splitter/include/realsense_splitter/realsense_splitter_node.hpp index a7d5945a..d6448296 100644 --- a/nvblox_examples/realsense_splitter/include/realsense_splitter/realsense_splitter_node.hpp +++ b/nvblox_examples/realsense_splitter/include/realsense_splitter/realsense_splitter_node.hpp @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // // 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 +// 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, @@ -41,7 +41,8 @@ class RealsenseSplitterNode : public rclcpp::Node enum class EmitterMode : int { kOn = 1, - kOff = 0 + kOff = 0, + kUnknown = 2 }; // Input callbacks diff --git a/nvblox_examples/realsense_splitter/package.xml b/nvblox_examples/realsense_splitter/package.xml index 65843a24..9e0b41e1 100644 --- a/nvblox_examples/realsense_splitter/package.xml +++ b/nvblox_examples/realsense_splitter/package.xml @@ -21,7 +21,7 @@ realsense_splitter - 0.20.0 + 0.30.0 Splits realsense outputs based on emitter status. alex Apache-2.0 @@ -37,6 +37,7 @@ sensor_msgs message_filters realsense2_camera_msgs + nvblox_ros_common ament_lint_auto ament_lint_common diff --git a/nvblox_examples/realsense_splitter/src/realsense_splitter_node.cpp b/nvblox_examples/realsense_splitter/src/realsense_splitter_node.cpp index 1645e0e9..6174b26f 100644 --- a/nvblox_examples/realsense_splitter/src/realsense_splitter_node.cpp +++ b/nvblox_examples/realsense_splitter/src/realsense_splitter_node.cpp @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // // 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 +// 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, @@ -17,13 +17,13 @@ #include "realsense_splitter/realsense_splitter_node.hpp" -#include "realsense_splitter/qos.hpp" +#include namespace nvblox { RealsenseSplitterNode::RealsenseSplitterNode(const rclcpp::NodeOptions & options) -: Node("odometry_flattener_node", options) +: Node("realsense_splitter_node", options) { RCLCPP_INFO(get_logger(), "Creating a RealsenseSplitterNode()."); @@ -102,8 +102,19 @@ int RealsenseSplitterNode::getEmitterModeFromMetadataMsg( constexpr size_t field_name_length = sizeof(frame_emitter_mode_str) / sizeof(frame_emitter_mode_str[0]); // Find the field - size_t field_location = - metadata->json_data.find(frame_emitter_mode_str) + field_name_length - 1; + const size_t frame_emitter_mode_start_location = + metadata->json_data.find(frame_emitter_mode_str); + // If the emitter mode is not found, return unknown and warn the user. + if (frame_emitter_mode_start_location == metadata->json_data.npos) { + constexpr int kPublishPeriodMs = 1000; + auto & clk = *get_clock(); + RCLCPP_WARN_THROTTLE( + get_logger(), clk, kPublishPeriodMs, + "Realsense frame metadata did not contain \"frame_emitter_mode\". Splitter will not work."); + return static_cast(EmitterMode::kUnknown); + } + // If it is found, parse the field. + const size_t field_location = frame_emitter_mode_start_location + field_name_length - 1; const int emitter_mode = static_cast(metadata->json_data[field_location]) - static_cast('0'); diff --git a/nvblox_examples/realsense_splitter/src/realsense_splitter_node_main.cpp b/nvblox_examples/realsense_splitter/src/realsense_splitter_node_main.cpp index c5d33a08..a7238d25 100644 --- a/nvblox_examples/realsense_splitter/src/realsense_splitter_node_main.cpp +++ b/nvblox_examples/realsense_splitter/src/realsense_splitter_node_main.cpp @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// Copyright (c) 2022-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // // 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 +// 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, diff --git a/nvblox_examples/semantic_label_conversion/CMakeLists.txt b/nvblox_examples/semantic_label_conversion/CMakeLists.txt new file mode 100644 index 00000000..22207646 --- /dev/null +++ b/nvblox_examples/semantic_label_conversion/CMakeLists.txt @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.5) +project(semantic_label_conversion) + +set(CMAKE_CXX_STANDARD 17) +add_compile_options(-Wall -Wextra -Wpedantic) + +# find dependencies +find_package(ament_cmake REQUIRED) + +# Installing script so their runable +install(PROGRAMS + semantic_label_conversion/semantic_label_converter.py + semantic_label_conversion/semantic_label_stamper.py + DESTINATION lib/${PROJECT_NAME}) + +install(DIRECTORY params launch + DESTINATION share/${PROJECT_NAME}) + +ament_package() diff --git a/nvblox_examples/semantic_label_conversion/README.md b/nvblox_examples/semantic_label_conversion/README.md new file mode 100644 index 00000000..b63c2906 --- /dev/null +++ b/nvblox_examples/semantic_label_conversion/README.md @@ -0,0 +1,50 @@ +# How to get semantic GT from Isaac Sim + +This package helps to convert semantic labels coming from Isaac Sim to consistent label images in mono8 and rgb8 used in nvblox. + +# Nodes + +`semantic_label_conversion.launch.py` launches the `semantic_label_stamper` and `semantic_label_converter` nodes. +The parameters in `params/semantic_label_conversion.yaml` are loaded. + +## **semantic_label_stamper** node + +Adds timestamps to the semantic labels. This is required to synchronize the semantic image and labels in the `semantic_label_converter` node with a `TimeSynchronizer`. + +| Subscriber Topics | Type | Description | +|----------------------------------|-----------------------|---------------------------------------------------| +| `//semantic_labels` | `std_msgs/msg/String` | Semantic labels definition published by Isaac Sim | + +| Publisher Topics | Type | Description | +|-----------------------------------------------------|---------------------------------------------|---------------------------------------| +| `/semantic_conversion//labels_stamped` | `semantic_labels/msg/SemanticLabelsStamped` | Resulting timestamped semantic labels | + + +## **semantic_label_converter** node + +Outputs the resulting mono8 and rgb8 GT images. + +| Subscriber Topics | Type | Description | +|-----------------------------------------------------|---------------------------------------------|------------------------------------------------------------------------------| +| `//semantic` | `sensor_msgs/msg/Image` | GT image published by Isaac Sim | +| `/semantic_conversion//labels_stamped` | `semantic_labels/msg/SemanticLabelsStamped` | Timestamped semantic labels published by the `semantic_label_converter` node | + +| Publisher Topics | Type | Description | +|---------------------------------------------------------|-------------------------|-----------------------------------------| +| `/semantic_conversion//semantic_mono8` | `sensor_msgs/msg/Image` | Resulting semantic GT one channel image | +| `/semantic_conversion//semantic_colorized` | `sensor_msgs/msg/Image` | Resulting semantic GT colorized image | + + +## Configuration + +The configuration file can be found in `params/semantic_label_conversion.yaml`. + +| Parameter | Type | Description | +|-----------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------| +| `camera_1_enabled` | `bool` | Enable the conversion for camera 1 images | +| `camera_1_name` | `string` | Name of camera 1 (topic names are dependent on this parameter) | +| `camera_2_enabled` | `bool` | Enable the conversion for camera 2 images | +| `camera_2_name` | `string` | Name of camera 2 (topic names are dependent on this parameter) | +| `labels.names` | `list` | List of strings containing label names defined in Isaac Sim that you wish to convert | +| `label..output_id` | `int` | Mono8 value that each label name gets converted to in the `/semantic_conversion//semantic_mono8` image | +| `label..output_color` | `list` | Rgb8 value stored as a list that each label name gets converted to in the `/semantic_conversion//semantic_colorized` image | diff --git a/nvblox_examples/semantic_label_conversion/launch/semantic_label_conversion.launch.py b/nvblox_examples/semantic_label_conversion/launch/semantic_label_conversion.launch.py new file mode 100644 index 00000000..74e7734f --- /dev/null +++ b/nvblox_examples/semantic_label_conversion/launch/semantic_label_conversion.launch.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch_ros.actions import Node + + +def generate_launch_description(): + + # Config file + config_file = os.path.join( + get_package_share_directory('semantic_label_conversion'), + 'params', 'semantic_label_conversion.yaml') + + semantic_label_stamper = Node(package='semantic_label_conversion', + executable='semantic_label_stamper.py', + name='semantic_label_stamper', + output='screen', + emulate_tty=True, + parameters=[config_file]) + + semantic_label_converter = Node(package='semantic_label_conversion', + executable='semantic_label_converter.py', + name='semantic_label_converter', + output='screen', + emulate_tty=True, + parameters=[config_file]) + + return LaunchDescription([ + semantic_label_stamper, + semantic_label_converter + ]) diff --git a/nvblox_examples/semantic_label_conversion/package.xml b/nvblox_examples/semantic_label_conversion/package.xml new file mode 100644 index 00000000..2eaf20eb --- /dev/null +++ b/nvblox_examples/semantic_label_conversion/package.xml @@ -0,0 +1,44 @@ + + + + + + + semantic_label_conversion + 0.30.0 + Package to convert semantic labels coming from Isaac Sim to consistent label images + Emilie Wirbel + NVIDIA Isaac ROS Software License + https://developer.nvidia.com/isaac-ros-gems/ + Emilie Wirbel + Remo Steiner + + ament_cmake + + nvblox_msgs + + ament_lint_auto + ament_lint_common + + ros2launch + + + ament_cmake + + diff --git a/nvblox_examples/semantic_label_conversion/params/semantic_label_conversion.yaml b/nvblox_examples/semantic_label_conversion/params/semantic_label_conversion.yaml new file mode 100644 index 00000000..63089f68 --- /dev/null +++ b/nvblox_examples/semantic_label_conversion/params/semantic_label_conversion.yaml @@ -0,0 +1,19 @@ +/**: + ros__parameters: + camera_1_enabled: true + camera_1_name: front/stereo_camera/left + camera_2_enabled: false + camera_2_name: front/stereo_camera/right + + labels: + # Semantic label names defined in Isaac Sim + names: ["unlabelled", "person"] + + # Definition of how the label names are converted + # to IDs on the mask image and color on the colorized mask + unlabelled: + output_id: 0 + output_color: [0, 0, 0] + person: + output_id: 1 + output_color: [255, 0, 0] diff --git a/nvblox_examples/semantic_label_conversion/semantic_label_conversion/__init__.py b/nvblox_examples/semantic_label_conversion/semantic_label_conversion/__init__.py new file mode 100644 index 00000000..6bbcb131 --- /dev/null +++ b/nvblox_examples/semantic_label_conversion/semantic_label_conversion/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/nvblox_examples/semantic_label_conversion/semantic_label_conversion/semantic_label_converter.py b/nvblox_examples/semantic_label_conversion/semantic_label_conversion/semantic_label_converter.py new file mode 100755 index 00000000..5b6d311b --- /dev/null +++ b/nvblox_examples/semantic_label_conversion/semantic_label_conversion/semantic_label_converter.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import json +import numpy as np +from typing import Dict, Tuple + +import message_filters +import rclpy +from cv_bridge import CvBridge +from rclpy.node import Node +from nvblox_msgs.msg import SemanticLabelsStamped +from sensor_msgs.msg import Image + + +class SemanticConverter(Node): + def __init__(self) -> None: + ''' + Helper node to convert IsaacSim Semantic Labels to a consistent semantic segmentation image + ''' + super().__init__('semantic_label_converter') + + # Declare params + self.declare_parameter('camera_1_enabled', True) + self.declare_parameter('camera_2_enabled', True) + self.declare_parameter('camera_1_name', 'left') + self.declare_parameter('camera_2_name', 'right') + self.declare_parameter('labels.names', ['unlabelled', 'person']) + + # Get params + camera_1_enabled = self.get_parameter( + 'camera_1_enabled').get_parameter_value().bool_value + camera_2_enabled = self.get_parameter( + 'camera_2_enabled').get_parameter_value().bool_value + camera_1_name = self.get_parameter( + 'camera_1_name').get_parameter_value().string_value + camera_2_name = self.get_parameter( + 'camera_2_name').get_parameter_value().string_value + label_names = self.get_parameter( + 'labels.names').get_parameter_value().string_array_value + + # Get dictionary that defines + # how label names are converted to ids and color + self.label_conversion_dict = {} + for label_name in label_names: + output_id_param_name = f"labels.{label_name}.output_id" + output_color_param_name = f"labels.{label_name}.output_color" + + self.declare_parameter(output_id_param_name, 0) + self.declare_parameter(output_color_param_name, [0, 0, 0]) + + output_id = self.get_parameter( + output_id_param_name).get_parameter_value().integer_value + output_color = list(self.get_parameter( + output_color_param_name).get_parameter_value().integer_array_value) + + self.label_conversion_dict[label_name] = { + 'output_id': output_id, + 'output_color': output_color + } + + if camera_1_enabled: + self.init_camera(camera_1_name) + if camera_2_enabled: + self.init_camera(camera_2_name) + + self.bridge = CvBridge() + + def init_camera(self, camera_name: str) -> None: + ''' + Initialize publishers and subscribers for a camera + Args: + camera_name (str): The name of the camera + ''' + # Subscriber + image_subscriber = message_filters.Subscriber( + self, Image, f"/{camera_name}/semantic") + labels_subscriber = message_filters.Subscriber( + self, SemanticLabelsStamped, + f"/semantic_conversion/{camera_name}/labels_stamped") + + # Publisher + publisher_mono8 = self.create_publisher( + Image, f"/semantic_conversion/{camera_name}/semantic_mono8", 1) + publisher_colorized = self.create_publisher( + Image, f"/semantic_conversion/{camera_name}/semantic_colorized", 1) + + # Synchronized callback + def on_camera_image_received(image_msg, label_msg): return \ + self.on_image_received( + publisher_mono8, publisher_colorized, image_msg, label_msg) + ts = message_filters.TimeSynchronizer( + [image_subscriber, labels_subscriber], 10) + ts.registerCallback(on_camera_image_received) + + def on_image_received(self, publisher_mono8, publisher_colorized, image_msg: Image, + labels_msg: SemanticLabelsStamped) -> None: + ''' + Callback to convert semantic image from IsaacSim to a consistent label image in mono8 + + Args: + image_msg (Image): Input image from Isaacsim, is in CV16SC1 format and labels id vary with scene + labels_msg (SemanticLabelsStamped): Stamped input labels message for the current image. + ''' + # Load the labels as a json + labels_dict = json.loads(labels_msg.labels) + # Build LUT for color conversions + lut_labels, lut_colors = self.build_labels_lut(labels_dict) + shape = (image_msg.height, image_msg.width, 1) + # Convert from signed int 32 to unsigned char (will cause issues if more than 255 classes) + data = np.frombuffer(image_msg.data, dtype=np.int32).reshape(shape) + data_mono8 = data.astype(np.uint8) + + # Generate labels arrays with the lookups (for id and coloring) + try: + labels = np.take(lut_labels, data_mono8) + colors = np.take(lut_colors, data_mono8, 0).squeeze() + except Exception as e: + print("Exception raised in semantic label converter:\n", e) + print("WARNING: We will skip this semantic image and label pair.") + return + + # Convert back to image messages and publish + labels_msg = self.bridge.cv2_to_imgmsg(labels, 'mono8') + labels_msg.header = image_msg.header + colors_msg = self.bridge.cv2_to_imgmsg(colors, "rgb8") + colors_msg.header = image_msg.header + publisher_mono8.publish(labels_msg) + publisher_colorized.publish(colors_msg) + + def build_labels_lut( + self, current_labels: Dict[str, + Dict[str, + str]]) -> Tuple[np.array, np.array]: + ''' + Build labels lookup table (LUT) from current labels dictionary. The dictionary is formatted as + {: {"class": }} where class_id_x is the id of the class in the + image and class_name_x is the one that was entered in the semantics schema. This lookup + maps all classes that are not in the reference as unlabelled, and remaps ids / colors of the + reference classes to the desired reference one + + Args: + current_labels (Dict[str, Dict[str, str]]): Labels string coming from IsaacSim + + Returns: + Tuple[np.array, np.array]: LUT for ids (Nx1) and colors (Nx3) where N is the number + of classes + ''' + # First, get the maximum label that appears in the image + max_label = -1 + for label_id, _ in current_labels.items(): + label_id_int = int(label_id) + if label_id_int > max_label: + max_label = label_id_int + # Initialize all remappings to zero + lut_labels = np.zeros(max_label + 1, dtype=np.uint8) + lut_colors = np.zeros((max_label + 1, 3), dtype=np.uint8) + # Go over all labels that are present, and get their remap + for label_id, label_name_dict in current_labels.items(): + label_id_int = int(label_id) + label_name = label_name_dict.get("class", None) + if label_name is None: + continue + label_name = label_name.lower() + # Look for the output id / color if if exists + target_label = self.label_conversion_dict.get( + label_name, {}).get("output_id", 0) + target_color = self.label_conversion_dict.get(label_name, {}).get( + "output_color", [0, 0, 0]) + # Update the remap + lut_labels[label_id_int] = target_label + lut_colors[label_id_int] = target_color + return lut_labels, lut_colors + + +def main(): + rclpy.init() + converter = SemanticConverter() + rclpy.spin(converter) + converter.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/nvblox_examples/semantic_label_conversion/semantic_label_conversion/semantic_label_stamper.py b/nvblox_examples/semantic_label_conversion/semantic_label_conversion/semantic_label_stamper.py new file mode 100755 index 00000000..ef8a6b1b --- /dev/null +++ b/nvblox_examples/semantic_label_conversion/semantic_label_conversion/semantic_label_stamper.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +import json + +import rclpy +from rclpy.node import Node +from nvblox_msgs.msg import SemanticLabelsStamped +from std_msgs.msg import String + + +class LabelsConverter(Node): + def __init__(self) -> None: + super().__init__('semantic_label_stamper') + + # Declare params + self.declare_parameter('camera_1_enabled', True) + self.declare_parameter('camera_2_enabled', True) + self.declare_parameter('camera_1_name', 'left') + self.declare_parameter('camera_2_name', 'right') + + # Get params + camera_1_enabled = self.get_parameter( + 'camera_1_enabled').get_parameter_value().bool_value + camera_2_enabled = self.get_parameter( + 'camera_2_enabled').get_parameter_value().bool_value + camera_1_name = self.get_parameter( + 'camera_1_name').get_parameter_value().string_value + camera_2_name = self.get_parameter( + 'camera_2_name').get_parameter_value().string_value + + if camera_1_enabled: + self.init_camera(camera_1_name) + if camera_2_enabled: + self.init_camera(camera_2_name) + + def init_camera(self, camera_name: str) -> None: + ''' + Initialize publishers and subscribers for a camera + Args: + camera_name (str): The name of the camera + ''' + # Publisher + labels_publisher = self.create_publisher( + SemanticLabelsStamped, f"/semantic_conversion/{camera_name}/labels_stamped", 10) + + # Subscriber + def on_camera_labels(msg): return self.on_labels(labels_publisher, msg) + self.create_subscription( + String, f"/{camera_name}/semantic_labels", on_camera_labels, 10) + + def on_labels(self, publisher, labels_string: String) -> None: + ''' + Parse the string message for the labels and turn it into a custom message with Header + + Args: + labels_string (String): String message coming out of IsaacSim + ''' + # Load the string with json + labels_data = json.loads(labels_string.data) + # Get the timestamp issue + time_data = labels_data['time_stamp'] + # Get the data without timestamp + labels_string = labels_data.copy() + labels_string.pop('time_stamp', None) + # Build and publish message + out_msg = SemanticLabelsStamped() + out_msg.labels = json.dumps(labels_string) + out_msg.header.stamp.sec = int(time_data['sec']) + out_msg.header.stamp.nanosec = int(time_data['nanosec']) + publisher.publish(out_msg) + + +def main(): + rclpy.init() + semantic_label_stamper = LabelsConverter() + rclpy.spin(semantic_label_stamper) + semantic_label_stamper.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/nvblox_isaac_sim/omniverse_scripts/__init__.py b/nvblox_isaac_sim/omniverse_scripts/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nvblox_isaac_sim/omniverse_scripts/carter_warehouse.py b/nvblox_isaac_sim/omniverse_scripts/carter_warehouse.py deleted file mode 100644 index 82f31be9..00000000 --- a/nvblox_isaac_sim/omniverse_scripts/carter_warehouse.py +++ /dev/null @@ -1,359 +0,0 @@ -# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# 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. -# -# SPDX-License-Identifier: Apache-2.0 - -import argparse -import time - -from omni.isaac.kit import SimulationApp - - -def meters_to_stage_units(length_m: float) -> float: - from omni.isaac.core.utils.stage import get_stage_units - stage_units_in_meters = get_stage_units() - return length_m / stage_units_in_meters - - -def stage_units_to_camera_units(length_in_stage_units: float) -> float: - camera_lengths_in_stage_units = 1.0 / 10.0 - return length_in_stage_units / camera_lengths_in_stage_units - - -def setup_carter_sensors(carter_prim_path: str, - camera_focal_length_m: float = 0.009, - carter_version: int = 1): - - # Set up variables based on carter version. - left_cam_path = 'chassis_link/camera_mount/carter_camera_stereo_left' - right_cam_path = 'chassis_link/camera_mount/carter_camera_stereo_right' - - if carter_version == 2: - left_cam_path = ( - 'chassis_link/stereo_cam_left/stereo_cam_left_sensor_frame/' - 'camera_sensor_left') - right_cam_path = ( - 'chassis_link/stereo_cam_right/stereo_cam_right_sensor_frame/' - 'camera_sensor_right') - - import omni - from pxr import Sdf - - # Enable - # Change the focal length of the camera (default in the carter model quite narrow). - camera_focal_length_in_camera_units = stage_units_to_camera_units( - meters_to_stage_units(camera_focal_length_m)) - - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path(f'{carter_prim_path}/{left_cam_path}.focalLength'), - value=camera_focal_length_in_camera_units, - prev=None) - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path(f'{carter_prim_path}/{right_cam_path}.focalLength'), - value=camera_focal_length_in_camera_units, - prev=None) - - # Lidar setup - carter_lidar_path = 'chassis_link/carter_lidar' - - # Set up Lidar for a 32 layer like configuration - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path(f'{carter_prim_path}/{carter_lidar_path}.highLod'), - value=True, - prev=None) - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path( - f'{carter_prim_path}/{carter_lidar_path}.horizontalResolution'), - value=0.2, - prev=None, - ) - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path(f'{carter_prim_path}/{carter_lidar_path}.minRange'), - value=0.7, - prev=None, - ) - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path( - f'{carter_prim_path}/{carter_lidar_path}.verticalFov'), - value=30.0, - prev=None, - ) - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path( - f'{carter_prim_path}/{carter_lidar_path}.verticalResolution'), - value=1.0, - prev=None, - ) - - # Modify the omnigraph to get lidar point cloud published - - import omni.graph.core as og - - controller = og.Controller() - - # Create pcl reader node - read_pcl_node_path = f'{carter_prim_path}/ActionGraph/isaac_read_lidar_point_cloud_node' - read_pcl_node = controller.create_node( - read_pcl_node_path, - 'omni.isaac.range_sensor.IsaacReadLidarPointCloud', - True, - ) - # Add relationship to lidar prim - import omni.usd - stage = omni.usd.get_context().get_stage() - read_pcl_prim = stage.GetPrimAtPath(read_pcl_node_path) - input_rel = read_pcl_prim.GetRelationship('inputs:lidarPrim') - input_rel.SetTargets([f'{carter_prim_path}/{carter_lidar_path}']) - # Retrieve on playback node - playback_node = controller.node('on_playback_tick', - f'{carter_prim_path}/ActionGraph') - # Connect the tick to the lidar pcl read - controller.connect(playback_node.get_attribute('outputs:tick'), - read_pcl_node.get_attribute('inputs:execIn')) - - # Create ros2 publisher node - publish_pcl_node = controller.create_node( - f'{carter_prim_path}/ActionGraph/ros2_publish_point_cloud', - 'omni.isaac.ros2_bridge.ROS2PublishPointCloud', True) - # Set frame id - publish_pcl_node.get_attribute('inputs:frameId').set('carter_lidar') - # Connect pcl read to pcl publish - controller.connect(read_pcl_node.get_attribute('outputs:execOut'), - publish_pcl_node.get_attribute('inputs:execIn')) - controller.connect(read_pcl_node.get_attribute('outputs:pointCloudData'), - publish_pcl_node.get_attribute('inputs:pointCloudData')) - # Get timestamp node and connect it - timestamp_node = controller.node('isaac_read_simulation_time', - f'{carter_prim_path}/ActionGraph') - controller.connect(timestamp_node.get_attribute('outputs:simulationTime'), - publish_pcl_node.get_attribute('inputs:timeStamp')) - # Get context node and connect it - context_node = controller.node('ros2_context', - f'{carter_prim_path}/ActionGraph') - controller.connect(context_node.get_attribute('outputs:context'), - publish_pcl_node.get_attribute('inputs:context')) - # Get namespace node and connect it - namespace_node = controller.node('node_namespace', - f'{carter_prim_path}/ActionGraph') - controller.connect(namespace_node.get_attribute('inputs:value'), - publish_pcl_node.get_attribute('inputs:nodeNamespace')) - - # Configure left camera resolution - # Create camera node and set target resolution - resolution_node = controller.create_node( - f'{carter_prim_path}/ROS_Cameras/isaac_set_viewport_resolution', - 'omni.isaac.core_nodes.IsaacSetViewportResolution', - True, - ) - resolution_node.get_attribute('inputs:width').set(640) - resolution_node.get_attribute('inputs:height').set(480) - # Get viewport creation node - viewport_node = controller.node('isaac_create_viewport_left', - f'{carter_prim_path}/ROS_Cameras') - # Connect it - controller.connect(viewport_node.get_attribute('outputs:execOut'), - resolution_node.get_attribute('inputs:execIn')) - controller.connect(viewport_node.get_attribute('outputs:viewport'), - resolution_node.get_attribute('inputs:viewport')) - - # Change publication topics - left_rgb = controller.node('ros2_create_camera_left_rgb', - f'{carter_prim_path}/ROS_Cameras') - left_rgb.get_attribute('inputs:topicName').set('/left/rgb') - left_info = controller.node('ros2_create_camera_left_info', - f'{carter_prim_path}/ROS_Cameras') - left_info.get_attribute('inputs:topicName').set('/left/camera_info') - left_depth = controller.node('ros2_create_camera_left_depth', - f'{carter_prim_path}/ROS_Cameras') - left_depth.get_attribute('inputs:topicName').set('/left/depth') - - # Finally, enable left rgb and depth - for enable_name in [ - 'enable_camera_left', 'enable_camera_left_rgb', - 'enable_camera_left_depth' - ]: - left_enable = controller.node(enable_name, - f'{carter_prim_path}/ROS_Cameras') - left_enable.get_attribute('inputs:condition').set(True) - - -def setup_forklifts_collision(): - # Finally forklifts - - import omni.kit.commands - from omni.isaac.core.utils.prims import is_prim_path_valid - from pxr import Sdf - - for forklift_name in ['Forklift', 'Forklift_01']: - forklift_path = f'/World/warehouse_with_forklifts/{forklift_name}' - # Ignore if they do not exist - if not is_prim_path_valid(forklift_path): - continue - # Enable collision on main body - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path( - f'{forklift_path}/S_ForkliftBody.physics:collisionEnabled'), - value=True, - prev=None, - ) - # Disable collision on invible main body box - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path( - f'{forklift_path}/chassis_collision.physics:collisionEnabled'), - value=False, - prev=None, - ) - # Enable collision on tine - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path( - f'{forklift_path}/S_ForkliftFork/collision_box.physics:collisionEnabled' - ), - value=False, - prev=None, - ) - # Disable collision on tine box - omni.kit.commands.execute( - 'ChangeProperty', - prop_path=Sdf.Path( - f'{forklift_path}/S_ForkliftFork/S_ForkliftFork.physics:collisionEnabled' - ), - value=True, - prev=None, - ) - - -def main(scenario_path: str, - tick_rate_hz: float = 20.0, - headless: bool = False, - carter_prim_path: str = '/World/Carter_ROS', - carter_version: int = 1): - # Start up the simulator - simulation_app = SimulationApp({ - 'renderer': 'RayTracedLighting', - 'headless': headless - }) - import omni - from omni.isaac.core import SimulationContext - # enable ROS2 bridge extension - from omni.isaac.core.utils.extensions import enable_extension - enable_extension('omni.isaac.ros2_bridge') - - from omni.isaac.core.utils.nucleus import get_assets_root_path - - assets_root_path = get_assets_root_path() - if assets_root_path is None: - import carb - carb.log_error('Could not find Isaac Sim assets folder') - simulation_app.close() - exit() - - usd_path = assets_root_path + scenario_path - print('------{0} n scenario_path:{1}', usd_path, scenario_path) - - # Load the stage - - omni.usd.get_context().open_stage(usd_path, None) - - # Wait two frames so that stage starts loading - simulation_app.update() - simulation_app.update() - - print('Loading stage...') - from omni.isaac.core.utils.stage import is_stage_loading - - while is_stage_loading(): - simulation_app.update() - print('Loading Complete') - - time_dt = 1.0 / tick_rate_hz - print(f'Running sim at {tick_rate_hz} Hz, with dt of {time_dt}') - simulation_context = SimulationContext(stage_units_in_meters=1.0) - - # Configure sensors - print( - f'Configuring sensors for Carter {carter_version} at: {carter_prim_path}' - ) - setup_carter_sensors(carter_prim_path, carter_version=carter_version) - setup_forklifts_collision() - - simulation_context.play() - simulation_context.step() - - # Simulate for one second to warm up sim and let everything settle - # Otherwise the resizes below sometimes don't stick. - for frame in range(round(tick_rate_hz)): - simulation_context.step() - - # Dock the second camera window - right_viewport = omni.ui.Workspace.get_window('Viewport') - left_viewport = omni.ui.Workspace.get_window('Viewport 2') - if right_viewport is not None and left_viewport is not None: - left_viewport.dock_in(right_viewport, omni.ui.DockPosition.LEFT) - right_viewport = None - left_viewport = None - - # Run the sim - last_frame_time = time.monotonic() - while simulation_app.is_running(): - simulation_context.step() - current_frame_time = time.monotonic() - if current_frame_time - last_frame_time < time_dt: - time.sleep(time_dt - (current_frame_time - last_frame_time)) - last_frame_time = time.monotonic() - - simulation_context.stop() - simulation_app.close() - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Sample app for running Carter in a Warehouse for NVblox.') - parser.add_argument( - '--scenario_path', - metavar='scenario_path', - type=str, - help='Path of the scenario to launch relative to the nucleus server ' - 'base path. Scenario must contain a carter robot.', - default='/Isaac/Samples/ROS2/Scenario/carter_warehouse_navigation.usd') - parser.add_argument( - '--tick_rate_hz', - metavar='tick_rate_hz', - type=int, - help='The rate (in hz) that we step the simulation at.', - default=20) - parser.add_argument('--carter_prim_path', - metavar='carter_prim_path', - type=str, - default='/World/Carter_ROS', - help='Path to Carter.') - parser.add_argument('--headless', action='store_true') - parser.add_argument('--carter_version', - type=int, - default=1, - help='Version of the Carter robot (1 or 2)') - args, unknown = parser.parse_known_args() - - main(args.scenario_path, args.tick_rate_hz, args.headless, - args.carter_prim_path, args.carter_version) diff --git a/nvblox_msgs/CMakeLists.txt b/nvblox_msgs/CMakeLists.txt index d06b9aea..65d9f06e 100644 --- a/nvblox_msgs/CMakeLists.txt +++ b/nvblox_msgs/CMakeLists.txt @@ -40,11 +40,12 @@ find_package(std_msgs REQUIRED) find_package(geometry_msgs REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} - "msg/Index3D.msg" + "srv/FilePath.srv" + "msg/SemanticLabelsStamped.msg" "msg/MeshBlock.msg" "msg/Mesh.msg" + "msg/Index3D.msg" "msg/DistanceMapSlice.msg" - "srv/FilePath.srv" DEPENDENCIES std_msgs geometry_msgs ) diff --git a/nvblox_msgs/msg/SemanticLabelsStamped.msg b/nvblox_msgs/msg/SemanticLabelsStamped.msg new file mode 100644 index 00000000..810b2071 --- /dev/null +++ b/nvblox_msgs/msg/SemanticLabelsStamped.msg @@ -0,0 +1,2 @@ +std_msgs/Header header +string labels diff --git a/nvblox_msgs/package.xml b/nvblox_msgs/package.xml index 1a960ff6..ffefb211 100644 --- a/nvblox_msgs/package.xml +++ b/nvblox_msgs/package.xml @@ -21,8 +21,8 @@ nvblox_msgs - 0.20.0 - ROS2 messages for Nvblox types + 0.30.0 + ROS 2 messages for Nvblox types Helen Oleynikova Hemal Shah diff --git a/nvblox_nav2/CMakeLists.txt b/nvblox_nav2/CMakeLists.txt index 5fb575b3..16564d35 100644 --- a/nvblox_nav2/CMakeLists.txt +++ b/nvblox_nav2/CMakeLists.txt @@ -62,19 +62,6 @@ include_directories(include) install(TARGETS ${lib_name} DESTINATION lib) -install(DIRECTORY - launch - DESTINATION share/${PROJECT_NAME} -) -install(DIRECTORY - params - DESTINATION share/${PROJECT_NAME} -) -install(DIRECTORY - config - DESTINATION share/${PROJECT_NAME} -) - if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() @@ -85,4 +72,5 @@ endif() # Install the plugin pluginlib_export_plugin_description_file(nav2_costmap_2d nvblox_costmap_layer.xml) ament_target_dependencies(${lib_name} ${dep_pkgs}) -ament_auto_package(INSTALL_TO_SHARE launch) + +ament_package() diff --git a/nvblox_nav2/config/carter_nvblox_nav2.rviz b/nvblox_nav2/config/carter_nvblox_nav2.rviz deleted file mode 100644 index daf79709..00000000 --- a/nvblox_nav2/config/carter_nvblox_nav2.rviz +++ /dev/null @@ -1,529 +0,0 @@ -Panels: - - Class: rviz_common/Displays - Help Height: 78 - Name: Displays - Property Tree Widget: - Expanded: - - /TF1 - - /TF1/Frames1 - - /TF1/Tree1 - - /NvbloxMesh1 - - /Robot & Sensors1 - - /Robot & Sensors1/rgb_left1 - - /Robot & Sensors1/depth_left1 - - /Global Navigation1 - - /Global Navigation1/Global Costmap1 - - /Global Navigation1/Global Path1 - - /Local Navigation1 - - /Local Navigation1/Local Costmap1 - - /Elbrus1/Camera1 - - /Elbrus1/Camera1/Visibility1 - - /Elbrus1/PointCloud21/Autocompute Value Bounds1 - - /Marker1 - - /PointCloud21 - - /PointCloud21/Autocompute Value Bounds1 - - /MarkerArray1 - - /MarkerArray1/Topic1 - Splitter Ratio: 0.447075217962265 - Tree Height: 940 - - Class: rviz_common/Selection - Name: Selection - - Class: rviz_common/Tool Properties - Expanded: - - /2D Goal Pose1 - - /Publish Point1 - Name: Tool Properties - Splitter Ratio: 0.5886790156364441 - - Class: rviz_common/Views - Expanded: - - /Current View1 - Name: Views - Splitter Ratio: 0.5 -Visualization Manager: - Class: "" - Displays: - - Class: rviz_default_plugins/TF - Enabled: true - Frame Timeout: 15 - Frames: - All Enabled: true - base_link: - Value: true - camera_left_ROS_frame: - Value: true - camera_right_ROS_frame: - Value: true - camera_sensor_left: - Value: true - camera_sensor_right: - Value: true - carter_lidar: - Value: true - caster_frame_base: - Value: true - caster_swivel_left: - Value: true - caster_swivel_right: - Value: true - caster_wheel_left: - Value: true - caster_wheel_right: - Value: true - chassis_link: - Value: true - map: - Value: true - odom: - Value: true - wheel_left: - Value: true - wheel_right: - Value: true - Marker Scale: 1 - Name: TF - Show Arrows: true - Show Axes: true - Show Names: false - Tree: - map: - odom: - base_link: - camera_left_ROS_frame: - {} - camera_right_ROS_frame: - {} - camera_sensor_left: - {} - camera_sensor_right: - {} - carter_lidar: - {} - chassis_link: - caster_frame_base: - caster_swivel_left: - caster_wheel_left: - {} - caster_swivel_right: - caster_wheel_right: - {} - wheel_left: - {} - wheel_right: - {} - Update Interval: 0 - Value: true - - Alpha: 0.5 - Cell Size: 1 - Class: rviz_default_plugins/Grid - Color: 160; 160; 164 - Enabled: true - Line Style: - Line Width: 0.029999999329447746 - Value: Lines - Name: Grid - Normal Cell Count: 0 - Offset: - X: 0 - Y: 0 - Z: 0 - Plane: XY - Plane Cell Count: 100 - Reference Frame: - Value: true - - Ceiling Height: 1.5 - Class: nvblox_rviz_plugin/NvbloxMesh - Cut Ceiling: true - Enabled: true - Mesh Color: Color + Shading - Name: NvbloxMesh - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /nvblox_node/mesh - Value: true - - Class: rviz_common/Group - Displays: - - Class: rviz_default_plugins/Image - Enabled: false - Max Value: 1 - Median window: 5 - Min Value: 0 - Name: rgb_left - Normalize Range: true - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /left/rgb - Value: false - - Class: rviz_default_plugins/Image - Enabled: false - Max Value: 15 - Median window: 5 - Min Value: 0 - Name: depth_left - Normalize Range: false - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /left/depth - Value: false - - Alpha: 1 - Class: rviz_default_plugins/RobotModel - Collision Enabled: false - Description File: /home/helen/ros_ws/src/ov_navigation/carter_description/urdf/carter.urdf - Description Source: File - Description Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: "" - Enabled: false - Links: - All Links Enabled: true - Expand Joint Details: false - Expand Link Details: false - Expand Tree: false - Link Tree Style: Links in Alphabetic Order - Mass Properties: - Inertia: false - Mass: false - Name: RobotModel - TF Prefix: "" - Update Interval: 0 - Value: false - Visual Enabled: true - Enabled: true - Name: Robot & Sensors - - Class: rviz_common/Group - Displays: - - Alpha: 0.699999988079071 - Class: rviz_default_plugins/Map - Color Scheme: costmap - Draw Behind: false - Enabled: true - Name: Global Costmap - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /global_costmap/costmap - Update Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /global_costmap/costmap_updates - Use Timestamp: false - Value: true - - Alpha: 1 - Buffer Length: 1 - Class: rviz_default_plugins/Path - Color: 25; 255; 0 - Enabled: true - Head Diameter: 0.30000001192092896 - Head Length: 0.20000000298023224 - Length: 0.30000001192092896 - Line Style: Billboards - Line Width: 0.10000000149011612 - Name: Global Path - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.029999999329447746 - Shaft Diameter: 0.10000000149011612 - Shaft Length: 0.10000000149011612 - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /plan - Value: true - Enabled: true - Name: Global Navigation - - Class: rviz_common/Group - Displays: - - Alpha: 1 - Buffer Length: 1 - Class: rviz_default_plugins/Path - Color: 239; 41; 41 - Enabled: true - Head Diameter: 0.30000001192092896 - Head Length: 0.20000000298023224 - Length: 0.30000001192092896 - Line Style: Billboards - Line Width: 0.029999999329447746 - Name: Local Path - Offset: - X: 0 - Y: 0 - Z: 0 - Pose Color: 255; 85; 255 - Pose Style: None - Radius: 0.029999999329447746 - Shaft Diameter: 0.10000000149011612 - Shaft Length: 0.10000000149011612 - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /local_plan - Value: true - - Alpha: 0.699999988079071 - Class: rviz_default_plugins/Map - Color Scheme: costmap - Draw Behind: false - Enabled: true - Name: Local Costmap - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /local_costmap/costmap - Update Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /local_costmap/costmap_updates - Use Timestamp: false - Value: true - Enabled: true - Name: Local Navigation - - Class: rviz_common/Group - Displays: - - Class: rviz_default_plugins/Camera - Enabled: false - Far Plane Distance: 100 - Image Rendering: background - Name: Camera - Overlay Alpha: 1 - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /left/rgb - Value: false - Visibility: - Elbrus: - PointCloud2: true - Value: true - Global Navigation: - "": true - Value: false - Grid: false - Local Navigation: - "": true - Value: false - Marker: true - MarkerArray: true - NvbloxMesh: false - PointCloud2: true - Robot & Sensors: - "": true - Value: false - TF: false - Value: true - Zoom Factor: 1 - - Alpha: 1 - Autocompute Intensity Bounds: true - Autocompute Value Bounds: - Max Value: 10 - Min Value: 0 - Value: false - Axis: Z - Channel Name: intensity - Class: rviz_default_plugins/PointCloud2 - Color: 255; 25; 0 - Color Transformer: AxisColor - Decay Time: 0 - Enabled: true - Invert Rainbow: false - Max Color: 255; 255; 255 - Max Intensity: 4096 - Min Color: 0; 0; 0 - Min Intensity: 0 - Name: PointCloud2 - Position Transformer: XYZ - Selectable: true - Size (Pixels): 3 - Size (m): 0.05000000074505806 - Style: Spheres - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /visual_slam/vis/loop_closure_cloud - Use Fixed Frame: true - Use rainbow: true - Value: true - Enabled: false - Name: Elbrus - - Class: rviz_default_plugins/Marker - Enabled: true - Name: Marker - Namespaces: - {} - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /nvblox_node/map_slice_bounds - Value: true - - Alpha: 1 - Autocompute Intensity Bounds: true - Autocompute Value Bounds: - Max Value: 0 - Min Value: -0.5 - Value: false - Axis: Z - Channel Name: intensity - Class: rviz_default_plugins/PointCloud2 - Color: 255; 255; 255 - Color Transformer: AxisColor - Decay Time: 0 - Enabled: false - Invert Rainbow: false - Max Color: 255; 255; 255 - Max Intensity: 4096 - Min Color: 0; 0; 0 - Min Intensity: 0 - Name: PointCloud2 - Position Transformer: XYZ - Selectable: true - Size (Pixels): 3 - Size (m): 0.02500000037252903 - Style: Flat Squares - Topic: - Depth: 5 - Durability Policy: Volatile - Filter size: 10 - History Policy: Keep Last - Reliability Policy: Reliable - Value: /point_cloud - Use Fixed Frame: true - Use rainbow: true - Value: false - - Class: rviz_default_plugins/MarkerArray - Enabled: false - Name: MarkerArray - Namespaces: - {} - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /nvblox_node/mesh_marker - Value: false - Enabled: true - Global Options: - Background Color: 255; 255; 255 - Fixed Frame: odom - Frame Rate: 30 - Name: root - Tools: - - Class: rviz_default_plugins/Interact - Hide Inactive Objects: true - - Class: rviz_default_plugins/MoveCamera - - Class: rviz_default_plugins/Select - - Class: rviz_default_plugins/FocusCamera - - Class: rviz_default_plugins/Measure - Line color: 128; 128; 0 - - Class: rviz_default_plugins/SetInitialPose - Covariance x: 0.25 - Covariance y: 0.25 - Covariance yaw: 0.06853891909122467 - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /initialpose - - Class: rviz_default_plugins/SetGoal - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /goal_pose - - Class: rviz_default_plugins/PublishPoint - Single click: true - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /clicked_point - Transformation: - Current: - Class: rviz_default_plugins/TF - Value: true - Views: - Current: - Class: rviz_default_plugins/Orbit - Distance: 23.513273239135742 - Enable Stereo Rendering: - Stereo Eye Separation: 0.05999999865889549 - Stereo Focal Distance: 1 - Swap Stereo Eyes: false - Value: false - Focal Point: - X: 2.2487809658050537 - Y: -0.7407564520835876 - Z: -1.808356523513794 - Focal Shape Fixed Size: false - Focal Shape Size: 0.05000000074505806 - Invert Z Axis: false - Name: Current View - Near Clip Distance: 0.009999999776482582 - Pitch: 0.6247962117195129 - Target Frame: base_link - Value: Orbit (rviz) - Yaw: 3.115401029586792 - Saved: ~ -Window Geometry: - Camera: - collapsed: false - Displays: - collapsed: false - Height: 1163 - Hide Left Dock: false - Hide Right Dock: false - QMainWindow State: 000000ff00000000fd00000004000000000000017b00000435fc020000000cfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b00000435000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb00000010007200670062005f006c00650066007402000008f4000000fe000003740000026efb0000001400640065007000740068005f006c0065006600740200000c8c000001e70000014600000125fb0000000a0049006d0061006700650200000c900000023f0000025e000001d3fb0000000c00430061006d0065007200610200000c2f00000104000001e600000190000000010000010f00000435fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003b00000435000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004ea0000043500000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 - Selection: - collapsed: false - Tool Properties: - collapsed: false - Views: - collapsed: false - Width: 1920 - X: 1920 - Y: 0 - depth_left: - collapsed: false - rgb_left: - collapsed: false diff --git a/nvblox_nav2/launch/carter_sim.launch.py b/nvblox_nav2/launch/carter_sim.launch.py deleted file mode 100644 index 40bb1d2a..00000000 --- a/nvblox_nav2/launch/carter_sim.launch.py +++ /dev/null @@ -1,118 +0,0 @@ -# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# 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. -# -# SPDX-License-Identifier: Apache-2.0 - -import os - -from ament_index_python.packages import get_package_share_directory - - -from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription -from launch.conditions import IfCondition -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration - -from launch_ros.actions import Node - - -def generate_launch_description(): - - params_file_arg = DeclareLaunchArgument( - 'params_file', default_value=os.path.join( - get_package_share_directory( - 'nvblox_nav2'), 'params', 'carter_nav2.yaml'), - description='Full path to param file to load') - use_sim_time_arg = DeclareLaunchArgument( - 'use_sim_time', default_value='True', - description='Use simulation (Omniverse Isaac Sim) clock if true') - - nvblox_param_arg = DeclareLaunchArgument( - 'nvblox_params_file', - default_value=os.path.join( - get_package_share_directory('nvblox_nav2'), 'params', 'nvblox.yaml' - ) - ) - use_depth_arg = DeclareLaunchArgument( - 'use_depth', default_value='True', - description='Use depth as an input for nvblox reconstruction' - ) - use_lidar_arg = DeclareLaunchArgument( - 'use_lidar', default_value='True', - description='Use lidar as an input for nvblox reconstruction' - ) - run_rviz_arg = DeclareLaunchArgument( - 'run_rviz', default_value='True', - description='Whether to start RVIZ') - run_nav2_arg = DeclareLaunchArgument( - 'run_nav2', default_value='True', - description='Whether to run nav2') - - nav2_bringup_launch_dir = os.path.join( - get_package_share_directory('nav2_bringup'), 'launch') - rviz_config_dir = os.path.join(get_package_share_directory( - 'nvblox_nav2'), 'config', 'carter_nvblox_nav2.rviz') - - rviz_launch = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(nav2_bringup_launch_dir, 'rviz_launch.py')), - launch_arguments={'namespace': '', 'use_namespace': 'False', - 'autostart': 'True', - 'rviz_config': rviz_config_dir}.items(), - condition=IfCondition(LaunchConfiguration('run_rviz'))) - - nav2_launch = IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(nav2_bringup_launch_dir, 'navigation_launch.py')), - launch_arguments={'use_sim_time': LaunchConfiguration('use_sim_time'), - 'params_file': LaunchConfiguration('params_file'), - 'autostart': 'True'}.items(), - condition=IfCondition(LaunchConfiguration('run_nav2'))) - - nvblox_node = Node( - package='nvblox_ros', executable='nvblox_node', - parameters=[LaunchConfiguration('nvblox_params_file'), - {'use_sim_time': LaunchConfiguration('use_sim_time'), - 'use_depth': LaunchConfiguration('use_depth'), - 'use_lidar': LaunchConfiguration('use_lidar')}], - output='screen', - remappings=[('depth/image', '/left/depth'), - ('depth/camera_info', '/left/camera_info'), - ('color/image', '/left/rgb'), - ('color/camera_info', '/left/camera_info'), - ('pointcloud', '/point_cloud') - ]) - - map_to_odom_publisher = Node( - package='tf2_ros', executable='static_transform_publisher', - parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time')}], - output='screen', - arguments=['0.0', '0.0', '-0.3', '0.0', '0.0', '0.0', 'map', - 'odom'], - condition=IfCondition(LaunchConfiguration('run_nav2'))) - - return LaunchDescription([ - params_file_arg, - use_sim_time_arg, - nvblox_param_arg, - use_depth_arg, - use_lidar_arg, - run_rviz_arg, - run_nav2_arg, - rviz_launch, - nav2_launch, - nvblox_node, - map_to_odom_publisher]) diff --git a/nvblox_nav2/launch/carter_sim_elbrus.launch.py b/nvblox_nav2/launch/carter_sim_elbrus.launch.py deleted file mode 100644 index 903a4040..00000000 --- a/nvblox_nav2/launch/carter_sim_elbrus.launch.py +++ /dev/null @@ -1,149 +0,0 @@ -# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# 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. -# -# SPDX-License-Identifier: Apache-2.0 - -import os - -from ament_index_python.packages import get_package_share_directory - -from launch import LaunchDescription -from launch.actions import DeclareLaunchArgument -from launch.actions import IncludeLaunchDescription -from launch.conditions import IfCondition -from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration -from launch_ros.actions import ComposableNodeContainer -from launch_ros.actions import Node -from launch_ros.descriptions import ComposableNode - - -def generate_launch_description(): - - use_sim_time = LaunchConfiguration('use_sim_time', default='True') - - param_dir = LaunchConfiguration( - 'params_file', - default=os.path.join( - get_package_share_directory( - 'nvblox_nav2'), 'params', 'carter_nav2.yaml' - ), - ) - - nvblox_param_dir = LaunchConfiguration( - 'nvblox_params_file', default=os.path.join( - get_package_share_directory('nvblox_nav2'), - 'params', 'nvblox.yaml'),) - - nav2_bringup_launch_dir = os.path.join( - get_package_share_directory('nav2_bringup'), 'launch') - - rviz_config_dir = os.path.join(get_package_share_directory( - 'nvblox_nav2'), 'config', 'carter_nvblox_nav2.rviz') - - lifecycle_nodes = ['map_server'] - - visual_slam_node = ComposableNode( - name='visual_slam_node', - package='isaac_ros_visual_slam', - plugin='isaac_ros::visual_slam::VisualSlamNode', - remappings=[('stereo_camera/left/camera_info', '/left/camera_info'), - ('stereo_camera/right/camera_info', '/right/camera_info'), - ('stereo_camera/left/image', '/left/rgb'), - ('stereo_camera/right/image', '/right/rgb')], - parameters=[{ - 'enable_rectified_pose': True, - 'denoise_input_images': True, - 'rectified_images': True, - 'enable_debug_mode': False, - 'debug_dump_path': '/tmp/elbrus', - 'left_camera_frame': 'carter_camera_stereo_left', - 'right_camera_frame': 'carter_camera_stereo_right', - 'map_frame': 'map', - 'fixed_frame': 'odom', - 'odom_frame': 'odom', - 'base_frame': 'base_link', - 'current_smooth_frame': 'base_link_smooth', - 'current_rectified_frame': 'base_link_rectified', - 'enable_observations_view': True, - 'enable_landmarks_view': True, - 'enable_reading_slam_internals': True, - 'enable_slam_visualization': True, - 'enable_localization_n_mapping': True, - 'use_sim_time': use_sim_time - }] - ) - visual_slam_launch_container = ComposableNodeContainer( - name='visual_slam_launch_container', - namespace='', - package='rclcpp_components', - executable='component_container', - composable_node_descriptions=[ - visual_slam_node - ], - output='screen' - ) - - return LaunchDescription( - [ - DeclareLaunchArgument( - 'params_file', default_value=param_dir, - description='Full path to param file to load'), - DeclareLaunchArgument( - 'use_sim_time', default_value='True', - description='Use simulation (Omniverse Isaac Sim) clock if true'), - DeclareLaunchArgument( - 'run_rviz', default_value='true', - description='Whether to start RVIZ'), - DeclareLaunchArgument( - 'run_nav2', default_value='true', - description='Whether to run nav2'), - Node( - package='nav2_lifecycle_manager', executable='lifecycle_manager', - name='lifecycle_manager_map', output='screen', - parameters=[{'use_sim_time': use_sim_time}, - {'autostart': True}, - {'node_names': lifecycle_nodes}], - condition=IfCondition( - LaunchConfiguration('run_nav2')) - ), - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join(nav2_bringup_launch_dir, 'rviz_launch.py')), - launch_arguments={'namespace': '', 'use_namespace': 'False', - 'autostart': 'True', - 'rviz_config': rviz_config_dir}.items(), - condition=IfCondition(LaunchConfiguration('run_rviz')),), - IncludeLaunchDescription( - PythonLaunchDescriptionSource( - os.path.join( - nav2_bringup_launch_dir, 'navigation_launch.py')), - launch_arguments={'use_sim_time': use_sim_time, - 'params_file': param_dir, - 'autostart': 'True'}.items(), - condition=IfCondition( - LaunchConfiguration('run_nav2')) - ), - Node( - package='nvblox_ros', executable='nvblox_node', - parameters=[nvblox_param_dir, {'use_sim_time': use_sim_time, - 'global_frame': 'odom'}], - output='screen', - remappings=[('depth/image', '/left/depth'), - ('depth/camera_info', '/left/camera_info'), - ('color/image', '/left/rgb'), - ('color/camera_info', '/left/camera_info'), ]), - visual_slam_launch_container - ]) diff --git a/nvblox_nav2/package.xml b/nvblox_nav2/package.xml index f6eb9ac6..dc0b6777 100644 --- a/nvblox_nav2/package.xml +++ b/nvblox_nav2/package.xml @@ -21,8 +21,8 @@ nvblox_nav2 - 0.20.0 - NVBlox ROS2 Nav2 interface + 0.30.0 + NVBlox ROS 2 Nav2 interface Helen Oleynikova Hemal Shah @@ -34,7 +34,6 @@ ament_cmake nav_msgs - nav2_bringup nav2_costmap_2d nvblox_msgs pluginlib diff --git a/nvblox_nav2/params/nvblox.yaml b/nvblox_nav2/params/nvblox.yaml deleted file mode 100644 index 269ebcd8..00000000 --- a/nvblox_nav2/params/nvblox.yaml +++ /dev/null @@ -1,47 +0,0 @@ -%YAML 1.2 -# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -# Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# 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. -# -# SPDX-License-Identifier: Apache-2.0 ---- -nvblox_node: - ros__parameters: - global_frame: odom - voxel_size: 0.05 - slice_height: 0.75 - #min_height: -0.25 - #min_height: -0.1 - min_height: 0.1 - max_height: 1.0 - esdf: true - esdf_2d: true - mesh: true - distance_slice: true - use_color: true - tsdf_integrator_max_integration_distance_m: 4.0 - lidar_tsdf_integrator_max_integration_distance_m: 10.0 - tsdf_integrator_truncation_distance_vox: 4.0 - tsdf_integrator_max_weight: 20.0 - esdf_integrator_min_weight: 2.0 - esdf_integrator_max_distance_m: 2.0 - esdf_integrator_max_site_distance_vox: 5.0 - max_tsdf_update_hz: 0.0 - max_pointcloud_update_hz: 0.0 - max_color_update_hz: 5.0 - max_mesh_update_hz: 1.0 - max_esdf_update_hz: 10.0 - lidar_width: 1800 - lidar_height: 31 - output_dir: /tmp/ diff --git a/nvblox_nav2/src/nvblox_costmap_layer.cpp b/nvblox_nav2/src/nvblox_costmap_layer.cpp index 721816ed..55199716 100644 --- a/nvblox_nav2/src/nvblox_costmap_layer.cpp +++ b/nvblox_nav2/src/nvblox_costmap_layer.cpp @@ -74,11 +74,26 @@ void NvbloxCostmapLayer::updateBounds( { // We give the full AABB of the map that we have available to the // upstream controller. + // According to nav2 the bounds can only grow bigger. So we only update the + // bounds if it grows bigger or we keep the old values if (slice_ != nullptr) { - *min_x = slice_->origin.x; - *max_x = *min_x + slice_->width * slice_->resolution; - *min_y = slice_->origin.y; - *max_y = *min_y + slice_->height * slice_->resolution; + const double current_plugin_min_x = slice_->origin.x; + const double current_plugin_min_y = slice_->origin.y; + const double current_plugin_max_x = current_plugin_min_x + slice_->width * slice_->resolution; + const double current_plugin_max_y = current_plugin_min_y + slice_->height * slice_->resolution; + + if (current_plugin_min_x < *min_x) { + *min_x = current_plugin_min_x; + } + if (current_plugin_max_x > *max_x) { + *max_x = current_plugin_max_x; + } + if (current_plugin_min_y < *min_y) { + *min_y = current_plugin_min_y; + } + if (current_plugin_max_y > *max_y) { + *max_y = current_plugin_max_y; + } } auto node = node_.lock(); diff --git a/nvblox_performance_measurement/README.md b/nvblox_performance_measurement/README.md new file mode 100644 index 00000000..ffb42a8c --- /dev/null +++ b/nvblox_performance_measurement/README.md @@ -0,0 +1,63 @@ +# nvblox_ros Performance Measurement + +The nvblox core library comes equipped with timers and nvtx ranges which provide a means of measuring performance. However, to get an accurate picture of nvblox performance on a robot, we would like to measure its performance inside a ROS 2 system. + +For this purpose, we have tools for measuring performance in this subfolder. + +The setup is indicated below: + + - We wrap the `nvblox_ros` node in a layer which exposes further information, specifically about which input messages are added to the reconstruction. + - The `nvblox_performance` node subscribes to input and output messages and records their timestamps. + - We also monitor the CPU percentage taken by the `nvblox_ros` node. + +
+ +# Running a benchmark +To launch a benchmark run +```bash +ros2 run nvblox_performance_measurement run_benchmark.py {YOUR_ROSBAG_PATH} +``` +where {YOUR_ROSBAG_PATH} is a path to your dataset. + +The output of running this script should be something like: +```bash +Message Numbers +depth: released : 518 processed : 514 +color: released : 516 processed : 513 +lidar: released : 520 processed : 0 +slice: processed : 265 +mesh: processed : 241 + +Message Frequencies +depth: released Hz: 19.7 processed Hz: 19.5 +color: released Hz: 19.6 processed Hz: 19.6 +lidar: released Hz: 19.7 processed Hz: nan +slice: processed Hz: 10.1 +mesh: processed Hz: 0.5 + +Mean CPU usage: 15.7% +Mean GPU usage: 9.9% + +``` + +The run script has a number of configuration arguments: +```bash +ros2 run nvblox_performance_measurement run_benchmark.py -h +usage: run_benchmark.py [-h] [--realsense] [--output_dir OUTPUT_DIR] [--save_results_files] + [--save_pulse_plot] [--save_kpi_json] + [bag_filepath] + +Run a benchmark of nvblox_ros. + +positional arguments: + bag_filepath Path to the bag to run the benchmark on. + +optional arguments: + -h, --help show this help message and exit + --realsense Whether to use realsense settings. + --output_dir OUTPUT_DIR + Directory in which to save the results files. + --save_results_files Flag indicating if we should save the results to files. + --save_pulse_plot Flag indicating if we should save the pulse plot. + --save_kpi_json Flag indicating if we should save the KPI table as a json. +``` diff --git a/nvblox_performance_measurement/nvblox_cpu_gpu_tools/nvblox_cpu_gpu_tools/gpu_percentage_node.py b/nvblox_performance_measurement/nvblox_cpu_gpu_tools/nvblox_cpu_gpu_tools/gpu_percentage_node.py index 8ebc40fb..aba3bea3 100644 --- a/nvblox_performance_measurement/nvblox_cpu_gpu_tools/nvblox_cpu_gpu_tools/gpu_percentage_node.py +++ b/nvblox_performance_measurement/nvblox_cpu_gpu_tools/nvblox_cpu_gpu_tools/gpu_percentage_node.py @@ -58,6 +58,11 @@ def __init__(self, on_jetson_flag: bool): ParameterDescriptor( type=ParameterType.PARAMETER_DOUBLE, description='The interval passed to psutil.')) + self.declare_parameter( + 'gpu_index', 0, + ParameterDescriptor( + type=ParameterType.PARAMETER_INTEGER, + description='The index of the GPU to track on a multi GPU system.')) # Get the initial values self._time_between_measurements_s = self.get_parameter( @@ -65,13 +70,21 @@ def __init__(self, on_jetson_flag: bool): self._measurement_interval_s = self.get_parameter( 'measurement_interval_s').get_parameter_value().double_value - # Check we only have a single GPU + # Index of the GPU to monitor + self._gpu_index = 0 + # If we have multiple GPUs, read the index to track from ROS if self.get_number_of_gpus() != 1: - self.get_logger().fatal( - f"""Detected {self.get_number_of_gpus()} GPUs. This node (currently) only works on - systems with a single GPU. Failing.""") - self._ready = False - return + self.get_logger().info('Multi GPU system detected.') + self._gpu_index = self.get_parameter( + 'gpu_index').get_parameter_value().integer_value + if self._gpu_index >= self.get_number_of_gpus() or self._gpu_index < 0: + self.get_logger().fatal( + f"""Detected {self.get_number_of_gpus()} GPUs. Requested to track + GPU index {self._gpu_index}. Not possible. Failing.""") + self._ready = False + return + self.get_logger().info( + f'Tracking GPU usage for GPU index: {self._gpu_index}.') # Indicating that the setup was successful self._ready = True diff --git a/nvblox_performance_measurement/nvblox_cpu_gpu_tools/package.xml b/nvblox_performance_measurement/nvblox_cpu_gpu_tools/package.xml index 0814320e..e5b04061 100644 --- a/nvblox_performance_measurement/nvblox_cpu_gpu_tools/package.xml +++ b/nvblox_performance_measurement/nvblox_cpu_gpu_tools/package.xml @@ -21,7 +21,7 @@ nvblox_cpu_gpu_tools - 0.20.0 + 0.30.0 nvblox CPU/GPU Tools alex Apache-2.0 diff --git a/nvblox_performance_measurement/nvblox_cpu_gpu_tools/setup.py b/nvblox_performance_measurement/nvblox_cpu_gpu_tools/setup.py index ffd56a36..844cd26f 100644 --- a/nvblox_performance_measurement/nvblox_cpu_gpu_tools/setup.py +++ b/nvblox_performance_measurement/nvblox_cpu_gpu_tools/setup.py @@ -21,7 +21,7 @@ setup( name=package_name, - version='0.20.0', + version='0.30.0', packages=[package_name], data_files=[ ('share/ament_index/resource_index/packages', @@ -32,7 +32,7 @@ zip_safe=True, maintainer='Alexander Millane', maintainer_email='amillane@nvidia.com', - description='TODO: Package description', + description='nvblox cpu gpu tools', license='Apache-2.0', tests_require=['pytest'], entry_points={ diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/CMakeLists.txt b/nvblox_performance_measurement/nvblox_performance_measurement/CMakeLists.txt index a7f9afb5..f18d6d34 100644 --- a/nvblox_performance_measurement/nvblox_performance_measurement/CMakeLists.txt +++ b/nvblox_performance_measurement/nvblox_performance_measurement/CMakeLists.txt @@ -27,7 +27,7 @@ endif() # Default to C++14 if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_STANDARD 17) endif() if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/metadata.yaml b/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/metadata.yaml deleted file mode 100644 index b087b9b2..00000000 --- a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/metadata.yaml +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7acb940be4bc86f20f4c7fba31a4b5480854096c74685c0021d502cf8677d466 -size 4314 diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/rosbag2_2022_04_26-09_05_29_0.db3 b/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/rosbag2_2022_04_26-09_05_29_0.db3 deleted file mode 100644 index aab10b81..00000000 --- a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/rosbag2_2022_04_26-09_05_29_0.db3 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe543bcb36909d21749934cd3ee0ed77553580d48cd140f0d8f08195a1327f8c -size 1802674176 diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/rosbag2_2022_04_26-09_05_29_0.db3-shm b/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/rosbag2_2022_04_26-09_05_29_0.db3-shm deleted file mode 100644 index 199c89c1..00000000 --- a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/rosbag2_2022_04_26-09_05_29_0.db3-shm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd4c9fda9cd3f9ae7c962b0ddf37232294d55580e1aa165aa06129b8549389eb -size 32768 diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/rosbag2_2022_04_26-09_05_29_0.db3-wal b/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse/rosbag2_2022_04_26-09_05_29_0.db3-wal deleted file mode 100644 index e69de29b..00000000 diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/realsense_office/metadata.yaml b/nvblox_performance_measurement/nvblox_performance_measurement/datasets/realsense_office/metadata.yaml deleted file mode 100644 index 79146525..00000000 --- a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/realsense_office/metadata.yaml +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ecffaba149b7b84868adeaaea661434a04f15ce98520db5a5c4671acd72ffe71 -size 3577 diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/realsense_office/rosbag2_2022_04_27-14_12_56_0.db3 b/nvblox_performance_measurement/nvblox_performance_measurement/datasets/realsense_office/rosbag2_2022_04_27-14_12_56_0.db3 deleted file mode 100644 index 0cb2916c..00000000 --- a/nvblox_performance_measurement/nvblox_performance_measurement/datasets/realsense_office/rosbag2_2022_04_27-14_12_56_0.db3 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4474a8844cf4c575e00fd8201d0a5b65d97e92652a942b4bd85330811ad4eab -size 753528832 diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/include/nvblox_performance_measurement/nvblox_performance_measurement_node.hpp b/nvblox_performance_measurement/nvblox_performance_measurement/include/nvblox_performance_measurement/nvblox_performance_measurement_node.hpp index c480a55a..772f20d0 100644 --- a/nvblox_performance_measurement/nvblox_performance_measurement/include/nvblox_performance_measurement/nvblox_performance_measurement_node.hpp +++ b/nvblox_performance_measurement/nvblox_performance_measurement/include/nvblox_performance_measurement/nvblox_performance_measurement_node.hpp @@ -36,13 +36,15 @@ class NvbloxPerformanceMeasurementNode : public NvbloxNode // Callback functions. These just stick images in a queue. // Process a single images virtual bool processDepthImage( - sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr, - sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg) override; + const std::pair & + depth_camera_pair) override; virtual bool processColorImage( - sensor_msgs::msg::Image::ConstSharedPtr & color_img_ptr, - sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg) override; + const std::pair & + color_camera_pair) override; virtual bool processLidarPointcloud( - sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud_ptr) override; + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud_ptr) override; virtual void processMesh() override; diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/launch/carter_sim_benchmark.launch.py b/nvblox_performance_measurement/nvblox_performance_measurement/launch/carter_sim_benchmark.launch.py index 3e89152c..82b0d861 100644 --- a/nvblox_performance_measurement/nvblox_performance_measurement/launch/carter_sim_benchmark.launch.py +++ b/nvblox_performance_measurement/nvblox_performance_measurement/launch/carter_sim_benchmark.launch.py @@ -33,7 +33,7 @@ def generate_launch_description(): nvblox_param_dir_arg = DeclareLaunchArgument( 'nvblox_params_file', default_value=os.path.join( - get_package_share_directory('nvblox_nav2'), 'params', 'nvblox.yaml' + get_package_share_directory('nvblox_examples_bringup'), 'config', 'nvblox', 'nvblox_base.yaml' ), ) @@ -42,10 +42,10 @@ def generate_launch_description(): description='Use realsense data (otherwise use Omniverse Isaac Sim data)') # Carter Sim Remaps - carter_sim_remaps = [('depth/image', '/left/depth'), - ('depth/camera_info', '/left/camera_info'), - ('color/image', '/left/rgb'), - ('color/camera_info', '/left/camera_info'), + carter_sim_remaps = [('depth/image', '/front/stereo_camera/left/depth'), + ('depth/camera_info', '/front/stereo_camera/left/camera_info'), + ('color/image', '/front/stereo_camera/left/rgb'), + ('color/camera_info', '/front/stereo_camera/left/camera_info'), ('pointcloud', '/point_cloud')] realsense_remaps = [('depth/image', '/camera/depth/image_rect_raw'), ('depth/camera_info', '/camera/depth/camera_info'), diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/package.xml b/nvblox_performance_measurement/nvblox_performance_measurement/package.xml index a3989bb6..03d7a3b1 100644 --- a/nvblox_performance_measurement/nvblox_performance_measurement/package.xml +++ b/nvblox_performance_measurement/nvblox_performance_measurement/package.xml @@ -21,7 +21,7 @@ nvblox_performance_measurement - 0.20.0 + 0.30.0 Tools for performance measurement of nvblox Alexander Millane Helen Oleynikova diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/scripts/analyze_timestamps.py b/nvblox_performance_measurement/nvblox_performance_measurement/scripts/analyze_timestamps.py index b6b28a89..63dba9ef 100755 --- a/nvblox_performance_measurement/nvblox_performance_measurement/scripts/analyze_timestamps.py +++ b/nvblox_performance_measurement/nvblox_performance_measurement/scripts/analyze_timestamps.py @@ -19,10 +19,8 @@ import sys import os -import io import argparse import numpy as np -import matplotlib.pyplot as plt def generate_pulses(stamps, pulse_height): @@ -39,9 +37,10 @@ def generate_pulses(stamps, pulse_height): return np.array(pulse_x), np.array(pulse_y) -def plot_pulses(stamps, pulse_height, pulse_color_code='b'): +def get_pulses_plot(stamps, pulse_height, line_name): + import plotly.graph_objects as go pulse_x, pulse_y = generate_pulses(stamps, pulse_height) - plt.plot(pulse_x, pulse_y, pulse_color_code) + return go.Scatter(x=pulse_x, y=pulse_y, name=line_name) def make_results_table(topic_to_stamps_map, cpu_percentages=None, gpu_percentages=None): @@ -97,15 +96,25 @@ def stamps_to_freq(stamps): return 1e9 / np.mean(np.diff(stamps)) table_string += f"slice:\t\t\t\t\tprocessed Hz:\t{slice_processed_freq:0.1f}\n" table_string += f"mesh:\t\t\t\t\tprocessed Hz:\t{mesh_processed_freq:0.1f}\n" + # Putting the table into a dictionary + table_dict = {'depth_released_freq': depth_released_freq, + 'depth_processed_freq': depth_processed_freq, + 'color_released_freq': color_released_freq, + 'color_processed_freq': color_processed_freq, + 'pointcloud_released_freq': lidar_released_freq, + 'pointcloud_processed_freq': lidar_processed_freq} + if cpu_percentages is not None: table_string += "\n" table_string += f"Mean CPU usage: {np.mean(cpu_percentages):0.1f}%\n" + table_dict['cpu_usage'] = np.mean(cpu_percentages) if gpu_percentages is not None: table_string += f"Mean GPU usage: {np.mean(gpu_percentages):0.1f}%\n" + table_dict['gpu_usage'] = np.mean(gpu_percentages) table_string += "\n\n" - return table_string + return table_string, table_dict def main(): diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/scripts/run_benchmark.py b/nvblox_performance_measurement/nvblox_performance_measurement/scripts/run_benchmark.py index 578a17a8..4aa0b53c 100755 --- a/nvblox_performance_measurement/nvblox_performance_measurement/scripts/run_benchmark.py +++ b/nvblox_performance_measurement/nvblox_performance_measurement/scripts/run_benchmark.py @@ -25,6 +25,7 @@ import sys import time from pathlib import Path +import math import numpy as np import rclpy @@ -123,66 +124,6 @@ def wait_for_result(self, result_future): print('Didn\'t hear back yet...') -def save_isaac_ros_benchmark_results( - stamps_dict, log_dir, heading, log_file=None, timestr=None): - - # Capturing metrics in the dictionary to be forwarded for json dump. - performance_data = {} - - def get_performance_data(stamps_ns: np.array, message_name_str: str, - messages_name_str: str): - def ns_to_ms(stamps): return stamps / 1e6 - def ns_to_s(stamps): return stamps / 1e9 - slice_intervals_ms = ns_to_ms(np.diff(stamps_ns)) - if len(slice_intervals_ms) > 0: - performance_data[f'Number of {messages_name_str} received'] = len( - stamps_ns) - performance_data[ - f'First {message_name_str} to {message_name_str} Latency (ms)'] = slice_intervals_ms[0] - performance_data[f'{message_name_str} Maximum Latency (ms)'] = max( - slice_intervals_ms) - performance_data[f'{message_name_str} Minimum Latency (ms)'] = min( - slice_intervals_ms) - performance_data[f'{message_name_str} Average Latency (ms)'] = sum( - slice_intervals_ms) / len(slice_intervals_ms) - performance_data[f'{message_name_str} Average FPS (s)'] = 1 / \ - ns_to_s((stamps_ns[-1] - stamps_ns[0]) / len(stamps_ns)) - return performance_data - - performance_data.update( - get_performance_data( - stamps_dict['/nvblox_node/map_slice'], - 'Slice', 'Slices')) - performance_data.update( - get_performance_data( - stamps_dict['/nvblox_node/depth_processed'], - 'DepthProcessed', 'DepthProcessed')) - performance_data.update( - get_performance_data( - stamps_dict['/nvblox_node/color_processed'], - 'ColorProcessed', 'ColorsProcessed')) - if '/nvblox_node/mesh' in stamps_dict: - performance_data.update( - get_performance_data( - stamps_dict['/nvblox_node/mesh'], - 'Mesh', 'Meshes')) - - # Dump JSON - for key, val in performance_data.items(): - performance_data[key] = float(val) - performance_data['name'] = heading - if not log_file: - if timestr is None: - timestr = time.strftime('%Y%m%d-%H%M%S') - log_file = os.path.join(log_dir, f'isaac-ros-log-{timestr}.json') - else: - log_file = os.path.join(log_dir, log_file) - print(f'Writing results in Isaac ROS format to: {log_file}') - - with open(log_file, 'x') as f: - f.write(json.dumps(performance_data)) - - def save_results_files(stamps_dict: dict, cpu_samples: np.ndarray, gpu_samples: np.ndarray, timers_string: str, results_table: str, output_dir: Path, timestr: str = None): if timestr is None: timestr = time.strftime('%Y%m%d-%H%M%S') @@ -196,6 +137,45 @@ def save_results_files(stamps_dict: dict, cpu_samples: np.ndarray, gpu_samples: print(f"Wrote results in directory: {output_dir}") +def save_pulse_figure(stamps_dict: dict, filepath: Path): + from plotly.subplots import make_subplots + + if len(stamps_dict['pointcloud']) > 0: + pointclouds_present = True + else: + pointclouds_present = False + + if pointclouds_present: + fig = make_subplots(rows=3, cols=1) + else: + fig = make_subplots(rows=2, cols=1) + + fig.add_trace(analyze_timestamps.get_pulses_plot( + stamps_dict['/nvblox_node/depth_processed'], 2.0, line_name='depth processed'), row=1, col=1) + fig.add_trace(analyze_timestamps.get_pulses_plot( + stamps_dict['depth/image'], 1.0, line_name='depth released'), row=1, col=1) + fig.add_trace(analyze_timestamps.get_pulses_plot( + stamps_dict['/nvblox_node/color_processed'], 2.0, line_name='color processed'), row=2, col=1) + fig.add_trace(analyze_timestamps.get_pulses_plot( + stamps_dict['color/image'], 1.0, line_name='color released'), row=2, col=1) + + if pointclouds_present: + fig.add_trace(analyze_timestamps.get_pulses_plot( + stamps_dict['/nvblox_node/pointcloud_processed'], 2.0, line_name='pointcloud processed'), row=3, col=1) + fig.add_trace(analyze_timestamps.get_pulses_plot( + stamps_dict['pointcloud'], 1.0, line_name='pointcloud released'), row=3, col=1) + + fig.write_html(str(filepath)) + print(f'Wrote pulse figure to: {filepath}') + + +def remove_nan_values_from_dict(input: dict) -> dict: + keys_to_delete = [key for key, value in input.items() if math.isnan(value)] + for key in keys_to_delete: + input.pop(key) + return input + + def main(args=None): rclpy.init(args=args) @@ -211,48 +191,56 @@ def main(args=None): "--output_dir", help="Directory in which to save the results files.") parser.add_argument( - "--json_file_name", - help="Exact name of the file where the results will be dumped. If not specified, file name \ - will have the form isaac-ros-log-timestr.json" - ) + "--save_results_files", action='store_const', const=True, + default=False, + help="Flag indicating if we should save the results to files.") parser.add_argument( - "--save_json", action='store_const', const=True, + "--save_pulse_plot", action='store_const', const=True, default=False, - help="Flag indicating if we should save the isaac ros benchmarking json object.") + help="Flag indicating if we should save the pulse plot.") parser.add_argument( - "--save_results_files", action='store_const', const=True, + "--save_kpi_json", action='store_const', const=True, default=False, - help="Flag indicating if we should save the results to files.") + help="Flag indicating if we should save the KPI table as a json.") args = parser.parse_args() - # If no bag provided, use the POL bag in {ROS2_WORKSPACE}/isaac_ros_nvblox/nvblox_nav2/... - # NOTE(alexmillane): Getting the dataset path here is kinda ugly, but I can't see a better way in ROS2 + # If no bag provided, use bags that come with the repo. + # NOTE(alexmillane): Getting the dataset path here is kinda ugly, but I can't see a better way in ROS 2 if args.bag_filepath is None: print("No bagfile provided. Using the bag distributed with this package (nvblox_performance_measurement).") nvblox_performance_measurement_share_dir = Path( get_package_share_directory('nvblox_performance_measurement')) workspace_dir = nvblox_performance_measurement_share_dir.parents[3] workspace_src_dir = workspace_dir / 'src' - - # Fetch git-lfs files - print(subprocess.getoutput(f'git lfs -X ""')) + + # Default bag locations (delivered via git lfs) if args.realsense: args.bag_filepath = workspace_src_dir / \ Path('isaac_ros_nvblox/nvblox_performance_measurement/nvblox_performance_measurement/datasets/realsense_office') else: args.bag_filepath = workspace_src_dir / \ - Path('isaac_ros_nvblox/nvblox_performance_measurement/nvblox_performance_measurement/datasets/isaac_sim_warehouse') + Path('isaac_ros_nvblox/nvblox_ros/test/test_cases/rosbags/nvblox_pol') # Check the bag exists if not os.path.exists(args.bag_filepath): - sys.exit(f"Bagfile at path: {args.bag_filepath} does not exist.") + sys.exit( + f"Bagfile at path: {args.bag_filepath} does not exist. \n\ + Either specify your own bag as an argument, or run \ + git lfs pull to download the default bag.") + + print(f"Going to run performance test with bag: {args.bag_filepath}") # Run the test try: + nvblox_startup_wait_time_s = 20.0 + print("Stating nvblox") nvblox_runner = NvbloxRunner() nvblox_runner.launch(args.realsense) + print( + f"Waiting for: {nvblox_startup_wait_time_s} for nvblox to start up before launching bag.") + time.sleep(nvblox_startup_wait_time_s) print("Starting the bag") bag_launcher = BagLauncher() @@ -265,7 +253,7 @@ def main(args=None): print( f"\n\n{timers_string}\n\nBenchmarking Results\n-------------------------------------\n") - results_table = analyze_timestamps.make_results_table( + results_table, results_table_dict = analyze_timestamps.make_results_table( stamps_dict, cpu_samples, gpu_samples) print(results_table) @@ -274,23 +262,31 @@ def main(args=None): args.output_dir = '/tmp' output_dir = Path(args.output_dir) timestr = time.strftime('%Y%m%d-%H%M%S') + print(f'Output directory is: {output_dir}') - # Creating a JSON object used by isaac_ros_benchmarking - if args.save_json: - # JSON dump params - # NOTE(alexmillane): These are copied from isaac_ros-benchmark - HEADER = 'Nvblox performance metrics' - LOG_FILE = args.json_file_name - LOGS_FOLDER = args.output_dir - save_isaac_ros_benchmark_results( - stamps_dict, log_dir=LOGS_FOLDER, heading=HEADER, - log_file=LOG_FILE, timestr=timestr) + # Create the output dir if it doesn't exist already + if not os.path.exists(output_dir): + print(f'Output directory doesn\'t exist so creating it') + os.makedirs(output_dir, exist_ok=True) # Saving the collected results if args.save_results_files: save_results_files(stamps_dict, cpu_samples, gpu_samples, timers_string, results_table, output_dir, timestr=timestr) + # Saving a plot + if args.save_pulse_plot: + save_pulse_figure(stamps_dict, output_dir / 'pulses.html') + + # Save results table as json + if args.save_kpi_json: + results_table_dict = remove_nan_values_from_dict( + results_table_dict) + timings_table_file = output_dir / 'ros_performance_kpis.json' + print(f"Writing the timings table to: {timings_table_file}") + with open(timings_table_file, "w") as timings_file: + json.dump(results_table_dict, timings_file, indent=4) + finally: # Kill nvblox_runner.kill() diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/src/lib/nvblox_performance_measurement_node.cpp b/nvblox_performance_measurement/nvblox_performance_measurement/src/lib/nvblox_performance_measurement_node.cpp index 8a766ee3..f91710ec 100644 --- a/nvblox_performance_measurement/nvblox_performance_measurement/src/lib/nvblox_performance_measurement_node.cpp +++ b/nvblox_performance_measurement/nvblox_performance_measurement/src/lib/nvblox_performance_measurement_node.cpp @@ -53,39 +53,41 @@ NvbloxPerformanceMeasurementNode::NvbloxPerformanceMeasurementNode() } bool NvbloxPerformanceMeasurementNode::processDepthImage( - sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr, - sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg) + const std::pair & + depth_camera_pair) { // Process as declared in NvbloxNode const bool success = - NvbloxNode::processDepthImage(depth_img_ptr, camera_info_msg); + NvbloxNode::processDepthImage(depth_camera_pair); // Indicate success to the outside world if (success) { nvblox_performance_measurement_msgs::msg::FrameProcessed msg; - msg.header = depth_img_ptr->header; + msg.header = depth_camera_pair.first->header; depth_processed_publisher_->publish(msg); } return success; } bool NvbloxPerformanceMeasurementNode::processColorImage( - sensor_msgs::msg::Image::ConstSharedPtr & color_img_ptr, - sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg) + const std::pair & + color_camera_pair) { // Process as declared in NvbloxNode const bool success = - NvbloxNode::processColorImage(color_img_ptr, camera_info_msg); + NvbloxNode::processColorImage(color_camera_pair); // Indicate success to the outside world if (success) { nvblox_performance_measurement_msgs::msg::FrameProcessed msg; - msg.header = color_img_ptr->header; + msg.header = color_camera_pair.first->header; color_processed_publisher_->publish(msg); } return success; } bool NvbloxPerformanceMeasurementNode::processLidarPointcloud( - sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud_ptr) + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud_ptr) { // Process as declared in NvbloxNode const bool success = diff --git a/nvblox_performance_measurement/nvblox_performance_measurement/src/lib/results_collector_node.cpp b/nvblox_performance_measurement/nvblox_performance_measurement/src/lib/results_collector_node.cpp index 0c6082c2..4050ce3d 100644 --- a/nvblox_performance_measurement/nvblox_performance_measurement/src/lib/results_collector_node.cpp +++ b/nvblox_performance_measurement/nvblox_performance_measurement/src/lib/results_collector_node.cpp @@ -17,7 +17,7 @@ #include "nvblox_performance_measurement/results_collector_node.hpp" -#include +#include #include #include @@ -46,13 +46,13 @@ ResultsCollectorNode::ResultsCollectorNode() // Subscriptions (through MessageStampRecorder objects) recorders_.push_back( std::make_unique>( - this, color_image_topic_name_, parseQoSString(color_qos_str))); + this, color_image_topic_name_, parseQosString(color_qos_str))); recorders_.push_back( std::make_unique>( - this, depth_image_topic_name_, parseQoSString(depth_qos_str))); + this, depth_image_topic_name_, parseQosString(depth_qos_str))); recorders_.push_back( std::make_unique>( - this, pointcloud_topic_name_, parseQoSString(pointcloud_qos_str))); + this, pointcloud_topic_name_, parseQosString(pointcloud_qos_str))); recorders_.push_back( std::make_unique< @@ -78,7 +78,7 @@ ResultsCollectorNode::ResultsCollectorNode() // Subscribing to CPU/GPU usage constexpr size_t kQueueSize = 10; - const auto qos = rclcpp::QoS(rclcpp::KeepLast(kQueueSize), parseQoSString(kDefaultQoS)); + const auto qos = rclcpp::QoS(rclcpp::KeepLast(kQueueSize), parseQosString(kDefaultQoS)); cpu_percentage_sub_ = this->create_subscription( cpu_topic_name_, qos, std::bind(&ResultsCollectorNode::cpuMessageCallback, this, _1)); diff --git a/nvblox_performance_measurement/nvblox_performance_measurement_msgs/package.xml b/nvblox_performance_measurement/nvblox_performance_measurement_msgs/package.xml index 0957f198..ff6f95c2 100644 --- a/nvblox_performance_measurement/nvblox_performance_measurement_msgs/package.xml +++ b/nvblox_performance_measurement/nvblox_performance_measurement_msgs/package.xml @@ -21,8 +21,8 @@ nvblox_performance_measurement_msgs - 0.20.0 - ROS2 messages for use in nvblox_performance_measurement + 0.30.0 + ROS 2 messages for use in nvblox_performance_measurement Helen Oleynikova Alexander Millane diff --git a/nvblox_ros/CMakeLists.txt b/nvblox_ros/CMakeLists.txt index b9c598d1..4c9ea60b 100644 --- a/nvblox_ros/CMakeLists.txt +++ b/nvblox_ros/CMakeLists.txt @@ -48,6 +48,8 @@ find_package(message_filters REQUIRED) find_package(Threads REQUIRED) find_package(nvblox REQUIRED) find_package(visualization_msgs REQUIRED) +find_package(cv_bridge REQUIRED) +find_package(nvblox_ros_common REQUIRED) ######## # CUDA # @@ -59,12 +61,16 @@ set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} --compiler-options -fPIC") # LIBRARIES # ############# add_library(${PROJECT_NAME}_lib SHARED - src/lib/conversions.cpp + src/lib/conversions/image_conversions.cu + src/lib/conversions/layer_conversions.cu + src/lib/conversions/mesh_conversions.cpp + src/lib/conversions/pointcloud_conversions.cu + src/lib/conversions/esdf_slice_conversions.cu src/lib/visualization.cpp - src/lib/cuda/conversions.cu src/lib/transformer.cpp + src/lib/mapper_initialization.cpp src/lib/nvblox_node.cpp - src/lib/qos.cpp + src/lib/nvblox_human_node.cpp ) target_link_libraries(${PROJECT_NAME}_lib nvblox::nvblox_lib nvblox::nvblox_eigen pthread) ament_target_dependencies(${PROJECT_NAME}_lib @@ -81,6 +87,8 @@ ament_target_dependencies(${PROJECT_NAME}_lib tf2_ros message_filters visualization_msgs + cv_bridge + nvblox_ros_common ) target_include_directories(${PROJECT_NAME}_lib PUBLIC $ @@ -89,6 +97,7 @@ target_include_directories(${PROJECT_NAME}_lib BEFORE PRIVATE $) rclcpp_components_register_nodes(${PROJECT_NAME}_lib "nvblox::NvbloxNode") +rclcpp_components_register_nodes(${PROJECT_NAME}_lib "nvblox::NvbloxHumanNode") if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.18) get_target_property(CUDA_ARCHS nvblox::nvblox_lib CUDA_ARCHITECTURES) @@ -103,6 +112,11 @@ add_executable(nvblox_node ) target_link_libraries(nvblox_node ${PROJECT_NAME}_lib) +add_executable(nvblox_human_node + src/nvblox_human_node_main.cpp +) +target_link_libraries(nvblox_human_node ${PROJECT_NAME}_lib) + ########### # INSTALL # ########### @@ -124,7 +138,7 @@ install( # Install nodes which live in lib for some reason. install( - TARGETS nvblox_node + TARGETS nvblox_node nvblox_human_node DESTINATION lib/${PROJECT_NAME} ) @@ -161,6 +175,8 @@ ament_export_dependencies( message_filters libstatistics_collector visualization_msgs + cv_bridge + nvblox_ros_common ) ament_package() diff --git a/nvblox_ros/include/nvblox_ros/conversions.hpp b/nvblox_ros/include/nvblox_ros/conversions.hpp deleted file mode 100644 index 391714df..00000000 --- a/nvblox_ros/include/nvblox_ros/conversions.hpp +++ /dev/null @@ -1,181 +0,0 @@ -// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -#ifndef NVBLOX_ROS__CONVERSIONS_HPP_ -#define NVBLOX_ROS__CONVERSIONS_HPP_ - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - - -namespace nvblox -{ - -// Convert vectors. -inline geometry_msgs::msg::Point32 point32MessageFromVector( - const Eigen::Vector3f & vector); - -inline geometry_msgs::msg::Point pointMessageFromVector( - const Eigen::Vector3f & vector); - -// Convert colors. -inline std_msgs::msg::ColorRGBA colorMessageFromColor(const Color & color); - -// Convert indices -inline nvblox_msgs::msg::Index3D index3DMessageFromIndex3D( - const Index3D & index); - -// Helper struct for storing PCL points. -// 16-byte alignment to match what PCL does internally: -// https://pointclouds.org/documentation/point__types_8hpp_source.html -struct alignas (16) PclPointXYZI -{ - float x; - float y; - float z; - float intensity; -}; - -// Helper class to store all the buffers. -class RosConverter -{ -public: - RosConverter(); - - // Convert camera info message to NVBlox camera object - Camera cameraFromMessage(const sensor_msgs::msg::CameraInfo & camera_info); - - // Convert image to depth frame object - bool depthImageFromImageMessage( - const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, - DepthImage * depth_frame); - - bool colorImageFromImageMessage( - const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, - ColorImage * color_image); - - // Convert depth frame to image message. - void imageMessageFromDepthImage( - const DepthImage & depth_frame, - const std::string & frame_id, - sensor_msgs::msg::Image * image_msg); - - // Convert a mesh to a message. - void meshMessageFromMeshLayer( - const BlockLayer & mesh_layer, - nvblox_msgs::msg::Mesh * mesh_msg); - - void meshMessageFromMeshBlocks( - const BlockLayer & mesh_layer, - const std::vector & block_indices, - nvblox_msgs::msg::Mesh * mesh_msg, - const std::vector & deleted_indices = std::vector()); - - void meshBlockMessageFromMeshBlock( - const MeshBlock & mesh_block, nvblox_msgs::msg::MeshBlock * mesh_block_msg); - - // Convert a mesh to a marker array. - void markerMessageFromMeshLayer( - const BlockLayer & mesh_layer, const std::string & frame_id, - visualization_msgs::msg::MarkerArray * marker_msg); - - void markerMessageFromMeshBlock( - const MeshBlock::ConstPtr & mesh_block, - const std::string & frame_id, - visualization_msgs::msg::Marker * marker_msg); - - // Convert an SDF to a pointcloud. - template - void pointcloudFromLayer( - const VoxelBlockLayer & layer, - sensor_msgs::msg::PointCloud2 * pointcloud_msg); - - // Convert an SDF to a pointcloud within an AABB (use this for slices, for - // example). - template - void pointcloudFromLayerInAABB( - const VoxelBlockLayer & layer, - const AxisAlignedBoundingBox & aabb, - sensor_msgs::msg::PointCloud2 * pointcloud_msg); - - // Create a distance map slice from an ESDF layer. Only works with z-axis - // slices for now. - void distanceMapSliceFromLayer( - const EsdfLayer & layer, float height, - nvblox_msgs::msg::DistanceMapSlice * map_slice); - - // Convert pointcloud to depth image. - void depthImageFromPointcloudGPU( - const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud, - const Lidar & lidar, DepthImage * depth_image_ptr); - - // This function returns true if the pointcloud passed in is consistent with - // the LiDAR intrinsics model. - bool checkLidarPointcloud( - const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud, - const Lidar & lidar); - // Write the pointcloud to file - void writeLidarPointcloudToFile( - const std::string filepath, - const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud); - -private: - // Helper functions for CUDA conversions. - template - void convertLayerInAABBToPCLCuda( - const VoxelBlockLayer & layer, - const AxisAlignedBoundingBox & aabb, - sensor_msgs::msg::PointCloud2 * pointcloud_msg); - - // Output methods to access GPU layer *slice* in a more efficient way. - // The output is a float image whose size *should* match the AABB with - // a given resolution (in meters). Otherwise behavior is undefined. - void populateSliceFromLayer( - const EsdfLayer & layer, - const AxisAlignedBoundingBox & aabb, - float z_slice_height, float resolution, - float unobserved_value, Image * image); - - // State. - cudaStream_t cuda_stream_ = nullptr; - - device_vector block_indices_device_; - device_vector layer_pointcloud_device_; - unified_ptr max_index_device_; - unified_ptr max_index_host_; - - // Pointcloud conversion - std::unordered_set checked_lidar_models_; - host_vector lidar_pointcloud_host_; - device_vector lidar_pointcloud_device_; -}; - -} // namespace nvblox - -#include "nvblox_ros/impl/conversions_impl.hpp" - -#endif // NVBLOX_ROS__CONVERSIONS_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/conversions/esdf_slice_conversions.hpp b/nvblox_ros/include/nvblox_ros/conversions/esdf_slice_conversions.hpp new file mode 100644 index 00000000..a8c46f68 --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/conversions/esdf_slice_conversions.hpp @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__CONVERSIONS__ESDF_SLICE_CONVERSIONS_HPP_ +#define NVBLOX_ROS__CONVERSIONS__ESDF_SLICE_CONVERSIONS_HPP_ + +#include + +#include + +#include "nvblox_ros/conversions/pointcloud_conversions.hpp" + +namespace nvblox +{ +namespace conversions +{ + +constexpr float kDistanceMapSliceUnknownValue = 1000.0f; + +// Helper class to store all the buffers. +class EsdfSliceConverter +{ +public: + EsdfSliceConverter(); + + // Create a distance map slice from an ESDF layer. Only works with z-axis + // slices for now. + void distanceMapSliceFromLayer( + const EsdfLayer & layer, float z_slice_level, + nvblox_msgs::msg::DistanceMapSlice * map_slice); + + void distanceMapSliceFromLayers( + const EsdfLayer & layer_1, + const EsdfLayer & layer_2, float z_slice_level, + Image * map_slice_image_ptr, + AxisAlignedBoundingBox * aabb_ptr); + + void distanceMapSliceImageFromLayer( + const EsdfLayer & layer, + float z_slice_level, + const AxisAlignedBoundingBox & aabb, + Image * map_slice_image_ptr); + + void distanceMapSliceImageFromLayer( + const EsdfLayer & layer, + float z_slice_level, + Image * map_slice_image_ptr, + AxisAlignedBoundingBox * aabb_ptr); + + void distanceMapSliceImageToMsg( + const Image & map_slice_image, const AxisAlignedBoundingBox & aabb, + float z_slice_level, float voxel_size, + nvblox_msgs::msg::DistanceMapSlice * map_slice); + + void sliceImageToPointcloud( + const Image & map_slice_image, + const AxisAlignedBoundingBox & aabb, + float z_slice_level, float voxel_size, + sensor_msgs::msg::PointCloud2 * pointcloud_msg); + + AxisAlignedBoundingBox getBoundingBoxOfLayerAtHeight( + const EsdfLayer & layer, const float z_slice_level); + +private: + // Output methods to access GPU layer *slice* in a more efficient way. + // The output is a float image whose size *should* match the AABB with + // a given resolution (in meters). Otherwise behavior is undefined. + void populateSliceFromLayer( + const EsdfLayer & layer, + const AxisAlignedBoundingBox & aabb, + float z_slice_height, float resolution, + float unobserved_value, Image * image); + + cudaStream_t cuda_stream_ = nullptr; + + // Buffers + unified_ptr max_index_device_; + unified_ptr max_index_host_; + device_vector pcl_pointcloud_device_; +}; + +} // namespace conversions +} // namespace nvblox + +#endif // NVBLOX_ROS__CONVERSIONS__ESDF_SLICE_CONVERSIONS_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/conversions/image_conversions.hpp b/nvblox_ros/include/nvblox_ros/conversions/image_conversions.hpp new file mode 100644 index 00000000..aa44c943 --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/conversions/image_conversions.hpp @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__CONVERSIONS__IMAGE_CONVERSIONS_HPP_ +#define NVBLOX_ROS__CONVERSIONS__IMAGE_CONVERSIONS_HPP_ + +#include + +#include + +#include + +#include +#include + +namespace nvblox +{ +namespace conversions +{ + +// Convert camera info message to NVBlox camera object +Camera cameraFromMessage(const sensor_msgs::msg::CameraInfo & camera_info); + +// Convert image to depth frame object +bool depthImageFromImageMessage( + const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, + DepthImage * depth_frame); + +bool colorImageFromImageMessage( + const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, + ColorImage * color_image); + +bool monoImageFromImageMessage( + const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, + MonoImage * mono_image); + +// Convert depth frame to image message. +void imageMessageFromDepthImage( + const DepthImage & depth_frame, + const std::string & frame_id, + sensor_msgs::msg::Image * image_msg); + +// Convert color frame to image message. +void imageMessageFromColorImage( + const ColorImage & color_image, + const std::string & frame_id, + sensor_msgs::msg::Image * image_msg); + +} // namespace conversions +} // namespace nvblox + +#endif // NVBLOX_ROS__CONVERSIONS__IMAGE_CONVERSIONS_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/conversions/impl/layer_conversions_impl.hpp b/nvblox_ros/include/nvblox_ros/conversions/impl/layer_conversions_impl.hpp new file mode 100644 index 00000000..b389172d --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/conversions/impl/layer_conversions_impl.hpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__CONVERSIONS__IMPL__LAYER_CONVERSIONS_IMPL_HPP_ +#define NVBLOX_ROS__CONVERSIONS__IMPL__LAYER_CONVERSIONS_IMPL_HPP_ + +namespace nvblox +{ +namespace conversions +{ + +// Convert an SDF to a pointcloud. +template +inline void LayerConverter::pointcloudMsgFromLayer( + const VoxelBlockLayer & layer, + sensor_msgs::msg::PointCloud2 * pointcloud_msg) +{ + AxisAlignedBoundingBox aabb; + aabb.setEmpty(); + pointcloudMsgFromLayerInAABB(layer, aabb, pointcloud_msg); +} + +} // namespace conversions +} // namespace nvblox + +#endif // NVBLOX_ROS__CONVERSIONS__IMPL__LAYER_CONVERSIONS_IMPL_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/conversions/layer_conversions.hpp b/nvblox_ros/include/nvblox_ros/conversions/layer_conversions.hpp new file mode 100644 index 00000000..9258a042 --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/conversions/layer_conversions.hpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__CONVERSIONS__LAYER_CONVERSIONS_HPP_ +#define NVBLOX_ROS__CONVERSIONS__LAYER_CONVERSIONS_HPP_ + +#include + +#include "nvblox_ros/conversions/pointcloud_conversions.hpp" + +namespace nvblox +{ +namespace conversions +{ + +// Helper class to store all the buffers. +class LayerConverter +{ +public: + LayerConverter(); + + // Convert a layer to a pointcloud. + template + void pointcloudMsgFromLayer( + const VoxelBlockLayer & layer, + sensor_msgs::msg::PointCloud2 * pointcloud_msg); + + // Convert a layer in AABB to a pointcloud. + template + void pointcloudMsgFromLayerInAABB( + const VoxelBlockLayer & layer, + const AxisAlignedBoundingBox & aabb, + sensor_msgs::msg::PointCloud2 * pointcloud_msg); + +private: + cudaStream_t cuda_stream_ = nullptr; + + // Buffers + device_vector pcl_pointcloud_device_; + unified_ptr max_index_device_; + unified_ptr max_index_host_; + device_vector block_indices_device_; +}; + +} // namespace conversions +} // namespace nvblox + +#include "nvblox_ros/conversions/impl/layer_conversions_impl.hpp" + +#endif // NVBLOX_ROS__CONVERSIONS__LAYER_CONVERSIONS_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/conversions/mesh_conversions.hpp b/nvblox_ros/include/nvblox_ros/conversions/mesh_conversions.hpp new file mode 100644 index 00000000..fccc8bb2 --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/conversions/mesh_conversions.hpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__CONVERSIONS__MESH_CONVERSIONS_HPP_ +#define NVBLOX_ROS__CONVERSIONS__MESH_CONVERSIONS_HPP_ + +#include + +#include +#include + +#include +#include +#include + + +namespace nvblox +{ +namespace conversions +{ + +// Convert a mesh to a message. +void meshMessageFromMeshLayer( + const BlockLayer & mesh_layer, + nvblox_msgs::msg::Mesh * mesh_msg); + +void meshMessageFromMeshBlocks( + const BlockLayer & mesh_layer, + const std::vector & block_indices, + nvblox_msgs::msg::Mesh * mesh_msg, + const std::vector & deleted_indices = std::vector()); + +// Convert a mesh to a marker array. +void markerMessageFromMeshLayer( + const BlockLayer & mesh_layer, const std::string & frame_id, + visualization_msgs::msg::MarkerArray * marker_msg); + +} // namespace conversions +} // namespace nvblox + +#endif // NVBLOX_ROS__CONVERSIONS__MESH_CONVERSIONS_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/conversions/pointcloud_conversions.hpp b/nvblox_ros/include/nvblox_ros/conversions/pointcloud_conversions.hpp new file mode 100644 index 00000000..f4536fb6 --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/conversions/pointcloud_conversions.hpp @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__CONVERSIONS__POINTCLOUD_CONVERSIONS_HPP_ +#define NVBLOX_ROS__CONVERSIONS__POINTCLOUD_CONVERSIONS_HPP_ + +#include + +#include +#include +#include + +#include +#include + +namespace nvblox +{ +namespace conversions +{ + +// Helper struct for storing PCL points. +// 16-byte alignment to match what PCL does internally: +// https://pointclouds.org/documentation/point__types_8hpp_source.html +struct alignas (16) PclPointXYZI +{ + float x; + float y; + float z; + float intensity; +}; + +void copyDevicePointcloudToMsg( + const device_vector & pcl_pointcloud_device, + sensor_msgs::msg::PointCloud2 * pointcloud_msg); + +// Helper class to store all the buffers. +class PointcloudConverter +{ +public: + PointcloudConverter(); + + // Internal pointcloud representation to a ROS pointcloud + void pointcloudMsgFromPointcloud( + const Pointcloud & pointcloud, + sensor_msgs::msg::PointCloud2 * pointcloud_msg); + + // Convert pointcloud to depth image. + void depthImageFromPointcloudGPU( + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud, + const Lidar & lidar, DepthImage * depth_image_ptr); + + // This function returns true if the pointcloud passed in is consistent with + // the LiDAR intrinsics model. + bool checkLidarPointcloud( + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud, + const Lidar & lidar); + + // Write the pointcloud to file + void writeLidarPointcloudToFile( + const std::string filepath, + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud); + + /// Generates a marker with a bunch of cubes in it. Note that the resultant + /// marker has does not have a frame or timestamp set (this is left to + /// caller). + /// + /// @param points Voxel centers + /// @param cube_size Edge length of the cubes in Marker msg + /// @param color What color the cubes appear + /// @param[out] marker_ptr Output marker + void pointsToCubesMarkerMsg( + const std::vector & points, + const float cube_size, const Color & color, + visualization_msgs::msg::Marker * marker_ptr); + +private: + std::unordered_set checked_lidar_models_; + + cudaStream_t cuda_stream_ = nullptr; + + // Buffers + host_vector lidar_pointcloud_host_; + device_vector lidar_pointcloud_device_; + device_vector pcl_pointcloud_device_; +}; + +} // namespace conversions +} // namespace nvblox + +#endif // NVBLOX_ROS__CONVERSIONS__POINTCLOUD_CONVERSIONS_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/impl/conversions_impl.hpp b/nvblox_ros/include/nvblox_ros/impl/conversions_impl.hpp deleted file mode 100644 index db376ee9..00000000 --- a/nvblox_ros/include/nvblox_ros/impl/conversions_impl.hpp +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -#ifndef NVBLOX_ROS__IMPL__CONVERSIONS_IMPL_HPP_ -#define NVBLOX_ROS__IMPL__CONVERSIONS_IMPL_HPP_ - -#include - -namespace nvblox -{ - -// Helper function to output all valid points. -template -inline void RosConverter::pointcloudFromLayerInAABB( - const VoxelBlockLayer & layer, const AxisAlignedBoundingBox & aabb, - sensor_msgs::msg::PointCloud2 * pointcloud_msg) -{ - CHECK_NOTNULL(pointcloud_msg); - convertLayerInAABBToPCLCuda(layer, aabb, pointcloud_msg); -} - -// Convert an SDF to a pointcloud. -template -inline void RosConverter::pointcloudFromLayer( - const VoxelBlockLayer & layer, - sensor_msgs::msg::PointCloud2 * pointcloud_msg) -{ - AxisAlignedBoundingBox aabb; - aabb.setEmpty(); - pointcloudFromLayerInAABB(layer, aabb, pointcloud_msg); -} - -geometry_msgs::msg::Point32 point32MessageFromVector( - const Eigen::Vector3f & vector) -{ - geometry_msgs::msg::Point32 point; - point.x = vector.x(); - point.y = vector.y(); - point.z = vector.z(); - return point; -} - -geometry_msgs::msg::Point pointMessageFromVector( - const Eigen::Vector3f & vector) -{ - geometry_msgs::msg::Point point; - point.x = vector.x(); - point.y = vector.y(); - point.z = vector.z(); - return point; -} - -std_msgs::msg::ColorRGBA colorMessageFromColor(const Color & color) -{ - std_msgs::msg::ColorRGBA color_msg; - color_msg.r = static_cast(color.r) / 255.0f; - color_msg.g = static_cast(color.g) / 255.0f; - color_msg.b = static_cast(color.b) / 255.0f; - color_msg.a = 1.0f; - return color_msg; -} - -nvblox_msgs::msg::Index3D index3DMessageFromIndex3D(const Index3D & index) -{ - nvblox_msgs::msg::Index3D index_msg; - index_msg.x = index.x(); - index_msg.y = index.y(); - index_msg.z = index.z(); - return index_msg; -} - -} // namespace nvblox - -#endif // NVBLOX_ROS__IMPL__CONVERSIONS_IMPL_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/impl/mapper_initialization_impl.hpp b/nvblox_ros/include/nvblox_ros/impl/mapper_initialization_impl.hpp new file mode 100644 index 00000000..9c569a36 --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/impl/mapper_initialization_impl.hpp @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__IMPL__MAPPER_INITIALIZATION_IMPL_HPP_ +#define NVBLOX_ROS__IMPL__MAPPER_INITIALIZATION_IMPL_HPP_ + +#include + +#include + +namespace nvblox +{ + +template +void set_mapper_parameter( + const std::string & mapper_name, + const std::string & parameter_name, + std::function parameter_setter, + rclcpp::Node * node) +{ + T parameter_value; + const std::string full_name = mapper_name + "." + parameter_name; + if (node->get_parameter(full_name, parameter_value)) { + // Print non default values + RCLCPP_INFO_STREAM( + node->get_logger(), + full_name << ": " << parameter_value); + // Set the mapper parameter + parameter_setter(parameter_value); + } +} + +} // namespace nvblox + +#endif // NVBLOX_ROS__IMPL__MAPPER_INITIALIZATION_IMPL_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/impl/nvblox_node_impl.hpp b/nvblox_ros/include/nvblox_ros/impl/nvblox_node_impl.hpp new file mode 100644 index 00000000..c036350c --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/impl/nvblox_node_impl.hpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__IMPL__NVBLOX_NODE_IMPL_HPP_ +#define NVBLOX_ROS__IMPL__NVBLOX_NODE_IMPL_HPP_ + +#include + +#include +#include +#include + +namespace nvblox +{ + +template +void NvbloxNode::processMessageQueue( + std::deque * queue_ptr, std::mutex * queue_mutex_ptr, + MessageReadyCallback message_ready_check, + ProcessMessageCallback callback) +{ + timing::Timer ros_total_timer("ros/total"); + + // Copy over all the pointers we actually want to process here. + std::vector items_to_process; + + std::unique_lock lock(*queue_mutex_ptr); + + if (queue_ptr->empty()) { + lock.unlock(); + return; + } + + auto it_first_valid = queue_ptr->end(); + auto it_last_valid = queue_ptr->begin(); + + for (auto it = queue_ptr->begin(); it != queue_ptr->end(); it++) { + // Process this image in the queue + if (message_ready_check(*it)) { + items_to_process.push_back(*it); + } else { + continue; + } + + // If we processed this frame, keep track of that fact so we can delete it + // at the end. + if (it_first_valid == queue_ptr->end()) { + it_first_valid = it; + } + if (it_last_valid <= it) { + it_last_valid = it; + } + } + + // Now we have 2 iterators pointing to what we want to delete. + if (it_first_valid != queue_ptr->end()) { + // Actually erase from the beginning of the queue. + queue_ptr->erase(queue_ptr->begin(), ++it_last_valid); + // Warn user if we're loosing messages unprocessed + const int num_messages_deleted = it_last_valid - queue_ptr->begin(); + const int num_messages_processed = items_to_process.size(); + if (num_messages_deleted > num_messages_processed) { + const int num_messages_lost = num_messages_deleted - num_messages_processed; + constexpr int kLostMessagesPublishPeriodMs = 1000; + auto & clk = *get_clock(); + RCLCPP_WARN_STREAM_THROTTLE( + get_logger(), clk, kLostMessagesPublishPeriodMs, + "Deleted " << num_messages_lost << "because we could not interpolate transforms."); + } + } + lock.unlock(); + + // Process everything that was found to be ready + if (items_to_process.empty()) { + return; + } + rclcpp::Time last_timestamp; + for (auto image_pair : items_to_process) { + callback(image_pair); + } + + // nvblox statistics + constexpr int kPublishPeriodMs = 10000; + auto & clk = *get_clock(); + RCLCPP_INFO_STREAM_THROTTLE( + get_logger(), clk, kPublishPeriodMs, + "Timing statistics: \n" << + nvblox::timing::Timing::Print()); +} + +template +void NvbloxNode::pushMessageOntoQueue( + MessageType message, + std::deque * queue_ptr, + std::mutex * queue_mutex_ptr) +{ + // Push it into the queue. + timing::Timer ros_total_timer("ros/total"); + { + const std::lock_guard lock(*queue_mutex_ptr); + queue_ptr->emplace_back(message); + } +} + +template +void NvbloxNode::limitQueueSizeByDeletingOldestMessages( + const int max_num_messages, const std::string & queue_name, + std::deque * queue_ptr, std::mutex * queue_mutex_ptr) +{ + // Delete extra elements in the queue. + timing::Timer ros_total_timer("ros/total"); + const std::lock_guard lock(*queue_mutex_ptr); + if (queue_ptr->size() > max_num_messages) { + const int num_elements_to_delete = queue_ptr->size() - max_num_messages; + queue_ptr->erase( + queue_ptr->begin(), + queue_ptr->begin() + num_elements_to_delete); + constexpr int kPublishPeriodMs = 1000; + auto & clk = *get_clock(); + RCLCPP_INFO_STREAM_THROTTLE( + get_logger(), clk, kPublishPeriodMs, + queue_name << " queue was longer than " << max_num_messages << + " deleted " << num_elements_to_delete << " messages."); + } +} + +template +void NvbloxNode::printMessageArrivalStatistics( + const MessageType & message, const std::string & output_prefix, + libstatistics_collector::topic_statistics_collector:: + ReceivedMessagePeriodCollector * statistics_collector) +{ + // Calculate statistics + statistics_collector->OnMessageReceived( + message, + get_clock()->now().nanoseconds()); + // Print statistics + constexpr int kPublishPeriodMs = 10000; + auto & clk = *get_clock(); + RCLCPP_INFO_STREAM_THROTTLE( + get_logger(), clk, kPublishPeriodMs, + output_prefix << ": \n" << + libstatistics_collector::moving_average_statistics:: + StatisticsDataToString( + statistics_collector->GetStatisticsResults())); +} + +} // namespace nvblox + +#endif // NVBLOX_ROS__IMPL__NVBLOX_NODE_IMPL_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/mapper_initialization.hpp b/nvblox_ros/include/nvblox_ros/mapper_initialization.hpp new file mode 100644 index 00000000..08ec3444 --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/mapper_initialization.hpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__MAPPER_INITIALIZATION_HPP_ +#define NVBLOX_ROS__MAPPER_INITIALIZATION_HPP_ + +#include + +#include + +#include + +#include "nvblox_ros/utils.hpp" + +namespace nvblox +{ + +void declareMapperParameters(const std::string & mapper_name, rclcpp::Node * node); + +template +void set_mapper_parameter( + const std::string & mapper_name, + const std::string & parameter_name, + std::function parameter_setter, + rclcpp::Node * node); + +void initializeMapper(const std::string & mapper_name, Mapper * mapper_ptr, rclcpp::Node * node); + +} // namespace nvblox + +#include "nvblox_ros/impl/mapper_initialization_impl.hpp" + +#endif // NVBLOX_ROS__MAPPER_INITIALIZATION_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/nvblox_human_node.hpp b/nvblox_ros/include/nvblox_ros/nvblox_human_node.hpp new file mode 100644 index 00000000..c6f14f7d --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/nvblox_human_node.hpp @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__NVBLOX_HUMAN_NODE_HPP_ +#define NVBLOX_ROS__NVBLOX_HUMAN_NODE_HPP_ + +#include +#include +#include + +#include +#include +#include + +#include "nvblox_ros/nvblox_node.hpp" + +namespace nvblox +{ + +class NvbloxHumanNode : public NvbloxNode +{ +public: + explicit NvbloxHumanNode(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()); + virtual ~NvbloxHumanNode() = default; + + // Setup. These are called by the constructor. + void getParameters(); + void initializeMultiMapper(); + void subscribeToTopics(); + void setupTimers(); + void advertiseTopics(); + + // Callbacks for Sensor + Mask + void depthPlusMaskImageCallback( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg, + const sensor_msgs::msg::Image::ConstSharedPtr & mask_img_ptr, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & mask_camera_info_msg); + void colorPlusMaskImageCallback( + const sensor_msgs::msg::Image::ConstSharedPtr & color_img_ptr, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg, + const sensor_msgs::msg::Image::ConstSharedPtr & mask_img_ptr, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & mask_camera_info_msg); + + // This is our internal type for passing around images, their matching + // segmentation masks, as well as the camera intrinsics. + using ImageSegmentationMaskMsgTuple = + std::tuple; + + // Override the depth processing from the base node + void processDepthQueue() override; + void processColorQueue() override; + + // The methods for processing images from the internal queue. + virtual bool processDepthImage( + const ImageSegmentationMaskMsgTuple & depth_mask_msg); + virtual bool processColorImage( + const ImageSegmentationMaskMsgTuple & color_mask_msg); + + // Publish human data on fixed frequency + void processHumanEsdf(); + + // Decay the human occupancy grid on fixed frequency + void decayHumanOccupancy(); + +protected: + // Publish human data (if any subscribers) that helps + // visualization and debugging. + void publishHumanDebugOutput(); + + // Mapper + // Holds the map layers and their associated integrators + // - TsdfLayer, ColorLayer, OccupancyLayer, EsdfLayer, MeshLayer + std::shared_ptr multi_mapper_; + std::shared_ptr human_mapper_; + + // Synchronize: Depth + CamInfo + SegmentationMake + CamInfo + typedef message_filters::sync_policies::ApproximateTime< + sensor_msgs::msg::Image, sensor_msgs::msg::CameraInfo, + sensor_msgs::msg::Image, sensor_msgs::msg::CameraInfo> + mask_time_policy_t; + std::shared_ptr> + timesync_depth_mask_; + std::shared_ptr> + timesync_color_mask_; + + // Segmentation mask sub. + message_filters::Subscriber segmentation_mask_sub_; + message_filters::Subscriber + segmentation_camera_info_sub_; + + // Publishers + rclcpp::Publisher::SharedPtr + human_pointcloud_publisher_; + rclcpp::Publisher::SharedPtr + human_esdf_pointcloud_publisher_; + rclcpp::Publisher::SharedPtr + combined_esdf_pointcloud_publisher_; + rclcpp::Publisher::SharedPtr + human_voxels_publisher_; + rclcpp::Publisher::SharedPtr + human_occupancy_publisher_; + rclcpp::Publisher::SharedPtr + human_map_slice_publisher_; + rclcpp::Publisher::SharedPtr + combined_map_slice_publisher_; + rclcpp::Publisher::SharedPtr + depth_frame_overlay_publisher_; + rclcpp::Publisher::SharedPtr + color_frame_overlay_publisher_; + + // Timers + rclcpp::TimerBase::SharedPtr human_occupancy_decay_timer_; + rclcpp::TimerBase::SharedPtr human_esdf_processing_timer_; + + // Rates. + float human_occupancy_decay_rate_hz_ = 10.0f; + float human_esdf_update_rate_hz_ = 10.0f; + + // Image queues. + // Note these differ from the base class image queues because they also + // include segmentation images. The base class queue are disused in the + // NvbloxHumanNode. + std::deque depth_mask_image_queue_; + std::deque color_mask_image_queue_; + + // Cache for GPU image + MonoImage mask_image_; + + // Image queue mutexes. + std::mutex depth_mask_queue_mutex_; + std::mutex color_mask_queue_mutex_; + + // Object for back projecting image to a pointcloud. + DepthImageBackProjector image_back_projector_; + + // Device caches + Pointcloud human_pointcloud_C_device_; + Pointcloud human_pointcloud_L_device_; + Pointcloud human_voxel_centers_L_device_; + + // Caching data of last depth frame for debug outputs + Camera depth_camera_; + Transform T_L_C_depth_; +}; + +} // namespace nvblox + +#endif // NVBLOX_ROS__NVBLOX_HUMAN_NODE_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/nvblox_node.hpp b/nvblox_ros/include/nvblox_ros/nvblox_node.hpp index 55ba0628..c6384a8e 100644 --- a/nvblox_ros/include/nvblox_ros/nvblox_node.hpp +++ b/nvblox_ros/include/nvblox_ros/nvblox_node.hpp @@ -42,7 +42,12 @@ #include #include -#include "nvblox_ros/conversions.hpp" +#include "nvblox_ros/conversions/image_conversions.hpp" +#include "nvblox_ros/conversions/layer_conversions.hpp" +#include "nvblox_ros/conversions/mesh_conversions.hpp" +#include "nvblox_ros/conversions/pointcloud_conversions.hpp" +#include "nvblox_ros/conversions/esdf_slice_conversions.hpp" +#include "nvblox_ros/mapper_initialization.hpp" #include "nvblox_ros/transformer.hpp" namespace nvblox @@ -51,9 +56,18 @@ namespace nvblox class NvbloxNode : public rclcpp::Node { public: - NvbloxNode(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()); + explicit NvbloxNode( + const rclcpp::NodeOptions & options = rclcpp::NodeOptions(), + const std::string & node_name = "nvblox_node"); virtual ~NvbloxNode() = default; + // Setup. These are called by the constructor. + void getParameters(); + void subscribeToTopics(); + void advertiseTopics(); + void advertiseServices(); + void setupTimers(); + // Callback functions. These just stick images in a queue. void depthImageCallback( const sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr, @@ -76,34 +90,82 @@ class NvbloxNode : public rclcpp::Node // Does whatever processing there is to be done, depending on what // transforms are available. - void processDepthQueue(); - void processColorQueue(); - void processPointcloudQueue(); - void processEsdf(); + virtual void processDepthQueue(); + virtual void processColorQueue(); + virtual void processPointcloudQueue(); + virtual void processEsdf(); virtual void processMesh(); - // Process a single images + // Publish data on fixed frequency + void publishOccupancyPointcloud(); + + // Process data virtual bool processDepthImage( - sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr, - sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg); + const std::pair & + depth_camera_pair); virtual bool processColorImage( - sensor_msgs::msg::Image::ConstSharedPtr & color_img_ptr, - sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg); + const std::pair & + color_camera_pair); virtual bool processLidarPointcloud( - sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud_ptr); + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud_ptr); bool canTransform(const std_msgs::msg::Header & header); - void publishSlicePlane(const rclcpp::Time & timestamp, const Transform & T_S_C); - -private: - // Helper functions to make the code more readable. - void updateEsdf(const rclcpp::Time & timestamp); - void updateMesh(const rclcpp::Time & timestamp); + void publishSlicePlane(const rclcpp::Time & timestamp, const Transform & T_L_C); +protected: // Map clearing - void clearMapOutsideOfRadius(const std::string & target_frame_id, const rclcpp::Time & timestamp); - + void clearMapOutsideOfRadiusOfLastKnownPose(); + + /// Used by callbacks (internally) to add messages to queues. + /// @tparam MessageType The type of the Message stored by the queue. + /// @param message Message to be added to the queue. + /// @param queue_ptr Queue where to add the message. + /// @param queue_mutex_ptr Mutex protecting the queue. + template + void pushMessageOntoQueue( + MessageType message, + std::deque * queue_ptr, + std::mutex * queue_mutex_ptr); + template + void printMessageArrivalStatistics( + const MessageType & message, const std::string & output_prefix, + libstatistics_collector::topic_statistics_collector:: + ReceivedMessagePeriodCollector * statistics_collector); + + // Used internally to unify processing of queues that process a message and a + // matching transform. + template + using ProcessMessageCallback = std::function; + template + using MessageReadyCallback = std::function; + + /// Processes a queue of messages by detecting if they're ready and then + /// passing them to a callback. + /// @tparam MessageType The type of the messages in the queue. + /// @param queue_ptr Queue of messages to process. + /// @param queue_mutex_ptr Mutex protecting the queue. + /// @param message_ready_check Callback called on each message to check if + /// it's ready to be processed + /// @param callback Callback to process each ready message. + template + void processMessageQueue( + std::deque * queue_ptr, std::mutex * queue_mutex_ptr, + MessageReadyCallback message_ready_check, + ProcessMessageCallback callback); + + // Check if interval between current stamp + bool isUpdateTooFrequent( + const rclcpp::Time & current_stamp, + const rclcpp::Time & last_update_stamp, + float max_update_rate_hz); + + template + void limitQueueSizeByDeletingOldestMessages( + const int max_num_messages, const std::string & queue_name, + std::deque * queue_ptr, std::mutex * queue_mutex_ptr); // ROS publishers and subscribers @@ -139,10 +201,13 @@ class NvbloxNode : public rclcpp::Node // Publishers rclcpp::Publisher::SharedPtr mesh_publisher_; rclcpp::Publisher::SharedPtr - pointcloud_publisher_; + esdf_pointcloud_publisher_; + rclcpp::Publisher::SharedPtr + occupancy_publisher_; rclcpp::Publisher::SharedPtr map_slice_publisher_; - rclcpp::Publisher::SharedPtr slice_bounds_publisher_; + rclcpp::Publisher::SharedPtr + slice_bounds_publisher_; rclcpp::Publisher::SharedPtr mesh_marker_publisher_; @@ -158,21 +223,26 @@ class NvbloxNode : public rclcpp::Node rclcpp::TimerBase::SharedPtr depth_processing_timer_; rclcpp::TimerBase::SharedPtr color_processing_timer_; rclcpp::TimerBase::SharedPtr pointcloud_processing_timer_; + rclcpp::TimerBase::SharedPtr occupancy_publishing_timer_; rclcpp::TimerBase::SharedPtr esdf_processing_timer_; rclcpp::TimerBase::SharedPtr mesh_processing_timer_; + rclcpp::TimerBase::SharedPtr clear_outside_radius_timer_; // ROS & nvblox settings float voxel_size_ = 0.05f; - bool esdf_ = true; bool esdf_2d_ = true; - bool distance_slice_ = true; - bool mesh_ = true; - float slice_height_ = 1.0f; + bool esdf_distance_slice_ = true; + float esdf_slice_height_ = 1.0f; + ProjectiveLayerType static_projective_layer_type_ = + ProjectiveLayerType::kTsdf; + bool is_realsense_data_ = false; - // Depth / Lidar / color toggle parameters + // Toggle parameters bool use_depth_ = true; bool use_lidar_ = true; bool use_color_ = true; + bool compute_esdf_ = true; + bool compute_mesh_ = true; // LIDAR settings, defaults for Velodyne VLP16 int lidar_width_ = 1800; @@ -180,39 +250,54 @@ class NvbloxNode : public rclcpp::Node float lidar_vertical_fov_rad_ = 30.0 * M_PI / 180.0; // Used for ESDF slicing. Everything between min and max height will be - // compressed to a single 2D level, output at slice_height_. - float min_height_ = 0.0f; - float max_height_ = 1.0f; + // compressed to a single 2D level (if esdf_2d_ enabled), output at + // esdf_slice_height_. + float esdf_2d_min_height_ = 0.0f; + float esdf_2d_max_height_ = 1.0f; // Slice visualization params std::string slice_visualization_attachment_frame_id_ = "base_link"; float slice_visualization_side_length_ = 10.0f; // ROS settings & update throttles - std::string global_frame_ = "map"; + std::string global_frame_ = "odom"; /// Pose frame to use if using transform topics. std::string pose_frame_ = "base_link"; - float max_tsdf_update_hz_ = 10.0f; + float max_depth_update_hz_ = 10.0f; float max_color_update_hz_ = 5.0f; - float max_pointcloud_update_hz_ = 10.0f; - float max_mesh_update_hz_ = 5.0f; - float max_esdf_update_hz_ = 2.0f; + float max_lidar_update_hz_ = 10.0f; + float mesh_update_rate_hz_ = 5.0f; + float esdf_update_rate_hz_ = 2.0f; + float occupancy_publication_rate_hz_ = 2.0f; + /// Specifies what rate to poll the color & depth updates at. /// Will exit as no-op if no new images are in the queue so it is safe to /// set this higher than you expect images to come in at. float max_poll_rate_hz_ = 100.0f; + /// How many messages to store in the sensor messages queues (depth, color, + /// lidar) before deleting oldest messages. + int maximum_sensor_message_queue_length_ = 30; + /// Map clearing params /// Note that values <=0.0 indicate that no clearing is performed. float map_clearing_radius_m_ = -1.0f; + std::string map_clearing_frame_id_ = "base_link"; + float clear_outside_radius_rate_hz_ = 1.0f; + + // The QoS settings for the image input topics + std::string depth_qos_str_ = "SYSTEM_DEFAULT"; + std::string color_qos_str_ = "SYSTEM_DEFAULT"; // Mapper // Holds the map layers and their associated integrators // - TsdfLayer, ColorLayer, EsdfLayer, MeshLayer - std::unique_ptr mapper_; + std::shared_ptr mapper_; // The most important part: the ROS converter. Just holds buffers as state. - RosConverter converter_; + conversions::LayerConverter layer_converter_; + conversions::PointcloudConverter pointcloud_converter_; + conversions::EsdfSliceConverter esdf_slice_converter_; // Caches for GPU images ColorImage color_image_; @@ -226,16 +311,14 @@ class NvbloxNode : public rclcpp::Node libstatistics_collector::topic_statistics_collector:: ReceivedMessagePeriodCollector rgb_frame_statistics_; - - // Output directory - std::string output_dir_ = ""; + libstatistics_collector::topic_statistics_collector:: + ReceivedMessagePeriodCollector + pointcloud_frame_statistics_; // State for integrators running at various speeds. - rclcpp::Time last_tsdf_update_time_; + rclcpp::Time last_depth_update_time_; rclcpp::Time last_color_update_time_; - rclcpp::Time last_pointcloud_update_time_; - rclcpp::Time last_esdf_update_time_; - rclcpp::Time last_mesh_update_time_; + rclcpp::Time last_lidar_update_time_; // Cache the last known number of subscribers. size_t mesh_subscriber_count_ = 0; @@ -254,11 +337,13 @@ class NvbloxNode : public rclcpp::Node std::mutex color_queue_mutex_; std::mutex pointcloud_queue_mutex_; - // Keeps track of the mesh blocks deleted such that we can publish them for deletion in the rviz - // plugin + // Keeps track of the mesh blocks deleted such that we can publish them for + // deletion in the rviz plugin Index3DSet mesh_blocks_deleted_; }; } // namespace nvblox +#include "nvblox_ros/impl/nvblox_node_impl.hpp" + #endif // NVBLOX_ROS__NVBLOX_NODE_HPP_ diff --git a/nvblox_ros/include/nvblox_ros/transformer.hpp b/nvblox_ros/include/nvblox_ros/transformer.hpp index 63c1de10..d7233ca1 100644 --- a/nvblox_ros/include/nvblox_ros/transformer.hpp +++ b/nvblox_ros/include/nvblox_ros/transformer.hpp @@ -40,6 +40,14 @@ class Transformer public: explicit Transformer(rclcpp::Node * node); + /// @brief Looks up the transform between the frame with the passed name and the global frame + /// (which is set by the setters below). We either use tf2 or a stored queue of + /// transforms from messages. + /// @param sensor_frame The frame name. + /// @param timestamp Time of the transform. Passing rclcpp::Time(0) will return the latest + /// transform in the queue. + /// @param transform The output transform. + /// @return true if the lookup was successful. bool lookupTransformToGlobalFrame( const std::string & sensor_frame, const rclcpp::Time & timestamp, @@ -91,12 +99,14 @@ class Transformer /// Use this as the "pose" frame that's coming in. Needs to be set. std::string pose_frame_; - /// Whether to use TF at all. If set to false, sensor_frame and pose_frame - /// *need* to be set. + /// Whether to use TF transforms at all. + /// If set to false, use_topic_transforms_ must be true + /// and pose_frame *needs* to be set. bool use_tf_transforms_ = true; - /// Whether to listen to topics for transforms. If set to true, will get - /// at least global -> pose frame from the topics. If set to false, everything - /// will be resolved through TF. + /// Whether to listen to topics for transforms. + /// If set to true, will try to get `global_frame` to `pose_frame` + /// transform from the topics. If set to false, + /// everything will be resolved through TF. bool use_topic_transforms_ = false; /// Timestamp tolerance to use for transform *topics* only. uint64_t timestamp_tolerance_ns_ = 1e8; // 100 milliseconds diff --git a/nvblox_ros/include/nvblox_ros/utils.hpp b/nvblox_ros/include/nvblox_ros/utils.hpp new file mode 100644 index 00000000..8fe94fa3 --- /dev/null +++ b/nvblox_ros/include/nvblox_ros/utils.hpp @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_ROS__UTILS_HPP_ +#define NVBLOX_ROS__UTILS_HPP_ + +#include + +#include + +namespace nvblox +{ + +template +void inline declareParameterWithoutDefault( + const std::string & name, + rclcpp::Node * node_ptr) +{ + try { + node_ptr->declare_parameter(name); + } catch ( + const rclcpp::exceptions::UninitializedStaticallyTypedParameterException & ex) + { + } +} + +} // namespace nvblox + +#endif // NVBLOX_ROS__UTILS_HPP_ diff --git a/nvblox_ros/package.xml b/nvblox_ros/package.xml index 9e63fa53..23c2dc2c 100644 --- a/nvblox_ros/package.xml +++ b/nvblox_ros/package.xml @@ -21,8 +21,8 @@ nvblox_ros - 0.20.0 - NVBlox ROS2 interface + 0.30.0 + NVBlox ROS 2 interface Helen Oleynikova Hemal Shah @@ -45,7 +45,9 @@ std_srvs tf2_ros message_filters + cv_bridge nvblox + nvblox_ros_common ament_lint_auto ament_lint_common diff --git a/nvblox_ros/src/lib/conversions.cpp b/nvblox_ros/src/lib/conversions.cpp deleted file mode 100644 index 0a9cb784..00000000 --- a/nvblox_ros/src/lib/conversions.cpp +++ /dev/null @@ -1,304 +0,0 @@ -// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -#include "nvblox_ros/conversions.hpp" - -#include - -#include - -#include -#include - -#include - -namespace nvblox -{ - -RosConverter::RosConverter() {cudaStreamCreate(&cuda_stream_);} - -// Convert camera info message to NVBlox camera object -Camera RosConverter::cameraFromMessage( - const sensor_msgs::msg::CameraInfo & camera_info) -{ - Camera camera(camera_info.k[0], camera_info.k[4], camera_info.k[2], - camera_info.k[5], camera_info.width, camera_info.height); - return camera; -} - -// Convert image to depth frame object -bool RosConverter::colorImageFromImageMessage( - const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, - ColorImage * color_image) -{ - CHECK_NOTNULL(color_image); - - // First check if we actually have a valid image here. - if (image_msg->encoding != "rgb8") { - return false; - } - - color_image->populateFromBuffer( - image_msg->height, image_msg->width, - reinterpret_cast(&image_msg->data[0]), MemoryType::kDevice); - - return true; -} - -void RosConverter::imageMessageFromDepthImage( - const DepthImage & depth_image, const std::string & frame_id, - sensor_msgs::msg::Image * image_msg) -{ - CHECK_NOTNULL(image_msg); - size_t image_size = - depth_image.width() * depth_image.height() * sizeof(float); - image_msg->data.resize(image_size); - - image_msg->header.frame_id = frame_id; - image_msg->width = depth_image.width(); - image_msg->height = depth_image.height(); - image_msg->step = depth_image.width() * sizeof(float); - - image_msg->encoding = "32FC1"; - - cudaMemcpy( - &image_msg->data[0], depth_image.dataConstPtr(), image_size, - cudaMemcpyDefault); -} - -void RosConverter::meshMessageFromMeshLayer( - const BlockLayer & mesh_layer, nvblox_msgs::msg::Mesh * mesh_msg) -{ - std::vector block_indices = mesh_layer.getAllBlockIndices(); - meshMessageFromMeshBlocks(mesh_layer, block_indices, mesh_msg); -} - -void RosConverter::meshMessageFromMeshBlocks( - const BlockLayer & mesh_layer, - const std::vector & block_indices, - nvblox_msgs::msg::Mesh * mesh_msg, - const std::vector & block_indices_to_delete) -{ - // Go through all the blocks, converting each individual one. - mesh_msg->block_size = mesh_layer.block_size(); - mesh_msg->block_indices.resize(block_indices.size()); - mesh_msg->blocks.resize(block_indices.size()); - - for (size_t i = 0; i < block_indices.size(); i++) { - // Get the block origin. - mesh_msg->block_indices[i] = index3DMessageFromIndex3D(block_indices[i]); - - MeshBlock::ConstPtr mesh_block = - mesh_layer.getBlockAtIndex(block_indices[i]); - if (mesh_block == nullptr) { - continue; - } - - // Convert the actual block. - meshBlockMessageFromMeshBlock(*mesh_block, &mesh_msg->blocks[i]); - } - - for (const Index3D & block_index : block_indices_to_delete) { - mesh_msg->block_indices.push_back(index3DMessageFromIndex3D(block_index)); - mesh_msg->blocks.push_back(nvblox_msgs::msg::MeshBlock()); - } -} - -void RosConverter::distanceMapSliceFromLayer( - const EsdfLayer & layer, float z_slice_level, - nvblox_msgs::msg::DistanceMapSlice * map_slice) -{ - CHECK_NOTNULL(map_slice); - - // Unlikely we're going to have anything be 1000 meters inside an obstacle. - float kUnknownValue = -1000.0f; - - // Figure out how big the map needs to be. - // To do this, we get all the block indices, figure out which intersect - // with our desired height, and select the min and max in the X and Y - // direction. - const float block_size = layer.block_size(); - constexpr int kVoxelsPerSide = VoxelBlock::kVoxelsPerSide; - const float voxel_size = block_size / kVoxelsPerSide; - std::vector block_indices = layer.getAllBlockIndices(); - AxisAlignedBoundingBox aabb; - aabb.setEmpty(); - - // Figure out the index of the desired height. - Index3D desired_z_block_index; - Index3D desired_z_voxel_index; - getBlockAndVoxelIndexFromPositionInLayer( - block_size, Vector3f(0.0f, 0.0f, z_slice_level), &desired_z_block_index, - &desired_z_voxel_index); - - for (const Index3D & block_index : block_indices) { - // Skip all other heights of block. - if (block_index.z() != desired_z_block_index.z()) { - continue; - } - - // Extend the AABB by the dimensions of this block. - aabb.extend(getAABBOfBlock(block_size, block_index)); - } - - Vector3f bounding_size = aabb.sizes(); - // Width = cols, height = rows - int width = static_cast(std::ceil(bounding_size.x() / voxel_size)); - int height = static_cast(std::ceil(bounding_size.y() / voxel_size)); - - int map_size = width * height; - - // Set all the other settings in the message. - map_slice->origin.x = aabb.min().x(); - map_slice->origin.y = aabb.min().y(); - map_slice->origin.z = z_slice_level; - - map_slice->resolution = voxel_size; - map_slice->width = width; - map_slice->height = height; - map_slice->unknown_value = kUnknownValue; - - // Allocate the map directly, we will write directly to this output to prevent - // copies. - map_slice->data.resize(map_size, kUnknownValue); - - // Create an image of the correct size. - Image image(height, width, MemoryType::kDevice); - - // Fill in the float image. - populateSliceFromLayer( - layer, aabb, z_slice_level, voxel_size, kUnknownValue, - &image); - - checkCudaErrors( - cudaMemcpy( - map_slice->data.data(), image.dataPtr(), - image.numel() * sizeof(float), cudaMemcpyDefault)); -} - -// Convert a mesh to a marker array. -void RosConverter::markerMessageFromMeshLayer( - const BlockLayer & mesh_layer, const std::string & frame_id, - visualization_msgs::msg::MarkerArray * marker_msg) -{ - // Get all the mesh blocks. - std::vector indices = mesh_layer.getAllBlockIndices(); - - marker_msg->markers.resize(indices.size()); - - size_t output_index = 0; - for (size_t i = 0; i < indices.size(); i++) { - MeshBlock::ConstPtr mesh_block = mesh_layer.getBlockAtIndex(indices[i]); - if (mesh_block->size() == 0) { - continue; - } - markerMessageFromMeshBlock( - mesh_block, frame_id, - &marker_msg->markers[output_index]); - marker_msg->markers[output_index].id = output_index; - std::stringstream ns_stream; - ns_stream << indices[i].x() << "_" << indices[i].y() << "_" << - indices[i].z(); - marker_msg->markers[output_index].ns = ns_stream.str(); - output_index++; - } - marker_msg->markers.resize(output_index); -} - -void RosConverter::markerMessageFromMeshBlock( - const MeshBlock::ConstPtr & mesh_block, const std::string & frame_id, - visualization_msgs::msg::Marker * marker) -{ - marker->header.frame_id = frame_id; - marker->ns = "mesh"; - marker->scale.x = 1; - marker->scale.y = 1; - marker->scale.z = 1; - marker->pose.orientation.x = 0; - marker->pose.orientation.y = 0; - marker->pose.orientation.z = 0; - marker->pose.orientation.w = 1; - marker->type = visualization_msgs::msg::Marker::TRIANGLE_LIST; - - // Assumes UNWELDED mesh: all vertices in order. - std::vector vertices = mesh_block->getVertexVectorOnCPU(); - std::vector colors = mesh_block->getColorVectorOnCPU(); - std::vector triangles = mesh_block->getTriangleVectorOnCPU(); - - CHECK_EQ(vertices.size(), colors.size()); - - marker->points.reserve(triangles.size()); - marker->colors.reserve(triangles.size()); - - for (size_t i = 0; i < triangles.size(); i++) { - int index = triangles[i]; - if (index >= colors.size() || index >= vertices.size()) { - continue; - } - marker->points.push_back(pointMessageFromVector(vertices[index])); - marker->colors.push_back(colorMessageFromColor(colors[index])); - } -} - -bool RosConverter::checkLidarPointcloud( - const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud, - const Lidar & lidar) -{ - // Check the cache - if (checked_lidar_models_.find(lidar) != checked_lidar_models_.end()) { - return true; - } - - // Go through the pointcloud and check that each point projects to a pixel - // center. - sensor_msgs::PointCloud2ConstIterator iter_xyz(*pointcloud, "x"); - for (; iter_xyz != iter_xyz.end(); ++iter_xyz) { - Vector3f point(iter_xyz[0], iter_xyz[1], iter_xyz[2]); - if (point.hasNaN()) { - continue; - } - Vector2f u_C; - if (!lidar.project(point, &u_C)) { - // Point fell outside the FoV specified in the intrinsics. - return false; - } - } - checked_lidar_models_.insert(lidar); - return true; -} - -void RosConverter::writeLidarPointcloudToFile( - const std::string filepath_prefix, - const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud) -{ - // Write the dimensions - std::ofstream width_file(filepath_prefix + "_dims.txt", std::ofstream::out); - width_file << pointcloud->width << ", " << pointcloud->height; - width_file.close(); - // Write the data - Eigen::MatrixX3f pointcloud_matrix(pointcloud->width * pointcloud->height, 3); - sensor_msgs::PointCloud2ConstIterator iter_xyz(*pointcloud, "x"); - int idx = 0; - for (; iter_xyz != iter_xyz.end(); ++iter_xyz) { - Vector3f point(iter_xyz[0], iter_xyz[1], iter_xyz[2]); - pointcloud_matrix.row(idx) = point; - idx++; - } - io::writeToCsv(filepath_prefix + ".csv", pointcloud_matrix); -} - -} // namespace nvblox diff --git a/nvblox_ros/src/lib/conversions/esdf_slice_conversions.cu b/nvblox_ros/src/lib/conversions/esdf_slice_conversions.cu new file mode 100644 index 00000000..6d9229b5 --- /dev/null +++ b/nvblox_ros/src/lib/conversions/esdf_slice_conversions.cu @@ -0,0 +1,351 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "nvblox_ros/conversions/esdf_slice_conversions.hpp" + +namespace nvblox { +namespace conversions { + +EsdfSliceConverter::EsdfSliceConverter() {cudaStreamCreate(&cuda_stream_);} + + +__global__ void populateSliceFromLayerKernel( + Index3DDeviceHashMapType block_hash, AxisAlignedBoundingBox aabb, + float block_size, float* image, int rows, int cols, float z_slice_height, + float resolution, float unobserved_value) { + const float voxel_size = block_size / EsdfBlock::kVoxelsPerSide; + const int pixel_col = blockIdx.x * blockDim.x + threadIdx.x; + const int pixel_row = blockIdx.y * blockDim.y + threadIdx.y; + + if (pixel_col >= cols || pixel_row >= rows) { + return; + } + + // Figure out where this pixel should map to. + Vector3f voxel_position(aabb.min().x() + resolution * pixel_col, + aabb.min().y() + resolution * pixel_row, + z_slice_height); + + Index3D block_index, voxel_index; + + getBlockAndVoxelIndexFromPositionInLayer(block_size, voxel_position, + &block_index, &voxel_index); + + // Get the relevant block. + EsdfBlock* block_ptr = nullptr; + auto it = block_hash.find(block_index); + if (it != block_hash.end()) { + block_ptr = it->second; + } else { + image::access(pixel_row, pixel_col, cols, image) = unobserved_value; + return; + } + + // Get the relevant pixel. + const EsdfVoxel* voxel = + &block_ptr->voxels[voxel_index.x()][voxel_index.y()][voxel_index.z()]; + float distance = unobserved_value; + if (voxel->observed) { + distance = voxel_size * std::sqrt(voxel->squared_distance_vox); + if (voxel->is_inside) { + distance = -distance; + } + } + image::access(pixel_row, pixel_col, cols, image) = distance; +} + +void EsdfSliceConverter::populateSliceFromLayer(const EsdfLayer& layer, + const AxisAlignedBoundingBox& aabb, + float z_slice_height, + float resolution, + float unobserved_value, + Image* image) { + CHECK(image->memory_type() == MemoryType::kDevice || + image->memory_type() == MemoryType::kUnified) + << "Output needs to be accessible on device"; + // NOTE(alexmillane): At the moment we assume that the image is pre-allocated + // to be the right size. + CHECK_EQ(image->rows(), + static_cast(std::ceil(aabb.sizes().y() / resolution))); + CHECK_EQ(image->cols(), + static_cast(std::ceil(aabb.sizes().x() / resolution))); + if (image->numel() <= 0) { + return; + } + const float voxel_size = layer.voxel_size(); + + // Create a GPU hash of the ESDF. + GPULayerView gpu_layer_view = layer.getGpuLayerView(); + + // Pass in the GPU hash and AABB and let the kernel figure it out. + constexpr int kThreadDim = 16; + const int rounded_rows = static_cast( + std::ceil(image->rows() / static_cast(kThreadDim))); + const int rounded_cols = static_cast( + std::ceil(image->cols() / static_cast(kThreadDim))); + dim3 block_dim(rounded_cols, rounded_rows); + dim3 thread_dim(kThreadDim, kThreadDim); + + populateSliceFromLayerKernel<<>>( + gpu_layer_view.getHash().impl_, aabb, layer.block_size(), + image->dataPtr(), image->rows(), image->cols(), z_slice_height, + resolution, unobserved_value); + checkCudaErrors(cudaStreamSynchronize(cuda_stream_)); + checkCudaErrors(cudaPeekAtLastError()); +} + +void EsdfSliceConverter::distanceMapSliceImageFromLayer( + const EsdfLayer & layer, float z_slice_level, + const AxisAlignedBoundingBox & aabb, Image * map_slice_image_ptr) +{ + CHECK_NOTNULL(map_slice_image_ptr); + + const float voxel_size = + layer.block_size() / VoxelBlock::kVoxelsPerSide; + Vector3f bounding_size = aabb.sizes(); + int width = static_cast(std::ceil(bounding_size.x() / voxel_size)); + int height = static_cast(std::ceil(bounding_size.y() / voxel_size)); + + // Allocate a new image if required + if (map_slice_image_ptr->rows() != height || map_slice_image_ptr->cols() != width) { + *map_slice_image_ptr = Image(height, width, MemoryType::kDevice); + } + + // Fill in the float image. + populateSliceFromLayer( + layer, aabb, z_slice_level, voxel_size, + kDistanceMapSliceUnknownValue, map_slice_image_ptr); +} + +void EsdfSliceConverter::distanceMapSliceImageFromLayer( + const EsdfLayer & layer, float z_slice_level, Image * map_slice_image_ptr, + AxisAlignedBoundingBox * aabb_ptr) +{ + CHECK_NOTNULL(map_slice_image_ptr); + CHECK_NOTNULL(aabb_ptr); + // Calls the AABB version of this function but first calculates the aabb + + // Get the AABB of the layer + *aabb_ptr = getBoundingBoxOfLayerAtHeight(layer, z_slice_level); + + // Get the slice image + distanceMapSliceImageFromLayer(layer, z_slice_level, *aabb_ptr, map_slice_image_ptr); +} + +void EsdfSliceConverter::distanceMapSliceImageToMsg( + const Image & map_slice_image, const AxisAlignedBoundingBox & aabb, + float z_slice_level, float voxel_size, + nvblox_msgs::msg::DistanceMapSlice * map_slice) +{ + CHECK_NOTNULL(map_slice); + + // Set up the message + const int width = map_slice_image.cols(); + const int height = map_slice_image.rows(); + + map_slice->origin.x = aabb.min().x(); + map_slice->origin.y = aabb.min().y(); + map_slice->origin.z = z_slice_level; + + map_slice->resolution = voxel_size; + map_slice->width = width; + map_slice->height = height; + map_slice->unknown_value = kDistanceMapSliceUnknownValue; + + // Allocate the map directly, we will write directly to this output to prevent + // copies. + map_slice->data.resize(width * height, kDistanceMapSliceUnknownValue); + + // Copy into the message + checkCudaErrors( + cudaMemcpy( + map_slice->data.data(), map_slice_image.dataConstPtr(), + map_slice_image.numel() * sizeof(float), cudaMemcpyDefault)); +} + +AxisAlignedBoundingBox EsdfSliceConverter::getBoundingBoxOfLayerAtHeight( + const EsdfLayer & layer, const float z_slice_level) +{ + // Get the bounding box of the layer at this height + // To do this, we get all the block indices, figure out which intersect + // with our desired height, and select the min and max in the X and Y + // direction. + + // Figure out the index of the desired height. + Index3D desired_z_block_index; + Index3D desired_z_voxel_index; + getBlockAndVoxelIndexFromPositionInLayer( + layer.block_size(), Vector3f(0.0f, 0.0f, z_slice_level), + &desired_z_block_index, &desired_z_voxel_index); + + // Get a bounding box for the whole layer + AxisAlignedBoundingBox aabb; + aabb.setEmpty(); + for (const Index3D & block_index : layer.getAllBlockIndices()) { + // Skip all other heights of block. + if (block_index.z() != desired_z_block_index.z()) { + continue; + } + // Extend the AABB by the dimensions of this block. + aabb.extend(getAABBOfBlock(layer.block_size(), block_index)); + } + return aabb; +} + + +void EsdfSliceConverter::distanceMapSliceFromLayer( + const EsdfLayer & layer, float z_slice_level, + nvblox_msgs::msg::DistanceMapSlice * map_slice) +{ + CHECK_NOTNULL(map_slice); + + // Get the AABB of the layer + const AxisAlignedBoundingBox aabb = + getBoundingBoxOfLayerAtHeight(layer, z_slice_level); + + // Get the slice as an image + Image map_slice_image; + distanceMapSliceImageFromLayer(layer, z_slice_level, aabb, &map_slice_image); + + // Convert slice image to a message + distanceMapSliceImageToMsg( + map_slice_image, aabb, z_slice_level, + layer.voxel_size(), map_slice); +} + +void EsdfSliceConverter::distanceMapSliceFromLayers( + const EsdfLayer & layer_1, const EsdfLayer & layer_2, float z_slice_level, + Image * map_slice_image_ptr, AxisAlignedBoundingBox * aabb_ptr) +{ + CHECK_NOTNULL(map_slice_image_ptr); + + // Combined (enclosing) AABB + const AxisAlignedBoundingBox aabb_1 = + getBoundingBoxOfLayerAtHeight(layer_1, z_slice_level); + const AxisAlignedBoundingBox aabb_2 = + getBoundingBoxOfLayerAtHeight(layer_2, z_slice_level); + *aabb_ptr = aabb_1.merged(aabb_2); + + Image map_slice_image_1; + distanceMapSliceImageFromLayer( + layer_1, z_slice_level, *aabb_ptr, + &map_slice_image_1); + distanceMapSliceImageFromLayer( + layer_2, z_slice_level, *aabb_ptr, + map_slice_image_ptr); + + // Get the minimal distance between the two slices + image::elementWiseMinInPlaceGPU(map_slice_image_1, map_slice_image_ptr); +} + + +// Calling rules: +// - Should be called with 2D grid of thread-blocks where the total number of +// threads in each dimension exceeds the number of pixels in each image +// dimension ie: +// - blockIdx.x * blockDim.x + threadIdx.x > cols +// - blockIdx.y * blockDim.y + threadIdx.y > rows +// - We assume that theres enough space in the pointcloud to store a point per +// pixel if required. +__global__ void sliceImageToPointcloudKernel( + const float* slice_image_ptr, // NOLINT + const int rows, // NOLINT + const int cols, // NOLINT + const AxisAlignedBoundingBox aabb, // NOLINT + const float z_slice_level, // NOLINT + const float voxel_size, // NOLINT + PclPointXYZI* pointcloud_ptr, // NOLINT + int* max_index) { + // Get the pixel addressed by this thread. + const int col_idx = blockIdx.x * blockDim.x + threadIdx.x; + const int row_idx = blockIdx.y * blockDim.y + threadIdx.y; + if (col_idx >= cols || row_idx >= rows) { + return; + } + + // Access the slice + const float pixel_value = + image::access(row_idx, col_idx, cols, slice_image_ptr); + + // If map slice not observed don't add this point + constexpr float kEps = 1e-2; + if (fabsf(pixel_value - kDistanceMapSliceUnknownValue) < kEps) { + return; + } + + // 3D point associated with this pixel. + PclPointXYZI point; + point.x = aabb.min().x() + voxel_size * col_idx; + point.y = aabb.min().y() + voxel_size * row_idx; + point.z = z_slice_level; + + // Set the points intensity + point.intensity = pixel_value; + + // Write the point to the ouput + const int next_index = atomicAdd(max_index, 1); + PclPointXYZI* point_out = &pointcloud_ptr[next_index]; + *point_out = point; +} + +void EsdfSliceConverter::sliceImageToPointcloud( + const Image& map_slice_image, const AxisAlignedBoundingBox& aabb, + float z_slice_level, float voxel_size, + sensor_msgs::msg::PointCloud2* pointcloud_msg) { + CHECK_NOTNULL(pointcloud_msg); + + // Allocate max space we could take up + pcl_pointcloud_device_.reserve(map_slice_image.numel()); + + // Allocate output space for the number of valid points + if (!max_index_device_) { + max_index_device_ = make_unified(MemoryType::kDevice); + } + max_index_device_.setZero(); + + // Kernel + // Call params + // - 1 thread per pixel + // - 8 x 8 threads per thread block + // - N x M thread blocks get 1 thread per pixel + constexpr dim3 kThreadsPerThreadBlock(8, 8, 1); + const dim3 num_blocks( + map_slice_image.cols() / kThreadsPerThreadBlock.x + 1, // NOLINT + map_slice_image.rows() / kThreadsPerThreadBlock.y + 1, // NOLINT + 1); + sliceImageToPointcloudKernel<<>>( + map_slice_image.dataConstPtr(), map_slice_image.rows(), + map_slice_image.cols(), aabb, z_slice_level, voxel_size, + pcl_pointcloud_device_.data(), max_index_device_.get()); + checkCudaErrors(cudaStreamSynchronize(cuda_stream_)); + checkCudaErrors(cudaPeekAtLastError()); + + // Retrieve how many points were actually used + max_index_host_ = max_index_device_.clone(MemoryType::kHost); + + // Resize the pointcloud + pcl_pointcloud_device_.resize(*max_index_host_); + + // Put the points in the message + copyDevicePointcloudToMsg(pcl_pointcloud_device_, pointcloud_msg); +} + +} // namespace conversions +} // namespace nvblox diff --git a/nvblox_ros/src/lib/conversions/image_conversions.cu b/nvblox_ros/src/lib/conversions/image_conversions.cu new file mode 100644 index 00000000..032363bd --- /dev/null +++ b/nvblox_ros/src/lib/conversions/image_conversions.cu @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "nvblox_ros/conversions/image_conversions.hpp" + +namespace nvblox { +namespace conversions { + +// Convert camera info message to NVBlox camera object +Camera cameraFromMessage( + const sensor_msgs::msg::CameraInfo& camera_info) { + Camera camera(camera_info.k[0], camera_info.k[4], camera_info.k[2], + camera_info.k[5], camera_info.width, camera_info.height); + return camera; +} + +// Convert image to GPU image +bool colorImageFromImageMessage( + const sensor_msgs::msg::Image::ConstSharedPtr& image_msg, + ColorImage* color_image) { + CHECK_NOTNULL(color_image); + + // Convert to our color encoding + cv_bridge::CvImageConstPtr rgba_cv_image = + cv_bridge::toCvCopy(image_msg, "rgba8"); + + color_image->populateFromBuffer( + image_msg->height, image_msg->width, + reinterpret_cast(rgba_cv_image->image.ptr()), + MemoryType::kDevice); + + return true; +} + +// Convert image to GPU image +bool monoImageFromImageMessage( + const sensor_msgs::msg::Image::ConstSharedPtr& image_msg, + MonoImage* mono_image) { + CHECK_NOTNULL(mono_image); + + // First check if we actually have a valid image here. + if (image_msg->encoding != "mono8") { + return false; + } + + mono_image->populateFromBuffer(image_msg->height, image_msg->width, + &image_msg->data[0], MemoryType::kDevice); + + return true; +} + +void imageMessageFromDepthImage( + const DepthImage& depth_image, const std::string& frame_id, + sensor_msgs::msg::Image* image_msg) { + CHECK_NOTNULL(image_msg); + size_t image_size = + depth_image.width() * depth_image.height() * sizeof(float); + image_msg->data.resize(image_size); + + image_msg->header.frame_id = frame_id; + image_msg->width = depth_image.width(); + image_msg->height = depth_image.height(); + image_msg->step = depth_image.width() * sizeof(float); + + image_msg->encoding = "32FC1"; + + cudaMemcpy(&image_msg->data[0], depth_image.dataConstPtr(), image_size, + cudaMemcpyDefault); +} + +void imageMessageFromColorImage( + const ColorImage& color_image, const std::string& frame_id, + sensor_msgs::msg::Image* image_msg) { + CHECK_NOTNULL(image_msg); + constexpr int num_channels = 4; + size_t image_size = color_image.width() * color_image.height() * + sizeof(uint8_t) * num_channels; + image_msg->data.resize(image_size); + + image_msg->header.frame_id = frame_id; + image_msg->width = color_image.width(); + image_msg->height = color_image.height(); + image_msg->step = color_image.width() * sizeof(uint8_t) * num_channels; + + image_msg->encoding = "rgba8"; + + cudaMemcpy(&image_msg->data[0], color_image.dataConstPtr(), image_size, + cudaMemcpyDefault); +} + +struct DivideBy1000 : public thrust::unary_function { + __host__ __device__ float operator()(const uint16_t& in) { + return static_cast(in) / 1000.0f; + } +}; + +// Convert image to depth frame object +bool depthImageFromImageMessage( + const sensor_msgs::msg::Image::ConstSharedPtr& image_msg, + DepthImage* depth_image) { + CHECK_NOTNULL(depth_image); + // If the image is a float, we can just copy it over directly. + // If the image is int16, we need to divide by 1000 to get the correct + // format for us. + + // First check if we actually have a valid image here. + if (image_msg->encoding != "32FC1" && image_msg->encoding != "16UC1") { + return false; + } + + // Fill it in. How this is done depends on what the image encoding is. + if (image_msg->encoding == "32FC1") { + // Float to float, so this should be a straight-up copy. :) + depth_image->populateFromBuffer( + image_msg->height, image_msg->width, + reinterpret_cast(&image_msg->data[0])); + } else if (image_msg->encoding == "16UC1") { + // Then we have to just go byte-by-byte and convert this. This is a massive + // pain and slow. We need to find a better way to do this; on GPU or + // through openCV. + const uint16_t* char_depth_buffer = + reinterpret_cast(&image_msg->data[0]); + const int numel = image_msg->height * image_msg->width; + + bool kUseCuda = false; + if (kUseCuda) { + // Make sure there's enough output space. + if (depth_image->numel() < numel) { + *depth_image = DepthImage(image_msg->height, image_msg->width, + MemoryType::kDevice); + } + + // Now just thrust it. + thrust::transform(char_depth_buffer, char_depth_buffer + numel, + depth_image->dataPtr(), DivideBy1000()); + } else { + std::vector float_depth_buffer(numel); + for (int i = 0; i < numel; i++) { + float_depth_buffer[i] = + static_cast(char_depth_buffer[i]) / 1000.0f; + } + depth_image->populateFromBuffer(image_msg->height, image_msg->width, + float_depth_buffer.data(), + MemoryType::kDevice); + } + } + + return true; +} + +} // namespace conversions +} // namespace nvblox diff --git a/nvblox_ros/src/lib/conversions/layer_conversions.cu b/nvblox_ros/src/lib/conversions/layer_conversions.cu new file mode 100644 index 00000000..3b3e726d --- /dev/null +++ b/nvblox_ros/src/lib/conversions/layer_conversions.cu @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "nvblox_ros/conversions/layer_conversions.hpp" + +namespace nvblox { +namespace conversions { + +LayerConverter::LayerConverter() { cudaStreamCreate(&cuda_stream_); } + +template +__device__ bool getVoxelIntensity(const VoxelType& voxel, float voxel_size, + float* intensity); + +template <> +__device__ bool getVoxelIntensity(const OccupancyVoxel& voxel, float voxel_size, + float* intensity) { + constexpr float kMinProbability = 0.5f; + *intensity = probabilityFromLogOdds(voxel.log_odds); + return probabilityFromLogOdds(voxel.log_odds) > kMinProbability; +} + +template <> +__device__ bool getVoxelIntensity(const EsdfVoxel& voxel, float voxel_size, + float* intensity) { + *intensity = voxel_size * sqrtf(voxel.squared_distance_vox); + if (voxel.is_inside) { + *intensity = -*intensity; + } + return voxel.observed; +} + +template <> +__device__ bool getVoxelIntensity(const TsdfVoxel& voxel, float voxel_size, + float* intensity) { + constexpr float kMinWeight = 0.1f; + *intensity = voxel.distance; + return voxel.weight > kMinWeight; +} + +// Inputs: GPU hash for the E/TSDF. +// AABB. +// Voxel Size (just needed for ESDF). +// Outputs: vector of pcl::PointXYZIs. +// max index (updated atomically). +template +__global__ void copyLayerToPCLKernel( + Index3DDeviceHashMapType> block_hash, + Index3D* block_indices, size_t num_indices, int max_output_indices, + AxisAlignedBoundingBox aabb, float block_size, PclPointXYZI* pointcloud, + int* max_index) { + const float voxel_size = block_size / VoxelBlock::kVoxelsPerSide; + + // Get the relevant block. + __shared__ VoxelBlock* block_ptr; + if (threadIdx.x == 0 && threadIdx.y == 0 && threadIdx.z == 0) { + block_ptr = nullptr; + auto it = block_hash.find(block_indices[blockIdx.x]); + if (it != block_hash.end()) { + block_ptr = it->second; + } + } + + __syncthreads(); + + if (block_ptr == nullptr) { + return; + } + + // For every voxel, check if it's in the AABB. + Index3D voxel_index(threadIdx.x, threadIdx.y, threadIdx.z); + + // Get the voxel position: + Vector3f voxel_position = getPositionFromBlockIndexAndVoxelIndex( + block_size, block_indices[blockIdx.x], voxel_index); + + if (!aabb.contains(voxel_position)) { + return; + } + + // Check if this voxel sucks or not. + const VoxelType& voxel = + block_ptr->voxels[voxel_index.x()][voxel_index.y()][voxel_index.z()]; + float intensity = 0.0f; + if (!getVoxelIntensity(voxel, voxel_size, &intensity)) { + return; + } + + // Otherwise shove it in the output. + int next_index = atomicAdd(max_index, 1); + + if (next_index >= max_output_indices) { + printf("Overrunning the space. This shouldn't happen.\n"); + return; + } + PclPointXYZI& point = pointcloud[next_index]; + point.x = voxel_position.x(); + point.y = voxel_position.y(); + point.z = voxel_position.z(); + point.intensity = intensity; +} + +template +void LayerConverter::pointcloudMsgFromLayerInAABB( + const VoxelBlockLayer& layer, const AxisAlignedBoundingBox& aabb, + sensor_msgs::msg::PointCloud2* pointcloud) { + CHECK_NOTNULL(pointcloud); + + constexpr int kVoxelsPerSide = VoxelBlock::kVoxelsPerSide; + constexpr int kVoxelsPerBlock = + kVoxelsPerSide * kVoxelsPerSide * kVoxelsPerSide; + const float voxel_size = layer.voxel_size(); + + // In case the AABB is infinite, make sure we have a finite number of + // voxels. + AxisAlignedBoundingBox aabb_intersect = getAABBOfAllocatedBlocks(layer); + if (!aabb.isEmpty()) { + aabb_intersect = aabb_intersect.intersection(aabb); + } + + // Figure out which blocks are in the AABB. + std::vector block_indices = + getAllocatedBlocksWithinAABB(layer, aabb_intersect); + // Copy to device memory. + block_indices_device_ = block_indices; + + if (block_indices.empty()) { + return; + } + size_t num_voxels = block_indices.size() * kVoxelsPerBlock; + + // Allocate a GPU pointcloud. + pcl_pointcloud_device_.resize(num_voxels); + + // Get the hash. + GPULayerView> gpu_layer_view = layer.getGpuLayerView(); + + // Create an output size variable. + if (!max_index_device_) { + max_index_device_ = make_unified(MemoryType::kDevice); + } + max_index_device_.setZero(); + + // Call the kernel. + int dim_block = block_indices.size(); + dim3 dim_threads(kVoxelsPerSide, kVoxelsPerSide, kVoxelsPerSide); + + copyLayerToPCLKernel<<>>( + gpu_layer_view.getHash().impl_, block_indices_device_.data(), + block_indices.size(), num_voxels, aabb_intersect, layer.block_size(), + pcl_pointcloud_device_.data(), max_index_device_.get()); + checkCudaErrors(cudaStreamSynchronize(cuda_stream_)); + checkCudaErrors(cudaPeekAtLastError()); + + // Copy the pointcloud out. + max_index_host_ = max_index_device_.clone(MemoryType::kHost); + pcl_pointcloud_device_.resize(*max_index_host_); + + // Copy to the message + copyDevicePointcloudToMsg(pcl_pointcloud_device_, pointcloud); +} + +// Template specializations. +template void LayerConverter::pointcloudMsgFromLayerInAABB( + const VoxelBlockLayer& layer, const AxisAlignedBoundingBox& aabb, + sensor_msgs::msg::PointCloud2* pointcloud); + +template void LayerConverter::pointcloudMsgFromLayerInAABB( + const VoxelBlockLayer& layer, const AxisAlignedBoundingBox& aabb, + sensor_msgs::msg::PointCloud2* pointcloud); + +template void LayerConverter::pointcloudMsgFromLayerInAABB( + const VoxelBlockLayer& layer, + const AxisAlignedBoundingBox& aabb, + sensor_msgs::msg::PointCloud2* pointcloud); + +} // namespace conversions +} // namespace nvblox diff --git a/nvblox_ros/src/lib/conversions/mesh_conversions.cpp b/nvblox_ros/src/lib/conversions/mesh_conversions.cpp new file mode 100644 index 00000000..5a96cbfd --- /dev/null +++ b/nvblox_ros/src/lib/conversions/mesh_conversions.cpp @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "nvblox_ros/conversions/mesh_conversions.hpp" + +namespace nvblox +{ +namespace conversions +{ + +geometry_msgs::msg::Point32 point32MessageFromVector( + const Eigen::Vector3f & vector) +{ + geometry_msgs::msg::Point32 point; + point.x = vector.x(); + point.y = vector.y(); + point.z = vector.z(); + return point; +} + +geometry_msgs::msg::Point pointMessageFromVector( + const Eigen::Vector3f & vector) +{ + geometry_msgs::msg::Point point; + point.x = vector.x(); + point.y = vector.y(); + point.z = vector.z(); + return point; +} + +std_msgs::msg::ColorRGBA colorMessageFromColor(const Color & color) +{ + std_msgs::msg::ColorRGBA color_msg; + color_msg.r = static_cast(color.r) / 255.0f; + color_msg.g = static_cast(color.g) / 255.0f; + color_msg.b = static_cast(color.b) / 255.0f; + color_msg.a = 1.0f; + return color_msg; +} + +nvblox_msgs::msg::Index3D index3DMessageFromIndex3D(const Index3D & index) +{ + nvblox_msgs::msg::Index3D index_msg; + index_msg.x = index.x(); + index_msg.y = index.y(); + index_msg.z = index.z(); + return index_msg; +} + +void meshMessageFromMeshLayer( + const BlockLayer & mesh_layer, + nvblox_msgs::msg::Mesh * mesh_msg) +{ + std::vector block_indices = mesh_layer.getAllBlockIndices(); + meshMessageFromMeshBlocks(mesh_layer, block_indices, mesh_msg); +} + +void meshBlockMessageFromMeshBlock( + const MeshBlock & mesh_block, nvblox_msgs::msg::MeshBlock * mesh_block_msg) +{ + CHECK_NOTNULL(mesh_block_msg); + + size_t num_vertices = mesh_block.vertices.size(); + + mesh_block_msg->vertices.resize(num_vertices); + mesh_block_msg->normals.resize(num_vertices); + mesh_block_msg->colors.resize(mesh_block.colors.size()); + mesh_block_msg->triangles.resize(mesh_block.triangles.size()); + + std::vector vertices = mesh_block.getVertexVectorOnCPU(); + std::vector normals = mesh_block.getNormalVectorOnCPU(); + std::vector colors = mesh_block.getColorVectorOnCPU(); + + // Copy over vertices and normals. + for (size_t i = 0; i < num_vertices; i++) { + mesh_block_msg->vertices[i] = point32MessageFromVector(vertices[i]); + mesh_block_msg->normals[i] = point32MessageFromVector(normals[i]); + } + + // Copy over colors if available. + for (size_t i = 0; i < mesh_block.colors.size(); i++) { + mesh_block_msg->colors[i] = colorMessageFromColor(colors[i]); + } + + // Copying over triangles is thankfully easy. + mesh_block_msg->triangles = mesh_block.getTriangleVectorOnCPU(); +} + +void meshMessageFromMeshBlocks( + const BlockLayer & mesh_layer, + const std::vector & block_indices, nvblox_msgs::msg::Mesh * mesh_msg, + const std::vector & block_indices_to_delete) +{ + // Go through all the blocks, converting each individual one. + mesh_msg->block_size = mesh_layer.block_size(); + mesh_msg->block_indices.resize(block_indices.size()); + mesh_msg->blocks.resize(block_indices.size()); + + for (size_t i = 0; i < block_indices.size(); i++) { + // Get the block origin. + mesh_msg->block_indices[i] = index3DMessageFromIndex3D(block_indices[i]); + + MeshBlock::ConstPtr mesh_block = + mesh_layer.getBlockAtIndex(block_indices[i]); + if (mesh_block == nullptr) { + continue; + } + + // Convert the actual block. + meshBlockMessageFromMeshBlock(*mesh_block, &mesh_msg->blocks[i]); + } + + for (const Index3D & block_index : block_indices_to_delete) { + mesh_msg->block_indices.push_back(index3DMessageFromIndex3D(block_index)); + mesh_msg->blocks.push_back(nvblox_msgs::msg::MeshBlock()); + } +} + +void markerMessageFromMeshBlock( + const MeshBlock::ConstPtr & mesh_block, + const std::string & frame_id, + visualization_msgs::msg::Marker * marker) +{ + marker->header.frame_id = frame_id; + marker->ns = "mesh"; + marker->scale.x = 1; + marker->scale.y = 1; + marker->scale.z = 1; + marker->pose.orientation.x = 0; + marker->pose.orientation.y = 0; + marker->pose.orientation.z = 0; + marker->pose.orientation.w = 1; + marker->type = visualization_msgs::msg::Marker::TRIANGLE_LIST; + + // Assumes UNWELDED mesh: all vertices in order. + std::vector vertices = mesh_block->getVertexVectorOnCPU(); + std::vector colors = mesh_block->getColorVectorOnCPU(); + std::vector triangles = mesh_block->getTriangleVectorOnCPU(); + + CHECK_EQ(vertices.size(), colors.size()); + + marker->points.reserve(triangles.size()); + marker->colors.reserve(triangles.size()); + + for (size_t i = 0; i < triangles.size(); i++) { + int index = triangles[i]; + if (index >= colors.size() || index >= vertices.size()) { + continue; + } + marker->points.push_back(pointMessageFromVector(vertices[index])); + marker->colors.push_back(colorMessageFromColor(colors[index])); + } +} + +// Convert a mesh to a marker array. +void markerMessageFromMeshLayer( + const BlockLayer & mesh_layer, const std::string & frame_id, + visualization_msgs::msg::MarkerArray * marker_msg) +{ + // Get all the mesh blocks. + std::vector indices = mesh_layer.getAllBlockIndices(); + + marker_msg->markers.resize(indices.size()); + + size_t output_index = 0; + for (size_t i = 0; i < indices.size(); i++) { + MeshBlock::ConstPtr mesh_block = mesh_layer.getBlockAtIndex(indices[i]); + if (mesh_block->size() == 0) { + continue; + } + markerMessageFromMeshBlock( + mesh_block, frame_id, + &marker_msg->markers[output_index]); + marker_msg->markers[output_index].id = output_index; + std::stringstream ns_stream; + ns_stream << indices[i].x() << "_" << indices[i].y() << "_" + << indices[i].z(); + marker_msg->markers[output_index].ns = ns_stream.str(); + output_index++; + } + marker_msg->markers.resize(output_index); +} + +} // namespace conversions +} // namespace nvblox diff --git a/nvblox_ros/src/lib/conversions/pointcloud_conversions.cu b/nvblox_ros/src/lib/conversions/pointcloud_conversions.cu new file mode 100644 index 00000000..b5bad5aa --- /dev/null +++ b/nvblox_ros/src/lib/conversions/pointcloud_conversions.cu @@ -0,0 +1,255 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include + +#include "nvblox_ros/conversions/pointcloud_conversions.hpp" + +namespace nvblox { +namespace conversions { + + +void copyDevicePointcloudToMsg( + const device_vector& pcl_pointcloud_device, + sensor_msgs::msg::PointCloud2* pointcloud_msg) { + // Copy into the pointcloud message. + const int num_points = pcl_pointcloud_device.size(); + size_t output_num_bytes = sizeof(PclPointXYZI) * num_points; + pointcloud_msg->data.resize(output_num_bytes); + // Copy over all the points. + cudaMemcpy(pointcloud_msg->data.data(), pcl_pointcloud_device.data(), + output_num_bytes, cudaMemcpyDeviceToHost); + + // Fill the other fields in the pointcloud_msg message. + pointcloud_msg->height = 1; + pointcloud_msg->width = num_points; + pointcloud_msg->point_step = sizeof(PclPointXYZI); + pointcloud_msg->row_step = output_num_bytes; + + // Populate the fields. + sensor_msgs::msg::PointField point_field; + point_field.name = "x"; + point_field.datatype = sensor_msgs::msg::PointField::FLOAT32; + point_field.offset = 0; + point_field.count = 1; + + pointcloud_msg->fields.push_back(point_field); + point_field.name = "y"; + point_field.offset += sizeof(float); + pointcloud_msg->fields.push_back(point_field); + point_field.name = "z"; + point_field.offset += sizeof(float); + pointcloud_msg->fields.push_back(point_field); + point_field.name = "intensity"; + point_field.offset += sizeof(float); + pointcloud_msg->fields.push_back(point_field); +} + + +PointcloudConverter::PointcloudConverter() {cudaStreamCreate(&cuda_stream_);} + + +bool PointcloudConverter::checkLidarPointcloud( + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud, + const Lidar & lidar) +{ + // Check the cache + if (checked_lidar_models_.find(lidar) != checked_lidar_models_.end()) { + return true; + } + + // Go through the pointcloud and check that each point projects to a pixel + // center. + sensor_msgs::PointCloud2ConstIterator iter_xyz(*pointcloud, "x"); + for (; iter_xyz != iter_xyz.end(); ++iter_xyz) { + Vector3f point(iter_xyz[0], iter_xyz[1], iter_xyz[2]); + if (point.hasNaN()) { + continue; + } + Vector2f u_C; + if (!lidar.project(point, &u_C)) { + // Point fell outside the FoV specified in the intrinsics. + return false; + } + } + checked_lidar_models_.insert(lidar); + return true; +} + +void PointcloudConverter::writeLidarPointcloudToFile( + const std::string filepath_prefix, + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud) +{ + // Write the dimensions + std::ofstream width_file(filepath_prefix + "_dims.txt", std::ofstream::out); + width_file << pointcloud->width << ", " << pointcloud->height; + width_file.close(); + // Write the data + Eigen::MatrixX3f pointcloud_matrix(pointcloud->width * pointcloud->height, 3); + sensor_msgs::PointCloud2ConstIterator iter_xyz(*pointcloud, "x"); + int idx = 0; + for (; iter_xyz != iter_xyz.end(); ++iter_xyz) { + Vector3f point(iter_xyz[0], iter_xyz[1], iter_xyz[2]); + pointcloud_matrix.row(idx) = point; + idx++; + } + io::writeToCsv(filepath_prefix + ".csv", pointcloud_matrix); +} + + +__global__ void depthImageFromPointcloudKernel( + const Vector3f* pointcloud, // NOLINT + const Lidar lidar, // NOLINT + const int num_bytes_between_points, // NOLINT + const int num_points, // NOLINT + float* depth_image) { + const int idx = blockIdx.x * blockDim.x + threadIdx.x; + + if (idx >= num_points) { + return; + } + + // Read a point from global memory + const Vector3f point = pointcloud[idx]; + + if (isnan(point.x()) || isnan(point.y()) || isnan(point.z())) { + return; + } + + // Project + Index2D u_C_int; + if (!lidar.project(point, &u_C_int)) { + return; + } + + // Write the depth to the image + // NOTE(alexmillane): It's possible multiple points project to the same pixel. + // Firstly, writes should be atomic, so we're fine in that respect. The last + // projection to write "wins" so we basically end up with a random value, of + // the values that project to this pixel. + image::access(u_C_int.y(), u_C_int.x(), lidar.num_azimuth_divisions(), + depth_image) = lidar.getDepth(point); +} + +void PointcloudConverter::depthImageFromPointcloudGPU( + const sensor_msgs::msg::PointCloud2::ConstSharedPtr& pointcloud, + const Lidar& lidar, DepthImage* depth_image_ptr) { + CHECK(depth_image_ptr->memory_type() == MemoryType::kDevice || + depth_image_ptr->memory_type() == MemoryType::kUnified); + + // Check output space, and reallocate if required + if ((depth_image_ptr->rows() != lidar.num_elevation_divisions()) || + (depth_image_ptr->cols() != lidar.num_azimuth_divisions())) { + *depth_image_ptr = + DepthImage(lidar.num_elevation_divisions(), + lidar.num_azimuth_divisions(), MemoryType::kDevice); + } + + // Set the entire image to 0. + depth_image_ptr->setZero(); + + // Get a pointer to the pointcloud + sensor_msgs::PointCloud2ConstIterator iter_xyz(*pointcloud, "x"); + const float* pointcloud_data_ptr = &(*iter_xyz); + const int num_bytes_between_points = pointcloud->point_step; + const int num_points = pointcloud->width * pointcloud->height; + + // Expand buffers where required + if (lidar.numel() > lidar_pointcloud_host_.capacity()) { + const int new_size = static_cast(lidar.numel()); + lidar_pointcloud_host_.reserve(new_size); + lidar_pointcloud_device_.reserve(new_size); + } + + // Copy the pointcloud into pinned host memory + lidar_pointcloud_host_.clear(); + for (; iter_xyz != iter_xyz.end(); ++iter_xyz) { + lidar_pointcloud_host_.push_back( + Vector3f(iter_xyz[0], iter_xyz[1], iter_xyz[2])); + } + // Copy the pointcloud to the GPU + lidar_pointcloud_device_ = lidar_pointcloud_host_; + + // Convert to an image on the GPU + constexpr int num_threads_per_block = 256; // because why not + const int num_thread_blocks = lidar.numel() / num_threads_per_block + 1; + depthImageFromPointcloudKernel<<>>( + lidar_pointcloud_device_.data(), lidar, num_bytes_between_points, + lidar.numel(), depth_image_ptr->dataPtr()); + checkCudaErrors(cudaStreamSynchronize(cuda_stream_)); + checkCudaErrors(cudaPeekAtLastError()); +} + +struct Vector3fToPcl { + __host__ __device__ PclPointXYZI operator()(const Vector3f& vec) const { + PclPointXYZI point; + point.x = vec.x(); + point.y = vec.y(); + point.z = vec.z(); + point.intensity = 1.0; + return point; + }; +}; + +// Internal pointcloud representation to a ROS pointcloud +void PointcloudConverter::pointcloudMsgFromPointcloud( + const Pointcloud& pointcloud, + sensor_msgs::msg::PointCloud2* pointcloud_msg) { + CHECK_NOTNULL(pointcloud_msg); + CHECK(pointcloud.memory_type() == MemoryType::kDevice || + pointcloud.memory_type() == MemoryType::kUnified); + + pcl_pointcloud_device_.resize(pointcloud.size()); + + thrust::transform(thrust::device, pointcloud.points().begin(), + pointcloud.points().end(), pcl_pointcloud_device_.begin(), + Vector3fToPcl()); + + // Put the points in the message + copyDevicePointcloudToMsg(pcl_pointcloud_device_, pointcloud_msg); +} + +void PointcloudConverter::pointsToCubesMarkerMsg( + const std::vector& points, const float cube_size, + const Color& color, visualization_msgs::msg::Marker* marker_ptr) { + CHECK_NOTNULL(marker_ptr); + // Publish + marker_ptr->action = marker_ptr->ADD; + marker_ptr->type = marker_ptr->CUBE_LIST; + marker_ptr->points.reserve(points.size()); + for (const Vector3f& p_L : points) { + geometry_msgs::msg::Point point; + point.x = p_L.x(); + point.y = p_L.y(); + point.z = p_L.z(); + marker_ptr->points.push_back(point); + } + marker_ptr->color.r = static_cast(color.r) / 255.0f; + marker_ptr->color.g = static_cast(color.g) / 255.0f; + marker_ptr->color.b = static_cast(color.b) / 255.0f; + marker_ptr->color.a = static_cast(color.a) / 255.0f; + marker_ptr->scale.x = cube_size; + marker_ptr->scale.y = cube_size; + marker_ptr->scale.z = cube_size; +} + +} // namespace conversions +} // namespace nvblox diff --git a/nvblox_ros/src/lib/cuda/conversions.cu b/nvblox_ros/src/lib/cuda/conversions.cu deleted file mode 100644 index 19222942..00000000 --- a/nvblox_ros/src/lib/cuda/conversions.cu +++ /dev/null @@ -1,458 +0,0 @@ -// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES -// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -#include "nvblox_ros/conversions.hpp" - -#include -#include - -#include - -#include -#include -#include -#include - -namespace nvblox { - -template -__device__ bool getVoxelIntensity(const VoxelType& voxel, float voxel_size, - float* intensity); - -template <> -__device__ bool getVoxelIntensity(const EsdfVoxel& voxel, float voxel_size, - float* intensity) { - *intensity = voxel_size * sqrtf(voxel.squared_distance_vox); - if (voxel.is_inside) { - *intensity = -*intensity; - } - return voxel.observed; -} - -template <> -__device__ bool getVoxelIntensity(const TsdfVoxel& voxel, float voxel_size, - float* intensity) { - constexpr float kMinWeight = 0.1f; - *intensity = voxel.distance; - return voxel.weight > kMinWeight; -} - -// Inputs: GPU hash for the E/TSDF. -// AABB. -// Voxel Size (just needed for ESDF). -// Outputs: vector of pcl::PointXYZIs. -// max index (updated atomically). -template -__global__ void copyPointcloudToPCL( - Index3DDeviceHashMapType> block_hash, - Index3D* block_indices, size_t num_indices, int max_output_indices, - AxisAlignedBoundingBox aabb, float block_size, PclPointXYZI* pointcloud, - int* max_index) { - const float voxel_size = block_size / VoxelBlock::kVoxelsPerSide; - - // Get the relevant block. - __shared__ VoxelBlock* block_ptr; - if (threadIdx.x == 0 && threadIdx.y == 0 && threadIdx.z == 0) { - block_ptr = nullptr; - auto it = block_hash.find(block_indices[blockIdx.x]); - if (it != block_hash.end()) { - block_ptr = it->second; - } - } - - __syncthreads(); - - if (block_ptr == nullptr) { - return; - } - - // For every voxel, check if it's in the AABB. - Index3D voxel_index(threadIdx.x, threadIdx.y, threadIdx.z); - - // Get the voxel position: - Vector3f voxel_position = getPositionFromBlockIndexAndVoxelIndex( - block_size, block_indices[blockIdx.x], voxel_index); - - if (!aabb.contains(voxel_position)) { - return; - } - - // Check if this voxel sucks or not. - const VoxelType& voxel = - block_ptr->voxels[voxel_index.x()][voxel_index.y()][voxel_index.z()]; - float intensity = 0.0f; - if (!getVoxelIntensity(voxel, voxel_size, &intensity)) { - return; - } - - // Otherwise shove it in the output. - int next_index = atomicAdd(max_index, 1); - - if (next_index >= max_output_indices) { - printf("Overrunning the space. This shouldn't happen.\n"); - return; - } - PclPointXYZI& point = pointcloud[next_index]; - point.x = voxel_position.x(); - point.y = voxel_position.y(); - point.z = voxel_position.z(); - point.intensity = intensity; -} - -template -void RosConverter::convertLayerInAABBToPCLCuda( - const VoxelBlockLayer& layer, const AxisAlignedBoundingBox& aabb, - sensor_msgs::msg::PointCloud2* pointcloud) { - constexpr int kVoxelsPerSide = VoxelBlock::kVoxelsPerSide; - constexpr int kVoxelsPerBlock = - kVoxelsPerSide * kVoxelsPerSide * kVoxelsPerSide; - const float voxel_size = layer.voxel_size(); - - // In case the AABB is infinite, make sure we have a finite number of - // voxels. - AxisAlignedBoundingBox aabb_intersect = getAABBOfAllocatedBlocks(layer); - if (!aabb.isEmpty()) { - aabb_intersect = aabb_intersect.intersection(aabb); - } - - // Figure out which blocks are in the AABB. - std::vector block_indices = - getAllocatedBlocksWithinAABB(layer, aabb_intersect); - // Copy to device memory. - block_indices_device_ = block_indices; - - if (block_indices.empty()) { - return; - } - size_t num_voxels = block_indices.size() * kVoxelsPerBlock; - - // Allocate a GPU pointcloud. - layer_pointcloud_device_.resize(num_voxels); - - // Get the hash. - GPULayerView> gpu_layer_view = layer.getGpuLayerView(); - - // Create an output size variable. - if (!max_index_device_) { - max_index_device_ = make_unified(MemoryType::kDevice); - } - max_index_device_.setZero(); - - // Call the kernel. - int dim_block = block_indices.size(); - dim3 dim_threads(kVoxelsPerSide, kVoxelsPerSide, kVoxelsPerSide); - - copyPointcloudToPCL<<>>( - gpu_layer_view.getHash().impl_, block_indices_device_.data(), - block_indices.size(), num_voxels, aabb_intersect, layer.block_size(), - layer_pointcloud_device_.data(), max_index_device_.get()); - checkCudaErrors(cudaStreamSynchronize(cuda_stream_)); - checkCudaErrors(cudaPeekAtLastError()); - - // Copy the pointcloud out. - max_index_host_ = max_index_device_.clone(MemoryType::kHost); - - size_t output_size = sizeof(PclPointXYZI) * *max_index_host_; - pointcloud->data.resize(output_size); - // Copy over all the points. - cudaMemcpy(pointcloud->data.data(), layer_pointcloud_device_.data(), - output_size, cudaMemcpyDeviceToHost); - - // Fill the other fields in the pointcloud message. - pointcloud->height = 1; - pointcloud->width = *max_index_host_; - pointcloud->point_step = sizeof(PclPointXYZI); - pointcloud->row_step = output_size; - - // Populate the fields. - sensor_msgs::msg::PointField point_field; - point_field.name = "x"; - point_field.datatype = sensor_msgs::msg::PointField::FLOAT32; - point_field.offset = 0; - point_field.count = 1; - - pointcloud->fields.push_back(point_field); - point_field.name = "y"; - point_field.offset += sizeof(float); - pointcloud->fields.push_back(point_field); - point_field.name = "z"; - point_field.offset += sizeof(float); - pointcloud->fields.push_back(point_field); - point_field.name = "intensity"; - point_field.offset += sizeof(float); - pointcloud->fields.push_back(point_field); -} - -// Template specializations. -template void RosConverter::convertLayerInAABBToPCLCuda( - const VoxelBlockLayer& layer, const AxisAlignedBoundingBox& aabb, - sensor_msgs::msg::PointCloud2* pointcloud); - -template void RosConverter::convertLayerInAABBToPCLCuda( - const VoxelBlockLayer& layer, const AxisAlignedBoundingBox& aabb, - sensor_msgs::msg::PointCloud2* pointcloud); - -void RosConverter::meshBlockMessageFromMeshBlock( - const MeshBlock& mesh_block, nvblox_msgs::msg::MeshBlock* mesh_block_msg) { - CHECK_NOTNULL(mesh_block_msg); - - size_t num_vertices = mesh_block.vertices.size(); - - mesh_block_msg->vertices.resize(num_vertices); - mesh_block_msg->normals.resize(num_vertices); - mesh_block_msg->colors.resize(mesh_block.colors.size()); - mesh_block_msg->triangles.resize(mesh_block.triangles.size()); - - std::vector vertices = mesh_block.getVertexVectorOnCPU(); - std::vector normals = mesh_block.getNormalVectorOnCPU(); - std::vector colors = mesh_block.getColorVectorOnCPU(); - - // Copy over vertices and normals. - for (size_t i = 0; i < num_vertices; i++) { - mesh_block_msg->vertices[i] = point32MessageFromVector(vertices[i]); - mesh_block_msg->normals[i] = point32MessageFromVector(normals[i]); - } - - // Copy over colors if available. - for (size_t i = 0; i < mesh_block.colors.size(); i++) { - mesh_block_msg->colors[i] = colorMessageFromColor(colors[i]); - } - - // Copying over triangles is thankfully easy. - mesh_block_msg->triangles = mesh_block.getTriangleVectorOnCPU(); -} - -struct DivideBy1000 : public thrust::unary_function { - __host__ __device__ float operator()(const uint16_t& in) { - return static_cast(in) / 1000.0f; - } -}; - -// Convert image to depth frame object -bool RosConverter::depthImageFromImageMessage( - const sensor_msgs::msg::Image::ConstSharedPtr& image_msg, - DepthImage* depth_image) { - CHECK_NOTNULL(depth_image); - // If the image is a float, we can just copy it over directly. - // If the image is int16, we need to divide by 1000 to get the correct - // format for us. - - // First check if we actually have a valid image here. - if (image_msg->encoding != "32FC1" && image_msg->encoding != "16UC1") { - return false; - } - - // Fill it in. How this is done depends on what the image encoding is. - if (image_msg->encoding == "32FC1") { - // Float to float, so this should be a straight-up copy. :) - depth_image->populateFromBuffer( - image_msg->height, image_msg->width, - reinterpret_cast(&image_msg->data[0])); - } else if (image_msg->encoding == "16UC1") { - // Then we have to just go byte-by-byte and convert this. This is a massive - // pain and slow. We need to find a better way to do this; on GPU or - // through openCV. - const uint16_t* char_depth_buffer = - reinterpret_cast(&image_msg->data[0]); - const int numel = image_msg->height * image_msg->width; - - bool kUseCuda = false; - if (kUseCuda) { - // Make sure there's enough output space. - if (depth_image->numel() < numel) { - *depth_image = DepthImage(image_msg->height, image_msg->width, - MemoryType::kDevice); - } - - // Now just thrust it. - thrust::transform(char_depth_buffer, char_depth_buffer + numel, - depth_image->dataPtr(), DivideBy1000()); - } else { - std::vector float_depth_buffer(numel); - for (int i = 0; i < numel; i++) { - float_depth_buffer[i] = - static_cast(char_depth_buffer[i]) / 1000.0f; - } - depth_image->populateFromBuffer(image_msg->height, image_msg->width, - float_depth_buffer.data(), - MemoryType::kDevice); - } - } - - return true; -} - -__global__ void populateSliceFromLayerKernel( - Index3DDeviceHashMapType block_hash, AxisAlignedBoundingBox aabb, - float block_size, float* image, int rows, int cols, float z_slice_height, - float resolution, float unobserved_value) { - const float voxel_size = block_size / EsdfBlock::kVoxelsPerSide; - const int pixel_col = blockIdx.x * blockDim.x + threadIdx.x; - const int pixel_row = blockIdx.y * blockDim.y + threadIdx.y; - - if (pixel_col >= cols || pixel_row >= rows) { - return; - } - - // Figure out where this pixel should map to. - Vector3f voxel_position(aabb.min().x() + resolution * pixel_col, - aabb.min().y() + resolution * pixel_row, - z_slice_height); - - Index3D block_index, voxel_index; - - getBlockAndVoxelIndexFromPositionInLayer(block_size, voxel_position, - &block_index, &voxel_index); - - // Get the relevant block. - EsdfBlock* block_ptr = nullptr; - auto it = block_hash.find(block_index); - if (it != block_hash.end()) { - block_ptr = it->second; - } else { - image::access(pixel_row, pixel_col, cols, image) = unobserved_value; - return; - } - - // Get the relevant pixel. - const EsdfVoxel* voxel = - &block_ptr->voxels[voxel_index.x()][voxel_index.y()][voxel_index.z()]; - float distance = unobserved_value; - if (voxel->observed) { - distance = voxel_size * std::sqrt(voxel->squared_distance_vox); - if (voxel->is_inside) { - distance = -distance; - } - } - image::access(pixel_row, pixel_col, cols, image) = distance; -} - -void RosConverter::populateSliceFromLayer(const EsdfLayer& layer, - const AxisAlignedBoundingBox& aabb, - float z_slice_height, - float resolution, - float unobserved_value, - Image* image) { - if (image->numel() <= 0) { - return; - } - const float voxel_size = layer.voxel_size(); - - // Create a GPU hash of the ESDF. - GPULayerView gpu_layer_view = layer.getGpuLayerView(); - - // Pass in the GPU hash and AABB and let the kernel figure it out. - constexpr int kThreadDim = 16; - const int rounded_rows = static_cast( - std::ceil(image->rows() / static_cast(kThreadDim))); - const int rounded_cols = static_cast( - std::ceil(image->cols() / static_cast(kThreadDim))); - dim3 block_dim(rounded_cols, rounded_rows); - dim3 thread_dim(kThreadDim, kThreadDim); - - populateSliceFromLayerKernel<<>>( - gpu_layer_view.getHash().impl_, aabb, layer.block_size(), - image->dataPtr(), image->rows(), image->cols(), z_slice_height, - resolution, unobserved_value); - checkCudaErrors(cudaStreamSynchronize(cuda_stream_)); - checkCudaErrors(cudaPeekAtLastError()); -} - -__global__ void depthImageFromPointcloudKernel( - const Vector3f* pointcloud, // NOLINT - const Lidar lidar, // NOLINT - const int num_bytes_between_points, // NOLINT - const int num_points, // NOLINT - float* depth_image) { - const int idx = blockIdx.x * blockDim.x + threadIdx.x; - - if (idx >= num_points) { - return; - } - - // Read a point from global memory - const Vector3f point = pointcloud[idx]; - - if (isnan(point.x()) || isnan(point.y()) || isnan(point.z())) { - return; - } - - // Project - Index2D u_C_int; - if (!lidar.project(point, &u_C_int)) { - return; - } - - // Write the depth to the image - // NOTE(alexmillane): It's possible multiple points project to the same pixel. - // Firstly, writes should be atomic, so we're fine in that respect. The last - // projection to write "wins" so we basically end up with a random value, of - // the values that project to this pixel. - image::access(u_C_int.y(), u_C_int.x(), lidar.num_azimuth_divisions(), - depth_image) = lidar.getDepth(point); -} - -void RosConverter::depthImageFromPointcloudGPU( - const sensor_msgs::msg::PointCloud2::ConstSharedPtr& pointcloud, - const Lidar& lidar, DepthImage* depth_image_ptr) { - CHECK(depth_image_ptr->memory_type() == MemoryType::kDevice || - depth_image_ptr->memory_type() == MemoryType::kUnified); - - // Check output space, and reallocate if required - if ((depth_image_ptr->rows() != lidar.num_elevation_divisions()) || - (depth_image_ptr->cols() != lidar.num_azimuth_divisions())) { - *depth_image_ptr = - DepthImage(lidar.num_elevation_divisions(), - lidar.num_azimuth_divisions(), MemoryType::kDevice); - } - - // Set the entire image to 0. - depth_image_ptr->setZero(); - - // Get a pointer to the pointcloud - sensor_msgs::PointCloud2ConstIterator iter_xyz(*pointcloud, "x"); - const float* pointcloud_data_ptr = &(*iter_xyz); - const int num_bytes_between_points = pointcloud->point_step; - const int num_points = pointcloud->width * pointcloud->height; - - // Expand buffers where required - if (lidar.numel() > lidar_pointcloud_host_.size()) { - const int new_size = static_cast(lidar.numel()); - lidar_pointcloud_host_.reserve(new_size); - lidar_pointcloud_device_.reserve(new_size); - } - - // Copy the pointcloud into pinned host memory - lidar_pointcloud_host_.clear(); - for (; iter_xyz != iter_xyz.end(); ++iter_xyz) { - lidar_pointcloud_host_.push_back( - Vector3f(iter_xyz[0], iter_xyz[1], iter_xyz[2])); - } - // Copy the pointcloud to the GPU - lidar_pointcloud_device_ = lidar_pointcloud_host_; - - // Convert to an image on the GPU - constexpr int num_threads_per_block = 256; // because why not - const int num_thread_blocks = lidar.numel() / num_threads_per_block + 1; - depthImageFromPointcloudKernel<<>>( - lidar_pointcloud_device_.data(), lidar, num_bytes_between_points, - lidar.numel(), depth_image_ptr->dataPtr()); - checkCudaErrors(cudaStreamSynchronize(cuda_stream_)); - checkCudaErrors(cudaPeekAtLastError()); -} - -} // namespace nvblox diff --git a/nvblox_ros/src/lib/mapper_initialization.cpp b/nvblox_ros/src/lib/mapper_initialization.cpp new file mode 100644 index 00000000..a2dd102a --- /dev/null +++ b/nvblox_ros/src/lib/mapper_initialization.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "nvblox_ros/mapper_initialization.hpp" + +#include + +#include + +namespace nvblox +{ + +WeightingFunctionType weighting_function_type_from_string( + const std::string & weighting_function_str, rclcpp::Node * node) +{ + if (weighting_function_str == "constant") { + return WeightingFunctionType::kConstantWeight; + } else if (weighting_function_str == "constant_dropoff") { + return WeightingFunctionType::kConstantDropoffWeight; + } else if (weighting_function_str == "inverse_square") { + return WeightingFunctionType::kInverseSquareWeight; + } else if (weighting_function_str == "inverse_square_dropoff") { + return WeightingFunctionType::kInverseSquareDropoffWeight; + } else { + RCLCPP_WARN_STREAM( + node->get_logger(), + "Requested weighting function: \"" << + weighting_function_str << + "\" not recognized. Defaulting to: " << + kDefaultWeightingFunctionType); + return kDefaultWeightingFunctionType; + } +} + +void declareMapperParameters( + const std::string & mapper_name, + rclcpp::Node * node) +{ + // Declare parameters + // NOTE(alexmillane): We have to use the insane syntax in + // declareParameterWithoutDefault() order to avoid passing a default value to + // declare_parameter(). The problem with using a default value is that when we + // later call node->get_parameter() in initialize_mapper(), it will always + // return true, even if the user has not set the parameter. To me this appears + // like a bug in ROS 2. After hours of trying, the insane combination of + // syntaxes in this function and initialize_mapper() was the only this I found + // that worked. UPDATE(helzor): Apparently this issue is fixed in later ROS 2 + // versions. + declareParameterWithoutDefault( + mapper_name + ".projective_integrator_max_integration_distance_m", node); + declareParameterWithoutDefault( + mapper_name + ".lidar_projective_integrator_max_integration_distance_m", + node); + declareParameterWithoutDefault( + mapper_name + ".projective_integrator_truncation_distance_vox", node); + declareParameterWithoutDefault( + mapper_name + ".tsdf_integrator_max_weight", node); + declareParameterWithoutDefault( + mapper_name + ".free_region_occupancy_probability", node); + declareParameterWithoutDefault( + mapper_name + ".occupied_region_occupancy_probability", node); + declareParameterWithoutDefault( + mapper_name + ".unobserved_region_occupancy_probability", node); + declareParameterWithoutDefault( + mapper_name + ".occupied_region_half_width_m", node); + declareParameterWithoutDefault( + mapper_name + ".free_region_decay_probability", node); + declareParameterWithoutDefault( + mapper_name + ".occupied_region_decay_probability", node); + declareParameterWithoutDefault( + mapper_name + ".mesh_integrator_min_weight", node); + declareParameterWithoutDefault( + mapper_name + ".mesh_integrator_weld_vertices", node); + declareParameterWithoutDefault( + mapper_name + ".color_integrator_max_integration_distance_m", node); + declareParameterWithoutDefault( + mapper_name + ".esdf_integrator_min_weight", node); + declareParameterWithoutDefault( + mapper_name + ".esdf_integrator_max_site_distance_vox", node); + declareParameterWithoutDefault( + mapper_name + ".esdf_integrator_max_distance_m", node); + declareParameterWithoutDefault( + mapper_name + ".weighting_mode", + node); +} + +void initializeMapper( + const std::string & mapper_name, Mapper * mapper_ptr, + rclcpp::Node * node) +{ + RCLCPP_INFO_STREAM(node->get_logger(), "Initialize Mapper:"); + + // tsdf or occupancy integrator + set_mapper_parameter( + mapper_name, "projective_integrator_max_integration_distance_m", + [&](auto value) { + mapper_ptr->tsdf_integrator().max_integration_distance_m(value); + mapper_ptr->occupancy_integrator().max_integration_distance_m(value); + }, + node); + set_mapper_parameter( + mapper_name, "lidar_projective_integrator_max_integration_distance_m", + [&](auto value) { + mapper_ptr->lidar_tsdf_integrator().max_integration_distance_m(value); + mapper_ptr->lidar_occupancy_integrator().max_integration_distance_m( + value); + }, + node); + set_mapper_parameter( + mapper_name, "projective_integrator_truncation_distance_vox", + [&](auto value) { + mapper_ptr->tsdf_integrator().truncation_distance_vox(value); + mapper_ptr->occupancy_integrator().truncation_distance_vox(value); + mapper_ptr->lidar_tsdf_integrator().truncation_distance_vox(value); + mapper_ptr->lidar_occupancy_integrator().truncation_distance_vox(value); + }, + node); + + // tsdf and color integrator + // NOTE(alexmillane): Currently weighting mode does not affect the occupancy + // integrator. + set_mapper_parameter( + mapper_name, "weighting_mode", + [&](auto value) { + const WeightingFunctionType weight_mode = + weighting_function_type_from_string(value, node); + mapper_ptr->tsdf_integrator().weighting_function_type(weight_mode); + mapper_ptr->color_integrator().weighting_function_type(weight_mode); + }, + node); + + // tsdf integrator + set_mapper_parameter( + mapper_name, "tsdf_integrator_max_weight", + [&](auto value) { + mapper_ptr->tsdf_integrator().max_weight(value); + mapper_ptr->lidar_tsdf_integrator().max_weight(value); + }, + node); + + // occupancy integrator + set_mapper_parameter( + mapper_name, "free_region_occupancy_probability", + [&](auto value) { + mapper_ptr->occupancy_integrator().free_region_occupancy_probability( + value); + mapper_ptr->lidar_occupancy_integrator() + .free_region_occupancy_probability(value); + }, + node); + set_mapper_parameter( + mapper_name, "occupied_region_occupancy_probability", + [&](auto value) { + mapper_ptr->occupancy_integrator() + .occupied_region_occupancy_probability(value); + mapper_ptr->lidar_occupancy_integrator() + .occupied_region_occupancy_probability(value); + }, + node); + set_mapper_parameter( + mapper_name, "unobserved_region_occupancy_probability", + [&](auto value) { + mapper_ptr->occupancy_integrator() + .unobserved_region_occupancy_probability(value); + mapper_ptr->lidar_occupancy_integrator() + .unobserved_region_occupancy_probability(value); + }, + node); + set_mapper_parameter( + mapper_name, "occupied_region_half_width_m", + [&](auto value) { + mapper_ptr->occupancy_integrator().occupied_region_half_width_m(value); + mapper_ptr->lidar_occupancy_integrator().occupied_region_half_width_m( + value); + }, + node); + + // occupancy decay + set_mapper_parameter( + mapper_name, "free_region_decay_probability", + [&](auto value) { + mapper_ptr->occupancy_decay_integrator().free_region_decay_probability( + value); + }, + node); + set_mapper_parameter( + mapper_name, "occupied_region_decay_probability", + [&](auto value) { + mapper_ptr->occupancy_decay_integrator() + .occupied_region_decay_probability(value); + }, + node); + + // mesh integrator + set_mapper_parameter( + mapper_name, "mesh_integrator_min_weight", + [&](auto value) {mapper_ptr->mesh_integrator().min_weight(value);}, + node); + set_mapper_parameter( + mapper_name, "mesh_integrator_weld_vertices", + [&](auto value) {mapper_ptr->mesh_integrator().weld_vertices(value);}, + node); + + // color integrator + set_mapper_parameter( + mapper_name, "color_integrator_max_integration_distance_m", + [&](auto value) { + mapper_ptr->color_integrator().max_integration_distance_m(value); + }, + node); + + // esdf integrator + set_mapper_parameter( + mapper_name, "esdf_integrator_min_weight", + [&](auto value) {mapper_ptr->esdf_integrator().min_weight(value);}, + node); + set_mapper_parameter( + mapper_name, "esdf_integrator_max_site_distance_vox", + [&](auto value) { + mapper_ptr->esdf_integrator().max_site_distance_vox(value); + }, + node); + set_mapper_parameter( + mapper_name, "esdf_integrator_max_distance_m", + [&](auto value) {mapper_ptr->esdf_integrator().max_distance_m(value);}, + node); +} + +} // namespace nvblox diff --git a/nvblox_ros/src/lib/nvblox_human_node.cpp b/nvblox_ros/src/lib/nvblox_human_node.cpp new file mode 100644 index 00000000..6b236681 --- /dev/null +++ b/nvblox_ros/src/lib/nvblox_human_node.cpp @@ -0,0 +1,606 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "nvblox_ros/nvblox_human_node.hpp" + +#include + +#include +#include +#include +#include + +#include +#include + +#include + + +namespace nvblox +{ + +NvbloxHumanNode::NvbloxHumanNode(const rclcpp::NodeOptions & options) +: NvbloxNode(options, "nvblox_human_node"), + human_pointcloud_C_device_(MemoryType::kDevice), + human_pointcloud_L_device_(MemoryType::kDevice) +{ + RCLCPP_INFO_STREAM(get_logger(), "NvbloxHumanNode::NvbloxHumanNode()"); + + // Get parameters specific to the human node. + getParameters(); + + // Initialize the MultiMapper and overwrite the base-class node's Mapper. + initializeMultiMapper(); + + // Subscribe to topics + // NOTE(alexmillane): This function modifies to base class subscriptions to + // add synchronization with segmentation masks. + subscribeToTopics(); + + // Add additional timers and publish more topics + setupTimers(); + advertiseTopics(); +} + +void NvbloxHumanNode::getParameters() +{ + human_occupancy_decay_rate_hz_ = declare_parameter( + "human_occupancy_decay_rate_hz", human_occupancy_decay_rate_hz_); + human_esdf_update_rate_hz_ = declare_parameter( + "human_esdf_update_rate_hz", human_esdf_update_rate_hz_); +} + +void NvbloxHumanNode::initializeMultiMapper() +{ + // Initialize the multi mapper. Composed of: + // - masked occupancy mapper for humans + // - unmasked mapper for static objects (with configurable projective layer + // type) + constexpr ProjectiveLayerType kDynamicLayerType = + ProjectiveLayerType::kOccupancy; + multi_mapper_ = std::make_shared( + voxel_size_, MemoryType::kDevice, kDynamicLayerType, + static_projective_layer_type_); + + // Over-write the base-class node's mapper with the unmasked mapper of + // the multi mapper. We also have to initialize it from ROS 2 params by + // calling initializeMapper() (again) (it its also called in the base + // constructor, on the now-deleted Mapper). + mapper_ = multi_mapper_.get()->unmasked_mapper(); + initializeMapper("mapper", mapper_.get(), this); + // Set to an invalid depth to ignore human pixels in the unmasked mapper + // during integration. + multi_mapper_->setDepthUnmaskedImageInvalidPixel(-1.f); + + // Initialize the human mapper (masked mapper of the multi mapper) + const std::string mapper_name = "human_mapper"; + human_mapper_ = multi_mapper_.get()->masked_mapper(); + // Human mapper params have not been declared yet + declareMapperParameters(mapper_name, this); + initializeMapper(mapper_name, human_mapper_.get(), this); + // Set to a distance bigger than the max. integration distance to not include + // non human pixels on the human mapper, but clear along the projection. + // TODO(remosteiner): Think of a better way to do this. + // Currently this leads to blocks being allocated even behind solid obstacles. + multi_mapper_->setDepthMaskedImageInvalidPixel( + human_mapper_->occupancy_integrator().max_integration_distance_m() * 2.f); +} + +void NvbloxHumanNode::subscribeToTopics() +{ + RCLCPP_INFO_STREAM(get_logger(), "NvbloxHumanNode::subscribeToTopics()"); + + // Increased queue size compared to the NvbloxNode, + // because of bigger delay comming from segmentation. + constexpr int kQueueSize = 40; + + // Unsubscribe from base-class synchronized topics. + // We redo synchronization below. + NvbloxNode::timesync_depth_.reset(); + NvbloxNode::timesync_color_.reset(); + + // Subscribe to segmentation masks + segmentation_mask_sub_.subscribe( + this, "mask/image", + parseQosString(color_qos_str_)); + segmentation_camera_info_sub_.subscribe(this, "mask/camera_info"); + + if (use_depth_) { + // Unsubscribe from the depth topic in nvblox_node + timesync_depth_.reset(); + // Subscribe to depth + mask + cam_infos + timesync_depth_mask_ = std::make_shared>( + mask_time_policy_t(kQueueSize), depth_sub_, depth_camera_info_sub_, + segmentation_mask_sub_, segmentation_camera_info_sub_); + timesync_depth_mask_->registerCallback( + std::bind( + &NvbloxHumanNode::depthPlusMaskImageCallback, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); + } + + if (use_color_) { + // Unsubscribe from the color topic in nvblox_node + timesync_color_.reset(); + // Subscribe to color + mask + cam_infos + timesync_color_mask_ = std::make_shared>( + mask_time_policy_t(kQueueSize), color_sub_, color_camera_info_sub_, + segmentation_mask_sub_, segmentation_camera_info_sub_); + timesync_color_mask_->registerCallback( + std::bind( + &NvbloxHumanNode::colorPlusMaskImageCallback, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); + } +} + +void NvbloxHumanNode::advertiseTopics() +{ + // Add some stuff + human_pointcloud_publisher_ = + create_publisher("~/human_pointcloud", 1); + human_voxels_publisher_ = + create_publisher("~/human_voxels", 1); + human_occupancy_publisher_ = + create_publisher("~/human_occupancy", 1); + human_esdf_pointcloud_publisher_ = + create_publisher( + "~/human_esdf_pointcloud", + 1); + combined_esdf_pointcloud_publisher_ = + create_publisher( + "~/combined_esdf_pointcloud", 1); + human_map_slice_publisher_ = + create_publisher( + "~/human_map_slice", + 1); + combined_map_slice_publisher_ = + create_publisher( + "~/combined_map_slice", 1); + depth_frame_overlay_publisher_ = + create_publisher("~/depth_frame_overlay", 1); + color_frame_overlay_publisher_ = + create_publisher("~/color_frame_overlay", 1); +} + +void NvbloxHumanNode::setupTimers() +{ + human_occupancy_decay_timer_ = create_wall_timer( + std::chrono::duration(1.0 / human_occupancy_decay_rate_hz_), + std::bind(&NvbloxHumanNode::decayHumanOccupancy, this), + group_processing_); + human_esdf_processing_timer_ = create_wall_timer( + std::chrono::duration(1.0 / human_esdf_update_rate_hz_), + std::bind(&NvbloxHumanNode::processHumanEsdf, this), group_processing_); +} + +void NvbloxHumanNode::depthPlusMaskImageCallback( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg, + const sensor_msgs::msg::Image::ConstSharedPtr & mask_img_ptr, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & mask_camera_info_msg) +{ + printMessageArrivalStatistics( + *depth_img_ptr, "Depth plus Mask Statistics", + &depth_frame_statistics_); + pushMessageOntoQueue( + std::make_tuple( + depth_img_ptr, camera_info_msg, mask_img_ptr, + mask_camera_info_msg), + &depth_mask_image_queue_, &depth_mask_queue_mutex_); +} + +void NvbloxHumanNode::colorPlusMaskImageCallback( + const sensor_msgs::msg::Image::ConstSharedPtr & color_img_ptr, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg, + const sensor_msgs::msg::Image::ConstSharedPtr & mask_img_ptr, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & mask_camera_info_msg) +{ + printMessageArrivalStatistics( + *color_img_ptr, "Color plus Mask Statistics", + &rgb_frame_statistics_); + pushMessageOntoQueue( + std::make_tuple( + color_img_ptr, camera_info_msg, mask_img_ptr, + mask_camera_info_msg), + &color_mask_image_queue_, &color_mask_queue_mutex_); +} + +void NvbloxHumanNode::processDepthQueue() +{ + auto message_ready = [this](const ImageSegmentationMaskMsgTuple & msg) { + return this->canTransform(std::get<0>(msg)->header) && + this->canTransform(std::get<2>(msg)->header); + }; + processMessageQueue( + &depth_mask_image_queue_, // NOLINT + &depth_mask_queue_mutex_, // NOLINT + message_ready, // NOLINT + std::bind( + &NvbloxHumanNode::processDepthImage, this, + std::placeholders::_1)); + + limitQueueSizeByDeletingOldestMessages( + maximum_sensor_message_queue_length_, + "depth_mask", &depth_mask_image_queue_, + &depth_mask_queue_mutex_); +} + +void NvbloxHumanNode::processColorQueue() +{ + auto message_ready = [this](const ImageSegmentationMaskMsgTuple & msg) { + return this->canTransform(std::get<0>(msg)->header) && + this->canTransform(std::get<2>(msg)->header); + }; + processMessageQueue( + &color_mask_image_queue_, // NOLINT + &color_mask_queue_mutex_, // NOLINT + message_ready, // NOLINT + std::bind( + &NvbloxHumanNode::processColorImage, this, + std::placeholders::_1)); + + limitQueueSizeByDeletingOldestMessages( + maximum_sensor_message_queue_length_, + "color_mask", &color_mask_image_queue_, + &color_mask_queue_mutex_); +} + +bool NvbloxHumanNode::processDepthImage( + const ImageSegmentationMaskMsgTuple & depth_mask_msg) +{ + timing::Timer ros_total_timer("ros/total"); + timing::Timer ros_depth_timer("ros/depth"); + timing::Timer transform_timer("ros/depth/transform"); + + // Message parts + const sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr = + std::get<0>(depth_mask_msg); + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & depth_camera_info_msg = + std::get<1>(depth_mask_msg); + const sensor_msgs::msg::Image::ConstSharedPtr & mask_img_ptr = + std::get<2>(depth_mask_msg); + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & mask_camera_info_msg = + std::get<3>(depth_mask_msg); + + // Check that we're not updating more quickly than we should. + if (isUpdateTooFrequent( + depth_img_ptr->header.stamp, last_depth_update_time_, + max_depth_update_hz_)) + { + return true; + } + last_depth_update_time_ = depth_img_ptr->header.stamp; + + // Get the TF for BOTH images. + const std::string depth_img_frame = depth_img_ptr->header.frame_id; + if (!transformer_.lookupTransformToGlobalFrame( + depth_img_frame, depth_img_ptr->header.stamp, &T_L_C_depth_)) + { + return false; + } + Transform T_L_C_mask; + const std::string mask_img_frame = mask_img_ptr->header.frame_id; + if (!transformer_.lookupTransformToGlobalFrame( + mask_img_frame, mask_img_ptr->header.stamp, &T_L_C_mask)) + { + return false; + } + Transform T_CM_CD = T_L_C_mask.inverse() * T_L_C_depth_; + if (is_realsense_data_) { + // There is an unresolved issue with the ROS realsense wrapper. + // Until it is fixed, the below inverse needs to be applied. + // https://github.com/IntelRealSense/realsense-ros/issues/2500 + T_CM_CD = T_CM_CD.inverse(); + } + transform_timer.Stop(); + + timing::Timer conversions_timer("ros/depth/conversions"); + // Convert camera info message to camera object. + depth_camera_ = conversions::cameraFromMessage(*depth_camera_info_msg); + const Camera mask_camera = + conversions::cameraFromMessage(*mask_camera_info_msg); + + // Convert the depth image. + if (!conversions::depthImageFromImageMessage(depth_img_ptr, &depth_image_) || + !conversions::monoImageFromImageMessage(mask_img_ptr, &mask_image_)) + { + RCLCPP_ERROR(get_logger(), "Failed to transform depth or mask image."); + return false; + } + conversions_timer.Stop(); + + // Integrate + timing::Timer integration_timer("ros/depth/integrate"); + multi_mapper_->integrateDepth( + depth_image_, mask_image_, T_L_C_depth_, + T_CM_CD, depth_camera_, mask_camera); + integration_timer.Stop(); + + timing::Timer overlay_timer("ros/depth/output/human_overlay"); + if (depth_frame_overlay_publisher_->get_subscription_count() > 0) { + sensor_msgs::msg::Image img_msg; + const ColorImage & depth_overlay = + multi_mapper_->getLastDepthFrameMaskOverlay(); + conversions::imageMessageFromColorImage( + depth_overlay, depth_img_frame, + &img_msg); + depth_frame_overlay_publisher_->publish(img_msg); + } + + return true; +} + +bool NvbloxHumanNode::processColorImage( + const ImageSegmentationMaskMsgTuple & color_mask_msg) +{ + timing::Timer ros_total_timer("ros/total"); + timing::Timer ros_color_timer("ros/color"); + timing::Timer transform_timer("ros/color/transform"); + + // Message parts + const sensor_msgs::msg::Image::ConstSharedPtr & color_img_ptr = + std::get<0>(color_mask_msg); + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg = + std::get<1>(color_mask_msg); + const sensor_msgs::msg::Image::ConstSharedPtr & mask_img_ptr = + std::get<2>(color_mask_msg); + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & mask_camera_info_msg = + std::get<3>(color_mask_msg); + + // Check that we're not updating more quickly than we should. + if (isUpdateTooFrequent( + color_img_ptr->header.stamp, last_color_update_time_, + max_color_update_hz_)) + { + return true; + } + last_color_update_time_ = color_img_ptr->header.stamp; + + // Get the TF for BOTH images. + Transform T_L_C; + const std::string color_img_frame = color_img_ptr->header.frame_id; + if (!transformer_.lookupTransformToGlobalFrame( + color_img_frame, color_img_ptr->header.stamp, &T_L_C)) + { + return false; + } + Transform T_L_C_mask; + const std::string mask_img_frame = mask_img_ptr->header.frame_id; + if (!transformer_.lookupTransformToGlobalFrame( + mask_img_frame, mask_img_ptr->header.stamp, &T_L_C_mask)) + { + return false; + } + transform_timer.Stop(); + + timing::Timer conversions_timer("ros/color/conversions"); + // Convert camera info message to camera object. + const Camera color_camera = conversions::cameraFromMessage(*camera_info_msg); + const Camera mask_camera = + conversions::cameraFromMessage(*mask_camera_info_msg); + if (!camerasAreEquivalent(color_camera, mask_camera, T_L_C, T_L_C_mask)) { + RCLCPP_ERROR( + get_logger(), + "Color and mask image are not coming from the same camera or frame."); + return false; + } + + // Convert the color image. + if (!conversions::colorImageFromImageMessage(color_img_ptr, &color_image_) || + !conversions::monoImageFromImageMessage(mask_img_ptr, &mask_image_)) + { + RCLCPP_ERROR(get_logger(), "Failed to transform color or mask image."); + return false; + } + conversions_timer.Stop(); + + // Integrate + timing::Timer integration_timer("ros/color/integrate"); + multi_mapper_->integrateColor(color_image_, mask_image_, T_L_C, color_camera); + integration_timer.Stop(); + + timing::Timer overlay_timer("ros/color/output/human_overlay"); + if (color_frame_overlay_publisher_->get_subscription_count() > 0) { + sensor_msgs::msg::Image img_msg; + const ColorImage & color_overlay = + multi_mapper_->getLastColorFrameMaskOverlay(); + conversions::imageMessageFromColorImage( + color_overlay, color_img_frame, + &img_msg); + color_frame_overlay_publisher_->publish(img_msg); + } + + return true; +} + +void NvbloxHumanNode::processHumanEsdf() +{ + timing::Timer ros_total_timer("ros/total"); + timing::Timer ros_human_total_timer("ros/humans"); + + if (last_depth_update_time_.seconds() <= 0.f) { + return; // no data yet. + } + publishHumanDebugOutput(); + + // Process the human esdf layer. + timing::Timer esdf_integration_timer("ros/humans/esdf/integrate"); + std::vector updated_blocks; + if (esdf_2d_) { + updated_blocks = human_mapper_->updateEsdfSlice( + esdf_2d_min_height_, esdf_2d_max_height_, esdf_slice_height_); + } else { + updated_blocks = human_mapper_->updateEsdf(); + } + esdf_integration_timer.Stop(); + + if (updated_blocks.empty()) { + return; + } + + timing::Timer esdf_output_timer("ros/humans/esdf/output"); + + // Check if anyone wants any human slice + if (esdf_distance_slice_ && + (human_esdf_pointcloud_publisher_->get_subscription_count() > 0) || + (human_map_slice_publisher_->get_subscription_count() > 0)) + { + // Get the slice as an image + timing::Timer esdf_slice_compute_timer("ros/humans/esdf/output/compute"); + AxisAlignedBoundingBox aabb; + Image map_slice_image; + esdf_slice_converter_.distanceMapSliceImageFromLayer( + human_mapper_->esdf_layer(), esdf_slice_height_, &map_slice_image, &aabb); + esdf_slice_compute_timer.Stop(); + + // Human slice pointcloud (for visualization) + if (human_esdf_pointcloud_publisher_->get_subscription_count() > 0) { + timing::Timer esdf_output_human_pointcloud_timer("ros/humans/esdf/output/pointcloud"); + sensor_msgs::msg::PointCloud2 pointcloud_msg; + esdf_slice_converter_.sliceImageToPointcloud( + map_slice_image, aabb, esdf_slice_height_, + human_mapper_->esdf_layer().voxel_size(), &pointcloud_msg); + pointcloud_msg.header.frame_id = global_frame_; + pointcloud_msg.header.stamp = get_clock()->now(); + human_esdf_pointcloud_publisher_->publish(pointcloud_msg); + } + + // Human slice (for navigation) + if (human_map_slice_publisher_->get_subscription_count() > 0) { + timing::Timer esdf_output_human_slice_timer("ros/humans/esdf/output/slice"); + nvblox_msgs::msg::DistanceMapSlice map_slice_msg; + esdf_slice_converter_.distanceMapSliceImageToMsg( + map_slice_image, aabb, esdf_slice_height_, + human_mapper_->voxel_size_m(), &map_slice_msg); + map_slice_msg.header.frame_id = global_frame_; + map_slice_msg.header.stamp = get_clock()->now(); + human_map_slice_publisher_->publish(map_slice_msg); + } + } + + // Check if anyone wants any human+statics slice + if (esdf_distance_slice_ && + (combined_esdf_pointcloud_publisher_->get_subscription_count() > + 0) || + (combined_map_slice_publisher_->get_subscription_count() > 0)) + { + // Combined slice + timing::Timer esdf_slice_compute_timer("ros/humans/esdf/output/combined/compute"); + Image combined_slice_image; + AxisAlignedBoundingBox combined_aabb; + esdf_slice_converter_.distanceMapSliceFromLayers( + mapper_->esdf_layer(), human_mapper_->esdf_layer(), esdf_slice_height_, + &combined_slice_image, &combined_aabb); + esdf_slice_compute_timer.Stop(); + + // Human+Static slice pointcloud (for visualization) + if (combined_esdf_pointcloud_publisher_->get_subscription_count() > 0) { + timing::Timer esdf_output_human_pointcloud_timer( + "ros/humans/esdf/output/combined/pointcloud"); + sensor_msgs::msg::PointCloud2 pointcloud_msg; + esdf_slice_converter_.sliceImageToPointcloud( + combined_slice_image, combined_aabb, esdf_slice_height_, + human_mapper_->esdf_layer().voxel_size(), &pointcloud_msg); + pointcloud_msg.header.frame_id = global_frame_; + pointcloud_msg.header.stamp = get_clock()->now(); + combined_esdf_pointcloud_publisher_->publish(pointcloud_msg); + } + + // Human+Static slice (for navigation) + if (combined_map_slice_publisher_->get_subscription_count() > 0) { + timing::Timer esdf_output_human_slice_timer("ros/humans/esdf/output/combined/slice"); + nvblox_msgs::msg::DistanceMapSlice map_slice_msg; + esdf_slice_converter_.distanceMapSliceImageToMsg( + combined_slice_image, combined_aabb, esdf_slice_height_, + human_mapper_->voxel_size_m(), &map_slice_msg); + map_slice_msg.header.frame_id = global_frame_; + map_slice_msg.header.stamp = get_clock()->now(); + human_map_slice_publisher_->publish(map_slice_msg); + } + } + esdf_output_timer.Stop(); +} + +void NvbloxHumanNode::decayHumanOccupancy() {human_mapper_->decayOccupancy();} + +void NvbloxHumanNode::publishHumanDebugOutput() +{ + timing::Timer ros_human_debug_timer("ros/humans/output/debug"); + + // Get a human pointcloud + if (human_pointcloud_publisher_->get_subscription_count() + + human_voxels_publisher_->get_subscription_count() > + 0) + { + // Grab the human only image. + const DepthImage & depth_image_only_humans = + multi_mapper_->getLastDepthFrameMasked(); + // Back project + image_back_projector_.backProjectOnGPU( + depth_image_only_humans, depth_camera_, &human_pointcloud_C_device_, + human_mapper_->occupancy_integrator().max_integration_distance_m()); + transformPointcloudOnGPU( + T_L_C_depth_, human_pointcloud_C_device_, + &human_pointcloud_L_device_); + } + + // Publish the human pointcloud + if (human_pointcloud_publisher_->get_subscription_count() > 0) { + // Back-project human depth image to pointcloud and publish. + sensor_msgs::msg::PointCloud2 pointcloud_msg; + pointcloud_converter_.pointcloudMsgFromPointcloud( + human_pointcloud_L_device_, + &pointcloud_msg); + pointcloud_msg.header.frame_id = global_frame_; + pointcloud_msg.header.stamp = get_clock()->now(); + human_pointcloud_publisher_->publish(pointcloud_msg); + } + + // Publish human voxels + if (human_voxels_publisher_->get_subscription_count() > 0) { + // Human voxels from points (in the layer frame) + image_back_projector_.pointcloudToVoxelCentersOnGPU( + human_pointcloud_L_device_, voxel_size_, + &human_voxel_centers_L_device_); + // Publish + visualization_msgs::msg::Marker marker_msg; + pointcloud_converter_.pointsToCubesMarkerMsg( + human_voxel_centers_L_device_.points().toVector(), voxel_size_, + Color::Red(), &marker_msg); + marker_msg.header.frame_id = global_frame_; + marker_msg.header.stamp = get_clock()->now(); + human_voxels_publisher_->publish(marker_msg); + } + + // Publish the human occupancy layer + if (human_occupancy_publisher_->get_subscription_count() > 0) { + sensor_msgs::msg::PointCloud2 pointcloud_msg; + layer_converter_.pointcloudMsgFromLayer( + human_mapper_->occupancy_layer(), + &pointcloud_msg); + pointcloud_msg.header.frame_id = global_frame_; + pointcloud_msg.header.stamp = get_clock()->now(); + human_occupancy_publisher_->publish(pointcloud_msg); + } +} + +} // namespace nvblox + +// Register the node as a component +#include "rclcpp_components/register_node_macro.hpp" +RCLCPP_COMPONENTS_REGISTER_NODE(nvblox::NvbloxHumanNode) diff --git a/nvblox_ros/src/lib/nvblox_node.cpp b/nvblox_ros/src/lib/nvblox_node.cpp index 18038174..3de5db4d 100644 --- a/nvblox_ros/src/lib/nvblox_node.cpp +++ b/nvblox_ros/src/lib/nvblox_node.cpp @@ -17,7 +17,6 @@ #include "nvblox_ros/nvblox_node.hpp" -#include #include #include #include @@ -29,30 +28,105 @@ #include #include -#include "nvblox_ros/conversions.hpp" -#include "nvblox_ros/qos.hpp" +#include + #include "nvblox_ros/visualization.hpp" namespace nvblox { -NvbloxNode::NvbloxNode(const rclcpp::NodeOptions & options) -: Node("nvblox_node", options), transformer_(this) +NvbloxNode::NvbloxNode( + const rclcpp::NodeOptions & options, + const std::string & node_name) +: Node(node_name, options), transformer_(this) +{ + // Get parameters first (stuff below depends on parameters) + getParameters(); + + // Set the transformer settings. + transformer_.set_global_frame(global_frame_); + transformer_.set_pose_frame(pose_frame_); + + // Create callback groups, which allows processing to go in parallel with the + // subscriptions. + group_processing_ = + create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); + + // Initialize the mapper (the interface to the underlying nvblox library) + // Note: This needs to be called after getParameters() + // The mapper includes: + // - Map layers + // - Integrators + const std::string mapper_name = "mapper"; + declareMapperParameters(mapper_name, this); + mapper_ = std::make_shared( + voxel_size_, MemoryType::kDevice, + static_projective_layer_type_); + initializeMapper(mapper_name, mapper_.get(), this); + + // Setup interactions with ROS + subscribeToTopics(); + setupTimers(); + advertiseTopics(); + advertiseServices(); + + // Start the message statistics + depth_frame_statistics_.Start(); + rgb_frame_statistics_.Start(); + pointcloud_frame_statistics_.Start(); + + RCLCPP_INFO_STREAM( + get_logger(), "Started up nvblox node in frame " << + global_frame_ << " and voxel size " << + voxel_size_); + + // Set state. + last_depth_update_time_ = rclcpp::Time(0ul, get_clock()->get_clock_type()); + last_color_update_time_ = rclcpp::Time(0ul, get_clock()->get_clock_type()); + last_lidar_update_time_ = rclcpp::Time(0ul, get_clock()->get_clock_type()); +} + +void NvbloxNode::getParameters() { + RCLCPP_INFO_STREAM(get_logger(), "NvbloxNode::getParameters()"); + + const bool is_occupancy = + declare_parameter("use_static_occupancy_layer", false); + if (is_occupancy) { + static_projective_layer_type_ = ProjectiveLayerType::kOccupancy; + RCLCPP_INFO_STREAM( + get_logger(), + "static_projective_layer_type: occupancy " + "(Attention: ESDF and Mesh integration is not yet implemented " + "for occupancy.)"); + } else { + static_projective_layer_type_ = ProjectiveLayerType::kTsdf; + RCLCPP_INFO_STREAM( + get_logger(), + "static_projective_layer_type: TSDF" + " (for occupancy set the use_static_occupancy_layer parameter)"); + } + // Declare & initialize the parameters. voxel_size_ = declare_parameter("voxel_size", voxel_size_); global_frame_ = declare_parameter("global_frame", global_frame_); pose_frame_ = declare_parameter("pose_frame", pose_frame_); - mesh_ = declare_parameter("mesh", mesh_); - esdf_ = declare_parameter("esdf", esdf_); + is_realsense_data_ = + declare_parameter("is_realsense_data", is_realsense_data_); + compute_mesh_ = declare_parameter("compute_mesh", compute_mesh_); + compute_esdf_ = declare_parameter("compute_esdf", compute_esdf_); esdf_2d_ = declare_parameter("esdf_2d", esdf_2d_); - distance_slice_ = declare_parameter("distance_slice", distance_slice_); + esdf_distance_slice_ = + declare_parameter("esdf_distance_slice", esdf_distance_slice_); use_color_ = declare_parameter("use_color", use_color_); use_depth_ = declare_parameter("use_depth", use_depth_); use_lidar_ = declare_parameter("use_lidar", use_lidar_); - slice_height_ = declare_parameter("slice_height", slice_height_); - min_height_ = declare_parameter("min_height", min_height_); - max_height_ = declare_parameter("max_height", max_height_); + esdf_slice_height_ = + declare_parameter("esdf_slice_height", esdf_slice_height_); + esdf_2d_min_height_ = + declare_parameter("esdf_2d_min_height", esdf_2d_min_height_); + esdf_2d_max_height_ = + declare_parameter("esdf_2d_max_height", esdf_2d_max_height_); lidar_width_ = declare_parameter("lidar_width", lidar_width_); lidar_height_ = declare_parameter("lidar_height", lidar_height_); lidar_vertical_fov_rad_ = declare_parameter( @@ -64,42 +138,44 @@ NvbloxNode::NvbloxNode(const rclcpp::NodeOptions & options) slice_visualization_attachment_frame_id_); slice_visualization_side_length_ = declare_parameter( "slice_visualization_side_length", slice_visualization_side_length_); + // Update rates - max_tsdf_update_hz_ = - declare_parameter("max_tsdf_update_hz", max_tsdf_update_hz_); + max_depth_update_hz_ = + declare_parameter("max_depth_update_hz", max_depth_update_hz_); max_color_update_hz_ = declare_parameter("max_color_update_hz", max_color_update_hz_); - max_pointcloud_update_hz_ = declare_parameter( - "max_pointcloud_update_hz", max_pointcloud_update_hz_); - max_mesh_update_hz_ = - declare_parameter("max_mesh_update_hz", max_mesh_update_hz_); - max_esdf_update_hz_ = - declare_parameter("max_esdf_update_hz", max_esdf_update_hz_); + max_lidar_update_hz_ = + declare_parameter("max_lidar_update_hz", max_lidar_update_hz_); + mesh_update_rate_hz_ = + declare_parameter("mesh_update_rate_hz", mesh_update_rate_hz_); + esdf_update_rate_hz_ = + declare_parameter("esdf_update_rate_hz", esdf_update_rate_hz_); + occupancy_publication_rate_hz_ = declare_parameter( + "occupancy_publication_rate_hz", occupancy_publication_rate_hz_); max_poll_rate_hz_ = declare_parameter("max_poll_rate_hz", max_poll_rate_hz_); + maximum_sensor_message_queue_length_ = + declare_parameter( + "maximum_sensor_message_queue_length", + maximum_sensor_message_queue_length_); + // Settings for QoS. - const std::string kDefaultQoS = "SYSTEM_DEFAULT"; - std::string depth_qos = - declare_parameter("depth_qos", kDefaultQoS); - std::string color_qos = - declare_parameter("color_qos", kDefaultQoS); + depth_qos_str_ = declare_parameter("depth_qos", depth_qos_str_); + color_qos_str_ = declare_parameter("color_qos", color_qos_str_); // Settings for map clearing map_clearing_radius_m_ = declare_parameter("map_clearing_radius_m", map_clearing_radius_m_); + map_clearing_frame_id_ = declare_parameter( + "map_clearing_frame_id", map_clearing_frame_id_); + clear_outside_radius_rate_hz_ = declare_parameter( + "clear_outside_radius_rate_hz", clear_outside_radius_rate_hz_); +} - // Set the transformer settings. - transformer_.set_global_frame(global_frame_); - transformer_.set_pose_frame(pose_frame_); - - // Initialize the map - mapper_ = std::make_unique(voxel_size_); - - // Create callback groups, which allows processing to go in parallel with the - // subscriptions. - group_processing_ = - create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); +void NvbloxNode::subscribeToTopics() +{ + RCLCPP_INFO_STREAM(get_logger(), "NvbloxNode::subscribeToTopics()"); constexpr int kQueueSize = 10; @@ -112,7 +188,7 @@ NvbloxNode::NvbloxNode(const rclcpp::NodeOptions & options) if (use_depth_) { // Subscribe to synchronized depth + cam_info topics - depth_sub_.subscribe(this, "depth/image", parseQoSString(depth_qos)); + depth_sub_.subscribe(this, "depth/image", parseQosString(depth_qos_str_)); depth_camera_info_sub_.subscribe(this, "depth/camera_info"); timesync_depth_.reset( @@ -126,7 +202,7 @@ NvbloxNode::NvbloxNode(const rclcpp::NodeOptions & options) } if (use_color_) { // Subscribe to synchronized color + cam_info topics - color_sub_.subscribe(this, "color/image", parseQoSString(color_qos)); + color_sub_.subscribe(this, "color/image", parseQosString(color_qos_str_)); color_camera_info_sub_.subscribe(this, "color/camera_info"); timesync_color_.reset( @@ -159,44 +235,15 @@ NvbloxNode::NvbloxNode(const rclcpp::NodeOptions & options) std::bind( &Transformer::poseCallback, &transformer_, std::placeholders::_1)); +} - // Create a timer for processing incoming messages. - // In case the rates are 0.0 (uncapped), set them to the max poll rate. - double effective_esdf_rate_hz = max_esdf_update_hz_; - if (effective_esdf_rate_hz <= 0.0) { - effective_esdf_rate_hz = max_poll_rate_hz_; - } - double effective_mesh_rate_hz = max_mesh_update_hz_; - if (effective_mesh_rate_hz <= 0.0) { - effective_mesh_rate_hz = max_poll_rate_hz_; - } - if (use_depth_) { - depth_processing_timer_ = create_wall_timer( - std::chrono::duration(1.0 / max_poll_rate_hz_), - std::bind(&NvbloxNode::processDepthQueue, this), group_processing_); - } - if (use_color_) { - color_processing_timer_ = create_wall_timer( - std::chrono::duration(1.0 / max_poll_rate_hz_), - std::bind(&NvbloxNode::processColorQueue, this), group_processing_); - } - if (use_lidar_) { - pointcloud_processing_timer_ = create_wall_timer( - std::chrono::duration(1.0 / max_poll_rate_hz_), - std::bind(&NvbloxNode::processPointcloudQueue, this), - group_processing_); - } - esdf_processing_timer_ = create_wall_timer( - std::chrono::duration(1.0 / effective_esdf_rate_hz), - std::bind(&NvbloxNode::processEsdf, this), group_processing_); - mesh_processing_timer_ = create_wall_timer( - std::chrono::duration(1.0 / effective_mesh_rate_hz), - std::bind(&NvbloxNode::processMesh, this), group_processing_); +void NvbloxNode::advertiseTopics() +{ + RCLCPP_INFO_STREAM(get_logger(), "NvbloxNode::advertiseTopics()"); - // Publishers mesh_publisher_ = create_publisher("~/mesh", 1); - pointcloud_publisher_ = - create_publisher("~/pointcloud", 1); + esdf_pointcloud_publisher_ = + create_publisher("~/esdf_pointcloud", 1); map_slice_publisher_ = create_publisher("~/map_slice", 1); mesh_marker_publisher_ = @@ -205,8 +252,14 @@ NvbloxNode::NvbloxNode(const rclcpp::NodeOptions & options) 1); slice_bounds_publisher_ = create_publisher( "~/map_slice_bounds", 1); + occupancy_publisher_ = + create_publisher("~/occupancy", 1); +} + +void NvbloxNode::advertiseServices() +{ + RCLCPP_INFO_STREAM(get_logger(), "NvbloxNode::advertiseServices()"); - // Services save_ply_service_ = create_service( "~/save_ply", std::bind( @@ -225,473 +278,397 @@ NvbloxNode::NvbloxNode(const rclcpp::NodeOptions & options) &NvbloxNode::loadMap, this, std::placeholders::_1, std::placeholders::_2), rmw_qos_profile_services_default, group_processing_); +} - // Integrator settings. - mapper_->tsdf_integrator().max_integration_distance_m( - declare_parameter( - "tsdf_integrator_max_integration_distance_m", - mapper_->tsdf_integrator().max_integration_distance_m())); - mapper_->lidar_tsdf_integrator().max_integration_distance_m( - declare_parameter( - "lidar_tsdf_integrator_max_integration_distance_m", - mapper_->lidar_tsdf_integrator().max_integration_distance_m())); - // These parameters are shared between the LIDAR and depth integrator or - // things get too complicated. - mapper_->tsdf_integrator().truncation_distance_vox( - declare_parameter( - "tsdf_integrator_truncation_distance_vox", - mapper_->tsdf_integrator().truncation_distance_vox())); - // Copy over to the LIDAR. - mapper_->lidar_tsdf_integrator().truncation_distance_vox( - mapper_->tsdf_integrator().truncation_distance_vox()); - mapper_->tsdf_integrator().max_weight( - declare_parameter( - "tsdf_integrator_max_weight", mapper_->tsdf_integrator().max_weight())); - // Copy over to the LIDAR. - mapper_->lidar_tsdf_integrator().max_weight( - mapper_->tsdf_integrator().max_weight()); - mapper_->mesh_integrator().min_weight( - declare_parameter( - "mesh_integrator_min_weight", mapper_->mesh_integrator().min_weight())); - mapper_->mesh_integrator().weld_vertices( - declare_parameter( - "mesh_integrator_weld_vertices", - mapper_->mesh_integrator().weld_vertices())); - mapper_->color_integrator().max_integration_distance_m( - declare_parameter( - "color_integrator_max_integration_distance_m", - mapper_->color_integrator().max_integration_distance_m())); - mapper_->esdf_integrator().min_weight( - declare_parameter( - "esdf_integrator_min_weight", mapper_->esdf_integrator().min_weight())); - mapper_->esdf_integrator().max_site_distance_vox( - declare_parameter( - "esdf_integrator_max_site_distance_vox", - mapper_->esdf_integrator().max_site_distance_vox())); - mapper_->esdf_integrator().max_distance_m( - declare_parameter( - "esdf_integrator_max_distance_m", - mapper_->esdf_integrator().max_distance_m())); - - // Where to put saved stuff - output_dir_ = declare_parameter("output_dir", output_dir_); - - RCLCPP_INFO_STREAM( - get_logger(), - "Outputting results (as requested) to: " << output_dir_); - - // Start the message statistics - depth_frame_statistics_.Start(); - rgb_frame_statistics_.Start(); +void NvbloxNode::setupTimers() +{ + RCLCPP_INFO_STREAM(get_logger(), "NvbloxNode::setupTimers()"); + if (use_depth_) { + depth_processing_timer_ = create_wall_timer( + std::chrono::duration(1.0 / max_poll_rate_hz_), + std::bind(&NvbloxNode::processDepthQueue, this), group_processing_); + } + if (use_color_) { + color_processing_timer_ = create_wall_timer( + std::chrono::duration(1.0 / max_poll_rate_hz_), + std::bind(&NvbloxNode::processColorQueue, this), group_processing_); + } + if (use_lidar_) { + pointcloud_processing_timer_ = create_wall_timer( + std::chrono::duration(1.0 / max_poll_rate_hz_), + std::bind(&NvbloxNode::processPointcloudQueue, this), + group_processing_); + } + esdf_processing_timer_ = create_wall_timer( + std::chrono::duration(1.0 / esdf_update_rate_hz_), + std::bind(&NvbloxNode::processEsdf, this), group_processing_); + mesh_processing_timer_ = create_wall_timer( + std::chrono::duration(1.0 / mesh_update_rate_hz_), + std::bind(&NvbloxNode::processMesh, this), group_processing_); - RCLCPP_INFO_STREAM( - get_logger(), "Started up nvblox node in frame " << - global_frame_ << " and voxel size " << - voxel_size_); + if (static_projective_layer_type_ == ProjectiveLayerType::kOccupancy) { + occupancy_publishing_timer_ = create_wall_timer( + std::chrono::duration(1.0 / occupancy_publication_rate_hz_), + std::bind(&NvbloxNode::publishOccupancyPointcloud, this), + group_processing_); + } - // Set state. - last_tsdf_update_time_ = rclcpp::Time(0ul, get_clock()->get_clock_type()); - last_color_update_time_ = rclcpp::Time(0ul, get_clock()->get_clock_type()); - last_pointcloud_update_time_ = - rclcpp::Time(0ul, get_clock()->get_clock_type()); - last_esdf_update_time_ = rclcpp::Time(0ul, get_clock()->get_clock_type()); - last_mesh_update_time_ = rclcpp::Time(0ul, get_clock()->get_clock_type()); + if (map_clearing_radius_m_ > 0.0f) { + clear_outside_radius_timer_ = create_wall_timer( + std::chrono::duration(1.0 / clear_outside_radius_rate_hz_), + std::bind(&NvbloxNode::clearMapOutsideOfRadiusOfLastKnownPose, this), + group_processing_); + } } void NvbloxNode::depthImageCallback( const sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr, const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg) { - // Message statistics - depth_frame_statistics_.OnMessageReceived( - *depth_img_ptr, - get_clock()->now().nanoseconds()); - constexpr int kPublishPeriodMs = 10000; - auto & clk = *get_clock(); - RCLCPP_INFO_STREAM_THROTTLE( - get_logger(), clk, kPublishPeriodMs, - "Depth frame statistics: \n" << - libstatistics_collector::moving_average_statistics:: - StatisticsDataToString( - depth_frame_statistics_.GetStatisticsResults())); - - RCLCPP_INFO_STREAM_THROTTLE( - get_logger(), clk, kPublishPeriodMs, - "Timing statistics: \n" << - nvblox::timing::Timing::Print()); - - timing::Timer ros_total_timer("ros/total"); - - // Push it into the queue. - { - const std::lock_guard lock(depth_queue_mutex_); - depth_image_queue_.emplace_back(depth_img_ptr, camera_info_msg); - } + printMessageArrivalStatistics( + *depth_img_ptr, "Depth Statistics", + &depth_frame_statistics_); + pushMessageOntoQueue( + {depth_img_ptr, camera_info_msg}, &depth_image_queue_, + &depth_queue_mutex_); } void NvbloxNode::colorImageCallback( const sensor_msgs::msg::Image::ConstSharedPtr & color_image_ptr, const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg) { - // Message statistics - rgb_frame_statistics_.OnMessageReceived( - *color_image_ptr, - get_clock()->now().nanoseconds()); - constexpr int kPublishPeriodMs = 10000; - auto & clk = *get_clock(); - RCLCPP_INFO_STREAM_THROTTLE( - get_logger(), clk, kPublishPeriodMs, - "RGB frame statistics: \n" << - libstatistics_collector::moving_average_statistics:: - StatisticsDataToString( - rgb_frame_statistics_.GetStatisticsResults())); - - timing::Timer ros_total_timer("ros/total"); - - // Push it into the queue. - { - const std::lock_guard lock(color_queue_mutex_); - color_image_queue_.emplace_back(color_image_ptr, camera_info_msg); - } + printMessageArrivalStatistics( + *color_image_ptr, "Color Statistics", + &rgb_frame_statistics_); + pushMessageOntoQueue( + {color_image_ptr, camera_info_msg}, &color_image_queue_, + &color_queue_mutex_); } void NvbloxNode::pointcloudCallback( const sensor_msgs::msg::PointCloud2::ConstSharedPtr pointcloud) { - constexpr int kPublishPeriodMs = 10000; - auto & clk = *get_clock(); - - RCLCPP_INFO_STREAM_THROTTLE( - get_logger(), clk, kPublishPeriodMs, - "Timing statistics: \n" << - nvblox::timing::Timing::Print()); - - timing::Timer ros_total_timer("ros/total"); - - // Push it into the queue. It's converted later on. - { - const std::lock_guard lock(pointcloud_queue_mutex_); - pointcloud_queue_.emplace_back(pointcloud); - } + printMessageArrivalStatistics( + *pointcloud, "Pointcloud Statistics", + &pointcloud_frame_statistics_); + pushMessageOntoQueue( + pointcloud, &pointcloud_queue_, + &pointcloud_queue_mutex_); } void NvbloxNode::processDepthQueue() { - timing::Timer ros_total_timer("ros/total"); - - // Copy over all the pointers we actually want to process here. - std::vector> - images_to_process; - - std::unique_lock lock(depth_queue_mutex_); - - if (depth_image_queue_.empty()) { - lock.unlock(); - return; - } - - auto it_first_valid = depth_image_queue_.end(); - auto it_last_valid = depth_image_queue_.begin(); - - for (auto it = depth_image_queue_.begin(); it != depth_image_queue_.end(); - it++) - { - sensor_msgs::msg::Image::ConstSharedPtr depth_img_ptr = it->first; - sensor_msgs::msg::CameraInfo::ConstSharedPtr camera_info_msg = it->second; - - rclcpp::Time timestamp = depth_img_ptr->header.stamp; - - // Process this image in the queue - if (canTransform(depth_img_ptr->header)) { - images_to_process.push_back( - std::make_pair(depth_img_ptr, camera_info_msg)); - } else { - continue; - } - - // If we processed this frame, keep track of that fact so we can delete it - // at the end. - if (it_first_valid == depth_image_queue_.end()) { - it_first_valid = it; - } - if (it_last_valid <= it) { - it_last_valid = it; - } - } - - // Now we have 2 iterators pointing to what we want to delete. - if (it_first_valid != depth_image_queue_.end()) { - // Actually erase from the beginning of the queue. - depth_image_queue_.erase(depth_image_queue_.begin(), ++it_last_valid); - } - lock.unlock(); - - // Now we actually process the depth images. - if (images_to_process.empty()) { - return; - } - - rclcpp::Time last_timestamp; - for (auto image_pair : images_to_process) { - // Cache clock_now. - last_timestamp = image_pair.first->header.stamp; - rclcpp::Time clock_now = last_timestamp; + using ImageInfoMsgPair = + std::pair; + auto message_ready = [this](const ImageInfoMsgPair & msg) { + return this->canTransform(msg.first->header); + }; + + processMessageQueue( + &depth_image_queue_, // NOLINT + &depth_queue_mutex_, // NOLINT + message_ready, // NOLINT + std::bind(&NvbloxNode::processDepthImage, this, std::placeholders::_1)); + + limitQueueSizeByDeletingOldestMessages( + maximum_sensor_message_queue_length_, + "depth", &depth_image_queue_, + &depth_queue_mutex_); +} - if (max_tsdf_update_hz_ > 0.0f && - (clock_now - last_tsdf_update_time_).seconds() < - 1.0f / max_tsdf_update_hz_) - { - // Skip integrating this. - continue; - } - last_tsdf_update_time_ = clock_now; +void NvbloxNode::processColorQueue() +{ + using ImageInfoMsgPair = + std::pair; + auto message_ready = [this](const ImageInfoMsgPair & msg) { + return this->canTransform(msg.first->header); + }; + + processMessageQueue( + &color_image_queue_, // NOLINT + &color_queue_mutex_, // NOLINT + message_ready, // NOLINT + std::bind(&NvbloxNode::processColorImage, this, std::placeholders::_1)); + + limitQueueSizeByDeletingOldestMessages( + maximum_sensor_message_queue_length_, + "color", &color_image_queue_, + &color_queue_mutex_); +} - processDepthImage(image_pair.first, image_pair.second); - } +void NvbloxNode::processPointcloudQueue() +{ + using PointcloudMsg = sensor_msgs::msg::PointCloud2::ConstSharedPtr; + auto message_ready = [this](const PointcloudMsg & msg) { + return this->canTransform(msg->header); + }; + processMessageQueue( + &pointcloud_queue_, // NOLINT + &pointcloud_queue_mutex_, // NOLINT + message_ready, // NOLINT + std::bind( + &NvbloxNode::processLidarPointcloud, this, + std::placeholders::_1)); - // Clear map outside radius, if requested - if (map_clearing_radius_m_ > 0.0f) { - const auto depth_img_ptr = images_to_process.back().first; - clearMapOutsideOfRadius(depth_img_ptr->header.frame_id, depth_img_ptr->header.stamp); - } + limitQueueSizeByDeletingOldestMessages( + maximum_sensor_message_queue_length_, + "pointcloud", &pointcloud_queue_, + &pointcloud_queue_mutex_); } -void NvbloxNode::processPointcloudQueue() +void NvbloxNode::processEsdf() { + if (!compute_esdf_) { + return; + } + const rclcpp::Time timestamp = get_clock()->now(); timing::Timer ros_total_timer("ros/total"); + timing::Timer ros_esdf_timer("ros/esdf"); - std::unique_lock lock(pointcloud_queue_mutex_); + timing::Timer esdf_integration_timer("ros/esdf/integrate"); + std::vector updated_blocks; + if (esdf_2d_) { + updated_blocks = mapper_->updateEsdfSlice( + esdf_2d_min_height_, esdf_2d_max_height_, esdf_slice_height_); + } else { + updated_blocks = mapper_->updateEsdf(); + } + esdf_integration_timer.Stop(); - if (pointcloud_queue_.empty()) { - lock.unlock(); + if (updated_blocks.empty()) { return; } - std::vector - pointclouds_to_process; - - auto it_first_valid = pointcloud_queue_.end(); - auto it_last_valid = pointcloud_queue_.begin(); + timing::Timer esdf_output_timer("ros/esdf/output"); - for (auto it = pointcloud_queue_.begin(); it != pointcloud_queue_.end(); - it++) + // If anyone wants a slice + if (esdf_distance_slice_ && + (esdf_pointcloud_publisher_->get_subscription_count() > 0 || + map_slice_publisher_->get_subscription_count() > 0)) { - sensor_msgs::msg::PointCloud2::ConstSharedPtr pointcloud_ptr = *it; - - rclcpp::Time timestamp = pointcloud_ptr->header.stamp; - - // Process this pointcloud in the queue - if (canTransform(pointcloud_ptr->header)) { - pointclouds_to_process.push_back(pointcloud_ptr); - } else { - continue; + // Get the slice as an image + timing::Timer esdf_slice_compute_timer("ros/esdf/output/compute"); + AxisAlignedBoundingBox aabb; + Image map_slice_image; + esdf_slice_converter_.distanceMapSliceImageFromLayer( + mapper_->esdf_layer(), esdf_slice_height_, &map_slice_image, &aabb); + esdf_slice_compute_timer.Stop(); + + // Slice pointcloud for RVIZ + if (esdf_pointcloud_publisher_->get_subscription_count() > 0) { + timing::Timer esdf_output_pointcloud_timer("ros/esdf/output/pointcloud"); + sensor_msgs::msg::PointCloud2 pointcloud_msg; + esdf_slice_converter_.sliceImageToPointcloud( + map_slice_image, aabb, esdf_slice_height_, + mapper_->esdf_layer().voxel_size(), &pointcloud_msg); + pointcloud_msg.header.frame_id = global_frame_; + pointcloud_msg.header.stamp = get_clock()->now(); + esdf_pointcloud_publisher_->publish(pointcloud_msg); } - // If we processed this frame, keep track of that fact so we can delete it - // at the end. - if (it_first_valid == pointcloud_queue_.end()) { - it_first_valid = it; - } - if (it_last_valid <= it) { - it_last_valid = it; + // Also publish the map slice (costmap for nav2). + if (map_slice_publisher_->get_subscription_count() > 0) { + timing::Timer esdf_output_human_slice_timer("ros/esdf/output/slice"); + nvblox_msgs::msg::DistanceMapSlice map_slice_msg; + esdf_slice_converter_.distanceMapSliceImageToMsg( + map_slice_image, aabb, esdf_slice_height_, + mapper_->voxel_size_m(), &map_slice_msg); + map_slice_msg.header.frame_id = global_frame_; + map_slice_msg.header.stamp = get_clock()->now(); + map_slice_publisher_->publish(map_slice_msg); } } - // Now we have 2 iterators pointing to what we want to delete. - if (it_first_valid != pointcloud_queue_.end()) { - // Actually erase from the beginning of the queue. - pointcloud_queue_.erase(pointcloud_queue_.begin(), ++it_last_valid); - } - lock.unlock(); - - // Now we actually process the depth images. - if (pointclouds_to_process.empty()) { - return; - } - - rclcpp::Time last_timestamp; - for (auto pointcloud : pointclouds_to_process) { - // Cache clock_now. - last_timestamp = pointcloud->header.stamp; - rclcpp::Time clock_now = last_timestamp; - - if (max_pointcloud_update_hz_ > 0.0f && - (clock_now - last_pointcloud_update_time_).seconds() < - 1.0f / max_pointcloud_update_hz_) + // Also publish the slice bounds (showing esdf max/min 2d height) + if (slice_bounds_publisher_->get_subscription_count() > 0) { + // The frame to which the slice limits visualization is attached. + // We get the transform from the plane-body (PB) frame, to the scene (S). + Transform T_S_PB; + if (transformer_.lookupTransformToGlobalFrame( + slice_visualization_attachment_frame_id_, rclcpp::Time(0), + &T_S_PB)) { - // Skip integrating this. - continue; + // Get and publish the planes representing the slice bounds in z. + const visualization_msgs::msg::Marker marker = sliceLimitsToMarker( + T_S_PB, slice_visualization_side_length_, timestamp, global_frame_, + esdf_2d_min_height_, esdf_2d_max_height_); + slice_bounds_publisher_->publish(marker); + } else { + constexpr float kTimeBetweenDebugMessages = 1.0; + RCLCPP_INFO_STREAM_THROTTLE( + get_logger(), *get_clock(), kTimeBetweenDebugMessages, + "Tried to publish slice bounds but couldn't look up frame: " << + slice_visualization_attachment_frame_id_); } - last_pointcloud_update_time_ = clock_now; - - processLidarPointcloud(pointcloud); - } - - // Clear map outside radius, if requested - if (map_clearing_radius_m_ > 0.0f) { - const auto pointcloud_ptr = pointclouds_to_process.back(); - clearMapOutsideOfRadius(pointcloud_ptr->header.frame_id, pointcloud_ptr->header.stamp); - } -} - -void NvbloxNode::processEsdf() -{ - timing::Timer ros_total_timer("ros/total"); - - // Esdf integrator (if enabled) - if (esdf_) { - const rclcpp::Time clock_now = get_clock()->now(); - last_esdf_update_time_ = clock_now; - - // Then do the update. - // Otherwise do nothing. - updateEsdf(clock_now); } } void NvbloxNode::processMesh() { - timing::Timer ros_total_timer("ros/total"); - - // Mesh integrator - if (mesh_) { - const rclcpp::Time clock_now = get_clock()->now(); - last_mesh_update_time_ = clock_now; - updateMesh(clock_now); + if (!compute_mesh_) { + return; } -} - -void NvbloxNode::processColorQueue() -{ + const rclcpp::Time timestamp = get_clock()->now(); timing::Timer ros_total_timer("ros/total"); + timing::Timer ros_mesh_timer("ros/mesh"); - // Copy over all the pointers we actually want to process here. - std::vector> - images_to_process; - - std::unique_lock lock(color_queue_mutex_); + timing::Timer mesh_integration_timer("ros/mesh/integrate_and_color"); + const std::vector mesh_updated_list = mapper_->updateMesh(); + mesh_integration_timer.Stop(); - if (color_image_queue_.empty()) { - lock.unlock(); - return; + // In the case that some mesh blocks have been re-added after deletion, remove + // them from the deleted list. + for (const Index3D & idx : mesh_updated_list) { + mesh_blocks_deleted_.erase(idx); } + // Make a list to be published to rviz of blocks to be removed from the viz + const std::vector mesh_blocks_to_delete(mesh_blocks_deleted_.begin(), + mesh_blocks_deleted_.end()); + mesh_blocks_deleted_.clear(); - auto it_first_valid = color_image_queue_.end(); - auto it_last_valid = color_image_queue_.begin(); - for (auto it = color_image_queue_.begin(); it != color_image_queue_.end(); - it++) - { - sensor_msgs::msg::Image::ConstSharedPtr color_img_ptr = it->first; - sensor_msgs::msg::CameraInfo::ConstSharedPtr camera_info_msg = it->second; - - rclcpp::Time timestamp = color_img_ptr->header.stamp; + bool should_publish = !mesh_updated_list.empty(); - // Process this image in the queue - if (canTransform(color_img_ptr->header)) { - images_to_process.push_back( - std::make_pair(color_img_ptr, camera_info_msg)); + // Publish the mesh updates. + timing::Timer mesh_output_timer("ros/mesh/output"); + size_t new_subscriber_count = mesh_publisher_->get_subscription_count(); + if (new_subscriber_count > 0) { + nvblox_msgs::msg::Mesh mesh_msg; + // In case we have new subscribers, publish the ENTIRE map once. + if (new_subscriber_count > mesh_subscriber_count_) { + RCLCPP_INFO(get_logger(), "Got a new subscriber, sending entire map."); + conversions::meshMessageFromMeshLayer(mapper_->mesh_layer(), &mesh_msg); + mesh_msg.clear = true; + should_publish = true; } else { - continue; - } - - // If we processed this frame, keep track of that fact so we can delete it - // at the end. - if (it_first_valid == color_image_queue_.end()) { - it_first_valid = it; + conversions::meshMessageFromMeshBlocks( + mapper_->mesh_layer(), + mesh_updated_list, &mesh_msg, + mesh_blocks_to_delete); } - if (it_last_valid <= it) { - it_last_valid = it; + mesh_msg.header.frame_id = global_frame_; + mesh_msg.header.stamp = timestamp; + if (should_publish) { + mesh_publisher_->publish(mesh_msg); } } + mesh_subscriber_count_ = new_subscriber_count; - // Now we have 2 iterators pointing to what we want to delete. - if (it_first_valid != color_image_queue_.end()) { - // Actually erase from the beginning of the queue. - color_image_queue_.erase(color_image_queue_.begin(), ++it_last_valid); - } - lock.unlock(); - - // Now we actually process the color images. - if (images_to_process.empty()) { - return; + // optionally publish the markers. + if (mesh_marker_publisher_->get_subscription_count() > 0) { + visualization_msgs::msg::MarkerArray marker_msg; + conversions::markerMessageFromMeshLayer( + mapper_->mesh_layer(), global_frame_, + &marker_msg); + mesh_marker_publisher_->publish(marker_msg); } - rclcpp::Time last_timestamp; - for (auto image_pair : images_to_process) { - // Cache clock_now. - rclcpp::Time clock_now = image_pair.first->header.stamp; - - if (max_color_update_hz_ > 0.0f && - (clock_now - last_color_update_time_).seconds() < - 1.0f / max_color_update_hz_) - { - // Skip integrating this. - continue; - } - last_color_update_time_ = clock_now; - - processColorImage(image_pair.first, image_pair.second); - } + mesh_output_timer.Stop(); } bool NvbloxNode::canTransform(const std_msgs::msg::Header & header) { - Transform T_S_C; + Transform T_L_C; return transformer_.lookupTransformToGlobalFrame( header.frame_id, - header.stamp, &T_S_C); + header.stamp, &T_L_C); +} + +bool NvbloxNode::isUpdateTooFrequent( + const rclcpp::Time & current_stamp, + const rclcpp::Time & last_update_stamp, + float max_update_rate_hz) +{ + if (max_update_rate_hz > 0.0f && + (current_stamp - last_update_stamp).seconds() < + 1.0f / max_update_rate_hz) + { + return true; + } + return false; } bool NvbloxNode::processDepthImage( - sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr, - sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg) + const std::pair & + depth_camera_pair) { - timing::Timer ros_tsdf_timer("ros/tsdf"); - timing::Timer transform_timer("ros/tsdf/transform"); + timing::Timer ros_depth_timer("ros/depth"); + timing::Timer transform_timer("ros/depth/transform"); + + // Message parts + const sensor_msgs::msg::Image::ConstSharedPtr & depth_img_ptr = + depth_camera_pair.first; + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg = + depth_camera_pair.second; + + // Check that we're not updating more quickly than we should. + if (isUpdateTooFrequent( + depth_img_ptr->header.stamp, last_depth_update_time_, + max_depth_update_hz_)) + { + return true; + } + last_depth_update_time_ = depth_img_ptr->header.stamp; + // Get the TF for this image. - Transform T_S_C; + Transform T_L_C; std::string target_frame = depth_img_ptr->header.frame_id; if (!transformer_.lookupTransformToGlobalFrame( - target_frame, depth_img_ptr->header.stamp, &T_S_C)) + target_frame, depth_img_ptr->header.stamp, &T_L_C)) { return false; } transform_timer.Stop(); - timing::Timer conversions_timer("ros/tsdf/conversions"); + timing::Timer conversions_timer("ros/depth/conversions"); // Convert camera info message to camera object. - Camera camera = converter_.cameraFromMessage(*camera_info_msg); + Camera camera = conversions::cameraFromMessage(*camera_info_msg); // Convert the depth image. - if (!converter_.depthImageFromImageMessage(depth_img_ptr, &depth_image_)) { + if (!conversions::depthImageFromImageMessage(depth_img_ptr, &depth_image_)) { RCLCPP_ERROR(get_logger(), "Failed to transform depth image."); return false; } conversions_timer.Stop(); // Integrate - timing::Timer integration_timer("ros/tsdf/integrate"); - mapper_->integrateDepth(depth_image_, T_S_C, camera); + timing::Timer integration_timer("ros/depth/integrate"); + mapper_->integrateDepth(depth_image_, T_L_C, camera); integration_timer.Stop(); return true; } bool NvbloxNode::processColorImage( - sensor_msgs::msg::Image::ConstSharedPtr & color_img_ptr, - sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg) + const std::pair & + color_camera_pair) { timing::Timer ros_color_timer("ros/color"); timing::Timer transform_timer("ros/color/transform"); + const sensor_msgs::msg::Image::ConstSharedPtr & color_img_ptr = + color_camera_pair.first; + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & camera_info_msg = + color_camera_pair.second; + + // Check that we're not updating more quickly than we should. + if (isUpdateTooFrequent( + color_img_ptr->header.stamp, last_color_update_time_, + max_color_update_hz_)) + { + return true; + } + last_color_update_time_ = color_img_ptr->header.stamp; + // Get the TF for this image. const std::string target_frame = color_img_ptr->header.frame_id; - Transform T_S_C; + Transform T_L_C; if (!transformer_.lookupTransformToGlobalFrame( - target_frame, color_img_ptr->header.stamp, &T_S_C)) + target_frame, color_img_ptr->header.stamp, &T_L_C)) { return false; } @@ -701,10 +678,10 @@ bool NvbloxNode::processColorImage( timing::Timer color_convert_timer("ros/color/conversion"); // Convert camera info message to camera object. - Camera camera = converter_.cameraFromMessage(*camera_info_msg); + Camera camera = conversions::cameraFromMessage(*camera_info_msg); // Convert the color image. - if (!converter_.colorImageFromImageMessage(color_img_ptr, &color_image_)) { + if (!conversions::colorImageFromImageMessage(color_img_ptr, &color_image_)) { RCLCPP_ERROR(get_logger(), "Failed to transform color image."); return false; } @@ -712,23 +689,32 @@ bool NvbloxNode::processColorImage( // Integrate. timing::Timer color_integrate_timer("ros/color/integrate"); - mapper_->integrateColor(color_image_, T_S_C, camera); + mapper_->integrateColor(color_image_, T_L_C, camera); color_integrate_timer.Stop(); return true; } bool NvbloxNode::processLidarPointcloud( - sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud_ptr) + const sensor_msgs::msg::PointCloud2::ConstSharedPtr & pointcloud_ptr) { timing::Timer ros_lidar_timer("ros/lidar"); timing::Timer transform_timer("ros/lidar/transform"); + // Check that we're not updating more quickly than we should. + if (isUpdateTooFrequent( + pointcloud_ptr->header.stamp, last_lidar_update_time_, + max_lidar_update_hz_)) + { + return true; + } + last_lidar_update_time_ = pointcloud_ptr->header.stamp; + // Get the TF for this image. const std::string target_frame = pointcloud_ptr->header.frame_id; - Transform T_S_C; + Transform T_L_C; if (!transformer_.lookupTransformToGlobalFrame( - target_frame, pointcloud_ptr->header.stamp, &T_S_C)) + target_frame, pointcloud_ptr->header.stamp, &T_L_C)) { return false; } @@ -745,7 +731,7 @@ bool NvbloxNode::processLidarPointcloud( // NOTE(alexmillane): Note that internally we cache checks, so each LiDAR // intrisics model is only tested against a single pointcloud. This is because // the check is expensive to perform. - if (!converter_.checkLidarPointcloud(pointcloud_ptr, lidar)) { + if (!pointcloud_converter_.checkLidarPointcloud(pointcloud_ptr, lidar)) { RCLCPP_ERROR_ONCE( get_logger(), "LiDAR intrinsics are inconsistent with the received " @@ -754,160 +740,52 @@ bool NvbloxNode::processLidarPointcloud( } timing::Timer lidar_conversion_timer("ros/lidar/conversion"); - converter_.depthImageFromPointcloudGPU( + pointcloud_converter_.depthImageFromPointcloudGPU( pointcloud_ptr, lidar, &pointcloud_image_); lidar_conversion_timer.Stop(); timing::Timer lidar_integration_timer("ros/lidar/integration"); - mapper_->integrateLidarDepth(pointcloud_image_, T_S_C, lidar); + mapper_->integrateLidarDepth(pointcloud_image_, T_L_C, lidar); lidar_integration_timer.Stop(); return true; } -void NvbloxNode::updateEsdf(const rclcpp::Time & timestamp) +void NvbloxNode::publishOccupancyPointcloud() { - timing::Timer ros_esdf_timer("ros/esdf"); - - timing::Timer esdf_integration_timer("ros/esdf/integrate"); - - std::vector updated_blocks; - if (esdf_2d_) { - updated_blocks = - mapper_->updateEsdfSlice(min_height_, max_height_, slice_height_); - } else { - updated_blocks = mapper_->updateEsdf(); - } - - esdf_integration_timer.Stop(); - - if (updated_blocks.empty()) { - return; - } - - timing::Timer esdf_output_timer("ros/esdf/output"); - - if (pointcloud_publisher_->get_subscription_count() > 0) { - timing::Timer output_pointcloud_timer("ros/esdf/output/pointcloud"); + timing::Timer ros_total_timer("ros/total"); + timing::Timer esdf_output_timer("ros/occupancy/output"); - // Output the ESDF. Let's just do the full thing for now. + if (occupancy_publisher_->get_subscription_count() > 0) { sensor_msgs::msg::PointCloud2 pointcloud_msg; - - // AABB of a certain slice height. - AxisAlignedBoundingBox aabb(Vector3f( - std::numeric_limits::lowest(), - std::numeric_limits::lowest(), - slice_height_ - voxel_size_ / 2.0f), - Vector3f( - std::numeric_limits::max(), - std::numeric_limits::max(), - slice_height_ + voxel_size_ / 2.0f)); - - converter_.pointcloudFromLayerInAABB( - mapper_->esdf_layer(), aabb, - &pointcloud_msg); - + layer_converter_.pointcloudMsgFromLayer(mapper_->occupancy_layer(), &pointcloud_msg); pointcloud_msg.header.frame_id = global_frame_; - pointcloud_msg.header.stamp = timestamp; - pointcloud_publisher_->publish(pointcloud_msg); - - output_pointcloud_timer.Stop(); - } - - // Also publish the map slice. - if (distance_slice_ && map_slice_publisher_->get_subscription_count() > 0) { - timing::Timer output_map_slice_timer("ros/esdf/output/map_slice"); - - nvblox_msgs::msg::DistanceMapSlice map_slice; - - converter_.distanceMapSliceFromLayer( - mapper_->esdf_layer(), slice_height_, - &map_slice); - map_slice.header.frame_id = global_frame_; - map_slice.header.stamp = timestamp; - map_slice_publisher_->publish(map_slice); - } -} - -void NvbloxNode::updateMesh(const rclcpp::Time & timestamp) -{ - timing::Timer ros_mesh_timer("ros/mesh"); - - timing::Timer mesh_integration_timer("ros/mesh/integrate_and_color"); - const std::vector mesh_updated_list = mapper_->updateMesh(); - mesh_integration_timer.Stop(); - - // In the case that some mesh blocks have been re-added after deletion, remove them from the - // deleted list. - for (const Index3D & idx : mesh_updated_list) { - mesh_blocks_deleted_.erase(idx); - } - // Make a list to be published to rviz of blocks to be removed from the viz - const std::vector mesh_blocks_to_delete( - mesh_blocks_deleted_.begin(), mesh_blocks_deleted_.end()); - mesh_blocks_deleted_.clear(); - - bool should_publish = !mesh_updated_list.empty(); - - // Publish the mesh updates. - timing::Timer mesh_output_timer("ros/mesh/output"); - size_t new_subscriber_count = mesh_publisher_->get_subscription_count(); - if (new_subscriber_count > 0) { - nvblox_msgs::msg::Mesh mesh_msg; - // In case we have new subscribers, publish the ENTIRE map once. - if (new_subscriber_count > mesh_subscriber_count_) { - RCLCPP_INFO(get_logger(), "Got a new subscriber, sending entire map."); - - converter_.meshMessageFromMeshBlocks( - mapper_->mesh_layer(), mapper_->mesh_layer().getAllBlockIndices(), - &mesh_msg); - mesh_msg.clear = true; - should_publish = true; - } else { - converter_.meshMessageFromMeshBlocks( - mapper_->mesh_layer(), - mesh_updated_list, &mesh_msg, mesh_blocks_to_delete); - } - mesh_msg.header.frame_id = global_frame_; - mesh_msg.header.stamp = timestamp; - if (should_publish) { - mesh_publisher_->publish(mesh_msg); - } - } - mesh_subscriber_count_ = new_subscriber_count; - - // optionally publish the markers. - if (mesh_marker_publisher_->get_subscription_count() > 0) { - visualization_msgs::msg::MarkerArray marker_msg; - converter_.markerMessageFromMeshLayer( - mapper_->mesh_layer(), global_frame_, - &marker_msg); - - mesh_marker_publisher_->publish(marker_msg); + pointcloud_msg.header.stamp = get_clock()->now(); + occupancy_publisher_->publish(pointcloud_msg); } - - mesh_output_timer.Stop(); } -void NvbloxNode::clearMapOutsideOfRadius( - const std::string & target_frame_id, - const rclcpp::Time & timestamp) +void NvbloxNode::clearMapOutsideOfRadiusOfLastKnownPose() { if (map_clearing_radius_m_ > 0.0f) { timing::Timer("ros/clear_outside_radius"); - // We use the most recent but processed transform to define the center. - Transform T_S_C; - if (transformer_.lookupTransformToGlobalFrame(target_frame_id, timestamp, &T_S_C)) { - std::vector blocks_cleared = mapper_->clearOutsideRadius( - T_S_C.translation(), map_clearing_radius_m_); + Transform T_L_MC; // MC = map clearing frame + if (transformer_.lookupTransformToGlobalFrame( + map_clearing_frame_id_, + rclcpp::Time(0), &T_L_MC)) + { + const std::vector blocks_cleared = mapper_->clearOutsideRadius( + T_L_MC.translation(), map_clearing_radius_m_); // We keep track of the deleted blocks for publishing later. mesh_blocks_deleted_.insert(blocks_cleared.begin(), blocks_cleared.end()); } else { - RCLCPP_WARN( - get_logger(), - "Could not get the transform in order clear the map. This should not be possible."); + constexpr float kTimeBetweenDebugMessages = 1.0; + RCLCPP_INFO_STREAM_THROTTLE( + get_logger(), *get_clock(), kTimeBetweenDebugMessages, + "Tried to clear map outside of radius but couldn't look up frame: " << + map_clearing_frame_id_); } } } diff --git a/nvblox_ros/src/lib/transformer.cpp b/nvblox_ros/src/lib/transformer.cpp index 8c879913..9c69b94a 100644 --- a/nvblox_ros/src/lib/transformer.cpp +++ b/nvblox_ros/src/lib/transformer.cpp @@ -21,6 +21,7 @@ #include #include + namespace nvblox { @@ -106,18 +107,29 @@ bool Transformer::lookupTransformTf( const rclcpp::Time & timestamp, Transform * transform) { - geometry_msgs::msg::TransformStamped T_S_C_msg; + geometry_msgs::msg::TransformStamped T_L_C_msg; try { - if (tf_buffer_->canTransform(from_frame, to_frame, timestamp)) { - T_S_C_msg = tf_buffer_->lookupTransform(from_frame, to_frame, timestamp); + std::string error_string; + if (tf_buffer_->canTransform( + from_frame, to_frame, timestamp, + rclcpp::Duration::from_nanoseconds(0), &error_string)) + { + T_L_C_msg = tf_buffer_->lookupTransform(from_frame, to_frame, timestamp); } else { + RCLCPP_DEBUG_STREAM( + node_->get_logger(), + "Cant transform: from:" << from_frame << " to " << to_frame << ". Error string: " << + error_string); return false; } } catch (tf2::TransformException & e) { + RCLCPP_DEBUG_STREAM( + node_->get_logger(), + "Cant transform: from:" << from_frame << " to " << to_frame << ". Error: " << e.what()); return false; } - *transform = transformToEigen(T_S_C_msg.transform); + *transform = transformToEigen(T_L_C_msg.transform); return true; } @@ -125,24 +137,35 @@ bool Transformer::lookupTransformQueue( const rclcpp::Time & timestamp, Transform * transform) { - uint64_t timestamp_ns = timestamp.nanoseconds(); + // Get latest transform + if (timestamp == rclcpp::Time(0)) { + if (transform_queue_.empty()) { + return false; + } - auto closest_match = transform_queue_.lower_bound(timestamp_ns); - if (closest_match == transform_queue_.end()) { - return false; - } + *transform = transform_queue_.rbegin()->second; + return true; + } else { + // Get closest transform + uint64_t timestamp_ns = timestamp.nanoseconds(); - // If we're too far off on the timestamp: - uint64_t distance = std::max(closest_match->first, timestamp_ns) - - std::min(closest_match->first, timestamp_ns); - if (distance > timestamp_tolerance_ns_) { - return false; - } + auto closest_match = transform_queue_.lower_bound(timestamp_ns); + if (closest_match == transform_queue_.end()) { + return false; + } - // We just do nearest neighbor here. - // TODO(holeynikova): add interpolation! - *transform = closest_match->second; - return true; + // If we're too far off on the timestamp: + uint64_t distance = std::max(closest_match->first, timestamp_ns) - + std::min(closest_match->first, timestamp_ns); + if (distance > timestamp_tolerance_ns_) { + return false; + } + + // We just do nearest neighbor here. + // TODO(holeynikova): add interpolation! + *transform = closest_match->second; + return true; + } } bool Transformer::lookupSensorTransform( diff --git a/nvblox_ros/src/nvblox_human_node_main.cpp b/nvblox_ros/src/nvblox_human_node_main.cpp new file mode 100644 index 00000000..0080bc5a --- /dev/null +++ b/nvblox_ros/src/nvblox_human_node_main.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include + +#include + +#include "nvblox_ros/nvblox_human_node.hpp" + +using namespace std::chrono_literals; + +int main(int argc, char * argv[]) +{ + google::InitGoogleLogging(argv[0]); + FLAGS_alsologtostderr = true; + google::InstallFailureSignalHandler(); + rclcpp::init(argc, argv); + + // Warmup CUDA so it doesn't affect our timings *as* much for the first + // CUDA call. + nvblox::warmupCuda(); + + rclcpp::executors::MultiThreadedExecutor exec; + std::shared_ptr node(new nvblox::NvbloxHumanNode()); + exec.add_node(node); + exec.spin(); + + rclcpp::shutdown(); + return 0; +} diff --git a/nvblox_ros/src/nvblox_node_main.cpp b/nvblox_ros/src/nvblox_node_main.cpp index 64257f7e..a9c6e5e6 100644 --- a/nvblox_ros/src/nvblox_node_main.cpp +++ b/nvblox_ros/src/nvblox_node_main.cpp @@ -16,7 +16,7 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include #include diff --git a/nvblox_ros/test/isaac_ros_nvblox_pol.py b/nvblox_ros/test/isaac_ros_nvblox_pol.py index 23e7626f..83d8b160 100644 --- a/nvblox_ros/test/isaac_ros_nvblox_pol.py +++ b/nvblox_ros/test/isaac_ros_nvblox_pol.py @@ -17,6 +17,7 @@ import os import pathlib +import subprocess import time from isaac_ros_test import IsaacROSBaseTest @@ -45,15 +46,15 @@ @pytest.mark.rostest def generate_test_description(): + # Fetch git-lfs files + print(subprocess.getoutput('git lfs pull -X ""')) + nvblox_node = Node( package='nvblox_ros', executable='nvblox_node', namespace=IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE), parameters=[{'global_frame': 'odom'}, {'use_sim_time': True}], - remappings=[('depth/image', 'left/depth'), - ('depth/camera_info', 'left/camera_info'), - ('color/image', 'left/rgb'), - ('color/camera_info', 'left/camera_info')], + remappings=[('depth/camera_info', 'color/camera_info')], output='screen' ) @@ -65,12 +66,12 @@ def generate_test_description(): IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE) + '/cmd_vel', 'clock:=' + IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE) + '/clock', - 'left/depth:=' + - IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE) + '/left/depth', - 'left/camera_info:=' + - IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE) + '/left/camera_info', - 'left/rgb:=' + - IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE) + '/left/rgb'], + 'front/stereo_camera/left/depth:=' + + IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE) + '/depth/image', + 'front/stereo_camera/left/camera_info:=' + + IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE) + '/color/camera_info', + 'front/stereo_camera/left/rgb:=' + + IsaacROSNvBloxTest.generate_namespace(_TEST_CASE_NAMESPACE) + '/color/image'], output='screen') return IsaacROSNvBloxTest.generate_test_description( @@ -82,7 +83,7 @@ class IsaacROSNvBloxTest(IsaacROSBaseTest): @ IsaacROSBaseTest.for_each_test_case('rosbags') def test_nvblox_node(self, test_folder): - TIMEOUT = 50 + TIMEOUT = 10 received_messages = {} self.generate_namespace_lookup( ['nvblox_node/mesh', 'nvblox_node/map_slice'], _TEST_CASE_NAMESPACE) diff --git a/nvblox_ros/test/test_cases/rosbags/nvblox_pol/metadata.yaml b/nvblox_ros/test/test_cases/rosbags/nvblox_pol/metadata.yaml index 27cbc92c..5b6dbe4b 100644 --- a/nvblox_ros/test/test_cases/rosbags/nvblox_pol/metadata.yaml +++ b/nvblox_ros/test/test_cases/rosbags/nvblox_pol/metadata.yaml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0badc1023826077c6447f920e04d77dfcc368ad58d71723a50fd57260d9d6ae8 -size 4605 +oid sha256:7ed941f8689cb9f15cb063a3ab49c757071d7e2d2ae19f784af2dc4936eb4489 +size 4330 diff --git a/nvblox_ros/test/test_cases/rosbags/nvblox_pol/nvblox_example_0.db3 b/nvblox_ros/test/test_cases/rosbags/nvblox_pol/nvblox_example_0.db3 deleted file mode 100644 index 9d7c7ef3..00000000 --- a/nvblox_ros/test/test_cases/rosbags/nvblox_pol/nvblox_example_0.db3 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a7e4cc10a8406d4fb123719161069c7aec92c7ca87fc65d4c59a46e2bd7de22d -size 318914560 diff --git a/nvblox_ros/test/test_cases/rosbags/nvblox_pol/nvblox_pol_0.db3 b/nvblox_ros/test/test_cases/rosbags/nvblox_pol/nvblox_pol_0.db3 new file mode 100644 index 00000000..32c1804c --- /dev/null +++ b/nvblox_ros/test/test_cases/rosbags/nvblox_pol/nvblox_pol_0.db3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3580c2f422c6ecfc389d379d42c82b4dcf18434325bc6b3e3e15e84f31277df +size 340615168 diff --git a/nvblox_ros_common/CMakeLists.txt b/nvblox_ros_common/CMakeLists.txt new file mode 100644 index 00000000..f600746e --- /dev/null +++ b/nvblox_ros_common/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.5) +project(nvblox_ros_common) + +set(CMAKE_CXX_STANDARD 17) + +add_compile_options(-Wall -Wextra -Wpedantic) + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) + +############# +# LIBRARIES # +############# +add_library(${PROJECT_NAME}_lib SHARED + src/qos.cpp +) +ament_target_dependencies(${PROJECT_NAME}_lib + rclcpp +) +target_include_directories(${PROJECT_NAME}_lib PUBLIC + $ + $) + +########### +# INSTALL # +########### + +# includes +install( + DIRECTORY include/ + DESTINATION include +) + +# libs +install( + TARGETS ${PROJECT_NAME}_lib + EXPORT ${PROJECT_NAME}Targets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +########## +# EXPORT # +########## +ament_export_include_directories( + include +) +ament_export_libraries( + ${PROJECT_NAME}_lib +) +ament_export_targets( + ${PROJECT_NAME}Targets +) + +ament_export_dependencies( + rclcpp +) + +ament_package() diff --git a/nvblox_ros_common/include/nvblox_ros_common/qos.hpp b/nvblox_ros_common/include/nvblox_ros_common/qos.hpp new file mode 100644 index 00000000..67a0c59e --- /dev/null +++ b/nvblox_ros_common/include/nvblox_ros_common/qos.hpp @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES +// Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef NVBLOX_IMAGE_PADDING__QOS_HPP_ +#define NVBLOX_IMAGE_PADDING__QOS_HPP_ + +#include + +#include + +namespace nvblox +{ + +rmw_qos_profile_t parseQosString(const std::string & str); + +} // namespace nvblox + +#endif // NVBLOX_IMAGE_PADDING__QOS_HPP_ diff --git a/nvblox_ros_common/package.xml b/nvblox_ros_common/package.xml new file mode 100644 index 00000000..9fb65a2b --- /dev/null +++ b/nvblox_ros_common/package.xml @@ -0,0 +1,48 @@ + + + + + + + nvblox_ros_common + 0.30.0 + Utilities used across the isaac_ros_nvblox repo + + Alexander Millane + Remo Steiner + Hemal Shah + Apache-2.0 + https://developer.nvidia.com/isaac-ros-gems/ + Alexander Millane + Remo Steiner + Emilie Wirbel + Vikram Ramasamy + + ament_cmake + ament_cmake_auto + + rclcpp + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/nvblox_examples/realsense_splitter/src/qos.cpp b/nvblox_ros_common/src/qos.cpp similarity index 97% rename from nvblox_examples/realsense_splitter/src/qos.cpp rename to nvblox_ros_common/src/qos.cpp index 4ba67063..acb65bd1 100644 --- a/nvblox_examples/realsense_splitter/src/qos.cpp +++ b/nvblox_ros_common/src/qos.cpp @@ -15,7 +15,7 @@ // // SPDX-License-Identifier: Apache-2.0 -#include "realsense_splitter/qos.hpp" +#include "nvblox_ros_common/qos.hpp" #include #include diff --git a/nvblox_rviz_plugin/package.xml b/nvblox_rviz_plugin/package.xml index e09ebf3c..3117520d 100644 --- a/nvblox_rviz_plugin/package.xml +++ b/nvblox_rviz_plugin/package.xml @@ -21,7 +21,7 @@ nvblox_rviz_plugin - 0.20.0 + 0.30.0 RViz visualization plugin for Nvblox meshes Helen Oleynikova diff --git a/resources/animation_confirmation_dialog.png b/resources/animation_confirmation_dialog.png new file mode 100644 index 00000000..9d328381 --- /dev/null +++ b/resources/animation_confirmation_dialog.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bbaae95c941efab466a58b39c23bb66b3a5ca15066979d3da68479389a1fd40 +size 563055 diff --git a/resources/basic_example_rviz.png b/resources/basic_example_rviz.png new file mode 100644 index 00000000..c3c9a4f9 --- /dev/null +++ b/resources/basic_example_rviz.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3533eac6cfb1b9f32c04e126bc56e1b2866bb6288489832916d89e22a82724c2 +size 980866 diff --git a/resources/isaac_ros_nvblox_nodegraph.png b/resources/isaac_ros_nvblox_nodegraph.png new file mode 100644 index 00000000..faac03cd --- /dev/null +++ b/resources/isaac_ros_nvblox_nodegraph.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bb3ebdb961032dd4ddae219082e0c6e1ffa076aec9e96fba56639dfb51d8fc6 +size 52052 diff --git a/resources/isaac_ros_nvblox_person_removal.png b/resources/isaac_ros_nvblox_person_removal.png new file mode 100644 index 00000000..071af1a4 --- /dev/null +++ b/resources/isaac_ros_nvblox_person_removal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ef407f2f0a2215a01eac210fc47b6f30a5c536b916c1435aba4cf748eb9c62b +size 129229 diff --git a/resources/isaac_sim_anim_people_scripts.png b/resources/isaac_sim_anim_people_scripts.png new file mode 100644 index 00000000..e122ef92 --- /dev/null +++ b/resources/isaac_sim_anim_people_scripts.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd6c65b5cf08d3b4cc19baedf44b91c6094e074025ab5db763c7bac502b7c70c +size 466575 diff --git a/resources/isaac_sim_nvblox_humans.gif b/resources/isaac_sim_nvblox_humans.gif new file mode 100644 index 00000000..6b8e638f --- /dev/null +++ b/resources/isaac_sim_nvblox_humans.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a31ab7bec7b51b5fdafaa63aeca50337f95b07c0f95d75a1fbd2b309015f15b +size 2228708 diff --git a/resources/nvblox_navigation_trim.gif b/resources/nvblox_navigation_trim.gif deleted file mode 100644 index 6d406241..00000000 --- a/resources/nvblox_navigation_trim.gif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c97cb6dbd41e71fc585705b30a84570e56072039d298327a72f587ea4607ff54 -size 3527013 diff --git a/resources/nvblox_performance_setup.png b/resources/nvblox_performance_setup.png new file mode 100644 index 00000000..b5cf1687 --- /dev/null +++ b/resources/nvblox_performance_setup.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:290e0f350d0dbdc083d273a605339aa40d6ec663338f8abe2c2eee8470ea8f9a +size 42637 diff --git a/resources/nvblox_realsense_example_diagram.png b/resources/nvblox_realsense_example_diagram.png index 817a3266..5164869c 100644 --- a/resources/nvblox_realsense_example_diagram.png +++ b/resources/nvblox_realsense_example_diagram.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41b92328273657372668be9357709a858a919cc85478cf87a4bdad94cf1eb63f -size 29997 +oid sha256:e05535205f9dc259b38adefb04fa7a29bbe3299a144f41297d07ea00808c2fa1 +size 31978 diff --git a/resources/realsense_nvblox_humans.gif b/resources/realsense_nvblox_humans.gif new file mode 100644 index 00000000..3d4361f8 --- /dev/null +++ b/resources/realsense_nvblox_humans.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:665667c3dbc2ffb5d782e0d2b303d7f3558a562286cd0e943bdce1795ef755be +size 9012829 diff --git a/resources/system_diagram_humans.png b/resources/system_diagram_humans.png new file mode 100644 index 00000000..8c09edc3 --- /dev/null +++ b/resources/system_diagram_humans.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7a7a590d1c1c6953d306cf885270f49b3f11da4f36826d0c7e15447fe22be9b +size 1651425 diff --git a/resources/system_diagram_technical_details.jpg b/resources/system_diagram_technical_details.jpg new file mode 100644 index 00000000..2b00464e --- /dev/null +++ b/resources/system_diagram_technical_details.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea8e7072790279e2cf25da660121ebc2e6783ca94c0683d4eb5e2e4074fed410 +size 72620