summaryrefslogtreecommitdiff
path: root/src/Spatial_searching
diff options
context:
space:
mode:
Diffstat (limited to 'src/Spatial_searching')
-rw-r--r--src/Spatial_searching/doc/Intro_spatial_searching.h48
-rw-r--r--src/Spatial_searching/example/CMakeLists.txt9
-rw-r--r--src/Spatial_searching/example/example_spatial_searching.cpp60
-rw-r--r--src/Spatial_searching/include/gudhi/Kd_tree_search.h287
-rw-r--r--src/Spatial_searching/test/CMakeLists.txt11
-rw-r--r--src/Spatial_searching/test/test_Kd_tree_search.cpp108
6 files changed, 523 insertions, 0 deletions
diff --git a/src/Spatial_searching/doc/Intro_spatial_searching.h b/src/Spatial_searching/doc/Intro_spatial_searching.h
new file mode 100644
index 00000000..30805570
--- /dev/null
+++ b/src/Spatial_searching/doc/Intro_spatial_searching.h
@@ -0,0 +1,48 @@
+/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
+ * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
+ * Author(s): Clement Jamin
+ *
+ * Copyright (C) 2016 Inria
+ *
+ * Modification(s):
+ * - YYYY/MM Author: Description of the modification
+ */
+
+#ifndef DOC_SPATIAL_SEARCHING_INTRO_SPATIAL_SEARCHING_H_
+#define DOC_SPATIAL_SEARCHING_INTRO_SPATIAL_SEARCHING_H_
+
+// needs namespaces for Doxygen to link on classes
+namespace Gudhi {
+namespace spatial_searching {
+
+/** \defgroup spatial_searching Spatial_searching
+ *
+ * \author Clément Jamin
+ *
+ * @{
+ *
+ * \section introduction Introduction
+ *
+ * This Gudhi component is a wrapper around
+ * <a target="_blank" href="http://doc.cgal.org/latest/Spatial_searching/index.html">CGAL dD spatial searching algorithms</a>.
+ * It provides a simplified API to perform (approximate) neighbor searches. Contrary to CGAL default behavior, the tree
+ * does not store the points themselves, but stores indices.
+ *
+ * For more details about the data structure or the algorithms, or for more advanced usages, reading
+ * <a target="_blank" href="http://doc.cgal.org/latest/Spatial_searching/index.html">CGAL documentation</a>
+ * is highly recommended.
+ *
+ * \section spatial_searching_examples Example
+ *
+ * This example generates 500 random points, then performs all-near-neighbors searches, and queries for nearest and furthest neighbors using different methods.
+ *
+ * \include Spatial_searching/example_spatial_searching.cpp
+ *
+ */
+/** @} */ // end defgroup spatial_searching
+
+} // namespace spatial_searching
+
+} // namespace Gudhi
+
+#endif // DOC_SPATIAL_SEARCHING_INTRO_SPATIAL_SEARCHING_H_
diff --git a/src/Spatial_searching/example/CMakeLists.txt b/src/Spatial_searching/example/CMakeLists.txt
new file mode 100644
index 00000000..eeb3e85f
--- /dev/null
+++ b/src/Spatial_searching/example/CMakeLists.txt
@@ -0,0 +1,9 @@
+project(Spatial_searching_examples)
+
+if(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0)
+ add_executable( Spatial_searching_example_spatial_searching example_spatial_searching.cpp )
+ target_link_libraries(Spatial_searching_example_spatial_searching ${CGAL_LIBRARY})
+ add_test(NAME Spatial_searching_example_spatial_searching
+ COMMAND $<TARGET_FILE:Spatial_searching_example_spatial_searching>)
+ install(TARGETS Spatial_searching_example_spatial_searching DESTINATION bin)
+endif(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0)
diff --git a/src/Spatial_searching/example/example_spatial_searching.cpp b/src/Spatial_searching/example/example_spatial_searching.cpp
new file mode 100644
index 00000000..034ad24a
--- /dev/null
+++ b/src/Spatial_searching/example/example_spatial_searching.cpp
@@ -0,0 +1,60 @@
+#include <gudhi/Kd_tree_search.h>
+
+#include <CGAL/Epick_d.h>
+#include <CGAL/Random.h>
+
+#include <vector>
+
+namespace gss = Gudhi::spatial_searching;
+
+int main(void) {
+ typedef CGAL::Epick_d<CGAL::Dimension_tag<4> > K;
+ typedef typename K::Point_d Point;
+ typedef std::vector<Point> Points;
+
+ typedef gss::Kd_tree_search<K, Points> Points_ds;
+
+ CGAL::Random rd;
+
+ Points points;
+ for (int i = 0; i < 500; ++i)
+ points.push_back(Point(rd.get_double(-1., 1), rd.get_double(-1., 1), rd.get_double(-1., 1), rd.get_double(-1., 1)));
+
+ Points_ds points_ds(points);
+
+ // 10-nearest neighbor query
+ std::cout << "10 nearest neighbors from points[20]:\n";
+ auto knn_range = points_ds.k_nearest_neighbors(points[20], 10, true);
+ for (auto const& nghb : knn_range)
+ std::cout << nghb.first << " (sq. dist. = " << nghb.second << ")\n";
+
+ // Incremental nearest neighbor query
+ std::cout << "Incremental nearest neighbors:\n";
+ auto inn_range = points_ds.incremental_nearest_neighbors(points[45]);
+ // Get the neighbors in distance order until we hit the first point
+ for (auto ins_iterator = inn_range.begin(); ins_iterator->first != 0; ++ins_iterator)
+ std::cout << ins_iterator->first << " (sq. dist. = " << ins_iterator->second << ")\n";
+
+ // 10-furthest neighbor query
+ std::cout << "10 furthest neighbors from points[20]:\n";
+ auto kfn_range = points_ds.k_furthest_neighbors(points[20], 10, true);
+ for (auto const& nghb : kfn_range)
+ std::cout << nghb.first << " (sq. dist. = " << nghb.second << ")\n";
+
+ // Incremental furthest neighbor query
+ std::cout << "Incremental furthest neighbors:\n";
+ auto ifn_range = points_ds.incremental_furthest_neighbors(points[45]);
+ // Get the neighbors in distance reverse order until we hit the first point
+ for (auto ifs_iterator = ifn_range.begin(); ifs_iterator->first != 0; ++ifs_iterator)
+ std::cout << ifs_iterator->first << " (sq. dist. = " << ifs_iterator->second << ")\n";
+
+ // All-near-neighbors search
+ std::cout << "All-near-neighbors search:\n";
+ std::vector<std::size_t> rs_result;
+ points_ds.all_near_neighbors(points[45], 0.5, std::back_inserter(rs_result));
+ K k;
+ for (auto const& p_idx : rs_result)
+ std::cout << p_idx << " (sq. dist. = " << k.squared_distance_d_object()(points[p_idx], points[45]) << ")\n";
+
+ return 0;
+}
diff --git a/src/Spatial_searching/include/gudhi/Kd_tree_search.h b/src/Spatial_searching/include/gudhi/Kd_tree_search.h
new file mode 100644
index 00000000..fedbb32e
--- /dev/null
+++ b/src/Spatial_searching/include/gudhi/Kd_tree_search.h
@@ -0,0 +1,287 @@
+/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
+ * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
+ * Author(s): Clement Jamin
+ *
+ * Copyright (C) 2016 Inria
+ *
+ * Modification(s):
+ * - 2019/08 Vincent Rouvreau: Fix issue #10 for CGAL and Eigen3
+ * - YYYY/MM Author: Description of the modification
+ */
+
+#ifndef KD_TREE_SEARCH_H_
+#define KD_TREE_SEARCH_H_
+
+#include <CGAL/Orthogonal_k_neighbor_search.h>
+#include <CGAL/Orthogonal_incremental_neighbor_search.h>
+#include <CGAL/Search_traits.h>
+#include <CGAL/Search_traits_adapter.h>
+#include <CGAL/Fuzzy_sphere.h>
+#include <CGAL/property_map.h>
+#include <CGAL/version.h> // for CGAL_VERSION_NR
+
+#include <Eigen/src/Core/util/Macros.h> // for EIGEN_VERSION_AT_LEAST
+
+#include <boost/property_map/property_map.hpp>
+#include <boost/iterator/counting_iterator.hpp>
+
+#include <cstddef>
+#include <vector>
+
+// Make compilation fail - required for external projects - https://github.com/GUDHI/gudhi-devel/issues/10
+#if CGAL_VERSION_NR < 1041101000
+# error Alpha_complex_3d is only available for CGAL >= 4.11
+#endif
+
+#if !EIGEN_VERSION_AT_LEAST(3,1,0)
+# error Alpha_complex_3d is only available for Eigen3 >= 3.1.0 installed with CGAL
+#endif
+
+namespace Gudhi {
+namespace spatial_searching {
+
+
+ /**
+ * \class Kd_tree_search Kd_tree_search.h gudhi/Kd_tree_search.h
+ * \brief Spatial tree data structure to perform (approximate) nearest and furthest neighbor search.
+ *
+ * \ingroup spatial_searching
+ *
+ * \details
+ * The class Kd_tree_search is a tree-based data structure, based on
+ * <a target="_blank" href="http://doc.cgal.org/latest/Spatial_searching/index.html">CGAL dD spatial searching data structures</a>.
+ * It provides a simplified API to perform (approximate) nearest and furthest neighbor searches. Contrary to CGAL default behavior, the tree
+ * does not store the points themselves, but stores indices.
+ *
+ * There are two types of queries: the <i>k-nearest or k-furthest neighbor query</i>, where <i>k</i> is fixed and the <i>k</i> nearest
+ * or furthest points are computed right away,
+ * and the <i>incremental nearest or furthest neighbor query</i>, where no number of neighbors is provided during the call, as the
+ * neighbors will be computed incrementally when the iterator on the range is incremented.
+ *
+ * \tparam Search_traits must be a model of the <a target="_blank"
+ * href="http://doc.cgal.org/latest/Spatial_searching/classSearchTraits.html">SearchTraits</a>
+ * concept, such as the <a target="_blank"
+ * href="http://doc.cgal.org/latest/Kernel_d/classCGAL_1_1Epick__d.html">CGAL::Epick_d</a> class, which
+ * can be static if you know the ambiant dimension at compile-time, or dynamic if you don't.
+ * \tparam Point_range is the type of the range that provides the points.
+ * It must be a range whose iterator type is a `RandomAccessIterator`.
+ */
+template <typename Search_traits, typename Point_range>
+class Kd_tree_search {
+ typedef boost::iterator_property_map<
+ typename Point_range::const_iterator,
+ CGAL::Identity_property_map<std::ptrdiff_t> > Point_property_map;
+
+ public:
+ /// The Traits.
+ typedef Search_traits Traits;
+ /// Number type used for distances.
+ typedef typename Traits::FT FT;
+ /// The point type.
+ typedef typename Point_range::value_type Point;
+
+ typedef CGAL::Search_traits<
+ FT, Point,
+ typename Traits::Cartesian_const_iterator_d,
+ typename Traits::Construct_cartesian_const_iterator_d> Traits_base;
+
+ typedef CGAL::Search_traits_adapter<
+ std::ptrdiff_t,
+ Point_property_map,
+ Traits_base> STraits;
+ typedef CGAL::Distance_adapter<
+ std::ptrdiff_t,
+ Point_property_map,
+ CGAL::Euclidean_distance<Traits_base> > Orthogonal_distance;
+
+ typedef CGAL::Orthogonal_k_neighbor_search<STraits> K_neighbor_search;
+ typedef typename K_neighbor_search::Tree Tree;
+ typedef typename K_neighbor_search::Distance Distance;
+ /// \brief The range returned by a k-nearest or k-furthest neighbor search.
+ /// Its value type is `std::pair<std::size_t, FT>` where `first` is the index
+ /// of a point P and `second` is the squared distance between P and the query point.
+ typedef K_neighbor_search KNS_range;
+
+ typedef CGAL::Orthogonal_incremental_neighbor_search<
+ STraits, Distance, CGAL::Sliding_midpoint<STraits>, Tree>
+ Incremental_neighbor_search;
+ /// \brief The range returned by an incremental nearest or furthest neighbor search.
+ /// Its value type is `std::pair<std::size_t, FT>` where `first` is the index
+ /// of a point P and `second` is the squared distance between P and the query point.
+ typedef Incremental_neighbor_search INS_range;
+
+ typedef CGAL::Fuzzy_sphere<STraits> Fuzzy_sphere;
+ /// \brief Constructor
+ /// @param[in] points Const reference to the point range. This range
+ /// is not copied, so it should not be destroyed or modified afterwards.
+ Kd_tree_search(Point_range const& points)
+ : m_points(points),
+ m_tree(boost::counting_iterator<std::ptrdiff_t>(0),
+ boost::counting_iterator<std::ptrdiff_t>(points.size()),
+ typename Tree::Splitter(),
+ STraits(std::begin(points))) {
+ // Build the tree now (we don't want to wait for the first query)
+ m_tree.build();
+ }
+
+ /// \brief Constructor
+ /// @param[in] points Const reference to the point range. This range
+ /// is not copied, so it should not be destroyed or modified afterwards.
+ /// @param[in] only_these_points Specifies the indices of the points that
+ /// should be actually inserted into the tree. The other points are ignored.
+ template <typename Point_indices_range>
+ Kd_tree_search(
+ Point_range const& points,
+ Point_indices_range const& only_these_points)
+ : m_points(points),
+ m_tree(
+ only_these_points.begin(), only_these_points.end(),
+ typename Tree::Splitter(),
+ STraits(std::begin(points))) {
+ // Build the tree now (we don't want to wait for the first query)
+ m_tree.build();
+ }
+
+ /// \brief Constructor
+ /// @param[in] points Const reference to the point range. This range
+ /// is not copied, so it should not be destroyed or modified afterwards.
+ /// @param[in] begin_idx, past_the_end_idx Define the subset of the points that
+ /// should be actually inserted into the tree. The other points are ignored.
+ Kd_tree_search(
+ Point_range const& points,
+ std::size_t begin_idx, std::size_t past_the_end_idx)
+ : m_points(points),
+ m_tree(
+ boost::counting_iterator<std::ptrdiff_t>(begin_idx),
+ boost::counting_iterator<std::ptrdiff_t>(past_the_end_idx),
+ typename Tree::Splitter(),
+ STraits(std::begin(points))) {
+ // Build the tree now (we don't want to wait for the first query)
+ m_tree.build();
+ }
+
+ // Be careful, this function invalidates the tree,
+ // which will be recomputed at the next query
+ void insert(std::ptrdiff_t point_idx) {
+ m_tree.insert(point_idx);
+ }
+
+ /// \brief Search for the k-nearest neighbors from a query point.
+ /// @param[in] p The query point.
+ /// @param[in] k Number of nearest points to search.
+ /// @param[in] sorted Indicates if the computed sequence of k-nearest neighbors needs to be sorted.
+ /// @param[in] eps Approximation factor.
+ /// @return A range (whose `value_type` is `std::size_t`) containing the k-nearest neighbors.
+ KNS_range k_nearest_neighbors(
+ Point const& p,
+ unsigned int k,
+ bool sorted = true,
+ FT eps = FT(0)) const {
+ // Initialize the search structure, and search all N points
+ // Note that we need to pass the Distance explicitly since it needs to
+ // know the property map
+ K_neighbor_search search(
+ m_tree,
+ p,
+ k,
+ eps,
+ true,
+ Orthogonal_distance(std::begin(m_points)), sorted);
+
+ return search;
+ }
+
+ /// \brief Search incrementally for the nearest neighbors from a query point.
+ /// @param[in] p The query point.
+ /// @param[in] eps Approximation factor.
+ /// @return A range (whose `value_type` is `std::size_t`) containing the
+ /// neighbors sorted by their distance to p.
+ /// All the neighbors are not computed by this function, but they will be
+ /// computed incrementally when the iterator on the range is incremented.
+ INS_range incremental_nearest_neighbors(Point const& p, FT eps = FT(0)) const {
+ // Initialize the search structure, and search all N points
+ // Note that we need to pass the Distance explicitly since it needs to
+ // know the property map
+ Incremental_neighbor_search search(
+ m_tree,
+ p,
+ eps,
+ true,
+ Orthogonal_distance(std::begin(m_points)) );
+
+ return search;
+ }
+
+ /// \brief Search for the k-furthest points from a query point.
+ /// @param[in] p The query point.
+ /// @param[in] k Number of furthest points to search.
+ /// @param[in] sorted Indicates if the computed sequence of k-furthest neighbors needs to be sorted.
+ /// @param[in] eps Approximation factor.
+ /// @return A range (whose `value_type` is `std::size_t`) containing the k-furthest neighbors.
+ KNS_range k_furthest_neighbors(
+ Point const& p,
+ unsigned int k,
+ bool sorted = true,
+ FT eps = FT(0)) const {
+ // Initialize the search structure, and search all N points
+ // Note that we need to pass the Distance explicitly since it needs to
+ // know the property map
+ K_neighbor_search search(
+ m_tree,
+ p,
+ k,
+ eps,
+ false,
+ Orthogonal_distance(std::begin(m_points)), sorted);
+
+ return search;
+ }
+
+ /// \brief Search incrementally for the furthest neighbors from a query point.
+ /// @param[in] p The query point.
+ /// @param[in] eps Approximation factor.
+ /// @return A range (whose `value_type` is `std::size_t`)
+ /// containing the neighbors sorted by their distance to p.
+ /// All the neighbors are not computed by this function, but they will be
+ /// computed incrementally when the iterator on the range is incremented.
+ INS_range incremental_furthest_neighbors(Point const& p, FT eps = FT(0)) const {
+ // Initialize the search structure, and search all N points
+ // Note that we need to pass the Distance explicitly since it needs to
+ // know the property map
+ Incremental_neighbor_search search(
+ m_tree,
+ p,
+ eps,
+ false,
+ Orthogonal_distance(std::begin(m_points)) );
+
+ return search;
+ }
+
+ /// \brief Search for all the neighbors in a ball.
+ /// @param[in] p The query point.
+ /// @param[in] radius The search radius
+ /// @param[out] it The points that lie inside the sphere of center `p` and radius `radius`.
+ /// Note: `it` is used this way: `*it++ = each_point`.
+ /// @param[in] eps Approximation factor.
+ template <typename OutputIterator>
+ void all_near_neighbors(Point const& p,
+ FT radius,
+ OutputIterator it,
+ FT eps = FT(0)) const {
+ m_tree.search(it, Fuzzy_sphere(p, radius, eps, m_tree.traits()));
+ }
+
+ int tree_depth() const {
+ return m_tree.root()->depth();
+ }
+
+ private:
+ Point_range const& m_points;
+ Tree m_tree;
+};
+
+} // namespace spatial_searching
+} // namespace Gudhi
+
+#endif // KD_TREE_SEARCH_H_
diff --git a/src/Spatial_searching/test/CMakeLists.txt b/src/Spatial_searching/test/CMakeLists.txt
new file mode 100644
index 00000000..18f7c6b8
--- /dev/null
+++ b/src/Spatial_searching/test/CMakeLists.txt
@@ -0,0 +1,11 @@
+project(Spatial_searching_tests)
+
+if(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0)
+ include(GUDHI_test_coverage)
+
+ add_executable( Spatial_searching_test_Kd_tree_search test_Kd_tree_search.cpp )
+ target_link_libraries(Spatial_searching_test_Kd_tree_search
+ ${CGAL_LIBRARY} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
+
+ gudhi_add_coverage_test(Spatial_searching_test_Kd_tree_search)
+endif ()
diff --git a/src/Spatial_searching/test/test_Kd_tree_search.cpp b/src/Spatial_searching/test/test_Kd_tree_search.cpp
new file mode 100644
index 00000000..d6c6fba3
--- /dev/null
+++ b/src/Spatial_searching/test/test_Kd_tree_search.cpp
@@ -0,0 +1,108 @@
+/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
+ * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
+ * Author(s): Clement Jamin
+ *
+ * Copyright (C) 2016 Inria
+ *
+ * Modification(s):
+ * - YYYY/MM Author: Description of the modification
+ */
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE Spatial_searching - test Kd_tree_search
+#include <boost/test/unit_test.hpp>
+
+#include <gudhi/Kd_tree_search.h>
+
+#include <CGAL/Epick_d.h>
+#include <CGAL/Random.h>
+
+#include <vector>
+
+BOOST_AUTO_TEST_CASE(test_Kd_tree_search) {
+ typedef CGAL::Epick_d<CGAL::Dimension_tag<4> > K;
+ typedef K::FT FT;
+ typedef K::Point_d Point;
+ typedef std::vector<Point> Points;
+
+ typedef Gudhi::spatial_searching::Kd_tree_search<
+ K, Points> Points_ds;
+
+ CGAL::Random rd;
+
+ Points points;
+ for (int i = 0; i < 500; ++i)
+ points.push_back(Point(rd.get_double(-1., 1), rd.get_double(-1., 1), rd.get_double(-1., 1), rd.get_double(-1., 1)));
+
+ Points_ds points_ds(points);
+
+ // Test k_nearest_neighbors
+ std::size_t closest_pt_index =
+ points_ds.k_nearest_neighbors(points[10], 1, false).begin()->first;
+ BOOST_CHECK(closest_pt_index == 10);
+
+ auto kns_range = points_ds.k_nearest_neighbors(points[20], 10, true);
+
+ std::vector<std::size_t> knn_result;
+ FT last_dist = -1.;
+ for (auto const& nghb : kns_range) {
+ BOOST_CHECK(nghb.second > last_dist);
+ knn_result.push_back(nghb.second);
+ last_dist = nghb.second;
+ }
+
+ // Test incremental_nearest_neighbors
+ closest_pt_index =
+ points_ds.incremental_nearest_neighbors(points[10]).begin()->first;
+ BOOST_CHECK(closest_pt_index == 10);
+
+ auto inn_range = points_ds.incremental_nearest_neighbors(points[20]);
+
+ std::vector<std::size_t> inn_result;
+ last_dist = -1.;
+ auto inn_it = inn_range.begin();
+ for (int i = 0; i < 10; ++inn_it, ++i) {
+ auto const& nghb = *inn_it;
+ BOOST_CHECK(nghb.second > last_dist);
+ inn_result.push_back(nghb.second);
+ last_dist = nghb.second;
+ }
+
+ // Same result for KNN and INN?
+ BOOST_CHECK(knn_result == inn_result);
+
+ // Test k_furthest_neighbors
+ auto kfn_range = points_ds.k_furthest_neighbors(points[20], 10, true);
+
+ std::vector<std::size_t> kfn_result;
+ last_dist = kfn_range.begin()->second;
+ for (auto const& nghb : kfn_range) {
+ BOOST_CHECK(nghb.second <= last_dist);
+ kfn_result.push_back(nghb.second);
+ last_dist = nghb.second;
+ }
+
+ // Test k_furthest_neighbors
+ auto ifn_range = points_ds.incremental_furthest_neighbors(points[20]);
+
+ std::vector<std::size_t> ifn_result;
+ last_dist = ifn_range.begin()->second;
+ auto ifn_it = ifn_range.begin();
+ for (int i = 0; i < 10; ++ifn_it, ++i) {
+ auto const& nghb = *ifn_it;
+ BOOST_CHECK(nghb.second <= last_dist);
+ ifn_result.push_back(nghb.second);
+ last_dist = nghb.second;
+ }
+
+ // Same result for KFN and IFN?
+ BOOST_CHECK(kfn_result == ifn_result);
+
+ // Test all_near_neighbors
+ Point rs_q(rd.get_double(-1., 1), rd.get_double(-1., 1), rd.get_double(-1., 1), rd.get_double(-1., 1));
+ std::vector<std::size_t> rs_result;
+ points_ds.all_near_neighbors(rs_q, 0.5, std::back_inserter(rs_result));
+ K k;
+ for (auto const& p_idx : rs_result)
+ BOOST_CHECK(k.squared_distance_d_object()(points[p_idx], rs_q) <= 0.5);
+}