DGtalTools  1.5.beta
mesh2heightfield.cpp
1 
30 #include <iostream>
31 #include <fstream>
32 #include "DGtal/base/Common.h"
33 #include "DGtal/helpers/StdDefs.h"
34 #include "DGtal/images/ImageContainerBySTLVector.h"
35 #include "DGtal/io/writers/GenericWriter.h"
36 #include "DGtal/io/readers/MeshReader.h"
37 #include "DGtal/io/writers/MeshWriter.h"
38 #include "DGtal/images/ConstImageAdapter.h"
39 #include "DGtal/kernel/BasicPointFunctors.h"
40 #include <DGtal/base/BasicFunctors.h>
41 #include "DGtal/math/linalg/SimpleMatrix.h"
42 
43 #include <DGtal/math/linalg/SimpleMatrix.h>
44 #include <DGtal/math/linalg/EigenDecomposition.h>
45 
46 
47 
48 #include "CLI11.hpp"
49 
50 
51 
52 using namespace std;
53 using namespace DGtal;
54 
55 
56 
104 
108 static CoVarianceMat
109 getCoVarianceMatFrom(const Matrix3x3Point &aMatrixPt){
110  CoVarianceMat res;
111  for(unsigned int i = 0; i<3; i++){
112  for(unsigned int j = 0; j<3; j++){
113  res.setComponent(i, j, aMatrixPt[j+i*3] );
114  }
115  }
116  return res;
117 }
118 
119 
120 std::pair<DGtal::Z3i::RealPoint, DGtal::Z3i::RealPoint>
122 {
123  std::pair<DGtal::Z3i::RealPoint, DGtal::Z3i::RealPoint> res;
124  Matrix3x3Point c ;
125  unsigned int nb = 0;
126  //compute centroïd of point cloud
127  Z3i::RealPoint centroid;
128  int cp=0;
129  for(auto it=begin; it != end; it++){
130  Z3i::RealPoint pt=*it;
131  centroid[0] += pt[0];
132  centroid[1] += pt[1];
133  centroid[2] += pt[2];
134  ++cp;
135  }
136  centroid /=cp;
137  //compute matrix like PCL : https://pointclouds.org/documentation/moment__of__inertia__estimation_8hpp_source.html
138  for(auto it=begin; it != end; it++){
139 
140  Z3i::RealPoint pt=*it;
141  pt[0] -= centroid[0];
142  pt[1] -= centroid[1];
143  pt[2] -= centroid[2];
144 
145  trace.info()<<pt<< std::endl;
146  c[4] += pt[1] * pt[1];
147  c[5] += pt[1] * pt[2];
148  c[8] += pt[2] * pt[2];
149  pt *= pt[0];
150  c[0] += pt[0];
151  c[1] += pt[1];
152  c[2] += pt[2];
153 
154  nb++;
155 
156  }
157  c[3] = c[1];
158  c[6] = c[2];
159  c[7] = c[5];
160  //normalize matrix
161  c = c/nb;
162  CoVarianceMat covar = getCoVarianceMatFrom(c);
166  unsigned int temp = 0;
167  unsigned int major_index = 0;
168  unsigned int middle_index = 1;
169  unsigned int minor_index = 2;
170  //reorder first and second axis according to their eigen value
171  if (eVals(major_index) < eVals(middle_index))
172  {
173  temp = major_index;
174  major_index = middle_index;
175  middle_index = temp;
176  }
177  if (eVals(major_index) < eVals(minor_index))
178  {
179  temp = major_index;
180  major_index = minor_index;
181  minor_index = temp;
182  }
183  if (eVals(middle_index) < eVals(minor_index))
184  {
185  temp = minor_index;
186  minor_index = middle_index;
187  middle_index = temp;
188  }
189 
190  res.first = eVects.column(major_index);
191  res.second = eVects.column(middle_index);
192 
193  return res;
194 }
195 
196 template<typename TPoint, typename TEmbeder>
197 TPoint
198 getNormal(const TPoint &vect, const TEmbeder &emb ){
199  Z3i::RealPoint p0 = emb(Z2i::RealPoint(0.0,0.0), false);
200  Z3i::RealPoint px = emb(Z2i::RealPoint(10.0,0.0), false);
201  Z3i::RealPoint py = emb(Z2i::RealPoint(0.0,10.0), false);
202  Z3i::RealPoint u = px-p0; u /= u.norm();
203  Z3i::RealPoint v = py-p0; v /= v.norm();
204  Z3i::RealPoint w = u.crossProduct(v); w /= w.norm();
206  t.setComponent(0,0, u[0]); t.setComponent(0,1, v[0]); t.setComponent(0, 2, w[0]);
207  t.setComponent(1,0, u[1]); t.setComponent(1,1, v[1]); t.setComponent(1, 2, w[1]);
208  t.setComponent(2,0, u[2]); t.setComponent(2,1, v[2]); t.setComponent(2, 2, w[2]);
209  return ((t.inverse())*vect);
210 }
211 
212 
213 
214 
215 template<typename TImage, typename TVectorField, typename TPoint>
216 void
217 fillPointArea(TImage &anImage, TVectorField &imageVectorField,
218  const TPoint aPoint, const unsigned int size, const unsigned int value, const Z3i::RealPoint &n){
219  typename TImage::Domain aDom(aPoint-TPoint::diagonal(size), aPoint+TPoint::diagonal(size));
220  for(typename TImage::Domain::ConstIterator it= aDom.begin(); it != aDom.end(); it++){
221  if (anImage.domain().isInside(*it)){
222  anImage.setValue(*it, value);
223  imageVectorField.setValue(*it, n);
224  }
225  }
226 }
227 
228 
229 int main( int argc, char** argv )
230 {
236  Image3D::Value, DGtal::functors::Identity > ImageAdapterExtractor;
237 
238 
239 // parse command line using CLI ----------------------------------------------
240  CLI::App app;
241  std::string inputFileName;
242  std::string outputFileName {"result.pgm"};
243  double meshScale = 1.0;
244  double triangleAreaUnit = 0.01;
245  double widthImageScan = {500};
246  double heightImageScan = {500};
247  bool orientAutoFrontX = false;
248  bool orientAutoFrontY = false;
249  bool orientAutoFrontZ = false;
250  bool orientAuto = true;
251  bool invertNormal = false;
252  bool orientBack = false;
253  bool exportNormals = false;
254  bool setBackgroundLastDepth = false;
255  bool backgroundNormalBack = false;
256  int maxScan {255};
257  int centerX {0};
258  int centerY {0};
259  int centerZ {200};
260 
261 
262  double nx{0};
263  double ny{0};
264  double nz{1};
265  DGtal::Z3i::RealPoint secDir; //orientation to be used when flag orientAuto is used
266  unsigned int minDiagVolSize {400}; //min size used to automatically resize mesh if included in unit box.
267  app.description("Convert a mesh file into a projected 2D image given from a normal direction N and from a starting point P. The 3D mesh discretized and scanned in the normal direction N, starting from P with a step 1.\n Example:\n mesh2heightfield -i ${DGtal}/examples/samples/tref.off heighfield.pgm \n");
268  app.add_option("-i,--input,1", inputFileName, "mesh file (.off)" )
269  ->required()
270  ->check(CLI::ExistingFile);
271  app.add_option("-o,--output,2", outputFileName, "sequence of discrete point file (.sdp) ", true );
272  app.add_option("--meshScale,-s", meshScale, "change the default mesh scale (each vertex multiplied by the scale) ");
273  app.add_option("--remeshMinArea,-a", triangleAreaUnit, "ajust the remeshing min triangle are used to avoid empty areas", true);
274 
275  app.add_option("--heightFieldMaxScan", maxScan, "set the maximal scan deep.", true );
276  app.add_option("-x,--centerX",
277  centerX, "choose x center of the projected image.", true);
278  app.add_option("-y,--centerY",
279  centerY, "choose y center of the projected image.", true);
280  app.add_option("-z,--centerZ",
281  centerZ, "choose z center of the projected image.", true);
282  auto optNx = app.add_option("--nx",
283  nx, "set the x component of the projection direction.", true);
284  auto optNy = app.add_option("--ny",
285  ny, "set the y component of the projection direction.", true);
286  auto optNz = app.add_option("--nz",
287  nz, "set the z component of the projection direction.", true);
288  app.add_flag("--invertNormals,-v", invertNormal, "invert normal vector of the mesh");
289  app.add_option("--width", widthImageScan, "set the width of the area to be extracted as an height field image. (note that the resulting image width also depends of the scale parameter (option --meshScale))", true );
290  app.add_option("--height", heightImageScan, "set the height of the area to extracted as an height field image. (note that the resulting image height also depends of the scale parameter (option --meshScale))", true );
291  app.add_flag("--orientAutoFrontX", orientAutoFrontX,"automatically orients the camera in front according the x axis." );
292  app.add_flag("--orientAutoFrontY", orientAutoFrontY,"automatically orients the camera in front according the y axis." );
293  app.add_flag("--orientAutoFrontZ", orientAutoFrontZ,"automatically orients the camera in front according the z axis." );
294  app.add_flag("--orientBack", orientBack, "change the camera direction to back instead front as given in orientAutoFront{X,Y,Z} options.");
295  app.add_flag("--exportNormals", exportNormals, "export mesh normal vectors (given in the image height field basis).");
296  app.add_flag("--backgroundNormalBack", backgroundNormalBack, "set the normals of background in camera opposite direction (to obtain a black background in rendering).");
297  app.add_flag("--setBackgroundLastDepth", setBackgroundLastDepth, "change the default background (black with the last filled intensity).");
298 
299  app.get_formatter()->column_width(40);
300  CLI11_PARSE(app, argc, argv);
301  // END parse command line using CLI ----------------------------------------------
302 
303  orientAuto = optNx->count() == 0 && optNy->count() == 0 && optNy->count() == 0;
304 
305  trace.info() << "Reading input file " << inputFileName ;
306  Mesh<Z3i::RealPoint> inputMesh(true);
307  inputMesh << inputFileName;
308  std::pair<Z3i::RealPoint, Z3i::RealPoint> b = inputMesh.getBoundingBox();
309  double diagDist = (b.first-b.second).norm();
310  if(diagDist<minDiagVolSize){
311  meshScale = minDiagVolSize/diagDist;
312  inputMesh.rescale(meshScale);
313  }
314 
315  triangleAreaUnit *= meshScale;
316  // get vertex
317  inputMesh.vertexBegin();
318 
319  inputMesh.quadToTriangularFaces();
320  trace.info() << " [done] " << std::endl ;
321  int maxArea = triangleAreaUnit+1.0 ;
322  while (maxArea> triangleAreaUnit)
323  {
324  trace.info()<< "Iterating mesh subdivision ... "<< maxArea;
325  maxArea = inputMesh.subDivideTriangularFaces(triangleAreaUnit);
326  trace.info() << " [done]"<< std::endl;
327  }
328 
329  std::pair<Z3i::RealPoint, Z3i::RealPoint> bb = inputMesh.getBoundingBox();
330  Image3D::Domain meshDomain( bb.first-Z3i::Point::diagonal(1),
331  bb.second+Z3i::Point::diagonal(1));
332 
333  // vol image filled from the mesh vertex.
334  Image3D meshVolImage(meshDomain);
335  VectorFieldImage3D meshNormalImage(meshDomain);
336  Z3i::RealPoint z(0.0,0.0,0.0);
337  for(Image3D::Domain::ConstIterator it = meshVolImage.domain().begin();
338  it != meshVolImage.domain().end(); it++)
339  {
340  meshVolImage.setValue(*it, 0);
341  meshNormalImage.setValue(*it, z);
342  }
343 
344  // Filling mesh faces in volume.
345  for(unsigned int i =0; i< inputMesh.nbFaces(); i++)
346  {
347  trace.progressBar(i, inputMesh.nbFaces());
348  typename Mesh<Z3i::RealPoint>::MeshFace aFace = inputMesh.getFace(i);
349  if(aFace.size()==3)
350  {
351  Z3i::RealPoint p1 = inputMesh.getVertex(aFace[0]);
352  Z3i::RealPoint p2 = inputMesh.getVertex(aFace[1]);
353  Z3i::RealPoint p3 = inputMesh.getVertex(aFace[2]);
354  Z3i::RealPoint n = (p2-p1).crossProduct(p3-p1);
355  n /= invertNormal? -n.norm(): n.norm();
356  Z3i::RealPoint c = (p1+p2+p3)/3.0;
357  fillPointArea(meshVolImage, meshNormalImage, p1, 1, 1, n);
358  fillPointArea(meshVolImage, meshNormalImage, p2, 1, 1, n);
359  fillPointArea(meshVolImage, meshNormalImage, p3, 1, 1, n);
360  fillPointArea(meshVolImage, meshNormalImage, c, 1, 1, n);
361  }
362  }
363 
364 
365  if(orientAutoFrontX || orientAutoFrontY || orientAutoFrontZ)
366  {
367  Z3i::Point ptL = meshVolImage.domain().lowerBound();
368  Z3i::Point ptU = meshVolImage.domain().upperBound();
369  Z3i::Point ptC = (ptL+ptU)/2;
370  centerX=ptC[0]; centerY=ptC[1]; centerZ=ptC[2];
371  nx=0; ny=0; nz=0;
372  }
373 
374  if(orientAutoFrontX)
375  {
376  nx=(orientBack?-1.0:1.0);
377  maxScan = meshVolImage.domain().upperBound()[0]- meshVolImage.domain().lowerBound()[0];
378  centerX = centerX + (orientBack? maxScan/2: -maxScan/2) ;
379  }
380  if(orientAutoFrontY)
381  {
382  ny=(orientBack?-1.0:1.0);
383  maxScan = meshVolImage.domain().upperBound()[1]- meshVolImage.domain().lowerBound()[1];
384  centerY = centerY + (orientBack? maxScan/2: -maxScan/2);
385  }
386  if(orientAutoFrontZ)
387  {
388  nz=(orientBack?-1.0:1.0);
389  maxScan = meshVolImage.domain().upperBound()[2]-meshVolImage.domain().lowerBound()[2];
390  centerZ = centerZ + (orientBack? maxScan/2: -maxScan/2);
391  }
392  if (orientAuto)
393  {
394  Z3i::Point ptL = meshVolImage.domain().lowerBound();
395  Z3i::Point ptU = meshVolImage.domain().upperBound();
396  Z3i::Point ptC = (ptL+ptU)/2;
397  int maxScanZ = meshVolImage.domain().upperBound()[2]-meshVolImage.domain().lowerBound()[2];
398  int maxScanY = meshVolImage.domain().upperBound()[1]-meshVolImage.domain().lowerBound()[1];
399  int maxScanX = meshVolImage.domain().upperBound()[0]-meshVolImage.domain().lowerBound()[0];
400  maxScan = std::max(std::max(maxScanX, maxScanY), maxScanZ);
401  trace.info() << "Automatic orientation on volume of bounds: " << ptL << " " << ptU << std::endl;
402  trace.info() << "Computing main dir...";
403  auto dir = getMainDirsCoVar(inputMesh.vertexBegin(), inputMesh.vertexEnd());
404  trace.info() << "[done]"<<std::endl;
405  nx = dir.first[0];
406  ny = dir.first[1];
407  nz = dir.first[2];
408  secDir = dir.second;
409  centerX=ptC[0]-(double)maxScan*nx/2;
410  centerY=ptC[1]-(double)maxScan*ny/2;
411  centerZ=ptC[2]-(double)maxScan*nz/2;
412 
413 
414  }
415  functors::Rescaling<unsigned int, Image2D::Value> scaleFctDepth(0, maxScan, 0, 255);
416  if(maxScan > std::numeric_limits<Image2D::Value>::max())
417  {
418  trace.info()<< "Max depth value outside image intensity range: " << maxScan
419  << " (use a rescaling functor which implies a loss of precision)" << std::endl;
420  }
421  trace.info() << "Processing image to output file " << outputFileName;
422 
423  Image2D::Domain aDomain2D(DGtal::Z2i::Point(0,0),
424  DGtal::Z2i::Point(widthImageScan, heightImageScan));
425  Z3i::Point ptCenter (centerX, centerY, centerZ);
426 
427  Z3i::RealPoint normalDir (nx, ny, nz);
428  Image2D resultingImage(aDomain2D);
429  VectorFieldImage2D resultingVectorField(aDomain2D);
430 
431 
432  for(Image2D::Domain::ConstIterator it = resultingImage.domain().begin();
433  it != resultingImage.domain().end(); it++){
434  resultingImage.setValue(*it, 0);
435  resultingVectorField.setValue(*it, z);
436  }
438 
439  unsigned int maxDepthFound = 0;
440  Z3i::Point c (ptCenter-normalDir, DGtal::functors::Round<>());
441  DGtal::functors::Point2DEmbedderIn3D<DGtal::Z3i::Domain > embedder(meshVolImage.domain(),
442  c,
443  normalDir,
444  widthImageScan);
445  if (orientAuto){
446  embedder = DGtal::functors::Point2DEmbedderIn3D<DGtal::Z3i::Domain >(meshVolImage.domain(),
447  c,
448  normalDir,
449  secDir,
450  widthImageScan);
451  }
452  int k = 0;
453  bool firstFound = false;
454  while (k < maxScan || !firstFound){
455  embedder.shiftOriginPoint(normalDir);
456  trace.progressBar(k, maxScan);
457  ImageAdapterExtractor extractedImage(meshVolImage, aDomain2D, embedder, idV);
458  for(Image2D::Domain::ConstIterator it = extractedImage.domain().begin();
459  it != extractedImage.domain().end(); it++)
460  {
461  if(resultingImage(*it)== 0 && extractedImage(*it)!=0)
462  {
463 
464  if (!firstFound) {
465  firstFound = true;
466  }
467  maxDepthFound = k;
468  resultingImage.setValue(*it, scaleFctDepth(maxScan-k));
469  resultingVectorField.setValue(*it, getNormal(meshNormalImage(embedder(*it)), embedder));
470  }
471  }
472  if (firstFound){
473  k++;
474  }
475  }
476  if (setBackgroundLastDepth)
477  {
478  for(Image2D::Domain::ConstIterator it = resultingImage.domain().begin();
479  it != resultingImage.domain().end(); it++){
480  if( resultingImage(*it)== 0 )
481  {
482  resultingImage.setValue(*it, scaleFctDepth(maxScan-maxDepthFound));
483  }
484  }
485  }
486  bool inverBgNormal = backgroundNormalBack;
487  for(Image2D::Domain::ConstIterator it = resultingImage.domain().begin();
488  it != resultingImage.domain().end(); it++){
489  if(resultingVectorField(*it)==Z3i::RealPoint(0.0, 0.0, 0.0))
490  {
491  resultingVectorField.setValue(*it,Z3i::RealPoint(0, 0, inverBgNormal?-1: 1));
492  }
493  }
494  resultingImage >> outputFileName;
495  if(exportNormals){
496  std::stringstream ss;
497  ss << outputFileName << ".normals";
498  std::ofstream outN;
499  outN.open(ss.str().c_str(), std::ofstream::out);
500  for(Image2D::Domain::ConstIterator it = resultingImage.domain().begin();
501  it != resultingImage.domain().end(); it++){
502  outN << (*it)[0] << " " << (*it)[1] << " " << 0 << std::endl;
503  outN <<(*it)[0]+ resultingVectorField(*it)[0] << " "
504  <<(*it)[1]+resultingVectorField(*it)[1] << " "
505  << resultingVectorField(*it)[2];
506  outN << std::endl;
507  }
508  }
509  return EXIT_SUCCESS;;
510 }
int main(int argc, char **argv)
static void getEigenDecomposition(const Matrix &matrix, Matrix &eigenVectors, Vector &eigenValues)
std::vector< Value >::const_iterator ConstIterator
std::vector< unsigned int > MeshFace
VertexStorage::iterator Iterator
Self crossProduct(const Self &v) const
double norm(const NormType type=L_2) const
SimpleMatrix< Component, TM, TN > inverse() const
void setComponent(const DGtal::Dimension i, const DGtal::Dimension j, const Component &aValue)
ColumnVector column(const DGtal::Dimension j) const
std::ostream & info()
void progressBar(const double currentValue, const double maximalValue)
Trace trace(traceWriterTerm)