650 lines
23 KiB
C++
650 lines
23 KiB
C++
/*
|
|
----
|
|
This file is part of SECONDO.
|
|
|
|
Copyright (C) 2019,
|
|
Faculty of Mathematics and Computer Science,
|
|
Database Systems for New Applications.
|
|
|
|
SECONDO is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
SECONDO is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with SECONDO; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
----
|
|
|
|
|
|
//[<] [\ensuremath{<}]
|
|
//[>] [\ensuremath{>}]
|
|
|
|
\setcounter{tocdepth}{3}
|
|
\tableofcontents
|
|
|
|
|
|
|
|
1 DbScan
|
|
|
|
*/
|
|
|
|
#include "DbScan.h"
|
|
|
|
#include <limits>
|
|
|
|
#include "MathUtils.h"
|
|
|
|
using namespace pointcloud2;
|
|
using namespace std;
|
|
|
|
/*
|
|
1.2 Constructors
|
|
|
|
1.2.2 Constructor for Sequences
|
|
|
|
Creates a DbScan instance for the points in the given 1-based points vector.
|
|
If useSequences is true, the points are expected to be grouped into sequences,
|
|
with \_isLastInSeq being true for the points at the end of the respective
|
|
sequence (e.g. points 1 .. 5 are a sequence if points-[>]at(5).\_isLastInSeq
|
|
is true).
|
|
|
|
*/
|
|
template<unsigned dim>
|
|
DbScan<dim>::DbScan(const double eps, const size_t minPts,
|
|
std::shared_ptr<std::vector<DbScanPoint<dim>>> points,
|
|
const bool useSequences) :
|
|
_eps(eps),
|
|
_epsSquare(eps * eps),
|
|
_minPts(minPts),
|
|
|
|
_pointCount(points.get()->size() - 1),
|
|
_points(points),
|
|
_useSequences(useSequences),
|
|
_filter(std::shared_ptr<BitArray>(nullptr)),
|
|
_mmRTree(new RtreeT2<dim, PointIndex, MMRTREE_NODE_SIZE_MAX>()),
|
|
|
|
// statistics
|
|
_clusters(std::vector<ClusterInfo<dim>>()),
|
|
_expandCount(0),
|
|
_distTestCount(0) {
|
|
}
|
|
|
|
/*
|
|
1.2.3 Constructor with Filter
|
|
|
|
Creates a DbScan instance for a selection of the points in the given
|
|
1-based points vector. Only the points for which "true" is set in the given
|
|
filter will be considered.
|
|
|
|
*/
|
|
template<unsigned dim>
|
|
DbScan<dim>::DbScan(const double eps, const size_t minPts,
|
|
std::shared_ptr<std::vector<DbScanPoint<dim>>> points,
|
|
std::shared_ptr<BitArray> filter) :
|
|
_eps(eps),
|
|
_epsSquare(eps * eps),
|
|
_minPts(minPts),
|
|
|
|
_pointCount(points.get()->size() - 1),
|
|
_points(points),
|
|
_useSequences(filter.get() == 0),
|
|
_filter(filter),
|
|
_mmRTree(new RtreeT2<dim, PointIndex, MMRTREE_NODE_SIZE_MAX>()),
|
|
|
|
// statistics
|
|
_clusters(std::vector<ClusterInfo<dim>>()),
|
|
_expandCount(0),
|
|
_distTestCount(0) {
|
|
}
|
|
|
|
/*
|
|
1.3 Run Methods
|
|
|
|
1.3.1 run
|
|
|
|
Performs DBSCAN, writing the result into the DbScanPoint::\_clusterIds.
|
|
|
|
*/
|
|
template<unsigned dim>
|
|
void DbScan<dim>::run() {
|
|
// construct an MMRTree to point to the start index and point count
|
|
// of each point sequence in allPoints
|
|
|
|
constructMMRTree(_mmRTree, *(_points.get()), _useSequences,
|
|
getIterator());
|
|
|
|
if (REPORT_TO_CONSOLE) {
|
|
// _mmRTree->printStats(_pointCount, sizeof(DbScanPoint<dim>));
|
|
}
|
|
|
|
// a range query in the MMRTree will now return the start index of
|
|
// a point sequence in allPoints. isLastInSeq marks the end of the sequence
|
|
|
|
// ------------------------------------------
|
|
// start DBSCAN
|
|
|
|
// create entry for noise (at index 0)
|
|
_clusters.clear();
|
|
_clusters.push_back(ClusterInfo<dim>()); // noise count, will be set below
|
|
|
|
size_t noiseCount = getUsedPointCount();
|
|
auto iter = getIterator();
|
|
int i;
|
|
while ((i = iter.get()->next()) > 0) {
|
|
DbScanPoint<dim>& point = _points.get()->at(i);
|
|
if ( point._clusterId != SCAN_UNCLASSIFIED )
|
|
continue;
|
|
|
|
size_t clusterID = _clusters.size(); // get next free ID
|
|
ClusterInfo<dim> clusterInfo = expandCluster(i, clusterID);
|
|
if ( clusterInfo._size == 0 ) {
|
|
// the given point is noise or a border point (but no core)
|
|
point._clusterId = SCAN_NOISE; // may be changed later
|
|
continue;
|
|
}
|
|
|
|
// report the size of the identified cluster
|
|
if (REPORT_TO_CONSOLE && clusterID <= REPORT_CLUSTER_SUMMARY_MAX) {
|
|
cout << "=> Cluster " << clusterID << ": "
|
|
<< formatInt(clusterInfo._size) << " points"
|
|
<< ", cohesion " << formatInt(clusterInfo._cohesion)
|
|
<< " (avg " << clusterInfo.getNeighborAvg()
|
|
<< "), bbox ";
|
|
clusterInfo._bbox.Print(cout); // also prints endl
|
|
if (REPORT_LINES_PER_CLUSTER > 0 &&
|
|
clusterID <= REPORT_CLUSTER_DETAILS_MAX) {
|
|
cout << endl;
|
|
}
|
|
}
|
|
|
|
_clusters.push_back(clusterInfo);
|
|
noiseCount -= clusterInfo._size;
|
|
}
|
|
_clusters[0]._size = noiseCount;
|
|
|
|
if (REPORT_TO_CONSOLE) {
|
|
if (REPORT_LINES_PER_CLUSTER > 0) {
|
|
// report noise points
|
|
size_t outCount = 0;
|
|
auto iter = getIterator();
|
|
int i;
|
|
while ((i = iter.get()->next()) > 0) {
|
|
DbScanPoint<dim>& point = _points.get()->at(i);
|
|
if (point._clusterId != SCAN_NOISE)
|
|
continue;
|
|
cout << point.toString() << endl;
|
|
++outCount;
|
|
if (outCount >= REPORT_LINES_PER_CLUSTER)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// report summary
|
|
std::cout << "=> Noise: " << formatInt(getNoiseCount()) << " points"
|
|
<< endl;
|
|
if (REPORT_LINES_PER_CLUSTER > 0)
|
|
std::cout << endl;
|
|
std::cout << " " << formatInt(getClusterCount()) << " clusters "
|
|
"found in " << formatInt(_expandCount) <<
|
|
" expand cluster calls" << endl;
|
|
std::cout << " Distance calculations: " << formatInt(_distTestCount)
|
|
<< " = ";
|
|
double n = getUsedPointCount();
|
|
std::cout << _distTestCount / (n * std::log(n)) << " * (n log n) = ";
|
|
std::cout << _distTestCount / (n * n) << " * (n ^ 2)" << endl << endl;
|
|
}
|
|
}
|
|
|
|
/*
|
|
1.3.2 runRecursive
|
|
|
|
Recursively runs DBSCAN with shrinking epsilon values until the extent of
|
|
the clusters is below (maxBboxSize) in any dimension. Returns a vector
|
|
if the weighted centers of each cluster.
|
|
|
|
*/
|
|
template<unsigned dim>
|
|
std::vector<PointBase<dim>> DbScan<dim>::runRecursive(
|
|
const size_t minClusterSize, const double maxBboxSize) {
|
|
|
|
assert (_filter.get() == 0);
|
|
|
|
vector<PointBase<dim>> resultingShapes;
|
|
|
|
// Information on clusters that are still to be refined is stored in
|
|
// "refineClusters". If no part of such a cluster is at some point added
|
|
// to the resultingShapes (because at stricter parameters, the cluster
|
|
// completely dissolves), the center of the original cluster will be
|
|
// used (despite the fact that this cluster is too big)
|
|
std::vector<RefineClusterInfo<dim>> refineClusters;
|
|
|
|
size_t iteration = 0;
|
|
do {
|
|
if (REPORT_TO_CONSOLE) {
|
|
cout << endl << "Running DbScan (iteration " << ++iteration << ") "
|
|
"with " << formatInt(_pointCount) << " points: "
|
|
"eps = " << _eps << ", minPts = " << formatInt(_minPts)
|
|
<< endl;
|
|
}
|
|
|
|
// start DbScan
|
|
run();
|
|
|
|
// evaluate the run, adding entries to "centers" and reducing
|
|
// _points and _pointCount to the points that require another run:
|
|
evaluateRun(resultingShapes, refineClusters,
|
|
minClusterSize, maxBboxSize);
|
|
|
|
// now raise the requirements for clusters in order to get smaller
|
|
// clusters for the remaining points
|
|
_eps *= 0.5;
|
|
_epsSquare = _eps * _eps;
|
|
// _minPts should not be changed
|
|
|
|
// exit loop if no more runs are required
|
|
if (_pointCount <= _minPts)
|
|
break;
|
|
|
|
// clear MMRTree, cluster information and statistics for the next run
|
|
_mmRTree.reset(new RtreeT2<dim, PointIndex, MMRTREE_NODE_SIZE_MAX>());
|
|
_clusters.clear();
|
|
_expandCount = 0;
|
|
_distTestCount = 0;
|
|
} while (_pointCount > _minPts);
|
|
|
|
|
|
// for clusters that dissolved in refinement, the center of the original,
|
|
// unrefined cluster will be used. If a cluster did not dissolve (but
|
|
// rather a refined cluster was added to the resultingShapes), the
|
|
// respective entry in refineClusters was already removed
|
|
for (RefineClusterInfo<dim>& cluster : refineClusters)
|
|
addToShapes(resultingShapes, cluster.center);
|
|
|
|
if (REPORT_TO_CONSOLE) {
|
|
cout << endl << formatInt(resultingShapes.size()) << " shapes found "
|
|
"in " << formatInt(iteration) << " iterations."
|
|
<< endl << endl;
|
|
}
|
|
|
|
return resultingShapes;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
1.5 Private Methods
|
|
|
|
1.5.1 expandCluster
|
|
|
|
Expands a cluster from the given point (if this point is a core point)
|
|
and sets clusterId for all points in the cluster.
|
|
|
|
Returns the ClusterInfo, i.e. the number of points, the bbox, and the cohesion in
|
|
the cluster (i.e. in the points which are density-reachable from the given
|
|
point). If the given point is no core point, the returned ClusterInfo has
|
|
size 0.
|
|
|
|
*/
|
|
template <unsigned dim>
|
|
ClusterInfo<dim> DbScan<dim>::expandCluster(size_t index, int clusterID) {
|
|
++_expandCount;
|
|
|
|
ClusterInfo<dim> clusterInfo { };
|
|
std::vector<size_t> seedIndices { index };
|
|
size_t i = 0;
|
|
do {
|
|
// get the current seed point and its neighbors
|
|
DbScanPoint<dim>& seed = _points.get()->at(seedIndices[i]);
|
|
std::vector<size_t> neighbors = getNeighborIndices(seed);
|
|
|
|
// if the current seed is not a core point, ignore it
|
|
// (if it is the first point, no cluster was found, i.e. count == 0)
|
|
if ( neighbors.size() < _minPts )
|
|
continue;
|
|
|
|
// if seed is the first point, a cluster was found
|
|
if (clusterInfo._size == 0) {
|
|
seed._clusterId = clusterID;
|
|
clusterInfo._bbox = seed.getBoundingBox2(0.0);
|
|
++clusterInfo._size;
|
|
|
|
// DEBUG: report seed point
|
|
if (REPORT_TO_CONSOLE
|
|
&& clusterInfo._size <= REPORT_LINES_PER_CLUSTER
|
|
&& clusterID <= REPORT_CLUSTER_DETAILS_MAX)
|
|
cout << seed.toString() << endl;
|
|
}
|
|
|
|
// seed is a core point; check whether its neighbor points
|
|
// are yet unclassified or were previously classified as noise
|
|
for (auto j = neighbors.begin(); j != neighbors.end(); ++j) {
|
|
DbScanPoint<dim>& neighbor = _points.get()->at(*j);
|
|
|
|
if ( neighbor._clusterId == SCAN_UNCLASSIFIED
|
|
|| neighbor._clusterId == SCAN_NOISE) {
|
|
|
|
if (neighbor._clusterId == SCAN_UNCLASSIFIED)
|
|
seedIndices.push_back(*j);
|
|
neighbor._clusterId = clusterID;
|
|
++clusterInfo._size;
|
|
clusterInfo._bbox.Extend(neighbor.getBoundingBox2(0.0));
|
|
|
|
// DEBUG: report neighbor point
|
|
if (REPORT_TO_CONSOLE
|
|
&& clusterInfo._size <= REPORT_LINES_PER_CLUSTER
|
|
&& clusterID <= REPORT_CLUSTER_DETAILS_MAX)
|
|
cout << neighbor.toString() << endl;
|
|
}
|
|
}
|
|
clusterInfo._cohesion += neighbors.size();
|
|
} while (++i < seedIndices.size());
|
|
|
|
return clusterInfo;
|
|
// also returns count which is 0, if the given point is no seed (but
|
|
// noise); otherwise, (count) points are density-connected with the given
|
|
// core. bbox is the bounding box of all points in the cluster
|
|
}
|
|
|
|
/*
|
|
1.5.2 getNeighborIndices
|
|
|
|
Finds the neighbors of the given point (i.e. the points within the \_eps range
|
|
from the given point) and returns a vector of their indices in \_allPoints.
|
|
The given point itself is not included.
|
|
|
|
*/
|
|
template <unsigned dim>
|
|
std::vector<size_t> DbScan<dim>::getNeighborIndices(
|
|
const DbScanPoint<dim>& point) {
|
|
std::vector<size_t> neighborIndices;
|
|
|
|
// iterate over all intersecting entries in the MMRTree
|
|
Rectangle2<dim> searchBox = point.getBoundingBox2(_eps);
|
|
auto it = _mmRTree->find(searchBox);
|
|
const PointIndex *startIndex;
|
|
while( (startIndex = it->next()) != 0) {
|
|
PointIndex index = *startIndex;
|
|
// test points in the sequence starting from startIndex; the last
|
|
// point in the sequence is marked with _isLastInSeq.
|
|
bool sequenceContinues;
|
|
do {
|
|
assert (index <= _pointCount); // _points is 1-based
|
|
DbScanPoint<dim>& target = _points.get()->at(index);
|
|
if (&point != &target) { // do not enter the point itself
|
|
++_distTestCount; // statistical
|
|
if (point.calculateDistanceSquare(target) <= _epsSquare)
|
|
neighborIndices.push_back(index);
|
|
}
|
|
sequenceContinues = _useSequences && !target._isLastInSeq;
|
|
++index;
|
|
} while (sequenceContinues);
|
|
}
|
|
delete it;
|
|
|
|
/* previously, the simple O(n^2) method tested all points:
|
|
for(size_t i = 1; i <= _pointCount; ++i) { // _allPoints is 1-based
|
|
DbScanPoint<dim>* targetPtr = _allPoints.get() + i;
|
|
if (&point == targetPtr) // do not enter the point itself
|
|
continue;
|
|
if (point.calculateDistanceSquare(targetPtr) <= _epsSquare)
|
|
neighborIndices.push_back(i);
|
|
}
|
|
*/
|
|
|
|
return neighborIndices;
|
|
}
|
|
|
|
/*
|
|
1.5.3 evaluateRun
|
|
|
|
Evaluates a run performed by the runRecursive() method by deciding,
|
|
|
|
* which clusters to refuse (for being too small),
|
|
|
|
* which clusters to use, by calculating their weighted centers and adding
|
|
them to the given shapes vector (i.e. the result vector of runRecursive())
|
|
|
|
* and which clusters to refine (for being larger than maxBboxSize).
|
|
The caller can refine those clusters using another DBSCAN with a smaller
|
|
epsilon. To prepare for that, \_points is reduced to the points
|
|
in clusters that need refinement and information on those clusters is
|
|
added to the given refineClusters vector.
|
|
|
|
*/
|
|
template<unsigned dim>
|
|
void DbScan<dim>::evaluateRun(std::vector<PointBase<dim>>& shapes,
|
|
std::vector<RefineClusterInfo<dim>>& refineClusters,
|
|
const size_t minClusterSize, const double maxBboxSize) {
|
|
|
|
const int REFUSE_CLUSTER = -1; // must be a negative value
|
|
|
|
size_t clusterCount = getClusterCount();
|
|
|
|
BitArray refine = BitArray(clusterCount, true); // 1-based
|
|
refine.initializeAll(false);
|
|
|
|
// First we have to decide,
|
|
// - which clusters to refuse (for being too small),
|
|
// - which to refine (for being too big),
|
|
// - and which to use by adding an entry to "centers"
|
|
// for the clusters that are refined or used, the weighted center is
|
|
// calculated.
|
|
|
|
// map clusters to a 0-based index position in "centers"
|
|
size_t centerCount = 0;
|
|
vector<int> centerIndex = vector<int>(1 + clusterCount, REFUSE_CLUSTER);
|
|
|
|
// iterate over clusters
|
|
for (size_t i = 1; i <= clusterCount; ++i) { // 1-based
|
|
ClusterInfo<dim>& clusterInfo = _clusters[i];
|
|
// is this cluster too small?
|
|
if (clusterInfo._size < minClusterSize) {
|
|
centerIndex[i] = REFUSE_CLUSTER;
|
|
continue;
|
|
}
|
|
|
|
// get the maximum extent of this cluster's bbox
|
|
double maxExtent = 0.0;
|
|
Rectangle2<dim>& bbox = clusterInfo._bbox;
|
|
for (size_t d = 0; d < dim; ++d)
|
|
maxExtent = MAX(maxExtent, bbox.MaxD(d) - bbox.MinD(d));
|
|
|
|
// does this cluster need to be refined?
|
|
if (maxExtent > maxBboxSize)
|
|
refine.set(i, true);
|
|
|
|
// use centerIndex[i] to refer to an entry in centers
|
|
// (which is yet to be created)
|
|
centerIndex[i] = centerCount++;
|
|
}
|
|
|
|
// create a vector for the clusters' centers (except for those clusters
|
|
// that will be refused
|
|
PointBase<dim> empty;
|
|
empty.clear();
|
|
vector<PointBase<dim>> centers = vector<PointBase<dim>>(
|
|
centerCount, empty);
|
|
|
|
// iterate over points and depending a point's cluster
|
|
// - ignore point if it is noise or its cluster is refused
|
|
// - copy it to beginning of _points and calculate its weighted center
|
|
// if its cluster needs refinement
|
|
// - also calculate the weighted center if cluster can be used
|
|
size_t writeIndex = 0;
|
|
// no getIterator() needed here since runRecursive() allows no filter
|
|
for (size_t readIndex = 1; readIndex <= _pointCount; ++readIndex) {
|
|
DbScanPoint<dim>& point = _points.get()->at(readIndex);
|
|
// if the point belongs to noise, ignore it
|
|
int clusterId = point._clusterId;
|
|
assert ((size_t)clusterId <= clusterCount);
|
|
if (clusterId == SCAN_NOISE)
|
|
continue;
|
|
|
|
// if the point's cluster is too small, ignore it
|
|
int centerInd = centerIndex[clusterId];
|
|
if (centerInd == REFUSE_CLUSTER)
|
|
continue;
|
|
|
|
// calculate the center of the clusters that will be used or refined
|
|
PointBase<dim>& center = centers[centerInd];
|
|
for (unsigned i = 0; i < dim; ++i)
|
|
center._coords[i] += point._coords[i];
|
|
|
|
// if the point's cluster needs refinement, copy it and reset the
|
|
// destination point's _clusterId for the next run
|
|
if (refine.get(clusterId)) {
|
|
++writeIndex; // _points is 1-based, so start writing at entry 1
|
|
DbScanPoint<dim>& dest = _points.get()->at(writeIndex);
|
|
if (writeIndex < readIndex) // otherwise, writeIndex == readIndex
|
|
dest.copyCoordsFrom(_points.get()->at(readIndex));
|
|
dest._clusterId = SCAN_UNCLASSIFIED;
|
|
dest._isLastInSeq = true;
|
|
}
|
|
}
|
|
|
|
// the newly added entries in "centers" now contain the sum of the
|
|
// coordinates of all points in the respective cluster. They are now
|
|
// divided by the number of points in the cluster to get the weighted
|
|
// center of the cluster
|
|
size_t refuseCount = 0;
|
|
size_t refineCount = 0;
|
|
size_t useCount = 0;
|
|
for (size_t clusterId = 1; clusterId <= clusterCount; ++clusterId) {
|
|
int centerInd = centerIndex[clusterId];
|
|
if (centerInd == REFUSE_CLUSTER) {
|
|
++refuseCount;
|
|
continue;
|
|
}
|
|
PointBase<dim>& center = centers[centerInd];
|
|
size_t clusterSize = _clusters[clusterId]._size;
|
|
for (unsigned i = 0; i < dim; ++i)
|
|
center._coords[i] /= static_cast<double>(clusterSize);
|
|
|
|
// since this center will either be added to shapes or is guaranteed
|
|
// to be considered in later iterations, remove possible entries
|
|
// from the refineClusters vector
|
|
for (size_t i = 0; i < refineClusters.size(); ++i) {
|
|
RefineClusterInfo<dim>& cluster = refineClusters[i];
|
|
if (center.isInsideBbox(cluster.bbox)) {
|
|
// remove this entry by replacing it with the last entry
|
|
if (i < refineClusters.size() - 1)
|
|
refineClusters[i] = refineClusters.back();
|
|
refineClusters.pop_back();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (refine.get(clusterId)) {
|
|
// if the cluster needs to be refined, remember its bbox and
|
|
// center to ensure that some part of it will be added to
|
|
// the shapes later: if the cluster dissolves in refinement,
|
|
// the center of this ClusterInfo will be used as a shape
|
|
RefineClusterInfo<dim> refineCluster;
|
|
refineCluster.bbox = _clusters[clusterId]._bbox;
|
|
refineCluster.center = center;
|
|
refineClusters.push_back(refineCluster);
|
|
++refineCount;
|
|
} else {
|
|
// if the cluster can be used, add it to the resulting shapes
|
|
addToShapes(shapes, center);
|
|
++useCount;
|
|
}
|
|
}
|
|
|
|
if (REPORT_TO_CONSOLE) {
|
|
cout << "refusing " << refuseCount << " clusters (too few points), "
|
|
"using " << useCount << " (dense enough), "
|
|
"refining " << refineCount << " (too extensive)." << endl;
|
|
}
|
|
// the first (writeIndex) entries of _points now hold the points
|
|
// whose clusters need refinement. We now reduce _points to these points
|
|
// (all others are points obsolete since either they were refused or
|
|
// their center has been calculated above and stored in "shapes")
|
|
_pointCount = writeIndex; // writeIndex is 1-based
|
|
_points.get()->resize(1 + _pointCount); // _points is 1-based
|
|
}
|
|
|
|
/*
|
|
1.5.4 addToShapes
|
|
|
|
Adds the given center of a cluster to the given vector of shapes, and
|
|
optionally reports the shape to the console.
|
|
|
|
*/
|
|
template<unsigned dim>
|
|
void DbScan<dim>::addToShapes(std::vector<PointBase<dim>>& shapes,
|
|
const PointBase<dim>& center) const {
|
|
shapes.push_back(center);
|
|
if (REPORT_TO_CONSOLE && shapes.size() <= REPORT_SHAPE_CENTERS_MAX) {
|
|
cout << "Shape found at dual point " << center.toString() << endl;
|
|
}
|
|
}
|
|
|
|
/*
|
|
3 Static functions
|
|
|
|
3.1 constructMMRTree
|
|
|
|
Constructs the given MMRTree. If useSequences = true, the entries of the
|
|
MMRTree will contain the start index of each point sequence in the given
|
|
1-based points vector; for useSequences = false, there will be an MMRTree
|
|
entry for every point index returned by the given iterator.
|
|
|
|
*/
|
|
template<unsigned dim>
|
|
void DbScan<dim>::constructMMRTree(
|
|
std::unique_ptr<RtreeT2<dim, PointIndex, MMRTREE_NODE_SIZE_MAX>>&
|
|
mmRTree,
|
|
const std::vector<DbScanPoint<dim>>& points,
|
|
const bool useSequences, std::shared_ptr<DbScan::iterator> iter) {
|
|
// construct an MMRTree to point to the start index and point count
|
|
// of each point sequence in "points"
|
|
bool startSequence = true;
|
|
size_t nodeStartIndex = 0; // the node's start index in points
|
|
int i;
|
|
double min[dim];
|
|
double max[dim];
|
|
for( unsigned int i=0;i<dim;i++){
|
|
min[i] = max[i] = 0;
|
|
}
|
|
|
|
while ((i = iter.get()->next()) > 0) {
|
|
if (startSequence) {
|
|
nodeStartIndex = i;
|
|
}
|
|
const DbScanPoint<dim>& point = points[i];
|
|
for (unsigned d = 0; d < dim; ++d) {
|
|
double coord = point._coords[d];
|
|
min[d] = startSequence ? coord : MIN(min[d], coord);
|
|
max[d] = startSequence ? coord : MAX(max[d], coord);
|
|
}
|
|
// is this the last point of the current node?
|
|
bool endOfSequence = !useSequences || point._isLastInSeq;
|
|
if (endOfSequence) {
|
|
// add the bounding box of the points in the current node
|
|
// as a new entry to the mmRTree
|
|
const Rectangle2<dim> nodeBox = Rectangle2<dim>( min, max );
|
|
mmRTree->insert(nodeBox, nodeStartIndex);
|
|
}
|
|
startSequence = endOfSequence;
|
|
}
|
|
}
|
|
|
|
|
|
// ensure defined references for the dimensions needed
|
|
// see stackoverflow.com/questions/8752837/
|
|
// undefined-reference-to-template-class-constructor
|
|
namespace pointcloud2 {
|
|
template class DbScan<3u>;
|
|
template class DbScan<4u>;
|
|
template class DbScan<5u>;
|
|
template class DbScan<6u>;
|
|
}
|
|
|