ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvRasterGrid.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ecvRasterGrid.h"
9 
10 // cloudViewer
11 #include <Delaunay2dMesh.h>
12 
13 // CV_DB_LIB
14 #include "ecvGenericPointCloud.h"
15 #include "ecvPointCloud.h"
16 #include "ecvProgressDialog.h"
17 #include "ecvScalarField.h"
18 
19 // Qt
20 #include <QCoreApplication>
21 #include <QMap>
22 
23 // System
24 #include <cassert>
25 
26 // default field names
28  : public QMap<ccRasterGrid::ExportableFields, QString> {
30  insert(ccRasterGrid::PER_CELL_VALUE, "Cell height values");
31  insert(ccRasterGrid::PER_CELL_COUNT, "population");
32  insert(ccRasterGrid::PER_CELL_MIN_VALUE, "min");
33  insert(ccRasterGrid::PER_CELL_MAX_VALUE, "max");
34  insert(ccRasterGrid::PER_CELL_AVG_VALUE, "average");
35  insert(ccRasterGrid::PER_CELL_VALUE_STD_DEV, "std. dev.");
36  insert(ccRasterGrid::PER_CELL_VALUE_RANGE, "range");
37  insert(ccRasterGrid::PER_CELL_MEDIAN_VALUE, "median");
39  insert(ccRasterGrid::PER_CELL_PERCENTILE_VALUE, "percentile");
40  }
41 };
43 
45  assert(s_defaultFieldNames.contains(field));
46  return s_defaultFieldNames[field];
47 }
48 
50  : width(0),
51  height(0),
52  gridStep(1.0),
53  minCorner(0, 0, 0),
54  minHeight(0),
55  maxHeight(0),
56  meanHeight(0),
57  nonEmptyCellCount(0),
58  validCellCount(0),
59  hasColors(false),
60  valid(false) {}
61 
63 
64 bool ccRasterGrid::ComputeGridSize(unsigned char Z,
65  const ccBBox& box,
66  double gridStep,
67  unsigned& gridWidth,
68  unsigned& gridHeight) {
69  gridWidth = gridHeight = 0;
70 
71  if (Z > 2 || !box.isValid() || gridStep <= 0) {
72  // invalid input
73  assert(false);
74  CVLog::Warning("[ccRasterGrid::ComputeGridSize] Invalid input");
75  return false;
76  }
77 
78  // vertical dimension
79  const unsigned char X = Z == 2 ? 0 : Z + 1;
80  const unsigned char Y = X == 2 ? 0 : X + 1;
81 
82  CCVector3d boxDiag = CCVector3d::fromArray(box.maxCorner().u) -
84  if (boxDiag.u[X] <= 0 || boxDiag.u[Y] <= 0) {
86  "[ccRasterGrid::ComputeGridSize] Invalid cloud bounding box!");
87  return false;
88  }
89 
90  // DGM: we now use the 'PixelIsArea' convention (the height value is
91  // computed at the grid cell center)
92  gridWidth = 1 + static_cast<unsigned>(boxDiag.u[X] / gridStep + 0.5);
93  gridHeight = 1 + static_cast<unsigned>(boxDiag.u[Y] / gridStep + 0.5);
94 
95  return true;
96 }
97 
99  // reset
100  width = height = 0;
101 
102  rows.resize(0);
103  scalarFields.resize(0);
104 
107  hasColors = false;
108 
109  setValid(false);
110 }
111 
113  for (Row& row : rows) {
114  std::fill(row.begin(), row.end(), ccRasterCell());
115  }
116 
119  hasColors = false;
120 
121  setValid(false);
122 }
123 bool ccRasterGrid::init(unsigned w, unsigned h, double s, const CCVector3d& c) {
124  // we always restart from scratch (clearer / safer)
125  clear();
126 
127  try {
128  rows.resize(h);
129  for (Row& row : rows) {
130  row.resize(w);
131  }
132  } catch (const std::bad_alloc&) {
133  // not enough memory
134  rows.resize(0);
135  return false;
136  }
137 
138  width = w;
139  height = h;
140  gridStep = s;
141  minCorner = c;
142 
143  return true;
144 }
145 
147  ccGenericPointCloud* cloud,
148  unsigned char Z,
149  ProjectionType projectionType,
150  bool interpolateEmptyCells,
151  ProjectionType sfInterpolation /*=INVALID_PROJECTION_TYPE*/,
152  ecvProgressDialog* progressDialog /*=0*/) {
153  if (!cloud) {
154  assert(false);
155  return false;
156  }
157 
158  unsigned gridTotalSize = width * height;
159  if (gridTotalSize == 0) {
160  assert(false);
161  return false;
162  }
163 
164  // input cloud as a ccPointCloud
165  ccPointCloud* pc = nullptr;
166  if (cloud->isA(CV_TYPES::POINT_CLOUD)) {
167  pc = static_cast<ccPointCloud*>(cloud);
168  }
169 
170  // do we need to interpolate scalar fields?
171  bool interpolateSF = (sfInterpolation != INVALID_PROJECTION_TYPE);
172  if (interpolateSF) {
173  if (pc && pc->hasScalarFields()) {
174  unsigned sfCount = pc->getNumberOfScalarFields();
175  try {
176  scalarFields.resize(sfCount);
177  for (unsigned i = 0; i < sfCount; ++i) {
178  scalarFields[i].resize(
179  gridTotalSize,
180  std::numeric_limits<SF::value_type>::quiet_NaN());
181  }
182  } catch (const std::bad_alloc&) {
183  // not enough memory
184  scalarFields.resize(0);
186  "[Rasterize] Failed to allocate memory for scalar "
187  "fields!");
188  }
189  } else {
190  interpolateSF = false;
191  }
192  }
193 
194  // filling the grid
195  unsigned pointCount = cloud->size();
196 
197  if (progressDialog) {
198  progressDialog->setMethodTitle(QObject::tr("Grid generation"));
199  progressDialog->setInfo(QObject::tr("Points: %L1\nCells: %L2 x %L3")
200  .arg(pointCount)
201  .arg(width)
202  .arg(height));
203  progressDialog->start();
204  progressDialog->show();
205  QCoreApplication::processEvents();
206  }
207  cloudViewer::NormalizedProgress nProgress(progressDialog, pointCount);
208 
209  // vertical dimension
210  assert(Z <= 2);
211  const unsigned char X = Z == 2 ? 0 : Z + 1;
212  const unsigned char Y = X == 2 ? 0 : X + 1;
213 
214  // we always handle the colors (if any)
215  hasColors = cloud->hasColors();
216 
217  for (unsigned n = 0; n < pointCount; ++n) {
218  // for each point
219  const CCVector3* P = cloud->getPoint(n);
220 
221  // project it inside the grid
222  CCVector3d relativePos = CCVector3d::fromArray(P->u) - minCorner;
223  int i = static_cast<int>((relativePos.u[X] / gridStep + 0.5));
224  int j = static_cast<int>((relativePos.u[Y] / gridStep + 0.5));
225 
226  // we skip points that fall outside of the grid!
227  if (i < 0 || i >= static_cast<int>(width) || j < 0 ||
228  j >= static_cast<int>(height)) {
229  if (!nProgress.oneStep()) {
230  // process cancelled by the user
231  return false;
232  }
233  continue;
234  }
235  assert(i >= 0 && j >= 0);
236 
237  // update the cell statistics
238  ccRasterCell& aCell = rows[j][i];
239  if (aCell.nbPoints) {
240  if (P->u[Z] < aCell.minHeight) {
241  aCell.minHeight = P->u[Z];
242  if (projectionType == PROJ_MINIMUM_VALUE) {
243  // we keep track of the lowest point
244  aCell.pointIndex = n;
245 
246  if (hasColors) {
247  assert(cloud->hasColors());
248  const ecvColor::Rgb& col = cloud->getPointColor(n);
249  aCell.color = CCVector3d(col.r, col.g, col.b);
250  }
251  }
252  } else if (P->u[Z] > aCell.maxHeight) {
253  aCell.maxHeight = P->u[Z];
254  if (projectionType == PROJ_MAXIMUM_VALUE) {
255  // we keep track of the highest point
256  aCell.pointIndex = n;
257 
258  if (hasColors) {
259  assert(cloud->hasColors());
260  const ecvColor::Rgb& col = cloud->getPointColor(n);
261  aCell.color = CCVector3d(col.r, col.g, col.b);
262  }
263  }
264  }
265 
266  if (projectionType == PROJ_AVERAGE_VALUE) {
267  // we keep track of the point which is the closest to the cell
268  // center (in 2D)
269  CCVector2d C((i + 0.5) * gridStep, (j + 0.5) * gridStep);
270  const CCVector3* Q = cloud->getPoint(
271  aCell.pointIndex); // former closest point
272  CCVector3d relativePosQ =
274 
275  double distToP =
276  (C - CCVector2d(relativePos.u[X], relativePos.u[Y]))
277  .norm2();
278  double distToQ =
279  (C - CCVector2d(relativePosQ.u[X], relativePosQ.u[Y]))
280  .norm2();
281  if (distToP < distToQ) {
282  aCell.pointIndex = n;
283  }
284 
285  if (hasColors) {
286  assert(cloud->hasColors());
287  const ecvColor::Rgb& col = cloud->getPointColor(n);
288  aCell.color += CCVector3d(col.r, col.g, col.b);
289  }
290  }
291  } else {
292  aCell.minHeight = aCell.maxHeight = P->u[Z];
293  aCell.pointIndex = n;
294 
295  if (hasColors) {
296  assert(cloud->hasColors());
297  const ecvColor::Rgb& col = cloud->getPointColor(n);
298  aCell.color = CCVector3d(col.r, col.g, col.b);
299  }
300  }
301 
302  // sum the points heights
303  double Pz = P->u[Z];
304  aCell.avgHeight += Pz;
305  aCell.stdDevHeight += Pz * Pz;
306 
307  // scalar fields
308  if (interpolateSF) {
309  assert(pc);
310 
311  // absolute position of the cell (e.g. in the 2D SF grid(s))
312  int pos = j * static_cast<int>(width) + i;
313  assert(pos < static_cast<int>(gridTotalSize));
314 
315  for (size_t k = 0; k < scalarFields.size(); ++k) {
316  assert(!scalarFields[k].empty());
317 
319  pc->getScalarField(static_cast<unsigned>(k));
320  assert(sf && pos < scalarFields[k].size());
321 
322  ScalarType sfValue = sf->getValue(n);
323 
324  if (ccScalarField::ValidValue(sfValue)) {
325  SF::value_type formerValue = scalarFields[k][pos];
326  if (aCell.nbPoints && std::isfinite(formerValue)) {
327  switch (sfInterpolation) {
328  case PROJ_MINIMUM_VALUE:
329  // keep the minimum value
330  scalarFields[k][pos] = std::min<SF::value_type>(
331  formerValue, sfValue);
332  break;
333  case PROJ_AVERAGE_VALUE:
334  // we sum all values (we will divide them later)
335  scalarFields[k][pos] += sfValue;
336  break;
337  case PROJ_MAXIMUM_VALUE:
338  // keep the maximum value
339  scalarFields[k][pos] = std::max<SF::value_type>(
340  formerValue, sfValue);
341  break;
342  default:
343  assert(false);
344  break;
345  }
346  } else {
347  // for the first (valid) point, we simply have to store
348  // its SF value (in any case)
349  scalarFields[k][pos] = sfValue;
350  }
351  }
352  }
353  }
354 
355  // update the number of points in the cell
356  ++aCell.nbPoints;
357 
358  if (!nProgress.oneStep()) {
359  // process cancelled by user
360  return false;
361  }
362  }
363 
364  // update SF grids for 'average' cases
365  if (sfInterpolation == PROJ_AVERAGE_VALUE) {
366  for (auto& scalarField : scalarFields) {
367  assert(!scalarField.empty());
368 
369  double* _gridSF = scalarField.data();
370  for (unsigned j = 0; j < height; ++j) {
371  Row& row = rows[j];
372  for (unsigned i = 0; i < width; ++i, ++_gridSF) {
373  if (row[i].nbPoints > 1) {
374  if (std::isfinite(*_gridSF)) // valid SF value
375  {
376  *_gridSF /= row[i].nbPoints;
377  }
378  }
379  }
380  }
381  }
382  }
383 
384  // update the main grid (average height and std.dev. computation + current
385  // 'height' value)
386  {
387  for (unsigned j = 0; j < height; ++j) {
388  Row& row = rows[j];
389  for (unsigned i = 0; i < width; ++i) {
390  ccRasterCell& cell = row[i];
391  if (cell.nbPoints > 1) {
392  cell.avgHeight /= cell.nbPoints;
393  cell.stdDevHeight =
394  sqrt(fabs(cell.stdDevHeight / cell.nbPoints -
395  cell.avgHeight * cell.avgHeight));
396  if (hasColors && projectionType == PROJ_AVERAGE_VALUE) {
397  cell.color /= cell.nbPoints;
398  }
399  } else {
400  cell.stdDevHeight = 0;
401  }
402 
403  if (cell.nbPoints != 0) {
404  // set the right 'height' value
405  switch (projectionType) {
406  case PROJ_MINIMUM_VALUE:
407  cell.h = cell.minHeight;
408  break;
409  case PROJ_AVERAGE_VALUE:
410  cell.h = cell.avgHeight;
411  break;
412  case PROJ_MAXIMUM_VALUE:
413  cell.h = cell.maxHeight;
414  break;
415  default:
416  assert(false);
417  break;
418  }
419  }
420  }
421  }
422  }
423 
424  // compute the number of non empty cells
425  nonEmptyCellCount = 0;
426  {
427  for (unsigned i = 0; i < height; ++i)
428  for (unsigned j = 0; j < width; ++j)
429  if (rows[i][j].nbPoints) ++nonEmptyCellCount;
430  }
431 
432  // specific case: interpolate the empty cells
433  if (interpolateEmptyCells) {
434  std::vector<CCVector2> the2DPoints;
435  if (nonEmptyCellCount < 3) {
437  "[Rasterize] Not enough non-empty cells for "
438  "interpolation!");
439  } else if (nonEmptyCellCount <
440  width * height) // otherwise it's useless!
441  {
442  try {
443  the2DPoints.resize(nonEmptyCellCount);
444  } catch (const std::bad_alloc&) {
445  // out of memory
447  "[Rasterize] Not enough memory to interpolate empty "
448  "cells!");
449  }
450  }
451 
452  // fill 2D vector with non-empty cell indexes
453  if (!the2DPoints.empty()) {
454  unsigned index = 0;
455  for (unsigned j = 0; j < height; ++j) {
456  const Row& row = rows[j];
457  for (unsigned i = 0; i < width; ++i) {
458  if (row[i].nbPoints) {
459  // we only use the non-empty cells for interpolation
460  the2DPoints[index++] =
461  CCVector2(static_cast<PointCoordinateType>(i),
462  static_cast<PointCoordinateType>(j));
463  }
464  }
465  }
466  assert(index == nonEmptyCellCount);
467 
468  // mesh the '2D' points
469  cloudViewer::Delaunay2dMesh delaunayMesh;
470  std::string errorStr;
471  if (delaunayMesh.buildMesh(
472  the2DPoints,
474  errorStr)) {
475  unsigned triNum = delaunayMesh.size();
476  // now we are going to 'project' all triangles on the grid
477  delaunayMesh.placeIteratorAtBeginning();
478  for (unsigned k = 0; k < triNum; ++k) {
479  const cloudViewer::VerticesIndexes* tsi =
480  delaunayMesh.getNextTriangleVertIndexes();
481  // get the triangle bounding box (in grid coordinates)
482  int P[3][2];
483  int xMin = 0, yMin = 0, xMax = 0, yMax = 0;
484  {
485  for (unsigned j = 0; j < 3; ++j) {
486  const CCVector2& P2D = the2DPoints[tsi->i[j]];
487  P[j][0] = static_cast<int>(P2D.x);
488  P[j][1] = static_cast<int>(P2D.y);
489  }
490  xMin = std::min(std::min(P[0][0], P[1][0]), P[2][0]);
491  yMin = std::min(std::min(P[0][1], P[1][1]), P[2][1]);
492  xMax = std::max(std::max(P[0][0], P[1][0]), P[2][0]);
493  yMax = std::max(std::max(P[0][1], P[1][1]), P[2][1]);
494  }
495  // now scan the cells
496  {
497  // pre-computation for barycentric coordinates
498  const double& valA = rows[P[0][1]][P[0][0]].h;
499  const double& valB = rows[P[1][1]][P[1][0]].h;
500  const double& valC = rows[P[2][1]][P[2][0]].h;
501 
502  double det = static_cast<double>(
503  (P[1][1] - P[2][1]) * (P[0][0] - P[2][0]) +
504  (P[2][0] - P[1][0]) * (P[0][1] - P[2][1]));
505 
506  for (int j = yMin; j <= yMax; ++j) {
507  Row& row = rows[static_cast<unsigned>(j)];
508 
509  for (int i = xMin; i <= xMax; ++i) {
510  // if the cell is empty
511  if (!row[i].nbPoints) {
512  // we test if it's included or not in the
513  // current triangle Point Inclusion in
514  // Polygon Test (inspired from W. Randolph
515  // Franklin - WRF)
516  bool inside = false;
517  for (int ti = 0; ti < 3; ++ti) {
518  const int* P1 = P[ti];
519  const int* P2 = P[(ti + 1) % 3];
520  if ((P2[1] <= j && j < P1[1]) ||
521  (P1[1] <= j && j < P2[1])) {
522  int t = (i - P2[0]) *
523  (P1[1] - P2[1]) -
524  (P1[0] - P2[0]) *
525  (j - P2[1]);
526  if (P1[1] < P2[1]) t = -t;
527  if (t < 0) inside = !inside;
528  }
529  }
530  // can we interpolate?
531  if (inside) {
532  double l1 = ((P[1][1] - P[2][1]) *
533  (i - P[2][0]) +
534  (P[2][0] - P[1][0]) *
535  (j - P[2][1])) /
536  det;
537  double l2 = ((P[2][1] - P[0][1]) *
538  (i - P[2][0]) +
539  (P[0][0] - P[2][0]) *
540  (j - P[2][1])) /
541  det;
542  double l3 = 1.0 - l1 - l2;
543 
544  row[i].h = l1 * valA + l2 * valB +
545  l3 * valC;
546  assert(std::isfinite(row[i].h));
547 
548  // interpolate color as well!
549  if (hasColors) {
550  const CCVector3d& colA =
551  rows[P[0][1]][P[0][0]]
552  .color;
553  const CCVector3d& colB =
554  rows[P[1][1]][P[1][0]]
555  .color;
556  const CCVector3d& colC =
557  rows[P[2][1]][P[2][0]]
558  .color;
559  row[i].color = l1 * colA +
560  l2 * colB +
561  l3 * colC;
562  }
563 
564  // interpolate the SFs as well!
565  for (auto& gridSF : scalarFields) {
566  assert(!gridSF.empty());
567 
568  const double& sfValA =
569  gridSF[P[0][0] +
570  P[0][1] * width];
571  const double& sfValB =
572  gridSF[P[1][0] +
573  P[1][1] * width];
574  const double& sfValC =
575  gridSF[P[2][0] +
576  P[2][1] * width];
577  assert(i + j * width <
578  gridSF.size());
579  gridSF[i + j * width] =
580  l1 * sfValA + l2 * sfValB +
581  l3 * sfValC;
582  }
583  }
584  }
585  }
586  }
587  }
588  }
589  } else {
590  CVLog::Warning(QString("[Rasterize] Empty cells interpolation "
591  "failed: Triangle lib. said '%1'")
592  .arg(QString::fromStdString(errorStr)));
593  }
594  }
595  }
596 
597  // computation of the average and extreme height values in the grid
598  {
599  minHeight = 0;
600  maxHeight = 0;
601  meanHeight = 0;
602  validCellCount = 0;
603 
604  for (unsigned i = 0; i < height; ++i) {
605  for (unsigned j = 0; j < width; ++j) {
606  double h = rows[i][j].h;
607 
608  if (std::isfinite(h)) // valid height
609  {
610  if (validCellCount) {
611  if (h < minHeight)
612  minHeight = h;
613  else if (h > maxHeight)
614  maxHeight = h;
615 
616  meanHeight += h;
617  } else {
618  // first valid cell
620  }
621  ++validCellCount;
622  }
623  }
624  }
625 
626  if (validCellCount) {
628  }
629  }
630 
631  setValid(true);
632 
633  return true;
634 }
635 
637  nonEmptyCellCount = 0;
638  {
639  for (unsigned i = 0; i < height; ++i)
640  for (unsigned j = 0; j < width; ++j)
641  if (rows[i][j].nbPoints) ++nonEmptyCellCount;
642  }
643  return nonEmptyCellCount;
644 }
645 
647  minHeight = 0;
648  maxHeight = 0;
649  meanHeight = 0;
650  validCellCount = 0;
651  size_t emptyCellCount = 0;
652 
653  for (unsigned i = 0; i < height; ++i) {
654  for (unsigned j = 0; j < width; ++j) {
655  double h = rows[i][j].h;
656 
657  if (std::isfinite(h)) // valid height
658  {
659  if (validCellCount) {
660  if (h < minHeight)
661  minHeight = h;
662  else if (h > maxHeight)
663  maxHeight = h;
664 
665  meanHeight += h;
666  } else {
667  // first valid cell
669  }
670  ++validCellCount;
671  } else {
672  ++emptyCellCount;
673  }
674  }
675  }
676 
677  if (validCellCount) {
679  }
680 }
681 
683  double customCellHeight /*=0*/) {
684  // fill empty cells (all but the 'INTERPOLATE_DELAUNAY' case)
685  if (fillEmptyCellsStrategy != LEAVE_EMPTY &&
686  fillEmptyCellsStrategy != INTERPOLATE_DELAUNAY) {
687  double defaultHeight = std::numeric_limits<double>::quiet_NaN();
688  switch (fillEmptyCellsStrategy) {
689  case FILL_MINIMUM_HEIGHT:
690  defaultHeight = minHeight;
691  break;
692  case FILL_MAXIMUM_HEIGHT:
693  defaultHeight = maxHeight;
694  break;
695  case FILL_CUSTOM_HEIGHT:
696  defaultHeight = customCellHeight;
697  break;
698  case FILL_AVERAGE_HEIGHT:
699  defaultHeight = meanHeight;
700  break;
701  default:
702  assert(false);
703  break;
704  }
705  assert(defaultHeight != 0);
706 
707  for (unsigned i = 0; i < height; ++i) {
708  for (unsigned j = 0; j < width; ++j) {
709  if (!std::isfinite(rows[i][j].h)) // empty cell (NaN)
710  {
711  rows[i][j].h = defaultHeight;
712  }
713  }
714  }
715  }
716 }
717 
719  const std::vector<ExportableFields>& exportedFields,
720  bool interpolateSF,
721  bool interpolateColors,
722  bool resampleInputCloudXY,
723  bool resampleInputCloudZ,
724  ccGenericPointCloud* inputCloud,
725  unsigned char Z,
726  const ccBBox& box,
727  bool fillEmptyCells,
728  double emptyCellsHeight,
729  bool exportToOriginalCS) const {
730  if (Z > 2 || !box.isValid()) {
731  // invalid input parameters
732  assert(false);
733  return nullptr;
734  }
735 
736  if (!isValid()) {
737  return nullptr;
738  }
739 
740  unsigned pointsCount = (fillEmptyCells ? width * height : validCellCount);
741  if (pointsCount == 0) {
742  CVLog::Warning("[Rasterize] Empty grid!");
743  return nullptr;
744  }
745 
746  ccPointCloud* cloudGrid = nullptr;
747 
748  // if we 'resample' the input cloud, we actually resample it (one point in
749  // each cell) and we may have to change some things aftewards (height,
750  // scalar fields, etc.)
751  if (resampleInputCloudXY) {
752  if (!inputCloud) {
754  "[Rasterize] Internal error: no input clouds specified "
755  "(for resampling)");
756  assert(false);
757  return nullptr;
758  }
759  cloudViewer::ReferenceCloud refCloud(inputCloud);
760  if (!refCloud.reserve(nonEmptyCellCount)) {
761  CVLog::Warning("[Rasterize] Not enough memory!");
762  return nullptr;
763  }
764 
765  for (unsigned j = 0; j < height; ++j) {
766  for (unsigned i = 0; i < width; ++i) {
767  const ccRasterCell& cell = rows[j][i];
768  if (cell.nbPoints) // non empty cell
769  {
770  refCloud.addPointIndex(cell.pointIndex);
771  }
772  }
773  }
774 
775  assert(refCloud.size() != 0);
776  cloudGrid = inputCloud->isA(CV_TYPES::POINT_CLOUD)
777  ? static_cast<ccPointCloud*>(inputCloud)
778  ->partialClone(&refCloud)
779  : ccPointCloud::From(&refCloud, inputCloud);
780  if (!cloudGrid) {
781  CVLog::Error("[Rasterize] Not enough memory");
782  return nullptr;
783  }
784  cloudGrid->setPointSize(
785  0); // 0 = default size (to avoid display issues)
786 
787  if (!resampleInputCloudZ) {
788  // we have to use the grid height instead of the original point
789  // height!
790  unsigned pointIndex = 0;
791  for (unsigned j = 0; j < height; ++j) {
792  for (unsigned i = 0; i < width; ++i) {
793  const ccRasterCell& cell = rows[j][i];
794  if (cell.nbPoints) // non empty cell
795  {
796  const_cast<CCVector3*>(cloudGrid->getPoint(pointIndex))
797  ->u[Z] =
798  static_cast<PointCoordinateType>(cell.h);
799  ++pointIndex;
800  }
801  }
802  }
803  }
804 
805  if (!interpolateSF && !fillEmptyCells) {
806  // DGM: we can't stop right away (even if we have already resampled
807  // the original cloud, we may have to create additional points
808  // and/or scalar fields) return cloudGrid;
809  }
810  } else {
811  cloudGrid = new ccPointCloud("grid");
812  }
813  assert(cloudGrid);
814 
815  // shall we generate additional scalar fields?
816  std::vector<cloudViewer::ScalarField*> exportedSFs;
817  if (!exportedFields.empty()) {
818  exportedSFs.resize(exportedFields.size(), nullptr);
819  for (size_t i = 0; i < exportedFields.size(); ++i) {
820  int sfIndex = -1;
821  switch (exportedFields[i]) {
822  case PER_CELL_VALUE:
823  case PER_CELL_COUNT:
824  case PER_CELL_MIN_VALUE:
825  case PER_CELL_MAX_VALUE:
826  case PER_CELL_AVG_VALUE:
831  case PER_CELL_VALUE_RANGE: {
832  QString sfName = GetDefaultFieldName(exportedFields[i]);
833  sfIndex = cloudGrid->getScalarFieldIndexByName(
834  qPrintable(sfName));
835  if (sfIndex >= 0) {
837  QString("[Rasterize] Scalar field '%1' already "
838  "exists. It will be overwritten.")
839  .arg(sfName));
840  } else {
841  sfIndex = cloudGrid->addScalarField(qPrintable(sfName));
842  }
843  } break;
844  default:
845  assert(false);
846  break;
847  }
848 
849  if (sfIndex < 0) {
851  "[Rasterize] Couldn't allocate scalar field(s)! Try to "
852  "free some memory ...");
853  break;
854  }
855 
856  exportedSFs[i] = cloudGrid->getScalarField(sfIndex);
857  assert(exportedSFs[i]);
858  }
859  }
860 
861  if (resampleInputCloudXY) {
862  // if the cloud already has colors and we add the empty cells, we must
863  // also add (black) colors
864  interpolateColors = (cloudGrid->hasColors() && fillEmptyCells);
865  } else {
866  // we need colors to interpolate them!
868  }
869 
870  // the resampled cloud already contains the points corresponding to 'filled'
871  // cells so we will only need to add the empty ones (if requested)
872  if (!resampleInputCloudXY || fillEmptyCells) {
873  if (!cloudGrid->reserve(pointsCount)) {
874  CVLog::Warning("[Rasterize] Not enough memory!");
875  delete cloudGrid;
876  return nullptr;
877  }
878 
879  if (interpolateColors && !cloudGrid->reserveTheRGBTable()) {
881  "[Rasterize] Not enough memory to interpolate colors!");
882  cloudGrid->unallocateColors();
883  interpolateColors = false;
884  }
885  }
886 
887  // horizontal dimensions
888  const unsigned char X = (Z == 2 ? 0 : Z + 1);
889  const unsigned char Y = (X == 2 ? 0 : X + 1);
890 
891  const unsigned char outX = (exportToOriginalCS ? X : 0);
892  const unsigned char outY = (exportToOriginalCS ? Y : 1);
893  const unsigned char outZ = (exportToOriginalCS ? Z : 2);
894 
895  // as the 'non empty cells points' are already in the cloud
896  // we must take care of where we put the scalar fields values!
897  unsigned nonEmptyCellIndex = 0;
898 
899  // we work with doubles as the grid step can be much smaller than the cloud
900  // coordinates!
901  double Py = box.minCorner().u[Y] /* + gridStep / 2*/;
902 
903  for (unsigned j = 0; j < height; ++j) {
904  const ccRasterCell* aCell = rows[j].data();
905  double Px = box.minCorner().u[X] /* + gridStep / 2*/;
906 
907  for (unsigned i = 0; i < width; ++i, ++aCell) {
908  if (std::isfinite(
909  aCell->h)) // valid cell (could have been interpolated)
910  {
911  // if we haven't resampled the original cloud, we must add the
912  // point corresponding to this non-empty cell
913  if (!resampleInputCloudXY || aCell->nbPoints == 0) {
914  CCVector3 Pf;
915  Pf.u[outX] = static_cast<PointCoordinateType>(Px);
916  Pf.u[outY] = static_cast<PointCoordinateType>(Py);
917  Pf.u[outZ] = static_cast<PointCoordinateType>(aCell->h);
918 
919  assert(cloudGrid->size() < cloudGrid->capacity());
920  cloudGrid->addPoint(Pf);
921 
922  if (interpolateColors) {
923  ecvColor::Rgb col(static_cast<ColorCompType>(std::min(
924  255.0, aCell->color.x)),
925  static_cast<ColorCompType>(std::min(
926  255.0, aCell->color.y)),
927  static_cast<ColorCompType>(std::min(
928  255.0, aCell->color.z)));
929 
930  cloudGrid->addRGBColor(col);
931  }
932  }
933 
934  // fill the associated SFs
935  assert(!resampleInputCloudXY || inputCloud);
936  assert(!resampleInputCloudXY ||
937  nonEmptyCellIndex <
938  inputCloud->size()); // we can't be here if we
939  // have a fully resampled
940  // cloud!
941  // (resampleInputCloudXY
942  // implies that inputCloud
943  // is defined)
944  assert(exportedSFs.size() == exportedFields.size());
945  for (size_t k = 0; k < exportedSFs.size(); ++k) {
946  cloudViewer::ScalarField* sf = exportedSFs[k];
947  if (!sf) {
948  continue;
949  }
950 
951  ScalarType sVal = NAN_VALUE;
952  switch (exportedFields[k]) {
953  case PER_CELL_VALUE:
954  sVal = static_cast<ScalarType>(aCell->h);
955  break;
956  case PER_CELL_COUNT:
957  sVal = static_cast<ScalarType>(aCell->nbPoints);
958  break;
959  case PER_CELL_MIN_VALUE:
960  sVal = static_cast<ScalarType>(aCell->minHeight);
961  break;
962  case PER_CELL_MAX_VALUE:
963  sVal = static_cast<ScalarType>(aCell->maxHeight);
964  break;
965  case PER_CELL_AVG_VALUE:
966  sVal = static_cast<ScalarType>(aCell->avgHeight);
967  break;
969  sVal = static_cast<ScalarType>(aCell->stdDevHeight);
970  break;
972  sVal = static_cast<ScalarType>(aCell->maxHeight -
973  aCell->minHeight);
974  break;
975  default:
976  assert(false);
977  break;
978  }
979 
980  if (resampleInputCloudXY) {
981  if (aCell->nbPoints != 0) {
982  // previously existing point
983  assert(nonEmptyCellIndex < inputCloud->size());
984  assert(nonEmptyCellIndex < sf->size());
985  sf->setValue(nonEmptyCellIndex, sVal);
986  } else {
987  // new point
988  assert(sf->size() < sf->capacity());
989  sf->addElement(sVal);
990  }
991  } else {
992  // new point
993  assert(sf->size() < sf->capacity());
994  sf->addElement(sVal);
995  }
996  }
997 
998  if (aCell->nbPoints != 0) {
999  ++nonEmptyCellIndex;
1000  }
1001  } else if (fillEmptyCells) // empty cell
1002  {
1003  // even if we have resampled the original cloud, we must add the
1004  // point corresponding to this empty cell
1005  {
1006  CCVector3 Pf;
1007  Pf.u[outX] = static_cast<PointCoordinateType>(Px);
1008  Pf.u[outY] = static_cast<PointCoordinateType>(Py);
1009  Pf.u[outZ] =
1010  static_cast<PointCoordinateType>(emptyCellsHeight);
1011 
1012  assert(cloudGrid->size() < cloudGrid->capacity());
1013  cloudGrid->addPoint(Pf);
1014 
1015  if (interpolateColors) {
1016  cloudGrid->addRGBColor(ecvColor::black);
1017  }
1018  }
1019 
1020  assert(exportedSFs.size() == exportedFields.size());
1021  for (size_t k = 0; k < exportedSFs.size(); ++k) {
1022  cloudViewer::ScalarField* sf = exportedSFs[k];
1023  if (!sf) {
1024  continue;
1025  }
1026 
1027  if (exportedFields[k] == PER_CELL_VALUE) {
1028  // we set the point height to the default height
1029  ScalarType s =
1030  static_cast<ScalarType>(emptyCellsHeight);
1031  assert(sf->size() < sf->capacity());
1032  sf->addElement(s);
1033  } else {
1034  assert(sf->size() < sf->capacity());
1035  sf->addElement(NAN_VALUE);
1036  }
1037  }
1038  }
1039 
1040  Px += gridStep;
1041  }
1042 
1043  Py += gridStep;
1044  }
1045 
1046  // finish the SFs initialization (if any)
1047  for (auto sf : exportedSFs) {
1048  if (sf) {
1049  sf->computeMinAndMax();
1050  }
1051  }
1052 
1053  // take care of former scalar fields
1054  if (!resampleInputCloudXY) {
1055  // do we need to interpolate the original SFs?
1056  if (interpolateSF && inputCloud &&
1057  inputCloud->isA(CV_TYPES::POINT_CLOUD)) {
1058  ccPointCloud* pc = static_cast<ccPointCloud*>(inputCloud);
1059  assert(scalarFields.size() == pc->getNumberOfScalarFields());
1060 
1061  for (size_t k = 0; k < scalarFields.size(); ++k) {
1062  assert(!scalarFields[k].empty());
1063 
1064  // the corresponding SF should exist on the input cloud
1065  ccScalarField* formerSf = static_cast<ccScalarField*>(
1066  pc->getScalarField(static_cast<int>(k)));
1067  assert(formerSf);
1068 
1069  // we try to create an equivalent SF on the output grid
1070  int sfIdx = cloudGrid->addScalarField(formerSf->getName());
1071  if (sfIdx < 0) // if we aren't lucky, the input cloud already
1072  // had a SF with the same name
1073  {
1074  sfIdx = cloudGrid->addScalarField(qPrintable(
1075  QString(formerSf->getName()).append(".old")));
1076  }
1077 
1078  if (sfIdx < 0) {
1080  "[Rasterize] Failed to allocate a new scalar field "
1081  "for storing SF '%s' values! Try to free some "
1082  "memory ...",
1083  formerSf->getName());
1084  } else {
1085  ccScalarField* sf = static_cast<ccScalarField*>(
1086  cloudGrid->getScalarField(sfIdx));
1087  assert(sf);
1088  // set sf values
1089  unsigned n = 0;
1090  const ScalarType emptyCellSFValue =
1092  const double* _sfGrid = scalarFields[k].data();
1093  for (unsigned j = 0; j < height; ++j) {
1094  const ccRasterGrid::Row& row = rows[j];
1095  for (unsigned i = 0; i < width; ++i, ++_sfGrid) {
1096  if (std::isfinite(
1097  row[i].h)) // valid cell (could have
1098  // been interpolated)
1099  {
1100  ScalarType s =
1101  static_cast<ScalarType>(*_sfGrid);
1102  sf->setValue(n++, s);
1103  } else if (fillEmptyCells) {
1104  sf->setValue(n++, emptyCellSFValue);
1105  }
1106  }
1107  }
1108  sf->computeMinAndMax();
1109  sf->importParametersFrom(formerSf);
1110  assert(sf->currentSize() == pointsCount);
1111  }
1112  }
1113  }
1114  } else // the cloud has already been resampled
1115  {
1116  // we simply add NAN values at the end of the SFs
1117  for (int k = 0;
1118  k < static_cast<int>(cloudGrid->getNumberOfScalarFields()); ++k) {
1119  cloudViewer::ScalarField* sf = cloudGrid->getScalarField(k);
1120  sf->resizeSafe(cloudGrid->size(), true, NAN_VALUE);
1121  }
1122  }
1123 
1124  QString gridName = QString("raster(%1)").arg(gridStep);
1125  if (inputCloud) {
1126  gridName.prepend(inputCloud->getName() + QString("."));
1127  }
1128  cloudGrid->setName(gridName);
1129 
1130  return cloudGrid;
1131 }
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
Vector2Tpl< double > CCVector2d
Double 2D Vector.
Definition: CVGeom.h:783
Vector2Tpl< PointCoordinateType > CCVector2
Default 2D Vector.
Definition: CVGeom.h:780
Vector3Tpl< double > CCVector3d
Double 3D Vector.
Definition: CVGeom.h:804
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int width
int size
int height
void * X
Definition: SmallVector.cpp:45
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:36
Type y
Definition: CVGeom.h:36
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
Bounding box structure.
Definition: ecvBBox.h:25
virtual bool hasColors() const
Returns whether colors are enabled or not.
A 3D cloud interface with associated features (color, normals, octree, etc.)
void setPointSize(unsigned size=0)
Sets point size.
virtual const ecvColor::Rgb & getPointColor(unsigned pointIndex) const =0
Returns color corresponding to a given point.
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void unallocateColors()
Erases the cloud colors.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
static ccPointCloud * From(const cloudViewer::GenericIndexedCloud *cloud, const ccGenericPointCloud *sourceCloud=nullptr)
Creates a new point cloud object from a GenericIndexedCloud.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
bool hasColors() const override
Returns whether colors are enabled or not.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
bool hasScalarFields() const override
Returns whether one or more scalar fields are instantiated.
A scalar field associated to display-related parameters.
void computeMinAndMax() override
Determines the min and max values.
void importParametersFrom(const ccScalarField *sf)
Imports the parameters from another scalar field.
const Vector3Tpl< T > & maxCorner() const
Returns max corner (const)
Definition: BoundingBox.h:156
const Vector3Tpl< T > & minCorner() const
Returns min corner (const)
Definition: BoundingBox.h:154
bool isValid() const
Returns whether bounding box is valid or not.
Definition: BoundingBox.h:203
A class to compute and handle a Delaunay 2D mesh on a subset of points.
virtual unsigned size() const override
Returns the number of triangles.
void placeIteratorAtBeginning() override
Places the mesh iterator at the beginning.
virtual bool buildMesh(const std::vector< CCVector2 > &points2D, std::size_t pointCountToUse, std::string &outputErrorStr)
Build the Delaunay mesh on top a set of 2D points.
VerticesIndexes * getNextTriangleVertIndexes() override
static constexpr int USE_ALL_POINTS
virtual unsigned size() const =0
Returns the number of points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
bool oneStep()
Increments total progress value of a single unit.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
unsigned capacity() const
Returns cloud capacity (i.e. reserved size)
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
unsigned size() const override
Returns the number of points.
virtual bool reserve(unsigned n)
Reserves some memory for hosting the point references.
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
void addElement(ScalarType value)
Definition: ScalarField.h:99
static ScalarType NaN()
Returns the specific NaN value.
Definition: ScalarField.h:46
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
bool resizeSafe(std::size_t count, bool initNewElements=false, ScalarType valueForNewElements=0)
Resizes memory (no exception thrown)
Definition: ScalarField.cpp:81
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
Definition: ScalarField.h:61
unsigned currentSize() const
Definition: ScalarField.h:100
RGB color structure.
Definition: ecvColorTypes.h:49
Graphical progress indicator (thread-safe)
virtual void setInfo(const char *infoStr) override
Notifies some information about the ongoing process.
virtual void start() override
virtual void setMethodTitle(const char *methodTitle) override
Notifies the algorithm title.
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
static DefaultFieldNames s_defaultFieldNames
@ POINT_CLOUD
Definition: CVTypes.h:104
bool interpolateColors(const ccHObject::Container &selectedEntities, QWidget *parent)
Interpolate colors from on entity and transfer them to another one.
constexpr Rgb black(0, 0, 0)
cloudViewer::NormalizedProgress * nProgress
Raster grid cell.
Definition: ecvRasterGrid.h:22
double avgHeight
Average height value.
Definition: ecvRasterGrid.h:37
CCVector3d color
Color.
Definition: ecvRasterGrid.h:49
double h
Height value.
Definition: ecvRasterGrid.h:35
unsigned nbPoints
Number of points projected in this cell.
Definition: ecvRasterGrid.h:45
unsigned pointIndex
Nearest point index (if any)
Definition: ecvRasterGrid.h:47
PointCoordinateType maxHeight
Max height value.
Definition: ecvRasterGrid.h:43
PointCoordinateType minHeight
Min height value.
Definition: ecvRasterGrid.h:41
double stdDevHeight
Height std.dev.
Definition: ecvRasterGrid.h:39
ExportableFields
Exportable fields.
Definition: ecvRasterGrid.h:79
@ PER_CELL_UNIQUE_COUNT_VALUE
Definition: ecvRasterGrid.h:89
@ PER_CELL_PERCENTILE_VALUE
Definition: ecvRasterGrid.h:88
@ PER_CELL_VALUE_STD_DEV
Definition: ecvRasterGrid.h:85
void updateCellStats()
Updates the statistics about the cells.
void clear()
Clears the grid.
unsigned validCellCount
Number of VALID cells.
unsigned nonEmptyCellCount
Number of NON-EMPTY cells.
void setValid(bool state)
Sets valid.
ProjectionType
Types of projection.
double gridStep
Grid step ('pixel' size)
ccRasterGrid()
Default constructor.
std::vector< ccRasterCell > Row
Row.
unsigned height
Number of rows.
bool hasColors
Whether the (average) colors are available or not.
bool init(unsigned w, unsigned h, double gridStep, const CCVector3d &minCorner)
Initializes / resets the grid.
double meanHeight
Average height (computed on the NON-EMPTY or INTERPOLATED cells)
bool fillWith(ccGenericPointCloud *cloud, unsigned char projectionDimension, ProjectionType projectionType, bool interpolateEmptyCells, ProjectionType sfInterpolation=INVALID_PROJECTION_TYPE, ecvProgressDialog *progressDialog=nullptr)
Fills the grid with a point cloud.
std::vector< SF > scalarFields
Associated scalar fields.
static QString GetDefaultFieldName(ExportableFields field)
Returns the default name of a given field.
CCVector3d minCorner
Min corner (3D)
void reset()
Resets the grid.
std::vector< Row > rows
All cells.
double maxHeight
Max height (computed on the NON-EMPTY or INTERPOLATED cells)
virtual ~ccRasterGrid()
Destructor.
unsigned updateNonEmptyCellCount()
Updates the number of non-empty cells.
bool isValid() const
Returns whether the grid is 'valid' or not.
unsigned width
Number of columns.
ccPointCloud * convertToCloud(const std::vector< ExportableFields > &exportedFields, bool interpolateSF, bool interpolateColors, bool resampleInputCloudXY, bool resampleInputCloudZ, ccGenericPointCloud *inputCloud, unsigned char Z, const ccBBox &box, bool fillEmptyCells, double emptyCellsHeight, bool exportToOriginalCS) const
Converts the grid to a cloud with scalar field(s)
EmptyCellFillOption
Option for handling empty cells.
static bool ComputeGridSize(unsigned char Z, const ccBBox &box, double gridStep, unsigned &width, unsigned &height)
Computes the raster size for a given bounding-box.
double minHeight
Min height (computed on the NON-EMPTY or INTERPOLATED cells)
void fillEmptyCells(EmptyCellFillOption fillEmptyCellsStrategy, double customCellHeight=0)
Fills the empty cell (for all strategies but 'INTERPOLATE_DELAUNAY')
Triangle described by the indexes of its 3 vertices.