Commit 76d35c23 authored by David Flynn's avatar David Flynn
Browse files

m42239/geometry: inferred direct position coding mode

This commit provides an implementation of m42239, permitting early
coding termination of sub-trees that contain few points:

 - the so-called IDCM feature uses an eligibility criteria prior
   to permitting an encoder mode decision.

 - the feature may be controlled via the command line / config file
   using the --inferredDirectCodingMode=1|0 option
parent ca4ac3f9
......@@ -99,6 +99,12 @@ class PCCVector3 {
data[2] += a;
return *this;
}
PCCVector3 &operator<<=(int val) {
data[0] <<= val;
data[1] <<= val;
data[2] <<= val;
return *this;
}
PCCVector3 &operator/=(const T a) {
assert(a != 0);
data[0] /= a;
......
......@@ -124,6 +124,17 @@ static int popcnt(uint32_t x) {
return ((x + (x >> 4) & 0xF0F0F0Fu) * 0x1010101u) >> 24;
}
//---------------------------------------------------------------------------
// Population count -- return the number of bits set in @x.
//
static int popcnt(uint8_t x) {
uint32_t val = x * 0x08040201u;
val >>= 3;
val &= 0x11111111u;
val *= 0x11111111u;
return val >> 28;
}
//---------------------------------------------------------------------------
// Test if population count is greater than 1.
// Returns non-zero if true.
......
......@@ -253,6 +253,16 @@ class PCCPointSet3 {
PCCPointSet3 &operator=(const PCCPointSet3 &rhs) = default;
~PCCPointSet3() = default;
void
swap(PCCPointSet3& other) {
using std::swap;
swap(positions, other.positions);
swap(colors, other.colors);
swap(reflectances, other.reflectances);
swap(withColors, other.withColors);
swap(withReflectances, other.withReflectances);
}
PCCPoint3D operator[](const size_t index) const {
assert(index < positions.size());
return positions[index];
......@@ -763,6 +773,14 @@ swap(const PCCPointSet3::Proxy& a, const PCCPointSet3::Proxy& b) {
a.swap(b);
}
//---------------------------------------------------------------------------
// Swap two point clouds
static void
swap(PCCPointSet3& a, PCCPointSet3& b) {
a.swap(b);
}
//---------------------------------------------------------------------------
} /* namespace pcc */
......
......@@ -38,6 +38,7 @@
#include "PCCKdTree.h"
#include "PCCMath.h"
#include <cstdint>
#include <vector>
namespace pcc {
......@@ -47,6 +48,8 @@ const uint32_t PCCTMC3MaxPredictionNearestNeighborCount = 8;
const uint32_t PCCTMC3Diff1AdaptiveDataModelCount = 512;
const uint32_t PCCTMC3AdaptiveDataModelAlphabetMaxSize = 2047;
const int MAX_NUM_DM_LEAF_POINTS = 2;
struct PCCOctree3Node {
// 3D position of the current node's origin (local x,y,z = 0).
PCCVector3<uint32_t> pos;
......@@ -65,6 +68,10 @@ struct PCCOctree3Node {
// /|
// 4 16 (z)
uint8_t neighPattern = 0;
// The current node's number of siblings plus one.
// ie, the number of child nodes present in this node's parent.
uint8_t numSiblingsPlus1;
};
struct PCCNeighborInfo {
......@@ -299,6 +306,21 @@ updateGeometryNeighState(
}
}
//---------------------------------------------------------------------------
// Determine if direct coding is permitted.
// If tool is enabled:
// - Block must not be near the bottom of the tree
// - The parent / grandparent are sparsely occupied
bool
isDirectModeEligible(
bool featureEnabled,
int nodeSizeLog2, const PCCOctree3Node& node, const PCCOctree3Node& child
) {
return featureEnabled && (nodeSizeLog2 >= 2)
&& (child.numSiblingsPlus1 == 1) && (node.numSiblingsPlus1 <= 2);
}
//---------------------------------------------------------------------------
}
......
......@@ -421,6 +421,9 @@ class PCCTMC3Decoder3 {
PCCReadFromBuffer<uint8_t>(bitstream.buffer, u8value, bitstream.size);
neighbourContextsEnabled = bool(u8value);
PCCReadFromBuffer<uint8_t>(bitstream.buffer, u8value, bitstream.size);
inferredDirectCodingModeEnabled = bool(u8value);
if (hasColors) {
pointCloud.addColors();
} else {
......@@ -540,6 +543,61 @@ class PCCTMC3Decoder3 {
return occupancy;
}
//-------------------------------------------------------------------------
// Decode a position of a point in a given volume.
PCCVector3<uint32_t>
decodePointPosition(
int nodeSizeLog2,
o3dgc::Arithmetic_Codec* arithmeticDecoder,
o3dgc::Static_Bit_Model& ctxPointPosBlock
){
PCCVector3<uint32_t> delta{};
for (int i = nodeSizeLog2; i > 0; i--) {
delta <<= 1;
delta[0] |= arithmeticDecoder->decode(ctxPointPosBlock);
delta[1] |= arithmeticDecoder->decode(ctxPointPosBlock);
delta[2] |= arithmeticDecoder->decode(ctxPointPosBlock);
}
return delta;
}
//-------------------------------------------------------------------------
// Direct coding of position of points in node (early tree termination).
// Decoded points are written to @outputPoints
// Returns the number of points emitted.
template<class OutputIt>
int decodeDirectPosition(
int nodeSizeLog2,
const PCCOctree3Node& node,
o3dgc::Arithmetic_Codec* arithmeticDecoder,
o3dgc::Adaptive_Bit_Model& ctxBlockSkipTh,
o3dgc::Adaptive_Bit_Model& ctxNumIdcmPointsEq1,
o3dgc::Static_Bit_Model& ctxPointPosBlock,
OutputIt outputPoints
) {
bool isDirectMode = arithmeticDecoder->decode(ctxBlockSkipTh);
if (!isDirectMode) {
return 0;
}
int numPoints = 1;
if (arithmeticDecoder->decode(ctxNumIdcmPointsEq1))
numPoints++;
for (int i = 0; i < numPoints; i++) {
// convert node-relative position to world position
PCCVector3<uint32_t> pos = node.pos + decodePointPosition(
nodeSizeLog2, arithmeticDecoder, ctxPointPosBlock);
*(outputPoints++) = {double(pos[0]), double(pos[1]), double(pos[2])};
}
return numPoints;
}
//-------------------------------------------------------------------------
int decodePositions(PCCBitstream &bitstream, PCCPointSet3 &pointCloud) {
......@@ -554,6 +612,9 @@ class PCCTMC3Decoder3 {
o3dgc::Adaptive_Bit_Model ctxSingleChild;
o3dgc::Adaptive_Bit_Model ctxSinglePointPerBlock;
o3dgc::Adaptive_Bit_Model ctxPointCountPerBlock;
o3dgc::Adaptive_Bit_Model ctxBlockSkipTh;
o3dgc::Adaptive_Bit_Model ctxNumIdcmPointsEq1;
// pattern model using ten 6-neighbour configurations
o3dgc::Adaptive_Data_Model ctxOccupancy[10];
for (int i = 0; i < 10; i++) {
......@@ -582,6 +643,7 @@ class PCCTMC3Decoder3 {
node00.end = uint32_t(pointCloud.getPointCount());
node00.pos = uint32_t(0);
node00.neighPattern = 0;
node00.numSiblingsPlus1 = 8;
fifo.push_back(node00);
size_t processedPointCount = 0;
......@@ -609,6 +671,9 @@ class PCCTMC3Decoder3 {
);
assert(occupancy > 0);
// population count of occupancy for IDCM
int numOccupied = popcnt(occupancy);
// split the current node
for (int i = 0; i < 8; i++) {
uint32_t mask = 1 << i;
......@@ -655,8 +720,27 @@ class PCCTMC3Decoder3 {
child.pos[0] = node0.pos[0] + (x << childSizeLog2);
child.pos[1] = node0.pos[1] + (y << childSizeLog2);
child.pos[2] = node0.pos[2] + (z << childSizeLog2);
child.numSiblingsPlus1 = numOccupied;
bool idcmEnabled = inferredDirectCodingModeEnabled;
if (isDirectModeEligible(idcmEnabled, nodeSizeLog2, node0, child)) {
int numPoints = decodeDirectPosition(
childSizeLog2, child, &arithmeticDecoder,
ctxBlockSkipTh, ctxNumIdcmPointsEq1, ctxEquiProb,
&pointCloud[processedPointCount]
);
processedPointCount += numPoints;
if (numPoints > 0) {
// node fully decoded, do not split: discard child
fifo.pop_back();
// NB: no further siblings to decode by definition of IDCM
assert(child.numSiblingsPlus1 == 1);
break;
}
}
// NB: there is no need to update the neighbour state for leaf nodes
numNodesNextLvl++;
if (neighbourContextsEnabled) {
updateGeometryNeighState(
......@@ -671,6 +755,7 @@ class PCCTMC3Decoder3 {
bitstream.size += compressedBitstreamSize;
return 0;
}
void inverseQuantization(PCCPointSet3 &pointCloud, const bool roundOutputPositions) {
const size_t pointCount = pointCloud.getPointCount();
const double invScale = 1.0 / positionQuantizationScale;
......@@ -710,6 +795,10 @@ class PCCTMC3Decoder3 {
// Controls the use of neighbour based contextualisation of octree
// occupancy during geometry coding.
bool neighbourContextsEnabled;
// Controls the use of early termination of the geometry tree
// by directly coding the position of isolated points.
bool inferredDirectCodingModeEnabled;
};
}
......
......@@ -38,6 +38,7 @@
#include <assert.h>
#include <map>
#include <deque>
#include <queue>
#include <set>
#include <string>
......@@ -70,6 +71,10 @@ struct PCCTMC3Encoder3Parameters {
// occupancy during geometry coding.
bool neighbourContextsEnabled;
// Controls the use of early termination of the geometry tree
// by directly coding the position of isolated points.
bool inferredDirectCodingModeEnabled;
std::map<std::string, PCCAttributeEncodeParamaters> attributeEncodeParameters;
};
......@@ -693,6 +698,56 @@ class PCCTMC3Encoder3 {
}
}
//-------------------------------------------------------------------------
// Encode a position of a point in a given volume.
void
encodePointPosition(
int nodeSizeLog2,
const PCCVector3<uint32_t>& pos,
o3dgc::Arithmetic_Codec* arithmeticEncoder,
o3dgc::Static_Bit_Model& ctxPointPosBlock
){
for (int mask = 1 << (nodeSizeLog2 - 1); mask; mask >>= 1) {
arithmeticEncoder->encode(!!(pos[0] & mask), ctxPointPosBlock);
arithmeticEncoder->encode(!!(pos[1] & mask), ctxPointPosBlock);
arithmeticEncoder->encode(!!(pos[2] & mask), ctxPointPosBlock);
}
}
//-------------------------------------------------------------------------
// Direct coding of position of points in node (early tree termination).
bool
encodeDirectPosition(
int nodeSizeLog2,
const PCCOctree3Node& node,
o3dgc::Arithmetic_Codec* arithmeticEncoder,
o3dgc::Adaptive_Bit_Model& ctxBlockSkipTh,
o3dgc::Adaptive_Bit_Model& ctxNumIdcmPointsEq1,
o3dgc::Static_Bit_Model& ctxPointPosBlock
) {
int numPoints = node.end - node.start;
if (numPoints > MAX_NUM_DM_LEAF_POINTS) {
arithmeticEncoder->encode(0, ctxBlockSkipTh);
return false;
}
arithmeticEncoder->encode(1, ctxBlockSkipTh);
arithmeticEncoder->encode(numPoints > 1, ctxNumIdcmPointsEq1);
for (auto idx = node.start; idx < node.end; idx++) {
// determine the point position relative to box edge
encodePointPosition(nodeSizeLog2, PCCVector3<uint32_t>{
int(pointCloud[idx][0]) - node.pos[0],
int(pointCloud[idx][1]) - node.pos[1],
int(pointCloud[idx][2]) - node.pos[2]},
arithmeticEncoder, ctxPointPosBlock);
}
return true;
}
//-------------------------------------------------------------------------
int encodePositions(
......@@ -710,6 +765,9 @@ class PCCTMC3Encoder3 {
o3dgc::Adaptive_Bit_Model ctxSingleChild;
o3dgc::Adaptive_Bit_Model ctxSinglePointPerBlock;
o3dgc::Adaptive_Bit_Model ctxPointCountPerBlock;
o3dgc::Adaptive_Bit_Model ctxBlockSkipTh;
o3dgc::Adaptive_Bit_Model ctxNumIdcmPointsEq1;
// occupancy map model using ten 6-neighbour configurations
o3dgc::Adaptive_Data_Model ctxOccupancy[10];
for (int i = 0; i < 10; i++) {
......@@ -737,8 +795,14 @@ class PCCTMC3Encoder3 {
node00.end = uint32_t(pointCloud.getPointCount());
node00.pos = uint32_t(0);
node00.neighPattern = 0;
node00.numSiblingsPlus1 = 8;
fifo.push_back(node00);
// map of pointCloud idx to DM idx, used to reorder the points
// after coding.
std::vector<int> pointIdxToDmIdx(int(pointCloud.getPointCount()), -1);
int nextDmIdx = 0;
size_t processedPointCount = 0;
std::vector<uint32_t> values;
......@@ -776,14 +840,18 @@ class PCCTMC3Encoder3 {
| (!!(int(point[0]) & bitpos) << 2);
});
// determine occupancy
// generate the bitmap of child occupancy and count
// the number of occupied children in node0.
int occupancy = 0;
int numSiblings = 0;
for (int i = 0; i < 8; i++) {
if (childCounts[i]) {
occupancy |= 1 << i;
numSiblings++;
}
}
// encode child occupancy map
assert(occupancy > 0);
encodeGeometryOccupancy(
params.neighbourContextsEnabled, &arithmeticEncoder, ctxSingleChild,
......@@ -816,8 +884,10 @@ class PCCTMC3Encoder3 {
continue;
}
// nodeSizeLog2 > 1: insert split children into fifo while updating
// neighbour state
// nodeSizeLog2 > 1: for each child:
// - determine elegibility for IDCM
// - directly code point positions if IDCM allowed and selected
// - otherwise, insert split children into fifo while updating neighbour state
int childPointsStartIdx = node0.start;
for (int i = 0; i < 8; i++) {
if (!childCounts[i]) {
......@@ -840,8 +910,30 @@ class PCCTMC3Encoder3 {
child.start = childPointsStartIdx;
childPointsStartIdx += childCounts[i];
child.end = childPointsStartIdx;
child.numSiblingsPlus1 = numSiblings;
bool idcmEnabled = params.inferredDirectCodingModeEnabled;
if (isDirectModeEligible(idcmEnabled, nodeSizeLog2, node0, child)) {
bool directModeUsed = encodeDirectPosition(
childSizeLog2, child, &arithmeticEncoder,
ctxBlockSkipTh, ctxNumIdcmPointsEq1, ctxEquiProb
);
if (directModeUsed) {
// point reordering to match decoder's order
for (auto idx = child.start; idx < child.end; idx++)
pointIdxToDmIdx[idx] = nextDmIdx++;
// NB: by definition, this is the only child node present
assert(child.numSiblingsPlus1 == 1);
// remove leaf node from fifo: it has been consumed and will
// not be further split.
fifo.pop_back();
break;
}
}
// NB: there is no need to update the neighbour state for leaf nodes
numNodesNextLvl++;
if (params.neighbourContextsEnabled) {
updateGeometryNeighState(
......@@ -852,6 +944,34 @@ class PCCTMC3Encoder3 {
}
}
////
// The following is to re-order the points according in the decoding
// order since IDCM causes leaves to be coded earlier than they
// otherwise would.
PCCPointSet3 pointCloud2;
if (pointCloud.hasColors())
pointCloud2.addColors();
if (pointCloud.hasReflectances())
pointCloud2.addReflectances();
pointCloud2.resize(pointCloud.getPointCount());
// copy points with DM points first, the rest second
int outIdx = nextDmIdx;
for (int i = 0; i < pointIdxToDmIdx.size(); i++) {
int dstIdx = pointIdxToDmIdx[i];
if (dstIdx == -1) {
dstIdx = outIdx++;
}
pointCloud2[dstIdx] = pointCloud[i];
if (pointCloud.hasColors())
pointCloud2.setColor(dstIdx, pointCloud.getColor(i));
if (pointCloud.hasReflectances())
pointCloud2.setReflectance(dstIdx, pointCloud.getReflectance(i));
}
swap(pointCloud, pointCloud2);
const uint32_t compressedBitstreamSize = arithmeticEncoder.stop_encoder();
bitstream.size += compressedBitstreamSize;
PCCWriteToBuffer<uint32_t>(compressedBitstreamSize, bitstream.buffer, startSize);
......@@ -873,6 +993,9 @@ class PCCTMC3Encoder3 {
PCCWriteToBuffer<uint8_t>(
uint8_t(params.neighbourContextsEnabled), bitstream.buffer, bitstream.size);
PCCWriteToBuffer<uint8_t>(
uint8_t(params.inferredDirectCodingModeEnabled), bitstream.buffer, bitstream.size);
for (int k = 0; k < 3; ++k) {
PCCWriteToBuffer<double>(minPositions[k], bitstream.buffer, bitstream.size);
}
......
......@@ -170,6 +170,10 @@ bool ParseParameters(int argc, char *argv[], Parameters &params) {
params.encodeParameters.neighbourContextsEnabled, true,
"Contextualise geometry octree occupancy based on neighbour patterns")
("inferredDirectCodingMode",
params.encodeParameters.inferredDirectCodingModeEnabled, true,
"Permits early termination of the geometry octree for isolated points")
// attribute processing
// NB: Attribute options are special in the way they are applied (see above)
("attribute",
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment