Commit a8e8d3e6 authored by David Flynn's avatar David Flynn
Browse files

geom: use single level based tree position representation

The geometry tree nodes contain a position element (pos) that identifies
the spatial position of the node.  At internal nodes this node position
is the partial position of a point and may either be represented using
the magnitude of the decoded points, or the magnitude of the current
depth.

The first form equates to:
 nextPos = curPos | (xyz << currentNodeSize)

The second:
 nextPos = (curPos << 1) | xyz

Where xyz represents a position bit from the coded occupancy.

In order to use the first form to determine neighbour relationships,
curPos must be shifted down to represent position within the current
depth.  This repeated shifting, along with the introduction of
simultaneous quantised and unquantised positions becomes increasingly
difficult to follow when adding non-cubic nodes (via OtQtBt).

This commit switches the representation to the second form above,
eliminating various shifted forms and duplicate state.  A decoded
positions is inverse quantised when reaching a leaf node.
parent 220acf28
......@@ -431,7 +431,7 @@ AttributeDecoder::decodeReflectancesRaht(
const int voxelCount = int(pointCloud.getPointCount());
std::vector<MortonCodeWithIndex> packedVoxel(voxelCount);
for (int n = 0; n < voxelCount; n++) {
packedVoxel[n].mortonCode = mortonAddr(pointCloud[n], 0);
packedVoxel[n].mortonCode = mortonAddr(pointCloud[n]);
packedVoxel[n].index = n;
}
sort(packedVoxel.begin(), packedVoxel.end());
......@@ -491,7 +491,7 @@ AttributeDecoder::decodeColorsRaht(
const int voxelCount = int(pointCloud.getPointCount());
std::vector<MortonCodeWithIndex> packedVoxel(voxelCount);
for (int n = 0; n < voxelCount; n++) {
packedVoxel[n].mortonCode = mortonAddr(pointCloud[n], 0);
packedVoxel[n].mortonCode = mortonAddr(pointCloud[n]);
packedVoxel[n].index = n;
}
sort(packedVoxel.begin(), packedVoxel.end());
......
......@@ -775,7 +775,7 @@ AttributeEncoder::encodeReflectancesTransformRaht(
const int voxelCount = int(pointCloud.getPointCount());
std::vector<MortonCodeWithIndex> packedVoxel(voxelCount);
for (int n = 0; n < voxelCount; n++) {
packedVoxel[n].mortonCode = mortonAddr(pointCloud[n], 0);
packedVoxel[n].mortonCode = mortonAddr(pointCloud[n]);
packedVoxel[n].index = n;
}
sort(packedVoxel.begin(), packedVoxel.end());
......@@ -844,7 +844,7 @@ AttributeEncoder::encodeColorsTransformRaht(
const int voxelCount = int(pointCloud.getPointCount());
std::vector<MortonCodeWithIndex> packedVoxel(voxelCount);
for (int n = 0; n < voxelCount; n++) {
packedVoxel[n].mortonCode = mortonAddr(pointCloud[n], 0);
packedVoxel[n].mortonCode = mortonAddr(pointCloud[n]);
packedVoxel[n].index = n;
}
sort(packedVoxel.begin(), packedVoxel.end());
......
......@@ -44,14 +44,13 @@ namespace pcc {
void
updateGeometryOccupancyAtlas(
const Vec3<uint32_t>& currentPosition,
const int nodeSizeLog2,
const pcc::ringbuf<PCCOctree3Node>& fifo,
const pcc::ringbuf<PCCOctree3Node>::iterator& fifoCurrLvlEnd,
MortonMap3D* occupancyAtlas,
Vec3<uint32_t>* atlasOrigin)
{
const uint32_t mask = (1 << occupancyAtlas->cubeSizeLog2()) - 1;
const int shift = occupancyAtlas->cubeSizeLog2() + nodeSizeLog2;
const int shift = occupancyAtlas->cubeSizeLog2();
const auto currentOrigin = currentPosition >> shift;
// only refresh the atlas if the current position lies outside the
......@@ -67,9 +66,9 @@ updateGeometryOccupancyAtlas(
if (currentOrigin != it->pos >> shift)
break;
const uint32_t x = (it->pos[0] >> nodeSizeLog2) & mask;
const uint32_t y = (it->pos[1] >> nodeSizeLog2) & mask;
const uint32_t z = (it->pos[2] >> nodeSizeLog2) & mask;
const uint32_t x = it->pos[0] & mask;
const uint32_t y = it->pos[1] & mask;
const uint32_t z = it->pos[2] & mask;
occupancyAtlas->setByte(x, y, z, it->siblingOccupancy);
}
}
......@@ -79,14 +78,13 @@ updateGeometryOccupancyAtlas(
void
updateGeometryOccupancyAtlasOccChild(
const Vec3<uint32_t>& pos,
int nodeSizeLog2,
uint8_t childOccupancy,
MortonMap3D* occupancyAtlas)
{
uint32_t mask = (1 << occupancyAtlas->cubeSizeLog2()) - 1;
uint32_t x = (pos[0] >> nodeSizeLog2) & mask;
uint32_t y = (pos[1] >> nodeSizeLog2) & mask;
uint32_t z = (pos[2] >> nodeSizeLog2) & mask;
uint32_t x = pos[0] & mask;
uint32_t y = pos[1] & mask;
uint32_t z = pos[2] & mask;
occupancyAtlas->setChildOcc(x, y, z, childOccupancy);
}
......@@ -140,14 +138,13 @@ GeometryNeighPattern
makeGeometryNeighPattern(
bool adjacent_child_contextualization_enabled_flag,
const Vec3<uint32_t>& position,
const int nodeSizeLog2,
const MortonMap3D& occupancyAtlas)
{
const int mask = occupancyAtlas.cubeSize() - 1;
const int cubeSizeMinusOne = mask;
const int32_t x = (position[0] >> nodeSizeLog2) & mask;
const int32_t y = (position[1] >> nodeSizeLog2) & mask;
const int32_t z = (position[2] >> nodeSizeLog2) & mask;
const int32_t x = position[0] & mask;
const int32_t y = position[1] & mask;
const int32_t z = position[2] & mask;
uint8_t neighPattern;
if (
x > 0 && x < cubeSizeMinusOne && y > 0 && y < cubeSizeMinusOne && z > 0
......
......@@ -177,14 +177,12 @@ struct GeometryNeighPattern {
GeometryNeighPattern makeGeometryNeighPattern(
bool adjacent_child_contextualization_enabled_flag,
const Vec3<uint32_t>& currentPosition,
const int nodeSizeLog2,
const MortonMap3D& occupancyAtlas);
// populate (if necessary) the occupancy atlas with occupancy information
// from @fifo.
void updateGeometryOccupancyAtlas(
const Vec3<uint32_t>& position,
const int nodeSizeLog2,
const ringbuf<PCCOctree3Node>& fifo,
const ringbuf<PCCOctree3Node>::iterator& fifoCurrLvlEnd,
MortonMap3D* occupancyAtlas,
......@@ -192,7 +190,6 @@ void updateGeometryOccupancyAtlas(
void updateGeometryOccupancyAtlasOccChild(
const Vec3<uint32_t>& pos,
int nodeSizeLog2,
uint8_t childOccupancy,
MortonMap3D* occupancyAtlas);
......
......@@ -378,16 +378,13 @@ mortonAddr(const int32_t x, const int32_t y, const int32_t z)
}
//---------------------------------------------------------------------------
// Convert a vector position (divided by 2^depth) to morton order address.
// Convert a vector position to morton order address.
template<typename T>
int64_t
mortonAddr(const Vec3<T>& vec, int depth)
mortonAddr(const Vec3<T>& vec)
{
int x = int(vec.x()) >> depth;
int y = int(vec.y()) >> depth;
int z = int(vec.z()) >> depth;
return mortonAddr(x, y, z);
return mortonAddr(int(vec.x()), int(vec.y()), int(vec.z()));
}
//---------------------------------------------------------------------------
......
......@@ -63,14 +63,13 @@ void
predictGeometryOccupancyIntra(
const MortonMap3D& occupancyAtlas,
Vec3<uint32_t> pos,
int nodeSizeLog2,
int* occupancyIsPredicted,
int* occupancyPrediction)
{
uint32_t mask = occupancyAtlas.cubeSize() - 1;
int32_t x = (pos[0] >> nodeSizeLog2) & mask;
int32_t y = (pos[1] >> nodeSizeLog2) & mask;
int32_t z = (pos[2] >> nodeSizeLog2) & mask;
int32_t x = pos[0] & mask;
int32_t y = pos[1] & mask;
int32_t z = pos[2] & mask;
int score[8] = {0, 0, 0, 0, 0, 0, 0, 0};
int numOccupied = 0;
......
......@@ -47,7 +47,6 @@ namespace pcc {
void predictGeometryOccupancyIntra(
const MortonMap3D& occupancyAtlas,
Vec3<uint32_t> pos,
int nodeSizeLog2,
int* occupacyIsPredIntra,
int* occupacyPredIntra);
......
......@@ -116,7 +116,6 @@ updateGeometryNeighState(
bool siblingRestriction,
const ringbuf<PCCOctree3Node>::iterator& bufEnd,
int64_t numNodesNextLvl,
int childSizeLog2,
PCCOctree3Node& child,
int childIdx,
uint8_t neighPattern,
......@@ -124,7 +123,7 @@ updateGeometryNeighState(
{
int64_t midx;
if (!siblingRestriction) {
midx = child.mortonIdx = mortonAddr(child.pos, childSizeLog2);
midx = child.mortonIdx = mortonAddr(child.pos);
}
static const struct {
......
......@@ -78,9 +78,8 @@ struct PCCOctree3Node {
// The occupancy map used describing the current node and its siblings.
uint8_t siblingOccupancy;
// The qp used for geometry quantisation
int qp;
Vec3<uint32_t> pos_quant;
Vec3<uint32_t> pos_base;
};
//---------------------------------------------------------------------------
......@@ -92,7 +91,6 @@ void updateGeometryNeighState(
bool siblingRestriction,
const ringbuf<PCCOctree3Node>::iterator& bufEnd,
int64_t numNodesNextLvl,
int childSizeLog2,
PCCOctree3Node& child,
int childIdx,
uint8_t neighPattern,
......
......@@ -411,19 +411,38 @@ GeometryOctreeDecoder::decodeDirectPosition(
if (_arithmeticDecoder->decode(_ctxNumIdcmPointsEq1))
numPoints++;
QuantizerGeom quantizer(node.qp);
for (int i = 0; i < numPoints; i++) {
// convert node-relative position to world position
Vec3<uint32_t> pos = decodePointPosition(nodeSizeLog2);
*(outputPoints++) = {
double(quantizer.scale(node.pos_quant[0] + pos[0]) + node.pos_base[0]),
double(quantizer.scale(node.pos_quant[1] + pos[1]) + node.pos_base[1]),
double(quantizer.scale(node.pos_quant[2] + pos[2]) + node.pos_base[2])};
}
for (int i = 0; i < numPoints; i++)
*(outputPoints++) = decodePointPosition(nodeSizeLog2);
return numPoints;
}
//-------------------------------------------------------------------------
// Helper to inverse quantise positions
Vec3<uint32_t>
invQuantPosition(int qp, int quantBitMask, const Vec3<uint32_t>& pos)
{
// pos represents the position within the coded tree as follows:
// |pppppqqqqqq|00
// - p = unquantised bit
// - q = quantised bit
// - 0 = bits that were not coded (MSBs of q)
// The reconstruction is:
// |ppppp00qqqqqq|
QuantizerGeom quantizer(qp);
int shiftBits = (qp - 4) / 6;
Vec3<uint32_t> recon;
for (int k = 0; k < 3; k++) {
int posQuant = pos[k] & (quantBitMask >> shiftBits);
recon[k] = (pos[k] ^ posQuant) << shiftBits;
recon[k] |= quantizer.scale(posQuant);
}
return recon;
}
//-------------------------------------------------------------------------
void
......@@ -474,11 +493,15 @@ decodeGeometryOctree(
int sliceQp = gps.geom_base_qp + gbh.geom_slice_qp_offset;
int numLvlsUntilQpOffset = -1;
int posQuantBits = 0;
if (gbh.geom_octree_qp_offset_enabled_flag)
numLvlsUntilQpOffset = gbh.geom_octree_qp_offset_depth;
else if (gps.geom_scaling_enabled_flag)
else if (gps.geom_scaling_enabled_flag) {
node00.qp = sliceQp;
// determine the mask of LSBs used in quantisation
posQuantBits = (1 << (nodeSizeLog2 - numLvlsUntilQpOffset)) - 1;
}
for (; !fifo.empty(); fifo.pop_front()) {
if (fifo.begin() == fifoCurrLvlEnd) {
......@@ -503,11 +526,8 @@ decodeGeometryOctree(
PCCOctree3Node& node0 = fifo.front();
if (numLvlsUntilQpOffset == 0) {
if (numLvlsUntilQpOffset == 0)
node0.qp = decoder.decodeQpOffset() + sliceQp;
node0.pos_base = node0.pos;
node0.pos_quant = {0, 0, 0};
}
int shiftBits = (node0.qp - 4) / 6;
int effectiveNodeSizeLog2 = nodeSizeLog2 - shiftBits;
......@@ -519,12 +539,12 @@ decodeGeometryOctree(
if (gps.neighbour_avail_boundary_log2) {
updateGeometryOccupancyAtlas(
node0.pos, nodeSizeLog2, fifo, fifoCurrLvlEnd, &occupancyAtlas,
node0.pos, fifo, fifoCurrLvlEnd, &occupancyAtlas,
&occupancyAtlasOrigin);
GeometryNeighPattern gnp = makeGeometryNeighPattern(
gps.adjacent_child_contextualization_enabled_flag, node0.pos,
nodeSizeLog2, occupancyAtlas);
occupancyAtlas);
node0.neighPattern = gnp.neighPattern;
occupancyAdjacencyGt0 = gnp.adjacencyGt0;
......@@ -538,7 +558,7 @@ decodeGeometryOctree(
// generate intra prediction
if (effectiveNodeSizeLog2 < gps.intra_pred_max_node_size_log2) {
predictGeometryOccupancyIntra(
occupancyAtlas, node0.pos, nodeSizeLog2, &occupancyIsPredicted,
occupancyAtlas, node0.pos, &occupancyIsPredicted,
&occupancyPrediction);
}
......@@ -554,7 +574,7 @@ decodeGeometryOctree(
// update atlas for advanced neighbours
if (gps.neighbour_avail_boundary_log2) {
updateGeometryOccupancyAtlasOccChild(
node0.pos, nodeSizeLog2, occupancy, &occupancyAtlas);
node0.pos, occupancy, &occupancyAtlas);
}
// population count of occupancy for IDCM
......@@ -583,11 +603,12 @@ decodeGeometryOctree(
numPoints = decoder.decodePositionLeafNumPoints();
}
QuantizerGeom quantizer(node0.qp);
const Vec3<double> point(
quantizer.scale(node0.pos_quant[0] + x) + node0.pos_base[0],
quantizer.scale(node0.pos_quant[1] + y) + node0.pos_base[1],
quantizer.scale(node0.pos_quant[2] + z) + node0.pos_base[2]);
// the final bits from the leaf:
Vec3<uint32_t> pos{(node0.pos[0] << 1) + x, (node0.pos[1] << 1) + y,
(node0.pos[2] << 1) + z};
pos = invQuantPosition(node0.qp, posQuantBits, pos);
const Vec3<double> point(pos[0], pos[1], pos[2]);
for (int i = 0; i < numPoints; ++i)
pointCloud[processedPointCount++] = point;
......@@ -601,23 +622,26 @@ decodeGeometryOctree(
auto& child = fifo.back();
child.qp = node0.qp;
child.pos_quant[0] = node0.pos_quant[0] + (x << effectiveChildSizeLog2);
child.pos_quant[1] = node0.pos_quant[1] + (y << effectiveChildSizeLog2);
child.pos_quant[2] = node0.pos_quant[2] + (z << effectiveChildSizeLog2);
child.pos_base = node0.pos_base;
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.pos[0] = (node0.pos[0] << 1) + x;
child.pos[1] = (node0.pos[1] << 1) + y;
child.pos[2] = (node0.pos[2] << 1) + z;
child.numSiblingsPlus1 = numOccupied;
child.siblingOccupancy = occupancy;
bool idcmEnabled = gps.inferred_direct_coding_mode_enabled_flag;
if (isDirectModeEligible(
idcmEnabled, effectiveNodeSizeLog2, node0, child)) {
// todo(df): this should go away when output is integer
Vec3<uint32_t> pointResidual[2];
int numPoints = decoder.decodeDirectPosition(
effectiveChildSizeLog2, child, &pointCloud[processedPointCount]);
processedPointCount += numPoints;
effectiveChildSizeLog2, child, pointResidual);
for (int j = 0; j < numPoints; j++) {
auto pos = (child.pos << effectiveChildSizeLog2) + pointResidual[j];
pos = invQuantPosition(node0.qp, posQuantBits, pos);
pointCloud[processedPointCount++] =
Vec3<double>(pos[0], pos[1], pos[2]);
}
if (numPoints > 0) {
// node fully decoded, do not split: discard child
......@@ -634,7 +658,7 @@ decodeGeometryOctree(
if (!gps.neighbour_avail_boundary_log2) {
updateGeometryNeighState(
gps.neighbour_context_restriction_flag, fifo.end(), numNodesNextLvl,
childSizeLog2, child, i, node0.neighPattern, occupancy);
child, i, node0.neighPattern, occupancy);
}
}
}
......@@ -645,9 +669,15 @@ decodeGeometryOctree(
pointCloud.resize(processedPointCount);
// return partial coding result
// todo(df): node.pos is still in quantised form -- this is probably wrong
// - add missing levels to node positions and inverse quantise
if (nodesRemaining) {
for (auto& node : fifo) {
int quantRemovedBits = (node.qp - 4) / 6;
auto pos = node.pos << nodeSizeLog2 - quantRemovedBits;
node.pos = invQuantPosition(node.qp, posQuantBits, pos);
}
*nodesRemaining = std::move(fifo);
return;
}
}
......
......@@ -442,18 +442,17 @@ geometryQuantization(
{
QuantizerGeom quantizer = QuantizerGeom(node.qp);
int qpShift = (node.qp - 4) / 6;
Vec3<uint32_t> start_pos = node.pos;
Vec3<uint32_t> start_pos_quant = 0;
Vec3<uint32_t> end_pos_quant = (1 << nodeSizeLog2) >> qpShift;
int quantBitsMask = (1 << nodeSizeLog2) - 1;
Vec3<uint32_t> end_pos_quant = ((1 << nodeSizeLog2) >> qpShift) - 1;
for (int i = node.start; i < node.end; i++) {
Vec3<double> reconPoint, quantizedPoint;
Vec3<double> reconPoint;
for (int k = 0; k < 3; k++) {
int64_t k_pos = pointCloud[i][k] - start_pos[k];
uint32_t pos = uint32_t(pointCloud[i][k]);
uint32_t quantPos = quantizer.quantize(pos & quantBitsMask);
quantPos = PCCClip(quantPos, 0, end_pos_quant[k]);
k_pos = quantizer.quantize(k_pos);
k_pos = PCCClip(k_pos, start_pos_quant[k], end_pos_quant[k] - 1);
pointCloud[i][k] = k_pos;
reconPoint[k] = quantizer.scale(k_pos) + start_pos[k];
pointCloud[i][k] = quantPos;
reconPoint[k] = (pos & ~quantBitsMask) + quantizer.scale(quantPos);
}
pointCloud.setPositionReconstructed(i, reconPoint);
}
......@@ -652,12 +651,12 @@ encodeGeometryOctree(
if (gps.neighbour_avail_boundary_log2) {
updateGeometryOccupancyAtlas(
node0.pos, nodeSizeLog2, fifo, fifoCurrLvlEnd, &occupancyAtlas,
node0.pos, fifo, fifoCurrLvlEnd, &occupancyAtlas,
&occupancyAtlasOrigin);
GeometryNeighPattern gnp = makeGeometryNeighPattern(
gps.adjacent_child_contextualization_enabled_flag, node0.pos,
nodeSizeLog2, occupancyAtlas);
occupancyAtlas);
node0.neighPattern = gnp.neighPattern;
occupancyAdjacencyGt0 = gnp.adjacencyGt0;
......@@ -671,14 +670,14 @@ encodeGeometryOctree(
// generate intra prediction
if (effectiveNodeSizeLog2 < gps.intra_pred_max_node_size_log2) {
predictGeometryOccupancyIntra(
occupancyAtlas, node0.pos, nodeSizeLog2, &occupancyIsPredicted,
occupancyAtlas, node0.pos, &occupancyIsPredicted,
&occupancyPrediction);
}
// update atlas for advanced neighbours
if (gps.neighbour_avail_boundary_log2) {
updateGeometryOccupancyAtlasOccChild(
node0.pos, nodeSizeLog2, occupancy, &occupancyAtlas);
node0.pos, occupancy, &occupancyAtlas);
}
// encode child occupancy map
......@@ -744,9 +743,9 @@ encodeGeometryOctree(
int z = !!(i & 1);
child.qp = node0.qp;
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.pos[0] = (node0.pos[0] << 1) + x;
child.pos[1] = (node0.pos[1] << 1) + y;
child.pos[2] = (node0.pos[2] << 1) + z;
child.start = childPointsStartIdx;
childPointsStartIdx += childCounts[i];
......@@ -783,7 +782,7 @@ encodeGeometryOctree(
if (!gps.neighbour_avail_boundary_log2) {
updateGeometryNeighState(
gps.neighbour_context_restriction_flag, fifo.end(), numNodesNextLvl,
childSizeLog2, child, i, node0.neighPattern, occupancy);
child, i, node0.neighPattern, occupancy);
}
}
}
......@@ -795,8 +794,11 @@ encodeGeometryOctree(
pointCloud[i] = pointCloud.getPositionReconstructed(i);
// return partial coding result
// todo(df): node.pos is still in quantised form -- this is probably wrong
// - add missing levels to node positions
// todo(df): this does not yet support inverse quantisation of node.pos
if (nodesRemaining) {
for (auto& node : fifo)
node.pos <<= nodeSizeLog2;
*nodesRemaining = std::move(fifo);
return;
}
......
Markdown is supported
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