ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
GrainsAsEllipsoids.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 "GrainsAsEllipsoids.h"
9 
11 #include <LineSet.h>
12 #include <ReferenceCloud.h>
13 #include <ecvGLMatrix.h>
14 #include <ecvMaterial.h>
15 #include <ecvMaterialSet.h>
16 #include <ecvMesh.h>
17 #include <ecvPointCloud.h>
18 #include <ecvSerializableObject.h>
19 
20 #include <QCoreApplication>
21 #include <QDir>
22 #include <fstream>
23 #include <iostream>
24 
25 #include "G3PointAction.h"
26 
28  : m_app(app), m_cloud(nullptr) {
29  assert(m_app);
30 
31  this->setMetaData("class_name", "GrainsAsEllipsoids");
32  this->setMetaData("plugin_name", "G3Point");
33 }
34 
36 
38  // Clean up mesh and lineset children
39  for (auto* mesh : m_meshes) {
40  if (mesh) {
41  removeChild(mesh);
42  delete mesh;
43  }
44  }
45  m_meshes.clear();
46 
47  for (auto* lineSet : m_lineSets) {
48  if (lineSet) {
49  removeChild(lineSet);
50  delete lineSet;
51  }
52  }
53  m_lineSets.clear();
54 
55  // Clean up points clouds
56  for (auto* pc : m_pointsClouds) {
57  if (pc) {
58  removeChild(pc);
59  delete pc;
60  }
61  }
62  m_pointsClouds.clear();
63 }
64 
66  ccPointCloud* cloud,
68  const std::vector<std::vector<int>>& stacks,
70  : m_cloud(cloud), m_app(app), m_stacks(stacks) {
71  this->setMetaData("class_name", "GrainsAsEllipsoids");
72  this->setMetaData("plugin_name", "G3Point");
73 
74  setGrainColorsTable(colors);
75 
76  m_center.resize(m_stacks.size());
77  m_radii.resize(m_stacks.size());
78  m_rotationMatrix.resize(m_stacks.size());
79 
80  // fit all ellipsoids
81  std::cout << "[GrainsAsEllipsoids::GrainsAsEllipsoids] fit "
82  << stacks.size() << " ellipsoids" << std::endl;
83 
84  lockVisibility(false);
85  setVisible(true);
86 
87  m_ccBBoxAll.setValidity(false);
89 
90  for (int idx = 0; idx < m_stacks.size(); idx++) {
91  if (!fitEllipsoidToGrain(idx, m_center[idx], m_radii[idx],
92  m_rotationMatrix[idx])) {
93  m_fitNotOK.insert(idx);
95  "[GrainsAsEllipsoids::GrainsAsEllipsoids] fit not possible "
96  "for grain " +
97  QString::number(idx) + " of size " +
98  QString::number(m_stacks[idx].size()));
99  } else { // update the bounding box
100  float maxRadius = m_radii[idx].maxCoeff();
101  CCVector3 center(m_center[idx](0), m_center[idx](1),
102  m_center[idx](2));
103  m_ccBBoxAll.add(CCVector3(center.x + maxRadius,
104  center.y + maxRadius,
105  center.z + maxRadius));
106  m_ccBBoxAll.add(CCVector3(center.x - maxRadius,
107  center.y - maxRadius,
108  center.z - maxRadius));
109  }
110  }
111 
112  // remove data corresponding to stacks were the fit was not successful
113  for (auto el : m_fitNotOK) {
114  // if the fit is not OK, we use the centroid as a center
115  int nPoints = static_cast<int>(m_stacks[el].size());
116  Eigen::MatrixX3d points(nPoints, 3);
117  for (int index = 0; index < nPoints; index++) {
118  const CCVector3* point = m_cloud->getPoint(m_stacks[el][index]);
119  points(index, 0) = point->x;
120  points(index, 1) = point->y;
121  points(index, 2) = point->z;
122  }
123  // compute the centroid of the label
124  Eigen::RowVector3d centroid = points.colwise().mean();
125  m_center[el] << centroid.x(), centroid.y(), centroid.z();
126 
127  m_radii[el].fill(0);
128 
129  m_rotationMatrix[el].fill(NAN);
130  }
131 
132  m_ccBBoxAll.setValidity(true);
133  m_meshNeedsUpdate = true;
134 }
135 
137  const RGBAColorsTableType& colorTable) {
138  m_grainColors.resize(colorTable.size());
139 
140  for (int k = 0; k < colorTable.size(); k++) {
141  ecvColor::Rgba color = colorTable[k];
142  m_grainColors[k] =
143  CCVector3f(static_cast<float>(color.r) / ecvColor::MAX,
144  static_cast<float>(color.g) / ecvColor::MAX,
145  static_cast<float>(color.b) / ecvColor::MAX);
146  }
147 }
148 
150  // create cloud
151  QString cloudName = "g3point_results";
152  ccPointCloud* cloud = new ccPointCloud(cloudName);
153 
154  for (int idx = 0; idx < m_center.size(); idx++) {
155  // if (m_fitNotOK.count(idx))
156  // {
157  // continue;
158  // }
159  Eigen::Vector3f center{m_center[idx].x(), m_center[idx].y(),
160  m_center[idx].z()};
161  Eigen::Vector3f point = center;
162  CCVector3 ccPoint(point(0), point(1), point(2));
163  cloud->addPoint(ccPoint);
164  }
165 
166  // allocate colors if necessary
167  if (cloud->resizeTheRGBTable()) {
168  for (unsigned int index = 0; index < cloud->size(); index++) {
170  m_grainColors[index].y * ecvColor::MAX * 0.8,
171  m_grainColors[index].z * ecvColor::MAX * 0.8);
172  cloud->setPointColor(index, color);
173  }
174  }
175 
176  int sfIdx;
178 
179  // EXPORT g3point_index
180  sfIdx = cloud->addScalarField("g3point_index");
181  if (sfIdx == -1) {
182  CVLog::Error(
183  "[GrainsAsEllipsoids::exportResultsAsCloud] impossible to "
184  "allocate g3point_index scalar field");
185  return false;
186  }
187  sf = cloud->getScalarField(sfIdx);
188  int indexInResults = 0;
189  for (int index = 0; index < m_center.size(); index++) {
190  // if (m_fitNotOK.count(index)) // when the fit was not successful, the
191  // point is not exported
192  // {
193  // continue;
194  // }
195  sf->setValue(indexInResults, index);
196  indexInResults++;
197  }
198  sf->computeMinAndMax();
199 
200  // <EXPORT RADII>
201  int sfIdxRadiusX = cloud->addScalarField("g3point_radius_x");
202  int sfIdxRadiusY = cloud->addScalarField("g3point_radius_y");
203  int sfIdxRadiusZ = cloud->addScalarField("g3point_radius_z");
204  if (sfIdxRadiusX == -1 || sfIdxRadiusY == -1 || sfIdxRadiusZ == -1) {
205  CVLog::Error(
206  "[GrainsAsEllipsoids::exportResultsAsCloud] impossible to "
207  "allocate scalar fields to export the radii");
208  return false;
209  }
210  cloudViewer::ScalarField* sfRadiusX = cloud->getScalarField(sfIdxRadiusX);
211  cloudViewer::ScalarField* sfRadiusY = cloud->getScalarField(sfIdxRadiusY);
212  cloudViewer::ScalarField* sfRadiusZ = cloud->getScalarField(sfIdxRadiusZ);
213  for (unsigned int index = 0; index < cloud->size(); index++) {
214  // if (m_fitNotOK.count(index))
215  // {
216  // continue;
217  // }
218  sfRadiusX->setValue(index, m_radii[index].x());
219  sfRadiusY->setValue(index, m_radii[index].y());
220  sfRadiusZ->setValue(index, m_radii[index].z());
221  }
222  sfRadiusX->computeMinAndMax();
223  sfRadiusY->computeMinAndMax();
224  sfRadiusZ->computeMinAndMax();
225  // </EXPORT RADII>
226 
227  // <EXPORT ROTATION>
228  int sfIdxR00 = cloud->addScalarField("g3point_r00");
229  int sfIdxR01 = cloud->addScalarField("g3point_r01");
230  int sfIdxR02 = cloud->addScalarField("g3point_r02");
231  int sfIdxR10 = cloud->addScalarField("g3point_r10");
232  int sfIdxR11 = cloud->addScalarField("g3point_r11");
233  int sfIdxR21 = cloud->addScalarField("g3point_r12");
234  int sfIdxR20 = cloud->addScalarField("g3point_r20");
235  int sfIdxR12 = cloud->addScalarField("g3point_r21");
236  int sfIdxR22 = cloud->addScalarField("g3point_r22");
237  if (sfIdxR00 == -1 || sfIdxR01 == -1 || sfIdxR02 == -1 || sfIdxR10 == -1 ||
238  sfIdxR11 == -1 || sfIdxR12 == -1 || sfIdxR20 == -1 || sfIdxR21 == -1 ||
239  sfIdxR22 == -1) {
240  CVLog::Error(
241  "[GrainsAsEllipsoids::exportResultsAsCloud] impossible to "
242  "allocate scalar fields to export the rotation");
243  return false;
244  }
245  cloudViewer::ScalarField* sfR00 = cloud->getScalarField(sfIdxR00);
246  cloudViewer::ScalarField* sfR01 = cloud->getScalarField(sfIdxR01);
247  cloudViewer::ScalarField* sfR02 = cloud->getScalarField(sfIdxR02);
248  cloudViewer::ScalarField* sfR10 = cloud->getScalarField(sfIdxR10);
249  cloudViewer::ScalarField* sfR11 = cloud->getScalarField(sfIdxR11);
250  cloudViewer::ScalarField* sfR12 = cloud->getScalarField(sfIdxR12);
251  cloudViewer::ScalarField* sfR20 = cloud->getScalarField(sfIdxR20);
252  cloudViewer::ScalarField* sfR21 = cloud->getScalarField(sfIdxR21);
253  cloudViewer::ScalarField* sfR22 = cloud->getScalarField(sfIdxR22);
254  for (unsigned int index = 0; index < cloud->size(); index++) {
255  // if (m_fitNotOK.count(index))
256  // {
257  // continue;
258  // }
259  sfR00->setValue(index, m_rotationMatrix[index](0, 0));
260  sfR01->setValue(index, m_rotationMatrix[index](0, 1));
261  sfR02->setValue(index, m_rotationMatrix[index](0, 2));
262  sfR10->setValue(index, m_rotationMatrix[index](1, 0));
263  sfR11->setValue(index, m_rotationMatrix[index](1, 1));
264  sfR12->setValue(index, m_rotationMatrix[index](1, 2));
265  sfR20->setValue(index, m_rotationMatrix[index](2, 0));
266  sfR21->setValue(index, m_rotationMatrix[index](2, 1));
267  sfR22->setValue(index, m_rotationMatrix[index](2, 2));
268  }
269  sfR00->computeMinAndMax();
270  sfR01->computeMinAndMax();
271  sfR02->computeMinAndMax();
272  sfR10->computeMinAndMax();
273  sfR11->computeMinAndMax();
274  sfR12->computeMinAndMax();
275  sfR20->computeMinAndMax();
276  sfR21->computeMinAndMax();
277  sfR22->computeMinAndMax();
278  // </EXPORT ROTATION>
279 
280  cloud->showColors(true);
281  cloud->setPointSize(9);
282 
283  m_cloud->addChild(cloud);
284  m_app->addToDB(cloud);
285 
286  return true;
287 }
288 
289 // INIT ORIGINAL SPHERE
290 
292  // clear memory of prev arrays
293  std::vector<float>().swap(vertices);
294  std::vector<float>().swap(normals);
295  std::vector<float>().swap(texCoords);
296 
297  float x, y, z, xy; // vertex position
298  float nx, ny, nz; // vertex normal
299  float s, t; // vertex texCoord
300 
301  float sectorStep = 2 * M_PI / sectorCount;
302  float stackStep = M_PI / stackCount;
303  float sectorAngle, stackAngle;
304 
305  for (int i = 0; i <= stackCount; ++i) {
306  stackAngle = M_PI / 2 - i * stackStep; // starting from pi/2 to -pi/2
307  xy = cosf(stackAngle); // r * cos(u)
308  z = sinf(stackAngle); // r * sin(u)
309 
310  // add (sectorCount+1) vertices per stack
311  // first and last vertices have same position and normal, but different
312  // tex coords
313  for (int j = 0; j <= sectorCount; ++j) {
314  sectorAngle = j * sectorStep; // starting from 0 to 2pi
315 
316  // vertex position (x, y, z)
317  x = xy * cosf(sectorAngle); // r * cos(u) * cos(v)
318  y = xy * sinf(sectorAngle); // r * cos(u) * sin(v)
319  vertices.push_back(x);
320  vertices.push_back(y);
321  vertices.push_back(z);
322 
323  // normalized vertex normal (nx, ny, nz)
324  nx = x;
325  ny = y;
326  nz = z;
327  normals.push_back(nx);
328  normals.push_back(ny);
329  normals.push_back(nz);
330 
331  // vertex tex coord (s, t) range between [0, 1]
332  s = (float)j / sectorCount;
333  t = (float)i / stackCount;
334  texCoords.push_back(s);
335  texCoords.push_back(t);
336  }
337  }
338 }
339 
341  // Clear previous indices
342  indices.clear();
343  lineIndices.clear();
344 
345  // generate CCW index list of sphere triangles
346  // k1--k1+1
347  // | / |
348  // | / |
349  // k2--k2+1
350 
351  int k1, k2;
352  for (int i = 0; i < stackCount; ++i) {
353  k1 = i * (sectorCount + 1); // beginning of current stack
354  k2 = k1 + sectorCount + 1; // beginning of next stack
355 
356  for (int j = 0; j < sectorCount; ++j, ++k1, ++k2) {
357  // 2 triangles per sector excluding first and last stacks
358  // k1 => k2 => k1+1
359  if (i != 0) {
360  indices.push_back(k1);
361  indices.push_back(k2);
362  indices.push_back(k1 + 1);
363  }
364 
365  // k1+1 => k2 => k2+1
366  if (i != (stackCount - 1)) {
367  indices.push_back(k1 + 1);
368  indices.push_back(k2);
369  indices.push_back(k2 + 1);
370  }
371 
372  // store indices for lines
373  // vertical lines for all stacks, k1 => k2
374  lineIndices.push_back(k1);
375  lineIndices.push_back(k2);
376  if (i != 0) // horizontal lines except 1st stack, k1 => k+1
377  {
378  lineIndices.push_back(k1);
379  lineIndices.push_back(k1 + 1);
380  }
381  }
382  }
383 }
384 
385 // ELLIPSOID FITTING
386 
390  if (index < m_stacks.size()) {
391  if (m_fitNotOK.count(index) == 0) {
392  float maxRadius = m_radii[index].maxCoeff();
393  CCVector3 center(m_center[index](0), m_center[index](1),
394  m_center[index](2));
395  m_ccBBoxOnlyOne.add(CCVector3(center.x + maxRadius,
396  center.y + maxRadius,
397  center.z + maxRadius));
398  m_ccBBoxOnlyOne.add(CCVector3(center.x - maxRadius,
399  center.y - maxRadius,
400  center.z - maxRadius));
402  }
403  } else {
404  CVLog::Error(
405  "[GrainsAsEllipsoids::updateBBox] asking for the bounding of "
406  "index " +
407  QString::number(index) + " out of range");
408  }
409 }
410 
412  const Eigen::Array3f& center,
413  const Eigen::Array3f& radii,
414  const Eigen::Matrix3f& rotationMatrix,
415  Eigen::ArrayXd& parameters) {
416  // INSPIRED BY MATLAB CODE
417 
418  // Cast ellipsoid defined with explicit parameters to implicit vector form.
419  //
420  // Examples:
421  // p = ellipse_ex2im([xc,yc,zc],[xr,yr,zr],eye(3,3));
422 
423  // Matlab code => Copyright 2011 Levente Hunyadi
424 
425  float xrr = 1 / radii(0);
426  float yrr = 1 / radii(1);
427  float zrr = 1 / radii(2);
428 
429  float r11 = rotationMatrix.data()[0];
430  float r21 = rotationMatrix.data()[1];
431  float r31 = rotationMatrix.data()[2];
432  float r12 = rotationMatrix.data()[3];
433  float r22 = rotationMatrix.data()[4];
434  float r32 = rotationMatrix.data()[5];
435  float r13 = rotationMatrix.data()[6];
436  float r23 = rotationMatrix.data()[7];
437  float r33 = rotationMatrix.data()[8];
438 
439  float xc = center(0);
440  float yc = center(1);
441  float zc = center(2);
442 
443  // terms collected from symbolic expression
444 
445  parameters << pow(r11, 2) * pow(xrr, 2) + pow(r21, 2) * pow(yrr, 2) +
446  pow(r31, 2) * pow(zrr, 2),
447  pow(r12, 2) * pow(xrr, 2) + pow(r22, 2) * pow(yrr, 2) +
448  pow(r32, 2) * pow(zrr, 2),
449  pow(r13, 2) * pow(xrr, 2) + pow(r23, 2) * pow(yrr, 2) +
450  pow(r33, 2) * pow(zrr, 2),
451  2 * r11 * r12 * pow(xrr, 2) + 2 * r21 * r22 * pow(yrr, 2) +
452  2 * r31 * r32 * pow(zrr, 2),
453  2 * r11 * r13 * pow(xrr, 2) + 2 * r21 * r23 * pow(yrr, 2) +
454  2 * r31 * r33 * pow(zrr, 2),
455  2 * r12 * r13 * pow(xrr, 2) + 2 * r22 * r23 * pow(yrr, 2) +
456  2 * r32 * r33 * pow(zrr, 2),
457  (-2) * (pow(r11, 2) * xc * pow(xrr, 2) +
458  pow(r21, 2) * xc * pow(yrr, 2) +
459  pow(r31, 2) * xc * pow(zrr, 2) +
460  r11 * r12 * pow(xrr, 2) * yc +
461  r11 * r13 * pow(xrr, 2) * zc +
462  r21 * r22 * yc * pow(yrr, 2) +
463  r21 * r23 * pow(yrr, 2) * zc +
464  r31 * r32 * yc * pow(zrr, 2) +
465  r31 * r33 * zc * pow(zrr, 2)),
466  (-2) * (pow(r12, 2) * pow(xrr, 2) * yc +
467  pow(r22, 2) * yc * pow(yrr, 2) +
468  pow(r32, 2) * yc * pow(zrr, 2) +
469  r11 * r12 * xc * pow(xrr, 2) +
470  r21 * r22 * xc * pow(yrr, 2) +
471  r12 * r13 * pow(xrr, 2) * zc +
472  r31 * r32 * xc * pow(zrr, 2) +
473  r22 * r23 * pow(yrr, 2) * zc +
474  r32 * r33 * zc * pow(zrr, 2)),
475  (-2) * (pow(r13, 2) * pow(xrr, 2) * zc +
476  pow(r23, 2) * pow(yrr, 2) * zc +
477  pow(r33, 2) * zc * pow(zrr, 2) +
478  r11 * r13 * xc * pow(xrr, 2) +
479  r12 * r13 * pow(xrr, 2) * yc +
480  r21 * r23 * xc * pow(yrr, 2) +
481  r22 * r23 * yc * pow(yrr, 2) +
482  r31 * r33 * xc * pow(zrr, 2) +
483  r32 * r33 * yc * pow(zrr, 2)),
484  pow(r11, 2) * pow(xc, 2) * pow(xrr, 2) +
485  2 * r11 * r12 * xc * pow(xrr, 2) * yc +
486  2 * r11 * r13 * xc * pow(xrr, 2) * zc +
487  pow(r12, 2) * pow(xrr, 2) * pow(yc, 2) +
488  2 * r12 * r13 * pow(xrr, 2) * yc * zc +
489  pow(r13, 2) * pow(xrr, 2) * pow(zc, 2) +
490  pow(r21, 2) * pow(xc, 2) * pow(yrr, 2) +
491  2 * r21 * r22 * xc * yc * pow(yrr, 2) +
492  2 * r21 * r23 * xc * pow(yrr, 2) * zc +
493  pow(r22, 2) * pow(yc, 2) * pow(yrr, 2) +
494  2 * r22 * r23 * yc * pow(yrr, 2) * zc +
495  pow(r23, 2) * pow(yrr, 2) * pow(zc, 2) +
496  pow(r31, 2) * pow(xc, 2) * pow(zrr, 2) +
497  2 * r31 * r32 * xc * yc * pow(zrr, 2) +
498  2 * r31 * r33 * xc * zc * pow(zrr, 2) +
499  pow(r32, 2) * pow(yc, 2) * pow(zrr, 2) +
500  2 * r32 * r33 * yc * zc * pow(zrr, 2) +
501  pow(r33, 2) * pow(zc, 2) * pow(zrr, 2) - 1;
502 
503  return true;
504 }
505 
506 bool GrainsAsEllipsoids::implicitToExplicit(const Eigen::ArrayXd& parameters,
507  Eigen::Array3f& center,
508  Eigen::Array3f& radii,
509  Eigen::Matrix3f& rotationMatrix) {
510  // INSPIRED BY MATLAB CODE
511 
512  // Cast ellipsoid defined with implicit parameter vector to explicit form.
513  // The implicit equation of a general ellipse is
514  // F(x,y,z) = Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + 2Iz - 1
515  // = 0
516  //
517  // Input arguments:
518  // v:
519  // the 10 parameters describing the ellipsoid algebraically
520  // Output arguments:
521  // center:
522  // ellispoid center coordinates [cx; cy; cz]
523  // ax:
524  // ellipsoid semi-axes (radii) [a; b; c]
525  // quat: NOT IN THIS CPP VERSION, ONLY MATLAB VERSION
526  // ellipsoid rotation in quaternion representation
527  // R:
528  // ellipsoid rotation (radii directions as rows of the 3x3 matrix)
529  //
530  // See also: ellipse_im2ex
531 
532  // Matlab code => Copyright 2011 Levente Hunyadi
533 
534  Eigen::ArrayXd p = parameters;
535 
536  p(3) = 0.5 * p(3);
537  p(4) = 0.5 * p(4);
538  p(5) = 0.5 * p(5);
539  p(6) = 0.5 * p(6);
540  p(7) = 0.5 * p(7);
541  p(8) = 0.5 * p(8);
542 
543  Eigen::MatrixXd q(4, 4);
544 
545  q << p(0), p(3), p(4), p(6), p(3), p(1), p(5), p(7), p(4), p(5), p(2), p(8),
546  p(6), p(7), p(8), p(9);
547 
548  center = q.block(0, 0, 3, 3)
549  .colPivHouseholderQr()
550  .solve(-p(Eigen::seq(6, 8)).matrix())
551  .cast<float>();
552 
553  Eigen::MatrixXd t(4, 4);
554  t = Eigen::MatrixXd::Identity(4, 4);
555  t(3, 0) = center(0);
556  t(3, 1) = center(1);
557  t(3, 2) = center(2);
558 
559  Eigen::MatrixXd s(4, 4);
560  s = t * q * t.transpose();
561 
562  // check for positive definiteness
563  Eigen::LLT<Eigen::MatrixXd> lltOfA(
564  (-s(3, 3) * s.block(0, 0, 3, 3).array()));
565  if (lltOfA.info() != Eigen::Success) {
566  return false;
567  }
568 
569  Eigen::EigenSolver<Eigen::MatrixXd> eigensolver(s.block(0, 0, 3, 3));
570  if (eigensolver.info() != Eigen::Success) {
571  return false;
572  }
573 
574  radii = (-s(3, 3) / eigensolver.eigenvalues().array().real())
575  .sqrt()
576  .cast<float>();
577  rotationMatrix =
578  eigensolver.eigenvectors().transpose().real().cast<float>();
579 
580  return true;
581 }
582 
583 bool GrainsAsEllipsoids::directFit(const Eigen::ArrayX3d& xyz,
584  Eigen::ArrayXd& parameters) {
585  // INSPIRED BY MATLAB CODE
586 
587  // Direct least squares fitting of ellipsoids under the constraint 4J - I^2
588  // > 0. The constraint confines the class of ellipsoids to fit to those
589  // whose smallest radius is at least half of the largest radius.
590  //
591  // Input arguments:
592  // x,y,z;
593  // x, y and z coodinates of 3D points
594  //
595  // Output arguments:
596  // p:
597  // a 10-parameter vector of the algebraic ellipsoid fit
598  //
599  // References:
600  // Qingde Li and John G. Griffiths, "Least Squares Ellipsoid Specific
601  // Fitting",
602  // Proceedings of the Geometric Modeling and Processing, 2004.
603 
604  // Matlab code reference => Copyright 2011 Levente Hunyadi
605 
606  Eigen::MatrixXd d(xyz.rows(), 10);
607 
608  d << xyz(Eigen::all, 0).pow(2).matrix(), xyz(Eigen::all, 1).pow(2).matrix(),
609  xyz(Eigen::all, 2).pow(2).matrix(),
610  (2 * xyz(Eigen::all, 1) * xyz(Eigen::all, 2)).matrix(),
611  (2 * xyz(Eigen::all, 0) * xyz(Eigen::all, 2)).matrix(),
612  (2 * xyz(Eigen::all, 0) * xyz(Eigen::all, 1)).matrix(),
613  (2 * xyz(Eigen::all, 0)).matrix(),
614  (2 * xyz(Eigen::all, 1)).matrix(),
615  (2 * xyz(Eigen::all, 2)).matrix(),
616  Eigen::MatrixXd::Ones(xyz.rows(), 1);
617 
618  Eigen::MatrixXd s = d.transpose() * d;
619 
620  int k = 4;
621  Eigen::Matrix3d c1;
622  Eigen::Matrix3d c2;
623  Eigen::MatrixXd c;
624  c = Eigen::MatrixXd::Zero(10, 10);
625  c1 << 0, k, k, k, 0, k, k, k, 0;
626  c1 = c1.array() / 2 - 1;
627  c2 = -k * Eigen::Matrix3d::Identity();
628  c.block(0, 0, 3, 3) = c1;
629  c.block(3, 3, 3, 3) = c2;
630 
631  Eigen::GeneralizedEigenSolver<Eigen::MatrixXd> eigensolver(s, c);
632  if (eigensolver.info() != Eigen::Success) {
633  return false;
634  }
635 
636  Eigen::ArrayXd eigenValues(10);
637  Eigen::VectorXd eigenValuesAsAMatrix(10);
638  eigenValues = eigensolver.eigenvalues().real();
639  eigenValuesAsAMatrix = eigensolver.eigenvalues().real().matrix();
640  Xb condition = (eigenValues > 0) && (!eigenValues.isInf());
641 
642  int flt = condition.count();
643  // std::cout << "flt " << flt << std::endl;
644  Eigen::ArrayXd finiteValues(flt);
645  finiteValues = Eigen::ArrayXd::Zero(flt);
646  int finiteValuesCounter = 0;
647  for (int k = 0; k < eigenValues.size(); k++) {
648  if (condition(k)) {
649  finiteValues(finiteValuesCounter++) = eigenValues(k);
650  }
651  }
652 
653  double eigenValue;
654  Eigen::MatrixXd v;
655  switch (flt) {
656  case 1: // regular case
657  eigenValue =
658  finiteValues(0); // there is only one positive finite value
659  for (k = 0; k < 10; k++) {
660  if (eigenValues(k) == eigenValue) {
661  v = eigensolver.eigenvectors()(Eigen::all, k).real();
662  break;
663  }
664  }
665  break;
666  case 0: // degenerate case
667  // # single positive eigenvalue becomes near-zero negative
668  // eigenvalue due to round-off error
669  eigenValue = eigenValues.abs().minCoeff();
670  for (k = 0; k < 10; k++) {
671  if (abs(eigenValues(k)) == eigenValue) {
672  v = eigensolver.eigenvectors()(Eigen::all, k).real();
673  break;
674  }
675  }
676  break;
677  default: // degenerate case
678  // several positive eigenvalues appear
679  eigenValue = finiteValues.abs().minCoeff();
680  for (k = 0; k < 10; k++) {
681  if (eigenValues(k) == eigenValue) {
682  v = eigensolver.eigenvectors()(Eigen::all, k).real();
683  break;
684  }
685  }
686  break;
687  }
688 
689  parameters.resize(10);
690 
691  parameters << v(0), v(1), v(2), 2 * v(5), 2 * v(4), 2 * v(3), 2 * v(6),
692  2 * v(7), 2 * v(8), v(9);
693 
694  return true;
695 }
696 
697 bool GrainsAsEllipsoids::fitEllipsoidToGrain(const int grainIndex,
698  Eigen::Array3f& center,
699  Eigen::Array3f& radii,
700  Eigen::Matrix3f& rotationMatrix,
701  const Method& method) {
702  // Shift point cloud to have only positive coordinates
703  // (problem with quadfit if the point cloud is far from the coordinates of
704  // the origin (0,0,0))
705 
706  bool ret = true;
707 
708  // extract the point cloud related to the current index
709  cloudViewer::ReferenceCloud referenceCloud(m_cloud);
710  for (int index : m_stacks[grainIndex]) {
711  referenceCloud.addPointIndex(index);
712  }
713 
714  ccPointCloud* grainCloud = m_cloud->partialClone(&referenceCloud);
715  Eigen::Map<const Eigen::MatrixX3f, Eigen::Unaligned, Eigen::Stride<1, 3>>
716  grainPoints(static_cast<const float*>(grainCloud->getPoint(0)->u),
717  grainCloud->size(), 3);
718 
719  CCVector3 bbMin;
720  CCVector3 bbMax;
721  grainCloud->getBoundingBox(bbMin, bbMax);
722  CCVector3 bb(bbMax - bbMin);
723  Eigen::Vector3d scales(bb.x, bb.y, bb.z);
724  double scale = 1 / scales.maxCoeff();
725  Eigen::RowVector3d means = grainPoints.cast<double>().colwise().mean();
726 
727  Eigen::ArrayXd p(10);
728 
729  switch (method) {
730  case DIRECT:
731  // Direct least squares fitting of ellipsoids under the constraint
732  // 4J - I**2 > 0. The constraint confines the class of ellipsoids to
733  // fit to those whose smallest radius is at least half of the
734  // largest radius.
735 
736  if (!directFit(
737  scale * (grainPoints.cast<double>().rowwise() - means),
738  p)) // Ellipsoid fit
739  {
740  return false;
741  }
742 
743  if (!implicitToExplicit(
744  p, center, radii,
745  rotationMatrix)) // Get the explicit parameters
746  {
747  return false;
748  }
749 
750  break;
751  default:
752  break;
753  }
754 
755  // Rescale the explicit parameters (the rotation matrix is unchanged by the
756  // scaling)
757  center = center / scale + Eigen::Array3f(means.cast<float>());
758  radii = radii / scale;
759 
760  // re-order the radii
761  std::vector<float> sortedRadii{radii(0), radii(1), radii(2)};
762  std::sort(sortedRadii.begin(), sortedRadii.end());
763  Eigen::Array3f updatedRadii = {
764  sortedRadii[0], sortedRadii[1],
765  sortedRadii[2]}; // from the smallest to the largest
766  Eigen::Matrix3f updatedRotationMatrix;
767  for (int k = 0; k < 3; k++) {
768  float radius = updatedRadii(k);
769  int col = 0;
770  for (int idx = 0; idx < 3; idx++) {
771  if (radii[idx] == radius) {
772  break;
773  }
774  col++;
775  }
776  updatedRotationMatrix(k, 0) = rotationMatrix(col, 0);
777  updatedRotationMatrix(k, 1) = rotationMatrix(col, 1);
778  updatedRotationMatrix(k, 2) = rotationMatrix(col, 2);
779  }
780 
781  radii = updatedRadii;
782  rotationMatrix = updatedRotationMatrix;
783 
784  ret = explicitToImplicit(center, radii, rotationMatrix, p);
785 
786  return ret;
787 }
788 
789 // DRAW
790 
792  // Validate basic state
793  if (m_center.empty() || m_radii.empty() || m_rotationMatrix.empty() ||
794  m_grainColors.empty()) {
796  "[GrainsAsEllipsoids::updateMeshAndLineSet] Empty data arrays, "
797  "cannot update mesh");
798  return;
799  }
800 
801  // Ensure sphere template is initialized
802  if (vertices.empty() || indices.empty() || lineIndices.empty()) {
805  // Verify initialization succeeded
806  if (vertices.empty() || indices.empty() || lineIndices.empty()) {
807  CVLog::Error(
808  "[GrainsAsEllipsoids::updateMeshAndLineSet] Failed to "
809  "initialize sphere template");
810  return;
811  }
812  }
813 
815  bool hasContext = false;
816  // Temporarily remove from DB Tree if requested and possible
817  if (m_app && getParent()) {
818  objContext = m_app->removeObjectTemporarilyFromDBTree(this);
819  hasContext = true;
820  }
821 
822  // -------------------------------------------------------------------------
823  // 1. Create objects if they don't exist
824  // -------------------------------------------------------------------------
825 
826  // Check if resize is needed (e.g. if data changed significantly)
827  if (m_meshes.size() != m_center.size()) {
829  m_meshes.resize(m_center.size(), nullptr);
830  m_lineSets.resize(m_center.size(), nullptr);
831  m_pointsClouds.resize(m_center.size(), nullptr);
832  }
833 
834  // Iterate through all grains to create or update
835  for (int idx = 0; idx < static_cast<int>(m_center.size()); idx++) {
836  // Only create if missing (nullptr)
837  if (!m_meshes[idx]) {
838  // Validate index bounds and data validity
839  if (m_fitNotOK.count(idx)) {
840  continue; // Skip ellipsoids that failed to fit
841  }
842 
843  // Check if rotation matrix contains NaN
844  bool hasNaN = false;
845  for (int i = 0; i < 3; i++) {
846  for (int j = 0; j < 3; j++) {
847  if (std::isnan(m_rotationMatrix[idx](i, j))) {
848  hasNaN = true;
849  break;
850  }
851  }
852  if (hasNaN) break;
853  }
854  if (hasNaN) {
856  "[GrainsAsEllipsoids::updateMeshAndLineSet] Rotation "
857  "matrix "
858  "contains NaN at index: " +
859  QString::number(idx));
860  continue;
861  }
862 
863  // Validate radii are positive
864  if (m_radii[idx](0) <= 0 || m_radii[idx](1) <= 0 ||
865  m_radii[idx](2) <= 0) {
867  "[GrainsAsEllipsoids::updateMeshAndLineSet] Invalid "
868  "radii "
869  "at index: " +
870  QString::number(idx));
871  continue;
872  }
873 
874  // --- Prepare Ellipsoid Vertices ---
875  Eigen::Matrix3f rotation(m_rotationMatrix[idx].transpose());
876  Eigen::Array3f center = m_center[idx];
877  Eigen::Array3f radii = m_radii[idx];
878 
879  std::vector<Eigen::Vector3d> ellipsoidVertices;
880  ellipsoidVertices.reserve(vertices.size() / 3);
881 
882  for (size_t i = 0; i < vertices.size(); i += 3) {
883  Eigen::Vector3f sphereVertex(vertices[i], vertices[i + 1],
884  vertices[i + 2]);
885  Eigen::Vector3f scaledVertex(sphereVertex.x() * radii(0),
886  sphereVertex.y() * radii(1),
887  sphereVertex.z() * radii(2));
888  Eigen::Vector3f rotatedVertex = rotation * scaledVertex;
889  Eigen::Vector3d finalVertex(rotatedVertex.x() + center(0),
890  rotatedVertex.y() + center(1),
891  rotatedVertex.z() + center(2));
892  ellipsoidVertices.push_back(finalVertex);
893  }
894 
895  // --- Create Mesh ---
896  if (!indices.empty()) {
897  ccPointCloud* vertexCloud = new ccPointCloud("vertices");
898  if (vertexCloud->resize(
899  static_cast<unsigned>(ellipsoidVertices.size()))) {
900  vertexCloud->setEigenPoints(ellipsoidVertices);
901 
902  ccMesh* mesh = new ccMesh(vertexCloud);
903  mesh->setName(QString("Ellipsoid_%1_Mesh").arg(idx));
904 
905  for (size_t i = 0; i < indices.size(); i += 3) {
906  mesh->addTriangle(
907  static_cast<unsigned>(indices[i]),
908  static_cast<unsigned>(indices[i + 1]),
909  static_cast<unsigned>(indices[i + 2]));
910  }
911 
912  CCVector3f colorVec = m_grainColors[idx];
913  ecvColor::Rgbaf materialColor(
914  colorVec.x, colorVec.y, colorVec.z,
915  static_cast<float>(m_transparency));
916 
917  ccMaterial::Shared material(new ccMaterial(
918  QString("Ellipsoid_%1_Material").arg(idx)));
919  material->setDiffuse(materialColor);
920  material->setIllum(0);
921 
922  ccMaterialSet* materialSet = new ccMaterialSet(
923  QString("Ellipsoid_%1_MaterialSet").arg(idx));
924  materialSet->addMaterial(material);
925  mesh->setMaterialSet(materialSet);
926  mesh->showMaterials(true);
927 
928  vertexCloud->setEnabled(false);
929  vertexCloud->setLocked(false);
930  mesh->addChild(vertexCloud);
931 
932  m_meshes[idx] = mesh;
933  addChild(mesh);
934  // Visibility will be set in the update loop below
935  } else {
936  delete vertexCloud;
937  }
938  }
939 
940  // --- Create LineSet ---
941  if (!lineIndices.empty() && !ellipsoidVertices.empty()) {
942  // Check line indices validity (simplified check)
943  std::vector<Eigen::Vector2i> lines;
944  lines.reserve(lineIndices.size() / 2);
945  for (size_t i = 0; i < lineIndices.size(); i += 2) {
946  if (i + 1 < lineIndices.size()) {
947  lines.push_back(Eigen::Vector2i(lineIndices[i],
948  lineIndices[i + 1]));
949  }
950  }
951 
954  ellipsoidVertices, lines,
955  QString("Ellipsoid_%1_Lines")
956  .arg(idx)
957  .toLatin1()
958  .data());
959 
961  Eigen::Vector3d lineColor(color.x * 0.8, color.y * 0.8,
962  color.z * 0.8);
963  lineSet->PaintUniformColor(lineColor);
964 
965  m_lineSets[idx] = lineSet;
966  addChild(lineSet);
967  }
968 
969  // --- Create PointCloud ---
970  if (m_cloud && idx < static_cast<int>(m_stacks.size())) {
971  const std::vector<int>& stack = m_stacks[idx];
972  if (!stack.empty()) {
973  cloudViewer::ReferenceCloud referenceCloud(m_cloud);
974  for (int index : stack) {
975  referenceCloud.addPointIndex(index);
976  }
977 
978  ccPointCloud* pc = m_cloud->partialClone(&referenceCloud);
979  if (pc) {
980  pc->setName(QString("Ellipsoid_%1_Points").arg(idx));
981 
982  if (pc->resizeTheRGBTable()) {
984  ecvColor::Rgb rgbColor(
985  static_cast<unsigned char>(
986  color.x * ecvColor::MAX * 0.8f),
987  static_cast<unsigned char>(
988  color.y * ecvColor::MAX * 0.8f),
989  static_cast<unsigned char>(
990  color.z * ecvColor::MAX * 0.8f));
991  for (unsigned int i = 0; i < pc->size(); i++) {
992  pc->setPointColor(i, rgbColor);
993  }
994  pc->showColors(true);
995  }
996 
997  m_pointsClouds[idx] = pc;
998  addChild(pc);
999  }
1000  }
1001  }
1002  }
1003  }
1004 
1005  // -------------------------------------------------------------------------
1006  // 2. Update Visibility and Properties
1007  // -------------------------------------------------------------------------
1008  for (int idx = 0; idx < static_cast<int>(m_center.size()); idx++) {
1009  bool isVisible = m_showAll || (idx == m_onlyOne);
1010 
1011  // Meshes
1012  if (m_meshes.size() > idx && m_meshes[idx]) {
1013  bool showMesh = isVisible && m_drawSurfaces;
1014  m_meshes[idx]->setVisible(showMesh);
1015  m_meshes[idx]->setEnabled(showMesh);
1016 
1017  // Update Transparency if needed
1018  if (showMesh) {
1019  ccMaterialSet* matSet = const_cast<ccMaterialSet*>(
1020  m_meshes[idx]->getMaterialSet());
1021  if (matSet && !matSet->empty()) {
1022  // CShared is QSharedPointer<const ccMaterial>
1023  // We need a non-const Shared pointer to modify it
1024  ccMaterial::CShared cMat = matSet->front();
1025 
1026  // If we need to modify it, we should check if we can cast
1027  // away constness (dangerous if truly const) or if we should
1028  // clone/replace. Since we created it, we know it's mutable
1029  // underlying object.
1030  ccMaterial::Shared mat =
1031  qSharedPointerConstCast<ccMaterial>(cMat);
1032 
1033  if (mat) {
1034  const ecvColor::Rgbaf& diffuse = mat->getDiffuseFront();
1035  if (std::abs(diffuse.a -
1036  static_cast<float>(m_transparency)) >
1037  1e-4) {
1038  ecvColor::Rgbaf newColor = diffuse;
1039  newColor.a = static_cast<float>(m_transparency);
1040  mat->setDiffuse(newColor);
1041  }
1042  }
1043  }
1044  }
1045  }
1046 
1047  // LineSets
1048  if (m_lineSets.size() > idx && m_lineSets[idx]) {
1049  bool showLine = isVisible && m_drawLines;
1050  m_lineSets[idx]->setVisible(showLine);
1051  m_lineSets[idx]->setEnabled(showLine);
1052  }
1053 
1054  // PointClouds
1055  if (m_pointsClouds.size() > idx && m_pointsClouds[idx]) {
1056  bool showPoints = isVisible && m_drawPoints;
1057  m_pointsClouds[idx]->setVisible(showPoints);
1058  m_pointsClouds[idx]->setEnabled(showPoints);
1059 
1060  if (showPoints && m_glPointSize > 0) {
1061  m_pointsClouds[idx]->setPointSize(
1062  static_cast<float>(m_glPointSize));
1063  }
1064  }
1065  }
1066 
1067  // Put back into DB Tree
1068  if (hasContext && m_app) {
1069  m_app->putObjectBackIntoDBTree(this, objContext);
1070  }
1071 }
1072 
1074  m_onlyOne = i;
1075  updateBBoxOnlyOne(i);
1076  m_meshNeedsUpdate = true;
1077  redrawDisplay();
1078 }
1079 
1081  m_showAll = !state;
1083  m_meshNeedsUpdate = true;
1084  redrawDisplay();
1085 }
1086 
1088  m_showAll = state;
1090  m_meshNeedsUpdate = true;
1091  redrawDisplay();
1092 }
1093 
1095  if (m_radii.empty()) // nothing to draw, probably due to a bad
1096  // initialization
1097  return;
1098 
1099  // Update mesh and lineset representations if needed
1100  if (m_meshNeedsUpdate) {
1102  m_meshNeedsUpdate = false;
1103  }
1104 
1105  // Call parent draw method to handle children drawing and other standard
1106  // behavior.
1107  //
1108  // ccHObject::draw() will:
1109  // 1. Call drawMeOnly(context) for this object (empty by default for
1110  // ccCustomHObject)
1111  // 2. Iterate through all children in m_children and call
1112  // child->draw(context)
1113  // for each child (see ecvHObject.cpp:1500-1502)
1114  //
1115  // Our child objects (meshes, linesets, point clouds) added via addChild()
1116  // will have their draw() method called, which in turn calls their
1117  // drawMeOnly() method, which calls ecvDisplayTools::Draw(context, this)
1118  // to perform the actual rendering.
1119  //
1120  // This ensures all child objects are rendered automatically.
1122 }
1123 
1124 ccBBox GrainsAsEllipsoids::getOwnBB(bool withGLFeatures) { return m_ccBBox; }
1125 
1128 bool genericArrayToFile(const std::vector<Eigen::Array3f>& data, QFile& out) {
1129  assert(out.isOpen() && (out.openMode() & QIODevice::WriteOnly));
1130 
1131  // removed to allow saving empty clouds
1132  // if (data.empty())
1133  //{
1134  // return ccSerializableObject::MemoryError();
1135  // }
1136 
1137  int N = 1;
1138 
1139  // component count (dataVersion>=20)
1140  ::uint8_t componentCount = static_cast<::uint8_t>(N);
1141  if (out.write((const char*)&componentCount, 1) < 0)
1143 
1144  // element count = array size (dataVersion>=20)
1145  ::uint32_t elementCount = static_cast<::uint32_t>(data.size());
1146  if (out.write((const char*)&elementCount, 4) < 0)
1148 
1149  // array data (dataVersion>=20)
1150  {
1151  // DGM: do it by chunks, in case it's too big to be processed by the
1152  // system
1153  const char* _data = (const char*)data.data();
1154  qint64 byteCount = static_cast<qint64>(elementCount);
1155  byteCount *= sizeof(Eigen::Array3f);
1156  while (byteCount != 0) {
1157  static const qint64 s_maxByteSaveCount =
1158  (1 << 26); // 64 Mb each time
1159  qint64 saveCount = std::min(byteCount, s_maxByteSaveCount);
1160  if (out.write(_data, saveCount) < 0)
1162  _data += saveCount;
1163  byteCount -= saveCount;
1164  }
1165  }
1166  return true;
1167 }
1168 
1169 bool readArrayHeader(QFile& in,
1170  short dataVersion,
1171  ::uint8_t& componentCount,
1172  ::uint32_t& elementCount) {
1173  assert(in.isOpen() && (in.openMode() & QIODevice::ReadOnly));
1174 
1175  if (dataVersion < 20) return ccSerializableObject::CorruptError();
1176 
1177  // component count (dataVersion>=20)
1178  if (in.read((char*)&componentCount, 1) < 0)
1180 
1181  // element count = array size (dataVersion>=20)
1182  if (in.read((char*)&elementCount, 4) < 0)
1184 
1185  return true;
1186 }
1187 
1188 template <typename T>
1189 bool stdVectorToFile(QString name, std::vector<T> vector) {
1190  std::ofstream file(name.toLatin1());
1191  int elementSize = vector[0].size();
1192  for (int i = 0; i < vector.size(); i++) {
1193  for (int j = 0; j < elementSize; j++) {
1194  file << vector[i][j] << ", ";
1195  }
1196  file << std::endl;
1197  }
1198  return true;
1199 }
1200 
1202  std::vector<Eigen::Matrix3f> rotationMatrix) {
1203  std::ofstream file(name.toLatin1());
1204  int elementSize = rotationMatrix[0].size();
1205  for (int i = 0; i < rotationMatrix.size(); i++) {
1206  for (int j = 0; j < elementSize; j++) {
1207  file << rotationMatrix[i](j) << ", ";
1208  }
1209  file << std::endl;
1210  }
1211  return true;
1212 }
1213 
1214 bool GrainsAsEllipsoids::toFile_MeOnly(QFile& out, short dataVersion) const {
1215  CVLog::Print("[G3Point] write GrainsAsEllipsoids object in .bin");
1216 
1217  if (!ccHObject::toFile_MeOnly(out, dataVersion)) {
1218  return false;
1219  }
1220 
1221  if (!ccSerializationHelper::GenericArrayToFile<Eigen::Array3f, 1,
1222  Eigen::Array3f>(m_center,
1223  out))
1224  return WriteError();
1225 
1226  if (!ccSerializationHelper::GenericArrayToFile<Eigen::Array3f, 1,
1227  Eigen::Array3f>(m_radii,
1228  out))
1229  return WriteError();
1230 
1231  if (!ccSerializationHelper::GenericArrayToFile<Eigen::Matrix3f, 1,
1232  Eigen::Matrix3f>(
1233  m_rotationMatrix, out))
1234  return WriteError();
1235 
1236  if (!ccSerializationHelper::GenericArrayToFile<CCVector3f, 1, CCVector3f>(
1237  m_grainColors, out))
1238  return WriteError();
1239 
1240  return true;
1241 }
1242 
1245 }
1246 
1248  short dataVersion,
1249  int flags,
1250  LoadedIDMap& oldToNewIDMap) {
1251  CVLog::Print("[G3Point] read GrainsAsEllipsoids object from .bin");
1252 
1253  if (!ccHObject::fromFile_MeOnly(in, dataVersion, flags, oldToNewIDMap))
1254  return false;
1255 
1256  if (!ccSerializationHelper::GenericArrayFromFile<Eigen::Array3f, 1,
1257  Eigen::Array3f>(
1258  m_center, in, dataVersion, "G3Point m_center")) {
1259  CVLog::Warning("[G3Point] error reading m_center");
1260  return ReadError();
1261  }
1262 
1263  if (!ccSerializationHelper::GenericArrayFromFile<Eigen::Array3f, 1,
1264  Eigen::Array3f>(
1265  m_radii, in, dataVersion, "G3Point m_radii")) {
1266  CVLog::Warning("[G3Point] error reading m_radii");
1267  return ReadError();
1268  }
1269 
1270  if (!ccSerializationHelper::GenericArrayFromFile<Eigen::Matrix3f, 1,
1271  Eigen::Matrix3f>(
1272  m_rotationMatrix, in, dataVersion,
1273  "G3Point m_rotationMatrix")) {
1274  CVLog::Warning("[G3Point] error reading m_rorationMatrix");
1275  return ReadError();
1276  }
1277 
1278  if (!ccSerializationHelper::GenericArrayFromFile<CCVector3f, 1, CCVector3f>(
1279  m_grainColors, in, dataVersion, "G3Point m_grainColors")) {
1280  CVLog::Warning("[G3Point] error reading m_rorationMatrix");
1281  return ReadError();
1282  }
1283 
1284  lockVisibility(false);
1285  setVisible(true);
1286 
1287  m_ccBBoxAll.setValidity(false);
1288  m_ccBBoxAll.clear();
1289 
1290  for (int idx = 0; idx < m_center.size(); idx++) {
1291  float maxRadius = m_radii[idx].maxCoeff();
1292  CCVector3 center(m_center[idx](0), m_center[idx](1), m_center[idx](2));
1293  if (m_radii[idx].x() !=
1294  -1) // all radii are equal to zero when the fit was not successful
1295  {
1296  m_fitNotOK.insert(idx);
1297  continue;
1298  }
1299  m_ccBBoxAll.add(CCVector3(center.x + maxRadius, center.y + maxRadius,
1300  center.z + maxRadius));
1301  m_ccBBoxAll.add(CCVector3(center.x - maxRadius, center.y - maxRadius,
1302  center.z - maxRadius));
1303  }
1304 
1305  m_ccBBoxAll.setValidity(true);
1306 
1308  m_meshNeedsUpdate = true;
1309  redrawDisplay();
1310 
1311  return true;
1312 }
constexpr double M_PI
Pi.
Definition: CVConst.h:19
Vector3Tpl< float > CCVector3f
Float 3D Vector.
Definition: CVGeom.h:801
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
int size
std::string name
int points
bool genericArrayToFile(const std::vector< Eigen::Array3f > &data, QFile &out)
bool readArrayHeader(QFile &in, short dataVersion, ::uint8_t &componentCount, ::uint32_t &elementCount)
bool rotationMatrixToFile(QString name, std::vector< Eigen::Matrix3f > rotationMatrix)
bool stdVectorToFile(QString name, std::vector< T > vector)
math::float4 color
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
bool fitEllipsoidToGrain(const int grainIndex, Eigen::Array3f &center, Eigen::Array3f &radii, Eigen::Matrix3f &rotationMatrix, const Method &method=DIRECT)
void updateBBoxOnlyOne(int index)
std::vector< Eigen::Array3f > m_center
std::vector< ccMesh * > m_meshes
std::vector< Eigen::Array3f > m_radii
bool implicitToExplicit(const Eigen::ArrayXd &parameters, Eigen::Array3f &center, Eigen::Array3f &radii, Eigen::Matrix3f &rotationMatrix)
void showOnlyOne(bool state)
void updateMeshAndLineSet()
Update mesh and lineset representations for ellipsoids.
std::vector< ccPointCloud * > m_pointsClouds
std::vector< float > normals
std::vector< int > indices
void draw(CC_DRAW_CONTEXT &context) override
Draws entity and its children.
void showAll(bool state)
ecvMainAppInterface * m_app
std::vector< float > vertices
bool toFile_MeOnly(QFile &out, short dataVersion) const override
Save own object data.
bool explicitToImplicit(const Eigen::Array3f &center, const Eigen::Array3f &radii, const Eigen::Matrix3f &rotationMatrix, Eigen::ArrayXd &parameters)
std::vector< CCVector3f > m_grainColors
std::vector< float > texCoords
Eigen::Array< bool, Eigen::Dynamic, Eigen::Dynamic > Xb
ccPointCloud * m_cloud
bool directFit(const Eigen::ArrayX3d &xyz, Eigen::ArrayXd &parameters)
std::set< int > m_fitNotOK
bool fromFile_MeOnly(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap) override
Loads own object data.
short minimumFileVersion_MeOnly() const override
GrainsAsEllipsoids(ecvMainAppInterface *app)
qCC_db
std::vector< cloudViewer::geometry::LineSet * > m_lineSets
std::vector< Eigen::Matrix3f > m_rotationMatrix
std::vector< std::vector< int > > m_stacks
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
void setGrainColorsTable(const RGBAColorsTableType &colorTable)
std::vector< int > lineIndices
Array of RGBA colors for each point.
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
Bounding box structure.
Definition: ecvBBox.h:25
virtual bool isVisible() const
Returns whether entity is visible or not.
virtual void lockVisibility(bool state)
Locks/unlocks visibility.
virtual void setVisible(bool state)
Sets entity visibility.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showMaterials(bool state)
Sets whether textures should be displayed or not.
void setPointSize(unsigned size=0)
Sets point size.
virtual bool toFile_MeOnly(QFile &out, short dataVersion) const
Save own object data.
void draw(CC_DRAW_CONTEXT &context) override
Draws entity and its children.
virtual short minimumFileVersion_MeOnly() const
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
virtual bool fromFile_MeOnly(QFile &in, short dataVersion, int flags, LoadedIDMap &oldToNewIDMap)
Loads own object data.
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
virtual void redrawDisplay(bool forceRedraw=true, bool only2D=false)
Redraws associated display.
void removeChild(ccHObject *child)
Mesh (triangle) material.
int addMaterial(ccMaterial::CShared mat, bool allowDuplicateNames=false)
Adds a material.
Mesh (triangle) material.
Definition: ecvMaterial.h:28
QSharedPointer< const ccMaterial > CShared
Const + Shared type.
Definition: ecvMaterial.h:31
QSharedPointer< ccMaterial > Shared
Shared type.
Definition: ecvMaterial.h:33
Triangular mesh.
Definition: ecvMesh.h:35
void setMaterialSet(ccMaterialSet *materialSet, bool autoReleaseOldMaterialSet=true)
Sets associated material set (may be shared)
void addTriangle(unsigned i1, unsigned i2, unsigned i3)
Adds a triangle to the mesh.
virtual void setLocked(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:117
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
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
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool resizeTheRGBTable(bool fillWithWhite=false)
Resizes the RGB colors array.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void setPointColor(size_t pointIndex, const ecvColor::Rgb &col)
Sets a particular point color.
ccPointCloud * partialClone(const cloudViewer::ReferenceCloud *selection, int *warnings=nullptr, bool withChildEntities=true) const
Creates a new point cloud object from a ReferenceCloud (selection)
static bool CorruptError()
Sends a custom error message (corrupted file) and returns 'false'.
QMultiMap< unsigned, unsigned > LoadedIDMap
Map of loaded unique IDs (old ID --> new ID)
static bool ReadError()
Sends a custom error message (read error) and returns 'false'.
static bool WriteError()
Sends a custom error message (write error) and returns 'false'.
static bool GenericArrayFromFile(std::vector< Type > &data, QFile &in, short dataVersion, const QString &verboseDescription)
Helper: loads a vector structure from file.
static bool GenericArrayToFile(const std::vector< Type > &data, QFile &out)
Helper: saves a vector to file.
void setValidity(bool state)
Sets bonding box validity.
Definition: BoundingBox.h:200
void clear()
Resets the bounding box.
Definition: BoundingBox.h:125
void add(const Vector3Tpl< T > &P)
'Enlarges' the bounding box with a point
Definition: BoundingBox.h:131
void setEigenPoints(const std::vector< Eigen::Vector3d > &points)
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
void getBoundingBox(CCVector3 &bbMin, CCVector3 &bbMax) override
Definition: PointCloudTpl.h:57
unsigned size() const override
Definition: PointCloudTpl.h:38
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.
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
virtual void computeMinAndMax()
Determines the min and max values.
Definition: ScalarField.h:123
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
LineSet define a sets of lines in 3D. A typical application is to display the point cloud corresponde...
Definition: LineSet.h:29
LineSet & PaintUniformColor(const Eigen::Vector3d &color)
Assigns each line in the LineSet the same color.
Definition: LineSet.h:101
RGB color structure.
Definition: ecvColorTypes.h:49
RGBA color structure.
Main application interface (for plugins)
virtual void putObjectBackIntoDBTree(ccHObject *obj, const ccHObjectContext &context)=0
Adds back object to DB tree.
virtual void addToDB(ccHObject *obj, bool updateZoom=false, bool autoExpandDBTree=true, bool checkDimensions=false, bool autoRedraw=true)=0
virtual ccHObjectContext removeObjectTemporarilyFromDBTree(ccHObject *obj)=0
Removes object temporarily from DB tree.
double colors[3]
int min(int a, int b)
Definition: cutil_math.h:53
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
ImGuiContext * context
Definition: Window.cpp:76
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
constexpr ColorCompType MAX
Max value of a single color component (default type)
Definition: ecvColorTypes.h:34
Eigen::Matrix< Index, 2, 1 > Vector2i
Definition: knncpp.h:29
std::vector< PointCoordinateType > radii
Definition: qM3C2Tools.cpp:42
Display context.
Backup "context" for an object.
Definition: lsd.c:149
int y
Definition: lsd.c:149
int x
Definition: lsd.c:149