38 #include "DGtal/base/Common.h"
39 #include "DGtal/base/CountedPtr.h"
40 #include "DGtal/base/CountedConstPtrOrConstPtr.h"
41 #include "DGtal/helpers/StdDefs.h"
42 #include "DGtal/math/Statistic.h"
43 #include "DGtal/math/MPolynomial.h"
44 #include "DGtal/topology/LightImplicitDigitalSurface.h"
45 #include "DGtal/graph/DepthFirstVisitor.h"
46 #include "DGtal/graph/GraphVisitorRange.h"
47 #include "DGtal/geometry/surfaces/estimation/CNormalVectorEstimator.h"
48 #include "DGtal/geometry/surfaces/estimation/VoronoiCovarianceMeasureOnDigitalSurface.h"
49 #include "DGtal/geometry/surfaces/estimation/VCMDigitalSurfaceLocalEstimator.h"
50 #include "DGtal/geometry/surfaces/estimation/TrueDigitalSurfaceLocalEstimator.h"
51 #include "DGtal/geometry/surfaces/estimation/IIGeometricFunctors.h"
52 #include "DGtal/geometry/surfaces/estimation/IntegralInvariantCovarianceEstimator.h"
53 #include "DGtal/geometry/volumes/KanungoNoise.h"
54 #include "DGtal/shapes/GaussDigitizer.h"
55 #include "DGtal/shapes/ShapeGeometricFunctors.h"
56 #include "DGtal/shapes/implicit/ImplicitPolynomial3Shape.h"
57 #include "DGtal/images/ImageContainerBySTLVector.h"
58 #include "DGtal/images/SimpleThresholdForegroundPredicate.h"
59 #include "DGtal/io/readers/MPolynomialReader.h"
60 #include "DGtal/io/colormaps/GradientColorMap.h"
61 #ifdef WITH_VISU3D_QGLVIEWER
62 #include "DGtal/io/viewers/Viewer3D.h"
63 #include "DGtal/io/Display3DFactory.h"
67 using namespace DGtal;
201 double minAABB {-10.0};
202 double maxAABB {10.0};
203 double gridstep {1.0};
204 double R_radius {5.0};
205 double r_radius {3.0};
207 double trivial_radius {3.0};
209 std::string polynomials;
210 std::string estimator {
"True"};
211 std::string kernel {
"hat"};
212 std::string output {
"output"};
213 std::string exportX {
"None"};
214 std::string view {
"None"};
215 bool angle_deviation_stats;
222 template <
typename SCell,
typename RealVector>
223 struct GradientMapAdapter {
224 typedef std::map<SCell,RealVector> SCell2RealVectorMap;
225 typedef SCell Argument;
229 RealVector operator()(
const Argument& arg )
const
231 typename SCell2RealVectorMap::const_iterator it = myMap->find( arg );
232 if ( it != myMap->end() )
return it->second;
238 template <
typename SCellEmbedder>
242 using SCellEmbedder::operator();
246 typedef SCell Argument;
249 typedef std::map<SCell,RealVector> SCell2RealVectorMap;
250 typedef GradientMapAdapter<SCell,RealVector> GradientMap;
257 GradientMap gradientMap()
const
259 return GradientMap( myMap );
268 const Estimator& estimator,
269 std::ostream& output )
274 typedef typename Estimator::Quantity Quantity;
276 std::map<Surfel,Quantity> normals;
277 for ( ConstIterator it = surface.
begin(), itE = surface.
end(); it != itE; ++it )
279 Quantity n_est = estimator.eval( it );
280 normals[ *it ] = n_est;
283 typedef SCellEmbedderWithNormal< CanonicSCellEmbedder<KSpace> > Embedder;
284 Embedder embedder( surfelEmbedder, normals );
292 template <
typename KSpace,
293 typename ImplicitShape,
295 typename TrueEstimator,
297 void computeEstimation
298 (
const AllParams ¶ms,
300 const ImplicitShape& shape,
301 const Surface& surface,
302 TrueEstimator& true_estimator,
303 Estimator& estimator )
305 typedef typename Surface::Surfel Surfel;
306 typedef typename Estimator::Quantity Quantity;
307 typedef double Scalar;
310 typedef typename VisitorRange::ConstIterator VisitorConstIterator;
312 std::string fname = params.output;
313 string nameEstimator = params.estimator;
316 std::vector<Quantity> n_estimations;
317 estimator.eval( range->begin(), range->end(), std::back_inserter( n_estimations ) );
318 trace.
info() <<
"- nb estimations = " << n_estimations.size() << std::endl;
323 std::vector<Quantity> n_true_estimations;
324 true_estimator.eval( range->begin(), range->end(), std::back_inserter( n_true_estimations ) );
325 trace.
info() <<
"- nb estimations = " << n_true_estimations.size() << std::endl;
329 ASSERT( n_estimations.size() == n_true_estimations.size() );
330 for (
unsigned int i = 0; i < n_estimations.size(); ++i )
331 if ( n_estimations[ i ].dot( n_true_estimations[ i ] ) < 0 )
332 n_estimations[ i ] = -n_estimations[ i ];
348 if ( params.angle_deviation_stats )
351 std::ostringstream adev_sstr;
352 adev_sstr << fname <<
"-" << nameEstimator <<
"-angle-deviation-"
353 << estimator.h() <<
".txt";
357 for ( VisitorConstIterator it = range->begin(), itE = range->end(); it != itE; ++it, ++i )
359 Quantity n_est = n_estimations[ i ];
360 Quantity n_true_est = n_true_estimations[ i ];
361 Scalar angle_error = acos( n_est.dot( n_true_est ) );
365 std::ofstream adev_output( adev_sstr.str().c_str() );
366 adev_output <<
"# Average error X of the absolute angle between two vector estimations." << std::endl;
367 adev_output <<
"# h L1 L2 Loo E[X] Var[X] Min[X] Max[X] Nb[X]" << std::endl;
368 adev_output << estimator.h()
369 <<
" " << adev_stat.
mean()
371 + adev_stat.
mean()*adev_stat.
mean() )
372 <<
" " << adev_stat.
max()
373 <<
" " << adev_stat.
mean()
375 <<
" " << adev_stat.
min()
376 <<
" " << adev_stat.
max()
382 if ( params.exportX !=
"None" )
385 std::ostringstream export_sstr;
386 export_sstr << fname <<
"-" << nameEstimator <<
"-cells-"
387 << estimator.h() <<
".txt";
388 std::ofstream export_output( export_sstr.str().c_str() );
389 export_output <<
"# ImaGene viewer (viewSetOfSurfels) file format for displaying cells." << std::endl;
390 bool adev = params.exportX ==
"AngleDeviation";
393 for ( VisitorConstIterator it = range->begin(), itE = range->end(); it != itE; ++it, ++i )
395 Quantity n_est = n_estimations[ i ];
396 Quantity n_true_est = n_true_estimations[ i ];
397 Scalar angle_error = acos( n_est.dot( n_true_est ) )*180.0 / 3.14159625;
401 <<
" " << min( 1023, max( 512+K.
sKCoord( s, 0 ), 0 ) )
402 <<
" " << min( 1023, max( 512+K.
sKCoord( s, 1 ), 0 ) )
403 <<
" " << min( 1023, max( 512+K.
sKCoord( s, 2 ), 0 ) )
404 <<
" " << K.
sSign( s );
406 if ( adev ) c = grad( max( 0.0, min( angle_error, 40.0 ) ) );
407 export_output <<
" " << ((double) c.
red() / 255.0 )
408 <<
" " << ((
double) c.
green() / 255.0 )
409 <<
" " << ((
double) c.
blue() / 255.0 );
410 export_output <<
" " << n_est[ 0 ] <<
" " << n_est[ 1 ]
411 <<
" " << n_est[ 2 ] << std::endl;
413 export_output.close();
416 if ( params.normals )
419 std::ostringstream export_sstr;
420 export_sstr << fname <<
"-" << nameEstimator <<
"-normals-"
421 << estimator.h() <<
".txt";
422 std::ofstream export_output( export_sstr.str().c_str() );
423 export_output <<
"# kx ky kz sign n_est[0] n_est[1] n_est[2] n_true[0] n_true[1] n_true[2]" << std::endl;
426 for ( VisitorConstIterator it = range->begin(), itE = range->end(); it != itE; ++it, ++i )
428 Quantity n_est = n_estimations[ i ];
429 Quantity n_true_est = n_true_estimations[ i ];
433 <<
" " << K.
sSign( s )
434 <<
" " << n_est[ 0 ] <<
" " << n_est[ 1 ] <<
" " << n_est[ 2 ]
435 <<
" " << n_true_est[ 0 ] <<
" " << n_true_est[ 1 ] <<
" " << n_true_est[ 2 ]
438 export_output.close();
444 std::ostringstream export_sstr;
445 export_sstr << fname <<
"-" << nameEstimator <<
"-noff-"
446 << estimator.h() <<
".off";
447 std::ofstream export_output( export_sstr.str().c_str() );
448 std::map<Surfel,Quantity> normals;
451 for ( VisitorConstIterator it = range->begin(), itE = range->end(); it != itE; ++it, ++i )
453 Quantity n_est = n_estimations[ i ];
454 normals[ *it ] = n_est;
457 typedef SCellEmbedderWithNormal< CanonicSCellEmbedder<KSpace> > Embedder;
458 Embedder embedder( surfelEmbedder, normals );
459 surface.exportAs3DNOFF( export_output, embedder );
460 export_output.close();
463 #ifdef WITH_VISU3D_QGLVIEWER
464 if ( params.exportX !=
"None" )
470 char name[] =
"Viewer";
474 QApplication application( argc, argv );
475 MyViewever3D viewer( K );
477 viewer <<
SetMode3D( s.className(),
"Basic" );
479 bool adev = params.view ==
"AngleDeviation";
483 for ( VisitorConstIterator it = range->begin(), itE = range->end(); it != itE; ++it, ++i )
485 Quantity n_est = n_estimations[ i ];
486 Quantity n_true_est = n_true_estimations[ i ];
487 Scalar angle_error = acos( n_est.dot( n_true_est ) )*180.0 / 3.14159625;
490 if ( adev ) c = grad( max( 0.0, min( angle_error, 40.0 ) ) );
491 viewer.setFillColor( c );
492 MyDisplay3DFactory::drawOrientedSurfelWithNormal( viewer, s, n_est,
false );
495 viewer << MyViewever3D::updateDisplay;
502 template <
typename KSpace,
503 typename ImplicitShape,
505 typename KernelFunction,
506 typename PointPredicate>
508 (
const AllParams ¶ms,
510 const ImplicitShape& shape,
511 const Surface& surface,
512 const KernelFunction& chi,
513 const PointPredicate& ptPred )
515 string nameEstimator = params.estimator;
516 double h = params.gridstep;
519 TrueEstimator true_estimator;
520 true_estimator.attach( shape );
521 true_estimator.setParams( K, NormalFunctor(), 20, 0.1, 0.01 );
522 true_estimator.
init( h, surface.begin(), surface.end() );
523 if ( nameEstimator ==
"True" )
528 estimator.
attach( shape );
529 estimator.setParams( K, NormalFunctor(), 20, 0.1, 0.01 );
530 estimator.init( h, surface.begin(), surface.end() );
532 computeEstimation( params, K, shape, surface, true_estimator, estimator );
534 else if ( nameEstimator ==
"VCM" )
538 typedef typename Surface::DigitalSurfaceContainer SurfaceContainer;
541 KernelFunction> VCMOnSurface;
544 KernelFunction, NormalFunctor> VCMNormalEstimator;
545 int embedding = params.embedding;
548 double R = params.R_radius;
549 double r = params.r_radius;
550 double t = params.trivial_radius;
551 double alpha = params.alpha;
552 if ( alpha != 0.0 ) R *= pow( h, alpha-1.0 );
553 if ( alpha != 0.0 ) r *= pow( h, alpha-1.0 );
554 trace.
info() <<
"- R=" << R <<
" r=" << r <<
" t=" << t << std::endl;
555 VCMNormalEstimator estimator;
556 estimator.attach( surface );
557 estimator.setParams( embType, R, r, chi, t, Metric(),
true );
558 estimator.init( h, surface.begin(), surface.end() );
560 computeEstimation( params, K, shape, surface, true_estimator, estimator );
562 else if ( nameEstimator ==
"II" )
568 typedef typename Domain::ConstIterator DomainConstIterator;
572 double r = params.r_radius;
573 double alpha = params.alpha;
574 if ( alpha != 0.0 ) r *= pow( h, alpha-1.0 );
578 Image image( domain );
579 for ( DomainConstIterator it = domain.begin(), itE = domain.end(); it != itE; ++it )
581 image.setValue( *it, ptPred( *it ) );
585 ThresholdedImage thresholdedImage( image,
false );
586 IINormalEstimator ii_estimator( K, thresholdedImage );
587 ii_estimator.setParams( r );
588 ii_estimator.init( h, surface.begin(), surface.end() );
591 computeEstimation( params, K, shape, surface, true_estimator, ii_estimator );
596 template <
typename KSpace,
597 typename ImplicitShape,
599 typename PointPredicate>
601 (
const AllParams& params,
603 const ImplicitShape& shape,
604 const Surface& surface,
605 const PointPredicate& ptPred )
607 string kernel = params.kernel;
608 double h = params.gridstep;
609 double r = params.r_radius;
610 double alpha = params.alpha;
611 if ( alpha != 0.0 ) r *= pow( h, alpha-1.0 );
612 if ( kernel ==
"hat" ) {
615 KernelFunction chi_r( 1.0, r );
616 chooseEstimator( params, K, shape, surface, chi_r, ptPred );
617 }
else if ( kernel ==
"ball" ) {
620 KernelFunction chi_r( 1.0, r );
621 chooseEstimator( params, K, shape, surface, chi_r, ptPred );
625 template <
typename KSpace,
626 typename ImplicitShape,
627 typename ImplicitDigitalShape >
629 (
const AllParams ¶ms,
631 const ImplicitShape& shape,
632 const ImplicitDigitalShape& dshape )
635 typedef double Scalar;
636 if ( params.noise == 0.0 )
641 typedef typename Surface::Surfel Surfel;
647 trace.
error() <<
"ERROR Unable to find bel." << std::endl;
650 SurfaceContainer* surfaceContainer =
new SurfaceContainer( K, dshape, surfAdj, bel );
652 trace.
info() <<
"- surface component has " << ptrSurface->size() <<
" surfels." << std::endl;
654 chooseKernel( params, K, shape, *ptrSurface, dshape );
659 typedef typename ImplicitDigitalShape::Domain
Domain;
663 typedef typename Surface::Surfel Surfel;
666 const Domain shapeDomain = dshape.getDomain();
667 KanungoPredicate* noisified_dshape =
new KanungoPredicate( dshape, shapeDomain, params.noise );
670 double minsize = dshape.getUpperBound()[0] - dshape.getLowerBound()[0];
671 unsigned int nb_surfels = 0;
672 unsigned int tries = 0;
677 trace.
error() <<
"ERROR Unable to find bel." << std::endl;
680 SurfaceContainer* surfaceContainer =
new SurfaceContainer( K, *noisified_dshape, surfAdj, bel );
682 nb_surfels = ptrSurface->size();
683 }
while ( ( nb_surfels < 2 * minsize ) && ( tries++ < 150 ) );
686 trace.
error() <<
"ERROR cannot find a proper bel in a big enough component." << std::endl;
689 trace.
info() <<
"- surface component has " << nb_surfels <<
" surfels." << std::endl;
691 chooseKernel( params, K, shape, *ptrSurface, *noisified_dshape );
698 int main(
int argc,
char** argv )
705 std::stringstream ss_descr;
706 ss_descr <<
"Computes a normal vector field over a digitized 3D implicit surface for several estimators (II|VCM|Trivial|True), specified with -e. You may add Kanungo noise with option -N. These estimators are compared with ground truth. You may then: 1) visualize the normals or the angle deviations with -V (if WITH_QGL_VIEWER is enabled), 2) outputs them as a list of cells/estimations with -n, 3) outputs them as a ImaGene file with -O, 4) outputs them as a NOFF file with -O, 5) computes estimation statistics with option -S.";
708 ss_descr <<
"Example of use:\n"
709 <<
"./generic3dNormalEstimator -p \"90-3*x^2-2*y^2-z^2\" -o VCM-ellipse -a -10 -A 10 -e VCM -R 3 -r 3 -t 2 -E 0 -x Normals" << endl << endl
710 <<
"Example of implicit surfaces (specified by -p):" << endl
711 <<
" - ellipse : 90-3*x^2-2*y^2-z^2 " << endl
712 <<
" - torus : -1*(x^2+y^2+z^2+6*6-2*2)^2+4*6*6*(x^2+y^2) " << endl
713 <<
" - rcube : 6561-x^4-y^4-z^4" << endl
714 <<
" - goursat : 8-0.03*x^4-0.03*y^4-0.03*z^4+2*x^2+2*y^2+2*z^2" << endl
715 <<
" - distel : 10000-(x^2+y^2+z^2+1000*(x^2+y^2)*(x^2+z^2)*(y^2+z^2))" << endl
716 <<
" - leopold : 100-(x^2*y^2*z^2+4*x^2+4*y^2+3*z^2)" << endl
717 <<
" - diabolo : x^2-(y^2+z^2)^2" << endl
718 <<
" - heart : -1*(x^2+2.25*y^2+z^2-1)^3+x^2*z^3+0.1125*y^2*z^3" << endl
719 <<
" - crixxi : -0.9*(y^2+z^2-1)^2-(x^2+y^2-1)^3" << endl << endl;
720 ss_descr <<
"Implemented estimators (specified by -e):" << endl
721 <<
" - True : supposed to be the ground truth for computations. Of course, it is only approximations." << endl
722 <<
" - VCM : normal estimator by digital Voronoi Covariance Matrix. Radii parameters are given by -R, -r." << endl
723 <<
" - II : normal estimator by Integral Invariants. Radius parameter is given by -r." << endl
724 <<
" - Trivial : the normal obtained by average trivial surfel normals in a ball neighborhood. Radius parameter is given by -r." << endl
726 ss_descr <<
"Note:" << endl
727 <<
" - This is a normal *direction* evaluator more than a normal vector evaluator. Orientations of normals are deduced from ground truth. This is due to the fact that II and VCM only estimates normal directions." << endl
728 <<
" - This tool only analyses one surface component, and one that contains at least as many surfels as the width of the digital bounding box. This is required when analysing noisy data, where a lot of the small components are spurious. The drawback is that you cannot analyse the normals on a surface with several components." << endl;
730 app.description(ss_descr.str());
732 app.add_option(
"--polynomial,-p",allParams.polynomials,
"the implicit polynomial whose zero-level defines the shape of interest.") ->required();
733 app.add_option(
"--noise,-N", allParams.noise,
"the Kanungo noise level l=arg, with l^d the probability that a point at distance d is flipped inside/outside.",
true );
734 app.add_option(
"--minAABB,-a", allParams.minAABB,
"the min value of the AABB bounding box (domain)",
true );
735 app.add_option(
"--maxAABB,-A", allParams.maxAABB,
"the max value of the AABB bounding box (domain)",
true );
736 app.add_option(
"--gridstep,-g", allParams.gridstep,
"the gridstep that defines the digitization (often called h).",
true);
737 app.add_option(
"--estimator,-e", allParams.estimator,
"the chosen normal estimator: True | VCM | II | Trivial",
true)
738 -> check(CLI::IsMember({
"True",
"VCM",
"II",
"Trivial"}));
739 app.add_option(
"--R-radius,-R", allParams.R_radius,
"the constant for parameter R in R(h)=R h^alpha (VCM).",
true);
740 app.add_option(
"--r-radius,-r", allParams.r_radius,
"the constant for parameter r in r(h)=r h^alpha (VCM,II,Trivial).",
true);
741 app.add_option(
"--kernel,-k", allParams.kernel,
"the function chi_r, either hat or ball.",
true);
742 app.add_option(
"--alpha", allParams.alpha,
"the parameter alpha in r(h)=r h^alpha (VCM).",
true);
743 app.add_option(
"--trivial-radius,-t", allParams.trivial_radius,
"the parameter t defining the radius for the Trivial estimator. Also used for reorienting the VCM.",
true);
744 app.add_option(
"--embedding,-E",allParams.embedding,
"the surfel -> point embedding for VCM estimator: 0: Pointels, 1: InnerSpel, 2: OuterSpel.",
true);
745 app.add_option(
"--output,-o", allParams.output,
"the output basename. All generated files will have the form <arg>-*, for instance <arg>-angle-deviation-<gridstep>.txt, <arg>-normals-<gridstep>.txt, <arg>-cells-<gridstep>.txt, <arg>-noff-<gridstep>.off.",
true);
746 app.add_flag(
"--angle-deviation-stats,-S", allParams.angle_deviation_stats,
"computes angle deviation error and outputs them in file <basename>-angle-deviation-<gridstep>.txt, as specified by -o <basename>.");
748 app.add_option(
"--export,-x",allParams.exportX,
"exports surfel normals which can be viewed with ImaGene tool 'viewSetOfSurfels' in file <basename>-cells-<gridstep>.txt, as specified by -o <basename>. Parameter <arg> is None|Normals|AngleDeviation. The color depends on the angle deviation in degree: 0 metallic blue, 5 light cyan, 10 light green, 15 light yellow, 20 yellow, 25 orange, 30 red, 35, dark red, 40- grey",
true );
749 app.add_flag(
"--normals,-n", allParams.normals,
"outputs every surfel, its estimated normal, and the ground truth normal in file <basename>-normals-<gridstep>.txt, as specified by -o <basename>.");
750 app.add_flag(
"--noff,-O", allParams.noff,
"exports the digital surface with normals as NOFF file <basename>-noff-<gridstep>.off, as specified by -o <basename>..");
751 #ifdef WITH_VISU3D_QGLVIEWER
752 app.add_option(
"--view,-V", allParams.view,
"view the digital surface with normals. Parameter <arg> is None|Normals|AngleDeviation. The color depends on the angle deviation in degree: 0 metallic blue, 5 light cyan, 10 light green, 15 light yellow, 20 yellow, 25 orange, 30 red, 35, dark red, 40- grey.");
756 app.get_formatter()->column_width(40);
757 CLI11_PARSE(app, argc, argv);
764 typedef double Scalar;
769 Polynomial3Reader reader;
770 string::const_iterator iter = reader.read( poly, allParams.polynomials.begin(), allParams.polynomials.end() );
771 if ( iter != allParams.polynomials.end() )
773 trace.
error() <<
"ERROR reading polynomial: I read only <" << allParams.polynomials.substr( 0, iter - allParams.polynomials.begin() )
774 <<
">, and I built P=" << poly << std::endl;
784 typedef ImplicitDigitalShape::Domain
Domain;
785 Scalar min_x = allParams.minAABB;
786 Scalar max_x = allParams.maxAABB;
787 Scalar h = allParams.gridstep;
791 dshape->attach( *shape );
792 dshape->init( p1, p2, h );
793 Domain domain = dshape->getDomain();
795 K.
init( domain.lowerBound(), domain.upperBound(),
true );
796 trace.
info() <<
"- domain is " << domain << std::endl;
799 chooseSurface( allParams, K, *shape, *dshape );
int main(int argc, char **argv)
void green(const unsigned char aGreenValue)
void red(const unsigned char aRedValue)
void blue(const unsigned char aBlueValue)
ConstIterator end() const
DigitalSurfaceContainer::Surfel Surfel
DigitalSurfaceContainer::SurfelConstIterator ConstIterator
ConstIterator begin() const
DigitalSurfaceContainer::KSpace KSpace
void exportAs3DNOFF(std::ostream &out, const SCellEmbedderWithGradientMap &scembedder) const
const DigitalSurfaceContainer & container() const
typename Self::Domain Domain
typename Self::Point Point
bool init(const Point &lower, const Point &upper, bool isClosed)
const Point & upperBound() const
Integer sKCoord(const SCell &c, Dimension k) const
Sign sSign(const SCell &c) const
const Point & lowerBound() const
PointVector< dim, double > RealVector
double unbiasedVariance() const
unsigned int samples() const
void addValue(Quantity v)
void beginBlock(const std::string &keyword="")
void attach(ConstAlias< Shape > aShape)
Space::RealVector RealVector
SpaceND< 2, Integer > Space
Trace trace(traceWriterTerm)
const KSpace & space() const