ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
vtk2cc.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 <Utils/vtk2cc.h>
9 
10 #ifdef _MSC_VER
11 #pragma warning(disable : 4996) // Use of [[deprecated]] feature
12 #endif
13 
14 // Local
15 #include <Utils/PCLCloud.h>
16 #include <Utils/PCLConv.h>
17 #include <Utils/cc2sm.h>
18 #include <Utils/my_point_types.h>
19 #include <Utils/sm2cc.h>
20 
21 // PCL
22 #include <pcl/common/io.h>
23 #include <pcl/io/vtk_io.h>
24 #include <pcl/io/vtk_lib_io.h>
25 
26 #include <pcl/io/impl/vtk_lib_io.hpp>
27 
28 // CV_CORE_LIB
29 #include <CVGeom.h>
30 
31 // CV_DB_LIB
32 #include <ecvHObject.h>
33 #include <ecvHObjectCaster.h>
34 #include <ecvMesh.h>
35 #include <ecvPointCloud.h>
36 #include <ecvPolyline.h>
37 #include <ecvScalarField.h>
38 
39 // CV_CORE_LIB
40 #include <CVLog.h>
41 
42 // VTK
43 #include <vtkFloatArray.h>
44 #include <vtkPolyData.h>
45 
46 // Support for VTK 7.1 upwards
47 #ifdef vtkGenericDataArray_h
48 #define SetTupleValue SetTypedTuple
49 #define InsertNextTupleValue InsertNextTypedTuple
50 #define GetTupleValue GetTypedTuple
51 #endif
52 
53 ccPointCloud* vtk2cc::ConvertToPointCloud(vtkPolyData* polydata, bool silent) {
54  if (!polydata) {
55  if (!silent) {
56  CVLog::Warning("[vtk2cc::ConvertToPointCloud] polydata is nullptr");
57  }
58  return nullptr;
59  }
60 
61  vtkIdType pointCount = polydata->GetNumberOfPoints();
62 
63  // Check for empty polydata
64  if (pointCount == 0) {
65  if (!silent) {
67  "[vtk2cc::ConvertToPointCloud] polydata has 0 points");
68  }
69  return nullptr;
70  }
71 
72  vtkPointData* pointData = polydata->GetPointData();
73  vtkFieldData* fieldData = polydata->GetFieldData();
74 
75  // Get colors - ONLY use colors if:
76  // 1. "HasSourceRGB" flag is set in FieldData (indicating actual source RGB
77  // data)
78  // 2. OR we find a specifically named "RGB" or "Colors" array with unsigned
79  // char data This prevents scalar field data from being mistakenly treated
80  // as colors
81  vtkUnsignedCharArray* colors = nullptr;
82  bool hasSourceRGBFlag = false;
83 
84  // Check for HasSourceRGB flag first
85  if (fieldData) {
86  vtkIntArray* hasRGBArray =
87  vtkIntArray::SafeDownCast(fieldData->GetArray("HasSourceRGB"));
88  if (hasRGBArray && hasRGBArray->GetValue(0) == 1) {
89  hasSourceRGBFlag = true;
90  }
91  }
92 
93  if (pointData) {
94  // STRICT: Only use colors if HasSourceRGB flag is explicitly set
95  // This prevents scalar fields (like curvature shown as color) from
96  // being mistakenly treated as actual RGB color data
97  if (hasSourceRGBFlag) {
98  // Look for explicitly named RGB arrays WITH 3 or 4 components
99  const char* colorArrayNames[] = {"RGB", "Colors", "rgba", "rgb"};
100  for (const char* name : colorArrayNames) {
101  vtkDataArray* arr = pointData->GetArray(name);
102  if (arr) {
103  if (arr->GetNumberOfComponents() == 3 ||
104  arr->GetNumberOfComponents() == 4) {
105  colors = vtkUnsignedCharArray::SafeDownCast(arr);
106  if (colors) break;
107  }
108  }
109  }
110  }
111  // Note: We intentionally do NOT fall back to searching by array name
112  // without the HasSourceRGB flag. This prevents scalar fields
113  // (curvature, intensity, etc.) from being confused with actual RGB
114  // colors.
115  }
116 
117  // Get normals - first try active normals, then fallback to named arrays
118  vtkFloatArray* normals = nullptr;
119  if (pointData) {
120  // First try active normals
121  normals = vtkFloatArray::SafeDownCast(pointData->GetNormals());
122 
123  // If no active normals, look for arrays named "Normals"
124  if (!normals) {
125  vtkDataArray* arr = pointData->GetArray("Normals");
126  if (arr && arr->GetNumberOfComponents() == 3) {
127  normals = vtkFloatArray::SafeDownCast(arr);
128  }
129  }
130  }
131 
132  // create cloud
133  ccPointCloud* cloud = new ccPointCloud("vertices");
134 
135  if (!cloud->resize(static_cast<unsigned>(pointCount))) {
136  if (!silent) {
137  CVLog::Warning(QString(
138  "[vtk2cc::ConvertToPointCloud] not enough memory!"));
139  }
140  delete cloud;
141  cloud = nullptr;
142  return nullptr;
143  }
144 
145  if (normals && !cloud->reserveTheNormsTable()) {
146  if (!silent) {
148  QString("[getPointCloudFromPolyData] not enough memory!"));
149  }
150  delete cloud;
151  cloud = nullptr;
152  return nullptr;
153  }
154 
155  if (colors && !cloud->reserveTheRGBTable()) {
156  if (!silent) {
158  QString("[getPointCloudFromPolyData] not enough memory!"));
159  }
160  delete cloud;
161  cloud = nullptr;
162  return nullptr;
163  }
164 
165  for (vtkIdType i = 0; i < pointCount; ++i) {
166  double coordinate[3];
167  polydata->GetPoint(i, coordinate);
168  cloud->setPoint(static_cast<std::size_t>(i),
169  CCVector3::fromArray(coordinate));
170  if (normals) {
171  float normal[3];
172  normals->GetTupleValue(i, normal);
173  CCVector3 N(static_cast<PointCoordinateType>(normal[0]),
174  static_cast<PointCoordinateType>(normal[1]),
175  static_cast<PointCoordinateType>(normal[2]));
176  cloud->addNorm(N);
177  }
178  if (colors) {
179  unsigned char color[3];
180  colors->GetTupleValue(i, color);
181  ecvColor::Rgb C(static_cast<ColorCompType>(color[0]),
182  static_cast<ColorCompType>(color[1]),
183  static_cast<ColorCompType>(color[2]));
184  cloud->addRGBColor(C);
185  }
186  }
187 
188  if (normals) {
189  cloud->showNormals(true);
190  }
191  if (colors) {
192  cloud->showColors(true);
193  }
194 
195  // Copy scalar fields (labels, intensity, etc.) from point data
196  if (pointData && pointCount > 0) {
197  int numArrays = pointData->GetNumberOfArrays();
198 
199  for (int i = 0; i < numArrays; ++i) {
200  vtkDataArray* dataArray = pointData->GetArray(i);
201 
202  // Only handle single-component (scalar) arrays
203  if (!dataArray || dataArray->GetNumberOfComponents() != 1) {
204  continue;
205  }
206 
207  // Check if the array has valid data
208  if (dataArray->GetNumberOfTuples() < pointCount) {
209  if (!silent) {
211  QString("[vtk2cc] Scalar array %1 has only %2 "
212  "tuples but pointCount is %3")
213  .arg(dataArray->GetName()
214  ? dataArray->GetName()
215  : "(unnamed)")
216  .arg(dataArray->GetNumberOfTuples())
217  .arg(pointCount));
218  }
219  continue;
220  }
221 
222  // Skip arrays already handled as colors/normals
223  if (dataArray == colors || dataArray == normals) {
224  continue;
225  }
226 
227  const char* arrayName = dataArray->GetName();
228  if (!arrayName || strlen(arrayName) == 0) {
229  continue; // Skip unnamed arrays
230  }
231 
232  // Create new scalar field
233  ccScalarField* scalarField = new ccScalarField(arrayName);
234  if (!scalarField->reserveSafe(static_cast<unsigned>(pointCount))) {
235  if (!silent) {
236  CVLog::Warning(QString("[vtk2cc] Failed to allocate scalar "
237  "field: %1")
238  .arg(arrayName));
239  }
240  scalarField->release();
241  continue;
242  }
243 
244  // Resize to ensure space is allocated
245  if (!scalarField->resizeSafe(static_cast<unsigned>(pointCount))) {
246  if (!silent) {
247  CVLog::Warning(QString("[vtk2cc] Failed to resize scalar "
248  "field: %1")
249  .arg(arrayName));
250  }
251  scalarField->release();
252  continue;
253  }
254 
255  // Copy data
256  for (vtkIdType j = 0; j < pointCount; ++j) {
257  double value = dataArray->GetTuple1(j);
258  scalarField->setValue(static_cast<unsigned>(j),
259  static_cast<ScalarType>(value));
260  }
261 
262  scalarField->computeMinAndMax();
263 
264  // Add to point cloud
265  int sfIdx = cloud->addScalarField(scalarField);
266  if (sfIdx < 0) {
267  if (!silent) {
269  QString("[vtk2cc] Failed to add scalar field: %1")
270  .arg(arrayName));
271  }
272  scalarField->release();
273  } else {
274  // Auto-detect and display label fields
275  QString fieldName = QString(arrayName).toLower();
276  if (fieldName.contains("label") ||
277  fieldName.contains("class") ||
278  fieldName.contains("segment") ||
279  fieldName.contains("cluster")) {
280  cloud->setCurrentDisplayedScalarField(sfIdx);
281  cloud->showSF(true);
282  }
283  }
284  }
285  }
286 
287  return cloud;
288 }
289 
290 ccMesh* vtk2cc::ConvertToMesh(vtkPolyData* polydata, bool silent) {
291  if (!polydata) {
292  return nullptr;
293  }
294 
295  vtkSmartPointer<vtkPoints> mesh_points = polydata->GetPoints();
296  if (!mesh_points) {
297  if (!silent) {
299  QString("[getMeshFromPolyData] polydata has no points!"));
300  }
301  return nullptr;
302  }
303 
304  unsigned nr_points =
305  static_cast<unsigned>(mesh_points->GetNumberOfPoints());
306  unsigned nr_polygons = static_cast<unsigned>(polydata->GetNumberOfPolys());
307  if (nr_points == 0) {
308  if (!silent) {
310  QString("[getMeshFromPolyData] cannot find points data!"));
311  }
312  return nullptr;
313  }
314 
315  ccPointCloud* vertices = ConvertToPointCloud(polydata, silent);
316  if (!vertices) {
317  return nullptr;
318  }
319  vertices->setEnabled(false);
320  // DGM: no need to lock it as it is only used by one mesh!
321  vertices->setLocked(false);
322 
323  // mesh
324  ccMesh* mesh = new ccMesh(vertices);
325  mesh->setName("Mesh");
326  mesh->addChild(vertices);
327 
328  if (!mesh->reserve(nr_polygons)) {
329  if (!silent) {
330  CVLog::Warning(QString("[getMeshFromPolyData] not enough memory!"));
331  }
332  delete mesh;
333  return nullptr;
334  }
335 
336 #ifdef VTK_CELL_ARRAY_V2
337  const vtkIdType* cell_points;
338 #else // VTK_CELL_ARRAY_V2
339  vtkIdType* cell_points;
340 #endif // VTK_CELL_ARRAY_V2
341  vtkIdType nr_cell_points;
342  vtkCellArray* mesh_polygons = polydata->GetPolys();
343  mesh_polygons->InitTraversal();
344  unsigned int validTriangles = 0;
345  unsigned int skippedCells = 0;
346 
347  while (mesh_polygons->GetNextCell(nr_cell_points, cell_points)) {
348  if (nr_cell_points != 3) {
349  // Skip non-triangle cells but continue processing
350  ++skippedCells;
351  continue;
352  }
353 
354  mesh->addTriangle(static_cast<unsigned>(cell_points[0]),
355  static_cast<unsigned>(cell_points[1]),
356  static_cast<unsigned>(cell_points[2]));
357  ++validTriangles;
358  }
359 
360  // Check if we have any valid triangles
361  if (validTriangles == 0) {
362  if (!silent) {
363  CVLog::Warning(QString(
364  "[getMeshFromPolyData] No triangles found in polydata"));
365  }
366  delete mesh;
367  return nullptr;
368  }
369 
370  // Log skipped cells if any
371  if (skippedCells > 0 && !silent) {
372  CVLog::Warning(QString("[getMeshFromPolyData] Skipped %1 non-triangle "
373  "cell(s), "
374  "added %2 triangle(s)")
375  .arg(skippedCells)
376  .arg(validTriangles));
377  }
378 
379  // do some cleaning
380  {
381  vertices->shrinkToFit();
382  mesh->shrinkToFit();
384  if (normals) {
385  normals->shrink_to_fit();
386  }
387  }
388 
389  return mesh;
390 }
391 
392 ccPolyline* vtk2cc::ConvertToPolyline(vtkPolyData* polydata, bool silent) {
393  if (!polydata) return nullptr;
394 
395  ccPointCloud* obj = ConvertToPointCloud(polydata, silent);
396  if (!obj) {
397  CVLog::Error(
398  QString("[getPolylineFromPolyData] failed to convert "
399  "vtkPolyData to ccPointCloud"));
400  return nullptr;
401  }
402 
403  if (obj->size() == 0) {
404  CVLog::Warning(QString(
405  "[getPolylineFromPolyData] polyline vertices is empty!"));
406  return nullptr;
407  }
408 
409  return ConvertToPolyline(obj);
410 }
411 
413  if (!vertices || !vertices->isKindOf(CV_TYPES::POINT_CLOUD)) {
414  return nullptr;
415  }
416 
417  ccPointCloud* polyVertices = ccHObjectCaster::ToPointCloud(vertices);
418  if (!polyVertices) {
419  return nullptr;
420  }
421 
422  ccPolyline* curvePoly = new ccPolyline(polyVertices);
423  {
424  if (!curvePoly) {
425  return nullptr;
426  }
427 
428  unsigned verticesCount = polyVertices->size();
429  if (curvePoly->reserve(verticesCount)) {
430  curvePoly->addPointIndex(0, verticesCount);
431  curvePoly->setVisible(true);
432 
433  bool closed = false;
434  CCVector3 start =
435  CCVector3::fromArray(polyVertices->getPoint(0)->u);
437  polyVertices->getPoint(verticesCount - 1)->u);
438  if (cloudViewer::LessThanEpsilon((end - start).norm())) {
439  closed = true;
440  } else {
441  closed = false;
442  }
443 
444  curvePoly->setClosed(closed);
445  curvePoly->setName("polyline");
446 
447  curvePoly->addChild(polyVertices);
448  curvePoly->showColors(true);
449  curvePoly->setTempColor(ecvColor::green);
450  curvePoly->set2DMode(false);
451  } else {
452  delete curvePoly;
453  curvePoly = nullptr;
454  }
455  }
456 
457  return curvePoly;
458 }
459 
461  vtkPolyData* polydata, QString baseName, const ecvColor::Rgb& color) {
462  // initialize output
463  ccHObject::Container container;
464 
465  vtkIdType iCells = polydata->GetNumberOfCells();
466  for (vtkIdType i = 0; i < iCells; i++) {
467  ccPointCloud* vertices = nullptr;
468  vtkCell* cell = polydata->GetCell(i);
469  vtkIdType ptsCount = cell->GetNumberOfPoints();
470  if (ptsCount > 1) {
471  vertices = new ccPointCloud("vertices");
472  if (!vertices->reserve(static_cast<unsigned>(ptsCount))) {
473  CVLog::Error("not enough memory to allocate vertices...");
474  return container;
475  }
476 
477  for (vtkIdType iPt = 0; iPt < ptsCount; ++iPt) {
478  CCVector3 P =
479  CCVector3::fromArray(cell->GetPoints()->GetPoint(iPt));
480  vertices->addPoint(P);
481  }
482  // end POINTS
483  }
484 
485  if (vertices && vertices->size() == 0) {
486  delete vertices;
487  vertices = nullptr;
488  }
489 
490  if (vertices) {
491  vertices->setName("vertices");
492  vertices->setEnabled(false);
493  vertices->setPointSize(4);
494  vertices->showColors(true);
495  vertices->setTempColor(ecvColor::red);
496  if (vertices->hasNormals()) vertices->showNormals(true);
497  if (vertices->hasScalarFields()) {
498  vertices->setCurrentDisplayedScalarField(0);
499  vertices->showSF(true);
500  }
501 
502  ccPolyline* poly = ConvertToPolyline(vertices);
503  if (!poly) {
504  delete vertices;
505  vertices = nullptr;
506  continue;
507  }
508 
509  // update global scale and shift by m_entity
510  QString contourName = baseName;
511  if (poly->size() > 1) {
512  contourName += QString(" (part %1)").arg(i + 1);
513  }
514  poly->setName(contourName);
515  poly->showColors(true);
516  poly->setTempColor(color);
517  poly->set2DMode(false);
518 
519  container.push_back(poly);
520  }
521  }
522  return container;
523 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
double normal[3]
std::string name
math::float4 color
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
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
Array of compressed 3D normals (single index)
Type u[3]
Definition: CVGeom.h:139
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
virtual void setVisible(bool state)
Sets entity visibility.
virtual void setTempColor(const ecvColor::Rgb &col, bool autoActivate=true)
Sets current temporary (unique)
virtual void showNormals(bool state)
Sets normals visibility.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
void setPointSize(unsigned size=0)
Sets point size.
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
Definition: ecvHObject.cpp:534
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
Triangular mesh.
Definition: ecvMesh.h:35
NormsIndexesTableType * getTriNormsTable() const override
Returns per-triangle normals shared array.
Definition: ecvMesh.h:344
bool reserve(std::size_t n)
Reserves the memory to store the vertex indexes (3 per triangle)
Definition: ecvMesh.cpp:2475
void addTriangle(unsigned i1, unsigned i2, unsigned i3)
Adds a triangle to the mesh.
Definition: ecvMesh.cpp:2428
void shrinkToFit()
Removes unused capacity.
Definition: ecvMesh.h:302
virtual void setLocked(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:117
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
void addNorm(const CCVector3 &N)
Pushes a normal vector on stack (shortcut)
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool hasNormals() const override
Returns whether normals are enabled or not.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool reserveTheNormsTable()
Reserves memory to store the compressed normals.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void shrinkToFit()
Removes unused capacity.
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.
Colored polyline.
Definition: ecvPolyline.h:24
void set2DMode(bool state)
Defines if the polyline is considered as 2D or 3D.
A scalar field associated to display-related parameters.
void computeMinAndMax() override
Determines the min and max values.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
void setPoint(size_t index, const CCVector3 &P)
const CCVector3 * getPoint(unsigned index) const override
void setClosed(bool state)
Sets whether the polyline is closed or not.
Definition: Polyline.h:29
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.
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
bool reserveSafe(std::size_t count)
Reserves memory (no exception thrown)
Definition: ScalarField.cpp:71
bool resizeSafe(std::size_t count, bool initNewElements=false, ScalarType valueForNewElements=0)
Resizes memory (no exception thrown)
Definition: ScalarField.cpp:81
RGB color structure.
Definition: ecvColorTypes.h:49
static ccMesh * ConvertToMesh(vtkPolyData *polydata, bool silent=false)
Definition: vtk2cc.cpp:290
static ccPolyline * ConvertToPolyline(vtkPolyData *polydata, bool silent=false)
Definition: vtk2cc.cpp:392
static ccPointCloud * ConvertToPointCloud(vtkPolyData *polydata, bool silent=false)
Definition: vtk2cc.cpp:53
static std::vector< ccHObject * > ConvertToMultiPolylines(vtkPolyData *polydata, QString baseName="Slice", const ecvColor::Rgb &color=ecvColor::green)
Definition: vtk2cc.cpp:460
double colors[3]
double normals[3]
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
@ POINT_CLOUD
Definition: CVTypes.h:104
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
constexpr Rgb red(MAX, 0, 0)
constexpr Rgb green(0, MAX, 0)