Exemple #1
0
int main( int argc, char** argv )
{
  QApplication application(argc,argv);

  typedef Z3i::Space Space;
  typedef Z3i::KSpace KSpace;
  typedef Z3i::Point Point;
  typedef Z3i::RealPoint RealPoint;
  typedef Z3i::RealVector RealVector;
  typedef HyperRectDomain<Space> Domain;
  typedef KSpace::Surfel Surfel;
  typedef KSpace::Cell Cell;

  typedef ImageSelector<Domain, unsigned char>::Type Image;
  typedef functors::IntervalForegroundPredicate<Image> ThresholdedImage;
  typedef ImplicitDigitalSurface< KSpace, ThresholdedImage > DigitalSurfaceContainer;

  //! [DVCM3D-typedefs]
  typedef ExactPredicateLpSeparableMetric<Space, 2> Metric;          // L2-metric type
  typedef functors::HatPointFunction<Point,double>  KernelFunction;  // chi function type 
  typedef VoronoiCovarianceMeasureOnDigitalSurface< DigitalSurfaceContainer, Metric,
                                                    KernelFunction > VCMOnSurface;
  typedef VCMOnSurface::Surfel2Normals::const_iterator S2NConstIterator;
  //! [DVCM3D-typedefs]

  string inputFilename = examplesPath + "samples/Al.100.vol";
  trace.info() << "File             = " << inputFilename << std::endl;
  int thresholdMin = 0;
  trace.info() << "Min image thres. = " << thresholdMin << std::endl;
  int thresholdMax = 1;
  trace.info() << "Max image thres. = " << thresholdMax << std::endl;
  const double R = 20;
  trace.info() << "Big radius     R = " << R << std::endl;
  const double r = 3;
  trace.info() << "Small radius   r = " << r << std::endl;
  const double trivial_r = 3;
  trace.info() << "Trivial radius t = " << trivial_r << std::endl; // for orienting the directions given by the tensor.
  const double T = 0.1;
  trace.info() << "Feature thres. T = " << T << std::endl; // threshold for displaying features as red.

  const double size = 1.0; // size of displayed normals.

  KSpace ks;
  // Reads the volume
  trace.beginBlock( "Loading image into memory and build digital surface." );
  Image image = GenericReader<Image>::import(inputFilename );
  ThresholdedImage thresholdedImage( image, thresholdMin, thresholdMax );
  trace.endBlock();
  trace.beginBlock( "Extracting boundary by scanning the space. " );
  ks.init( image.domain().lowerBound(),
                           image.domain().upperBound(), true );
  SurfelAdjacency<KSpace::dimension> surfAdj( true ); // interior in all directions.
  Surfel bel = Surfaces<KSpace>::findABel( ks, thresholdedImage, 10000 );
  DigitalSurfaceContainer* container = 
    new DigitalSurfaceContainer( ks, thresholdedImage, surfAdj, bel, false  );
  DigitalSurface< DigitalSurfaceContainer > surface( container ); //acquired
  trace.info() << "Digital surface has " << surface.size() << " surfels." << std::endl;
  trace.endBlock();

  //! [DVCM3D-instantiation]
  Surfel2PointEmbedding embType = Pointels; // Could be Pointels|InnerSpel|OuterSpel; 
  Metric l2;                                // Euclidean L2 metric 
  KernelFunction chi( 1.0, r );             // hat function with support of radius r
  VCMOnSurface vcm_surface( surface, embType, R, r, 
                            chi, trivial_r, l2, true /* verbose */ );
  //! [DVCM3D-instantiation]

  trace.beginBlock( "Displaying VCM" );
  Viewer3D<> viewer( ks );
  Cell dummy;
  viewer.setWindowTitle("3D VCM viewer");
  viewer << SetMode3D( dummy.className(), "Basic" );
  viewer.show();

  GradientColorMap<double> grad( 0, T );
  grad.addColor( Color( 128, 128, 255 ) );
  grad.addColor( Color( 255, 255, 255 ) );
  grad.addColor( Color( 255, 255, 0 ) );
  grad.addColor( Color( 255, 0, 0 ) );
  RealVector lambda; // eigenvalues of chi-vcm
  for ( S2NConstIterator it = vcm_surface.mapSurfel2Normals().begin(), 
          itE = vcm_surface.mapSurfel2Normals().end(); it != itE; ++it )
    {
      Surfel s = it->first;
      Point kp = ks.sKCoords( s );
      RealPoint rp( 0.5 * (double) kp[ 0 ], 0.5 * (double) kp[ 1 ], 0.5 * (double) kp[ 2 ] );
      RealVector n = it->second.vcmNormal;
      vcm_surface.getChiVCMEigenvalues( lambda, s );
      double ratio = lambda[ 1 ] / ( lambda[ 0 ] + lambda[ 1 ] + lambda[ 2 ] ); 
      viewer.setFillColor( grad( ratio > T ? T : ratio ) );
      viewer << ks.unsigns( s );
      n *= size;
      viewer.setLineColor( Color::Black );
      viewer.addLine( rp + n, rp - n, 0.1 );
     }
  viewer << Viewer3D<>::updateDisplay;
  application.exec();
  trace.endBlock();
  return 0;
}
bool testUmbrellaComputer()
{
  using namespace Z3i;
  
  typedef Space::RealPoint RealPoint;
  typedef ImplicitBall<Space> EuclideanShape;
  typedef GaussDigitizer<Space,EuclideanShape> DigitalShape; 
  typedef GaussDigitizer<Space,EuclideanShape>::Domain Domain;
  typedef LightImplicitDigitalSurface<KSpace,DigitalShape> Boundary;
  typedef Boundary::SurfelConstIterator ConstIterator;
  //typedef Boundary::Tracker Tracker;
  typedef Boundary::Surfel Surfel;
  typedef Boundary::DigitalSurfaceTracker DigitalSurfaceTracker;
  typedef DigitalSurface<Boundary> MyDigitalSurface;
  typedef UmbrellaComputer<DigitalSurfaceTracker> MyUmbrellaComputer;

  unsigned int nbok = 0;
  unsigned int nb = 0;
  trace.beginBlock ( "Testing block ... UmbrellaComputer" );
  // Creating shape
  Point c( 0, 0, 0 );
  EuclideanShape ball( c, 2 ); // ball r=4
  DigitalShape shape;
  shape.attach( ball );
  shape.init( RealPoint( -10.0, -10.0, -10.0 ), 
	      RealPoint( 10.0, 10.0, 10.0 ), 1.0 );
  // Creating cellular grid space around.
  Domain domain = shape.getDomain();
  KSpace K;
  nbok += K.init( domain.lowerBound(), domain.upperBound(), true ) ? 1 : 0; 
  nb++;
  trace.info() << "(" << nbok << "/" << nb << ") "
	       << "K.init() is ok" << std::endl;
  // Find start surfel on surface.
  Surfel bel = Surfaces<KSpace>::findABel( K, shape, 10000 );
  // Define surface container then surface itself.
  Boundary boundary( K, // cellular space
		     shape, // point predicate
                     SurfelAdjacency<KSpace::dimension>( true ), // adjacency
		     bel // starting surfel
		     );
  MyDigitalSurface digSurf( boundary ); // boundary is cloned

  // Get tracker on surface.
  DigitalSurfaceTracker* ptrTracker = boundary.newTracker( bel );
  MyUmbrellaComputer umbrella;
  KSpace::DirIterator dirIt = K.sDirs( bel );
  Dimension k = *dirIt;
  Dimension j = *(++dirIt);
  trace.beginBlock ( "Testing block ... forward umbrella" );
  umbrella.init( *ptrTracker, k, true, j );
  unsigned int nb_forward = 0;
  Surfel init_bel = bel;
  do {
    Point x = K.sKCoords( bel );
    trace.info() << x << std::endl;
    umbrella.next();
    ++nb_forward;
    bel = umbrella.surfel();
  } while ( bel != init_bel );
  trace.endBlock();
  trace.beginBlock ( "Testing block ... backward umbrella" );
  unsigned int nb_backward = 0;
  do {
    Point x = K.sKCoords( bel );
    trace.info() << x << std::endl;
    umbrella.previous();
    ++nb_backward;
    bel = umbrella.surfel();
  } while ( bel != init_bel );
  nb++, nbok += nb_forward == nb_backward ? 1 : 0;
  
  trace.info() << "(" << nbok << "/" << nb << ") "
               << " nb_forward(" << nb_forward
	       << ") == nb_backward(" << nb_backward << ")"
	       << std::endl;
  trace.endBlock();
  unsigned int nbsurfels = 0;
  for ( ConstIterator it = boundary.begin(), it_end = boundary.end();
        it != it_end; ++it )
    {
      ++nbsurfels;
    }
  trace.info() << nbsurfels << " surfels found." << std::endl;

  trace.endBlock();

  delete ptrTracker;
  return nbok == nb;
}
Exemple #3
0
int main( int argc, char** argv )
{
  QApplication application(argc,argv);
  string inputFilename = argc > 1 ? argv[ 1 ] : examplesPath+"/samples/Al.100.vol";
  int threshold = argc > 2 ? atoi( argv[ 2 ] ) : 0;
  int widthNum = argc > 3 ? atoi( argv[ 3 ] ) : 2;
  int widthDen = argc > 4 ? atoi( argv[ 4 ] ) : 1;

  //! [polyhedralizer-readVol]
  trace.beginBlock( "Reading vol file into an image." );
  typedef ImageContainerBySTLVector< Domain, int> Image;
  Image image = VolReader<Image>::importVol(inputFilename);
  typedef functors::SimpleThresholdForegroundPredicate<Image> DigitalObject;
  DigitalObject digitalObject( image, threshold );
  trace.endBlock();
  //! [polyhedralizer-readVol]

  //! [polyhedralizer-KSpace]
  trace.beginBlock( "Construct the Khalimsky space from the image domain." );
  KSpace ks;
  bool space_ok = ks.init( image.domain().lowerBound(), image.domain().upperBound(), true );
  if (!space_ok)
    {
      trace.error() << "Error in the Khamisky space construction."<<endl;
      return 2;
    }
  trace.endBlock();
  //! [polyhedralizer-KSpace]

  //! [polyhedralizer-SurfelAdjacency]
  typedef SurfelAdjacency<KSpace::dimension> MySurfelAdjacency;
  MySurfelAdjacency surfAdj( false ); // exterior in all directions.
  //! [polyhedralizer-SurfelAdjacency]

  //! [polyhedralizer-ExtractingSurface]
  trace.beginBlock( "Extracting boundary by tracking the surface. " );
  typedef KSpace::Surfel Surfel;
  Surfel start_surfel = Surfaces<KSpace>::findABel( ks, digitalObject, 100000 );
  typedef ImplicitDigitalSurface< KSpace, DigitalObject > MyContainer;
  typedef DigitalSurface< MyContainer > MyDigitalSurface;
  typedef MyDigitalSurface::ConstIterator ConstIterator;
  MyContainer container( ks, digitalObject, surfAdj, start_surfel );
  MyDigitalSurface digSurf( container );
  trace.info() << "Digital surface has " << digSurf.size() << " surfels."
               << endl;
  trace.endBlock();
  //! [polyhedralizer-ExtractingSurface]

  //! [polyhedralizer-ComputingPlaneSize]
  // First pass to find biggest planes.
  trace.beginBlock( "Decomposition first pass. Computes all planes so as to sort vertices by the plane size." );
  typedef BreadthFirstVisitor<MyDigitalSurface> Visitor;
  typedef ChordGenericNaivePlaneComputer<Z3,Z3::Point, DGtal::int64_t> NaivePlaneComputer;
  map<Surfel,unsigned int> v2size;
  for ( ConstIterator it = digSurf.begin(), itE= digSurf.end(); it != itE; ++it )
    v2size[ *it ] = 0;
  int j = 0;
  int nb = digSurf.size();
  NaivePlaneComputer planeComputer;
  vector<Point> layer;
  vector<Surfel> layer_surfel;
  for ( ConstIterator it = digSurf.begin(), itE= digSurf.end(); it != itE; ++it )
    {
      if ( ( (++j) % 50 == 0 ) || ( j == nb ) ) trace.progressBar( j, nb );
      Surfel v = *it;
      planeComputer.init( widthNum, widthDen );
      // The visitor takes care of all the breadth-first traversal.
      Visitor visitor( digSurf, v );
      layer.clear();
      layer_surfel.clear();
      Visitor::Size currentSize = visitor.current().second;
      while ( ! visitor.finished() )
        {
          Visitor::Node node = visitor.current();
          v = node.first;
          int axis = ks.sOrthDir( v );
          Point p = ks.sCoords( ks.sDirectIncident( v, axis ) );
          if ( node.second != currentSize )
            {
              bool isExtended = planeComputer.extend( layer.begin(), layer.end() );
              if ( isExtended )
                {
                  for ( vector<Surfel>::const_iterator it_layer = layer_surfel.begin(),
                          it_layer_end = layer_surfel.end(); it_layer != it_layer_end; ++it_layer )
                    {
                      ++v2size[ *it_layer ];
                    }
                  layer_surfel.clear();
                  layer.clear();
                  currentSize = node.second;
                }
              else
                break;
            }
          layer_surfel.push_back( v );
          layer.push_back( p );
          visitor.expand();
        }
    }
  // Prepare queue
  typedef PairSorted2nd<Surfel,int> SurfelWeight;
  priority_queue<SurfelWeight> Q;
  for ( ConstIterator it = digSurf.begin(), itE= digSurf.end(); it != itE; ++it )
    Q.push( SurfelWeight( *it, v2size[ *it ] ) );
  trace.endBlock();
  //! [polyhedralizer-ComputingPlaneSize]

  //! [polyhedralizer-segment]
  // Segmentation into planes
  trace.beginBlock( "Decomposition second pass. Visits vertices from the one with biggest plane to the one with smallest plane." );
  typedef Triple<NaivePlaneComputer, Color, pair<RealVector,double> > RoundPlane;
  set<Surfel> processedVertices;
  vector<RoundPlane*> roundPlanes;
  map<Surfel,RoundPlane*> v2plane;
  j = 0;
  while ( ! Q.empty() )
    {
      if ( ( (++j) % 50 == 0 ) || ( j == nb ) ) trace.progressBar( j, nb );
      Surfel v = Q.top().first;
      Q.pop();
      if ( processedVertices.find( v ) != processedVertices.end() ) // already in set
        continue; // process to next vertex

      RoundPlane* ptrRoundPlane = new RoundPlane;
      roundPlanes.push_back( ptrRoundPlane ); // to delete them afterwards.
      v2plane[ v ] = ptrRoundPlane;
      ptrRoundPlane->first.init( widthNum, widthDen );
      ptrRoundPlane->third = make_pair( RealVector::zero, 0.0 );
      // The visitor takes care of all the breadth-first traversal.
      Visitor visitor( digSurf, v );
      layer.clear();
      layer_surfel.clear();
      Visitor::Size currentSize = visitor.current().second;
      while ( ! visitor.finished() )
        {
          Visitor::Node node = visitor.current();
          v = node.first;
          Dimension axis = ks.sOrthDir( v );
          Point p = ks.sCoords( ks.sDirectIncident( v, axis ) );
          if ( node.second != currentSize )
            {
              bool isExtended = ptrRoundPlane->first.extend( layer.begin(), layer.end() );
              if ( isExtended )
                {
                  for ( vector<Surfel>::const_iterator it_layer = layer_surfel.begin(),
                          it_layer_end = layer_surfel.end(); it_layer != it_layer_end; ++it_layer )
                    {
                      Surfel s = *it_layer;
                      processedVertices.insert( s );
                      if ( v2plane.find( s ) == v2plane.end() )
                        v2plane[ s ] = ptrRoundPlane;
                    }
                  layer.clear();
                  layer_surfel.clear();
                  currentSize = node.second;
                }
              else break;
            }
          layer_surfel.push_back( v );
          layer.push_back( p );
          if ( processedVertices.find( v ) != processedVertices.end() )
            // surfel is already in some plane.
            visitor.ignore();
          else
            visitor.expand();
        }
      if ( visitor.finished() )
        {
          for ( vector<Surfel>::const_iterator it_layer = layer_surfel.begin(),
                  it_layer_end = layer_surfel.end(); it_layer != it_layer_end; ++it_layer )
            {
              Surfel s = *it_layer;
              processedVertices.insert( s );
              if ( v2plane.find( s ) == v2plane.end() )
                v2plane[ s ] = ptrRoundPlane;
            }
        }
      // Assign random color for each plane.
      ptrRoundPlane->second = Color( rand() % 192 + 64, rand() % 192 + 64, rand() % 192 + 64, 255 );
    }
  trace.endBlock();
  //! [polyhedralizer-segment]

  //! [polyhedralizer-lsf]
  for ( vector<RoundPlane*>::iterator
          it = roundPlanes.begin(), itE = roundPlanes.end();
        it != itE; ++it )
    {
      NaivePlaneComputer& computer = (*it)->first;
      RealVector normal;
      double mu = LSF( normal, computer.begin(), computer.end() );
      (*it)->third = make_pair( normal, mu );
    }
  //! [polyhedralizer-lsf]

  //! [polyhedralizer-projection]
  map<Surfel, RealPoint> coordinates;
  for ( map<Surfel,RoundPlane*>::const_iterator
          it = v2plane.begin(), itE = v2plane.end();
        it != itE; ++it )
    {
      Surfel v = it->first;
      RoundPlane* rplane = it->second;
      Point p = ks.sKCoords( v );
      RealPoint rp( (double)p[ 0 ]/2.0, (double)p[ 1 ]/2.0, (double)p[ 2 ]/2.0 );
      double mu = rplane->third.second;
      RealVector normal = rplane->third.first;
      double lambda = mu - rp.dot( normal );
      coordinates[ v ] = rp + lambda*normal;
    }
  typedef vector<Surfel> SurfelRange;
  map<Surfel, RealPoint> new_coordinates;
  for ( ConstIterator it = digSurf.begin(), itE= digSurf.end(); it != itE; ++it )
    {
      Surfel s = *it;
      SurfelRange neighbors;
      back_insert_iterator<SurfelRange> writeIt = back_inserter( neighbors );
      digSurf.writeNeighbors( writeIt, *it );
      RealPoint x = RealPoint::zero;
      for ( SurfelRange::const_iterator its = neighbors.begin(), itsE = neighbors.end();
            its != itsE; ++its )
        x += coordinates[ *its ];
      new_coordinates[ s ] = x / neighbors.size();
    }
  //! [polyhedralizer-projection]

  //! [polyhedralizer-MakeMesh]
  typedef unsigned int Number;
  typedef Mesh<RealPoint> MyMesh;
  typedef MyMesh::MeshFace MeshFace;
  typedef MyDigitalSurface::FaceSet FaceSet;
  typedef MyDigitalSurface::VertexRange VertexRange;
  map<Surfel, Number> index;   // Numbers all vertices.
  Number nbv = 0;
  MyMesh polyhedron( true );
  // Insert all projected surfels as vertices of the polyhedral surface.
  for ( ConstIterator it = digSurf.begin(), itE= digSurf.end(); it != itE; ++it )
    {
      polyhedron.addVertex( new_coordinates[ *it ] );
      index[ *it ] = nbv++;
    }
  // Define faces of the mesh. Outputs closed faces.
  FaceSet faces = digSurf.allClosedFaces();
  for ( typename FaceSet::const_iterator itf = faces.begin(), itf_end = faces.end();
        itf != itf_end; ++itf )
    {
      MeshFace mface( itf->nbVertices );
      VertexRange vtcs = digSurf.verticesAroundFace( *itf );
      int i = 0;
      for ( typename VertexRange::const_iterator itv = vtcs.begin(), itv_end = vtcs.end();
            itv != itv_end; ++itv )
        {
          mface[ i++ ] = index[ *itv ];
        }
      polyhedron.addFace( mface, Color( 255, 243, 150, 255 ) );
    }
  //! [polyhedralizer-MakeMesh]

  //! [polyhedralizer-visualization]
  typedef Viewer3D<Space,KSpace> MyViewer3D;
  MyViewer3D viewer( ks );
  viewer.show();
  bool isOK = polyhedron >> "test.off";
  bool isOK2 = polyhedron >> "test.obj";
  viewer << polyhedron;
  viewer << MyViewer3D::updateDisplay;
  application.exec();
  //! [polyhedralizer-visualization]

  //! [polyhedralizer-freeMemory]
  for ( vector<RoundPlane*>::iterator
          it = roundPlanes.begin(), itE = roundPlanes.end();
        it != itE; ++it )
    delete *it;
  //! [polyhedralizer-freeMemory]

  if (isOK && isOK2)
    return 0;
  else
    return 1;
}
bool testCombinatorialSurface()
{
  using namespace Z3i;
  
  typedef Space::RealPoint RealPoint;
  typedef ImplicitBall<Space> EuclideanShape;
  typedef GaussDigitizer<Space,EuclideanShape> DigitalShape; 
  typedef GaussDigitizer<Space,EuclideanShape>::Domain Domain;
  typedef LightImplicitDigitalSurface<KSpace,DigitalShape> Boundary;
  typedef Boundary::SurfelConstIterator ConstIterator;
  //typedef Boundary::Tracker Tracker;
  typedef Boundary::Surfel Surfel;
  //typedef Boundary::DigitalSurfaceTracker DigitalSurfaceTracker;
  typedef DigitalSurface<Boundary> MyDigitalSurface;
  //typedef UmbrellaComputer<DigitalSurfaceTracker> MyUmbrellaComputer;
  typedef DigitalSurface<Boundary>::Face Face;
  typedef DigitalSurface<Boundary>::Arc Arc;
  typedef DigitalSurface<Boundary>::Vertex Vertex;

  unsigned int nbok = 0;
  unsigned int nb = 0;
  trace.beginBlock ( "Testing block ... Combinatorial surface" );
  // Creating shape
  Point c( 0, 0, 0 );
  EuclideanShape ball( c, 8 ); // ball r=4
  DigitalShape shape;
  shape.attach( ball );
  shape.init( RealPoint( -2.0, -3.0, -10.0 ), 
	      RealPoint( 10.0, 10.0, 10.0 ), 1.0 );
  // Creating cellular grid space around.
  Domain domain = shape.getDomain();
  KSpace K;
  nbok += K.init( domain.lowerBound(), domain.upperBound(), true ) ? 1 : 0; 
  nb++;
  trace.info() << "(" << nbok << "/" << nb << ") "
	       << "K.init() is ok" << std::endl;
  // Find start surfel on surface.
  Surfel bel = Surfaces<KSpace>::findABel( K, shape, 10000 );
  // Define surface container then surface itself.
  Boundary boundary( K, // cellular space
		     shape, // point predicate
                     SurfelAdjacency<KSpace::dimension>( true ), // adjacency
		     bel // starting surfel
		     );
  MyDigitalSurface digSurf( boundary ); // boundary is cloned


  trace.beginBlock ( "Testing block ... Digital surface faces." );
  MyDigitalSurface::FaceSet all_faces = digSurf.allFaces();
  for ( MyDigitalSurface::FaceSet::const_iterator it = all_faces.begin(),
          it_end = all_faces.end(); it != it_end; ++it )
    {
      std::cerr << "    face=" << K.sKCoords( digSurf.pivot( *it ) ) << ":";
      std::cerr << "(" << it->nbVertices << ")" << (it->isClosed() ? "C": "O");
      MyDigitalSurface::VertexRange vtx = digSurf.verticesAroundFace( *it );
      for ( unsigned int i = 0; i < vtx.size(); ++i )
        {
          std::cerr << " " << K.sKCoords( vtx[ i ] );
        }
      std::cerr << std::endl;
    }
  trace.endBlock();

  // Checks that vertices of a face are in the same order as the
  // incident arcs.
  trace.beginBlock( "Testing block ...Check order faces/arcs" );
  unsigned int nbvtcs = 0;
  unsigned int nbarcs = 0;
  unsigned int nbfaces = 0;
  for ( ConstIterator it = boundary.begin(), it_end = boundary.end();
        it != it_end; ++it, ++nbvtcs )
    {
      const Vertex & vtx = *it;
      MyDigitalSurface::ArcRange arcs = digSurf.outArcs( vtx );
      for ( unsigned int i = 0; i < arcs.size(); ++i, ++nbarcs )
        {
          const Arc & arc = arcs[ i ];
          MyDigitalSurface::FaceRange 
            faces = digSurf.facesAroundArc( arc );
          for ( unsigned int j = 0; j < faces.size(); ++j, ++nbfaces )
            {
              const Face & face = faces[ j ];
              // search vertex in face.
              MyDigitalSurface::VertexRange
                vertices = digSurf.verticesAroundFace( face );
              unsigned int k = 0; 
              while ( ( k < vertices.size() ) && ( vertices[ k ] != vtx ) )
                ++k;
              ++nb;
              if ( k == vertices.size() ) 
                trace.info() << "Error at vertex " << vtx
                             << ". Vertex not found in incident face."
                             << std::endl;
              else ++nbok;
              ++nb;
              if ( digSurf.head( arc ) != vertices[ (k+1) % vertices.size() ] )
                trace.info() << "Error at vertex " << vtx
                             << ". Arc is not in incident face."
                             << std::endl;
              else ++nbok;
            }
        }
    }
  trace.info() << "(" << nbok << "/" << nb << ") "
	       << "Tested nbvtcs=" << nbvtcs
	       << " nbarcs=" << nbarcs
	       << " nbfaces=" << nbfaces
               << std::endl;
  trace.endBlock();

  trace.beginBlock( "Testing block ... export as OFF: ex-digital-surface.off" );
  ofstream fout( "ex-digital-surface.off" );
  if ( fout.good() )
    digSurf.exportSurfaceAs3DOFF( fout );
  fout.close();
  trace.endBlock();



  unsigned int nbsurfels = 0;
  for ( ConstIterator it = boundary.begin(), it_end = boundary.end();
        it != it_end; ++it )
    {
      ++nbsurfels;
    }
  trace.info() << nbsurfels << " surfels found." << std::endl;
  trace.endBlock();

  return nbok == nb;
}