From a59d15417d8b4f6f884fac51d4ee1baa25135504 Mon Sep 17 00:00:00 2001 From: juliangaal Date: Sat, 20 Jan 2024 15:06:48 +0100 Subject: [PATCH] Hello, World --- .clang-format | 135 ++++++++ .dockerignore | 2 + .gitattributes | 1 + .github/workflows/build_nanoflann_pcl.yml | 28 ++ .gitignore | 362 ++++++++++++++++++++ CMakeLists.txt | 96 ++++++ LICENSE | 8 + README.md | 190 ++++++++++ benchmark/data/bun.pcd | 3 + benchmark/data/carpark.pcd | 3 + benchmark/data/cloud_0043.pcd | 3 + benchmark/data/cloud_0368.pcd | 3 + benchmark/data/cloud_0422.pcd | 3 + benchmark/data/cloud_0601.pcd | 3 + benchmark/data/scan_006.pcd | 3 + benchmark/data/scan_007.pcd | 3 + benchmark/real/benchmark_insertion.cpp | 144 ++++++++ benchmark/real/benchmark_insertion.xml | 129 +++++++ benchmark/real/benchmark_knn_search.cpp | 200 +++++++++++ benchmark/real/benchmark_knn_search.xml | 125 +++++++ benchmark/real/benchmark_radius_search.cpp | 200 +++++++++++ benchmark/real/benchmark_radius_search.xml | 125 +++++++ benchmark/synth/benchmark_insertion.cpp | 113 ++++++ benchmark/synth/benchmark_insertion.xml | 95 +++++ benchmark/synth/benchmark_knn_search.cpp | 100 ++++++ benchmark/synth/benchmark_knn_search.xml | 65 ++++ benchmark/synth/benchmark_radius_search.cpp | 147 ++++++++ benchmark/synth/benchmark_radius_search.xml | 95 +++++ cmake/benchmark.cmake | 6 + cmake/nanoflann_pclConfig.cmake.in | 4 + docker/Dockerfile | 43 +++ example/CMakeLists.txt | 13 + example/nanoflann_pcl_example.cpp | 31 ++ include/nanoflann_pcl/nanoflann.hpp | 3 + include/nanoflann_pcl/nanoflann_adapter.hpp | 141 ++++++++ test/test_nanoflann_pcl.cpp | 60 ++++ util/catch2_postprocessing.py | 64 ++++ util/util.h | 45 +++ 38 files changed, 2794 insertions(+) create mode 100644 .clang-format create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 .github/workflows/build_nanoflann_pcl.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 benchmark/data/bun.pcd create mode 100644 benchmark/data/carpark.pcd create mode 100644 benchmark/data/cloud_0043.pcd create mode 100644 benchmark/data/cloud_0368.pcd create mode 100644 benchmark/data/cloud_0422.pcd create mode 100644 benchmark/data/cloud_0601.pcd create mode 100644 benchmark/data/scan_006.pcd create mode 100644 benchmark/data/scan_007.pcd create mode 100644 benchmark/real/benchmark_insertion.cpp create mode 100644 benchmark/real/benchmark_insertion.xml create mode 100644 benchmark/real/benchmark_knn_search.cpp create mode 100644 benchmark/real/benchmark_knn_search.xml create mode 100644 benchmark/real/benchmark_radius_search.cpp create mode 100644 benchmark/real/benchmark_radius_search.xml create mode 100644 benchmark/synth/benchmark_insertion.cpp create mode 100644 benchmark/synth/benchmark_insertion.xml create mode 100644 benchmark/synth/benchmark_knn_search.cpp create mode 100644 benchmark/synth/benchmark_knn_search.xml create mode 100644 benchmark/synth/benchmark_radius_search.cpp create mode 100644 benchmark/synth/benchmark_radius_search.xml create mode 100644 cmake/benchmark.cmake create mode 100644 cmake/nanoflann_pclConfig.cmake.in create mode 100644 docker/Dockerfile create mode 100644 example/CMakeLists.txt create mode 100644 example/nanoflann_pcl_example.cpp create mode 100644 include/nanoflann_pcl/nanoflann.hpp create mode 100644 include/nanoflann_pcl/nanoflann_adapter.hpp create mode 100644 test/test_nanoflann_pcl.cpp create mode 100644 util/catch2_postprocessing.py create mode 100644 util/util.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..427dc0f --- /dev/null +++ b/.clang-format @@ -0,0 +1,135 @@ +# play with config here: https://zed0.co.uk/clang-format-configurator/ +--- +Language: Cpp +#BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: CaseSensitive +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptControlMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseTab: Never diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9ef9604 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +build + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e197573 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +benchmark/data/*.pcd filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/build_nanoflann_pcl.yml b/.github/workflows/build_nanoflann_pcl.yml new file mode 100644 index 0000000..500ea79 --- /dev/null +++ b/.github/workflows/build_nanoflann_pcl.yml @@ -0,0 +1,28 @@ +name: Build nanoflann_pcl + +on: + push: + branches: [ master, feature/ci ] + paths-ignore: '**.md' + pull_request: + branches: [ master ] + paths-ignore: '**.md' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: checkout code + uses: actions/checkout@v2 + + - name: set up docker + uses: docker/setup-buildx-action@v1 + + - name: build docker image + env: + IMAGE_NAME: nanoflann_pcl + TAG: latest + run: | + docker buildx create --use + docker buildx build -t $IMAGE_NAME:$TAG --platform linux/amd64,linux/arm64 --build-arg NANOFLANN_VERSION=1.5.0 -f docker/Dockerfile . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f586f31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,362 @@ +# Created by https://www.toptal.com/developers/gitignore/api/c++,clion,vim,python,cmake +# Edit at https://www.toptal.com/developers/gitignore?templates=c++,clion,vim,python,cmake + +### 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 + +### CLion ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### CLion Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### 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/ + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.toptal.com/developers/gitignore/api/c++,clion,vim,python,cmake + +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..662bece --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,96 @@ +cmake_minimum_required(VERSION 3.0) + +project(nanoflann_pcl VERSION 0.1 + DESCRIPTION "A header-only c++ library that provides a Nanoflann adapter for Point Cloud Library (PCL) types" + HOMEPAGE_URL "https://github.com/juliangaal/nanoflann_pcl") + +include(cmake/benchmark.cmake) + +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_CXX_STANDARD 17) +set(NANOFLANN_VERSION "1.5.0" CACHE STRING "Nanoflann version") +set(CMAKE_INSTALL_PREFIX "/usr/local/" CACHE STRING "install prefix") +option(BUILD_TESTS OFF) + +add_compile_definitions(DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data") + + +find_package(PCL REQUIRED) +find_package(nanoflann ${NANOFLANN_VERSION} EXACT REQUIRED PATHS ${NANOFLANN_INSTALL_PREFIX}) + +if (BUILD_TESTS) + enable_testing() + find_package(Catch2 3 REQUIRED) + include_directories(include util) + include_directories(SYSTEM ${PCL_INCLUDE_DIRS}) + add_compile_options(-Werror -Wall -Wpedantic -Weffc++) + link_libraries(Catch2::Catch2WithMain nanoflann::nanoflann ${PCL_LIBRARIES}) + + # general tests + add_executable(test_nanoflann_pcl test/test_nanoflann_pcl.cpp) + + # synthetic benchmarks + add_executable(synth_benchmark_insertion benchmark/synth/benchmark_insertion.cpp) + add_executable(synth_benchmark_knn_search benchmark/synth/benchmark_knn_search.cpp) + add_executable(synth_benchmark_radius_search benchmark/synth/benchmark_radius_search.cpp) + + # real benchmarks + add_executable(real_benchmark_insertion benchmark/real/benchmark_insertion.cpp) + add_executable(real_benchmark_knn_search benchmark/real/benchmark_knn_search.cpp) + add_executable(real_benchmark_radius_search benchmark/real/benchmark_radius_search.cpp) + + # test targets + add_test(NAME test COMMAND test_nanoflann_pcl) + + # benchmarks + add_benchmark(synth_benchmark_insertion 10) + add_benchmark(synth_benchmark_radius_search 1000) + add_benchmark(synth_benchmark_knn_search 1000) + add_benchmark(real_benchmark_insertion 10) + add_benchmark(real_benchmark_radius_search 1000) + add_benchmark(real_benchmark_knn_search 1000) +endif() + +# support install targets. Thank you to the eye opening +# https://github.com/bernedom/SI/blob/main/cmake/SIConfig.cmake.in +# and accompaning https://dominikberner.ch/cmake-interface-lib/ +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +add_library(nanoflann_pcl INTERFACE) + +target_compile_features(nanoflann_pcl INTERFACE cxx_std_11) + +target_include_directories( + ${PROJECT_NAME} + INTERFACE $ + $ +) + +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/SI/cmake) + +install(EXPORT ${PROJECT_NAME}Targets + FILE ${PROJECT_NAME}Targets.cmake + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) + +install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME} DESTINATION include) + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ed62ef2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +Copyright 2024 Julian Gaal + +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..943a62d --- /dev/null +++ b/README.md @@ -0,0 +1,190 @@ +# `nanoflann_pcl` - [Nanoflann](https://github.com/jlblancoc/nanoflann) adaptor for [PCL](https://pointclouds.org/) + +This repo implements a [nanoflann adaptor](https://github.com/jlblancoc/nanoflann/blob/master/examples/pointcloud_adaptor_example.cpp) for efficient n-dimensional neighborhood search with PCL point types. + +**Why? Nanoflann is generally faster in robotics contexts than the built-in FLANN adapter for PCL pointclouds that ships with PCL.** + +You can expect this speedup, according to the benchmarks on real point clouds (see below): + +* insertion: **30%** performance boost +* knn search: **25%** performance boost +* radius search: **24%** performance boost + +Existing adapters [1](https://github.com/laboshinl/loam_velodyne/blob/master/include/loam_velodyne/nanoflann_pcl.h), [2](https://github.com/vectr-ucla/direct_lidar_inertial_odometry/blob/master/include/nano_gicp/nanoflann_adaptor.h) considered for [warpsense](https://github.com/juliangaal/warpsense) focus on knn search and either + +* **segfault** during radius search +* return **no results** during radius search + +This adaptor returns the correct results for both knn and radius search. + +## Usage + +```cpp +#include +#include +#include + +pcl::PointCloud::Ptr cloud(new pcl::PointCloud); +// fill cloud ... + +// util +constexpr int k = 100; +constexpr float radius = 10.0; +std::vector nanoflann_indices; +std::vector nanoflann_distances; + +// setup +nanoflann::KdTreeFLANN nanoflann_kdtree; +nanoflann_kdtree.setEpsilon(0); // default: 0 +nanoflann_kdtree.setSortedResults(true); // default: true +nanoflann_kdtree.setInputCloud(cloud); + +// search +nanoflann_kdtree.radiusSearch(cloud->points.back(), radius, nanoflann_indices, nanoflann_distances); +nanoflann_kdtree.nearestKSearch(cloud->points.back(), k, nanoflann_indices, nanoflann_distances); +``` + +All you need to do is install this header and nanoflann, then replace namespace `pcl::` with `nanoflann::`. + +## Installation + +2 Options: + +* Drop headers into you project and add to your include files +* use provided CMake install target: + + ```bash + mkdir build && cd build + cmake .. + cmake --build --config Release . + ctest -V -R test --output-on-failure # optional: run tests + cmake --build . --config Release --target install -- -j $(nproc) + ``` + and see `example/CMakeLists.txt` for how to integrate into a CMake project. + +## Supported Nanoflann versions + +* 1.5.0 (Released Jun 16, 2023) +* 1.5.1 +* 1.5.2 +* 1.5.3 + +It makes little sense to support even older versions, because even the upstream apt package installs versions 1.5.3+ (as of 01/24). Furthermore, Nanoflanns only real requirement is a C++11-compatible compiler (or greater) in case you want to simply drop the header file into your project. + +If you are dependent on earlier versions of Nanoflann, feel free to open a PR. Note that pre 1.5.0 has a different API for radius search, explained [here ('API changes')](https://github.com/jlblancoc/nanoflann/releases/tag/v1.5.0) + +## Benchmarks + +Benchmarks where performed using [Catch2 v3](https://github.com/catchorg/Catch2/blob/devel/docs/benchmarks.md) on a modest Intel i5-4670 @ 3.40GHz × 4. + +Notation: + +* point cloud sizes: denoted by Nk (N thousand points) +* number of neighbors searched: denoted by Nn (N neighbors) +* size of radius searched: denoted by Nm (N meters) +* ms = milliseconds +* Std = standard deviation across n iterations + +### Synthetic + +Pointclouds filled with random points. May or not be realistic for your usage scenario. + +#### Insertion + +Pointcloud: 1k - 100mio points. Iterations: 1k. + +| Test Case | Flann Mean (ms) | Nanoflann Mean (ms) | Flann Std (ms) | Nanoflann Std (ms) | +| --- | --- | --- | --- | --- | +| tree insertion 1k | 0.09820 | **0.05533** | **0.00165** | 0.00272 | +| tree insertion 10k | 1.31673 | **0.84244** | 0.00695 | **0.00434** | +| tree insertion 100k | 18.74870 | **12.52820** | 0.06198 | **0.02963** | +| tree insertion 1mio | 463.73200 | **304.11100** | **4.17225** | 14.31570 | +| tree insertion 10mio | 10045.50000 | **6094.39000** | 50.90730 | **35.52600** | +| tree insertion 100mio | 184126.00000 | **110075.00000** | 3621.70000 | **1174.57000** | + +#### KNN Search + +Pointcloud: 100k points. Iterations: 1k. Tested: 1 - 1000 neighbors + +| Test Case | Flann Mean (ms) | Nanoflann Mean (ms) | Flann Std (ms) | Nanoflann Std (ms) | +| --- | --- | --- | --- | --- | +| knn search 1 | 0.00132 | **0.00012** | 0.00005 | 0.00000 | +| knn search 10 | 0.00166 | **0.00036** | 0.00014 | **0.00001** | +| knn search 100 | 0.00514 | **0.00354** | 0.00027 | **0.00015** | +| knn search 1000 | **0.10350** | 0.27315 | **0.00309** | 0.00682 | + +#### Radius Search + +Pointcloud: 100k points. Iterations: 1k. 1m - 50m radius. + +| Test Case | Flann Mean (ms) | Nanoflann Mean (ms) | Flann Std (ms) | Nanoflann Std (ms) | +| --- | --- | --- | --- | --- | +| Radius search 1m | **0.00137** | 0.00014 | 0.00009 | **0.00003** | +| Radius search 10m | 0.00142 | **0.00014** | 0.00015 | **0.00001** | +| Radius search 20m | 0.00141 | **0.00014** | 0.00005 | **0.00000** | +| Radius search 30m | 0.00155 | **0.00027** | 0.00007 | **0.00001** | +| Radius search 40m | 0.00156 | **0.00027** | 0.00010 | **0.00002** | +| Radius search 50m | 0.00157 | **0.00027** | 0.00014 | **0.00001** | + +### Real-World pointclouds + +`benchmark/data` includes multiple scans from + +* Velodyne VLP32E in Hanover, provided by [University of Osnabrueck](http://kos.informatik.uni-osnabrueck.de/3Dscans/) +* Riegl VLZ terrestrial scanner scans, also provided by ^^ +* Bunny, provided by [Standford](https://graphics.stanford.edu/data/3Dscanrep/) + +#### Insertion + +Iterations: 10 + +| Test Case | Flann Mean (ms) | Nanoflann Mean (ms) | Flann Std (ms) | Nanoflann Std (ms) | +| --- | --- | --- | --- | --- | +| cloud_0601 3.7k | 0.41748 | **0.31421** | **0.00711** | 0.01105 | +| cloud_0422 9.7k | 1.06278 | **0.77641** | 0.04849 | **0.01556** | +| cloud_0043 13k | 1.48485 | **1.10457** | 0.07269 | **0.02060** | +| cloud_0368 18.8k | 2.12193 | **1.60147** | 0.02214 | **0.01090** | +| carpark 52k | 7.44440 | **5.85080** | 0.09779 | **0.04552** | +| bunny 40k | 4.25128 | **3.21900** | 0.03517 | **0.00950** | +| scan_006 560k | 93.51190 | **74.09750** | **1.39890** | 1.48065 | +| scan_007 825k | 154.64700 | **118.64000** | 1.93568 | **1.33667** | + + +#### KNN Search + +Iterations: 1k + +| Test Case | Flann Mean (ms) | Nanoflann Mean (ms) | Flann Std (ms) | Nanoflann Std (ms) | +| --- | --- | --- | --- | --- | +| cloud_0601 3.7k, 100n | 0.00710 | **0.00685** | 0.00151 | **0.00081** | +| cloud_0422 9.7k, 100n | 0.00389 | **0.00315** | 0.00073 | **0.00029** | +| cloud_0043 13k, 100n | 0.00646 | **0.00485** | 0.00129 | **0.00055** | +| cloud_0368 18.8k, 100n | 0.00505 | **0.00442** | 0.00042 | **0.00033** | +| bunny 40k, 100n | 0.00827 | **0.00509** | 0.00107 | **0.00081** | +| carpark 52k, 100n | 0.00464 | **0.00353** | **0.00063** | 0.00083 | +| scan_006 560k, 100n | 0.00673 | **0.00627** | **0.00127** | 0.00139 | +| scan_007 825k, 100n | 0.00516 | **0.00455** | **0.00081** | 0.00091 | + + +#### Radius Search + +Iterations: 1k + +| Test Case | Flann Mean (ms) | Nanoflann Mean (ms) | Flann Std (ms) | Nanoflann Std (ms) | +| --- | --- | --- | --- | --- | +| cloud_0601 3.7k, 10m | 0.01555 | **0.00930** | 0.00219 | **0.00116** | +| cloud_0422 9.7k, 10m | 0.39186 | **0.29293** | 0.03156 | **0.01990** | +| cloud_0043 13k, 10m | 0.52155 | **0.41522** | 0.02084 | **0.02042** | +| cloud_0368 18.8k, 10m | 0.69870 | **0.55135** | 0.03031 | **0.02405** | +| bunny 40k, 10m | 2.35269 | **1.83126** | **0.07192** | 0.07355 | +| carpark 52k, 10m | 2.61272 | **2.02952** | 0.14101 | **0.06963** | +| scan_006 560k, 10m | 2.88219 | **2.37084** | **0.10212** | 0.11535 | +| scan_007 825k, 10m | 2.45282 | **1.96704** | **0.09085** | 0.10605 | + + +### Reproduce Benchmarks + +* Run benchmark. For a list of benchmarks, see `ctest -N` and chose e.g. `ctest -V -R benchmark_insertion`. Catch2 will save `NAME_OF_BENCHMARK.xml` +* Process `NAME_OF_BENCHMARK.xml` with `catch2_postprocessing.py` in `util/` +* e.g. `python3 catch2_postprocessing.py benchmark_insertion.xml --categories "flann tree insertion" "nanoflann tree insertion"` +* if required, save csv file with `--export_csv` diff --git a/benchmark/data/bun.pcd b/benchmark/data/bun.pcd new file mode 100644 index 0000000..da751c0 --- /dev/null +++ b/benchmark/data/bun.pcd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1637b664cf81ed0b2b187eecc9d62e45908bb16c8778f6d75bf14bcc95cac9de +size 263629 diff --git a/benchmark/data/carpark.pcd b/benchmark/data/carpark.pcd new file mode 100644 index 0000000..811aa1d --- /dev/null +++ b/benchmark/data/carpark.pcd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c065aa404c0652d420ab18f3b897088110ab423dff394ac15fdd4d6bf31caa41 +size 1451019 diff --git a/benchmark/data/cloud_0043.pcd b/benchmark/data/cloud_0043.pcd new file mode 100644 index 0000000..16e0925 --- /dev/null +++ b/benchmark/data/cloud_0043.pcd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66d429e9a8dccbf52e1ecc9b97dd2fada16cc1ca5bd35ddc8997a6e10e56eec4 +size 161476 diff --git a/benchmark/data/cloud_0368.pcd b/benchmark/data/cloud_0368.pcd new file mode 100644 index 0000000..bfb4eec --- /dev/null +++ b/benchmark/data/cloud_0368.pcd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7edebc10b3e6922b045d2109905c735cb68627e0ead4f532753734d814fb05f +size 229444 diff --git a/benchmark/data/cloud_0422.pcd b/benchmark/data/cloud_0422.pcd new file mode 100644 index 0000000..ea1af9d --- /dev/null +++ b/benchmark/data/cloud_0422.pcd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f998415588e376353f233c3fb2694f6afa08c8c341bb9860f50328f097fe594a +size 120508 diff --git a/benchmark/data/cloud_0601.pcd b/benchmark/data/cloud_0601.pcd new file mode 100644 index 0000000..80edfad --- /dev/null +++ b/benchmark/data/cloud_0601.pcd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05bfdae6b8d1692caaddeb5e369e781e6df4faaa029bb2d75838835ee5705deb +size 49036 diff --git a/benchmark/data/scan_006.pcd b/benchmark/data/scan_006.pcd new file mode 100644 index 0000000..23d9367 --- /dev/null +++ b/benchmark/data/scan_006.pcd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0d947e9895f857741f8893a35e7280810595e6de1968424a2259d15fb52a0f2 +size 8953461 diff --git a/benchmark/data/scan_007.pcd b/benchmark/data/scan_007.pcd new file mode 100644 index 0000000..130ba88 --- /dev/null +++ b/benchmark/data/scan_007.pcd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:210085498df6cceac07649ac5f9fdf4999878e80674eed4233200c6d0d15473b +size 13211273 diff --git a/benchmark/real/benchmark_insertion.cpp b/benchmark/real/benchmark_insertion.cpp new file mode 100644 index 0000000..d407780 --- /dev/null +++ b/benchmark/real/benchmark_insertion.cpp @@ -0,0 +1,144 @@ +#include "util.h" +#include +#include +#include +#include + +namespace fs = std::filesystem; + +TEST_CASE("cloud_0601 3.7k", "[insertion]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0601.pcd").string()); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("cloud_0422 9.7k", "[insertion]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0422.pcd").string()); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("cloud_0043 13k", "[insertion]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0043.pcd").string()); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("cloud_0368 18.8k", "[insertion]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0368.pcd").string()); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("carpark 52k", "[insertion]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "carpark.pcd").string()); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("bunny 40k", "[insertion]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "bun000_UnStructured.pcd").string()); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("scan_006 560k", "[insertion]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "scan_006.pcd").string()); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("scan_007 825k", "[insertion]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "scan_007.pcd").string()); + std::cout << cloud->size() << "\n"; + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} diff --git a/benchmark/real/benchmark_insertion.xml b/benchmark/real/benchmark_insertion.xml new file mode 100644 index 0000000..362d892 --- /dev/null +++ b/benchmark/real/benchmark_insertion.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +825690 + + + + + + diff --git a/benchmark/real/benchmark_knn_search.cpp b/benchmark/real/benchmark_knn_search.cpp new file mode 100644 index 0000000..7697e76 --- /dev/null +++ b/benchmark/real/benchmark_knn_search.cpp @@ -0,0 +1,200 @@ +#include "util.h" +#include +#include +#include +#include + +namespace fs = std::filesystem; +constexpr int k = 100; + +TEST_CASE("cloud_0601 3.7k, 100n", "[knn]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0601.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; + + // nanoflann benchmark (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; +} + +TEST_CASE("cloud_0422 9.7k, 100n", "[knn]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0422.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; + + // nanoflann benchmark (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; +} + +TEST_CASE("cloud_0043 13k, 100n", "[knn]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0043.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; + + // nanoflann benchmark (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; +} + +TEST_CASE("cloud_0368 18.8k, 100n", "[knn]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0368.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; + + // nanoflann benchmark (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; +} + +TEST_CASE("bunny 40k, 100n", "[knn]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "bun000_UnStructured.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; + + // nanoflann benchmark (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; +} + +TEST_CASE("carpark 52k, 100n", "[knn]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "carpark.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; + + // nanoflann benchmark (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; +} + +TEST_CASE("scan_006 560k, 100n", "[knn]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "scan_006.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; + + // nanoflann benchmark (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; +} + +TEST_CASE("scan_007 825k, 100n", "[knn]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "scan_007.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; + + // nanoflann benchmark (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(query, k, flann_indices, flann_distances); + }; +} diff --git a/benchmark/real/benchmark_knn_search.xml b/benchmark/real/benchmark_knn_search.xml new file mode 100644 index 0000000..16d27f3 --- /dev/null +++ b/benchmark/real/benchmark_knn_search.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/benchmark/real/benchmark_radius_search.cpp b/benchmark/real/benchmark_radius_search.cpp new file mode 100644 index 0000000..e946fa3 --- /dev/null +++ b/benchmark/real/benchmark_radius_search.cpp @@ -0,0 +1,200 @@ +#include "util.h" +#include +#include +#include +#include + +namespace fs = std::filesystem; +constexpr float radius = 10.0; + +TEST_CASE("cloud_0601 3.7k, 10m", "[radius]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0601.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; +} + +TEST_CASE("cloud_0422 9.7k, 10m", "[radius]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0422.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; +} + +TEST_CASE("cloud_0043 13k, 10m", "[radius]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0043.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; +} + +TEST_CASE("cloud_0368 18.8k, 10m", "[radius]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "cloud_0368.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; +} + +TEST_CASE("bunny 40k, 10m", "[radius]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "bun000_UnStructured.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; +} + +TEST_CASE("carpark 52k, 10m", "[radius]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "carpark.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; +} + +TEST_CASE("scan_006 560k, 10m", "[radius]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "scan_006.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; +} + +TEST_CASE("scan_007 825k, 10m", "[radius]") { + auto cloud = read_cloud((fs::path(DATA_DIR) / "scan_007.pcd").string()); + auto query = cloud->points.at(get_random_number(0, cloud->points.size()-1)); + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(query, radius, flann_indices, flann_distances); + }; +} diff --git a/benchmark/real/benchmark_radius_search.xml b/benchmark/real/benchmark_radius_search.xml new file mode 100644 index 0000000..ffbf046 --- /dev/null +++ b/benchmark/real/benchmark_radius_search.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/benchmark/synth/benchmark_insertion.cpp b/benchmark/synth/benchmark_insertion.cpp new file mode 100644 index 0000000..345522f --- /dev/null +++ b/benchmark/synth/benchmark_insertion.cpp @@ -0,0 +1,113 @@ +#include "util.h" +#include +#include +#include +#include + +TEST_CASE("tree insertion 1k", "[insertion]") { + constexpr int n = 1'000; + auto cloud = generate_cloud(n); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("tree insertion 10k", "[insertion]") { + constexpr int n = 10'000; + auto cloud = generate_cloud(n); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("tree insertion 100k", "[insertion]") { + constexpr int n = 100'000; + auto cloud = generate_cloud(n); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("tree insertion 1mio", "[insertion]") { + constexpr int n = 1'000'000; + auto cloud = generate_cloud(n); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("tree insertion 10mio", "[insertion]") { + constexpr int n = 10'000'000; + auto cloud = generate_cloud(n); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} + +TEST_CASE("tree insertion 100mio", "[insertion]") { + constexpr int n = 100'000'000; + auto cloud = generate_cloud(n); + + // flann benchmark + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + BENCHMARK("flann tree insertion") { return flann_kdtree.setInputCloud(cloud); }; + + // nanoflann benchmark + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + BENCHMARK("nanoflann tree insertion") { return nanoflann_kdtree.setInputCloud(cloud); }; +} diff --git a/benchmark/synth/benchmark_insertion.xml b/benchmark/synth/benchmark_insertion.xml new file mode 100644 index 0000000..0071411 --- /dev/null +++ b/benchmark/synth/benchmark_insertion.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/benchmark/synth/benchmark_knn_search.cpp b/benchmark/synth/benchmark_knn_search.cpp new file mode 100644 index 0000000..bc1809c --- /dev/null +++ b/benchmark/synth/benchmark_knn_search.cpp @@ -0,0 +1,100 @@ +#include "util.h" +#include +#include +#include +#include + +constexpr int n = 100'000; +auto cloud = generate_cloud(n); + +TEST_CASE("knn search 1n", "[knn]") { + constexpr int k = 1; + + // flann benchmarks (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(cloud->points.back(), k, flann_indices, flann_distances); + }; + + // nanoflann benchmarks (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(cloud->points.back(), k, nanoflann_indices, nanoflann_distances); + }; +} + +TEST_CASE("knn search 10n", "[knn]") { + constexpr int k = 10; + + // flann benchmarks (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(cloud->points.back(), k, flann_indices, flann_distances); + }; + + // nanoflann benchmarks (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(cloud->points.back(), k, nanoflann_indices, nanoflann_distances); + }; +} + +TEST_CASE("knn search 100n", "[knn]") { + constexpr int k = 100; + + // flann benchmarks (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(cloud->points.back(), k, flann_indices, flann_distances); + }; + + // nanoflann benchmarks (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(cloud->points.back(), k, nanoflann_indices, nanoflann_distances); + }; +} + +TEST_CASE("knn search 1000n", "[knn]") { + constexpr int k = 1000; + + // flann benchmarks (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann knn search") { + return flann_kdtree.nearestKSearch(cloud->points.back(), k, flann_indices, flann_distances); + }; + + // nanoflann benchmarks (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann knn search") { + return nanoflann_kdtree.nearestKSearch(cloud->points.back(), k, nanoflann_indices, nanoflann_distances); + }; +} diff --git a/benchmark/synth/benchmark_knn_search.xml b/benchmark/synth/benchmark_knn_search.xml new file mode 100644 index 0000000..9b60b53 --- /dev/null +++ b/benchmark/synth/benchmark_knn_search.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/benchmark/synth/benchmark_radius_search.cpp b/benchmark/synth/benchmark_radius_search.cpp new file mode 100644 index 0000000..1a2b18e --- /dev/null +++ b/benchmark/synth/benchmark_radius_search.cpp @@ -0,0 +1,147 @@ +#include "util.h" +#include +#include +#include +#include + +constexpr int n = 100'000; +auto cloud = generate_cloud(n, -50, 50); + +TEST_CASE("Radius search 1m", "[radius]") { + constexpr float radius = 1; + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(cloud->points.back(), radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(cloud->points.back(), radius, nanoflann_indices, nanoflann_distances); + }; +} + +TEST_CASE("Radius search 10m", "[radius]") { + constexpr float radius = 10; + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(cloud->points.back(), radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(cloud->points.back(), radius, nanoflann_indices, nanoflann_distances); + }; +} + +TEST_CASE("Radius search 20m", "[radius]") { + constexpr float radius = 20; + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(cloud->points.back(), radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(cloud->points.back(), radius, nanoflann_indices, nanoflann_distances); + }; +} + +TEST_CASE("Radius search 30m", "[radius]") { + constexpr float radius = 30; + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(cloud->points.back(), radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(cloud->points.back(), radius, nanoflann_indices, nanoflann_distances); + }; +} + +TEST_CASE("Radius search 40m", "[radius]") { + constexpr float radius = 40; + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(cloud->points.back(), radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(cloud->points.back(), radius, nanoflann_indices, nanoflann_distances); + }; +} + +TEST_CASE("Radius search 50m", "[radius]") { + constexpr float radius = 50; + + // flann benchmark (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + BENCHMARK("flann radius search") { + return flann_kdtree.radiusSearch(cloud->points.back(), radius, flann_indices, flann_distances); + }; + + // nanoflann benchmark (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + BENCHMARK("nanoflann radius search") { + return nanoflann_kdtree.radiusSearch(cloud->points.back(), radius, nanoflann_indices, nanoflann_distances); + }; +} + diff --git a/benchmark/synth/benchmark_radius_search.xml b/benchmark/synth/benchmark_radius_search.xml new file mode 100644 index 0000000..e991e36 --- /dev/null +++ b/benchmark/synth/benchmark_radius_search.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmake/benchmark.cmake b/cmake/benchmark.cmake new file mode 100644 index 0000000..7d26f43 --- /dev/null +++ b/cmake/benchmark.cmake @@ -0,0 +1,6 @@ +function(add_benchmark BENCHMARK_NAME BENCHMARK_SAMPLES) + add_test( + NAME ${BENCHMARK_NAME} + COMMAND ${BENCHMARK_NAME} --reporter console --reporter xml::out=${BENCHMARK_NAME}.xml --benchmark-samples ${BENCHMARK_SAMPLES} + ) +endfunction() diff --git a/cmake/nanoflann_pclConfig.cmake.in b/cmake/nanoflann_pclConfig.cmake.in new file mode 100644 index 0000000..9c15f36 --- /dev/null +++ b/cmake/nanoflann_pclConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..f7c72ab --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,43 @@ +FROM debian:12.1-slim + +# install pcl and compilation tools +RUN apt update && apt install -y git cmake build-essential libpcl-dev + +# Install catch2 +RUN git clone https://github.com/catchorg/Catch2.git &&\ + cd Catch2 &&\ + git checkout v3.1.1 &&\ + mkdir build &&\ + cd build &&\ + cmake .. &&\ + make &&\ + make install + +# Install nanoflann +ARG NANOFLANN_VERSION +RUN git clone https://github.com/jlblancoc/nanoflann.git &&\ + cd nanoflann &&\ + git checkout "v${NANOFLANN_VERSION}" &&\ + mkdir build &&\ + cd build &&\ + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="/usr/local/nanoflann_v${NANOFLANN_VERSION}" .. &&\ + make &&\ + make install + +# compile +COPY . . +RUN mkdir build && cd build &&\ + cmake -DBUILD_TESTS=ON -DNANOFLANN_VERSION="${NANOFLANN_VERSION}" -DNANOFLANN_INSTALL_PREFIX="/usr/local/nanoflann_v${NANOFLANN_VERSION}" .. &&\ + cmake --build . &&\ + ctest -V -R test --output-on-failure &&\ + cmake --build . --config Release --target install -- -j $(nproc) + +# compile example and test install target while doing that +WORKDIR example +RUN mkdir build && cd build &&\ + cmake -DNANOFLANN_VERSION="${NANOFLANN_VERSION}" -DNANOFLANN_INSTALL_PREFIX="/usr/local/nanoflann_v${NANOFLANN_VERSION}" .. &&\ + cmake --build . &&\ + ./nanoflann_pcl_example + +# cleanup +RUN rm -rf Catch2 nanoflann nvim-linux64.tar.gz diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..7e052f5 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.0) + +project(nanoflann_pcl_example) + +find_package(nanoflann_pcl 0.1 EXACT REQUIRED) +find_package(nanoflann ${NANOFLANN_VERSION} EXACT REQUIRED PATHS ${NANOFLANN_INSTALL_PREFIX}) +find_package(PCL REQUIRED) +find_package(Threads REQUIRED) # may be optional, depending on your system and cmake version + +add_executable(nanoflann_pcl_example nanoflann_pcl_example.cpp) +target_include_directories(nanoflann_pcl_example PRIVATE SYSTEM ${PCL_INCLUDE_DIRS}) +target_compile_options(nanoflann_pcl_example PRIVATE -Werror -Wall -Wpedantic -Weffc++) +target_link_libraries(nanoflann_pcl_example PRIVATE nanoflann_pcl::nanoflann_pcl nanoflann::nanoflann Threads::Threads ${PCL_LIBRARIES}) diff --git a/example/nanoflann_pcl_example.cpp b/example/nanoflann_pcl_example.cpp new file mode 100644 index 0000000..46caa4c --- /dev/null +++ b/example/nanoflann_pcl_example.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +int main(void) { + int n = 1'000; + pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + cloud->reserve(n); + + // random data + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution dis(-1000.0, 1000.0); + + for (int i = 0; i < n; ++i) { + pcl::PointXYZ point; + point.x = dis(gen); + point.y = dis(gen); + point.z = dis(gen); + cloud->push_back(point); + } + + + // usage + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + nanoflann_kdtree.radiusSearch(cloud->points.back(), 100, nanoflann_indices, nanoflann_distances); + nanoflann_kdtree.nearestKSearch(cloud->points.back(), 100, nanoflann_indices, nanoflann_distances); +} diff --git a/include/nanoflann_pcl/nanoflann.hpp b/include/nanoflann_pcl/nanoflann.hpp new file mode 100644 index 0000000..a40eb00 --- /dev/null +++ b/include/nanoflann_pcl/nanoflann.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "nanoflann_adapter.hpp" diff --git a/include/nanoflann_pcl/nanoflann_adapter.hpp b/include/nanoflann_pcl/nanoflann_adapter.hpp new file mode 100644 index 0000000..ad4afca --- /dev/null +++ b/include/nanoflann_pcl/nanoflann_adapter.hpp @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include +#include + +static_assert(NANOFLANN_VERSION == 0x150 || NANOFLANN_VERSION == 0x151 || NANOFLANN_VERSION == 0x152 || + NANOFLANN_VERSION == 0x153, + "Unsupported nanoflann version. Supported are version 1.5.0-1.5.3"); + +namespace nanoflann { + +template class KdTreeFLANN { + public: + using PointCloud = typename pcl::PointCloud; + using PointCloudPtr = typename pcl::PointCloud::Ptr; + using PointCloudConstPtr = typename pcl::PointCloud::ConstPtr; + + using Ptr = std::shared_ptr>; + using ConstPtr = std::shared_ptr>; + using IndicesPtr = std::shared_ptr>; + using IndicesConstPtr = std::shared_ptr>; + + KdTreeFLANN(bool sorted = false); + + ~KdTreeFLANN() = default; + + inline void setEpsilon(float eps) { _params.eps = eps; } + + inline void setSortedResults(bool sorted) { _params.sorted = sorted; } + + void setInputCloud(const PointCloudConstPtr& cloud, const IndicesConstPtr& indices = IndicesConstPtr()); + + inline PointCloudConstPtr getInputCloud() const { return _adaptor.pcl; } + + int nearestKSearch(const PointT& point, int k, std::vector& k_indices, + std::vector& k_sqr_distances) const; + + int radiusSearch(const PointT& point, float radius, std::vector& k_indices, + std::vector& k_sqr_distances) const; + + protected: + nanoflann::SearchParameters _params; + + struct PointCloud_Adaptor { + PointCloud_Adaptor() : pcl(), indices() {} + ~PointCloud_Adaptor() = default; + inline size_t kdtree_get_point_count() const; + inline float kdtree_get_pt(const size_t idx, int dim) const; + template bool kdtree_get_bbox(BBOX&) const { return false; } + PointCloudConstPtr pcl; + IndicesConstPtr indices; + }; + + using KDTreeFlann_PCL_SO3 = nanoflann::KDTreeSingleIndexAdaptor, + PointCloud_Adaptor, 3, int>; + + PointCloud_Adaptor _adaptor; + + KDTreeFlann_PCL_SO3 _kdtree; +}; + +template +inline KdTreeFLANN::KdTreeFLANN(bool sorted) + : _params(), _adaptor(), _kdtree(3, _adaptor, KDTreeSingleIndexAdaptorParams(25)) { + _params.sorted = sorted; +} + +template +inline void KdTreeFLANN::setInputCloud(const KdTreeFLANN::PointCloudConstPtr& cloud, + const IndicesConstPtr& indices) { + _adaptor.pcl = cloud; + _adaptor.indices = indices; + _kdtree.buildIndex(); +} + +template +inline int KdTreeFLANN::nearestKSearch(const PointT& point, int num_closest, std::vector& k_indices, + std::vector& k_sqr_distances) const { + k_indices.resize(num_closest); + k_sqr_distances.resize(num_closest); + + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(k_indices.data(), k_sqr_distances.data()); + _kdtree.findNeighbors(resultSet, point.data, _params); + return resultSet.size(); +} + +template +inline int KdTreeFLANN::radiusSearch(const PointT& point, float radius, std::vector& k_indices, + std::vector& k_sqr_distances) const { + static std::vector> result; + result.reserve(128); + + // From nanoflann README + // > Important note: If L2 norms are used, notice that search radius and all passed and returned distances are + // actually squared distances. + // + // I argue that the user expects searches in m. Therefore square the radius for the user. + // + // Earlier versions of this lbrary added tiny offset to effectively search <= radius, but + // this does not correspond to the behavior in pcl, therfore remove offset + float sq_radius = radius * radius; // + 0.05 + + const size_t n_found = _kdtree.radiusSearch(point.data, sq_radius, result); + + k_indices.resize(n_found); + k_sqr_distances.resize(n_found); + for (size_t i = 0; i < n_found; i++) { + k_indices[i] = result[i].first; + k_sqr_distances[i] = result[i].second; + } + return n_found; +} + +template inline size_t KdTreeFLANN::PointCloud_Adaptor::kdtree_get_point_count() const { + if (indices) { + return indices->size(); + } + if (pcl) { + return pcl->points.size(); + } + return 0; +} + +template +inline float KdTreeFLANN::PointCloud_Adaptor::kdtree_get_pt(const size_t idx, int dim) const { + const PointT& p = (indices) ? pcl->points[(*indices)[idx]] : pcl->points[idx]; + if (dim == 0) { + return p.x; + } else if (dim == 1) { + return p.y; + } else { + return p.z; + } + + return 0.0; +} + +} // namespace nanoflann diff --git a/test/test_nanoflann_pcl.cpp b/test/test_nanoflann_pcl.cpp new file mode 100644 index 0000000..89cb14e --- /dev/null +++ b/test/test_nanoflann_pcl.cpp @@ -0,0 +1,60 @@ +#include "util.h" +#include +#include +#include +#include + +constexpr int n = 1'000; +constexpr int k = 100; +constexpr float radius = 100; +auto cloud = generate_cloud(n); + +TEST_CASE("Radius search", "[radius]") { + // flann test (radius) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + flann_kdtree.radiusSearch(cloud->points.back(), radius, flann_indices, flann_distances); + + // nanoflann test (radius) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + nanoflann_kdtree.radiusSearch(cloud->points.back(), radius, nanoflann_indices, nanoflann_distances); + + REQUIRE(flann_indices.size() == nanoflann_indices.size()); + REQUIRE(flann_distances.size() == nanoflann_distances.size()); + REQUIRE(flann_distances.size() == flann_indices.size()); + + for (auto i = 0u; i < flann_indices.size(); ++i) { + REQUIRE(flann_indices[i] == nanoflann_indices[i]); + } +} + +TEST_CASE("knn search", "[knn]") { + // flann test (knn) + std::vector flann_indices; + std::vector flann_distances; + pcl::KdTreeFLANN flann_kdtree; + flann_kdtree.setSortedResults(true); + flann_kdtree.setInputCloud(cloud); + flann_kdtree.nearestKSearch(cloud->points.back(), k, flann_indices, flann_distances); + + // nanoflann test (knn) + std::vector nanoflann_indices; + std::vector nanoflann_distances; + nanoflann::KdTreeFLANN nanoflann_kdtree; + nanoflann_kdtree.setInputCloud(cloud); + nanoflann_kdtree.nearestKSearch(cloud->points.back(), k, nanoflann_indices, nanoflann_distances); + + REQUIRE(flann_indices.size() == nanoflann_indices.size()); + REQUIRE(flann_distances.size() == nanoflann_distances.size()); + REQUIRE(flann_distances.size() == flann_indices.size()); + + for (auto i = 0u; i < flann_indices.size(); ++i) { + REQUIRE(flann_indices[i] == nanoflann_indices[i]); + } +} diff --git a/util/catch2_postprocessing.py b/util/catch2_postprocessing.py new file mode 100644 index 0000000..d09e494 --- /dev/null +++ b/util/catch2_postprocessing.py @@ -0,0 +1,64 @@ +import csv +import xml.etree.ElementTree as ET +import argparse + +header = ['Test Case', 'Flann Mean (ms)', 'Nanoflann Mean (ms)', 'Flann Std (ms)', 'Nanoflann Std (ms)'] + +def parse_xml(filename, categories): + tree = ET.parse(filename) + root = tree.getroot() + data = [] + + for testcase in root.findall('.//TestCase'): + test_case_name = testcase.get('name') + flann_mean = float(testcase.find(f'.//BenchmarkResults[@name="{categories[0]}"]/mean').get('value')) / 1e6 + flann_std = float(testcase.find(f'.//BenchmarkResults[@name="{categories[0]}"]/standardDeviation').get('value')) / 1e6 + nanoflann_mean = float(testcase.find(f'.//BenchmarkResults[@name="{categories[1]}"]/mean').get('value')) / 1e6 + nanoflann_std = float(testcase.find(f'.//BenchmarkResults[@name="{categories[1]}"]/standardDeviation').get('value')) / 1e6 + data.append([test_case_name, flann_mean, nanoflann_mean, flann_std, nanoflann_std]) + + return data + +def write_to_csv(data, csv_filename): + with open(csv_filename, mode='w', newline='') as csv_file: + writer = csv.writer(csv_file) + writer.writerow(header) + writer.writerows(data) + +def format_float_if_float(value): + try: + float_value = float(value) + return "{:.5f}".format(float_value) + except ValueError: + return value + +def convert_to_markdown_table(data): + markdown_table = "| " + " | ".join(header) + " |\n" + markdown_table += "| " + " | ".join(["---"] * len(header)) + " |\n" + + for row in data: + markdown_table += "| " + " | ".join(map(lambda x: format_float_if_float(x), row)) + " |\n" + + return markdown_table + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Parse XML and convert to CSV and Markdown table.') + parser.add_argument('xml_file', type=str, help='Path to the input XML file') + parser.add_argument('--export_csv', action='store_true', help='Export data to CSV file') + parser.add_argument('--categories', nargs='+', default=[], help='List of categories to filter data') + + args = parser.parse_args() + + assert(len(args.categories) == 2), "please provide two categories" + assert(args.categories[0] != args.categories[1]), "categories must differ" + assert('nanoflann' not in args.categories[0] and 'nanoflann' in args.categories[1]), "first category must be flann (to match header)" + + parsed_data = parse_xml(args.xml_file, args.categories) + + if args.export_csv: + csv_filename = 'output.csv' + write_to_csv(parsed_data, csv_filename) + print(f'Data exported to CSV: {csv_filename}') + + markdown_table = convert_to_markdown_table(parsed_data) + print(markdown_table) diff --git a/util/util.h b/util/util.h new file mode 100644 index 0000000..53210df --- /dev/null +++ b/util/util.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +template +T get_random_number(T min, T max) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution dis(min, max); + return dis(gen); +} + +template +typename pcl::PointCloud::Ptr generate_cloud(int n, float min = -1000.0, float max = 1000.0) { + typename pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + cloud->reserve(n); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution dis(min, max); + + for (int i = 0; i < n; ++i) { + pcl::PointXYZ point; + point.x = dis(gen); + point.y = dis(gen); + point.z = dis(gen); + cloud->push_back(point); + } + + return cloud; +} + +template +typename pcl::PointCloud::Ptr read_cloud(std::string filepath) { + typename pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + if (pcl::io::loadPCDFile(filepath, *cloud) == -1) { + throw std::runtime_error("Couldn't read file.\n"); + } + return cloud; +}