Spatial Partitioning and Search Operations with Octrees¶
An octree is a tree data structure in which each internal node has exactly eight children. Octrees are most often used to partition a three-dimensional space by recursively subdividing it into eight octants. Octrees are the three-dimensional analog of quadtrees. The word is derived from oct (Greek root meaning “eight”) + tree. Octrees are often used in 3D graphic and 3D game engines.
In this tutorial we will learn how to use the octree for spatial partitioning and neighbor search within the point cloud data. This tutorial covers the process of performing “Neighbors within Radius Search”, “Approximate Nearest Neighbor (ANN) Search” and “K-Nearest Neighbors (KNN) Search”.
Note
This tutorial can be run both inside and outside a Docker* image. We assume that the pcl-oneapi-tutorials Deb package has been installed, and the user has copied the tutorial directory from /opt/intel/pcl/oneapi/tutorials/ to a user-writable directory.
Prepare the environment:
oneapi_octree_search.cpp
should be in the directory with following content:1#include <iostream> 2#include <fstream> 3#include <numeric> 4#include <pcl/oneapi/octree/octree.hpp> 5#include <pcl/oneapi/containers/device_array.h> 6#include <pcl/point_cloud.h> 7 8using namespace pcl::oneapi; 9 10float dist(Octree::PointType p, Octree::PointType q) { 11 return std::sqrt((p.x-q.x)*(p.x-q.x) + (p.y-q.y)*(p.y-q.y) + (p.z-q.z)*(p.z-q.z)); 12} 13 14int main (int argc, char** argv) 15{ 16 std::cout << "Running on device: " << dpct::get_default_queue().get_device().get_info<sycl::info::device::name>() << "\n"; 17 18 std::size_t data_size = 871000; 19 std::size_t query_size = 10000; 20 float cube_size = 1024.f; 21 float max_radius = cube_size / 30.f; 22 float shared_radius = cube_size / 30.f; 23 const int max_answers = 5; 24 const int k = 5; 25 std::size_t i; 26 std::vector<Octree::PointType> points; 27 std::vector<Octree::PointType> queries; 28 std::vector<float> radiuses; 29 std::vector<int> indices; 30 31 //Generate point cloud data, queries, radiuses, indices 32 srand (0); 33 points.resize(data_size); 34 for(i = 0; i < data_size; ++i) 35 { 36 points[i].x = ((float)rand())/(float)RAND_MAX * cube_size; 37 points[i].y = ((float)rand())/(float)RAND_MAX * cube_size; 38 points[i].z = ((float)rand())/(float)RAND_MAX * cube_size; 39 } 40 41 queries.resize(query_size); 42 radiuses.resize(query_size); 43 for (i = 0; i < query_size; ++i) 44 { 45 queries[i].x = ((float)rand())/(float)RAND_MAX * cube_size; 46 queries[i].y = ((float)rand())/(float)RAND_MAX * cube_size; 47 queries[i].z = ((float)rand())/(float)RAND_MAX * cube_size; 48 radiuses[i] = ((float)rand())/(float)RAND_MAX * max_radius; 49 }; 50 51 indices.resize(query_size / 2); 52 for(i = 0; i < query_size / 2; ++i) 53 { 54 indices[i] = i * 2; 55 } 56 57 //Prepare oneAPI cloud 58 pcl::oneapi::Octree::PointCloud cloud_device; 59 cloud_device.upload(points); 60 61 //oneAPI build 62 pcl::oneapi::Octree octree_device; 63 octree_device.setCloud(cloud_device); 64 octree_device.build(); 65 66 //Upload queries and radiuses 67 pcl::oneapi::Octree::Queries queries_device; 68 pcl::oneapi::Octree::Radiuses radiuses_device; 69 queries_device.upload(queries); 70 radiuses_device.upload(radiuses); 71 72 //Prepare output buffers on device 73 pcl::oneapi::NeighborIndices result_device1(queries_device.size(), max_answers); 74 pcl::oneapi::NeighborIndices result_device2(queries_device.size(), max_answers); 75 pcl::oneapi::NeighborIndices result_device3(indices.size(), max_answers); 76 pcl::oneapi::NeighborIndices result_device_ann(queries_device.size(), 1); 77 pcl::oneapi::Octree::ResultSqrDists dists_device_ann; 78 pcl::oneapi::NeighborIndices result_device_knn(queries_device.size(), k); 79 pcl::oneapi::Octree::ResultSqrDists dists_device_knn; 80 81 //oneAPI octree radius search with shared radius 82 octree_device.radiusSearch(queries_device, shared_radius, max_answers, result_device1); 83 84 //oneAPI octree radius search with individual radius 85 octree_device.radiusSearch(queries_device, radiuses_device, max_answers, result_device2); 86 87 //oneAPI octree radius search with shared radius using indices to specify 88 //the queries. 89 pcl::oneapi::Octree::Indices cloud_indices; 90 cloud_indices.upload(indices); 91 octree_device.radiusSearch(queries_device, cloud_indices, shared_radius, max_answers, result_device3); 92 93 //oneAPI octree ANN search 94 //if neighbor points distances results are not required, can just call 95 //octree_device.approxNearestSearch(queries_device, result_device_ann) 96 octree_device.approxNearestSearch(queries_device, result_device_ann, dists_device_ann); 97 98 //oneAPI octree KNN search 99 //if neighbor points distances results are not required, can just call 100 //octree_device.nearestKSearchBatch(queries_device, k, result_device_knn) 101 octree_device.nearestKSearchBatch(queries_device, k, result_device_knn, dists_device_knn); 102 103 //Download results 104 std::vector<int> sizes1; 105 std::vector<int> sizes2; 106 std::vector<int> sizes3; 107 result_device1.sizes.download(sizes1); 108 result_device2.sizes.download(sizes2); 109 result_device3.sizes.download(sizes3); 110 111 std::vector<int> downloaded_buffer1, downloaded_buffer2, downloaded_buffer3, results_batch; 112 result_device1.data.download(downloaded_buffer1); 113 result_device2.data.download(downloaded_buffer2); 114 result_device3.data.download(downloaded_buffer3); 115 116 int query_idx = 2; 117 std::cout << "Neighbors within shared radius search at (" 118 << queries[query_idx].x << " " 119 << queries[query_idx].y << " " 120 << queries[query_idx].z << ") with radius=" << shared_radius << std::endl; 121 for (i = 0; i < sizes1[query_idx]; ++i) 122 { 123 std::cout << " " << points[downloaded_buffer1[max_answers * query_idx + i]].x 124 << " " << points[downloaded_buffer1[max_answers * query_idx + i]].y 125 << " " << points[downloaded_buffer1[max_answers * query_idx + i]].z 126 << " (distance: " << dist(points[downloaded_buffer1[max_answers * query_idx + i]], queries[query_idx]) << ")" << std::endl; 127 } 128 129 std::cout << "Neighbors within individual radius search at (" 130 << queries[query_idx].x << " " 131 << queries[query_idx].y << " " 132 << queries[query_idx].z << ") with radius=" << radiuses[query_idx] << std::endl; 133 for (i = 0; i < sizes2[query_idx]; ++i) 134 { 135 std::cout << " " << points[downloaded_buffer2[max_answers * query_idx + i]].x 136 << " " << points[downloaded_buffer2[max_answers * query_idx + i]].y 137 << " " << points[downloaded_buffer2[max_answers * query_idx + i]].z 138 << " (distance: " << dist(points[downloaded_buffer2[max_answers * query_idx + i]], queries[query_idx]) << ")" << std::endl; 139 } 140 141 std::cout << "Neighbors within indices radius search at (" 142 << queries[query_idx].x << " " 143 << queries[query_idx].y << " " 144 << queries[query_idx].z << ") with radius=" << shared_radius << std::endl; 145 for (i = 0; i < sizes3[query_idx/2]; ++i) 146 { 147 std::cout << " " << points[downloaded_buffer3[max_answers * query_idx / 2 + i]].x 148 << " " << points[downloaded_buffer3[max_answers * query_idx / 2 + i]].y 149 << " " << points[downloaded_buffer3[max_answers * query_idx / 2 + i]].z 150 << " (distance: " << dist(points[downloaded_buffer3[max_answers * query_idx / 2 + i]], queries[2]) << ")" << std::endl; 151 } 152 153 std::cout << "Approximate nearest neighbor at (" 154 << queries[query_idx].x << " " 155 << queries[query_idx].y << " " 156 << queries[query_idx].z << ")" << std::endl; 157 std::cout << " " << points[result_device_ann.data[query_idx]].x 158 << " " << points[result_device_ann.data[query_idx]].y 159 << " " << points[result_device_ann.data[query_idx]].z 160 << " (distance: " << std::sqrt(dists_device_ann[query_idx]) << ")" << std::endl; 161 162 std::cout << "K-nearest neighbors (k = " << k << ") at (" 163 << queries[query_idx].x << " " 164 << queries[query_idx].y << " " 165 << queries[query_idx].z << ")" << std::endl; 166 for (i = query_idx * k; i < (query_idx + 1) * k; ++i) 167 { 168 std::cout << " " << points[result_device_knn.data[i]].x 169 << " " << points[result_device_knn.data[i]].y 170 << " " << points[result_device_knn.data[i]].z 171 << " (distance: " << std::sqrt(dists_device_knn[i]) << ")" << std::endl; 172 } 173}
Source the Intel® oneAPI Base Toolkit environment:
(Optional) Setup proxy setting to download test data:
Build the code:
Run the binary:
Expected results example:
The search only finds the first five neighbors (as specified by
max_answers
), so a different radius finds different points.Neighbors within shared radius search at (671.675 733.78 466.178) with radius=34.1333 660.296 725.957 439.677 (distance: 29.8829) 665.768 721.884 442.919 (distance: 26.7846) 683.988 714.608 445.164 (distance: 30.9962) 677.927 725.08 446.531 (distance: 22.3788) 695.066 723.509 445.762 (distance: 32.7028) Neighbors within individual radius search at (671.675 733.78 466.178) with radius=19.3623 672.71 736.679 447.835 (distance: 18.6) 664.46 731.504 452.074 (distance: 16.0048) 671.238 725.881 461.408 (distance: 9.23819) 667.707 718.527 466.622 (distance: 15.7669) 654.552 733.636 467.795 (distance: 17.1993) Neighbors within indices radius search at (671.675 733.78 466.178) with radius=34.1333 660.296 725.957 439.677 (distance: 29.8829) 665.768 721.884 442.919 (distance: 26.7846) 683.988 714.608 445.164 (distance: 30.9962) 677.927 725.08 446.531 (distance: 22.3788) 695.066 723.509 445.762 (distance: 32.7028)![]()
Code Explanation¶
Generate point cloud data, queries, radiuses, indices with a random number.
//Generate point cloud data, queries, radiuses, indices
srand (0);
points.resize(data_size);
for(i = 0; i < data_size; ++i)
{
points[i].x = ((float)rand())/(float)RAND_MAX * cube_size;
points[i].y = ((float)rand())/(float)RAND_MAX * cube_size;
points[i].z = ((float)rand())/(float)RAND_MAX * cube_size;
}
queries.resize(query_size);
radiuses.resize(query_size);
for (i = 0; i < query_size; ++i)
{
queries[i].x = ((float)rand())/(float)RAND_MAX * cube_size;
queries[i].y = ((float)rand())/(float)RAND_MAX * cube_size;
queries[i].z = ((float)rand())/(float)RAND_MAX * cube_size;
radiuses[i] = ((float)rand())/(float)RAND_MAX * max_radius;
};
indices.resize(query_size / 2);
for(i = 0; i < query_size / 2; ++i)
{
indices[i] = i * 2;
}
- Create and build the oneAPI™ point cloud; then upload the queries and
radiuses to a oneAPI™ device.
//Prepare oneAPI cloud
pcl::oneapi::Octree::PointCloud cloud_device;
cloud_device.upload(points);
//oneAPI build
pcl::oneapi::Octree octree_device;
octree_device.setCloud(cloud_device);
octree_device.build();
//Upload queries and radiuses
pcl::oneapi::Octree::Queries queries_device;
pcl::oneapi::Octree::Radiuses radiuses_device;
queries_device.upload(queries);
radiuses_device.upload(radiuses);
Create output buffers where we can download output from the oneAPI™ device.
//Prepare output buffers on device
pcl::oneapi::NeighborIndices result_device1(queries_device.size(), max_answers);
pcl::oneapi::NeighborIndices result_device2(queries_device.size(), max_answers);
pcl::oneapi::NeighborIndices result_device3(indices.size(), max_answers);
pcl::oneapi::NeighborIndices result_device_ann(queries_device.size(), 1);
pcl::oneapi::Octree::ResultSqrDists dists_device_ann;
pcl::oneapi::NeighborIndices result_device_knn(queries_device.size(), k);
pcl::oneapi::Octree::ResultSqrDists dists_device_knn;
The fist radius search method is “search with shared radius”. In this search method, all queries use the same radius to find the neighbors.
The second radius search method is “search with individual radius”. In this search method, each query uses its own specific radius to find the neighbors.
The third radius search method is “search with shared radius using indices”. In this search method, all queries use the same radius, and indices specify the queries.
Perform ANN search.
Perform KNN search.
Download the search results from the oneAPI™ device. The size vector contains the size of found neighbors for each query. The downloaded_buffer vector contains the index of all found neighbors for each query.
//Download results
std::vector<int> sizes1;
std::vector<int> sizes2;
std::vector<int> sizes3;
result_device1.sizes.download(sizes1);
result_device2.sizes.download(sizes2);
result_device3.sizes.download(sizes3);
std::vector<int> downloaded_buffer1, downloaded_buffer2, downloaded_buffer3, results_batch;
result_device1.data.download(downloaded_buffer1);
result_device2.data.download(downloaded_buffer2);
result_device3.data.download(downloaded_buffer3);
Print the query, radius, and found neighbors to verify that the result is correct.
int query_idx = 2;
std::cout << "Neighbors within shared radius search at ("
<< queries[query_idx].x << " "
<< queries[query_idx].y << " "
<< queries[query_idx].z << ") with radius=" << shared_radius << std::endl;
for (i = 0; i < sizes1[query_idx]; ++i)
{
std::cout << " " << points[downloaded_buffer1[max_answers * query_idx + i]].x
<< " " << points[downloaded_buffer1[max_answers * query_idx + i]].y
<< " " << points[downloaded_buffer1[max_answers * query_idx + i]].z
<< " (distance: " << dist(points[downloaded_buffer1[max_answers * query_idx + i]], queries[query_idx]) << ")" << std::endl;
}
std::cout << "Neighbors within individual radius search at ("
<< queries[query_idx].x << " "
<< queries[query_idx].y << " "
<< queries[query_idx].z << ") with radius=" << radiuses[query_idx] << std::endl;
for (i = 0; i < sizes2[query_idx]; ++i)
{
std::cout << " " << points[downloaded_buffer2[max_answers * query_idx + i]].x
<< " " << points[downloaded_buffer2[max_answers * query_idx + i]].y
<< " " << points[downloaded_buffer2[max_answers * query_idx + i]].z
<< " (distance: " << dist(points[downloaded_buffer2[max_answers * query_idx + i]], queries[query_idx]) << ")" << std::endl;
}
std::cout << "Neighbors within indices radius search at ("
<< queries[query_idx].x << " "
<< queries[query_idx].y << " "
<< queries[query_idx].z << ") with radius=" << shared_radius << std::endl;
for (i = 0; i < sizes3[query_idx/2]; ++i)
{
std::cout << " " << points[downloaded_buffer3[max_answers * query_idx / 2 + i]].x
<< " " << points[downloaded_buffer3[max_answers * query_idx / 2 + i]].y
<< " " << points[downloaded_buffer3[max_answers * query_idx / 2 + i]].z
<< " (distance: " << dist(points[downloaded_buffer3[max_answers * query_idx / 2 + i]], queries[2]) << ")" << std::endl;
}
std::cout << "Approximate nearest neighbor at ("
<< queries[query_idx].x << " "
<< queries[query_idx].y << " "
<< queries[query_idx].z << ")" << std::endl;
std::cout << " " << points[result_device_ann.data[query_idx]].x
<< " " << points[result_device_ann.data[query_idx]].y
<< " " << points[result_device_ann.data[query_idx]].z
<< " (distance: " << std::sqrt(dists_device_ann[query_idx]) << ")" << std::endl;
std::cout << "K-nearest neighbors (k = " << k << ") at ("
<< queries[query_idx].x << " "
<< queries[query_idx].y << " "
<< queries[query_idx].z << ")" << std::endl;
for (i = query_idx * k; i < (query_idx + 1) * k; ++i)
{
std::cout << " " << points[result_device_knn.data[i]].x
<< " " << points[result_device_knn.data[i]].y
<< " " << points[result_device_knn.data[i]].z
<< " (distance: " << std::sqrt(dists_device_knn[i]) << ")" << std::endl;
}