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

attr: refactor colourspace conversion to support other formats

This commit enables signalling a colour matrix index using
cicp_matrix_coefficients_idx (ISO/IEC 23001-8 codec independent code
points).

It adds the following options that replace the existing colorTransform
option:

 - colourMatrix: The coded representation according to ISO/IEC 23001-8.

 - convertPlyColourspace: Enables conversion to/from RGB using the
   indicated matrix.

Configuration files are updated to use the new option and to
signal the identity matrix in the case of direct GBR coding.
parent f5e788ec
...@@ -23,7 +23,7 @@ categories: ...@@ -23,7 +23,7 @@ categories:
# - automatically derive dist2 based on single initial value by the encoder: # - automatically derive dist2 based on single initial value by the encoder:
# - the initial dist2 is scaled by positionQuantisationScale # - the initial dist2 is scaled by positionQuantisationScale
# - generates dist2 per lod # - generates dist2 per lod
- colorTransform: 1 - convertPlyColourspace: 1
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
...@@ -71,7 +71,7 @@ categories: ...@@ -71,7 +71,7 @@ categories:
decflags: decflags:
- mode: 1 - mode: 1
- colorTransform: 1 - convertPlyColourspace: 1
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
......
...@@ -32,7 +32,7 @@ categories: ...@@ -32,7 +32,7 @@ categories:
# - generates dist2 per lod # - generates dist2 per lod
- -
- !conditional 'defined ${seq_lod} && defined ${seq_dist2}' - !conditional 'defined ${seq_lod} && defined ${seq_dist2}'
- colorTransform: 1 - convertPlyColourspace: 1
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
...@@ -80,7 +80,7 @@ categories: ...@@ -80,7 +80,7 @@ categories:
decflags: decflags:
- mode: 1 - mode: 1
- colorTransform: 1 - convertPlyColourspace: 1
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
......
...@@ -29,7 +29,7 @@ categories: ...@@ -29,7 +29,7 @@ categories:
# - generates dist2 per lod # - generates dist2 per lod
- -
- !conditional 'defined ${seq_lod} && defined ${seq_dist2}' - !conditional 'defined ${seq_lod} && defined ${seq_dist2}'
- colorTransform: 0 - convertPlyColourspace: 0
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
...@@ -62,11 +62,12 @@ categories: ...@@ -62,11 +62,12 @@ categories:
- qp: 4 - qp: 4
- qpChromaOffset: 0 - qpChromaOffset: 0
- bitdepth: 8 - bitdepth: 8
- colourMatrix: 0
- attribute: color - attribute: color
decflags: decflags:
- mode: 1 - mode: 1
- colorTransform: 0 - convertPlyColourspace: 0
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
......
...@@ -19,13 +19,13 @@ categories: ...@@ -19,13 +19,13 @@ categories:
#### ####
# attribute coding (common options -- relies on option ordering) # attribute coding (common options -- relies on option ordering)
# - avoid measuring loss of colourspace conversion # - code directly GBR (no need to perform colourspace conversion)
# - scale 16bit reflectance data to 8bit # - scale 16bit reflectance data to 8bit
# - use predicting transform for lossless conditions # - use predicting transform for lossless conditions
# - automatically derive dist2 based on single initial value by the encoder: # - automatically derive dist2 based on single initial value by the encoder:
# - the initial dist2 is scaled by positionQuantisationScale # - the initial dist2 is scaled by positionQuantisationScale
# - generates dist2 per lod # - generates dist2 per lod
- colorTransform: 0 - convertPlyColourspace: 0
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
...@@ -68,11 +68,12 @@ categories: ...@@ -68,11 +68,12 @@ categories:
r05: 34 r05: 34
- qpChromaOffset: 0 - qpChromaOffset: 0
- bitdepth: 8 - bitdepth: 8
- colourMatrix: 0
- attribute: color - attribute: color
decflags: decflags:
- mode: 1 - mode: 1
- colorTransform: 0 - convertPlyColourspace: 0
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
......
...@@ -20,7 +20,7 @@ categories: ...@@ -20,7 +20,7 @@ categories:
# attribute coding (common options -- relies on option ordering) # attribute coding (common options -- relies on option ordering)
# - uses raht transform # - uses raht transform
# - scale 16bit reflectance data to 8bit # - scale 16bit reflectance data to 8bit
- colorTransform: 1 - convertPlyColourspace: 1
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
...@@ -58,7 +58,7 @@ categories: ...@@ -58,7 +58,7 @@ categories:
decflags: decflags:
- mode: 1 - mode: 1
- colorTransform: 1 - convertPlyColourspace: 1
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
......
...@@ -25,7 +25,7 @@ categories: ...@@ -25,7 +25,7 @@ categories:
# attribute coding (common options -- relies on option ordering) # attribute coding (common options -- relies on option ordering)
# - use raht # - use raht
# - scale 16bit reflectance data to 8bit # - scale 16bit reflectance data to 8bit
- colorTransform: 1 - convertPlyColourspace: 1
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
...@@ -63,7 +63,7 @@ categories: ...@@ -63,7 +63,7 @@ categories:
decflags: decflags:
- mode: 1 - mode: 1
- colorTransform: 1 - convertPlyColourspace: 1
- -
- !conditional '${reflectance8b16b_scale_factor}' - !conditional '${reflectance8b16b_scale_factor}'
- hack.reflectanceScale: ${reflectance8b16b_scale_factor} - hack.reflectanceScale: ${reflectance8b16b_scale_factor}
......
...@@ -28,7 +28,7 @@ categories: ...@@ -28,7 +28,7 @@ categories:
# - automatically derive dist2 based on single initial value by the encoder: # - automatically derive dist2 based on single initial value by the encoder:
# - the initial dist2 is scaled by positionQuantisationScale # - the initial dist2 is scaled by positionQuantisationScale
# - generates dist2 per lod # - generates dist2 per lod
- colorTransform: 1 - convertPlyColourspace: 1
- transformType: 2 - transformType: 2
- numberOfNearestNeighborsInPrediction: 3 - numberOfNearestNeighborsInPrediction: 3
- levelOfDetailCount: ${seq_lod} - levelOfDetailCount: ${seq_lod}
...@@ -58,7 +58,7 @@ categories: ...@@ -58,7 +58,7 @@ categories:
decflags: decflags:
- mode: 1 - mode: 1
- colorTransform: 1 - convertPlyColourspace: 1
pcerrorflags: pcerrorflags:
- dropdups: 2 - dropdups: 2
......
...@@ -25,7 +25,7 @@ categories: ...@@ -25,7 +25,7 @@ categories:
#### ####
# attribute coding (common options -- relies on option ordering) # attribute coding (common options -- relies on option ordering)
# - use raht # - use raht
- colorTransform: 1 - convertPlyColourspace: 1
- transformType: 1 - transformType: 1
## ##
...@@ -46,7 +46,7 @@ categories: ...@@ -46,7 +46,7 @@ categories:
decflags: decflags:
- mode: 1 - mode: 1
- colorTransform: 1 - convertPlyColourspace: 1
pcerrorflags: pcerrorflags:
- dropdups: 2 - dropdups: 2
......
...@@ -83,14 +83,13 @@ If outputting non-integer point co-ordinates (eg, due to the output ...@@ -83,14 +83,13 @@ If outputting non-integer point co-ordinates (eg, due to the output
geometry scaling), the precision of the binary and ASCII versions are geometry scaling), the precision of the binary and ASCII versions are
not identical. not identical.
### `--colourTransform=0|1` ### `--convertPlyColourspace=0|1`
Controls the use of a colour space transformation before attribute Controls the conversion of ply RGB colour attributes to/from the
coding and after decoding. colourspace set by an attribute's `colourMatrix` before attribute
coding and after decoding. When disabled (0), or if there is no
| Value | Description | converter available for the requested `colourMatrix`, no conversion
|:-----:| ---------------------- | happens; however the `colourMatrix` value is still written to the
| 0 | none | bitstream.
| 1 | RGB to YCbCr (Rec.709) |
### `--hack.reflectanceScale=0|1` ### `--hack.reflectanceScale=0|1`
Some input data uses 8-bit reflectance data scaled by 255 and represented Some input data uses 8-bit reflectance data scaled by 255 and represented
...@@ -286,6 +285,28 @@ Saves the current attribute configuration for coding the named attribute. ...@@ -286,6 +285,28 @@ Saves the current attribute configuration for coding the named attribute.
This option must be specified after the options corresponding to This option must be specified after the options corresponding to
the attribute. the attribute.
### `--colourMatrix=INT-VALUE`
Indicates the colourspace of the coded attribute values according to
the ISO/IEC 23001-8 Codec Independent Code Points for ColourMatrix.
When used in conjunction with `convertPlyColourspace=1`, a colourspace
conversion will be performed at the input/output of the encoder and
decoder if supported.
| Value | RGB converter | Description |
|:-----:|:-------------:|------------------------------------------ |
| 0 | n/a | Direct coding (eg, RGB, XYZ) |
| 1 | Yes | YCbCr ITU-R BT.709 |
| 2 | n/a | Unspecified |
| 3 | n/a | Reserved |
| 4 | No | USA Title 47 CFR 73.682 (a)(20) |
| 5 | No | YCbCr ITU-R BT.601 |
| 6 | No | YCbCr SMPTE 170M |
| 7 | No | YCbCr SMPTE 240M |
| 8 | No | YCgCo / YCgCoR |
| 9 | No | YCbCr ITU-R BT.2020 |
| 10 | No | YCbCr ITU-R BT.2020 (constant luminance) |
| 11 | No | YDzDx SMPTE ST 2085 |
### `--bitdepth=INT-VALUE` ### `--bitdepth=INT-VALUE`
The bitdepth of the attribute data. NB, this is not necessarily the The bitdepth of the attribute data. NB, this is not necessarily the
same as the bitdepth of the PLY property. same as the bitdepth of the PLY property.
......
...@@ -82,8 +82,8 @@ struct Parameters { ...@@ -82,8 +82,8 @@ struct Parameters {
pcc::EncoderParams encoder; pcc::EncoderParams encoder;
pcc::DecoderParams decoder; pcc::DecoderParams decoder;
// todo(df): this should be per-attribute // perform attribute colourspace conversion on ply input/output.
ColorTransform colorTransform; bool convertColourspace;
// todo(df): this should be per-attribute // todo(df): this should be per-attribute
int reflectanceScale; int reflectanceScale;
...@@ -141,6 +141,11 @@ private: ...@@ -141,6 +141,11 @@ private:
//============================================================================ //============================================================================
void convertToGbr(const SequenceParameterSet& sps, PCCPointSet3& cloud);
void convertFromGbr(const SequenceParameterSet& sps, PCCPointSet3& cloud);
//============================================================================
int int
main(int argc, char* argv[]) main(int argc, char* argv[])
{ {
...@@ -203,17 +208,19 @@ readUInt(std::istream& in, T& val) ...@@ -203,17 +208,19 @@ readUInt(std::istream& in, T& val)
namespace pcc { namespace pcc {
static std::istream& static std::istream&
operator>>(std::istream& in, AxisOrder& val) operator>>(std::istream& in, ColourMatrix& val)
{ {
return readUInt(in, val); return readUInt(in, val);
} }
} // namespace pcc } // namespace pcc
namespace pcc {
static std::istream& static std::istream&
operator>>(std::istream& in, ColorTransform& val) operator>>(std::istream& in, AxisOrder& val)
{ {
return readUInt(in, val); return readUInt(in, val);
} }
} // namespace pcc
namespace pcc { namespace pcc {
static std::istream& static std::istream&
...@@ -231,6 +238,31 @@ operator>>(std::istream& in, PartitionMethod& val) ...@@ -231,6 +238,31 @@ operator>>(std::istream& in, PartitionMethod& val)
} }
} // namespace pcc } // namespace pcc
namespace pcc {
static std::ostream&
operator<<(std::ostream& out, const ColourMatrix& val)
{
switch (val) {
case ColourMatrix::kIdentity: out << "0 (Identity)"; break;
case ColourMatrix::kBt709: out << "1 (Bt709)"; break;
case ColourMatrix::kUnspecified: out << "2 (Unspecified)"; break;
case ColourMatrix::kReserved_3: out << "3 (Reserved)"; break;
case ColourMatrix::kUsa47Cfr73dot682a20:
out << "4 (Usa47Cfr73dot682a20)";
break;
case ColourMatrix::kBt601: out << "5 (Bt601)"; break;
case ColourMatrix::kSmpte170M: out << "6 (Smpte170M)"; break;
case ColourMatrix::kSmpte240M: out << "7 (Smpte240M)"; break;
case ColourMatrix::kYCgCo: out << "8 (kYCgCo)"; break;
case ColourMatrix::kBt2020Ncl: out << "9 (Bt2020Ncl)"; break;
case ColourMatrix::kBt2020Cl: out << "10 (Bt2020Cl)"; break;
case ColourMatrix::kSmpte2085: out << "11 (Smpte2085)"; break;
default: out << "Unknown"; break;
}
return out;
}
} // namespace pcc
namespace pcc { namespace pcc {
static std::ostream& static std::ostream&
operator<<(std::ostream& out, const AxisOrder& val) operator<<(std::ostream& out, const AxisOrder& val)
...@@ -391,14 +423,11 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -391,14 +423,11 @@ ParseParameters(int argc, char* argv[], Parameters& params)
params.outputBinaryPly, false, params.outputBinaryPly, false,
"Output ply files using binary (or otherwise ascii) format") "Output ply files using binary (or otherwise ascii) format")
// general ("convertPlyColourspace",
// todo(df): this should be per-attribute params.convertColourspace, true,
("colorTransform", "Convert ply colourspace according to attribute colourMatrix")
params.colorTransform, COLOR_TRANSFORM_RGB_TO_YCBCR,
"The colour transform to be applied:\n"
" 0: none\n"
" 1: RGB to YCbCr (Rec.709)")
// general
// todo(df): this should be per-attribute // todo(df): this should be per-attribute
("hack.reflectanceScale", ("hack.reflectanceScale",
params.reflectanceScale, 1, params.reflectanceScale, 1,
...@@ -527,6 +556,13 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -527,6 +556,13 @@ ParseParameters(int argc, char* argv[], Parameters& params)
params_attr.desc.attr_bitdepth, 8, params_attr.desc.attr_bitdepth, 8,
"Attribute bitdepth") "Attribute bitdepth")
// todo(df): this should be per-attribute
("colourMatrix",
params_attr.desc.cicp_matrix_coefficients_idx, ColourMatrix::kBt709,
"Matrix used in colourspace conversion\n"
" 0: none (identity)\n"
" 1: ITU-T BT.709")
("transformType", ("transformType",
params_attr.aps.attr_encoding, AttributeEncoding::kPredictingTransform, params_attr.aps.attr_encoding, AttributeEncoding::kPredictingTransform,
"Coding method to use for attribute:\n" "Coding method to use for attribute:\n"
...@@ -713,10 +749,25 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -713,10 +749,25 @@ ParseParameters(int argc, char* argv[], Parameters& params)
auto& attr_aps = params.encoder.aps[it.second]; auto& attr_aps = params.encoder.aps[it.second];
auto& attr_enc = params.encoder.attr[it.second]; auto& attr_enc = params.encoder.attr[it.second];
// Avoid wasting bits signalling chroma quant step size for reflectance // default values for attribute
attr_sps.cicp_colour_primaries_idx = 2;
attr_sps.cicp_transfer_characteristics_idx = 2;
attr_sps.cicp_video_full_range_flag = true;
if (it.first == "reflectance") { if (it.first == "reflectance") {
// Avoid wasting bits signalling chroma quant step size for reflectance
attr_aps.aps_chroma_qp_offset = 0; attr_aps.aps_chroma_qp_offset = 0;
attr_enc.abh.attr_layer_qp_delta_chroma.clear(); attr_enc.abh.attr_layer_qp_delta_chroma.clear();
// There is no matrix for reflectace
attr_sps.cicp_matrix_coefficients_idx = ColourMatrix::kUnspecified;
attr_sps.attr_num_dimensions = 1;
attr_sps.attributeLabel = KnownAttributeLabel::kReflectance;
}
if (it.first == "color") {
attr_sps.attr_num_dimensions = 3;
attr_sps.attributeLabel = KnownAttributeLabel::kColour;
} }
bool isLifting = bool isLifting =
...@@ -960,9 +1011,8 @@ SequenceEncoder::compressOneFrame(Stopwatch* clock) ...@@ -960,9 +1011,8 @@ SequenceEncoder::compressOneFrame(Stopwatch* clock)
clock->start(); clock->start();
if (params->colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) { if (params->convertColourspace)
convertGbrToYCbCrBt709(pointCloud); convertFromGbr(params->encoder.sps, pointCloud);
}
if (params->reflectanceScale > 1 && pointCloud.hasReflectances()) { if (params->reflectanceScale > 1 && pointCloud.hasReflectances()) {
const auto pointCount = pointCloud.getPointCount(); const auto pointCount = pointCloud.getPointCount();
...@@ -995,9 +1045,8 @@ SequenceEncoder::compressOneFrame(Stopwatch* clock) ...@@ -995,9 +1045,8 @@ SequenceEncoder::compressOneFrame(Stopwatch* clock)
clock->stop(); clock->stop();
if (!params->reconstructedDataPath.empty()) { if (!params->reconstructedDataPath.empty()) {
if (params->colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) { if (params->convertColourspace)
convertYCbCrBt709ToGbr(*reconPointCloud); convertToGbr(params->encoder.sps, *reconPointCloud);
}
if (params->reflectanceScale > 1 && reconPointCloud->hasReflectances()) { if (params->reflectanceScale > 1 && reconPointCloud->hasReflectances()) {
const auto pointCount = reconPointCloud->getPointCount(); const auto pointCount = reconPointCloud->getPointCount();
...@@ -1036,13 +1085,13 @@ SequenceEncoder::onPostRecolour(const PCCPointSet3& cloud) ...@@ -1036,13 +1085,13 @@ SequenceEncoder::onPostRecolour(const PCCPointSet3& cloud)
std::string plyName{expandNum(params->postRecolorPath, frameNum)}; std::string plyName{expandNum(params->postRecolorPath, frameNum)};
// todo(df): stop the clock // todo(df): stop the clock
if (params->colorTransform != COLOR_TRANSFORM_RGB_TO_YCBCR) { if (!params->convertColourspace) {
ply::write(cloud, _plyAttrNames, plyName, !params->outputBinaryPly); ply::write(cloud, _plyAttrNames, plyName, !params->outputBinaryPly);
return; return;
} }
PCCPointSet3 tmpCloud(cloud); PCCPointSet3 tmpCloud(cloud);
convertYCbCrBt709ToGbr(tmpCloud); convertToGbr(params->encoder.sps, tmpCloud);
ply::write(tmpCloud, _plyAttrNames, plyName, !params->outputBinaryPly); ply::write(tmpCloud, _plyAttrNames, plyName, !params->outputBinaryPly);
} }
...@@ -1104,9 +1153,8 @@ SequenceDecoder::onOutputCloud( ...@@ -1104,9 +1153,8 @@ SequenceDecoder::onOutputCloud(
// copy the point cloud in order to modify it according to the output options // copy the point cloud in order to modify it according to the output options
PCCPointSet3 pointCloud(decodedPointCloud); PCCPointSet3 pointCloud(decodedPointCloud);
if (params->colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) { if (params->convertColourspace)
convertYCbCrBt709ToGbr(pointCloud); convertToGbr(sps, pointCloud);
}
if (params->reflectanceScale > 1 && pointCloud.hasReflectances()) { if (params->reflectanceScale > 1 && pointCloud.hasReflectances()) {
const auto pointCount = pointCloud.getPointCount(); const auto pointCount = pointCloud.getPointCount();
...@@ -1142,3 +1190,50 @@ SequenceDecoder::onOutputCloud( ...@@ -1142,3 +1190,50 @@ SequenceDecoder::onOutputCloud(
// todo(df): frame number should be derived from the bitstream // todo(df): frame number should be derived from the bitstream
frameNum++; frameNum++;
} }
//============================================================================
const AttributeDescription*
findColourAttrDesc(const SequenceParameterSet& sps)
{
// todo(df): don't assume that there is only one colour attribute in the sps
for (const auto& desc : sps.attributeSets) {
if (desc.attributeLabel == KnownAttributeLabel::kColour)
return &desc;
}
return nullptr;
}
//----------------------------------------------------------------------------
void
convertToGbr(const SequenceParameterSet& sps, PCCPointSet3& cloud)
{
const AttributeDescription* attrDesc = findColourAttrDesc(sps);
if (!attrDesc)
return;
switch (attrDesc->cicp_matrix_coefficients_idx) {
case ColourMatrix::kBt709: convertYCbCrBt709ToGbr(cloud); break;
default: break;
}
}
//----------------------------------------------------------------------------
void
convertFromGbr(const SequenceParameterSet& sps, PCCPointSet3& cloud)
{
const AttributeDescription* attrDesc = findColourAttrDesc(sps);
if (!attrDesc)
return;
switch (attrDesc->cicp_matrix_coefficients_idx) {
case ColourMatrix::kBt709: convertGbrToYCbCrBt709(cloud); break;
default: break;
}
}
//============================================================================
...@@ -42,12 +42,6 @@ ...@@ -42,12 +42,6 @@
#include "pcc_chrono.h"