Commit ef9b47b9 authored by David Flynn's avatar David Flynn Committed by David Flynn
Browse files

cli: add support for encoding pointcloud sequences

This commit enables the codec to encapsulate more than one frame in the
coded bitstream.  The number of frames to encode is controlled by the
frameCount option.

Support is added to encode multiple source frames from separate ply
files based on a template input file name.  A printf-like "%d" directive
acts as a placeholder for the current frame number.

Decoding functions in a similar way with a each frame decoded from the
bitstream being written to a separate ply file named according to a
template pattern.

The first frame number used for file I/O may be set using the
firstFrameNum option.
parent 105120bd
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
## Running ## Running
This TMC13 codec implementation encodes single frames. A single binary This TMC13 codec implementation encodes frame sequences. A single binary
contains the encoder and decoder implementation, with selection using contains the encoder and decoder implementation, with selection using
the `--mode` option. Documentation of options is provided via the the `--mode` option. Documentation of options is provided via the
`--help` command line option. `--help` command line option.
......
...@@ -16,9 +16,30 @@ encoding functionality. A value of 1 switches to decoding mode. ...@@ -16,9 +16,30 @@ encoding functionality. A value of 1 switches to decoding mode.
I/O parameters I/O parameters
-------------- --------------
### `--firstFrameNum=INT-VALUE`
The initial frame number of the input or output sequence.
The software replaces any instance of a '%d' printf format directive
with the current frame number when evaluating the following options:
- uncompressedDataPath
- reconstructedDataPath
- postRecolourPath
- preInvScalePath
NB: When decoding, this option relates only to the output file names.
In order to have the decoder produce identically numbered output ply
files as the encoder input, specify the same value of firstFrameNum for
the decoder.
### `--frameCount=INT-VALUE`
(Encoder only)
The number of frames to be encoded.
### `--uncompressedDataPath=FILE` ### `--uncompressedDataPath=FILE`
(Encoder only) (Encoder only)
The input source point cloud to be compressed. The input source point cloud to be compressed. The first instance of
'%d' in FILE will be expanded with the current frame number.
### `--compressedStreamPath=FILE` ### `--compressedStreamPath=FILE`
The compressed bitstream file output when encoding or input when decoding. The compressed bitstream file output when encoding or input when decoding.
...@@ -28,6 +49,9 @@ The reconstructed point cloud file. When encoding, the output is the ...@@ -28,6 +49,9 @@ The reconstructed point cloud file. When encoding, the output is the
locally decoded picture. It is expected that the reconstructed output locally decoded picture. It is expected that the reconstructed output
of the encoder and decoder match exactly. of the encoder and decoder match exactly.
The first instance of '%d' in FILE will be expanded with the current
frame number.
### `--postRecolourPath=FILE` ### `--postRecolourPath=FILE`
(Encoder only) (Encoder only)
As part of the encoding process, it may be necessary to re-colour the As part of the encoding process, it may be necessary to re-colour the
...@@ -35,6 +59,9 @@ point cloud if the point geometry is altered. This diagnostic output ...@@ -35,6 +59,9 @@ point cloud if the point geometry is altered. This diagnostic output
file corresponds to the re-coloured point cloud prior to attribute file corresponds to the re-coloured point cloud prior to attribute
coding without output geometry scaling. coding without output geometry scaling.
The first instance of '%d' in FILE will be expanded with the current
frame number.
### `--preInvScalePath=FILE` ### `--preInvScalePath=FILE`
(Decoder only) (Decoder only)
This diagnostic output corresponds to the decoded point cloud (geometry This diagnostic output corresponds to the decoded point cloud (geometry
...@@ -44,6 +71,9 @@ When compared to the output of `postRecolourPath`, the performance of ...@@ -44,6 +71,9 @@ When compared to the output of `postRecolourPath`, the performance of
attribute coding may be directly measured without being confounded attribute coding may be directly measured without being confounded
by any geometry losses. by any geometry losses.
The first instance of '%d' in FILE will be expanded with the current
frame number.
### `--outputBinaryPly=0|1` ### `--outputBinaryPly=0|1`
Sets the output format of PLY files (Binary=1, ASCII=0). Reading and Sets the output format of PLY files (Binary=1, ASCII=0). Reading and
writing binary PLY files is more efficient than the ASCII variant, writing binary PLY files is more efficient than the ASCII variant,
......
...@@ -5,16 +5,16 @@ Using the codec ...@@ -5,16 +5,16 @@ Using the codec
./tmc3 [--help] [-c config.cfg] [--parameter=value] ./tmc3 [--help] [-c config.cfg] [--parameter=value]
``` ```
The encoder takes as input a PLY file describing a point cloud with The encoder takes as input one or more PLY files describing a point
integer positions and, optionally, per-point integer colour and cloud sequence with integer positions and, optionally, per-point integer
reflectance attributes. colour and reflectance attributes.
The output of the encoder is a binary bitstream encapsulated using the The output of the encoder is a binary bitstream encapsulated using the
G-PCC annex-B format. G-PCC annex-B format.
Conversely, the decoder takes as input a compressed bitstream file in Conversely, the decoder takes as input a compressed bitstream file in
G-PCC annex-B format and produces a reconstructed PLY file with position G-PCC annex-B format and produces one or more reconstructed PLY file
and any present attribute values. with position and any present attribute values.
The software may be configured using either command line arguments or from The software may be configured using either command line arguments or from
a configuration file specified using the `-c|--config=` option. a configuration file specified using the `-c|--config=` option.
...@@ -26,4 +26,4 @@ rate point configuration files for a variety of common test conditions. ...@@ -26,4 +26,4 @@ rate point configuration files for a variety of common test conditions.
Parameters are set by the last value encountered on the command line. Parameters are set by the last value encountered on the command line.
Therefore if a setting is set via a configuration file, and then a Therefore if a setting is set via a configuration file, and then a
subsequent command line parameter changes that same setting, the command subsequent command line parameter changes that same setting, the command
line parameter value will be used. line parameter value will be used.
\ No newline at end of file
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#include <cstdint> #include <cstdint>
#include <iterator> #include <iterator>
#include <utility> #include <utility>
#include <string>
#include <vector> #include <vector>
#if _MSC_VER #if _MSC_VER
...@@ -74,7 +75,6 @@ PCCSystemEndianness() ...@@ -74,7 +75,6 @@ PCCSystemEndianness()
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Replace any occurence of %d with formatted number. The %d format // Replace any occurence of %d with formatted number. The %d format
// specifier may use the formatting conventions of snprintf(). // specifier may use the formatting conventions of snprintf().
std::string expandNum(const std::string& src, int num); std::string expandNum(const std::string& src, int num);
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
......
...@@ -58,6 +58,8 @@ struct DecoderParams { ...@@ -58,6 +58,8 @@ struct DecoderParams {
class PCCTMC3Decoder3 { class PCCTMC3Decoder3 {
public: public:
class Callbacks;
PCCTMC3Decoder3(const DecoderParams& params) : _params(params) { init(); } PCCTMC3Decoder3(const DecoderParams& params) : _params(params) { init(); }
PCCTMC3Decoder3(const PCCTMC3Decoder3&) = default; PCCTMC3Decoder3(const PCCTMC3Decoder3&) = default;
...@@ -66,9 +68,7 @@ public: ...@@ -66,9 +68,7 @@ public:
void init(); void init();
int decompress( int decompress(const PayloadBuffer* buf, Callbacks* callback);
const PayloadBuffer* buf,
std::function<void(const PCCPointSet3&)> onOutputCloud);
//========================================================================== //==========================================================================
...@@ -124,6 +124,13 @@ private: ...@@ -124,6 +124,13 @@ private:
GeometryBrickHeader _gbh; GeometryBrickHeader _gbh;
}; };
//----------------------------------------------------------------------------
class PCCTMC3Decoder3::Callbacks {
public:
virtual void onOutputCloud(const PCCPointSet3&) = 0;
};
//============================================================================ //============================================================================
} // namespace pcc } // namespace pcc
...@@ -72,9 +72,6 @@ struct EncoderParams { ...@@ -72,9 +72,6 @@ struct EncoderParams {
// todo(df): this should go away // todo(df): this should go away
std::map<std::string, int> attributeIdxMap; std::map<std::string, int> attributeIdxMap;
// Filename for saving recoloured point cloud.
std::string postRecolorPath;
// Parameters that control partitioning // Parameters that control partitioning
PartitionParams partition; PartitionParams partition;
...@@ -86,6 +83,8 @@ struct EncoderParams { ...@@ -86,6 +83,8 @@ struct EncoderParams {
class PCCTMC3Encoder3 { class PCCTMC3Encoder3 {
public: public:
class Callbacks;
PCCTMC3Encoder3(); PCCTMC3Encoder3();
PCCTMC3Encoder3(const PCCTMC3Encoder3&) = default; PCCTMC3Encoder3(const PCCTMC3Encoder3&) = default;
PCCTMC3Encoder3& operator=(const PCCTMC3Encoder3& rhs) = default; PCCTMC3Encoder3& operator=(const PCCTMC3Encoder3& rhs) = default;
...@@ -94,14 +93,14 @@ public: ...@@ -94,14 +93,14 @@ public:
int compress( int compress(
const PCCPointSet3& inputPointCloud, const PCCPointSet3& inputPointCloud,
EncoderParams* params, EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn, Callbacks*,
PCCPointSet3* reconstructedCloud = nullptr); PCCPointSet3* reconstructedCloud = nullptr);
void compressPartition( void compressPartition(
const PCCPointSet3& inputPointCloud, const PCCPointSet3& inputPointCloud,
const PCCPointSet3& originPartCloud, const PCCPointSet3& originPartCloud,
EncoderParams* params, EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn, Callbacks*,
PCCPointSet3* reconstructedCloud = nullptr); PCCPointSet3* reconstructedCloud = nullptr);
static void fixupParameterSets(EncoderParams* params); static void fixupParameterSets(EncoderParams* params);
...@@ -146,6 +145,14 @@ private: ...@@ -146,6 +145,14 @@ private:
std::multimap<Vec3<double>, int32_t> quantizedToOrigin; std::multimap<Vec3<double>, int32_t> quantizedToOrigin;
}; };
//----------------------------------------------------------------------------
class PCCTMC3Encoder3::Callbacks {
public:
virtual void onOutputBuffer(const PayloadBuffer&) = 0;
virtual void onPostRecolour(const PCCPointSet3&) = 0;
};
//============================================================================ //============================================================================
} // namespace pcc } // namespace pcc
...@@ -61,11 +61,20 @@ struct Parameters { ...@@ -61,11 +61,20 @@ struct Parameters {
// when true, configure the encoder as if no attributes are specified // when true, configure the encoder as if no attributes are specified
bool disableAttributeCoding; bool disableAttributeCoding;
// Frame number of first file in input sequence.
int firstFrameNum;
// Number of frames to process.
int frameCount;
std::string uncompressedDataPath; std::string uncompressedDataPath;
std::string compressedStreamPath; std::string compressedStreamPath;
std::string reconstructedDataPath; std::string reconstructedDataPath;
// Filename for saving pre inverse scaled point cloud. // Filename for saving recoloured point cloud (encoder).
std::string postRecolorPath;
// Filename for saving pre inverse scaled point cloud (decoder).
std::string preInvScalePath; std::string preInvScalePath;
pcc::EncoderParams encoder; pcc::EncoderParams encoder;
...@@ -78,6 +87,52 @@ struct Parameters { ...@@ -78,6 +87,52 @@ struct Parameters {
int reflectanceScale; int reflectanceScale;
}; };
//----------------------------------------------------------------------------
class SequenceEncoder : public PCCTMC3Encoder3::Callbacks {
public:
// NB: params must outlive the lifetime of the decoder.
SequenceEncoder(Parameters* params);
int compress(Stopwatch* clock);
protected:
int compressOneFrame(Stopwatch* clock);
void onOutputBuffer(const PayloadBuffer& buf) override;
void onPostRecolour(const PCCPointSet3& cloud) override;
private:
Parameters* params;
PCCTMC3Encoder3 encoder;
std::ofstream bytestreamFile;
int frameNum;
};
//----------------------------------------------------------------------------
class SequenceDecoder : public PCCTMC3Decoder3::Callbacks {
public:
// NB: params must outlive the lifetime of the decoder.
SequenceDecoder(const Parameters* params);
int decompress(Stopwatch* clock);
protected:
void onOutputCloud(const PCCPointSet3& decodedPointCloud) override;
private:
const Parameters* params;
PCCTMC3Decoder3 decoder;
std::ofstream bytestreamFile;
int frameNum;
Stopwatch* clock;
};
//============================================================================ //============================================================================
int int
...@@ -98,9 +153,9 @@ main(int argc, char* argv[]) ...@@ -98,9 +153,9 @@ main(int argc, char* argv[])
int ret = 0; int ret = 0;
if (params.isDecoder) { if (params.isDecoder) {
ret = Decompress(params, clock_user); ret = SequenceDecoder(&params).decompress(&clock_user);
} else { } else {
ret = Compress(params, clock_user); ret = SequenceEncoder(&params).compress(&clock_user);
} }
clock_wall.stop(); clock_wall.stop();
...@@ -258,6 +313,15 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -258,6 +313,15 @@ ParseParameters(int argc, char* argv[], Parameters& params)
" 1: decode") " 1: decode")
// i/o parameters // i/o parameters
("firstFrameNum",
params.firstFrameNum, 0,
"Frame number for use with interpolating %d format specifiers"
"in input/output filenames")
("frameCount",
params.frameCount, 1,
"Number of frames to encode")
("reconstructedDataPath", ("reconstructedDataPath",
params.reconstructedDataPath, {}, params.reconstructedDataPath, {},
"The ouput reconstructed pointcloud file path (decoder only)") "The ouput reconstructed pointcloud file path (decoder only)")
...@@ -271,7 +335,7 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -271,7 +335,7 @@ ParseParameters(int argc, char* argv[], Parameters& params)
"The compressed bitstream path (encoder=output, decoder=input)") "The compressed bitstream path (encoder=output, decoder=input)")
("postRecolorPath", ("postRecolorPath",
params.encoder.postRecolorPath, {}, params.postRecolorPath, {},
"Recolored pointcloud file path (encoder only)") "Recolored pointcloud file path (encoder only)")
("preInvScalePath", ("preInvScalePath",
...@@ -779,101 +843,166 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -779,101 +843,166 @@ ParseParameters(int argc, char* argv[], Parameters& params)
return true; return true;
} }
//============================================================================
SequenceEncoder::SequenceEncoder(Parameters* params) : params(params)
{}
//----------------------------------------------------------------------------
int
SequenceEncoder::compress(Stopwatch* clock)
{
bytestreamFile.open(params->compressedStreamPath, ios::binary);
if (!bytestreamFile.is_open()) {
return -1;
}
const int lastFrameNum = params->firstFrameNum + params->frameCount;
for (frameNum = params->firstFrameNum; frameNum < lastFrameNum; frameNum++) {
if (compressOneFrame(clock))
return -1;
}
std::cout << "Total bitstream size " << bytestreamFile.tellp() << " B\n";
bytestreamFile.close();
return 0;
}
//----------------------------------------------------------------------------
int int
Compress(Parameters& params, Stopwatch& clock) SequenceEncoder::compressOneFrame(Stopwatch* clock)
{ {
std::string srcName{expandNum(params->uncompressedDataPath, frameNum)};
PCCPointSet3 pointCloud; PCCPointSet3 pointCloud;
if ( if (!pointCloud.read(srcName) || pointCloud.getPointCount() == 0) {
!pointCloud.read(params.uncompressedDataPath)
|| pointCloud.getPointCount() == 0) {
cout << "Error: can't open input file!" << endl; cout << "Error: can't open input file!" << endl;
return -1; return -1;
} }
// Sanitise the input point cloud // Sanitise the input point cloud
// todo(df): remove the following with generic handling of properties // todo(df): remove the following with generic handling of properties
bool codeColour = params.encoder.attributeIdxMap.count("color"); bool codeColour = params->encoder.attributeIdxMap.count("color");
if (!codeColour) if (!codeColour)
pointCloud.removeColors(); pointCloud.removeColors();
assert(codeColour == pointCloud.hasColors()); assert(codeColour == pointCloud.hasColors());
bool codeReflectance = params.encoder.attributeIdxMap.count("reflectance"); bool codeReflectance = params->encoder.attributeIdxMap.count("reflectance");
if (!codeReflectance) if (!codeReflectance)
pointCloud.removeReflectances(); pointCloud.removeReflectances();
assert(codeReflectance == pointCloud.hasReflectances()); assert(codeReflectance == pointCloud.hasReflectances());
ofstream fout(params.compressedStreamPath, ios::binary); clock->start();
if (!fout.is_open()) {
return -1;
}
clock.start();
if (params.colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) { if (params->colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
pointCloud.convertRGBToYUV(); pointCloud.convertRGBToYUV();
} }
if (params.reflectanceScale > 1 && pointCloud.hasReflectances()) { if (params->reflectanceScale > 1 && pointCloud.hasReflectances()) {
const auto pointCount = pointCloud.getPointCount(); const auto pointCount = pointCloud.getPointCount();
for (size_t i = 0; i < pointCount; ++i) { for (size_t i = 0; i < pointCount; ++i) {
int val = pointCloud.getReflectance(i) / params.reflectanceScale; int val = pointCloud.getReflectance(i) / params->reflectanceScale;
pointCloud.setReflectance(i, val); pointCloud.setReflectance(i, val);
} }
} }
PCCTMC3Encoder3 encoder;
// The reconstructed point cloud // The reconstructed point cloud
std::unique_ptr<PCCPointSet3> reconPointCloud; std::unique_ptr<PCCPointSet3> reconPointCloud;
if (!params.reconstructedDataPath.empty()) { if (!params->reconstructedDataPath.empty()) {
reconPointCloud.reset(new PCCPointSet3); reconPointCloud.reset(new PCCPointSet3);
} }
auto bytestreamLenFrameStart = bytestreamFile.tellp();
int ret = encoder.compress( int ret = encoder.compress(
pointCloud, &params.encoder, pointCloud, &params->encoder, this, reconPointCloud.get());
[&](const PayloadBuffer& buf) { writeTlv(buf, fout); },
reconPointCloud.get());
if (ret) { if (ret) {
cout << "Error: can't compress point cloud!" << endl; cout << "Error: can't compress point cloud!" << endl;
return -1; return -1;
} }
std::cout << "Total bitstream size " << fout.tellp() << " B" << std::endl; auto bytestreamLenFrameEnd = bytestreamFile.tellp();
fout.close(); int frameLen = bytestreamLenFrameEnd - bytestreamLenFrameStart;
clock.stop(); std::cout << "Total frame size " << frameLen << " B" << std::endl;
if (!params.reconstructedDataPath.empty()) { clock->stop();
if (params.colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
if (!params->reconstructedDataPath.empty()) {
if (params->colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
reconPointCloud->convertYUVToRGB(); reconPointCloud->convertYUVToRGB();
} }
if (params.reflectanceScale > 1 && reconPointCloud->hasReflectances()) { if (params->reflectanceScale > 1 && reconPointCloud->hasReflectances()) {
const auto pointCount = reconPointCloud->getPointCount(); const auto pointCount = reconPointCloud->getPointCount();
for (size_t i = 0; i < pointCount; ++i) { for (size_t i = 0; i < pointCount; ++i) {
int val = reconPointCloud->getReflectance(i) * params.reflectanceScale; int val =
reconPointCloud->getReflectance(i) * params->reflectanceScale;
reconPointCloud->setReflectance(i, val); reconPointCloud->setReflectance(i, val);
} }
} }
reconPointCloud->write( std::string recName{expandNum(params->reconstructedDataPath, frameNum)};
params.reconstructedDataPath, !params.outputBinaryPly); reconPointCloud->write(recName, !params->outputBinaryPly);
} }
return 0; return 0;
} }
//----------------------------------------------------------------------------
void
SequenceEncoder::onOutputBuffer(const PayloadBuffer& buf)
{
writeTlv(buf, bytestreamFile);
}
//----------------------------------------------------------------------------
void
SequenceEncoder::onPostRecolour(const PCCPointSet3& cloud)
{
if (params->postRecolorPath.empty()) {
return;
}
std::string plyName{expandNum(params->postRecolorPath, frameNum)};
// todo(df): stop the clock
if (params->colorTransform != COLOR_TRANSFORM_RGB_TO_YCBCR) {
cloud.write(plyName);
return;
}
PCCPointSet3 tmpCloud(cloud);