Commit 29ab50ed authored by Vladyslav Zakharchenko's avatar Vladyslav Zakharchenko Committed by David Flynn
Browse files

attr/m44940: add binary tree based level-of-detail generator

This uses a binary-tree to generate the levels-of-details for the
lifting/predicting transforms as an alternative to Euclidean distance
thresholding methods.
parent 81b3c8b8
......@@ -32,6 +32,10 @@ categories:
- levelOfDetailCount: ${seq_lod}
- positionQuantizationScaleAdjustsDist2: 1
- dist2: ${seq_dist2}
- lodBinaryTree: 0
-
- !conditional '"${group}" =~ m{^cat3}'
- lodBinaryTree: 1
##
# attribute coding -- reflectance
......
......@@ -41,6 +41,10 @@ categories:
- levelOfDetailCount: ${seq_lod}
- positionQuantizationScaleAdjustsDist2: 1
- dist2: ${seq_dist2}
- lodBinaryTree: 0
-
- !conditional '"${group}" =~ m{^cat3}'
- lodBinaryTree: 1
##
# attribute coding -- reflectance
......
......@@ -32,6 +32,10 @@ categories:
- transformType: 2
- numberOfNearestNeighborsInPrediction: 3
- levelOfDetailCount: ${seq_lod}
- lodBinaryTree: 0
-
- !conditional '"${group}" =~ m{^cat3}'
- lodBinaryTree: 1
- positionQuantizationScaleAdjustsDist2: 1
- dist2: ${seq_dist2}
......
......@@ -236,6 +236,14 @@ attribute prediction.
### `--levelOfDetailCount=INT-VALUE`
Attribute's number of levels of detail.
### `--lodBinaryTree=0|1`
Controls the level-of-detail generation method:
| Value | Description |
|:-----:| ------------------------------- |
| 0 | Binary-tree based |
| 1 | Euclidean distance thresholding |
### `--quantizationStepLuma=INT-VALUE`
Attribute's luma quantization step size.
......
......@@ -238,6 +238,8 @@ AttributeDecoder::decodeReflectancesPred(
std::vector<PCCPredictor> predictors;
std::vector<uint32_t> numberOfPointsPerLOD;
std::vector<uint32_t> indexesLOD;
if (!aps.lod_binary_tree_enabled_flag) {
if (aps.num_detail_levels <= 1) {
buildPredictorsFastNoLod(
pointCloud, aps.num_pred_nearest_neighbours, aps.search_range,
......@@ -248,6 +250,13 @@ AttributeDecoder::decodeReflectancesPred(
aps.num_pred_nearest_neighbours, aps.search_range, aps.search_range,
predictors, numberOfPointsPerLOD, indexesLOD);
}
} else {
buildLevelOfDetailBinaryTree(pointCloud, numberOfPointsPerLOD, indexesLOD);
computePredictors(
pointCloud, numberOfPointsPerLOD, indexesLOD,
aps.num_pred_nearest_neighbours, predictors);
}
const int64_t maxReflectance = (1ll << desc.attr_bitdepth) - 1;
for (size_t predictorIndex = 0; predictorIndex < pointCount;
++predictorIndex) {
......@@ -316,6 +325,8 @@ AttributeDecoder::decodeColorsPred(
std::vector<PCCPredictor> predictors;
std::vector<uint32_t> numberOfPointsPerLOD;
std::vector<uint32_t> indexesLOD;
if (!aps.lod_binary_tree_enabled_flag) {
if (aps.num_detail_levels <= 1) {
buildPredictorsFastNoLod(
pointCloud, aps.num_pred_nearest_neighbours, aps.search_range,
......@@ -326,6 +337,13 @@ AttributeDecoder::decodeColorsPred(
aps.num_pred_nearest_neighbours, aps.search_range, aps.search_range,
predictors, numberOfPointsPerLOD, indexesLOD);
}
} else {
buildLevelOfDetailBinaryTree(pointCloud, numberOfPointsPerLOD, indexesLOD);
computePredictors(
pointCloud, numberOfPointsPerLOD, indexesLOD,
aps.num_pred_nearest_neighbours, predictors);
}
uint32_t values[3];
for (size_t predictorIndex = 0; predictorIndex < pointCount;
++predictorIndex) {
......@@ -510,10 +528,19 @@ AttributeDecoder::decodeColorsLift(
std::vector<PCCPredictor> predictors;
std::vector<uint32_t> numberOfPointsPerLOD;
std::vector<uint32_t> indexesLOD;
if (!aps.lod_binary_tree_enabled_flag) {
buildPredictorsFast(
pointCloud, aps.dist2, aps.num_detail_levels,
aps.num_pred_nearest_neighbours, aps.search_range, aps.search_range,
predictors, numberOfPointsPerLOD, indexesLOD);
} else {
buildLevelOfDetailBinaryTree(pointCloud, numberOfPointsPerLOD, indexesLOD);
computePredictors(
pointCloud, numberOfPointsPerLOD, indexesLOD,
aps.num_pred_nearest_neighbours, predictors);
}
for (size_t predictorIndex = 0; predictorIndex < pointCount;
++predictorIndex) {
predictors[predictorIndex].computeWeights();
......@@ -573,10 +600,19 @@ AttributeDecoder::decodeReflectancesLift(
std::vector<PCCPredictor> predictors;
std::vector<uint32_t> numberOfPointsPerLOD;
std::vector<uint32_t> indexesLOD;
if (!aps.lod_binary_tree_enabled_flag) {
buildPredictorsFast(
pointCloud, aps.dist2, aps.num_detail_levels,
aps.num_pred_nearest_neighbours, aps.search_range, aps.search_range,
predictors, numberOfPointsPerLOD, indexesLOD);
} else {
buildLevelOfDetailBinaryTree(pointCloud, numberOfPointsPerLOD, indexesLOD);
computePredictors(
pointCloud, numberOfPointsPerLOD, indexesLOD,
aps.num_pred_nearest_neighbours, predictors);
}
for (size_t predictorIndex = 0; predictorIndex < pointCount;
++predictorIndex) {
predictors[predictorIndex].computeWeights();
......
......@@ -429,6 +429,8 @@ AttributeEncoder::encodeReflectancesPred(
std::vector<PCCPredictor> predictors;
std::vector<uint32_t> numberOfPointsPerLOD;
std::vector<uint32_t> indexesLOD;
if (!aps.lod_binary_tree_enabled_flag) {
if (aps.num_detail_levels <= 1) {
buildPredictorsFastNoLod(
pointCloud, aps.num_pred_nearest_neighbours, aps.search_range,
......@@ -439,6 +441,13 @@ AttributeEncoder::encodeReflectancesPred(
aps.num_pred_nearest_neighbours, aps.search_range, aps.search_range,
predictors, numberOfPointsPerLOD, indexesLOD);
}
} else {
buildLevelOfDetailBinaryTree(pointCloud, numberOfPointsPerLOD, indexesLOD);
computePredictors(
pointCloud, numberOfPointsPerLOD, indexesLOD,
aps.num_pred_nearest_neighbours, predictors);
}
const int64_t clipMax = (1ll << desc.attr_bitdepth) - 1;
PCCResidualsEntropyEstimator context;
for (size_t predictorIndex = 0; predictorIndex < pointCount;
......@@ -577,6 +586,8 @@ AttributeEncoder::encodeColorsPred(
std::vector<PCCPredictor> predictors;
std::vector<uint32_t> numberOfPointsPerLOD;
std::vector<uint32_t> indexesLOD;
if (!aps.lod_binary_tree_enabled_flag) {
if (aps.num_detail_levels <= 1) {
buildPredictorsFastNoLod(
pointCloud, aps.num_pred_nearest_neighbours, aps.search_range,
......@@ -587,6 +598,13 @@ AttributeEncoder::encodeColorsPred(
aps.num_pred_nearest_neighbours, aps.search_range, aps.search_range,
predictors, numberOfPointsPerLOD, indexesLOD);
}
} else {
buildLevelOfDetailBinaryTree(pointCloud, numberOfPointsPerLOD, indexesLOD);
computePredictors(
pointCloud, numberOfPointsPerLOD, indexesLOD,
aps.num_pred_nearest_neighbours, predictors);
}
const int64_t clipMax = (1ll << desc.attr_bitdepth) - 1;
uint32_t values[3];
PCCResidualsEntropyEstimator context;
......@@ -821,10 +839,19 @@ AttributeEncoder::encodeColorsLift(
std::vector<PCCPredictor> predictors;
std::vector<uint32_t> numberOfPointsPerLOD;
std::vector<uint32_t> indexesLOD;
if (!aps.lod_binary_tree_enabled_flag) {
buildPredictorsFast(
pointCloud, aps.dist2, aps.num_detail_levels,
aps.num_pred_nearest_neighbours, aps.search_range, aps.search_range,
predictors, numberOfPointsPerLOD, indexesLOD);
} else {
buildLevelOfDetailBinaryTree(pointCloud, numberOfPointsPerLOD, indexesLOD);
computePredictors(
pointCloud, numberOfPointsPerLOD, indexesLOD,
aps.num_pred_nearest_neighbours, predictors);
}
for (size_t predictorIndex = 0; predictorIndex < pointCount;
++predictorIndex) {
predictors[predictorIndex].computeWeights();
......@@ -906,10 +933,19 @@ AttributeEncoder::encodeReflectancesLift(
std::vector<PCCPredictor> predictors;
std::vector<uint32_t> numberOfPointsPerLOD;
std::vector<uint32_t> indexesLOD;
if (!aps.lod_binary_tree_enabled_flag) {
buildPredictorsFast(
pointCloud, aps.dist2, aps.num_detail_levels,
aps.num_pred_nearest_neighbours, aps.search_range, aps.search_range,
predictors, numberOfPointsPerLOD, indexesLOD);
} else {
buildLevelOfDetailBinaryTree(pointCloud, numberOfPointsPerLOD, indexesLOD);
computePredictors(
pointCloud, numberOfPointsPerLOD, indexesLOD,
aps.num_pred_nearest_neighbours, predictors);
}
for (size_t predictorIndex = 0; predictorIndex < pointCount;
++predictorIndex) {
predictors[predictorIndex].computeWeights();
......
......@@ -470,5 +470,244 @@ private:
std::vector<PCCIncrementalKdTree3Node> nodes;
uint32_t root;
};
//---------------------------------------
class PCCKdTree3 {
struct PCCKdTree3Node {
PCCBox3D BB;
PCCPoint3D centd;
uint32_t id;
uint32_t start;
uint32_t end;
PCCAxis3 axis;
uint32_t median;
uint32_t medianIdx;
};
struct PointIDNode {
PCCPoint3D pos;
uint32_t id;
bool isVisisted;
};
public:
PCCKdTree3(const PCCPointSet3& pointCloud, uint8_t depth)
{
init(pointCloud, depth);
}
PCCKdTree3(const PCCKdTree3&) = default;
PCCKdTree3& operator=(const PCCKdTree3&) = default;
~PCCKdTree3(void) = default;
std::vector<PCCKdTree3Node> nodes;
void build()
{
uint32_t nodeCount =
(1 << (kdDepth + 1)) - 1; // std::pow(2, kdDepth + 1) - 1
for (size_t nodeIt = 1; nodeIt < nodeCount; nodeIt++) {
bool isLeftNode = (nodeIt & 1) != 0;
uint32_t parentNodeIdx =
isLeftNode ? (nodeIt - 1) >> 1 : (nodeIt - 2) >> 1;
const uint32_t start = isLeftNode
? static_cast<uint32_t>(nodes[parentNodeIdx].start)
: static_cast<uint32_t>(nodes[parentNodeIdx].medianIdx + 1);
const uint32_t end = isLeftNode
? static_cast<uint32_t>(nodes[parentNodeIdx].medianIdx)
: static_cast<uint32_t>(nodes[parentNodeIdx].end);
PCCBox3D BB = nodes[parentNodeIdx].BB;
isLeftNode
? BB.max[nodes[parentNodeIdx].axis] = nodes[parentNodeIdx].median
: BB.min[nodes[parentNodeIdx].axis] = nodes[parentNodeIdx].median;
PCCPoint3D nodeMean = computePCCMean(start, end);
PCCAxis3 axis = computeSplitAxisVar(start, end, nodeMean);
uint32_t medianIdx = findMedian(start, end, axis);
PCCKdTree3Node& node = nodes[nodeIt];
node.BB = BB;
node.centd = nodeMean;
node.id = nodeIt;
node.start = start;
node.end = end;
node.axis = axis;
node.median = pointCloudTemp[medianIdx].pos[axis];
node.medianIdx = medianIdx;
}
}
void init(const PCCPointSet3& pointCloud, uint8_t depth)
{
kdDepth = depth;
nodes.resize(0);
nodes.resize(std::pow(2, kdDepth + 1) - 1);
uint32_t pointCount = pointCloud.getPointCount();
pointCloudTemp.resize(pointCount);
for (size_t i = 0; i < pointCount; i++) {
pointCloudTemp[i].pos = pointCloud[i];
pointCloudTemp[i].id = i;
pointCloudTemp[i].isVisisted = false;
}
PCCBox3D BB = computeBoundingBox(0, pointCount);
PCCPoint3D nodeMean = computePCCMean(0, pointCount);
PCCAxis3 axis = computeSplitAxisVar(0, pointCount, nodeMean);
uint32_t medianIdx = findMedian(0, pointCount, axis);
PCCKdTree3Node& rootNode = nodes[0];
rootNode.BB = BB;
rootNode.centd = nodeMean;
rootNode.id = 0;
rootNode.start = static_cast<uint32_t>(0);
rootNode.end = static_cast<uint32_t>(pointCount - 1);
;
rootNode.axis = axis;
rootNode.median = pointCloudTemp[medianIdx].pos[axis];
rootNode.medianIdx = medianIdx;
}
uint32_t searchClosestAvailablePoint(PCCPoint3D queryPoint)
{
uint32_t idToClosestPoint = -1;
uint32_t id = 0;
for (int8_t d = 0; d < kdDepth; d++) {
id = (queryPoint[nodes[id].axis] <= nodes[id].median) ? 2 * id + 1
: 2 * id + 2;
}
const uint32_t start = nodes[id].start;
const uint32_t end = nodes[id].end;
const uint32_t closestDistThr = 1;
uint32_t smallestDist = -1;
uint32_t closestID = 0;
for (size_t i = start; i <= end; ++i) {
if (!pointCloudTemp[i].isVisisted) {
PCCPoint3D diff = pointCloudTemp[i].pos - queryPoint;
uint32_t dist =
std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]);
if (dist <= closestDistThr) {
return pointCloudTemp[i].id;
}
if (dist < smallestDist) {
smallestDist = dist;
idToClosestPoint = pointCloudTemp[i].id;
closestID = i;
}
}
}
if (smallestDist != -1) {
pointCloudTemp[closestID].isVisisted = true;
}
return idToClosestPoint;
}
private:
PCCBox3D computeBoundingBox(const uint32_t start, const uint32_t end) const
{
PCCPoint3D minBB = pointCloudTemp[start].pos;
PCCPoint3D maxBB = pointCloudTemp[start].pos;
for (size_t i = start + 1; i < end; ++i) {
const PCCPoint3D& pt = pointCloudTemp[i].pos;
for (int32_t k = 0; k < 3; ++k) {
if (minBB[k] > pt[k]) {
minBB[k] = pt[k];
} else if (maxBB[k] < pt[k]) {
maxBB[k] = pt[k];
}
}
}
PCCBox3D BB;
{
BB.min = minBB;
BB.max = maxBB;
}
return BB;
}
PCCAxis3 computeSplitAxis(const uint32_t start, const uint32_t end) const
{
PCCBox3D BB = computeBoundingBox(start, end);
PCCPoint3D d = BB.max - BB.min;
if (d.x() > d.y() && d.x() > d.z()) {
return PCC_AXIS3_X;
} else if (d.y() > d.z()) {
return PCC_AXIS3_Y;
} else {
return PCC_AXIS3_Z;
}
}
PCCAxis3 computeSplitAxisVar(
const uint32_t start, const uint32_t end, PCCPoint3D nodeMean) const
{
double nodeVar[3] = {0, 0, 0};
for (size_t axis = 0; axis < 3; ++axis) {
double acc = 0, diff = 0;
for (size_t i = start; i < end; i++) {
diff = pointCloudTemp[i].pos[axis] - nodeMean[axis];
acc += diff * diff;
}
nodeVar[axis] = acc / (end - start);
}
if (nodeVar[0] > nodeVar[1] && nodeVar[0] > nodeVar[2]) {
return PCC_AXIS3_X;
} else if (nodeVar[1] > nodeVar[2]) {
return PCC_AXIS3_Y;
} else {
return PCC_AXIS3_Z;
}
}
PCCPoint3D computePCCMean(const uint32_t start, const uint32_t end)
{
assert(end >= start);
PCCPoint3D nodeMean;
for (size_t axis = 0; axis < 3; ++axis) {
uint32_t acc = 0;
for (size_t i = start; i < end; i++) {
acc += pointCloudTemp[i].pos[axis];
}
nodeMean[axis] = round(acc / (end - start));
}
return nodeMean;
}
uint32_t findMedian(uint32_t start, uint32_t end, const PCCAxis3 splitAxis)
{
assert(start < end);
if (end == start + 1) {
return start;
}
const uint32_t medianIndex = start + (end - start) / 2;
while (1) {
double pivot = pointCloudTemp[medianIndex].pos[splitAxis];
std::swap(pointCloudTemp[medianIndex], pointCloudTemp[end - 1]);
uint32_t store, p;
for (store = p = start; p < end; p++) {
if (pointCloudTemp[p].pos[splitAxis] < pivot) {
if (p != store) {
std::swap(pointCloudTemp[p], pointCloudTemp[store]);
}
++store;
}
}
std::swap(pointCloudTemp[store], pointCloudTemp[end - 1]);
while (store < medianIndex
&& pointCloudTemp[store].pos[splitAxis]
== pointCloudTemp[store + 1].pos[splitAxis]) {
// optimization in case of duplicated values
++store;
}
if (store == medianIndex) {
return medianIndex;
} else if (store > medianIndex) {
end = store;
} else {
start = store + 1;
}
}
}
private:
std::vector<PointIDNode> pointCloudTemp;
uint8_t kdDepth;
};
//---------------------------------------
} // namespace pcc
#endif /* PCCKdTree_h */
......@@ -584,7 +584,150 @@ updatePredictors(
}
//---------------------------------------------------------------------------
// LoD generation using Binary-tree
inline void
buildLevelOfDetailBinaryTree(
const PCCPointSet3& pointCloud,
std::vector<uint32_t>& numberOfPointsPerLOD,
std::vector<uint32_t>& indexes)
{
const uint32_t pointCount = pointCloud.getPointCount();
uint8_t const btDepth = std::log2(round(pointCount / 2)) - 1;
PCCKdTree3 kdtree(pointCloud, btDepth);
kdtree.build();
bool skipLayer = true; //if true, skips alternate layer of BT
std::vector<bool> skipDepth(btDepth + 1, false);
if (skipLayer) {
for (int i = skipDepth.size() - 2; i >= 0; i--) {
skipDepth[i] = !skipDepth[i + 1]; //if true, that layer is skipped
}
}
indexes.resize(pointCount);
std::vector<bool> visited(pointCount, false);
uint32_t lod = 0;
uint32_t start = 0;
uint32_t end = 0;
for (size_t i = 0; i < btDepth + 1; i++) {
start = end;
end = start + (1 << i);
if (!skipDepth[i]) {
for (int j = start; j < end; j++) {
auto indx = kdtree.searchClosestAvailablePoint(kdtree.nodes[j].centd);
if (indx != PCC_UNDEFINED_INDEX && !visited[indx]) {
indexes[lod++] = indx;
visited[indx] = true;
}
}
numberOfPointsPerLOD.push_back(lod);
}
}
for (size_t i = 0; i < pointCount; i++) {
if (!visited[i]) {
indexes[lod++] = i;
}
}
numberOfPointsPerLOD.push_back(lod);
}
//---------------------------------------------------------------------------
struct PointCloudWrapper {
PointCloudWrapper(
const PCCPointSet3& pointCloud, const std::vector<uint32_t>& indexes)
: _pointCloud(pointCloud), _indexes(indexes)
{}
inline size_t kdtree_get_point_count() const { return _pointCount; }
inline void kdtree_set_point_count(const size_t pointCount)
{
assert(pointCount < _indexes.size());
assert(pointCount < _pointCloud.getPointCount());
_pointCount = pointCount;
}
inline double kdtree_get_pt(const size_t idx, int dim) const
{
assert(idx < _pointCount && dim < 3);
return _pointCloud[_indexes[idx]][dim];
}
template<class BBOX>
bool kdtree_get_bbox(BBOX& /* bb */) const
{
return false;
}
const PCCPointSet3& _pointCloud;
const std::vector<uint32_t>& _indexes;
size_t _pointCount = 0;
};
//---------------------------------------------------------------------------
inline void
computePredictors(
const PCCPointSet3& pointCloud,
const std::vector<uint32_t>& numberOfPointsPerLOD,
const std::vector<uint32_t>& indexes,
const size_t numberOfNearestNeighborsInPrediction,
std::vector<PCCPredictor>& predictors)
{
const uint32_t PCCTMC3MaxPredictionNearestNeighborCount = 3;
const size_t pointCount = pointCloud.getPointCount();
const size_t lodCount = numberOfPointsPerLOD.size();
assert(lodCount);
predictors.resize(pointCount);
// delta prediction for LOD0
uint32_t i0 = numberOfPointsPerLOD[0];
for (uint32_t i = 0; i < i0; ++i) {
auto& predictor = predictors[i];
if (i == 0) {
predictor.init(PCC_UNDEFINED_INDEX);
} else {
predictor.init(PCC_UNDEFINED_INDEX);
}
}
PointCloudWrapper pointCloudWrapper(pointCloud, indexes);
const nanoflann::SearchParams params(10, 0.0f, true);
size_t indices[PCCTMC3MaxPredictionNearestNeighborCount];
double sqrDist[PCCTMC3MaxPredictionNearestNeighborCount];
nanoflann::KNNResultSet<double> resultSet(
numberOfNearestNeighborsInPrediction);
for (uint32_t lodIndex = 1; lodIndex < lodCount; ++lodIndex) {
pointCloudWrapper.kdtree_set_point_count(i0);
nanoflann::KDTreeSingleIndexAdaptor<
nanoflann::L2_Simple_Adaptor<double, PointCloudWrapper>,
PointCloudWrapper, 3>
kdtree(
3, pointCloudWrapper, nanoflann::KDTreeSingleIndexAdaptorParams(10));
kdtree.buildIndex();
const uint32_t i1 = numberOfPointsPerLOD[lodIndex];
for (uint32_t i = i0; i < i1; ++i) {
const uint32_t pointIndex = indexes[i];
const auto& point = pointCloud[pointIndex];
auto& predictor = predictors[i];
resultSet.init(indices, sqrDist);
kdtree.findNeighbors(resultSet, &point[0], params);
const uint32_t resultCount = resultSet.size();
if (sqrDist[0] == 0.0 || resultCount == 1) {
const uint32_t predIndex = indices[0];
predictor.init(predIndex);
} else {
predictor.neighborCount = resultCount;
for (size_t n = 0; n < resultCount; ++n) {