Commit 693f8517 authored by David Flynn's avatar David Flynn
Browse files

hls: initial slice and partitioning framework

This commit provides a basic frame work for partitioning a frame into
multiple slices, with the continued assumption of single-frame
sequences.

The decoder is modified to independently decode each slice and
accumulate decoded points in a buffer for output.

The encoder is updated to support partitioning the input point cloud
into slices and to independently code each slice.  Points from
reconstructed slices are accumulated and output at the end of the
frame period.

The partitioning process (partitioning methods are defined in
partitioning.cpp) proceeds as follows:

 - quantise the input point cloud without duplicate point removal
   or reordering points.

 - apply the partitioning function to produce a list of tiles
   and slices, each slice having an origin, id, and list of point
   indexes that identify points in the input point cloud.

 - producing a source point cloud for each partition as a subset
   of the input point cloud.

 - compressing each partition (slice) as normal by quantising
   the partitioned input.  Recolouring is necessarily performed
   against the partitioned input since the recolouring method
   cannot correctly handle recolouring a partition from a complete
   point cloud.

NB: this commit does not provide any partitioning methods.
parent 0d955732
......@@ -121,6 +121,13 @@ disabled and a pre-filtering process is used to remove co-located points.
This option instructs the encoder to ignore all options relating to
attribute coding, as if they had never been configured.
### `--partitionMethod=0`
Selects the partitioning method to map points to tiles and slices:
| Value | Description |
|:-----:| --------------------|
| 0 | none (single slice) |
Geometry coding
---------------
......
......@@ -86,6 +86,7 @@ file(GLOB PROJECT_INC_FILES
"io_hls.h"
"io_tlv.h"
"osspecific.h"
"partitioning.h"
"pcc_chrono.h"
"ringbuf.h"
"tables.h"
......
......@@ -96,6 +96,7 @@ private:
// The point cloud currently being decoded
PCCPointSet3 _currentPointCloud;
PCCPointSet3 _accumCloud;
// Received parameter sets, mapping parameter set id -> parameterset
std::map<int, SequenceParameterSet> _spss;
......
......@@ -47,6 +47,16 @@
namespace pcc {
//============================================================================
enum class PartitionMethod
{
// Don't partition input
kNone = 0,
};
//============================================================================
struct EncoderParams {
SequenceParameterSet sps;
GeometryParameterSet gps;
......@@ -60,27 +70,36 @@ struct EncoderParams {
// Filename for saving recoloured point cloud.
std::string postRecolorPath;
// Method for partitioning the input cloud
PartitionMethod partitionMethod;
};
//============================================================================
class PCCTMC3Encoder3 {
public:
PCCTMC3Encoder3() { init(); }
PCCTMC3Encoder3() = default;
PCCTMC3Encoder3(const PCCTMC3Encoder3&) = default;
PCCTMC3Encoder3& operator=(const PCCTMC3Encoder3& rhs) = default;
~PCCTMC3Encoder3() = default;
void init();
int compress(
const PCCPointSet3& inputPointCloud,
EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud = nullptr);
void compressPartition(
const PCCPointSet3& inputPointCloud,
EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud = nullptr);
static void fixupParameterSets(EncoderParams* params);
private:
void reconstructedPointCloud(PCCPointSet3* reconstructedCloud);
void appendReconstructedPoints(PCCPointSet3* reconstructedCloud);
void encodeGeometryBrick(PayloadBuffer* buf);
......
......@@ -141,6 +141,14 @@ operator>>(std::istream& in, AttributeEncoding& val)
}
} // namespace pcc
namespace pcc {
static std::istream&
operator>>(std::istream& in, PartitionMethod& val)
{
return readUInt(in, val);
}
} // namespace pcc
namespace pcc {
static std::ostream&
operator<<(std::ostream& out, const AttributeEncoding& val)
......@@ -154,6 +162,17 @@ operator<<(std::ostream& out, const AttributeEncoding& val)
}
} // namespace pcc
namespace pcc {
static std::ostream&
operator<<(std::ostream& out, const PartitionMethod& val)
{
switch (val) {
case PartitionMethod::kNone: out << "0 (None)"; break;
}
return out;
}
} // namespace pcc
namespace df {
namespace program_options_lite {
template<typename T>
......@@ -297,6 +316,11 @@ ParseParameters(int argc, char* argv[], Parameters& params)
params.encoder.gps.geom_unique_points_flag, true,
"Enables removal of duplicated points")
("partitionMethod",
params.encoder.partitionMethod, PartitionMethod::kNone,
"Method used to partition input point cloud into slices/tiles:\n"
" 0: none")
("disableAttributeCoding",
params.disableAttributeCoding, false,
"Ignore attribute coding configuration")
......
......@@ -69,18 +69,21 @@ PCCTMC3Decoder3::decompress(
const PayloadBuffer* buf,
std::function<void(const PCCPointSet3&)> onOutputCloud)
{
// offset completed slice by sliceOrigin
// todo(df): call _accumCloud.clear() at start of frame
// Starting a new geometry brick/slice/tile, transfer any
// finished points to the output accumulator
if (!buf || buf->type == PayloadType::kGeometryBrick) {
if (size_t numPoints = _currentPointCloud.getPointCount()) {
for (size_t i = 0; i < numPoints; i++)
for (int k = 0; k < 3; k++)
_currentPointCloud[i][k] += _sliceOrigin[k];
_accumCloud.append(_currentPointCloud);
}
}
if (!buf) {
// flush decoder, output pending cloud if any
onOutputCloud(_currentPointCloud);
onOutputCloud(_accumCloud);
return 0;
}
......@@ -143,11 +146,7 @@ PCCTMC3Decoder3::storeTileInventory(TileInventory&& inventory)
}
//==========================================================================
// Initialise the point cloud storage and decode a single geometry brick.
//
// NB: there is an implicit assumption that there is only a single geometry
// brick per point cloud.
// todo(??): fixme
// Initialise the point cloud storage and decode a single geometry slice.
int
PCCTMC3Decoder3::decodeGeometryBrick(const PayloadBuffer& buf)
......@@ -212,9 +211,6 @@ PCCTMC3Decoder3::decodeGeometryBrick(const PayloadBuffer& buf)
//--------------------------------------------------------------------------
// Initialise the point cloud storage and decode a single geometry brick.
//
// NB: there is an implicit assumption that there is only a single geometry
// brick per point cloud.
void
PCCTMC3Decoder3::decodeAttributeBrick(const PayloadBuffer& buf)
......
......@@ -43,20 +43,13 @@
#include "geometry.h"
#include "io_hls.h"
#include "osspecific.h"
#include "partitioning.h"
#include "pcc_chrono.h"
namespace pcc {
//============================================================================
void
PCCTMC3Encoder3::init()
{
pointCloud.clear();
}
//----------------------------------------------------------------------------
int
PCCTMC3Encoder3::compress(
const PCCPointSet3& inputPointCloud,
......@@ -64,7 +57,7 @@ PCCTMC3Encoder3::compress(
std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud)
{
init();
fixupParameterSets(params);
// Determine input bounding box (for SPS metadata) if not manually set
if (params->sps.seq_bounding_box_whd == PCCVector3<int>{0}) {
......@@ -83,8 +76,97 @@ PCCTMC3Encoder3::compress(
}
}
// placeholder to "activate" the parameter sets
_sps = &params->sps;
_gps = &params->gps;
_aps.clear();
for (const auto& aps : params->aps) {
_aps.push_back(&aps);
}
// write out all parameter sets prior to encoding
outputFn(write(*_sps));
outputFn(write(*_gps));
for (const auto aps : _aps) {
outputFn(write(*aps));
}
// initial geometry IDs
_tileId = 0;
_sliceId = 0;
// If partitioning is not enabled, encode input as a single "partition"
if (params->partitionMethod == PartitionMethod::kNone) {
// todo(df): params->gps.geom_box_present_flag = false;
_sliceOrigin = PCCVector3<int>{0};
compressPartition(inputPointCloud, params, outputFn, reconstructedCloud);
return 0;
}
// Partition the input point cloud
// - quantise the input point cloud (without duplicate point removal)
// - 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;
PCCBox3<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;
}
} while (0);
if (!partitions.tileInventory.tiles.empty()) {
outputFn(write(partitions.tileInventory));
}
// Encode each partition:
// - create a pointset comprising just the partitioned points
// - 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));
}
_sliceId = partition.sliceId;
_tileId = partition.tileId;
_sliceOrigin = partition.origin;
compressPartition(srcPartition, params, outputFn, reconstructedCloud);
}
return 0;
}
//----------------------------------------------------------------------------
void
PCCTMC3Encoder3::fixupParameterSets(EncoderParams* params)
{
// fixup parameter set IDs
params->sps.sps_seq_parameter_set_id = 0;
params->gps.gps_seq_parameter_set_id = 0;
......@@ -122,28 +204,23 @@ PCCTMC3Encoder3::compress(
attr_sps.attributeLabel = KnownAttributeLabel::kReflectance;
}
}
}
// placeholder to "activate" the parameter sets
_sps = &params->sps;
_gps = &params->gps;
_aps.clear();
for (const auto& aps : params->aps) {
_aps.push_back(&aps);
}
// write out all parameter sets prior to encoding
outputFn(write(*_sps));
outputFn(write(*_gps));
for (const auto aps : _aps) {
outputFn(write(*aps));
}
//----------------------------------------------------------------------------
void
PCCTMC3Encoder3::compressPartition(
const PCCPointSet3& inputPointCloud,
EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud)
{
// geometry compression consists of the following stages:
// - prefilter/quantize geometry (non-normative)
// - encode geometry (single slice, id = 0)
// - recolour
// The quantization process will determine the bounding box
pointCloud.clear();
quantization(inputPointCloud);
// geometry encoding
......@@ -185,6 +262,7 @@ PCCTMC3Encoder3::compress(
}
// dump recoloured point cloud
// todo(df): this needs to work with partitioned clouds
if (!params->postRecolorPath.empty()) {
PCCPointSet3 tempPointCloud(pointCloud);
tempPointCloud.convertYUVToRGB();
......@@ -234,9 +312,7 @@ PCCTMC3Encoder3::compress(
// should be distinguishable from the current slice.
_sliceId++;
reconstructedPointCloud(reconstructedCloud);
return 0;
appendReconstructedPoints(reconstructedCloud);
}
//----------------------------------------------------------------------------
......@@ -254,7 +330,7 @@ PCCTMC3Encoder3::encodeGeometryBrick(PayloadBuffer* buf)
GeometryBrickHeader gbh;
gbh.geom_geom_parameter_set_id = _gps->gps_geom_parameter_set_id;
gbh.geom_slice_id = _sliceId;
gbh.geom_tile_id = _tileId;
gbh.geom_tile_id = std::max(0, _tileId);
gbh.geomBoxOrigin = _sliceOrigin;
gbh.geom_box_log2_scale = 0;
gbh.geom_max_node_size_log2 = nodeSizeLog2;
......@@ -279,34 +355,35 @@ PCCTMC3Encoder3::encodeGeometryBrick(PayloadBuffer* buf)
//----------------------------------------------------------------------------
void
PCCTMC3Encoder3::reconstructedPointCloud(PCCPointSet3* reconstructedCloud)
PCCTMC3Encoder3::appendReconstructedPoints(PCCPointSet3* reconstructedCloud)
{
if (reconstructedCloud == nullptr) {
return;
}
const size_t pointCount = pointCloud.getPointCount();
size_t outIdx = reconstructedCloud->getPointCount();
reconstructedCloud->addRemoveAttributes(
pointCloud.hasColors(), pointCloud.hasReflectances());
reconstructedCloud->resize(pointCount);
reconstructedCloud->resize(outIdx + pointCount);
const double minPositionQuantizationScale = 0.0000000001;
const double invScale =
fabs(_sps->seq_source_geom_scale_factor) > minPositionQuantizationScale
? 1.0 / _sps->seq_source_geom_scale_factor
: 1.0;
for (size_t i = 0; i < pointCount; ++i) {
for (size_t i = 0; i < pointCount; ++i, ++outIdx) {
const auto quantizedPoint = pointCloud[i];
auto& point = (*reconstructedCloud)[i];
auto& point = (*reconstructedCloud)[outIdx];
for (size_t k = 0; k < 3; ++k) {
point[k] = (quantizedPoint[k] + _sliceOrigin[k]) * invScale
+ _sps->seq_bounding_box_xyz0[k];
}
if (pointCloud.hasColors()) {
reconstructedCloud->setColor(i, pointCloud.getColor(i));
reconstructedCloud->setColor(outIdx, pointCloud.getColor(i));
}
if (pointCloud.hasReflectances()) {
reconstructedCloud->setReflectance(i, pointCloud.getReflectance(i));
reconstructedCloud->setReflectance(outIdx, pointCloud.getReflectance(i));
}
}
}
......
/* The copyright in this software is being made available under the BSD
* Licence, included below. This software may be subject to other third
* party and contributor rights, including patent rights, and no such
* rights are granted under this licence.
*
* Copyright (c) 2017-2018, ISO/IEC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the ISO/IEC nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <cstdint>
#include <vector>
#include "PCCMath.h"
#include "PCCPointSet.h"
#include "hls.h"
namespace pcc {
//============================================================================
struct Partition {
// The *_slice_id to encode this partition with
int sliceId;
// The identifier of the tileset that describes the bounding box of all
// slices with the same tile_id. A value of -1 indicates no tile_id
// mapping.
int tileId;
// The value of geom_box_origin for this partition, using the
// translated+scaled co-ordinate system.
PCCVector3<int> origin;
// Point indexes of the source point cloud that form this partition.
std::vector<int32_t> pointIndexes;
};
//----------------------------------------------------------------------------
struct PartitionSet {
TileInventory tileInventory;
std::vector<Partition> slices;
};
//============================================================================
} // namespace pcc
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