TMC3.cpp 25.3 KB
Newer Older
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
1
/* The copyright in this software is being made available under the BSD
David Flynn's avatar
David Flynn committed
2
3
4
 * 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.
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
5
 *
David Flynn's avatar
David Flynn committed
6
 * Copyright (c) 2017-2018, ISO/IEC
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
7
8
9
10
11
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
David Flynn's avatar
David Flynn committed
12
13
14
15
16
17
18
19
20
21
 * * 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.
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
22
23
24
25
 *
 * 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
David Flynn's avatar
David Flynn committed
26
27
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
28
29
30
31
 * 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)
David Flynn's avatar
David Flynn committed
32
33
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
34
35
36
 */

#include "TMC3.h"
David Flynn's avatar
David Flynn committed
37
38
39
40
41

#include <memory>

#include "PCCTMC3Encoder.h"
#include "PCCTMC3Decoder.h"
42
#include "constants.h"
43
#include "program_options_lite.h"
44
#include "io_tlv.h"
45
#include "version.h"
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
46
47
48
49

using namespace std;
using namespace pcc;

50
51
52
53
54
//============================================================================

struct Parameters {
  bool isDecoder;

55
56
57
  // command line parsing should adjust dist2 values according to PQS
  bool positionQuantizationScaleAdjustsDist2;

58
59
60
  // output mode for ply writing (binary or ascii)
  bool outputBinaryPly;

61
62
63
  // when true, configure the encoder as if no attributes are specified
  bool disableAttributeCoding;

64
65
66
67
  std::string uncompressedDataPath;
  std::string compressedStreamPath;
  std::string reconstructedDataPath;

68
69
70
  // Filename for saving pre inverse scaled point cloud.
  std::string preInvScalePath;

71
72
73
74
75
  pcc::EncoderParams encoder;
  pcc::DecoderParams decoder;

  // todo(df): this should be per-attribute
  ColorTransform colorTransform;
76
77
78

  // todo(df): this should be per-attribute
  int reflectanceScale;
79
80
81
82
};

//============================================================================

83
84
85
int
main(int argc, char* argv[])
{
86
  cout << "MPEG PCC tmc3 version " << ::pcc::version << endl;
87

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
88
89
90
91
  Parameters params;
  if (!ParseParameters(argc, argv, params)) {
    return -1;
  }
92
93
94
95
96
97
98

  // Timers to count elapsed wall/user time
  pcc::chrono::Stopwatch<std::chrono::steady_clock> clock_wall;
  pcc::chrono::Stopwatch<pcc::chrono::utime_inc_children_clock> clock_user;

  clock_wall.start();

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
99
  int ret = 0;
100
  if (params.isDecoder) {
101
    ret = Decompress(params, clock_user);
102
103
  } else {
    ret = Compress(params, clock_user);
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
104
105
  }

106
107
108
109
110
111
112
113
  clock_wall.stop();

  using namespace std::chrono;
  auto total_wall = duration_cast<milliseconds>(clock_wall.count()).count();
  auto total_user = duration_cast<milliseconds>(clock_user.count()).count();
  std::cout << "Processing time (wall): " << total_wall / 1000.0 << " s\n";
  std::cout << "Processing time (user): " << total_user / 1000.0 << " s\n";

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
114
115
116
  return ret;
}

117
118
119
//---------------------------------------------------------------------------
// :: Command line / config parsing helpers

120
121
122
123
template<typename T>
static std::istream&
readUInt(std::istream& in, T& val)
{
124
125
126
127
128
129
  unsigned int tmp;
  in >> tmp;
  val = T(tmp);
  return in;
}

130
131
132
static std::istream&
operator>>(std::istream& in, ColorTransform& val)
{
133
134
135
  return readUInt(in, val);
}

136
namespace pcc {
137
static std::istream&
138
operator>>(std::istream& in, AttributeEncoding& val)
139
{
140
  return readUInt(in, val);
141
142
}
}  // namespace pcc
143

144
145
146
147
148
149
150
151
namespace pcc {
static std::istream&
operator>>(std::istream& in, PartitionMethod& val)
{
  return readUInt(in, val);
}
}  // namespace pcc

152
namespace pcc {
153
static std::ostream&
154
operator<<(std::ostream& out, const AttributeEncoding& val)
155
{
156
  switch (val) {
157
158
159
  case AttributeEncoding::kPredictingTransform: out << "0 (Pred)"; break;
  case AttributeEncoding::kRAHTransform: out << "1 (RAHT)"; break;
  case AttributeEncoding::kLiftingTransform: out << "2 (Lift)"; break;
160
161
  }
  return out;
162
163
}
}  // namespace pcc
164

165
166
167
168
169
170
namespace pcc {
static std::ostream&
operator<<(std::ostream& out, const PartitionMethod& val)
{
  switch (val) {
  case PartitionMethod::kNone: out << "0 (None)"; break;
171
  case PartitionMethod::kUniformGeom: out << "0 (UniformGeom)"; break;
172
  case PartitionMethod::kOctreeUniform: out << "0 (UniformOctree)"; break;
173
  default: out << int(val) << " (Unknown)"; break;
174
175
176
177
178
  }
  return out;
}
}  // namespace pcc

179
180
181
namespace df {
namespace program_options_lite {
  template<typename T>
182
  struct option_detail<pcc::Vec3<T>> {
183
184
185
186
    static constexpr bool is_container = true;
    static constexpr bool is_fixed_size = true;
    typedef T* output_iterator;

187
188
    static void clear(pcc::Vec3<T>& container){};
    static output_iterator make_output_iterator(pcc::Vec3<T>& container)
189
190
191
192
193
194
195
    {
      return &container[0];
    }
  };
}  // namespace program_options_lite
}  // namespace df

196
197
198
//---------------------------------------------------------------------------
// :: Command line / config parsing

199
200
201
bool
ParseParameters(int argc, char* argv[], Parameters& params)
{
202
203
  namespace po = df::program_options_lite;

204
205
206
207
208
  struct {
    AttributeDescription desc;
    AttributeParameterSet aps;
  } params_attr;

209
210
211
212
213
214
215
216
217
218
219
220
221
  bool print_help = false;

  // a helper to set the attribute
  std::function<po::OptionFunc::Func> attribute_setter =
    [&](po::Options&, const std::string& name, po::ErrorReporter) {
      // copy the current state of parsed attribute parameters
      //
      // NB: this does not cause the default values of attr to be restored
      // for the next attribute block.  A side-effect of this is that the
      // following is allowed leading to attribute foo having both X=1 and
      // Y=2:
      //   "--attr.X=1 --attribute foo --attr.Y=2 --attribute foo"
      //
222
223
224
225
226
227
228
229
230
231
232
233
234
235

      // NB: insert returns any existing element
      const auto& it = params.encoder.attributeIdxMap.insert(
        {name, int(params.encoder.attributeIdxMap.size())});

      if (it.second) {
        params.encoder.sps.attributeSets.push_back(params_attr.desc);
        params.encoder.aps.push_back(params_attr.aps);
        return;
      }

      // update existing entry
      params.encoder.sps.attributeSets[it.first->second] = params_attr.desc;
      params.encoder.aps[it.first->second] = params_attr.aps;
236
237
    };

238
  /* clang-format off */
239
240
241
242
243
244
245
246
247
248
249
  // The definition of the program/config options, along with default values.
  //
  // NB: when updating the following tables:
  //      (a) please keep to 80-columns for easier reading at a glance,
  //      (b) do not vertically align values -- it breaks quickly
  //
  po::Options opts;
  opts.addOptions()
  ("help", print_help, false, "this help text")
  ("config,c", po::parseConfigFile, "configuration file name")

250
251
  (po::Section("General"))

252
  ("mode", params.isDecoder, false,
253
254
    "The encoding/decoding mode:\n"
    "  0: encode\n"
255
    "  1: decode")
256
257
258

  // i/o parameters
  ("reconstructedDataPath",
259
260
    params.reconstructedDataPath, {},
    "The ouput reconstructed pointcloud file path (decoder only)")
261
262

  ("uncompressedDataPath",
263
264
    params.uncompressedDataPath, {},
    "The input pointcloud file path")
265
266

  ("compressedStreamPath",
267
268
    params.compressedStreamPath, {},
    "The compressed bitstream path (encoder=output, decoder=input)")
269

270
  ("postRecolorPath",
271
    params.encoder.postRecolorPath, {},
272
    "Recolored pointcloud file path (encoder only)")
273
274

  ("preInvScalePath",
275
    params.preInvScalePath, {},
276
    "Pre inverse scaled pointcloud file path (decoder only)")
277

278
279
280
281
  ("outputBinaryPly",
    params.outputBinaryPly, false,
    "Output ply files using binary (or otherwise ascii) format")

282
  // general
283
  // todo(df): this should be per-attribute
284
  ("colorTransform",
285
286
287
288
    params.colorTransform, COLOR_TRANSFORM_RGB_TO_YCBCR,
    "The colour transform to be applied:\n"
    "  0: none\n"
    "  1: RGB to YCbCr (Rec.709)")
289

290
291
292
293
294
295
  // todo(df): this should be per-attribute
  ("hack.reflectanceScale",
    params.reflectanceScale, 1,
    "scale factor to be applied to reflectance "
    "pre encoding / post reconstruction")

296
297
  // NB: if adding decoder options, uncomment the Decoder section marker
  // (po::Section("Decoder"))
298
299
300

  (po::Section("Encoder"))

301
302
303
304
305
306
307
308
309
  ("seq_bounding_box_xyz0",
    params.encoder.sps.seq_bounding_box_xyz0, {0},
    "seq_bounding_box_xyz0.  NB: seq_bounding_box_whd must be set for this "
    "parameter to have an effect")

  ("seq_bounding_box_whd",
    params.encoder.sps.seq_bounding_box_whd, {0},
    "seq_bounding_box_whd")

310
  ("positionQuantizationScale",
311
    params.encoder.sps.seq_source_geom_scale_factor, 1.f,
312
    "Scale factor to be applied to point positions during quantization process")
313

314
315
316
317
  ("positionQuantizationScaleAdjustsDist2",
    params.positionQuantizationScaleAdjustsDist2, false,
    "Scale dist2 values by squared positionQuantizationScale")

318
  ("mergeDuplicatedPoints",
319
    params.encoder.gps.geom_unique_points_flag, true,
320
    "Enables removal of duplicated points")
321

322
323
324
  ("partitionMethod",
    params.encoder.partitionMethod, PartitionMethod::kNone,
    "Method used to partition input point cloud into slices/tiles:\n"
325
326
    "  0: none\n"
    "  1: none (deprecated)\n"
327
328
    "  2: n Uniform-Geometry partition bins along the longest edge\n"
    "  3: Uniform Geometry partition at n octree depth")
329
330
331
332
333
334

  ("partitionNumUniformGeom",
    params.encoder.partitionNumUniformGeom, 0,
    "Number of bins for partitionMethod=2:\n"
    "  0: slice partition with adaptive-defined bins\n"
    "  >=1: slice partition with user-defined bins\n")
335

336
337
338
339
  ("partitionOctreeDepth",
    params.encoder.partitionOctreeDepth, 2,
    "Depth of octree partition for partitionMethod=3")

340
341
342
343
  ("disableAttributeCoding",
    params.disableAttributeCoding, false,
    "Ignore attribute coding configuration")

344
  (po::Section("Geometry"))
345

346
  // tools
347
348
349
350
351
352
  ("bitwiseOccupancyCoding",
    params.encoder.gps.bitwise_occupancy_coding_flag, true,
    "Selects between bitwise and bytewise occupancy coding:\n"
    "  0: bytewise\n"
    "  1: bitwise")

353
  ("neighbourContextRestriction",
354
    params.encoder.gps.neighbour_context_restriction_flag, false,
355
    "Limit geometry octree occupancy contextualisation to sibling nodes")
356

357
  ("neighbourAvailBoundaryLog2",
358
    params.encoder.gps.neighbour_avail_boundary_log2, 0,
359
360
361
    "Defines the avaliability volume for neighbour occupancy lookups."
    " 0: unconstrained")

362
  ("inferredDirectCodingMode",
363
    params.encoder.gps.inferred_direct_coding_mode_enabled_flag, true,
364
    "Permits early termination of the geometry octree for isolated points")
365

366
367
368
369
  ("adjacentChildContextualization",
    params.encoder.gps.adjacent_child_contextualization_enabled_flag, true,
    "Occupancy contextualization using neighbouring adjacent children")

370
371
372
373
  ("intra_pred_max_node_size_log2",
    params.encoder.gps.intra_pred_max_node_size_log2, 0,
    "octree nodesizes eligible for occupancy intra prediction")

374
375
376
377
  ("ctxOccupancyReductionFactor",
     params.encoder.gps.geom_occupancy_ctx_reduction_factor, 3,
     "Adjusts the number of contexts used in occupancy coding")

378
379
380
381
  ("trisoup_node_size_log2",
    params.encoder.gps.trisoup_node_size_log2, 0,
    "Size of nodes for surface triangulation.\n"
    "  0: disabled\n")
382

383
384
  (po::Section("Attributes"))

385
386
387
  // attribute processing
  //   NB: Attribute options are special in the way they are applied (see above)
  ("attribute",
388
389
390
    attribute_setter,
    "Encode the given attribute (NB, must appear after the"
    "following attribute parameters)")
391

392
393
394
395
  ("bitdepth",
    params_attr.desc.attr_bitdepth, 8,
    "Attribute bitdepth")

396
  ("transformType",
397
    params_attr.aps.attr_encoding, AttributeEncoding::kPredictingTransform,
398
    "Coding method to use for attribute:\n"
399
    "  0: Hierarchical neighbourhood prediction\n"
400
    "  1: Region Adaptive Hierarchical Transform (RAHT)\n"
401
    "  2: Hierarichical neighbourhood prediction as lifting transform")
402

403
  ("rahtLeafDecimationDepth",
404
    params_attr.aps.raht_binary_level_threshold, 3,
405
406
    "Sets coefficients to zero in the bottom n levels of RAHT tree. "
    "Used for chroma-subsampling in attribute=color only.")
407

408
  ("rahtDepth",
409
    params_attr.aps.raht_depth, 21,
410
411
    "Number of bits for morton representation of an RAHT co-ordinate"
    "component")
412

413
  ("numberOfNearestNeighborsInPrediction",
414
    params_attr.aps.num_pred_nearest_neighbours, 3,
415
    "Attribute's maximum number of nearest neighbors to be used for prediction")
416

417
418
419
420
421
422
  ("adaptivePredictionThreshold",
    params_attr.aps.adaptive_prediction_threshold, -1,
    "Neighbouring attribute value difference that enables choice of "
    "single|multi predictors. Applies to transformType=2 only.\n"
    "  -1: auto = 2**(bitdepth-2)")

423
424
425
426
  ("attributeSearchRange",
    params_attr.aps.search_range, 128,
    "Range for nearest neighbor search")

427
428
429
430
431
432
  ("lodBinaryTree",
    params_attr.aps.lod_binary_tree_enabled_flag, false,
    "Controls LoD generation method:\n"
    " 0: distance based subsampling\n"
    " 1: binary tree")

433
434
435
436
437
  ("max_num_direct_predictors",
    params_attr.aps.max_num_direct_predictors, 3,
    "Maximum number of nearest neighbour candidates used in direct"
    "attribute prediction")

438
  ("levelOfDetailCount",
439
    params_attr.aps.num_detail_levels, 1,
440
    "Attribute's number of levels of detail")
441

442
443
  ("dist2",
    params_attr.aps.dist2, {},
444
445
    "Attribute's list of squared distances, or initial value for automatic"
    "derivation")
446
447
448
449
450
451
452
453

  ("qp",
    params_attr.aps.init_qp, 4,
    "Attribute's luma quantisation parameter")

  ("qpChromaOffset",
    params_attr.aps.aps_chroma_qp_offset, 0,
    "Attribute's chroma quantisation parameter offset (relative to luma)")
454
455
456
457

  ("aps_slice_qp_deltas_present_flag",
    params_attr.aps.aps_slice_qp_deltas_present_flag, false,
    "Enable signalling of per-slice QP values")
458
  ;
459
  /* clang-format on */
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474

  po::setDefaults(opts);
  po::ErrorReporter err;
  const list<const char*>& argv_unhandled =
    po::scanArgv(opts, argc, (const char**)argv, err);

  for (const auto arg : argv_unhandled) {
    err.warn() << "Unhandled argument ignored: " << arg << "\n";
  }

  if (argc == 1 || print_help) {
    po::doHelp(std::cout, opts, 78);
    return false;
  }

475
476
  // Certain coding modes are not available when trisoup is enabled.
  // Disable them, and warn if set (they may be set as defaults).
477
  if (params.encoder.gps.trisoup_node_size_log2 > 0) {
478
479
480
481
482
483
484
485
486
487
    if (!params.encoder.gps.geom_unique_points_flag)
      err.warn() << "TriSoup geometry does not preserve duplicated points\n";

    if (params.encoder.gps.inferred_direct_coding_mode_enabled_flag)
      err.warn() << "TriSoup geometry is incompatable with IDCM\n";

    params.encoder.gps.geom_unique_points_flag = true;
    params.encoder.gps.inferred_direct_coding_mode_enabled_flag = false;
  }

488
489
490
491
492
493
494
  // support disabling attribute coding (simplifies configuration)
  if (params.disableAttributeCoding) {
    params.encoder.attributeIdxMap.clear();
    params.encoder.sps.attributeSets.clear();
    params.encoder.aps.clear();
  }

495
  // fixup any per-attribute settings
496
  for (const auto& it : params.encoder.attributeIdxMap) {
497
    auto& attr_sps = params.encoder.sps.attributeSets[it.second];
498
499
    auto& attr_aps = params.encoder.aps[it.second];

500
501
    // Avoid wasting bits signalling chroma quant step size for reflectance
    if (it.first == "reflectance") {
502
      attr_aps.aps_chroma_qp_offset = 0;
503
504
    }

505
506
507
508
509
    bool isLifting =
      attr_aps.attr_encoding == AttributeEncoding::kPredictingTransform
      || attr_aps.attr_encoding == AttributeEncoding::kLiftingTransform;

    // derive the dist2 values based on an initial value
510
511
    if (isLifting) {
      if (attr_aps.dist2.size() > attr_aps.num_detail_levels) {
512
        attr_aps.dist2.resize(attr_aps.num_detail_levels);
513
514
515
516
517
518
519
520
521
522
523
      } else if (
        attr_aps.dist2.size() < attr_aps.num_detail_levels
        && !attr_aps.dist2.empty()) {
        if (attr_aps.dist2.size() < attr_aps.num_detail_levels) {
          attr_aps.dist2.resize(attr_aps.num_detail_levels);
          const double distRatio = 4.0;
          uint64_t d2 = attr_aps.dist2[0];
          for (int i = 0; i < attr_aps.num_detail_levels; ++i) {
            attr_aps.dist2[i] = d2;
            d2 = uint64_t(std::round(distRatio * d2));
          }
524
525
526
        }
      }
    }
527
528
529
530
531
532
533
534
535
536
537
    // In order to simplify specification of dist2 values, which are
    // depending on the scale of the coded point cloud, the following
    // adjust the dist2 values according to PQS.  The user need only
    // specify the unquantised PQS value.
    if (params.positionQuantizationScaleAdjustsDist2) {
      double pqs = params.encoder.sps.seq_source_geom_scale_factor;
      double pqs2 = pqs * pqs;
      for (auto& dist2 : attr_aps.dist2)
        dist2 = int64_t(std::round(pqs2 * dist2));
    }

538
539
540
541
542
543
544
545
546
547
548
    // Set default threshold based on bitdepth
    if (attr_aps.adaptive_prediction_threshold == -1) {
      attr_aps.adaptive_prediction_threshold = 1
        << (attr_sps.attr_bitdepth - 2);
    }

    if (attr_aps.attr_encoding == AttributeEncoding::kLiftingTransform) {
      attr_aps.adaptive_prediction_threshold = 0;
    }

    // For RAHT, ensure that the unused lod count = 0 (prevents mishaps)
549
    if (attr_aps.attr_encoding == AttributeEncoding::kRAHTransform) {
550
      attr_aps.num_detail_levels = 0;
551
      attr_aps.adaptive_prediction_threshold = 0;
552
553

      // todo(df): suggest chroma quant_step_size for raht
554
      attr_aps.aps_chroma_qp_offset = 0;
555
556
557
    }
  }

558
  // sanity checks
559
560
561
562
563
564

  if (params.encoder.gps.intra_pred_max_node_size_log2)
    if (!params.encoder.gps.neighbour_avail_boundary_log2)
      err.error() << "Geometry intra prediction requires finite"
                     "neighbour_avail_boundary_log2\n";

565
566
567
568
569
570
571
572
  for (const auto& it : params.encoder.attributeIdxMap) {
    const auto& attr_sps = params.encoder.sps.attributeSets[it.second];
    const auto& attr_aps = params.encoder.aps[it.second];

    bool isLifting =
      attr_aps.attr_encoding == AttributeEncoding::kPredictingTransform
      || attr_aps.attr_encoding == AttributeEncoding::kLiftingTransform;

573
574
575
576
577
578
579
580
581
582
583
    if (it.first == "color") {
      // todo(??): permit relaxing of the following constraint
      if (attr_sps.attr_bitdepth > 8)
        err.error() << it.first << ".bitdepth must be less than 9\n";
    }

    if (it.first == "reflectance") {
      if (attr_sps.attr_bitdepth > 16)
        err.error() << it.first << ".bitdepth must be less than 17\n";
    }

584
    if (isLifting) {
585
      int lod = attr_aps.num_detail_levels;
586
      if (lod > 255 || lod < 0) {
587
        err.error() << it.first
588
                    << ".levelOfDetailCount must be in the range [0,255]\n";
589
      }
590
591
      if (attr_aps.dist2.size() != lod) {
        err.error() << it.first << ".dist2 does not have " << lod
592
                    << " entries\n";
593
      }
594

595
596
597
598
599
      if (attr_aps.adaptive_prediction_threshold < 0) {
        err.error() << it.first
                    << ".adaptivePredictionThreshold must be positive\n";
      }

600
      if (
601
        attr_aps.num_pred_nearest_neighbours
602
        > kAttributePredictionMaxNeighbourCount) {
603
604
        err.error() << it.first
                    << ".numberOfNearestNeighborsInPrediction must be <= "
605
                    << kAttributePredictionMaxNeighbourCount << "\n";
606
      }
607
    }
608
609
610
611
612
613
614
615

    if (attr_aps.init_qp < 4)
      err.error() << it.first << ".qp must be greater than 3\n";

    if (attr_aps.init_qp + attr_aps.aps_chroma_qp_offset < 4) {
      err.error() << it.first << ".qpChromaOffset must be greater than "
                  << attr_aps.init_qp - 5 << "\n";
    }
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
616
617
  }

618
619
  // check required arguments are specified

620
  if (!params.isDecoder && params.uncompressedDataPath.empty())
621
622
    err.error() << "uncompressedDataPath not set\n";

623
  if (params.isDecoder && params.reconstructedDataPath.empty())
624
625
626
627
628
629
630
631
632
633
    err.error() << "reconstructedDataPath not set\n";

  if (params.compressedStreamPath.empty())
    err.error() << "compressedStreamPath not set\n";

  // report the current configuration (only in the absence of errors so
  // that errors/warnings are more obvious and in the same place).
  if (err.is_errored)
    return false;

634
635
  // Dump the complete derived configuration
  cout << "+ Effective configuration parameters\n";
636

637
  po::dumpCfg(cout, opts, "General", 4);
638
  if (params.isDecoder) {
639
    po::dumpCfg(cout, opts, "Decoder", 4);
640
  } else {
641
642
643
    po::dumpCfg(cout, opts, "Encoder", 4);
    po::dumpCfg(cout, opts, "Geometry", 4);

644
    for (const auto& it : params.encoder.attributeIdxMap) {
645
      // NB: when dumping the config, opts references params_attr
646
647
      params_attr.desc = params.encoder.sps.attributeSets[it.second];
      params_attr.aps = params.encoder.aps[it.second];
648
649
650
      cout << "    " << it.first << "\n";
      po::dumpCfg(cout, opts, "Attributes", 8);
    }
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
651
652
  }

653
654
  cout << endl;

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
655
656
  return true;
}
657

658
int
659
Compress(Parameters& params, Stopwatch& clock)
660
{
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
661
  PCCPointSet3 pointCloud;
662
663
664
  if (
    !pointCloud.read(params.uncompressedDataPath)
    || pointCloud.getPointCount() == 0) {
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
665
666
667
668
    cout << "Error: can't open input file!" << endl;
    return -1;
  }

669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
  // Sanitise the input point cloud
  // todo(df): remove the following with generic handling of properties
  bool codeColour = params.encoder.attributeIdxMap.count("color");
  if (!codeColour)
    pointCloud.removeColors();
  assert(codeColour == pointCloud.hasColors());

  bool codeReflectance = params.encoder.attributeIdxMap.count("reflectance");
  if (!codeReflectance)
    pointCloud.removeReflectances();
  assert(codeReflectance == pointCloud.hasReflectances());

  ofstream fout(params.compressedStreamPath, ios::binary);
  if (!fout.is_open()) {
    return -1;
  }

686
687
  clock.start();

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
688
689
690
  if (params.colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
    pointCloud.convertRGBToYUV();
  }
691
692
693
694
695
696
697
698
699

  if (params.reflectanceScale > 1 && pointCloud.hasReflectances()) {
    const auto pointCount = pointCloud.getPointCount();
    for (size_t i = 0; i < pointCount; ++i) {
      int val = pointCloud.getReflectance(i) / params.reflectanceScale;
      pointCloud.setReflectance(i, val);
    }
  }

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
700
701
  PCCTMC3Encoder3 encoder;

702
703
  // The reconstructed point cloud
  std::unique_ptr<PCCPointSet3> reconPointCloud;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
704
  if (!params.reconstructedDataPath.empty()) {
705
    reconPointCloud.reset(new PCCPointSet3);
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
706
707
  }

708
  int ret = encoder.compress(
709
710
    pointCloud, &params.encoder,
    [&](const PayloadBuffer& buf) { writeTlv(buf, fout); },
711
    reconPointCloud.get());
712
  if (ret) {
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
713
714
715
    cout << "Error: can't compress point cloud!" << endl;
    return -1;
  }
716

717
  std::cout << "Total bitstream size " << fout.tellp() << " B" << std::endl;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
718
719
  fout.close();

720
721
  clock.stop();

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
722
723
  if (!params.reconstructedDataPath.empty()) {
    if (params.colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
724
725
726
727
728
729
730
731
732
      reconPointCloud->convertYUVToRGB();
    }

    if (params.reflectanceScale > 1 && reconPointCloud->hasReflectances()) {
      const auto pointCount = reconPointCloud->getPointCount();
      for (size_t i = 0; i < pointCount; ++i) {
        int val = reconPointCloud->getReflectance(i) * params.reflectanceScale;
        reconPointCloud->setReflectance(i, val);
      }
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
733
    }
734

735
736
    reconPointCloud->write(
      params.reconstructedDataPath, !params.outputBinaryPly);
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
737
738
739
740
  }

  return 0;
}
741
int
742
Decompress(Parameters& params, Stopwatch& clock)
743
{
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
744
745
746
747
748
  ifstream fin(params.compressedStreamPath, ios::binary);
  if (!fin.is_open()) {
    return -1;
  }

749
750
  clock.start();

751
  PayloadBuffer buf;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
752
  PCCTMC3Decoder3 decoder;
753

754
755
756
  while (true) {
    PayloadBuffer* buf_ptr = &buf;
    readTlv(fin, &buf);
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
757

758
759
760
    // at end of file (or other error), flush decoder
    if (!fin)
      buf_ptr = nullptr;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
761

762
763
764
    int ret = decoder.decompress(
      params.decoder, buf_ptr, [&](const PCCPointSet3& decodedPointCloud) {
        PCCPointSet3 pointCloud(decodedPointCloud);
765

766
767
768
769
        if (params.colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
          pointCloud.convertYUVToRGB();
        }

770
771
772
773
774
775
776
777
        if (params.reflectanceScale > 1 && pointCloud.hasReflectances()) {
          const auto pointCount = pointCloud.getPointCount();
          for (size_t i = 0; i < pointCount; ++i) {
            int val = pointCloud.getReflectance(i) * params.reflectanceScale;
            pointCloud.setReflectance(i, val);
          }
        }

778
779
        // Dump the decoded colour using the pre inverse scaled geometry
        if (!params.preInvScalePath.empty()) {
780
          pointCloud.write(params.preInvScalePath, !params.outputBinaryPly);
781
782
        }

783
        decoder.inverseQuantization(pointCloud);
784
785
786

        clock.stop();

787
788
        if (!pointCloud.write(
              params.reconstructedDataPath, !params.outputBinaryPly)) {
789
790
791
792
793
794
795
796
797
798
799
800
801
          cout << "Error: can't open output file!" << endl;
        }

        clock.start();
      });

    if (ret) {
      cout << "Error: can't decompress point cloud!" << endl;
      return -1;
    }

    if (!buf_ptr)
      break;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
802
  }
803

804
805
806
807
808
809
  fin.clear();
  fin.seekg(0, ios_base::end);
  std::cout << "Total bitstream size " << fin.tellg() << " B" << std::endl;

  clock.stop();

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
810
811
  return 0;
}