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. ...@@ -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 This option instructs the encoder to ignore all options relating to
attribute coding, as if they had never been configured. 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 Geometry coding
--------------- ---------------
......
...@@ -86,6 +86,7 @@ file(GLOB PROJECT_INC_FILES ...@@ -86,6 +86,7 @@ file(GLOB PROJECT_INC_FILES
"io_hls.h" "io_hls.h"
"io_tlv.h" "io_tlv.h"
"osspecific.h" "osspecific.h"
"partitioning.h"
"pcc_chrono.h" "pcc_chrono.h"
"ringbuf.h" "ringbuf.h"
"tables.h" "tables.h"
......
...@@ -96,6 +96,7 @@ private: ...@@ -96,6 +96,7 @@ private:
// The point cloud currently being decoded // The point cloud currently being decoded
PCCPointSet3 _currentPointCloud; PCCPointSet3 _currentPointCloud;
PCCPointSet3 _accumCloud;
// Received parameter sets, mapping parameter set id -> parameterset // Received parameter sets, mapping parameter set id -> parameterset
std::map<int, SequenceParameterSet> _spss; std::map<int, SequenceParameterSet> _spss;
......
...@@ -47,6 +47,16 @@ ...@@ -47,6 +47,16 @@
namespace pcc { namespace pcc {
//============================================================================
enum class PartitionMethod
{
// Don't partition input
kNone = 0,
};
//============================================================================
struct EncoderParams { struct EncoderParams {
SequenceParameterSet sps; SequenceParameterSet sps;
GeometryParameterSet gps; GeometryParameterSet gps;
...@@ -60,27 +70,36 @@ struct EncoderParams { ...@@ -60,27 +70,36 @@ struct EncoderParams {
// Filename for saving recoloured point cloud. // Filename for saving recoloured point cloud.
std::string postRecolorPath; std::string postRecolorPath;
// Method for partitioning the input cloud
PartitionMethod partitionMethod;
}; };
//============================================================================ //============================================================================
class PCCTMC3Encoder3 { class PCCTMC3Encoder3 {
public: public:
PCCTMC3Encoder3() { init(); } PCCTMC3Encoder3() = default;
PCCTMC3Encoder3(const PCCTMC3Encoder3&) = default; PCCTMC3Encoder3(const PCCTMC3Encoder3&) = default;
PCCTMC3Encoder3& operator=(const PCCTMC3Encoder3& rhs) = default; PCCTMC3Encoder3& operator=(const PCCTMC3Encoder3& rhs) = default;
~PCCTMC3Encoder3() = default; ~PCCTMC3Encoder3() = default;
void init();
int compress( int compress(
const PCCPointSet3& inputPointCloud, const PCCPointSet3& inputPointCloud,
EncoderParams* params, EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn, std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud = nullptr); 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: private:
void reconstructedPointCloud(PCCPointSet3* reconstructedCloud); void appendReconstructedPoints(PCCPointSet3* reconstructedCloud);
void encodeGeometryBrick(PayloadBuffer* buf); void encodeGeometryBrick(PayloadBuffer* buf);
......
...@@ -141,6 +141,14 @@ operator>>(std::istream& in, AttributeEncoding& val) ...@@ -141,6 +141,14 @@ operator>>(std::istream& in, AttributeEncoding& val)
} }
} // namespace pcc } // namespace pcc
namespace pcc {
static std::istream&
operator>>(std::istream& in, PartitionMethod& val)
{
return readUInt(in, val);
}
} // namespace pcc
namespace pcc { namespace pcc {
static std::ostream& static std::ostream&
operator<<(std::ostream& out, const AttributeEncoding& val) operator<<(std::ostream& out, const AttributeEncoding& val)
...@@ -154,6 +162,17 @@ operator<<(std::ostream& out, const AttributeEncoding& val) ...@@ -154,6 +162,17 @@ operator<<(std::ostream& out, const AttributeEncoding& val)
} }
} // namespace pcc } // 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 df {
namespace program_options_lite { namespace program_options_lite {
template<typename T> template<typename T>
...@@ -297,6 +316,11 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -297,6 +316,11 @@ ParseParameters(int argc, char* argv[], Parameters& params)
params.encoder.gps.geom_unique_points_flag, true, params.encoder.gps.geom_unique_points_flag, true,
"Enables removal of duplicated points") "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", ("disableAttributeCoding",
params.disableAttributeCoding, false, params.disableAttributeCoding, false,
"Ignore attribute coding configuration") "Ignore attribute coding configuration")
......
...@@ -69,18 +69,21 @@ PCCTMC3Decoder3::decompress( ...@@ -69,18 +69,21 @@ PCCTMC3Decoder3::decompress(
const PayloadBuffer* buf, const PayloadBuffer* buf,
std::function<void(const PCCPointSet3&)> onOutputCloud) 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 (!buf || buf->type == PayloadType::kGeometryBrick) {
if (size_t numPoints = _currentPointCloud.getPointCount()) { if (size_t numPoints = _currentPointCloud.getPointCount()) {
for (size_t i = 0; i < numPoints; i++) for (size_t i = 0; i < numPoints; i++)
for (int k = 0; k < 3; k++) for (int k = 0; k < 3; k++)
_currentPointCloud[i][k] += _sliceOrigin[k]; _currentPointCloud[i][k] += _sliceOrigin[k];
_accumCloud.append(_currentPointCloud);
} }
} }
if (!buf) { if (!buf) {
// flush decoder, output pending cloud if any // flush decoder, output pending cloud if any
onOutputCloud(_currentPointCloud); onOutputCloud(_accumCloud);
return 0; return 0;
} }
...@@ -143,11 +146,7 @@ PCCTMC3Decoder3::storeTileInventory(TileInventory&& inventory) ...@@ -143,11 +146,7 @@ PCCTMC3Decoder3::storeTileInventory(TileInventory&& inventory)
} }
//========================================================================== //==========================================================================
// Initialise the point cloud storage and decode a single geometry brick. // Initialise the point cloud storage and decode a single geometry slice.
//
// NB: there is an implicit assumption that there is only a single geometry
// brick per point cloud.
// todo(??): fixme
int int
PCCTMC3Decoder3::decodeGeometryBrick(const PayloadBuffer& buf) PCCTMC3Decoder3::decodeGeometryBrick(const PayloadBuffer& buf)
...@@ -212,9 +211,6 @@ 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. // 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 void
PCCTMC3Decoder3::decodeAttributeBrick(const PayloadBuffer& buf) PCCTMC3Decoder3::decodeAttributeBrick(const PayloadBuffer& buf)
......
...@@ -43,20 +43,13 @@ ...@@ -43,20 +43,13 @@
#include "geometry.h" #include "geometry.h"
#include "io_hls.h" #include "io_hls.h"
#include "osspecific.h" #include "osspecific.h"
#include "partitioning.h"
#include "pcc_chrono.h" #include "pcc_chrono.h"
namespace pcc { namespace pcc {
//============================================================================ //============================================================================
void
PCCTMC3Encoder3::init()
{
pointCloud.clear();
}
//----------------------------------------------------------------------------
int int
PCCTMC3Encoder3::compress( PCCTMC3Encoder3::compress(
const PCCPointSet3& inputPointCloud, const PCCPointSet3& inputPointCloud,
...@@ -64,7 +57,7 @@ PCCTMC3Encoder3::compress( ...@@ -64,7 +57,7 @@ PCCTMC3Encoder3::compress(
std::function<void(const PayloadBuffer&)> outputFn, std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud) PCCPointSet3* reconstructedCloud)
{ {
init(); fixupParameterSets(params);
// Determine input bounding box (for SPS metadata) if not manually set // Determine input bounding box (for SPS metadata) if not manually set
if (params->sps.seq_bounding_box_whd == PCCVector3<int>{0}) { if (params->sps.seq_bounding_box_whd == PCCVector3<int>{0}) {
...@@ -83,8 +76,97 @@ PCCTMC3Encoder3::compress( ...@@ -83,8 +76,97 @@ PCCTMC3Encoder3::compress(
} }
} }
_sliceOrigin = PCCVector3<int>{0}; // 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 // fixup parameter set IDs
params->sps.sps_seq_parameter_set_id = 0; params->sps.sps_seq_parameter_set_id = 0;
params->gps.gps_seq_parameter_set_id = 0; params->gps.gps_seq_parameter_set_id = 0;
...@@ -122,28 +204,23 @@ PCCTMC3Encoder3::compress( ...@@ -122,28 +204,23 @@ PCCTMC3Encoder3::compress(
attr_sps.attributeLabel = KnownAttributeLabel::kReflectance; 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: // geometry compression consists of the following stages:
// - prefilter/quantize geometry (non-normative) // - prefilter/quantize geometry (non-normative)
// - encode geometry (single slice, id = 0) // - encode geometry (single slice, id = 0)
// - recolour // - recolour
// The quantization process will determine the bounding box pointCloud.clear();
quantization(inputPointCloud); quantization(inputPointCloud);
// geometry encoding // geometry encoding
...@@ -185,6 +262,7 @@ PCCTMC3Encoder3::compress( ...@@ -185,6 +262,7 @@ PCCTMC3Encoder3::compress(
} }
// dump recoloured point cloud // dump recoloured point cloud
// todo(df): this needs to work with partitioned clouds
if (!params->postRecolorPath.empty()) { if (!params->postRecolorPath.empty()) {
PCCPointSet3 tempPointCloud(pointCloud); PCCPointSet3 tempPointCloud(pointCloud);
tempPointCloud.convertYUVToRGB(); tempPointCloud.convertYUVToRGB();
...@@ -234,9 +312,7 @@ PCCTMC3Encoder3::compress( ...@@ -234,9 +312,7 @@ PCCTMC3Encoder3::compress(
// should be distinguishable from the current slice. // should be distinguishable from the current slice.
_sliceId++; _sliceId++;
reconstructedPointCloud(reconstructedCloud); appendReconstructedPoints(reconstructedCloud);
return 0;
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
...@@ -254,7 +330,7 @@ PCCTMC3Encoder3::encodeGeometryBrick(PayloadBuffer* buf) ...@@ -254,7 +330,7 @@ PCCTMC3Encoder3::encodeGeometryBrick(PayloadBuffer* buf)
GeometryBrickHeader gbh; GeometryBrickHeader gbh;
gbh.geom_geom_parameter_set_id = _gps->gps_geom_parameter_set_id; gbh.geom_geom_parameter_set_id = _gps->gps_geom_parameter_set_id;
gbh.geom_slice_id = _sliceId; gbh.geom_slice_id = _sliceId;
gbh.geom_tile_id = _tileId; gbh.geom_tile_id = std::max(0, _tileId);
gbh.geomBoxOrigin = _sliceOrigin; gbh.geomBoxOrigin = _sliceOrigin;
gbh.geom_box_log2_scale = 0; gbh.geom_box_log2_scale = 0;
gbh.geom_max_node_size_log2 = nodeSizeLog2; gbh.geom_max_node_size_log2 = nodeSizeLog2;
...@@ -279,34 +355,35 @@ PCCTMC3Encoder3::encodeGeometryBrick(PayloadBuffer* buf) ...@@ -279,34 +355,35 @@ PCCTMC3Encoder3::encodeGeometryBrick(PayloadBuffer* buf)
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void void
PCCTMC3Encoder3::reconstructedPointCloud(PCCPointSet3* reconstructedCloud) PCCTMC3Encoder3::appendReconstructedPoints(PCCPointSet3* reconstructedCloud)
{ {
if (reconstructedCloud == nullptr) { if (reconstructedCloud == nullptr) {
return; return;
} }
const size_t pointCount = pointCloud.getPointCount(); const size_t pointCount = pointCloud.getPointCount();
size_t outIdx = reconstructedCloud->getPointCount();
reconstructedCloud->addRemoveAttributes( reconstructedCloud->addRemoveAttributes(
pointCloud.hasColors(), pointCloud.hasReflectances()); pointCloud.hasColors(), pointCloud.hasReflectances());
reconstructedCloud->resize(pointCount); reconstructedCloud->resize(outIdx + pointCount);
const double minPositionQuantizationScale = 0.0000000001; const double minPositionQuantizationScale = 0.0000000001;
const double invScale = const double invScale =
fabs(_sps->seq_source_geom_scale_factor) > minPositionQuantizationScale fabs(_sps->seq_source_geom_scale_factor) > minPositionQuantizationScale
? 1.0 / _sps->seq_source_geom_scale_factor ? 1.0 / _sps->seq_source_geom_scale_factor
: 1.0; : 1.0;
for (size_t i = 0; i < pointCount; ++i) { for (size_t i = 0; i < pointCount; ++i, ++outIdx) {
const auto quantizedPoint = pointCloud[i]; const auto quantizedPoint = pointCloud[i];
auto& point = (*reconstructedCloud)[i]; auto& point = (*reconstructedCloud)[outIdx];
for (size_t k = 0; k < 3; ++k) { for (size_t k = 0; k < 3; ++k) {
point[k] = (quantizedPoint[k] + _sliceOrigin[k]) * invScale point[k] = (quantizedPoint[k] + _sliceOrigin[k]) * invScale
+ _sps->seq_bounding_box_xyz0[k]; + _sps->seq_bounding_box_xyz0[k];
} }
if (pointCloud.hasColors()) { if (pointCloud.hasColors()) {
reconstructedCloud->setColor(i, pointCloud.getColor(i)); reconstructedCloud->setColor(outIdx, pointCloud.getColor(i));
} }
if (pointCloud.hasReflectances()) { 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;