diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..bfce055
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,10 @@
+BasedOnStyle: Google
+UseTab: Never
+IndentWidth: 4
+AccessModifierOffset: -4
+ColumnLimit: 100
+BinPackParameters: false
+SortIncludes: true
+Standard: c++17
+DerivePointerAlignment: false
+PointerAlignment: Right
diff --git a/.cmake-format.yaml b/.cmake-format.yaml
new file mode 100644
index 0000000..36140f3
--- /dev/null
+++ b/.cmake-format.yaml
@@ -0,0 +1,4 @@
+enable_markup: false
+line_width: 120
+format:
+ max_subgroups_hwrap: 5
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..36c9760
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @nachovizzo @benemer @tizianoGuadagnino
diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml
new file mode 100644
index 0000000..7984caa
--- /dev/null
+++ b/.github/workflows/cpp.yml
@@ -0,0 +1,56 @@
+name: C++ API
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+
+env:
+ BUILD_TYPE: Release
+
+jobs:
+ cpp_api:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup cmake
+ uses: jwlawson/actions-setup-cmake@v1.13
+ with:
+ cmake-version: "3.25.x"
+ - name: Configure CMake
+ run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${{github.workspace}}/cpp/kinematic_icp
+ - name: Build
+ run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+
+ # As the previous job will always install the dependencies from cmake, and this is guaranteed to
+ # work, we also want to support dev sandboxes where the main dependencies are already
+ # pre-installed in the system. For now, we only support dev machines under a GNU/Linux
+ # environmnets. If you are reading this and need the same functionallity in Windows/macOS please
+ # open a ticket.
+ cpp_api_dev:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: ~/.apt/cache
+ key: ${{ runner.os }}-apt-${{ hashFiles('**/ubuntu_dependencies.yml') }}
+ restore-keys: |
+ ${{ runner.os }}-apt-
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y build-essential cmake git libeigen3-dev libtbb-dev
+ - name: Configure CMake
+ run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${{github.workspace}}/cpp/kinematic_icp
+ - name: Build
+ run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 0000000..87740ba
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,15 @@
+name: Style Check
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+
+jobs:
+ pre-commit:
+ name: Pre-commit checks
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: pre-commit/action@v3.0.0
diff --git a/.github/workflows/ros.yml b/.github/workflows/ros.yml
new file mode 100644
index 0000000..60c7b28
--- /dev/null
+++ b/.github/workflows/ros.yml
@@ -0,0 +1,24 @@
+name: ROS nodes
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+
+jobs:
+ ros2_node:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ release: [humble, iron, jazzy, rolling]
+ container: osrf/ros:${{ matrix.release }}-desktop
+ steps:
+ - name: Setup cmake
+ uses: jwlawson/actions-setup-cmake@v1.13
+ with:
+ cmake-version: "3.25.x"
+ - uses: actions/checkout@v3
+ - name: Run colcon
+ run: source /opt/ros/${{ matrix.release }}/setup.bash && colcon build --event-handlers console_direct+
+ shell: bash
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca717a6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,117 @@
+# Created by https://www.toptal.com/developers/gitignore/api/c++
+# Edit at https://www.toptal.com/developers/gitignore?templates=c++
+
+### C++ ###
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# End of https://www.toptal.com/developers/gitignore/api/c++
+n# Created by https://www.toptal.com/developers/gitignore/api/cmake
+# Edit at https://www.toptal.com/developers/gitignore?templates=cmake
+
+### CMake ###
+CMakeLists.txt.user
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Testing
+Makefile
+cmake_install.cmake
+install_manifest.txt
+compile_commands.json
+CTestTestfile.cmake
+_deps
+
+### CMake Patch ###
+# External projects
+*-prefix/
+
+# End of https://www.toptal.com/developers/gitignore/api/cmake
+n# Created by https://www.toptal.com/developers/gitignore/api/ros
+# Edit at https://www.toptal.com/developers/gitignore?templates=ros
+
+### ROS ###
+devel/
+logs/
+build/
+bin/
+lib/
+msg_gen/
+srv_gen/
+msg/*Action.msg
+msg/*ActionFeedback.msg
+msg/*ActionGoal.msg
+msg/*ActionResult.msg
+msg/*Feedback.msg
+msg/*Goal.msg
+msg/*Result.msg
+msg/_*.py
+build_isolated/
+devel_isolated/
+
+# Generated by dynamic reconfigure
+*.cfgc
+/cfg/cpp/
+/cfg/*.py
+
+# Ignore generated docs
+*.dox
+*.wikidoc
+
+# eclipse stuff
+.project
+.cproject
+
+# qcreator stuff
+CMakeLists.txt.user
+
+srv/_*.py
+*.pcd
+*.pyc
+qtcreator-*
+*.user
+
+/planning/cfg
+/planning/docs
+/planning/src
+
+*~
+
+# Emacs
+.#*
+
+# Catkin custom files
+CATKIN_IGNORE
+
+# End of https://www.toptal.com/developers/gitignore/api/ros
+n
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..37cfe77
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,18 @@
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: trailing-whitespace
+ exclude: \.patch$
+ - id: end-of-file-fixer
+ exclude: \.patch$
+ - id: check-yaml
+ - id: check-added-large-files
+- repo: https://github.com/pre-commit/mirrors-clang-format
+ rev: v14.0.0
+ hooks:
+ - id: clang-format
+- repo: https://github.com/ahans/cmake-format-precommit
+ rev: 8e52fb6506f169dddfaa87f88600d765fca48386
+ hooks:
+ - id: cmake-format
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..66982c8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+Stachniss.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..58a34ff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,106 @@
+
+
Kinematic-ICP
+
+
+
+
+
+
Paper
+
•
+
Contact Us
+
+
+
+[Kinematic-ICP](https://www.ipb.uni-bonn.de/wp-content/papercite-data/pdf/kissteam2025icra.pdf) is a LiDAR odometry approach that explicitly incorporates the kinematic constraints of mobile robots into the classic point-to-point ICP algorithm.
+
+
+
+
+
+# How to Build
+
+Our system operates on ROS2, supporting **ROS Humble**, **Iron**, and **Jazzy**. To build and run Kinematic-ICP, follow these steps:
+
+1. **Clone the Repository**:
+ ```sh
+ cd /src
+ git clone https://github.com/PRBonn/kinematic-icp
+ cd ..
+ ```
+
+2. **Ensure all Dependencies are Installed**:
+ ```sh
+ rosdep install --from-paths src --ignore-src -r -y
+ ```
+
+3. **Build the Workspace**:
+ ```sh
+ colcon build
+ ```
+
+4. **Source the Setup Script**:
+ ```sh
+ source ./install/setup.bash
+ ```
+
+
+# TF Requirements
+
+Kinematic ICP can enhance existing odometry using a 3D LiDAR. However, there are specific requirements regarding motion and transformations due to the assumption that the robot operates on a unicycle kinematic model. Below are the key requirements:
+
+1. **Planar Movement**: The robot is expected to move primarily on a planar surface.
+
+2. **Existing Odometry**: An existing odometry source must be provided, such as the platform's wheel odometry. In the ROS ecosystem, this means that another node must publish the `tf` transformation between `base_link` and `odom`. (Note: The names may vary and can be adjusted in the pipeline parameters.)
+
+3. **Static Transform for LiDAR**: To utilize the platform's motion model effectively, the system needs to compute the pose in `base_link`. Therefore, a static `tf` transform between `base_link` and the LiDAR frame (extrinsic calibration) is required. If this calibration is significantly inaccurate, it may compromise system performance.
+
+Finally, Kinematic ICP will publish a new `tf` transformation between `base_link` and `odom_lidar`.
+
+# Running the System
+
+This system offers two entry points for deployment, depending on your use case: one for real-time operation and one for offline processing.
+
+## 1. Real-Time Deployment: `online_node`
+
+Use the `online_node` to run the system on a robotics platform. The only required parameter is the **lidar_topic**. You can start the system using the following command:
+
+```sh
+ros2 launch kinematic_icp online_node.launch.py lidar_topic:=
+```
+
+To enable simultaneous visualization through RViz, use the `visualize` flag set to `true`:
+
+```sh
+ros2 launch kinematic_icp online_node.launch.py lidar_topic:= visualize:=true
+```
+
+## 2. Offline Processing: `offline_node`
+
+For post-processing and analysis, the `offline_node` processes a ROS bag file at CPU speed, ensuring no frames are dropped. This mode is ideal for reviewing trajectory results, debugging, and speeding up bag file processing. You can launch the offline node with the following command:
+
+```sh
+ros2 launch kinematic_icp offline_node.launch.py lidar_topic:= bag_filename:=
+```
+
+RViz can also be used in this mode by setting the `visualize` flag to `true`. Additionally, the system will output a file in TUM format containing the estimated poses, named **_kinematic_poses_tum.txt**. This file is saved in the same directory as the ROS bag file by default.
+
+To specify a custom directory for the output file, use the `output_dir` parameter:
+
+```sh
+ros2 launch kinematic_icp offline_node.launch.py lidar_topic:= bag_filename:= output_dir:=
+```
+
+
+## Citation
+
+If you use this library for any academic work, please cite our original [paper](https://www.ipb.uni-bonn.de/wp-content/papercite-data/pdf/kissteam2025icra.pdf).
+
+```bibtex
+@article{kissteam2024arxiv,
+ author = {Guadagnino ,Tiziano and Mersch, Benedikt and Vizzo, Ignacio and Gupta, Saurabh and Malladi, Meher V.R. and Lobefaro, Luca and Doisy, Guillaume and Stachniss, Cyrill}
+ title = {{Kinematic-ICP: Enhancing LiDAR Odometry with Kinematic Constraints for Wheeled Mobile Robots Moving on Planar Surfaces
+ journal = arXiv Preprint,
+ year = {2024},
+ codeurl = {https://github.com/PRBonn/kinematic-icp},
+}
+```
diff --git a/cpp/COLCON_IGNORE b/cpp/COLCON_IGNORE
new file mode 100644
index 0000000..e69de29
diff --git a/cpp/kinematic_icp/CMakeLists.txt b/cpp/kinematic_icp/CMakeLists.txt
new file mode 100644
index 0000000..718f731
--- /dev/null
+++ b/cpp/kinematic_icp/CMakeLists.txt
@@ -0,0 +1,58 @@
+# MIT License
+#
+# Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+# Stachniss.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+cmake_minimum_required(VERSION 3.16...3.26)
+project(kinematic_icp_cpp VERSION 0.0.1 LANGUAGES CXX)
+
+# Setup build options for the underlying kiss dependency
+option(USE_CCACHE "Build using Ccache if found on the path" ON)
+option(USE_SYSTEM_EIGEN3 "Use system pre-installed Eigen" ON)
+option(USE_SYSTEM_SOPHUS "Use system pre-installed Sophus" OFF)
+option(USE_SYSTEM_TSL-ROBIN-MAP "Use system pre-installed tsl_robin" ON)
+option(USE_SYSTEM_TBB "Use system pre-installed oneAPI/tbb" ON)
+include(kiss_icp/kiss-icp.cmake)
+
+# KISS dependecies that are also used in Kinematic.
+# If the internal FetchContent from kiss is in use (through the above options)
+# find_package will use those dependencies, otherwise it will search system-wise
+find_package(Eigen3 QUIET NO_MODULE)
+find_package(Sophus QUIET NO_MODULE)
+find_package(TBB QUIET NO_MODULE)
+# ccache setup
+if(USE_CCACHE)
+ find_program(CCACHE_PATH ccache)
+ if(CCACHE_PATH)
+ set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
+ set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
+ message(STATUS "Using ccache: ${CCACHE_PATH}")
+ endif()
+endif()
+
+# Set build type (repeat here for C++ only consumers)
+set(CMAKE_BUILD_TYPE Release)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
+include(cmake/CompilerOptions.cmake)
+
+add_subdirectory(registration)
+add_subdirectory(pipeline)
diff --git a/cpp/kinematic_icp/LICENSE b/cpp/kinematic_icp/LICENSE
new file mode 100644
index 0000000..66982c8
--- /dev/null
+++ b/cpp/kinematic_icp/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+Stachniss.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/cpp/kinematic_icp/cmake/CompilerOptions.cmake b/cpp/kinematic_icp/cmake/CompilerOptions.cmake
new file mode 100644
index 0000000..ffe7fee
--- /dev/null
+++ b/cpp/kinematic_icp/cmake/CompilerOptions.cmake
@@ -0,0 +1,52 @@
+# We ackowledge the original license for this file
+# MIT License
+#
+# Copyright (c) 2022 Ignacio Vizzo, Tiziano Guadagnino, Benedikt Mersch, Cyrill
+# Stachniss.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+function(set_global_target_properties target)
+ target_compile_features(${target} PUBLIC cxx_std_17)
+ target_compile_definitions(${target} PUBLIC $<$:_USE_MATH_DEFINES>)
+ target_compile_options(
+ ${target}
+ PRIVATE # MSVC
+ $<$:/W4>
+ $<$:/WX>
+ # Clang/AppleClang
+ $<$:-fcolor-diagnostics>
+ $<$:-Werror>
+ $<$:-Wall>
+ $<$:-Wextra>
+ $<$:-Wconversion>
+ $<$:-Wno-sign-conversion>
+ # GNU
+ $<$:-fdiagnostics-color=always>
+ $<$:-Wall>
+ $<$:-Wextra>
+ $<$:-Wcast-align>
+ $<$:-Wcast-qual>
+ $<$:-Wconversion>
+ $<$:-Wdisabled-optimization>
+ $<$:-Woverloaded-virtual>)
+ set(INCLUDE_DIRS ${PROJECT_SOURCE_DIR})
+ get_filename_component(INCLUDE_DIRS ${INCLUDE_DIRS} PATH)
+ target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+ PUBLIC $ $)
+endfunction()
diff --git a/cpp/kinematic_icp/kiss_icp/LICENSE b/cpp/kinematic_icp/kiss_icp/LICENSE
new file mode 100644
index 0000000..d94e830
--- /dev/null
+++ b/cpp/kinematic_icp/kiss_icp/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2022 Ignacio Vizzo, Tiziano Guadagnino, Benedikt Mersch, Cyrill
+Stachniss.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/cpp/kinematic_icp/kiss_icp/kiss-icp.cmake b/cpp/kinematic_icp/kiss_icp/kiss-icp.cmake
new file mode 100644
index 0000000..11bfe59
--- /dev/null
+++ b/cpp/kinematic_icp/kiss_icp/kiss-icp.cmake
@@ -0,0 +1,31 @@
+# MIT License
+#
+# Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+# Stachniss.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# Silence timestamp warning
+if(CMAKE_VERSION VERSION_GREATER 3.24)
+ cmake_policy(SET CMP0135 OLD)
+endif()
+
+include(FetchContent)
+FetchContent_Declare(kiss_icp URL https://github.com/PRBonn/kiss-icp/archive/refs/tags/v1.1.0.tar.gz SOURCE_SUBDIR
+ cpp/kiss_icp)
+FetchContent_MakeAvailable(kiss_icp)
diff --git a/cpp/kinematic_icp/pipeline/CMakeLists.txt b/cpp/kinematic_icp/pipeline/CMakeLists.txt
new file mode 100644
index 0000000..25fa5b3
--- /dev/null
+++ b/cpp/kinematic_icp/pipeline/CMakeLists.txt
@@ -0,0 +1,26 @@
+# MIT License
+#
+# Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+# Stachniss.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+add_library(kinematic_icp_pipeline STATIC)
+target_sources(kinematic_icp_pipeline PRIVATE KinematicICP.cpp)
+target_link_libraries(kinematic_icp_pipeline PUBLIC kinematic_icp_registration kiss_icp_pipeline)
+set_global_target_properties(kinematic_icp_pipeline)
diff --git a/cpp/kinematic_icp/pipeline/KinematicICP.cpp b/cpp/kinematic_icp/pipeline/KinematicICP.cpp
new file mode 100644
index 0000000..c93e878
--- /dev/null
+++ b/cpp/kinematic_icp/pipeline/KinematicICP.cpp
@@ -0,0 +1,90 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#include "KinematicICP.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+auto transform_points(const std::vector &points, const Sophus::SE3d &pose) {
+ std::vector points_transformed(points.size());
+ std::transform(points.cbegin(), points.cend(), points_transformed.begin(),
+ [&](const auto &point) { return pose * point; });
+ return points_transformed;
+}
+
+auto Voxelize(const std::vector &frame, const double voxel_size) {
+ const std::vector &frame_downsample =
+ kiss_icp::VoxelDownsample(frame, voxel_size * 0.5);
+ const std::vector &source =
+ kiss_icp::VoxelDownsample(frame_downsample, voxel_size * 1.5);
+ return std::make_tuple(source, frame_downsample);
+}
+} // namespace
+namespace kinematic_icp::pipeline {
+
+KinematicICP::Vector3dVectorTuple KinematicICP::RegisterFrame(
+ const std::vector &frame,
+ const std::vector ×tamps,
+ const Sophus::SE3d &lidar_to_base,
+ const Sophus::SE3d &relative_odometry) {
+ const auto &deskew_frame = [&]() -> std::vector {
+ if (!config_.deskew || timestamps.empty()) return frame;
+ return kiss_icp::DeSkewScan(frame, timestamps,
+ lidar_to_base.inverse() * relative_odometry * lidar_to_base);
+ }();
+ const auto &deskew_frame_in_base = transform_points(deskew_frame, lidar_to_base);
+ // Preprocess the input cloud
+ const auto &cropped_frame =
+ kiss_icp::Preprocess(deskew_frame_in_base, config_.max_range, config_.min_range);
+
+ // Voxelize
+ const auto &[source, frame_downsample] = Voxelize(cropped_frame, config_.voxel_size);
+
+ // Get adaptive_threshold
+ const double sigma = adaptive_threshold_.ComputeThreshold();
+
+ // Run ICP
+ const auto new_pose = registration_.ComputeRobotMotion(source, // frame
+ local_map_, // voxel_map
+ last_pose_, // last_pose
+ relative_odometry, // robot_motion
+ 3 * sigma); // max_correspondence_dist
+
+ // Compute the difference between the prediction and the actual estimate
+ const auto model_deviation = (last_pose_ * relative_odometry).inverse() * new_pose;
+
+ // Update step: threshold, local map and the last pose
+ adaptive_threshold_.UpdateModelDeviation(model_deviation);
+ local_map_.Update(frame_downsample, new_pose);
+ last_pose_ = new_pose;
+
+ // Return the (deskew) input raw scan (frame) and the points used for
+ // registration (source)
+ return {deskew_frame_in_base, source};
+}
+} // namespace kinematic_icp::pipeline
diff --git a/cpp/kinematic_icp/pipeline/KinematicICP.hpp b/cpp/kinematic_icp/pipeline/KinematicICP.hpp
new file mode 100644
index 0000000..2aff12e
--- /dev/null
+++ b/cpp/kinematic_icp/pipeline/KinematicICP.hpp
@@ -0,0 +1,81 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "kinematic_icp/registration/Registration.hpp"
+
+namespace kinematic_icp::pipeline {
+
+using Config = kiss_icp::pipeline::KISSConfig;
+
+class KinematicICP {
+public:
+ using Vector3dVector = std::vector;
+ using Vector3dVectorTuple = std::tuple;
+
+ explicit KinematicICP(const kiss_icp::pipeline::KISSConfig &config)
+ : registration_(
+ config.max_num_iterations, config.convergence_criterion, config.max_num_threads),
+ config_(config),
+ local_map_(config.voxel_size, config.max_range, config.max_points_per_voxel),
+ adaptive_threshold_(config.initial_threshold, config.min_motion_th, config.max_range) {}
+
+ Vector3dVectorTuple RegisterFrame(const std::vector &frame,
+ const std::vector ×tamps,
+ const Sophus::SE3d &lidar_to_base,
+ const Sophus::SE3d &relative_odometry);
+
+ inline void SetPose(const Sophus::SE3d &pose) {
+ last_pose_ = pose;
+ local_map_.Clear();
+ adaptive_threshold_ = kiss_icp::AdaptiveThreshold(config_.initial_threshold,
+ config_.min_motion_th, config_.max_range);
+ };
+
+ std::vector LocalMap() const { return local_map_.Pointcloud(); };
+
+ const kiss_icp::VoxelHashMap &VoxelMap() const { return local_map_; };
+ kiss_icp::VoxelHashMap &VoxelMap() { return local_map_; };
+
+ const Sophus::SE3d &pose() const { return last_pose_; }
+ Sophus::SE3d &pose() { return last_pose_; }
+
+private:
+ Sophus::SE3d last_pose_;
+ // Kinematic module
+ KinematicRegistration registration_;
+ // KISS-ICP pipeline modules
+ kiss_icp::pipeline::KISSConfig config_;
+ kiss_icp::VoxelHashMap local_map_;
+ kiss_icp::AdaptiveThreshold adaptive_threshold_;
+};
+
+} // namespace kinematic_icp::pipeline
diff --git a/cpp/kinematic_icp/registration/CMakeLists.txt b/cpp/kinematic_icp/registration/CMakeLists.txt
new file mode 100644
index 0000000..25d98cf
--- /dev/null
+++ b/cpp/kinematic_icp/registration/CMakeLists.txt
@@ -0,0 +1,25 @@
+# MIT License
+#
+# Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+# Stachniss.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+add_library(kinematic_icp_registration Registration.cpp)
+target_link_libraries(kinematic_icp_registration kiss_icp_core Eigen3::Eigen TBB::tbb Sophus::Sophus)
+set_global_target_properties(kinematic_icp_registration)
diff --git a/cpp/kinematic_icp/registration/Registration.cpp b/cpp/kinematic_icp/registration/Registration.cpp
new file mode 100644
index 0000000..1cc95b0
--- /dev/null
+++ b/cpp/kinematic_icp/registration/Registration.cpp
@@ -0,0 +1,193 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#include "Registration.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using LinearSystem = std::pair;
+using Correspondences = std::vector>;
+
+namespace {
+constexpr double epsilon = std::numeric_limits::min();
+
+double ComputeOdometryRegularization(const Correspondences &associations,
+ const Sophus::SE3d &odometry_initial_guess) {
+ const double sum_of_squared_residuals =
+ std::transform_reduce(associations.cbegin(), associations.cend(), 0.0, std::plus(),
+ [&](const auto &association) {
+ const auto &[source, target] = association;
+ return (odometry_initial_guess * source - target).squaredNorm();
+ });
+ const double N = static_cast(associations.size());
+ const double mean_squared_residual = sum_of_squared_residuals / N;
+ const double beta = 1.0 / (mean_squared_residual + epsilon);
+ return beta;
+}
+
+Correspondences DataAssociation(const std::vector &points,
+ const kiss_icp::VoxelHashMap &voxel_map,
+ const Sophus::SE3d &T,
+ const double max_correspondance_distance) {
+ using points_iterator = std::vector::const_iterator;
+ Correspondences associations;
+ associations.reserve(points.size());
+ associations = tbb::parallel_reduce(
+ // Range
+ tbb::blocked_range{points.cbegin(), points.cend()},
+ // Identity
+ associations,
+ // 1st lambda: Parallel computation
+ [&](const tbb::blocked_range &r, Correspondences res) -> Correspondences {
+ res.reserve(r.size());
+ std::for_each(r.begin(), r.end(), [&](const auto &point) {
+ const auto &[closest_neighbor, distance] = voxel_map.GetClosestNeighbor(T * point);
+ if (distance < max_correspondance_distance) {
+ res.emplace_back(point, closest_neighbor);
+ }
+ });
+ return res;
+ },
+ // 2nd lambda: Parallel reduction
+ [](Correspondences a, const Correspondences &b) -> Correspondences {
+ a.insert(a.end(), //
+ std::make_move_iterator(b.cbegin()), //
+ std::make_move_iterator(b.cend()));
+ return a;
+ });
+
+ return associations;
+}
+
+Eigen::Vector2d ComputePerturbation(const Correspondences &correspondences,
+ const Sophus::SE3d ¤t_estimate,
+ const double beta) {
+ auto compute_jacobian_and_residual = [&](const auto &correspondence) {
+ const auto &[source, target] = correspondence;
+ const Eigen::Vector3d residual = current_estimate * source - target;
+ Eigen::Matrix J;
+ J.col(0) = current_estimate.so3() * Eigen::Vector3d::UnitX();
+ J.col(1) = current_estimate.so3() * Eigen::Vector3d(-source.y(), source.x(), 0.0);
+ return std::make_tuple(J, residual);
+ };
+
+ auto sum_linear_systems = [](LinearSystem a, const LinearSystem &b) {
+ a.first += b.first;
+ a.second += b.second;
+ return a;
+ };
+
+ using correspondence_iterator = Correspondences::const_iterator;
+ auto [JTJ, JTr] = tbb::parallel_reduce(
+ // Range
+ tbb::blocked_range{correspondences.cbegin(),
+ correspondences.cend()},
+ // Identity
+ LinearSystem(Eigen::Matrix2d::Zero(), Eigen::Vector2d::Zero()),
+ // 1st Lambda: Parallel computation
+ [&](const tbb::blocked_range &r, LinearSystem J) -> LinearSystem {
+ return std::transform_reduce(
+ r.begin(), r.end(), J, sum_linear_systems, [&](const auto &correspondence) {
+ const auto &[J_r, residual] = compute_jacobian_and_residual(correspondence);
+ return LinearSystem(J_r.transpose() * J_r, // JTJ
+ J_r.transpose() * residual); // JTr
+ });
+ },
+ // 2nd Lambda: Parallel reduction of the private Jacboians
+ sum_linear_systems);
+ const double num_correspondences = static_cast(correspondences.size());
+
+ const Eigen::Matrix2d Omega = Eigen::Vector2d(beta, 0).asDiagonal();
+ JTJ /= num_correspondences;
+ JTr /= num_correspondences;
+ JTJ += Omega;
+ return -(JTJ.inverse() * JTr);
+}
+
+} // namespace
+
+namespace kinematic_icp {
+
+KinematicRegistration::KinematicRegistration(int max_num_iteration,
+ double convergence_criterion,
+ int max_num_threads)
+ : max_num_iterations_(max_num_iteration),
+ convergence_criterion_(convergence_criterion),
+ // Only manipulate the number of threads if the user specifies something
+ // greater than 0
+ max_num_threads_(max_num_threads > 0 ? max_num_threads
+ : tbb::this_task_arena::max_concurrency()) {
+ // This global variable requires static duration storage to be able to
+ // manipulate the max concurrency from TBB across the entire class
+ static const auto tbb_control_settings = tbb::global_control(
+ tbb::global_control::max_allowed_parallelism, static_cast(max_num_threads_));
+}
+
+Sophus::SE3d KinematicRegistration::ComputeRobotMotion(const std::vector &frame,
+ const kiss_icp::VoxelHashMap &voxel_map,
+ const Sophus::SE3d &last_robot_pose,
+ const Sophus::SE3d &relative_wheel_odometry,
+ const double max_correspondence_distance) {
+ Sophus::SE3d current_estimate = last_robot_pose * relative_wheel_odometry;
+ if (voxel_map.Empty()) return current_estimate;
+
+ auto motion_model = [](const Eigen::Vector2d &integrated_controls) {
+ Sophus::SE3d::Tangent dx = Sophus::SE3d::Tangent::Zero();
+ const double &displacement = integrated_controls(0);
+ const double &theta = integrated_controls(1);
+ dx(0) = displacement * std::sin(theta) / (theta + epsilon);
+ dx(1) = displacement * (1.0 - std::cos(theta)) / (theta + epsilon);
+ dx(5) = theta;
+ return Sophus::SE3d::exp(dx);
+ };
+ auto correspondences =
+ DataAssociation(frame, voxel_map, current_estimate, max_correspondence_distance);
+
+ const double regularization_term =
+ ComputeOdometryRegularization(correspondences, current_estimate);
+ // ICP-loop
+ for (int j = 0; j < max_num_iterations_; ++j) {
+ const auto dx = ComputePerturbation(correspondences, current_estimate, regularization_term);
+ const auto delta_motion = motion_model(dx);
+ current_estimate = current_estimate * delta_motion;
+ // Break loop
+ if (dx.norm() < convergence_criterion_) break;
+ correspondences =
+ DataAssociation(frame, voxel_map, current_estimate, max_correspondence_distance);
+ }
+ // Spit the final transformation
+ return current_estimate;
+}
+} // namespace kinematic_icp
diff --git a/cpp/kinematic_icp/registration/Registration.hpp b/cpp/kinematic_icp/registration/Registration.hpp
new file mode 100644
index 0000000..e67d105
--- /dev/null
+++ b/cpp/kinematic_icp/registration/Registration.hpp
@@ -0,0 +1,47 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace kinematic_icp {
+
+struct KinematicRegistration {
+ explicit KinematicRegistration(int max_num_iteration,
+ double convergence_criterion,
+ int max_num_threads);
+
+ Sophus::SE3d ComputeRobotMotion(const std::vector &frame,
+ const kiss_icp::VoxelHashMap &voxel_map,
+ const Sophus::SE3d &last_robot_pose,
+ const Sophus::SE3d &relative_wheel_odometry,
+ const double max_correspondence_distance);
+
+ int max_num_iterations_;
+ double convergence_criterion_;
+ int max_num_threads_;
+};
+} // namespace kinematic_icp
diff --git a/ros/CMakeLists.txt b/ros/CMakeLists.txt
new file mode 100644
index 0000000..d4e2678
--- /dev/null
+++ b/ros/CMakeLists.txt
@@ -0,0 +1,79 @@
+# MIT License
+#
+# Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+# Stachniss.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+cmake_minimum_required(VERSION 3.16...3.26)
+project(kinematic_icp VERSION 0.0.1 LANGUAGES CXX)
+
+set(CMAKE_BUILD_TYPE Release)
+
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../cpp/kinematic_icp ${CMAKE_CURRENT_BINARY_DIR}/kinematic_icp)
+
+find_package(ament_cmake REQUIRED)
+find_package(geometry_msgs REQUIRED)
+find_package(nav_msgs REQUIRED)
+find_package(rclcpp REQUIRED)
+find_package(rclcpp_components REQUIRED)
+find_package(rcutils REQUIRED)
+find_package(rosbag2_cpp REQUIRED)
+find_package(rosbag2_storage REQUIRED)
+find_package(sensor_msgs REQUIRED)
+find_package(std_msgs REQUIRED)
+find_package(std_srvs REQUIRED)
+find_package(tf2_ros REQUIRED)
+find_package(visualization_msgs REQUIRED)
+
+set(dependencies
+ geometry_msgs
+ nav_msgs
+ rclcpp
+ rclcpp_components
+ rcutils
+ rosbag2_cpp
+ rosbag2_storage
+ sensor_msgs
+ std_msgs
+ std_srvs
+ tf2_ros
+ visualization_msgs)
+
+add_library(
+ kinematic_icp_ros SHARED
+ src/kinematic_icp_ros/server/LidarOdometryServer.cpp src/kinematic_icp_ros/utils/RosUtils.cpp
+ src/kinematic_icp_ros/utils/RosbagUtils.cpp src/kinematic_icp_ros/nodes/online_node.cpp
+)# Adding it here for composition
+target_compile_features(kinematic_icp_ros PUBLIC cxx_std_17)
+target_include_directories(kinematic_icp_ros PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_link_libraries(kinematic_icp_ros kinematic_icp_pipeline "${cpp_typesupport_target}")
+ament_target_dependencies(kinematic_icp_ros ${dependencies})
+
+add_executable(kinematic_icp_offline_node src/kinematic_icp_ros/nodes/offline_node.cpp)
+target_link_libraries(kinematic_icp_offline_node PUBLIC kinematic_icp_ros)
+
+rclcpp_components_register_node(kinematic_icp_ros PLUGIN "kinematic_icp_ros::OnlineNode" EXECUTABLE
+ kinematic_icp_online_node)
+
+install(TARGETS kinematic_icp_ros kinematic_icp_online_node kinematic_icp_offline_node LIBRARY DESTINATION lib
+ RUNTIME DESTINATION lib/${PROJECT_NAME})
+
+install(DIRECTORY config launch rviz DESTINATION share/${PROJECT_NAME}/)
+
+ament_package()
diff --git a/ros/LICENSE b/ros/LICENSE
new file mode 100644
index 0000000..66982c8
--- /dev/null
+++ b/ros/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+Stachniss.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/ros/config/kinematic_icp_ros.yaml b/ros/config/kinematic_icp_ros.yaml
new file mode 100644
index 0000000..6d4e0b6
--- /dev/null
+++ b/ros/config/kinematic_icp_ros.yaml
@@ -0,0 +1,23 @@
+/**:
+ ros__parameters:
+ # Preprocessing
+ max_range: 100.0
+ min_range: 0.0
+ deskew: true
+
+ # Mapping parameters
+ voxel_size: 1.0
+ max_points_per_voxel: 20
+
+ # Adaptive threshold
+ initial_threshold: 2.0
+ min_motion_th: 0.1
+
+ # Registration
+ max_num_iterations: 10
+ convergence_criterion: 0.001
+ max_num_threads: 1
+
+ # Covariance diagonal values
+ orientation_covariance: 0.1
+ position_covariance: 0.1
diff --git a/ros/include/kinematic_icp_ros/nodes/offline_node.hpp b/ros/include/kinematic_icp_ros/nodes/offline_node.hpp
new file mode 100644
index 0000000..186a1f8
--- /dev/null
+++ b/ros/include/kinematic_icp_ros/nodes/offline_node.hpp
@@ -0,0 +1,61 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#pragma once
+
+#include
+#include
+
+// ROS
+#include
+#include
+#include
+
+#include "kinematic_icp_ros/server/LidarOdometryServer.hpp"
+#include "kinematic_icp_ros/utils/RosbagUtils.hpp"
+
+namespace kinematic_icp_ros {
+
+class OfflineNode {
+public:
+ OfflineNode() = delete;
+ explicit OfflineNode(const rclcpp::NodeOptions &options);
+
+ void writePosesInTumFormat();
+ void Run();
+
+private:
+ // Common for offline/online nodes
+ std::string pcl_topic_;
+ std::shared_ptr odometry_server_;
+ rclcpp::Node::SharedPtr node_;
+ // store data for the experiments
+ using PoseWithStamp = std::pair;
+ std::vector poses_with_timestamps_;
+ std::string output_dir_;
+ std::string poses_filename_;
+
+ // Offline node specifics
+ BagMultiplexer bag_multiplexer_;
+};
+
+} // namespace kinematic_icp_ros
diff --git a/ros/include/kinematic_icp_ros/nodes/online_node.hpp b/ros/include/kinematic_icp_ros/nodes/online_node.hpp
new file mode 100644
index 0000000..d23932b
--- /dev/null
+++ b/ros/include/kinematic_icp_ros/nodes/online_node.hpp
@@ -0,0 +1,54 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#pragma once
+
+#include
+#include
+
+// ROS
+#include
+#include
+
+#include "kinematic_icp_ros/server/LidarOdometryServer.hpp"
+
+namespace kinematic_icp_ros {
+
+class OnlineNode {
+public:
+ OnlineNode() = delete;
+ explicit OnlineNode(const rclcpp::NodeOptions &options);
+
+ // Neccesary for ROS 2 composition
+ rclcpp::node_interfaces::NodeBaseInterface::SharedPtr get_node_base_interface();
+
+private:
+ // Common for offline/online nodes
+ std::string pcl_topic_;
+ std::shared_ptr odometry_server_;
+ rclcpp::Node::SharedPtr node_;
+
+ // Online node specifics
+ rclcpp::Subscription::SharedPtr pointcloud_sub_;
+};
+
+} // namespace kinematic_icp_ros
diff --git a/ros/include/kinematic_icp_ros/server/LidarOdometryServer.hpp b/ros/include/kinematic_icp_ros/server/LidarOdometryServer.hpp
new file mode 100644
index 0000000..3fe8fab
--- /dev/null
+++ b/ros/include/kinematic_icp_ros/server/LidarOdometryServer.hpp
@@ -0,0 +1,107 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#pragma once
+
+#include "kinematic_icp/pipeline/KinematicICP.hpp"
+
+// ROS 2 C
+#include
+#include
+#include
+
+// ROS 2 C++
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// STL
+#include
+#include
+#include
+#include
+
+namespace kinematic_icp_ros {
+
+class LidarOdometryServer {
+public:
+ /// LidarOdometryServer constructor
+ LidarOdometryServer() = delete;
+ explicit LidarOdometryServer(rclcpp::Node::SharedPtr node);
+
+ /// Register new frame
+ void RegisterFrame(const sensor_msgs::msg::PointCloud2::ConstSharedPtr &msg);
+ std::unique_ptr kinematic_icp_;
+ builtin_interfaces::msg::Time current_stamp_;
+
+private:
+ /// Stream the estimated pose to ROS
+ void PublishOdometryMsg(const Sophus::SE3d &pose, const Sophus::SE3d::Tangent &velocity);
+
+ /// Stream the debugging point clouds for visualization (if required)
+ void PublishClouds(const std::vector frame,
+ const std::vector keypoints);
+
+ // Temporal initializaiton strattegy until we convert the odometry server to life cycle
+ void InitializePoseAndExtrinsic(const std::string &lidar_frame_id);
+
+ /// Tools for broadcasting TFs.
+ std::unique_ptr tf_broadcaster_;
+ std::unique_ptr tf2_buffer_;
+ std::unique_ptr tf2_listener_;
+ std::chrono::milliseconds tf_timeout_;
+ bool publish_odom_tf_;
+ bool invert_odom_tf_;
+ bool publish_debug_clouds_;
+ Sophus::SE3d sensor_to_base_footprint_;
+
+ /// Data publishers.
+ rclcpp::Publisher::SharedPtr odom_publisher_;
+ rclcpp::Publisher::SharedPtr frame_publisher_;
+ rclcpp::Publisher::SharedPtr kpoints_publisher_;
+ rclcpp::Publisher::SharedPtr map_publisher_;
+ rclcpp::Publisher::SharedPtr voxel_grid_pub_;
+
+ /// SetPose service
+ rclcpp::Service::SharedPtr set_pose_srv_;
+
+ /// Internal messages, models the current state that will be published by this node.
+ nav_msgs::msg::Odometry odom_msg_;
+ geometry_msgs::msg::TransformStamped tf_msg_;
+
+ /// Global/map coordinate frame.
+ std::string lidar_odom_frame_{"odom_lidar"};
+ std::string wheel_odom_frame_{"odom"};
+ std::string base_frame_{"base_link"};
+ // TF frame initialization flag
+ bool initialize_odom_node;
+
+ rclcpp::Node::SharedPtr node_;
+};
+
+} // namespace kinematic_icp_ros
diff --git a/ros/include/kinematic_icp_ros/utils/RosUtils.hpp b/ros/include/kinematic_icp_ros/utils/RosUtils.hpp
new file mode 100644
index 0000000..5001fba
--- /dev/null
+++ b/ros/include/kinematic_icp_ros/utils/RosUtils.hpp
@@ -0,0 +1,150 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#pragma once
+
+// tf2
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+// ROS 2
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace tf2 {
+
+inline geometry_msgs::msg::Transform sophusToTransform(const Sophus::SE3d &T) {
+ geometry_msgs::msg::Transform t;
+ t.translation.x = T.translation().x();
+ t.translation.y = T.translation().y();
+ t.translation.z = T.translation().z();
+
+ Eigen::Quaterniond q(T.so3().unit_quaternion());
+ t.rotation.x = q.x();
+ t.rotation.y = q.y();
+ t.rotation.z = q.z();
+ t.rotation.w = q.w();
+
+ return t;
+}
+
+inline geometry_msgs::msg::Pose sophusToPose(const Sophus::SE3d &T) {
+ geometry_msgs::msg::Pose t;
+ t.position.x = T.translation().x();
+ t.position.y = T.translation().y();
+ t.position.z = T.translation().z();
+
+ Eigen::Quaterniond q(T.so3().unit_quaternion());
+ t.orientation.x = q.x();
+ t.orientation.y = q.y();
+ t.orientation.z = q.z();
+ t.orientation.w = q.w();
+
+ return t;
+}
+
+inline Sophus::SE3d transformToSophus(const geometry_msgs::msg::TransformStamped &transform) {
+ const auto &t = transform.transform;
+ return Sophus::SE3d(
+ Sophus::SE3d::QuaternionType(t.rotation.w, t.rotation.x, t.rotation.y, t.rotation.z),
+ Sophus::SE3d::Point(t.translation.x, t.translation.y, t.translation.z));
+}
+
+inline Sophus::SE3d poseToSophus(const geometry_msgs::msg::Pose &pose) {
+ const auto &T = pose;
+ return Sophus::SE3d(Sophus::SE3d::QuaternionType(T.orientation.w, T.orientation.x,
+ T.orientation.y, T.orientation.z),
+ Sophus::SE3d::Point(T.position.x, T.position.y, T.position.z));
+}
+
+} // namespace tf2
+
+namespace kinematic_icp_ros::utils {
+using PointCloud2 = sensor_msgs::msg::PointCloud2;
+using PointField = sensor_msgs::msg::PointField;
+using Header = std_msgs::msg::Header;
+
+inline Sophus::SE3d LookupTransform(
+ const std::string &target_frame,
+ const std::string &source_frame,
+ const std::unique_ptr &tf2_buffer,
+ const rclcpp::Time &time = tf2_ros::toRclcpp(tf2::TimePointZero)) {
+ try {
+ auto tf = tf2_buffer->lookupTransform(target_frame, source_frame, time);
+ return tf2::transformToSophus(tf);
+ } catch (tf2::TransformException &ex) {
+ RCLCPP_WARN(rclcpp::get_logger("LookupTransform"), "%s", ex.what());
+ return {};
+ }
+}
+
+inline Sophus::SE3d LookupDeltaTransform(const std::string &target_frame,
+ const rclcpp::Time &target_time,
+ const std::string &source_frame,
+ const rclcpp::Time &source_time,
+ const std::string &fixed_frame,
+ const rclcpp::Duration timeout,
+ const std::unique_ptr &tf2_buffer) {
+ try {
+ auto tf = tf2_buffer->lookupTransform(target_frame, target_time, source_frame, source_time,
+ fixed_frame, timeout);
+ return tf2::transformToSophus(tf);
+ } catch (tf2::TransformException &ex) {
+ RCLCPP_WARN(rclcpp::get_logger("LookupTransform"), "%s", ex.what());
+ return {};
+ }
+}
+
+std::optional GetTimestampField(const PointCloud2::ConstSharedPtr msg);
+
+std::vector NormalizeTimestamps(const std::vector ×tamps);
+
+auto ExtractTimestampsFromMsg(const PointCloud2::ConstSharedPtr msg,
+ const PointField ×tamp_field);
+
+std::vector GetTimestamps(const PointCloud2::ConstSharedPtr msg);
+
+std::vector PointCloud2ToEigen(const PointCloud2::ConstSharedPtr msg,
+ const Sophus::SE3d &T = {});
+
+std::unique_ptr EigenToPointCloud2(const std::vector &points,
+ const Header &header);
+
+visualization_msgs::msg::Marker VoxelsToMarker(const std::vector &voxels,
+ const double voxel_size,
+ const Header &header);
+} // namespace kinematic_icp_ros::utils
diff --git a/ros/include/kinematic_icp_ros/utils/RosbagUtils.hpp b/ros/include/kinematic_icp_ros/utils/RosbagUtils.hpp
new file mode 100644
index 0000000..bd7102d
--- /dev/null
+++ b/ros/include/kinematic_icp_ros/utils/RosbagUtils.hpp
@@ -0,0 +1,89 @@
+// MIT License
+
+// Copyright (c) 2024 Tiziano Guadagnino, Benedikt Mersch, Ignacio Vizzo, Cyrill
+// Stachniss.
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+// tf2
+#include
+#include
+
+// ROS
+#include
+#include
+
+// rosbag headers
+#include
+#include
+
+class BufferableBag {
+public:
+ // Wrapper node to process the transforamtions present in the bagfile
+ struct TFBridge {
+ TFBridge(rclcpp::Node::SharedPtr node);
+ void ProcessTFMessage(std::shared_ptr msg) const;
+ std::unique_ptr tf_broadcaster;
+ std::unique_ptr tf_static_broadcaster;
+ rclcpp::Serialization serializer;
+ };
+
+ BufferableBag(const std::string &bag_filename,
+ const std::shared_ptr tf_bridge,
+ const std::string &topic,
+ const std::chrono::seconds buffer_size = std::chrono::seconds(1));
+
+ std::size_t message_count() const;
+ void BufferMessages();
+ rosbag2_storage::SerializedBagMessage PopNextMessage();
+ bool finished() const;
+
+private:
+ std::shared_ptr tf_bridge_;
+ std::unique_ptr bag_reader_;
+ std::queue buffer_;
+ std::chrono::seconds buffer_size_;
+ std::string topic_;
+ std::size_t message_count_{0};
+};
+
+class BagMultiplexer {
+public:
+ // assume we feed this bags in ordered fashion, plesae behave
+ void AddBag(BufferableBag &&bag);
+
+ // How many messages in total in all the bagfiles
+ std::size_t message_count() const;
+
+ rosbag2_storage::SerializedBagMessage GetNextMessage();
+ bool IsMessageAvailable() const;
+
+private:
+ std::vector bags_;
+ std::size_t current_index_{0};
+ std::size_t message_count_{0};
+};
diff --git a/ros/include/kinematic_icp_ros/utils/indicators.hpp b/ros/include/kinematic_icp_ros/utils/indicators.hpp
new file mode 100644
index 0000000..7f7a729
--- /dev/null
+++ b/ros/include/kinematic_icp_ros/utils/indicators.hpp
@@ -0,0 +1,3037 @@
+
+#ifndef INDICATORS_COLOR
+#define INDICATORS_COLOR
+
+namespace indicators {
+enum class Color { grey, red, green, yellow, blue, magenta, cyan, white, unspecified };
+}
+
+#endif
+
+#ifndef INDICATORS_FONT_STYLE
+#define INDICATORS_FONT_STYLE
+
+namespace indicators {
+enum class FontStyle { bold, dark, italic, underline, blink, reverse, concealed, crossed };
+}
+
+#endif
+
+#ifndef INDICATORS_PROGRESS_TYPE
+#define INDICATORS_PROGRESS_TYPE
+
+namespace indicators {
+enum class ProgressType { incremental, decremental };
+}
+
+#endif
+
+//!
+//! termcolor
+//! ~~~~~~~~~
+//!
+//! termcolor is a header-only c++ library for printing colored messages
+//! to the terminal. Written just for fun with a help of the Force.
+//!
+//! :copyright: (c) 2013 by Ihor Kalnytskyi
+//! :license: BSD, see LICENSE for details
+//!
+
+#ifndef TERMCOLOR_HPP_
+#define TERMCOLOR_HPP_
+
+#include
+#include
+#include
+
+// Detect target's platform and set some macros in order to wrap platform
+// specific code this library depends on.
+#if defined(_WIN32) || defined(_WIN64)
+#define TERMCOLOR_TARGET_WINDOWS
+#elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
+#define TERMCOLOR_TARGET_POSIX
+#endif
+
+// If implementation has not been explicitly set, try to choose one based on
+// target platform.
+#if !defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) && !defined(TERMCOLOR_USE_WINDOWS_API) && \
+ !defined(TERMCOLOR_USE_NOOP)
+#if defined(TERMCOLOR_TARGET_POSIX)
+#define TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES
+#define TERMCOLOR_AUTODETECTED_IMPLEMENTATION
+#elif defined(TERMCOLOR_TARGET_WINDOWS)
+#define TERMCOLOR_USE_WINDOWS_API
+#define TERMCOLOR_AUTODETECTED_IMPLEMENTATION
+#endif
+#endif
+
+// These headers provide isatty()/fileno() functions, which are used for
+// testing whether a standard stream refers to the terminal.
+#if defined(TERMCOLOR_TARGET_POSIX)
+#include
+#elif defined(TERMCOLOR_TARGET_WINDOWS)
+#if defined(_MSC_VER)
+#if !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+#endif
+#include
+#include
+#endif
+
+namespace termcolor {
+// Forward declaration of the `_internal` namespace.
+// All comments are below.
+namespace _internal {
+inline int colorize_index();
+inline FILE *get_standard_stream(const std::ostream &stream);
+inline bool is_colorized(std::ostream &stream);
+inline bool is_atty(const std::ostream &stream);
+
+#if defined(TERMCOLOR_TARGET_WINDOWS)
+inline void win_change_attributes(std::ostream &stream, int foreground, int background = -1);
+#endif
+} // namespace _internal
+
+inline std::ostream &colorize(std::ostream &stream) {
+ stream.iword(_internal::colorize_index()) = 1L;
+ return stream;
+}
+
+inline std::ostream &nocolorize(std::ostream &stream) {
+ stream.iword(_internal::colorize_index()) = 0L;
+ return stream;
+}
+
+inline std::ostream &reset(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[00m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, -1);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bold(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[1m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &dark(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[2m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &italic(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[3m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &underline(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[4m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, COMMON_LVB_UNDERSCORE);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &blink(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[5m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &reverse(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[7m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &concealed(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[8m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &crossed(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[9m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+template
+inline std::ostream &color(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ char command[12];
+ std::snprintf(command, sizeof(command), "\033[38;5;%dm", code);
+ stream << command;
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+template
+inline std::ostream &on_color(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ char command[12];
+ std::snprintf(command, sizeof(command), "\033[48;5;%dm", code);
+ stream << command;
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+template
+inline std::ostream &color(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ char command[20];
+ std::snprintf(command, sizeof(command), "\033[38;2;%d;%d;%dm", r, g, b);
+ stream << command;
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+template
+inline std::ostream &on_color(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ char command[20];
+ std::snprintf(command, sizeof(command), "\033[48;2;%d;%d;%dm", r, g, b);
+ stream << command;
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &grey(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[30m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ 0 // grey (black)
+ );
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &red(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[31m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_RED);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &green(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[32m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_GREEN);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &yellow(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[33m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_GREEN | FOREGROUND_RED);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &blue(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[34m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_BLUE);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &magenta(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[35m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_BLUE | FOREGROUND_RED);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &cyan(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[36m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_BLUE | FOREGROUND_GREEN);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &white(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[37m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bright_grey(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[90m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ 0 | FOREGROUND_INTENSITY // grey (black)
+ );
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bright_red(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[91m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_RED | FOREGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bright_green(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[92m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bright_yellow(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[93m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bright_blue(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[94m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, FOREGROUND_BLUE | FOREGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bright_magenta(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[95m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bright_cyan(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[96m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &bright_white(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[97m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(
+ stream, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_grey(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[40m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ 0 // grey (black)
+ );
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_red(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[41m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_RED);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_green(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[42m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_GREEN);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_yellow(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[43m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_GREEN | BACKGROUND_RED);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_blue(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[44m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_BLUE);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_magenta(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[45m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_BLUE | BACKGROUND_RED);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_cyan(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[46m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_GREEN | BACKGROUND_BLUE);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_white(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[47m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED);
+#endif
+ }
+
+ return stream;
+}
+
+inline std::ostream &on_bright_grey(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[100m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ 0 | BACKGROUND_INTENSITY // grey (black)
+ );
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_bright_red(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[101m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_RED | BACKGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_bright_green(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[102m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_GREEN | BACKGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_bright_yellow(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[103m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_bright_blue(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[104m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1, BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_bright_magenta(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[105m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_bright_cyan(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[106m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(stream, -1,
+ BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+#endif
+ }
+ return stream;
+}
+
+inline std::ostream &on_bright_white(std::ostream &stream) {
+ if (_internal::is_colorized(stream)) {
+#if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES)
+ stream << "\033[107m";
+#elif defined(TERMCOLOR_USE_WINDOWS_API)
+ _internal::win_change_attributes(
+ stream, -1, BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY);
+#endif
+ }
+
+ return stream;
+}
+
+//! Since C++ hasn't a way to hide something in the header from
+//! the outer access, I have to introduce this namespace which
+//! is used for internal purpose and should't be access from
+//! the user code.
+namespace _internal {
+// An index to be used to access a private storage of I/O streams. See
+// colorize / nocolorize I/O manipulators for details. Due to the fact
+// that static variables ain't shared between translation units, inline
+// function with local static variable is used to do the trick and share
+// the variable value between translation units.
+inline int colorize_index() {
+ static int colorize_index = std::ios_base::xalloc();
+ return colorize_index;
+}
+
+//! Since C++ hasn't a true way to extract stream handler
+//! from the a given `std::ostream` object, I have to write
+//! this kind of hack.
+inline FILE *get_standard_stream(const std::ostream &stream) {
+ if (&stream == &std::cout)
+ return stdout;
+ else if ((&stream == &std::cerr) || (&stream == &std::clog))
+ return stderr;
+
+ return nullptr;
+}
+
+// Say whether a given stream should be colorized or not. It's always
+// true for ATTY streams and may be true for streams marked with
+// colorize flag.
+inline bool is_colorized(std::ostream &stream) {
+ return is_atty(stream) || static_cast(stream.iword(colorize_index()));
+}
+
+//! Test whether a given `std::ostream` object refers to
+//! a terminal.
+inline bool is_atty(const std::ostream &stream) {
+ FILE *std_stream = get_standard_stream(stream);
+
+ // Unfortunately, fileno() ends with segmentation fault
+ // if invalid file descriptor is passed. So we need to
+ // handle this case gracefully and assume it's not a tty
+ // if standard stream is not detected, and 0 is returned.
+ if (!std_stream) return false;
+
+#if defined(TERMCOLOR_TARGET_POSIX)
+ return ::isatty(fileno(std_stream));
+#elif defined(TERMCOLOR_TARGET_WINDOWS)
+ return ::_isatty(_fileno(std_stream));
+#else
+ return false;
+#endif
+}
+
+#if defined(TERMCOLOR_TARGET_WINDOWS)
+//! Change Windows Terminal colors attribute. If some
+//! parameter is `-1` then attribute won't changed.
+inline void win_change_attributes(std::ostream &stream, int foreground, int background) {
+ // yeah, i know.. it's ugly, it's windows.
+ static WORD defaultAttributes = 0;
+
+ // Windows doesn't have ANSI escape sequences and so we use special
+ // API to change Terminal output color. That means we can't
+ // manipulate colors by means of "std::stringstream" and hence
+ // should do nothing in this case.
+ if (!_internal::is_atty(stream)) return;
+
+ // get terminal handle
+ HANDLE hTerminal = INVALID_HANDLE_VALUE;
+ if (&stream == &std::cout)
+ hTerminal = GetStdHandle(STD_OUTPUT_HANDLE);
+ else if (&stream == &std::cerr)
+ hTerminal = GetStdHandle(STD_ERROR_HANDLE);
+
+ // save default terminal attributes if it unsaved
+ if (!defaultAttributes) {
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if (!GetConsoleScreenBufferInfo(hTerminal, &info)) return;
+ defaultAttributes = info.wAttributes;
+ }
+
+ // restore all default settings
+ if (foreground == -1 && background == -1) {
+ SetConsoleTextAttribute(hTerminal, defaultAttributes);
+ return;
+ }
+
+ // get current settings
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if (!GetConsoleScreenBufferInfo(hTerminal, &info)) return;
+
+ if (foreground != -1) {
+ info.wAttributes &= ~(info.wAttributes & 0x0F);
+ info.wAttributes |= static_cast(foreground);
+ }
+
+ if (background != -1) {
+ info.wAttributes &= ~(info.wAttributes & 0xF0);
+ info.wAttributes |= static_cast(background);
+ }
+
+ SetConsoleTextAttribute(hTerminal, info.wAttributes);
+}
+#endif // TERMCOLOR_TARGET_WINDOWS
+
+} // namespace _internal
+
+} // namespace termcolor
+
+#undef TERMCOLOR_TARGET_POSIX
+#undef TERMCOLOR_TARGET_WINDOWS
+
+#if defined(TERMCOLOR_AUTODETECTED_IMPLEMENTATION)
+#undef TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES
+#undef TERMCOLOR_USE_WINDOWS_API
+#endif
+
+#endif // TERMCOLOR_HPP_
+
+#ifndef INDICATORS_TERMINAL_SIZE
+#define INDICATORS_TERMINAL_SIZE
+#include
+
+#if defined(_WIN32)
+#include
+
+namespace indicators {
+
+static inline std::pair terminal_size() {
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ int cols, rows;
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
+ cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
+ rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ return {static_cast(rows), static_cast(cols)};
+}
+
+static inline size_t terminal_width() { return terminal_size().second; }
+
+} // namespace indicators
+
+#else
+
+#include //ioctl() and TIOCGWINSZ
+#include // for STDOUT_FILENO
+
+namespace indicators {
+
+static inline std::pair terminal_size() {
+ struct winsize size {};
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &size);
+ return {static_cast(size.ws_row), static_cast(size.ws_col)};
+}
+
+static inline size_t terminal_width() { return terminal_size().second; }
+
+} // namespace indicators
+
+#endif
+
+#endif
+
+/*
+Activity Indicators for Modern C++
+https://github.com/p-ranav/indicators
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2019 Dawid Pilarski .
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+#ifndef INDICATORS_SETTING
+#define INDICATORS_SETTING
+
+#include
+// #include
+// #include
+// #include
+#include
+#include
+#include
+#include
+#include
+
+namespace indicators {
+
+namespace details {
+
+template
+struct if_else;
+
+template <>
+struct if_else {
+ using type = std::true_type;
+};
+
+template <>
+struct if_else {
+ using type = std::false_type;
+};
+
+template
+struct if_else_type;
+
+template
+struct if_else_type {
+ using type = True;
+};
+
+template
+struct if_else_type {
+ using type = False;
+};
+
+template
+struct conjuction;
+
+template <>
+struct conjuction<> : std::true_type {};
+
+template
+struct conjuction
+ : if_else_type>::type {};
+
+template
+struct disjunction;
+
+template <>
+struct disjunction<> : std::false_type {};
+
+template
+struct disjunction
+ : if_else_type>::type {};
+
+enum class ProgressBarOption {
+ bar_width = 0,
+ prefix_text,
+ postfix_text,
+ start,
+ end,
+ fill,
+ lead,
+ remainder,
+ max_postfix_text_len,
+ completed,
+ show_percentage,
+ show_elapsed_time,
+ show_remaining_time,
+ saved_start_time,
+ foreground_color,
+ spinner_show,
+ spinner_states,
+ font_styles,
+ hide_bar_when_complete,
+ min_progress,
+ max_progress,
+ progress_type,
+ stream
+};
+
+template
+struct Setting {
+ template ::value>::type>
+ explicit Setting(Args &&...args) : value(std::forward(args)...) {}
+ Setting(const Setting &) = default;
+ Setting(Setting &&) = default;
+
+ static constexpr auto id = Id;
+ using type = T;
+
+ T value{};
+};
+
+template
+struct is_setting : std::false_type {};
+
+template
+struct is_setting> : std::true_type {};
+
+template
+struct are_settings : if_else...>::value>::type {};
+
+template <>
+struct are_settings<> : std::true_type {};
+
+template
+struct is_setting_from_tuple;
+
+template
+struct is_setting_from_tuple> : std::true_type {};
+
+template
+struct is_setting_from_tuple>
+ : if_else...>::value>::type {};
+
+template
+struct are_settings_from_tuple
+ : if_else...>::value>::type {};
+
+template
+struct always_true {
+ static constexpr auto value = true;
+};
+
+template
+Default &&get_impl(Default &&def) {
+ return std::forward(def);
+}
+
+template
+auto get_impl(Default && /*def*/, T &&first, Args &&.../*tail*/) ->
+ typename std::enable_if<(std::decay::type::id == Id),
+ decltype(std::forward(first))>::type {
+ return std::forward(first);
+}
+
+template
+auto get_impl(Default &&def, T && /*first*/, Args &&...tail) ->
+ typename std::enable_if<(std::decay::type::id != Id),
+ decltype(get_impl(std::forward