encoder.cpp 15.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* 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.
 */

#include "PCCTMC3Encoder.h"

#include <cassert>
#include <set>

#include "AttributeEncoder.h"
#include "PCCPointSetProcessing.h"
43
#include "geometry.h"
44
45
#include "io_hls.h"
#include "osspecific.h"
46
#include "partitioning.h"
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "pcc_chrono.h"

namespace pcc {

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

int
PCCTMC3Encoder3::compress(
  const PCCPointSet3& inputPointCloud,
  EncoderParams* params,
  std::function<void(const PayloadBuffer&)> outputFn,
  PCCPointSet3* reconstructedCloud)
{
60
  fixupParameterSets(params);
61

62
  // Determine input bounding box (for SPS metadata) if not manually set
63
  if (params->sps.seq_bounding_box_whd == Vec3<int>{0}) {
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    const auto& bbox = inputPointCloud.computeBoundingBox();
    for (int k = 0; k < 3; k++) {
      params->sps.seq_bounding_box_xyz0[k] = int(bbox.min[k]);

      // somehow determine the decoder's reconstructed points bounding box
      // and update sps accordingly.
      auto max_k = bbox.max[k] - bbox.min[k];
      max_k = std::round(max_k * params->sps.seq_source_geom_scale_factor);
      max_k = std::round(max_k / params->sps.seq_source_geom_scale_factor);

      // NB: plus one to convert to range
      params->sps.seq_bounding_box_whd[k] = int(max_k) + 1;
    }
  }

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
  // 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;
101
    _sliceOrigin = Vec3<int>{0};
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
    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;
117
    Box3<int32_t> clampBox{{0, 0, 0}, {INT32_MAX, INT32_MAX, INT32_MAX}};
118
119
120
121
122
123
124
    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;
125
126
127
128
129

    case PartitionMethod::kUniformGeom:
      partitions = partitionByUniformGeom(
        quantizedInputCloud, params->partitionNumUniformGeom);
      break;
130
131
132
133
134

    case PartitionMethod::kOctreeUniform:
      partitions = partitionByOctreeDepth(
        quantizedInputCloud, params->partitionOctreeDepth);
      break;
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
    }
  } 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;
}

//----------------------------------------------------------------------------
176

177
178
179
void
PCCTMC3Encoder3::fixupParameterSets(EncoderParams* params)
{
180
181
182
183
184
185
186
187
188
189
190
191
192
  // fixup parameter set IDs
  params->sps.sps_seq_parameter_set_id = 0;
  params->gps.gps_seq_parameter_set_id = 0;
  params->gps.gps_geom_parameter_set_id = 0;
  for (int i = 0; i < params->aps.size(); i++) {
    params->aps[i].aps_seq_parameter_set_id = 0;
    params->aps[i].aps_attr_parameter_set_id = i;
  }

  // development level / header
  params->sps.profileCompatibility.profile_compatibility_flags = 0;
  params->sps.level = 0;

193
194
195
  // the encoder writes out the slice origin in the GBH
  // NB: this may be disabled during encoding
  params->gps.geom_box_present_flag = true;
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

  // fixup attribute parameters
  for (auto it : params->attributeIdxMap) {
    auto& attr_sps = params->sps.attributeSets[it.second];
    attr_sps.attr_instance_id = it.second;

    attr_sps.cicp_colour_primaries_idx = 2;
    attr_sps.cicp_transfer_characteristics_idx = 2;
    attr_sps.cicp_matrix_coefficients_idx = 2;
    attr_sps.cicp_video_full_range_flag = true;

    if (it.first == "color") {
      attr_sps.attr_num_dimensions = 3;
      attr_sps.attributeLabel = KnownAttributeLabel::kColour;
    }

    if (it.first == "reflectance") {
      attr_sps.attr_num_dimensions = 1;
      attr_sps.attributeLabel = KnownAttributeLabel::kReflectance;
    }
  }
217
}
218

219
//----------------------------------------------------------------------------
220

221
222
223
224
225
226
227
void
PCCTMC3Encoder3::compressPartition(
  const PCCPointSet3& inputPointCloud,
  EncoderParams* params,
  std::function<void(const PayloadBuffer&)> outputFn,
  PCCPointSet3* reconstructedCloud)
{
228
229
  // geometry compression consists of the following stages:
  //  - prefilter/quantize geometry (non-normative)
230
  //  - encode geometry (single slice, id = 0)
231
232
  //  - recolour

233
  pointCloud.clear();
234
  quantization(inputPointCloud);
235
236

  // geometry encoding
237
  _sliceId = 0;
238
  _tileId = 0;
239

240
241
242
243
244
245
  if (1) {
    PayloadBuffer payload(PayloadType::kGeometryBrick);

    pcc::chrono::Stopwatch<pcc::chrono::utime_inc_children_clock> clock_user;
    clock_user.start();

246
    encodeGeometryBrick(&payload);
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261

    clock_user.stop();

    double bpp = double(8 * payload.size()) / inputPointCloud.getPointCount();
    std::cout << "positions bitstream size " << payload.size() << " B (" << bpp
              << " bpp)\n";

    auto total_user = std::chrono::duration_cast<std::chrono::milliseconds>(
      clock_user.count());
    std::cout << "positions processing time (user): "
              << total_user.count() / 1000.0 << " s" << std::endl;

    outputFn(payload);
  }

262
263
264
  // recolouring

  // NB: recolouring is required if points are added / removed
265
266
  bool recolourNeeded =
    _gps->geom_unique_points_flag || _gps->trisoup_node_size_log2 > 0;
267
268
269

  if (recolourNeeded) {
    recolour(
270
      inputPointCloud, _sps->seq_source_geom_scale_factor,
271
      _sps->seq_bounding_box_xyz0, _sliceOrigin, &pointCloud);
272
  }
273
274

  // dump recoloured point cloud
275
  // todo(df): this needs to work with partitioned clouds
276
277
278
279
280
281
  if (!params->postRecolorPath.empty()) {
    PCCPointSet3 tempPointCloud(pointCloud);
    tempPointCloud.convertYUVToRGB();
    tempPointCloud.write(params->postRecolorPath);
  }

282
283
  // attributeCoding

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
  // for each attribute
  for (const auto& it : params->attributeIdxMap) {
    int attrIdx = it.second;
    const auto& attr_sps = _sps->attributeSets[attrIdx];
    const auto& attr_aps = *_aps[attrIdx];
    const auto& label = attr_sps.attributeLabel;

    PayloadBuffer payload(PayloadType::kAttributeBrick);

    pcc::chrono::Stopwatch<pcc::chrono::utime_inc_children_clock> clock_user;
    clock_user.start();

    // todo(df): move elsewhere?
    AttributeBrickHeader abh;
    abh.attr_attr_parameter_set_id = attr_aps.aps_attr_parameter_set_id;
    abh.attr_sps_attr_idx = attrIdx;
300
    abh.attr_geom_slice_id = _sliceId;
301
302
    abh.attr_qp_delta_luma = 0;
    abh.attr_qp_delta_chroma = 0;
303
    write(attr_aps, abh, &payload);
304
305

    AttributeEncoder attrEncoder;
306
    attrEncoder.encode(attr_sps, attr_aps, abh, pointCloud, &payload);
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
    clock_user.stop();

    int coded_size = int(payload.size());
    double bpp = double(8 * coded_size) / inputPointCloud.getPointCount();
    std::cout << label << "s bitstream size " << coded_size << " B (" << bpp
              << " bpp)\n";

    auto time_user = std::chrono::duration_cast<std::chrono::milliseconds>(
      clock_user.count());
    std::cout << label
              << "s processing time (user): " << time_user.count() / 1000.0
              << " s" << std::endl;

    outputFn(payload);
  }

323
324
325
326
  // prevent re-use of this sliceId:  the next slice (geometry + attributes)
  // should be distinguishable from the current slice.
  _sliceId++;

327
  appendReconstructedPoints(reconstructedCloud);
328
329
330
331
}

//----------------------------------------------------------------------------

332
333
334
335
void
PCCTMC3Encoder3::encodeGeometryBrick(PayloadBuffer* buf)
{
  // todo(df): confirm minimum of 1 isn't needed
336
337
  int32_t maxBB =
    std::max({1, _sliceBoxWhd[0], _sliceBoxWhd[1], _sliceBoxWhd[2]});
338
339
340
341
342
343

  // the current node dimension (log2) encompasing maxBB
  int nodeSizeLog2 = ceillog2(maxBB + 1);

  GeometryBrickHeader gbh;
  gbh.geom_geom_parameter_set_id = _gps->gps_geom_parameter_set_id;
344
  gbh.geom_slice_id = _sliceId;
345
  gbh.geom_tile_id = std::max(0, _tileId);
346
  gbh.geomBoxOrigin = _sliceOrigin;
347
348
349
350
351
352
353
  gbh.geom_box_log2_scale = 0;
  gbh.geom_max_node_size_log2 = nodeSizeLog2;
  gbh.geom_num_points = int(pointCloud.getPointCount());
  write(*_gps, gbh, buf);

  // todo(df): remove estimate when arithmetic codec is replaced
  int maxAcBufLen = int(pointCloud.getPointCount()) * 3 * 4 + 1024;
354
355
  EntropyEncoder arithmeticEncoder(maxAcBufLen, nullptr);
  arithmeticEncoder.start();
356

357
  if (_gps->trisoup_node_size_log2 == 0) {
358
    encodeGeometryOctree(*_gps, gbh, pointCloud, &arithmeticEncoder);
359
  } else {
360
361
    encodeGeometryTrisoup(*_gps, gbh, pointCloud, &arithmeticEncoder);
  }
362

363
  uint32_t dataLen = arithmeticEncoder.stop();
364
365
366
367
368
  std::copy_n(arithmeticEncoder.buffer(), dataLen, std::back_inserter(*buf));
}

//----------------------------------------------------------------------------

369
void
370
PCCTMC3Encoder3::appendReconstructedPoints(PCCPointSet3* reconstructedCloud)
371
372
373
374
375
{
  if (reconstructedCloud == nullptr) {
    return;
  }
  const size_t pointCount = pointCloud.getPointCount();
376
  size_t outIdx = reconstructedCloud->getPointCount();
377
378
379

  reconstructedCloud->addRemoveAttributes(
    pointCloud.hasColors(), pointCloud.hasReflectances());
380
  reconstructedCloud->resize(outIdx + pointCount);
381
382
383
384
385
386

  const double minPositionQuantizationScale = 0.0000000001;
  const double invScale =
    fabs(_sps->seq_source_geom_scale_factor) > minPositionQuantizationScale
    ? 1.0 / _sps->seq_source_geom_scale_factor
    : 1.0;
387
  for (size_t i = 0; i < pointCount; ++i, ++outIdx) {
388
    const auto quantizedPoint = pointCloud[i];
389
    auto& point = (*reconstructedCloud)[outIdx];
390
    for (size_t k = 0; k < 3; ++k) {
391
392
      point[k] = (quantizedPoint[k] + _sliceOrigin[k]) * invScale
        + _sps->seq_bounding_box_xyz0[k];
393
394
    }
    if (pointCloud.hasColors()) {
395
      reconstructedCloud->setColor(outIdx, pointCloud.getColor(i));
396
397
    }
    if (pointCloud.hasReflectances()) {
398
      reconstructedCloud->setReflectance(outIdx, pointCloud.getReflectance(i));
399
400
401
402
403
    }
  }
}

//----------------------------------------------------------------------------
404
405
// translates and scales inputPointCloud, storing the result in
// this->pointCloud for use by the encoding process.
406

407
void
408
409
PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
{
410
  // Currently the sequence width/height/depth must be set
411
  assert(_sps->seq_bounding_box_whd != Vec3<int>{0});
412

413
414
  // Clamp all points to [clampBox.min, clampBox.max] after translation
  // and quantisation.
415
  Box3<int32_t> clampBox{{0, 0, 0}, {INT32_MAX, INT32_MAX, INT32_MAX}};
416
  if (_sps->seq_bounding_box_whd != Vec3<int>{0}) {
417
418
419
    // todo(df): this is icky (not to mention rounding issues)
    // NB: the sps seq_bounding_box_* uses unscaled co-ordinates => convert
    // NB: minus 1 to convert to max x/y/z position
420
    clampBox = Box3<int32_t>{{0, 0, 0}, _sps->seq_bounding_box_whd};
421
422
423
424
425
    for (int k = 0; k < 3; k++)
      clampBox.max[k] =
        int(ceil(clampBox.max[k] * _sps->seq_source_geom_scale_factor)) - 1;
  }

426
427
  if (_gps->geom_unique_points_flag) {
    quantizePositionsUniq(
428
429
      _sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0,
      clampBox, inputPointCloud, &pointCloud);
430
  } else {
431
    quantizePositions(
432
433
      _sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0,
      clampBox, inputPointCloud, &pointCloud);
434
435
  }

436
  // Offset the point cloud to account for (preset) _sliceOrigin.
437
438
  Vec3<double> sliceOriginD{double(_sliceOrigin[0]), double(_sliceOrigin[1]),
                            double(_sliceOrigin[2])};
439

440
  // The new maximum bounds of the offset cloud
441
  Vec3<int> maxBound{0};
442

443
444
  const size_t pointCount = pointCloud.getPointCount();
  for (size_t i = 0; i < pointCount; ++i) {
445
    const Vec3<double> point = (pointCloud[i] -= sliceOriginD);
446
    for (int k = 0; k < 3; ++k) {
447
448
449
450
      const int k_coord = int(point[k]);
      assert(k_coord >= 0);
      if (maxBound[k] < k_coord)
        maxBound[k] = k_coord;
451
452
    }
  }
453
454
455

  // todo(df): don't update maxBound if something is forcing the value?
  _sliceBoxWhd = maxBound;
456
457
458
459
460
}

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

}  // namespace pcc