ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
nearest_neighbor_search.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include "core/Tensor.h"
14 #include "pybind/docstring.h"
15 #include "pybind/pybind_utils.h"
16 
17 namespace cloudViewer {
18 namespace core {
19 namespace nns {
20 
21 void pybind_core_nns(py::module &m) {
22  py::module m_nns = m.def_submodule("nns");
23  py::class_<NearestNeighborSearch, std::shared_ptr<NearestNeighborSearch>>
24  nns(m_nns, "NearestNeighborSearch",
25  R"(NearestNeighborSearch class for nearest neighbor search.
26 
27 This class holds multiple index types to accelerate various nearest neighbor
28 search operations for a dataset of points with shape {n,d} with `n` as the number
29 of points and `d` as the dimension of the points. The class supports knn search,
30 fixed-radius search, multi-radius search, and hybrid search.
31 
32 Example:
33  The following example demonstrates how to perform knn search using the class::
34 
35  import cloudViewer as cv3d
36  import numpy as np
37 
38  dataset = np.random.rand(10,3)
39  query_points = np.random.rand(5,3)
40 
41  nns = cv3d.core.nns.NearestNeighborSearch(dataset)
42 
43  # initialize the knn_index before we can use knn_search
44  nns.knn_index()
45 
46  # perform knn search to get the 3 closest points with respect to the
47  # Euclidean distance. The returned distance is given as the squared
48  # distances
49  indices, squared_distances = nns.knn_search(query_points, knn=3)
50 
51  )");
52 
53  // Constructors.
54  nns.def(py::init<const Tensor &, const Dtype>(), "dataset_points"_a,
55  "index_dtype"_a = core::Int64);
56 
57  // Index functions.
58  nns.def("knn_index", &NearestNeighborSearch::KnnIndex,
59  R"(Initialize the index for knn search.
60 
61 This function needs to be called once before performing search operations.
62 
63 Returns:
64  True on success.
65  )");
66 
67  nns.def(
68  "fixed_radius_index",
70  if (!radius.has_value()) {
71  return self.FixedRadiusIndex();
72  } else {
73  return self.FixedRadiusIndex(radius.value());
74  }
75  },
76  py::arg("radius") = py::none(),
77  R"(Initialize the index for fixed-radius search.
78 
79 This function needs to be called once before performing search operations.
80 
81 Args:
82  radius (float, optional): Radius value for fixed-radius search. Required
83  for GPU fixed radius index.
84 
85 Returns:
86  True on success.
87  )");
88 
89  nns.def("multi_radius_index", &NearestNeighborSearch::MultiRadiusIndex,
90  R"(Initialize the index for multi-radius search.
91 
92 This function needs to be called once before performing search operations.
93 
94 Returns:
95  True on success.
96  )");
97 
98  nns.def(
99  "hybrid_index",
101  if (!radius.has_value()) {
102  return self.HybridIndex();
103  } else {
104  return self.HybridIndex(radius.value());
105  }
106  },
107  py::arg("radius") = py::none(),
108  R"(Initialize the index for hybrid search.
109 
110 This function needs to be called once before performing search operations.
111 
112 Args:
113  radius (float, optional): Radius value for hybrid search. Required
114  for GPU hybrid index.
115 
116 Returns:
117  True on success.
118  )");
119 
120  // Search functions.
121  nns.def("knn_search", &NearestNeighborSearch::KnnSearch, "query_points"_a,
122  "knn"_a,
123  R"(Perform knn search.
124 
125 Note:
126  To use knn_search initialize the index using knn_index before calling this function.
127 
128 Args:
129  query_points (cloudViewer.core.Tensor): Query points with shape {n, d}.
130  knn (int): Number of neighbors to search per query point.
131 
132 Example:
133  The following searches the 3 nearest neighbors for random dataset and query points::
134 
135  import cloudViewer as cv3d
136  import numpy as np
137 
138  dataset = np.random.rand(10,3)
139  query_points = np.random.rand(5,3)
140 
141  nns = cv3d.core.nns.NearestNeighborSearch(dataset)
142 
143  # initialize the knn_index before we can use knn_search
144  nns.knn_index()
145 
146  # perform knn search to get the 3 closest points with respect to the
147  # Euclidean distance. The returned distance is given as the squared
148  # distances
149  indices, squared_distances = nns.knn_search(query_points, knn=3)
150 
151 Returns:
152  Tuple of Tensors (indices, squared_distances).
153  - indices: Tensor of shape {n, knn}.
154  - squared_distances: Tensor of shape {n, knn}. The distances are squared L2 distances.
155 
156  )");
157 
158  nns.def(
159  "fixed_radius_search",
160  [](NearestNeighborSearch &self, Tensor query_points, double radius,
162  if (!sort.has_value()) {
163  return self.FixedRadiusSearch(query_points, radius, true);
164  } else {
165  return self.FixedRadiusSearch(query_points, radius,
166  sort.value());
167  }
168  },
169  py::arg("query_points"), py::arg("radius"),
170  py::arg("sort") = py::none(),
171  R"(Perform fixed-radius search.
172 
173 Note:
174  To use fixed_radius_search initialize the index using fixed_radius_index before calling this function.
175 
176 Args:
177  query_points (cloudViewer.core.Tensor): Query points with shape {n, d}.
178  radius (float): Radius value for fixed-radius search. Note that this
179  parameter can differ from the radius used to initialize the index
180  for convenience, which may cause the index to be rebuilt for GPU
181  devices.
182  sort (bool, optional): Sort the results by distance. Default is True.
183 
184 Returns:
185  Tuple of Tensors (indices, splits, distances).
186  - indices: The indices of the neighbors.
187  - distances: The squared L2 distances.
188  - splits: The splits of the indices and distances defining the start
189  and exclusive end of each query point's neighbors. The shape is {num_queries+1}
190 
191 Example:
192  The following searches the neighbors within a radius of 1.0 a set of data and query points::
193 
194  # define data and query points
195  points = np.array([
196  [0.1,0.1,0.1],
197  [0.9,0.9,0.9],
198  [0.5,0.5,0.5],
199  [1.7,1.7,1.7],
200  [1.8,1.8,1.8],
201  [0.3,2.4,1.4]], dtype=np.float32)
202 
203  queries = np.array([
204  [1.0,1.0,1.0],
205  [0.5,2.0,2.0],
206  [0.5,2.1,2.1],
207  [100,100,100],
208  ], dtype=np.float32)
209 
210  nns = cv3d.core.nns.NearestNeighborSearch(points)
211  nns.fixed_radius_index(radius=1.0)
212 
213  neighbors_index, neighbors_distance, neighbors_splits = nns.fixed_radius_search(queries, radius=1.0, sort=True)
214  # returns neighbors_index = [1, 2, 5, 5]
215  # neighbors_distance = [0.03 0.75 0.56000006 0.62]
216  # neighbors_splits = [0, 2, 3, 4, 4]
217 
218  for i, (start_i, end_i) in enumerate(zip(neighbors_splits, neighbors_splits[1:])):
219  start_i = start_i.item()
220  end_i = end_i.item()
221  print(f"query_point {i} has the neighbors {neighbors_index[start_i:end_i].numpy()} "
222  f"with squared distances {neighbors_distance[start_i:end_i].numpy()}")
223  )");
224 
225  nns.def("multi_radius_search", &NearestNeighborSearch::MultiRadiusSearch,
226  "query_points"_a, "radii"_a,
227  R"(Perform multi-radius search. Each query point has an independent radius.
228 
229 Note:
230  To use multi_radius_search initialize the index using multi_radius_index before calling this function.
231 
232 Args:
233  query_points (cloudViewer.core.Tensor): Query points with shape {n, d}.
234  radii (cloudViewer.core.Tensor): Radii of query points. Each query point has one radius.
235 
236 Returns:
237  Tuple of Tensors (indices, splits, distances).
238  - indices: The indices of the neighbors.
239  - distances: The squared L2 distances.
240  - splits: The splits of the indices and distances defining the start
241  and exclusive end of each query point's neighbors. The shape is {num_queries+1}
242 
243 Example:
244  The following searches the neighbors with an individual radius for each query point::
245 
246  # define data, query points and radii
247  points = np.array([
248  [0.1,0.1,0.1],
249  [0.9,0.9,0.9],
250  [0.5,0.5,0.5],
251  [1.7,1.7,1.7],
252  [1.8,1.8,1.8],
253  [0.3,2.4,1.4]], dtype=np.float32)
254 
255  queries = np.array([
256  [1.0,1.0,1.0],
257  [0.5,2.0,2.0],
258  [0.5,2.1,2.1],
259  [100,100,100],
260  ], dtype=np.float32)
261 
262  radii = np.array([0.5, 1.0, 1.5, 2], dtype=np.float32)
263 
264  nns = cv3d.core.nns.NearestNeighborSearch(points)
265 
266  nns.multi_radius_index()
267 
268  neighbors_index, neighbors_distance, neighbors_splits = nns.multi_radius_search(queries, radii)
269  # returns neighbors_index = [1 5 5 3 4]
270  # neighbors_distance = [0.03 0.56 0.62 1.76 1.87]
271  # neighbors_splits = [0 1 2 5 5]
272 
273 
274  for i, (start_i, end_i) in enumerate(zip(neighbors_splits, neighbors_splits[1:])):
275  start_i = start_i.item()
276  end_i = end_i.item()
277  print(f"query_point {i} has the neighbors {neighbors_index[start_i:end_i].numpy()} "
278  f"with squared distances {neighbors_distance[start_i:end_i].numpy()}")
279  )");
280 
281  nns.def("hybrid_search", &NearestNeighborSearch::HybridSearch,
282  "query_points"_a, "radius"_a, "max_knn"_a,
283  R"(Perform hybrid search.
284 
285 Hybrid search behaves similarly to fixed-radius search, but with a maximum number of neighbors to search per query point.
286 
287 Note:
288  To use hybrid_search initialize the index using hybrid_index before calling this function.
289 
290 Args:
291  query_points (cloudViewer.core.Tensor): Query points with shape {n, d}.
292  radius (float): Radius value for hybrid search.
293  max_knn (int): Maximum number of neighbor to search per query.
294 
295 Returns:
296  Tuple of Tensors (indices, distances, counts)
297  - indices: The indices of the neighbors with shape {n, max_knn}.
298  If there are less than max_knn neighbors within the radius then the
299  last entries are padded with -1.
300  - distances: The squared L2 distances with shape {n, max_knn}.
301  If there are less than max_knn neighbors within the radius then the
302  last entries are padded with 0.
303  - counts: Counts of neighbour for each query points with shape {n}.
304  )");
305 }
306 
307 } // namespace nns
308 } // namespace core
309 } // namespace cloudViewer
A Class for nearest neighbor search.
std::tuple< Tensor, Tensor, Tensor > HybridSearch(const Tensor &query_points, const double radius, const int max_knn) const
std::tuple< Tensor, Tensor, Tensor > MultiRadiusSearch(const Tensor &query_points, const Tensor &radii)
std::pair< Tensor, Tensor > KnnSearch(const Tensor &query_points, int knn)
constexpr bool has_value() const noexcept
Definition: Optional.h:440
constexpr T const & value() const &
Definition: Optional.h:465
void pybind_core_nns(py::module &m)
const Dtype Int64
Definition: Dtype.cpp:47
Generic file read and write utility for python interface.