-
Notifications
You must be signed in to change notification settings - Fork 20
/
dbscan.cpp
130 lines (98 loc) · 3.78 KB
/
dbscan.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include "dbscan.hpp"
#include <cstddef>
#include <nanoflann/nanoflann.hpp>
#include <type_traits>
#include <vector>
// And this is the "dataset to kd-tree" adaptor class:
inline auto get_pt(const point2& p, std::size_t dim)
{
if(dim == 0) return p.x;
return p.y;
}
inline auto get_pt(const point3& p, std::size_t dim)
{
if(dim == 0) return p.x;
if(dim == 1) return p.y;
return p.z;
}
template<typename Point>
struct adaptor
{
const std::span<const Point>& points;
adaptor(const std::span<const Point>& points) : points(points) { }
/// CRTP helper method
//inline const Derived& derived() const { return obj; }
// Must return the number of data points
inline std::size_t kdtree_get_point_count() const { return points.size(); }
// Returns the dim'th component of the idx'th point in the class:
// Since this is inlined and the "dim" argument is typically an immediate value, the
// "if/else's" are actually solved at compile time.
inline float kdtree_get_pt(const std::size_t idx, const std::size_t dim) const
{
return get_pt(points[idx], dim);
}
// Optional bounding-box computation: return false to default to a standard bbox computation loop.
// Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again.
// Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds)
template <class BBOX>
bool kdtree_get_bbox(BBOX& /*bb*/) const { return false; }
auto const * elem_ptr(const std::size_t idx) const
{
return &points[idx].x;
}
};
auto sort_clusters(std::vector<std::vector<size_t>>& clusters)
{
for(auto& cluster: clusters)
{
std::sort(cluster.begin(), cluster.end());
}
}
template<int n_cols, typename Adaptor>
auto dbscan(const Adaptor& adapt, float eps, int min_pts)
{
eps *= eps;
using namespace nanoflann;
using my_kd_tree_t = KDTreeSingleIndexAdaptor<L2_Simple_Adaptor<float, decltype(adapt)>, decltype(adapt), n_cols>;
auto index = my_kd_tree_t(n_cols, adapt, KDTreeSingleIndexAdaptorParams(10));
index.buildIndex();
const auto n_points = adapt.kdtree_get_point_count();
auto visited = std::vector<bool>(n_points);
auto clusters = std::vector<std::vector<size_t>>();
auto matches = std::vector<std::pair<size_t, float>>();
auto sub_matches = std::vector<std::pair<size_t, float>>();
for(size_t i = 0; i < n_points; i++)
{
if (visited[i]) continue;
index.radiusSearch(adapt.elem_ptr(i), eps, matches, SearchParams(32, 0.f, false));
if (matches.size() < static_cast<size_t>(min_pts)) continue;
visited[i] = true;
auto cluster = std::vector({i});
while (matches.empty() == false)
{
auto nb_idx = matches.back().first;
matches.pop_back();
if (visited[nb_idx]) continue;
visited[nb_idx] = true;
index.radiusSearch(adapt.elem_ptr(nb_idx), eps, sub_matches, SearchParams(32, 0.f, false));
if (sub_matches.size() >= static_cast<size_t>(min_pts))
{
std::copy(sub_matches.begin(), sub_matches.end(), std::back_inserter(matches));
}
cluster.push_back(nb_idx);
}
clusters.emplace_back(std::move(cluster));
}
sort_clusters(clusters);
return clusters;
}
auto dbscan(const std::span<const point2>& data, float eps, int min_pts) -> std::vector<std::vector<size_t>>
{
const auto adapt = adaptor<point2>(data);
return dbscan<2>(adapt, eps, min_pts);
}
auto dbscan(const std::span<const point3>& data, float eps, int min_pts) -> std::vector<std::vector<size_t>>
{
const auto adapt = adaptor<point3>(data);
return dbscan<3>(adapt, eps, min_pts);
}