Skip to content

Commit

Permalink
v0.8.0 Patch (#29)
Browse files Browse the repository at this point in the history
Added array traits to support default point types.
Documentation and examples updated.
Map traits return type fix.
Added SplitterMidpoint unit tests.
  • Loading branch information
Jaybro authored Jul 3, 2023
1 parent 2d02756 commit 62e457b
Show file tree
Hide file tree
Showing 15 changed files with 525 additions and 254 deletions.
44 changes: 27 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,38 @@ Available under the [MIT](https://en.wikipedia.org/wiki/MIT_License) license.

# Capabilities

* KdTree:
* Nearest neighbor, approximate nearest neighbor, radius, box, and customizable nearest neighbor searches.
* Multiple tree splitting rules: `kLongestMedian`, `kMidpoint` and `kSlidingMidpoint`.
* [Metrics](https://en.wikipedia.org/wiki/Metric_(mathematics)):
* Support for topological spaces with identifications. E.g., points on the circle `[-pi, pi]`.
* Available metrics: `L1`, `L2Squared`, `SO2`, and `SE2Squared`. Metrics can be customized.
* Compile time and run time known dimensions.
* Static tree builds.
* Thread safe queries.
* PicoTree can interface with different types of points or point sets through traits classes. These can be custom implementations or one of the `pico_tree::PointTraits<>` and `pico_tree::SpaceTraits<>` classes provided by this library. There is default support for the following data types:
KdTree:
* Nearest neighbor, approximate nearest neighbor, radius, box, and customizable nearest neighbor searches.
* [Metrics](https://en.wikipedia.org/wiki/Metric_(mathematics)):
* Support for topological spaces with identifications. E.g., points on the circle `[-pi, pi]`.
* Available metrics: `L1`, `L2Squared`, `SO2`, and `SE2Squared`. Metrics can be customized.
* Multiple tree splitting rules: `kLongestMedian`, `kMidpoint` and `kSlidingMidpoint`.
* Compile time and run time known dimensions.
* Static tree builds.
* Thread safe queries.

PicoTree can interface with different types of points and point sets through traits classes. These can be custom implementations or one of the `pico_tree::SpaceTraits<>` and `pico_tree::PointTraits<>` classes provided by this library.
* Space type support:
* `std::vector<PointType>`.
* A specialization of `pico_tree::PointTraits<>` is required for each `PointType`. There are traits available for Eigen and OpenCV point types.
* `pico_tree::SpaceMap<PointType>` and `pico_tree::PointMap<>`.
* These classes allow interfacing with raw pointers. It is assumed that points and their coordinates are laid out contiguously in memory.
* `Eigen::Matrix` and `Eigen::Map<Eigen::Matrix>`.
* `pico_tree::SpaceMap<PointType>`.
* `Eigen::Matrix<>` and `Eigen::Map<Eigen::Matrix<>>`.
* `cv::Mat`.
* Point type support:
* Fixed size arrays and `std::array<>`.
* `pico_tree::PointMap<>`.
* `Eigen::Vector<>` and `Eigen::Map<Eigen::Vector<>>`.
* `cv::Vec<>`.
* `pico_tree::SpaceMap<PointType>` and `pico_tree::PointMap<>` allow interfacing with dynamic size arrays. It is assumed that points and their coordinates are laid out contiguously in memory.

# Examples

* [Minimal working example](./examples/kd_tree/kd_tree_minimal.cpp) using an std::vector of points.
* Creating [traits](./examples/kd_tree/kd_tree_traits.cpp) classes for a custom point and point set.
* Using the KdTree's [search](./examples/kd_tree/kd_tree_search.cpp) options and creating a custom search visitor.
* [Minimal working example](./examples/kd_tree/kd_tree_minimal.cpp) using an `std::vector<>` of points.
* [Creating a KdTree](./examples/kd_tree/kd_tree_creation.cpp) and taking the input by value or reference.
* Using the KdTree's [search](./examples/kd_tree/kd_tree_search.cpp) capabilities.
* Working with [dynamic size arrays](./examples/kd_tree/kd_tree_dynamic_arrays.cpp).
* Supporting a [custom point type](./examples/kd_tree/kd_tree_custom_point_type.cpp).
* Supporting a [custom space type](./examples/kd_tree/kd_tree_custom_space_type.cpp).
* Creating a [custom search visitor](./examples/kd_tree/kd_tree_custom_search_visitor.cpp).
* Support for [Eigen](./examples/eigen/eigen.cpp) and [OpenCV](./examples/opencv/opencv.cpp) data types.
* How to use the [KdTree with Python](./examples/python/kd_tree.py).

Expand Down
110 changes: 43 additions & 67 deletions examples/eigen/eigen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,31 @@
#include <pico_tree/kd_tree.hpp>
#include <pico_tree/vector_traits.hpp>

// NOTES:

// Because we use C++17 there is no need to take care of memory alignment:
// https://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html
// https://eigen.tuxfamily.org/dox-devel/group__TopicStlContainers.html

// The Eigen example is not a performance benchmark. So don't take the "elapsed
// time" numbers too seriously.

using Index = int;

template <typename Point>
using PointsMapCm = Eigen::Map<Eigen::Matrix<
using PointsMapColMajor = Eigen::Map<Eigen::Matrix<
typename Point::Scalar,
Point::RowsAtCompileTime,
Eigen::Dynamic>>;

template <typename Point>
using PointsMapRm = Eigen::Map<Eigen::Matrix<
using PointsMapRowMajor = Eigen::Map<Eigen::Matrix<
typename Point::Scalar,
Eigen::Dynamic,
Point::ColsAtCompileTime,
Eigen::RowMajor>>;

std::size_t const kRunCount = 1024 * 1024;
int const kNumPoints = 1024 * 1024 * 2;
std::size_t const kNumPoints = 1024 * 1024 * 2;
float const kArea = 1000.0;
Index const kMaxLeafCount = 16;
std::size_t const kMaxLeafCount = 16;

template <typename PointX>
std::vector<PointX> GenerateRandomEigenN(int n, typename PointX::Scalar size) {
std::vector<PointX> GenerateRandomEigenN(
std::size_t n, typename PointX::Scalar size) {
std::vector<PointX> random(n);
for (auto& p : random) {
p = PointX::Random() * size / typename PointX::Scalar(2.0);
Expand All @@ -46,10 +40,12 @@ std::vector<PointX> GenerateRandomEigenN(int n, typename PointX::Scalar size) {
// neighbors.
void BasicVector() {
using PointX = Eigen::Vector2f;
using Scalar = typename PointX::Scalar;
using KdTree = pico_tree::KdTree<std::vector<PointX>>;
using Scalar = typename KdTree::ScalarType;
using Index = typename KdTree::IndexType;

// Including <pico_tree/eigen.hpp> provides support for Eigen types with
// std::vector.
// Including <pico_tree/eigen3_traits.hpp> provides support for Eigen types
// with std::vector.
pico_tree::KdTree<std::vector<PointX>> tree(
GenerateRandomEigenN<PointX>(kNumPoints, kArea), kMaxLeafCount);

Expand All @@ -64,95 +60,75 @@ void BasicVector() {

// Creates a KdTree from an Eigen::Matrix<> and searches for nearest neighbors.
void BasicMatrix() {
using KdTree = pico_tree::KdTree<Eigen::Matrix3Xf>;
using Neighbor = typename KdTree::NeighborType;
using Scalar = typename Eigen::Matrix3Xf::Scalar;
constexpr int Dim = Eigen::Matrix3Xf::RowsAtCompileTime;

Eigen::Vector3f p = Eigen::Vector3f::Random() * kArea / Scalar(2.0);

// The KdTree takes the matrix by value. Prevent a copy by:
// * Using a move.
// * Creating an Eigen::Map<>.
// * Wrap with an std::reference_wrapper<>.
{
pico_tree::KdTree<Eigen::Matrix3Xf> tree(
Eigen::Matrix3Xf::Random(Dim, kNumPoints) * kArea / Scalar(2.0),
kMaxLeafCount);

pico_tree::Neighbor<Index, Scalar> nn;
ScopedTimer t("pico_tree eigen val", kRunCount);
for (std::size_t i = 0; i < kRunCount; ++i) {
tree.SearchNn(p, nn);
}
}

{
Eigen::Matrix3Xf matrix =
Eigen::Matrix3Xf::Random(Dim, kNumPoints) * kArea / Scalar(2.0);
KdTree tree(
Eigen::Matrix3Xf::Random(Dim, kNumPoints) * kArea / Scalar(2.0),
kMaxLeafCount);

pico_tree::KdTree<std::reference_wrapper<Eigen::Matrix3Xf>> tree(
matrix, kMaxLeafCount);

pico_tree::Neighbor<Index, Scalar> nn;
ScopedTimer t("pico_tree eigen ref", kRunCount);
for (std::size_t i = 0; i < kRunCount; ++i) {
tree.SearchNn(p, nn);
}
Eigen::Vector3f p = Eigen::Vector3f::Random() * kArea / Scalar(2.0);
Neighbor nn;
ScopedTimer t("pico_tree eigen matrix", kRunCount);
for (std::size_t i = 0; i < kRunCount; ++i) {
tree.SearchNn(p, nn);
}
}

// Creates a KdTree from a col-major matrix. The matrix maps an
// std::vector<Eigen::Vector3f>.
void VectorMapColMajor() {
void ColMajorSupport() {
using PointX = Eigen::Vector3f;
using Map = PointsMapColMajor<PointX>;
using KdTree = pico_tree::KdTree<Map>;
using Neighbor = typename KdTree::NeighborType;
using Scalar = typename PointX::Scalar;
constexpr int Dim = PointX::RowsAtCompileTime;
using Map = PointsMapCm<PointX>;

auto points = GenerateRandomEigenN<PointX>(kNumPoints, kArea);
PointX p = PointX::Random() * kArea / Scalar(2.0);

std::cout << "Eigen RowMajor: " << Map::IsRowMajor << std::endl;
{
pico_tree::KdTree<Map> tree(
Map(points.data()->data(), Dim, points.size()), kMaxLeafCount);

std::vector<pico_tree::Neighbor<Index, Scalar>> knn;
ScopedTimer t("pico_tree deflt l2", kRunCount);
for (std::size_t i = 0; i < kRunCount; ++i) {
tree.SearchKnn(p, 1, knn);
}

KdTree tree(Map(points.data()->data(), Dim, points.size()), kMaxLeafCount);

std::vector<Neighbor> knn;
ScopedTimer t("pico_tree col major", kRunCount);
for (std::size_t i = 0; i < kRunCount; ++i) {
tree.SearchKnn(p, 1, knn);
}
}

// Creates a KdTree from a row-major matrix. The matrix maps an
// std::vector<Eigen::RowVector3f>.
void VectorMapRowMajor() {
void RowMajorSupport() {
using PointX = Eigen::RowVector3f;
using Map = PointsMapRowMajor<PointX>;
using KdTree = pico_tree::KdTree<Map>;
using Neighbor = typename KdTree::NeighborType;
using Scalar = typename PointX::Scalar;
constexpr int Dim = PointX::ColsAtCompileTime;
using Map = PointsMapRm<PointX>;

auto points = GenerateRandomEigenN<PointX>(kNumPoints, kArea);
PointX p = PointX::Random() * kArea / Scalar(2.0);

std::cout << "Eigen RowMajor: " << PointX::IsRowMajor << std::endl;

{
pico_tree::KdTree<Map> tree(
Map(points.data()->data(), points.size(), Dim), kMaxLeafCount);
KdTree tree(Map(points.data()->data(), points.size(), Dim), kMaxLeafCount);

std::vector<pico_tree::Neighbor<Index, Scalar>> knn;
ScopedTimer t("pico_tree deflt l2", kRunCount);
for (std::size_t i = 0; i < kRunCount; ++i) {
tree.SearchKnn(p, 1, knn);
}
std::vector<Neighbor> knn;
ScopedTimer t("pico_tree row major", kRunCount);
for (std::size_t i = 0; i < kRunCount; ++i) {
tree.SearchKnn(p, 1, knn);
}
}

int main() {
BasicVector();
BasicMatrix();
VectorMapColMajor();
VectorMapRowMajor();
ColMajorSupport();
RowMajorSupport();
return 0;
}
26 changes: 17 additions & 9 deletions examples/kd_tree/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
add_executable(kd_tree_minimal kd_tree_minimal.cpp)
set_default_target_properties(kd_tree_minimal)
target_link_libraries(kd_tree_minimal PUBLIC PicoTree::PicoTree)
function(add_demo_executable TARGET_NAME)
add_executable(${TARGET_NAME} ${TARGET_NAME}.cpp)
set_default_target_properties(${TARGET_NAME})
target_link_libraries(${TARGET_NAME} PRIVATE pico_toolshed)
endfunction()

add_executable(kd_tree_traits kd_tree_traits.cpp)
set_default_target_properties(kd_tree_traits)
target_link_libraries(kd_tree_traits PUBLIC PicoTree::PicoTree)
add_demo_executable(kd_tree_minimal)

add_executable(kd_tree_search kd_tree_search.cpp)
set_default_target_properties(kd_tree_search)
target_link_libraries(kd_tree_search PUBLIC pico_toolshed)
add_demo_executable(kd_tree_creation)

add_demo_executable(kd_tree_search)

add_demo_executable(kd_tree_dynamic_arrays)

add_demo_executable(kd_tree_custom_point_type)

add_demo_executable(kd_tree_custom_space_type)

add_demo_executable(kd_tree_custom_search_visitor)
79 changes: 79 additions & 0 deletions examples/kd_tree/kd_tree_creation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <iostream>
#include <pico_tree/array_traits.hpp>
#include <pico_tree/kd_tree.hpp>
#include <pico_tree/vector_traits.hpp>

// This application demonstrates how a KdTree can take its input point set.
// Although all of the examples use an std::vector<> as the input for building a
// KdTree, they will work with any of the inputs supported by this library
// (e.g., Eigen::Matrix<>).

template <typename Tree>
void QueryTree(Tree const& tree) {
float query[3] = {4.0f, 4.0f, 4.0f};
pico_tree::Neighbor<int, float> nn;
tree.SearchNn(query, nn);

std::cout << "Index closest point: " << nn.index << std::endl;
}

auto MakePointSet() {
std::vector<std::array<float, 3>> points{
{0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}};

return points;
}

// The KdTree takes the input by value. In this example, creating a KdTree
// results in a copy of the input point set.
void BuildKdTreeWithCopy() {
int max_leaf_size = 12;
auto points = MakePointSet();

pico_tree::KdTree<std::vector<std::array<float, 3>>> tree(
points, max_leaf_size);

QueryTree(tree);
}

// The KdTree takes the input by value. In this example, the point sets are not
// copied but moved into the KdTree. This prevents a copy.
void BuildKdTreeWithMove() {
int max_leaf_size = 12;
auto points = MakePointSet();

pico_tree::KdTree<std::vector<std::array<float, 3>>> tree1(
std::move(points), max_leaf_size);

pico_tree::KdTree<std::vector<std::array<float, 3>>> tree2(
MakePointSet(), max_leaf_size);

QueryTree(tree1);
QueryTree(tree2);
}

// The KdTree takes the input by value. In this example, the input is taken by
// reference. This prevents a copy.
void BuildKdTreeWithReference() {
int max_leaf_size = 12;
auto points = MakePointSet();

// By reference.
pico_tree::KdTree<std::reference_wrapper<std::vector<std::array<float, 3>>>>
tree1(points, max_leaf_size);

// By const reference.
pico_tree::KdTree<
std::reference_wrapper<std::vector<std::array<float, 3>> const>>
tree2(points, max_leaf_size);

QueryTree(tree1);
QueryTree(tree2);
}

int main() {
BuildKdTreeWithReference();
BuildKdTreeWithMove();
BuildKdTreeWithCopy();
return 0;
}
Loading

0 comments on commit 62e457b

Please sign in to comment.