Computation of principal curvatures and directions on a mesh defined by a an implicit shape discretized as a digital surface, using constant or interpolated corrected curvature measures (based on the theory of corrected normal currents). It uses a digital normal vector estimator to improve curvature estimations. Errors with respect to true expected curvatures are also computed.
This first example uses constant per face corrected normal vector field to compute curvatures.
This second example uses vertex-interpolated corrected normal vector field to compute curvatures.
#include <iostream>
#include <fstream>
#include <algorithm>
#include "DGtal/base/Common.h"
#include "DGtal/shapes/SurfaceMesh.h"
#include "DGtal/geometry/meshes/CorrectedNormalCurrentComputer.h"
#include "DGtal/helpers/Shortcuts.h"
#include "DGtal/helpers/ShortcutsGeometry.h"
#include "DGtal/io/writers/SurfaceMeshWriter.h"
#include "DGtal/io/colormaps/GradientColorMap.h"
#include "DGtal/io/colormaps/QuantifiedColorMap.h"
{
return gradcmap;
}
void usage( int argc, char* argv[] )
{
std::cout << "Usage: " << std::endl
<< "\t" << argv[ 0 ] << " <P> <B> <h> <R> <mode>" << std::endl
<< std::endl
<< "Computation of principal curvatures and directions on" << std::endl
<< "a digitized implicit shape using constant or " << std::endl
<< "interpolated corrected curvature measures (based " << std::endl
<< "on the theory of corrected normal currents)." << std::endl
<< "- builds the surface mesh from polynomial <P>" << std::endl
<< "- <B> defines the digitization space size [-B,B]^3" << std::endl
<< "- <h> is the gridstep digitization" << std::endl
<< "- <R> is the radius of the measuring balls" << std::endl
<< "- <mode> is either Const for constant corrected normal" << std::endl
<< " vector field or Interp for interpolated corrected" << std::endl
<< " normal vector field." << std::endl
<< "It produces several OBJ files to display principal " << std::endl
<< "curvatures and directions estimations: `example-cnc-K1.obj`" << std::endl
<< "`example-cnc-K2.obj`, `example-cnc-D1.obj`, and" << std::endl
<< "`example-cnc-D2.obj` as well as associated MTL files." << std::endl;
std::cout << "You may either write your own polynomial as 3*x^2*y-z^2*x*y+1" << std::endl
<<"or use a predefined polynomial in the following list:" << std::endl;
auto L = SH::getPolynomialList();
for ( const auto& p : L )
std::cout << p.first << " : " << p.second << std::endl;
}
int main(
int argc,
char* argv[] )
{
if ( argc <= 1 )
{
usage( argc, argv );
return 0;
}
std::string poly = argv[ 1 ];
const double B = argc > 2 ? atof( argv[ 2 ] ) : 1.0;
const double h = argc > 3 ? atof( argv[ 3 ] ) : 1.0;
const double R = argc > 4 ? atof( argv[ 4 ] ) : 2.0;
std::string mode = argc > 5 ? argv[ 5 ] : "Const";
bool interpolated = mode == "Interp";
if ( interpolated )
std::cout << "Using vertex-*Interpolated* Corrected Normal Current" << std::endl;
else
std::cout << "Using face-*Constant* Corrected Normal Current" << std::endl;
auto params = SH::defaultParameters() | SHG::defaultParameters();
params( "t-ring", 3 )( "surfaceTraversal", "Default" );
params( "polynomial", poly )( "gridstep", h );
params( "minAABB", -B )( "maxAABB", B );
params( "offset", 3.0 );
auto shape = SH::makeImplicitShape3D( params );
auto K = SH::getKSpace( params );
auto dshape = SH::makeDigitizedImplicitShape3D( shape, params );
auto bimage = SH::makeBinaryImage( dshape, params );
if ( bimage == nullptr )
{
<< poly.c_str() << ">" << std::endl;
return 1;
}
auto sembedder = SH::getSCellEmbedder(
K );
auto embedder = SH::getCellEmbedder(
K );
auto surface = SH::makeDigitalSurface( bimage,
K, params );
auto surfels = SH::getSurfelRange( surface, params );
trace.
info() <<
"- surface has " << surfels.size()<<
" surfels." << std::endl;
SM smesh;
std::vector< SM::Vertices > faces;
SH::Cell2Index c2i;
auto pointels = SH::getPointelRange( c2i, surface );
auto vertices = SH::RealPoints( pointels.size() );
std::transform( pointels.cbegin(), pointels.cend(),
vertices.begin(),
[&] (const SH::Cell& c) { return h * embedder( c ); } );
for ( auto&& surfel : *surface )
{
const auto primal_surfel_vtcs = SH::getPointelRange(
K, surfel );
SM::Vertices face;
for ( auto&& primal_vtx : primal_surfel_vtcs )
face.push_back( c2i[ primal_vtx ] );
faces.push_back( face );
}
faces.cbegin(), faces.cend() );
auto exp_K1 = SHG::getFirstPrincipalCurvatures ( shape,
K, surfels, params );
auto exp_K2 = SHG::getSecondPrincipalCurvatures( shape,
K, surfels, params );
auto exp_D1 = SHG::getFirstPrincipalDirections ( shape,
K, surfels, params );
auto exp_D2 = SHG::getSecondPrincipalDirections( shape,
K, surfels, params );
CNC cnc( smesh );
auto face_normals = SHG::getCTrivialNormalVectors( surface, surfels, params );
smesh.setFaceNormals( face_normals.cbegin(), face_normals.cend() );
if ( interpolated ) smesh.computeVertexNormalsFromFaceNormals();
auto mu0 = cnc.computeMu0();
auto muXY = cnc.computeMuXY();
std::vector< double > K1( smesh.nbFaces() );
std::vector< double >
K2( smesh.nbFaces() );
std::vector< RealVector > D1( smesh.nbFaces() );
std::vector< RealVector > D2( smesh.nbFaces() );
for ( auto f = 0; f < smesh.nbFaces(); ++f )
{
const auto b = smesh.faceCentroid( f );
const auto N = smesh.faceNormals()[ f ];
const auto area = mu0 .measure( b, R, f );
const auto M = muXY.measure( b, R, f );
std::tie( K1[ f ], K2[ f ], D1[ f ], D2[ f ] )
= cnc.principalCurvatures( area, M, N );
}
auto exp_K1_min_max = std::minmax_element( exp_K1.cbegin(), exp_K1.cend() );
auto exp_K2_min_max = std::minmax_element( exp_K2.cbegin(), exp_K2.cend() );
auto K1_min_max = std::minmax_element( K1.cbegin(), K1.cend() );
auto K2_min_max = std::minmax_element(
K2.cbegin(),
K2.cend() );
std::cout << "Expected K1 curvatures:"
<< " min=" << *exp_K1_min_max.first << " max=" << *exp_K1_min_max.second
<< std::endl;
std::cout << "Computed k1 curvatures:"
<< " min=" << *K1_min_max.first << " max=" << *K1_min_max.second
<< std::endl;
std::cout << "Expected k2 curvatures:"
<< " min=" << *exp_K2_min_max.first << " max=" << *exp_K2_min_max.second
<< std::endl;
std::cout << "Computed k2 curvatures:"
<< " min=" << *K2_min_max.first << " max=" << *K2_min_max.second
<< std::endl;
const auto error_K1 = SHG::getScalarsAbsoluteDifference( K1, exp_K1 );
const auto stat_error_K1 = SHG::getStatistic( error_K1 );
const auto error_K1_l2 = SHG::getScalarsNormL2( K1, exp_K1 );
trace.
info() <<
"|K1-K1_CNC|_oo = " << stat_error_K1.max() << std::endl;
trace.
info() <<
"|K1-K1_CNC|_2 = " << error_K1_l2 << std::endl;
const auto error_K2 = SHG::getScalarsAbsoluteDifference( K2, exp_K2 );
const auto stat_error_K2 = SHG::getStatistic( error_K2 );
const auto error_K2_l2 = SHG::getScalarsNormL2( K2, exp_K2 );
trace.
info() <<
"|K2-K2_CNC|_oo = " << stat_error_K2.max() << std::endl;
trace.
info() <<
"|K2-K2_CNC|_2 = " << error_K2_l2 << std::endl;
smesh.vertexNormals() = SH::RealVectors();
smesh.faceNormals() = SH::RealVectors();
const double Kmax = std::max( fabs( *exp_K1_min_max.first ),
fabs( *exp_K2_min_max.second ) );
auto colorsK1 = SMW::Colors( smesh.nbFaces() );
auto colorsK2 = SMW::Colors( smesh.nbFaces() );
for ( auto i = 0; i < smesh.nbFaces(); i++ )
{
colorsK1[ i ] = colormapK1( K1[ i ] );
colorsK2[ i ] = colormapK2( K2[ i ] );
}
SMW::writeOBJ( "example-cnc-K1", smesh, colorsK1 );
SMW::writeOBJ( "example-cnc-K2", smesh, colorsK2 );
const auto avg_e = smesh.averageEdgeLength();
SH::RealPoints positions( smesh.nbFaces() );
for ( auto f = 0; f < positions.size(); ++f )
{
D1[ f ] *= smesh.localWindow( f );
positions[ f ] = smesh.faceCentroid( f ) - 0.5 * D1[ f ];
}
SH::saveVectorFieldOBJ( positions, D1, 0.05 * avg_e, SH::Colors(),
"example-cnc-D1",
SH::Color::Black, SH::Color( 0, 128, 0 ) );
for ( auto f = 0; f < positions.size(); ++f )
{
D2[ f ] *= smesh.localWindow( f );
positions[ f ] = smesh.faceCentroid( f ) - 0.5 * D2[ f ];
}
SH::saveVectorFieldOBJ( positions, D2, 0.05 * avg_e, SH::Colors(),
"example-cnc-D2",
SH::Color::Black, SH::Color(128, 0,128 ) );
return 0;
}
Structure representing an RGB triple with alpha component.
Aim: This class template may be used to (linearly) convert scalar values in a given range into a colo...
void addColor(const Color &color)
Aim: This class is used to simplify shape and surface creation. With it, you can create new shapes an...
Aim: This class is used to simplify shape and surface creation. With it, you can create new shapes an...
DGtal::GradientColorMap< double > makeColorMap(double min_value, double max_value)
[curvature-comparator-Includes]
Z3i this namespace gathers the standard of types for 3D imagery.
DGtal is the top-level namespace which contains all DGtal functions and types.
QuantifiedColorMap< TColorMap > makeQuantifiedColorMap(TColorMap colormap, int nb=50)
std::pair< typename graph_traits< DGtal::DigitalSurface< TDigitalSurfaceContainer > >::vertex_iterator, typename graph_traits< DGtal::DigitalSurface< TDigitalSurfaceContainer > >::vertex_iterator > vertices(const DGtal::DigitalSurface< TDigitalSurfaceContainer > &digSurf)
Aim: Utility class to compute curvature measures induced by (1) a corrected normal current defined by...
Aim: An helper class for writing mesh file formats (Waverfront OBJ at this point) and creating a Surf...
Aim: Represents an embedded mesh as faces and a list of vertices. Vertices may be shared among faces ...