Commit bcf5394c authored by Yiting Shao's avatar Yiting Shao Committed by David Flynn
Browse files

slices/m48892: perform geometry quantisation prior to slice partitioning

Adjust the operation sequence of slice partition and geometry quantization. We
do geometry quantization first and then do the slice partition, so that the
point number of each slice would be kept in the predefined range under
lossless/lossy geometry coding conditions.
parent e72b9af7
......@@ -428,6 +428,7 @@ public:
std::swap(getReflectance(index1), getReflectance(index2));
}
}
Box3<double> computeBoundingBox() const
{
Box3<double> bbox = {std::numeric_limits<double>::max(),
......@@ -446,6 +447,34 @@ public:
}
return bbox;
}
//--------------------------------------------------------------------------
// Determine the bounding box of the set of points given by the indicies
// given by iterating over [begin, end)
template<typename ForwardIt>
Box3<double> computeBoundingBox(ForwardIt begin, ForwardIt end) const
{
Box3<double> bbox = {std::numeric_limits<double>::max(),
std::numeric_limits<double>::lowest()};
for (auto it = begin; it != end; ++it) {
int i = *it;
const Vec3<double>& pt = (*this)[i];
for (int k = 0; k < 3; ++k) {
if (pt[k] > bbox.max[k]) {
bbox.max[k] = pt[k];
}
if (pt[k] < bbox.min[k]) {
bbox.min[k] = pt[k];
}
}
}
return bbox;
}
//--------------------------------------------------------------------------
static bool compareSeparators(char aChar, const char* const sep)
{
int i = 0;
......
......@@ -39,6 +39,8 @@
#include <cstddef>
#include <set>
#include <vector>
#include <utility>
#include <map>
#include "KDTreeVectorOfVectorsAdaptor.h"
#include "PCCPointSet.h"
......@@ -80,10 +82,12 @@ quantizePositionsUniq(
const Vec3<int> offset,
const Box3<int> clamp,
const PCCPointSet3& src,
PCCPointSet3* dst)
PCCPointSet3* dst,
std::multimap<Vec3<double>, int32_t>& doubleQuantizedToOrigin)
{
// Determine the set of unique quantised points
std::multimap<Vec3<int32_t>, int32_t> intQuantizedToOrigin;
std::set<Vec3<int32_t>> uniquePoints;
int numSrcPoints = src.getPointCount();
for (int i = 0; i < numSrcPoints; ++i) {
......@@ -96,6 +100,7 @@ quantizePositionsUniq(
}
uniquePoints.insert(quantizedPoint);
intQuantizedToOrigin.insert(std::make_pair(quantizedPoint, i));
}
// Populate output point cloud
......@@ -105,12 +110,18 @@ quantizePositionsUniq(
dst->addRemoveAttributes(src.hasColors(), src.hasReflectances());
}
dst->resize(uniquePoints.size());
doubleQuantizedToOrigin.clear();
int idx = 0;
for (const auto& point : uniquePoints) {
auto& dstPoint = (*dst)[idx++];
for (int k = 0; k < 3; ++k)
dstPoint[k] = double(point[k]);
std::multimap<Vec3<int32_t>, int32_t>::iterator pos;
for (pos = intQuantizedToOrigin.lower_bound(point);
pos != intQuantizedToOrigin.upper_bound(point); ++pos) {
doubleQuantizedToOrigin.insert(std::make_pair(dstPoint, pos->second));
}
}
}
......
......@@ -45,25 +45,12 @@
#include "PCCPointSet.h"
#include "PCCPointSetProcessing.h"
#include "hls.h"
#include "partitioning.h"
namespace pcc {
//============================================================================
enum class PartitionMethod
{
// Don't partition input
kNone = 0,
// Partition according to uniform geometry
kUniformGeom = 2,
// Partition according to the depth of octree
kOctreeUniform = 3,
};
//============================================================================
struct EncoderAttributeParams {
// NB: this only makes sense for setting configurable parameters
AttributeBrickHeader abh;
......@@ -88,14 +75,8 @@ struct EncoderParams {
// Filename for saving recoloured point cloud.
std::string postRecolorPath;
// Method for partitioning the input cloud
PartitionMethod partitionMethod;
// Number of slices used by PartitionMethod::kUniformGeom
int partitionNumUniformGeom;
// Depth of octree used by PartitionMethod::kOctreeUniform
int partitionOctreeDepth;
// Parameters that control partitioning
PartitionParams partition;
// attribute recolouring parameters
RecolourParams recolour;
......@@ -118,6 +99,7 @@ public:
void compressPartition(
const PCCPointSet3& inputPointCloud,
const PCCPointSet3& originPartCloud,
EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud = nullptr);
......@@ -129,7 +111,12 @@ private:
void encodeGeometryBrick(PayloadBuffer* buf);
void quantization(const PCCPointSet3& inputPointCloud);
PCCPointSet3 quantization(const PCCPointSet3& inputPointCloud);
void getSrcPartition(
const PCCPointSet3& inputPointCloud,
PCCPointSet3& srcPartition,
std::vector<int32_t> indexes);
private:
PCCPointSet3 pointCloud;
......@@ -150,6 +137,9 @@ private:
// Identifies the current tile
int _tileId;
// Map quantized points to the original input points
std::multimap<Vec3<double>, int32_t> quantizedToOrigin;
};
//============================================================================
......
......@@ -168,8 +168,8 @@ operator<<(std::ostream& out, const PartitionMethod& val)
{
switch (val) {
case PartitionMethod::kNone: out << "0 (None)"; break;
case PartitionMethod::kUniformGeom: out << "0 (UniformGeom)"; break;
case PartitionMethod::kOctreeUniform: out << "0 (UniformOctree)"; break;
case PartitionMethod::kUniformGeom: out << "2 (UniformGeom)"; break;
case PartitionMethod::kOctreeUniform: out << "3 (UniformOctree)"; break;
default: out << int(val) << " (Unknown)"; break;
}
return out;
......@@ -323,22 +323,28 @@ ParseParameters(int argc, char* argv[], Parameters& params)
"Enables removal of duplicated points")
("partitionMethod",
params.encoder.partitionMethod, PartitionMethod::kNone,
params.encoder.partition.method, PartitionMethod::kUniformGeom,
"Method used to partition input point cloud into slices/tiles:\n"
" 0: none\n"
" 1: none (deprecated)\n"
" 2: n Uniform-Geometry partition bins along the longest edge\n"
" 3: Uniform Geometry partition at n octree depth")
("partitionNumUniformGeom",
params.encoder.partitionNumUniformGeom, 0,
"Number of bins for partitionMethod=2:\n"
" 0: slice partition with adaptive-defined bins\n"
" >=1: slice partition with user-defined bins\n")
("partitionOctreeDepth",
params.encoder.partitionOctreeDepth, 2,
"Depth of octree partition for partitionMethod=3")
params.encoder.partition.octreeDepth, 1,
"Depth of octree partition for partitionMethod=4")
("sliceMaxPoints",
params.encoder.partition.sliceMaxPoints, 1100000,
"Maximum number of points per slice")
("sliceMinPoints",
params.encoder.partition.sliceMinPoints, 550000,
"Minimum number of points per slice (soft limit)")
("tileSize",
params.encoder.partition.tileSize, 0,
"Partition input into cubic tiles of given size")
("cabac_bypass_stream_enabled_flag",
params.encoder.sps.cabac_bypass_stream_enabled_flag, false,
......@@ -632,6 +638,12 @@ ParseParameters(int argc, char* argv[], Parameters& params)
// sanity checks
if (
params.encoder.partition.sliceMaxPoints
< params.encoder.partition.sliceMinPoints)
err.error()
<< "sliceMaxPoints must be greater than or equal to sliceMinPoints\n";
if (params.encoder.gps.intra_pred_max_node_size_log2)
if (!params.encoder.gps.neighbour_avail_boundary_log2)
err.error() << "Geometry intra prediction requires finite"
......
......@@ -94,48 +94,117 @@ PCCTMC3Encoder3::compress(
// initial geometry IDs
_tileId = 0;
_sliceId = 0;
_sliceOrigin = Vec3<int>{0};
// Partition the input point cloud into tiles
// - quantize the input point cloud (without duplicate point removal)
// - inverse quantize the cloud above to get the initial-sized cloud
// - if tile partitioning is enabled,partitioning function produces
// vectors tileMaps which map tileIDs to point indexes.
// Compute the tile metadata for each partition.
// - if not, regard the whole input cloud as a single tile to facilitate
// slice partitioning subsequent
// todo(df):
PartitionSet partitions;
PCCPointSet3 quantizedInputCloud;
quantizedInputCloud = quantization(inputPointCloud);
std::vector<std::vector<int32_t>> tileMaps;
if (params->partition.tileSize) {
PCCPointSet3 inverseQuantizedCloud;
Box3<int32_t> clampBox{{0, 0, 0}, {INT32_MAX, INT32_MAX, INT32_MAX}};
quantizePositions(
1.0 / _sps->seq_source_geom_scale_factor, 0, clampBox,
quantizedInputCloud, &inverseQuantizedCloud);
tileMaps = tilePartition(params->partition, inverseQuantizedCloud);
// Get the bounding box of current tile and write it into tileInventory
partitions.tileInventory.tiles.resize(tileMaps.size());
for (int t = 0; t < tileMaps.size(); t++) {
Box3<double> bbox = inverseQuantizedCloud.computeBoundingBox(
tileMaps[t].begin(), tileMaps[t].end());
auto& tileIvt = partitions.tileInventory.tiles[t];
for (int k = 0; k < 3; k++) {
tileIvt.tile_bounding_box_whd[k] = bbox.max[k] - bbox.min[k];
tileIvt.tile_bounding_box_xyz0[k] =
bbox.min[k] - _sps->seq_bounding_box_xyz0[k];
}
}
} else {
tileMaps.emplace_back();
auto& tile = tileMaps.back();
for (int i = 0; i < quantizedInputCloud.getPointCount(); i++)
tile.push_back(i);
}
// If partitioning is not enabled, encode input as a single "partition"
if (params->partitionMethod == PartitionMethod::kNone) {
if (params->partition.method == PartitionMethod::kNone) {
// todo(df): params->gps.geom_box_present_flag = false;
_sliceOrigin = Vec3<int>{0};
compressPartition(inputPointCloud, params, outputFn, reconstructedCloud);
compressPartition(
quantizedInputCloud, inputPointCloud, params, outputFn,
reconstructedCloud);
return 0;
}
// Partition the input point cloud
// - quantise the input point cloud (without duplicate point removal)
// - get the partitial cloud of each tile
// - partitioning function produces a list of point indexes, origin and
// optional tile metadata for each partition.
// - encode any tile metadata
// todo(df): consider requiring partitioning function to sort the input
// points and provide ranges rather than a set of indicies.
PartitionSet partitions;
do {
PCCPointSet3 quantizedInputCloud;
Box3<int32_t> clampBox{{0, 0, 0}, {INT32_MAX, INT32_MAX, INT32_MAX}};
quantizePositions(
_sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0,
clampBox, inputPointCloud, &quantizedInputCloud);
switch (params->partitionMethod) {
// NB: this method is handled earlier
case PartitionMethod::kNone: return 1;
case PartitionMethod::kUniformGeom:
partitions = partitionByUniformGeom(
quantizedInputCloud, params->partitionNumUniformGeom);
break;
case PartitionMethod::kOctreeUniform:
partitions = partitionByOctreeDepth(
quantizedInputCloud, params->partitionOctreeDepth);
break;
for (int t = 0; t < tileMaps.size(); t++) {
const auto& tile = tileMaps[t];
// Get the point cloud of current tile and compute their bounding boxes
PCCPointSet3 tileCloud;
getSrcPartition(quantizedInputCloud, tileCloud, tile);
Box3<double> bbox = tileCloud.computeBoundingBox();
Vec3<int> tile_quantized_box_xyz0;
for (int k = 0; k < 3; k++) {
tile_quantized_box_xyz0[k] = int(bbox.min[k]);
}
// Move the tile cloud to coodinate origin
// for the convenience of slice partitioning
quantizePositions(
1, tile_quantized_box_xyz0, clampBox, tileCloud, &tileCloud);
//Slice partition of current tile
std::vector<Partition> curSlices;
switch (params->partition.method) {
// NB: this method is handled earlier
case PartitionMethod::kNone: return 1;
case PartitionMethod::kUniformGeom:
curSlices = partitionByUniformGeom(params->partition, tileCloud, t);
break;
case PartitionMethod::kOctreeUniform:
curSlices = partitionByOctreeDepth(params->partition, tileCloud, t);
break;
}
// Map slice indexes to tile indexes(the original indexes)
for (int i = 0; i < curSlices.size(); i++) {
for (int p = 0; p < curSlices[i].pointIndexes.size(); p++) {
curSlices[i].pointIndexes[p] = tile[curSlices[i].pointIndexes[p]];
}
}
partitions.slices.insert(
partitions.slices.end(), curSlices.begin(), curSlices.end());
}
std::cout << "Slice number: " << partitions.slices.size() << std::endl;
} while (0);
if (!partitions.tileInventory.tiles.empty()) {
if (partitions.tileInventory.tiles.size() > 1) {
assert(partitions.tileInventory.tiles.size() == tileMaps.size());
std::cout << "Tile number: " << tileMaps.size() << std::endl;
outputFn(write(partitions.tileInventory));
}
......@@ -145,28 +214,28 @@ PCCTMC3Encoder3::compress(
for (const auto& partition : partitions.slices) {
// create partitioned point set
PCCPointSet3 srcPartition;
srcPartition.addRemoveAttributes(
inputPointCloud.hasColors(), inputPointCloud.hasReflectances());
int partitionSize = partition.pointIndexes.size();
srcPartition.resize(partitionSize);
for (int i = 0; i < partitionSize; i++) {
int inputIdx = partition.pointIndexes[i];
srcPartition[i] = inputPointCloud[inputIdx];
if (inputPointCloud.hasColors())
srcPartition.setColor(i, inputPointCloud.getColor(inputIdx));
if (inputPointCloud.hasReflectances())
srcPartition.setReflectance(
i, inputPointCloud.getReflectance(inputIdx));
getSrcPartition(quantizedInputCloud, srcPartition, partition.pointIndexes);
// Get the original partial cloud corresponding to each slice for recolor
std::vector<int32_t> partitionOriginIdxes;
for (int i = 0; i < partition.pointIndexes.size(); i++) {
const auto& point = srcPartition[i];
std::multimap<Vec3<double>, int32_t>::iterator pos;
for (pos = quantizedToOrigin.lower_bound(point);
pos != quantizedToOrigin.upper_bound(point); ++pos) {
partitionOriginIdxes.push_back(pos->second);
}
}
PCCPointSet3 partitionInOriginCloud;
getSrcPartition(
inputPointCloud, partitionInOriginCloud, partitionOriginIdxes);
_sliceId = partition.sliceId;
_tileId = partition.tileId;
_sliceOrigin = partition.origin;
compressPartition(srcPartition, params, outputFn, reconstructedCloud);
compressPartition(
srcPartition, partitionInOriginCloud, params, outputFn,
reconstructedCloud);
}
return 0;
......@@ -240,6 +309,7 @@ PCCTMC3Encoder3::fixupParameterSets(EncoderParams* params)
void
PCCTMC3Encoder3::compressPartition(
const PCCPointSet3& inputPointCloud,
const PCCPointSet3& originPartCloud,
EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud)
......@@ -250,7 +320,28 @@ PCCTMC3Encoder3::compressPartition(
// - recolour
pointCloud.clear();
quantization(inputPointCloud);
pointCloud = inputPointCloud;
// Offset the point cloud to account for (preset) _sliceOrigin.
Vec3<double> sliceOriginD{double(_sliceOrigin[0]), double(_sliceOrigin[1]),
double(_sliceOrigin[2])};
// The new maximum bounds of the offset cloud
Vec3<int> maxBound{0};
const size_t pointCount = pointCloud.getPointCount();
for (size_t i = 0; i < pointCount; ++i) {
const Vec3<double> point = (pointCloud[i] -= sliceOriginD);
for (int k = 0; k < 3; ++k) {
const int k_coord = int(point[k]);
assert(k_coord >= 0);
if (maxBound[k] < k_coord)
maxBound[k] = k_coord;
}
}
// todo(df): don't update maxBound if something is forcing the value?
_sliceBoxWhd = maxBound;
// geometry encoding
if (1) {
......@@ -278,12 +369,9 @@ PCCTMC3Encoder3::compressPartition(
// recolouring
// NB: recolouring is required if points are added / removed
bool recolourNeeded =
_gps->geom_unique_points_flag || _gps->trisoup_node_size_log2 > 0;
if (recolourNeeded) {
if (_gps->geom_unique_points_flag || _gps->trisoup_node_size_log2 > 0) {
recolour(
params->recolour, inputPointCloud, _sps->seq_source_geom_scale_factor,
params->recolour, originPartCloud, _sps->seq_source_geom_scale_factor,
_sps->seq_bounding_box_xyz0, _sliceOrigin, &pointCloud);
}
......@@ -425,9 +513,13 @@ PCCTMC3Encoder3::appendReconstructedPoints(PCCPointSet3* reconstructedCloud)
// translates and scales inputPointCloud, storing the result in
// this->pointCloud for use by the encoding process.
void
PCCPointSet3
PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
{
PCCPointSet3 pointCloud0;
pointCloud0.clear();
quantizedToOrigin.clear();
// Currently the sequence width/height/depth must be set
assert(_sps->seq_bounding_box_whd != Vec3<int>{0});
......@@ -447,11 +539,11 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
if (_gps->geom_unique_points_flag) {
quantizePositionsUniq(
_sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0,
clampBox, inputPointCloud, &pointCloud);
clampBox, inputPointCloud, &pointCloud0, quantizedToOrigin);
} else {
quantizePositions(
_sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0,
clampBox, inputPointCloud, &pointCloud);
clampBox, inputPointCloud, &pointCloud0);
}
// Offset the point cloud to account for (preset) _sliceOrigin.
......@@ -461,9 +553,9 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
// The new maximum bounds of the offset cloud
Vec3<int> maxBound{0};
const size_t pointCount = pointCloud.getPointCount();
const size_t pointCount = pointCloud0.getPointCount();
for (size_t i = 0; i < pointCount; ++i) {
const Vec3<double> point = (pointCloud[i] -= sliceOriginD);
const Vec3<double> point = (pointCloud0[i] -= sliceOriginD);
for (int k = 0; k < 3; ++k) {
const int k_coord = int(point[k]);
assert(k_coord >= 0);
......@@ -474,6 +566,36 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
// todo(df): don't update maxBound if something is forcing the value?
_sliceBoxWhd = maxBound;
return pointCloud0;
}
//----------------------------------------------------------------------------
// get the partial point cloud according to required point indexes
void
PCCTMC3Encoder3::getSrcPartition(
const PCCPointSet3& inputPointCloud,
PCCPointSet3& srcPartition,
std::vector<int32_t> Indexes)
{
//PCCPointSet3 srcPartition;
srcPartition.addRemoveAttributes(
inputPointCloud.hasColors(), inputPointCloud.hasReflectances());
int partitionSize = Indexes.size();
srcPartition.resize(partitionSize);
for (int i = 0; i < partitionSize; i++) {
int inputIdx = Indexes[i];
srcPartition[i] = inputPointCloud[inputIdx];
if (inputPointCloud.hasColors())
srcPartition.setColor(i, inputPointCloud.getColor(inputIdx));
if (inputPointCloud.hasReflectances())
srcPartition.setReflectance(i, inputPointCloud.getReflectance(inputIdx));
}
//return srcPartition;
}
//============================================================================
......
......@@ -37,9 +37,26 @@
#include <algorithm>
#include <cstdlib>
#include <map>
#include <stdint.h>
namespace pcc {
//============================================================================
// Determine whether half of slices are smaller than maxPoints
bool
halfQualified(const std::vector<Partition> slices, int maxPoints)
{
int Qualified = 0;
for (int i = 0; i < slices.size(); i++) {
if (slices[i].pointIndexes.size() < maxPoints)
Qualified++;
}
return ((double)Qualified / (double)slices.size()) > 0.5;
}
//============================================================================
template<typename T>
......@@ -82,10 +99,11 @@ shortestAxis(const Box3<T>& curBox)
// ratio of longest:shortest axis is used).
// No tile metadata is generated.
PartitionSet
partitionByUniformGeom(const PCCPointSet3& cloud, int numPartitions)
std::vector<Partition>
partitionByUniformGeom(
const PartitionParams& params, const PCCPointSet3& cloud, int tileID)
{
PartitionSet partitions;
std::vector<Partition> slices;
Box3<double> bbox = cloud.computeBoundingBox();
......@@ -94,56 +112,63 @@ partitionByUniformGeom(const PCCPointSet3& cloud, int numPartitions)
int minEdgeAxis = shortestAxis(bbox);
int minEdge = bbox.max[minEdgeAxis] - bbox.min[minEdgeAxis];
int sliceNum;
int sliceSize;
if (!numPartitions) {
sliceNum = maxEdge / minEdge;
sliceSize = minEdge;
} else {
sliceNum = numPartitions;
sliceSize = maxEdge / sliceNum;
}
partitions.slices.resize(sliceNum);
int sliceNum = minEdge ? (maxEdge / minEdge) : 1;
int sliceSize = minEdge ? minEdge : maxEdge;
for (int i = 0; i < sliceNum; i++) {
auto& slice = partitions.slices[i];
slice.sliceId = i;
slice.tileId = -1;
slice.origin = Vec3<int>{0};
}
while (1) {
slices.clear();
slices.resize(sliceNum);
for (int i = 0; i < sliceNum; i++) {
auto& slice = slices[i];
slice.sliceId = i;