int main( int argc, char** argv )
{
  // parse command line ----------------------------------------------
  po::options_description general_opt("Allowed options are");
  general_opt.add_options()
    ("help,h", "display this message")
    ("input,i", po::value< std::string >(), ".vol file")
    ("radius,r",  po::value< double >(), "Kernel radius for IntegralInvariant" )
    ("threshold,t",  po::value< unsigned int >()->default_value(8), "Min size of SCell boundary of an object" )
    ("minImageThreshold,l",  po::value<  int >()->default_value(0), "set the minimal image threshold to define the image object (object defined by the voxel with intensity belonging to ]minImageThreshold, maxImageThreshold ] )." )
    ("maxImageThreshold,u",  po::value<  int >()->default_value(255), "set the minimal image threshold to define the image object (object defined by the voxel with intensity belonging to ]minImageThreshold, maxImageThreshold] )." )
    ("mode,m", po::value< std::string >()->default_value("mean"), "type of output : mean, gaussian, k1, k2, prindir1, prindir2 or normal (default mean)")
    ("exportOBJ,o", po::value< std::string >(), "Export the scene to specified OBJ/MTL filename (extensions added)." )
    ("exportDAT,d", po::value<std::string>(), "Export resulting curvature (for mean, gaussian, k1 or k2 mode) in a simple data file each line representing a surfel. ")
    ("exportOnly", "Used to only export the result without the 3d Visualisation (usefull for scripts)." )
    ("imageScale,s", po::value<std::vector<double> >()->multitoken(), "scaleX, scaleY, scaleZ: re sample the source image according with a grid of size 1.0/scale (usefull to compute curvature on image defined on anisotropic grid). Set by default to 1.0 for the three axis.  ")
    ("normalization,n", "When exporting to OBJ, performs a normalization so that the geometry fits in [-1/2,1/2]^3") ;

  bool parseOK = true;
  po::variables_map vm;
  try
    {
      po::store( po::parse_command_line( argc, argv, general_opt ), vm );
    }
  catch( const std::exception & ex )
    {
      parseOK = false;
      trace.error() << " Error checking program options: " << ex.what() << std::endl;
    }
  bool neededArgsGiven=true;

  if (parseOK && !(vm.count("input"))){
    missingParam("--input");
    neededArgsGiven=false;
  }
  if (parseOK && !(vm.count("radius"))){
    missingParam("--radius");
    neededArgsGiven=false;
  }

  bool normalization = false;
  if (parseOK && vm.count("normalization"))
    normalization = true;

  std::string mode;
  if( parseOK )
    mode =  vm["mode"].as< std::string >();
  if ( parseOK && ( mode.compare("gaussian") != 0 ) && ( mode.compare("mean") != 0 ) &&
       ( mode.compare("k1") != 0 ) && ( mode.compare("k2") != 0 ) &&
       ( mode.compare("prindir1") != 0 ) && ( mode.compare("prindir2") != 0 ) && ( mode.compare("normal") != 0 ))
    {
      parseOK = false;
      trace.error() << " The selected mode ("<<mode << ") is not defined."<<std::endl;
    }

#ifndef WITH_VISU3D_QGLVIEWER
  bool enable_visu = false;
#else
  bool enable_visu = !vm.count("exportOnly"); ///<! Default QGLViewer viewer. Disabled if exportOnly is set.
#endif
  bool enable_obj = vm.count("exportOBJ"); ///<! Export to a .obj file.
  bool enable_dat = vm.count("exportDAT"); ///<! Export to a .dat file.

  if( !enable_visu && !enable_obj && !enable_dat )
    {
#ifndef WITH_VISU3D_QGLVIEWER
      trace.error() << "You should specify what you want to export with --export and/or --exportDat." << std::endl;
#else
      trace.error() << "You should specify what you want to export with --export and/or --exportDat, or remove --exportOnly." << std::endl;
#endif
      neededArgsGiven = false;
    }

  if(!neededArgsGiven || !parseOK || vm.count("help") || argc <= 1 )
    {
      trace.info()<< "Visualisation of 3d curvature from .vol file using curvature from Integral Invariant" <<std::endl
                  << general_opt << "\n"
                  << "Basic usage: "<<std::endl
                  << "\t3dCurvatureViewer -i file.vol --radius 5 --mode mean"<<std::endl
                  << std::endl
                  << "Below are the different available modes: " << std::endl
                  << "\t - \"mean\" for the mean curvature" << std::endl
                  << "\t - \"gaussian\" for the Gaussian curvature" << std::endl
                  << "\t - \"k1\" for the first principal curvature" << std::endl
                  << "\t - \"k2\" for the second principal curvature" << std::endl
                  << "\t - \"prindir1\" for the first principal curvature direction" << std::endl
                  << "\t - \"prindir2\" for the second principal curvature direction" << std::endl
                  << "\t - \"normal\" for the normal vector" << std::endl
                  << std::endl;
      return 0;
    }
  unsigned int threshold = vm["threshold"].as< unsigned int >();
  int minImageThreshold =  vm["minImageThreshold"].as<  int >();
  int maxImageThreshold =  vm["maxImageThreshold"].as<  int >();

  double h = 1.0;

  std::string export_obj_filename;
  std::string export_dat_filename;

  if( enable_obj )
    {
      export_obj_filename = vm["exportOBJ"].as< std::string >();
      if( export_obj_filename.find(".obj") == std::string::npos )
        {
          std::ostringstream oss;
          oss << export_obj_filename << ".obj" << std::endl;
          export_obj_filename = oss.str();
        }
    }


  if( enable_dat )
    {
      export_dat_filename = vm["exportDAT"].as<std::string>();
    }

  double re_convolution_kernel = vm["radius"].as< double >();


  std::vector<  double > aGridSizeReSample;
  if( vm.count( "imageScale" ))
    {
      std::vector< double> vectScale = vm["imageScale"].as<std::vector<double > >();
      if( vectScale.size() != 3 )
        {
          trace.error() << "The grid size should contains 3 elements" << std::endl;
          return 0;
        }
      else
        {
          aGridSizeReSample.push_back(1.0/vectScale.at(0));
          aGridSizeReSample.push_back(1.0/vectScale.at(1));
          aGridSizeReSample.push_back(1.0/vectScale.at(2));
        }
    }
  else
    {
      aGridSizeReSample.push_back(1.0);
      aGridSizeReSample.push_back(1.0);
      aGridSizeReSample.push_back(1.0);
    }



  // Construction of the shape from vol file
  typedef Z3i::Space::RealPoint RealPoint;
  typedef Z3i::Point Point;
  typedef ImageSelector< Z3i::Domain,  int>::Type Image;
  typedef DGtal::functors::BasicDomainSubSampler< HyperRectDomain<SpaceND<3, int> >,
                                                  DGtal::int32_t, double >   ReSampler;
  typedef DGtal::ConstImageAdapter<Image, Image::Domain, ReSampler,
                                   Image::Value,  DGtal::functors::Identity >  SamplerImageAdapter;
  typedef IntervalForegroundPredicate< SamplerImageAdapter > ImagePredicate;
  typedef BinaryPointPredicate<DomainPredicate<Image::Domain>, ImagePredicate, AndBoolFct2  > Predicate;
  typedef Z3i::KSpace KSpace;
  typedef KSpace::SCell SCell;
  typedef KSpace::Cell Cell;

  trace.beginBlock("Loading the file");
  std::string filename = vm["input"].as< std::string >();
  Image image = GenericReader<Image>::import( filename );

  PointVector<3,int> shiftVector3D( 0 ,0, 0 );
  DGtal::functors::BasicDomainSubSampler< HyperRectDomain< SpaceND< 3, int > >,
                                          DGtal::int32_t, double > reSampler(image.domain(),
                                                                             aGridSizeReSample,  shiftVector3D);
  const functors::Identity identityFunctor{};
  SamplerImageAdapter sampledImage ( image, reSampler.getSubSampledDomain(), reSampler, identityFunctor );
  ImagePredicate predicateIMG = ImagePredicate( sampledImage,  minImageThreshold, maxImageThreshold );
  DomainPredicate<Z3i::Domain> domainPredicate( sampledImage.domain() );
  AndBoolFct2 andF;
  Predicate predicate(domainPredicate, predicateIMG, andF  );


  Z3i::Domain domain =  sampledImage.domain();
  Z3i::KSpace K;
  bool space_ok = K.init( domain.lowerBound()-Z3i::Domain::Point::diagonal(),
                          domain.upperBound()+Z3i::Domain::Point::diagonal(), true );
  if (!space_ok)
    {
      trace.error() << "Error in the Khalimsky space construction."<<std::endl;
      return 2;
    }
  CanonicSCellEmbedder< KSpace > embedder( K );
  SurfelAdjacency< Z3i::KSpace::dimension > Sadj( true );
  trace.endBlock();
  // Viewer settings


  // Extraction of components
  typedef KSpace::SurfelSet SurfelSet;
  typedef SetOfSurfels< KSpace, SurfelSet > MySetOfSurfels;
  typedef DigitalSurface< MySetOfSurfels > MyDigitalSurface;

  trace.beginBlock("Extracting surfaces");
  std::vector< std::vector<SCell > > vectConnectedSCell;
  Surfaces<KSpace>::extractAllConnectedSCell(vectConnectedSCell,K, Sadj, predicate, false);
  std::ofstream outDat;
  if( enable_dat )
    {
      trace.info() << "Exporting curvature as dat file: "<< export_dat_filename <<std::endl;
      outDat.open( export_dat_filename.c_str() );
      outDat << "# data exported from 3dCurvatureViewer implementing the II curvature estimator (Coeurjolly, D.; Lachaud, J.O; Levallois, J., (2013). Integral based Curvature"
             << "  Estimators in Digital Geometry. DGCI 2013.) " << std::endl;
      outDat << "# format: surfel coordinates (in Khalimsky space) curvature: "<< mode <<  std::endl;
    }

  trace.info()<<"Number of components= "<<vectConnectedSCell.size()<<std::endl;
  trace.endBlock();

  if( vectConnectedSCell.size() == 0 )
    {
      trace.error()<< "No surface component exists. Please check the vol file threshold parameter.";
      trace.info()<<std::endl;
      exit(2);
    }

#ifdef WITH_VISU3D_QGLVIEWER
  QApplication application( argc, argv );
  typedef Viewer3D<Z3i::Space, Z3i::KSpace> Viewer;
#endif
  typedef Board3D<Z3i::Space, Z3i::KSpace> Board;

#ifdef WITH_VISU3D_QGLVIEWER
  Viewer viewer( K );
#endif
  Board board( K );

#ifdef WITH_VISU3D_QGLVIEWER
  if( enable_visu )
    {
      viewer.show();
    }
#endif

  for( unsigned int i = 0; i<vectConnectedSCell.size(); ++i )
    {
      if( vectConnectedSCell[i].size() <= threshold )
        {
          continue;
        }

      MySetOfSurfels  aSet(K, Sadj);

      for( std::vector<SCell>::const_iterator it = vectConnectedSCell.at(i).begin();
           it != vectConnectedSCell.at(i).end();
           ++it )
        {
          aSet.surfelSet().insert( *it);
        }

      MyDigitalSurface digSurf( aSet );


      typedef DepthFirstVisitor<MyDigitalSurface> Visitor;
      typedef GraphVisitorRange< Visitor > VisitorRange;
      typedef VisitorRange::ConstIterator SurfelConstIterator;
      VisitorRange range( new Visitor( digSurf, *digSurf.begin() ) );
      SurfelConstIterator abegin = range.begin();
      SurfelConstIterator aend = range.end();

      VisitorRange range2( new Visitor( digSurf, *digSurf.begin() ) );
      SurfelConstIterator abegin2 = range2.begin();

      trace.beginBlock("Curvature computation on a component");
      if( ( mode.compare("gaussian") == 0 ) || ( mode.compare("mean") == 0 )
          || ( mode.compare("k1") == 0 ) || ( mode.compare("k2") == 0 ))
        {
          typedef double Quantity;
          std::vector< Quantity > results;
          std::back_insert_iterator< std::vector< Quantity > > resultsIterator( results );
          if ( mode.compare("mean") == 0 )
            {
              typedef functors::IIMeanCurvature3DFunctor<Z3i::Space> MyIICurvatureFunctor;
              typedef IntegralInvariantVolumeEstimator<Z3i::KSpace, Predicate, MyIICurvatureFunctor> MyIIEstimator;

              MyIICurvatureFunctor functor;
              functor.init( h, re_convolution_kernel );

              MyIIEstimator estimator( functor );
              estimator.attach( K, predicate );
              estimator.setParams( re_convolution_kernel/h );
              estimator.init( h, abegin, aend );

              estimator.eval( abegin, aend, resultsIterator );
            }
          else if ( mode.compare("gaussian") == 0 )
            {
              typedef functors::IIGaussianCurvature3DFunctor<Z3i::Space> MyIICurvatureFunctor;
              typedef IntegralInvariantCovarianceEstimator<Z3i::KSpace, Predicate, MyIICurvatureFunctor> MyIIEstimator;

              MyIICurvatureFunctor functor;
              functor.init( h, re_convolution_kernel );

              MyIIEstimator estimator( functor ); estimator.attach( K,
                                                                    predicate ); estimator.setParams( re_convolution_kernel/h );
              estimator.init( h, abegin, aend );

              estimator.eval( abegin, aend, resultsIterator );
            }
          else if ( mode.compare("k1") == 0 )
            {
              typedef functors::IIFirstPrincipalCurvature3DFunctor<Z3i::Space> MyIICurvatureFunctor;
              typedef IntegralInvariantCovarianceEstimator<Z3i::KSpace, Predicate, MyIICurvatureFunctor> MyIIEstimator;

              MyIICurvatureFunctor functor;
              functor.init( h, re_convolution_kernel );

              MyIIEstimator estimator( functor );
              estimator.attach( K, predicate );
              estimator.setParams( re_convolution_kernel/h );
              estimator.init( h, abegin, aend );

              estimator.eval( abegin, aend, resultsIterator );
            }
          else if ( mode.compare("k2") == 0 )
            {
              typedef functors::IISecondPrincipalCurvature3DFunctor<Z3i::Space> MyIICurvatureFunctor;
              typedef IntegralInvariantCovarianceEstimator<Z3i::KSpace, Predicate, MyIICurvatureFunctor> MyIIEstimator;

              MyIICurvatureFunctor functor;
              functor.init( h, re_convolution_kernel );

              MyIIEstimator estimator( functor );
              estimator.attach( K, predicate );
              estimator.setParams( re_convolution_kernel/h );
              estimator.init( h, abegin, aend );

              estimator.eval( abegin, aend, resultsIterator );
            }
          trace.endBlock();


          // Drawing results
          trace.beginBlock("Visualisation");
          Quantity min = results[ 0 ];
          Quantity max = results[ 0 ];
          for ( unsigned int i = 1; i < results.size(); ++i )
            {
              if ( results[ i ] < min )
                {
                  min = results[ i ];
                }
              else if ( results[ i ] > max )
                {
                  max = results[ i ];
                }
            }
          trace.info() << "Max value= "<<max<<"  min value= "<<min<<std::endl;
          ASSERT( min <= max );
          typedef GradientColorMap< Quantity > Gradient;
          Gradient cmap_grad( min, (max==min)? max+1: max );
          cmap_grad.addColor( Color( 50, 50, 255 ) );
          cmap_grad.addColor( Color( 255, 0, 0 ) );
          cmap_grad.addColor( Color( 255, 255, 10 ) );

#ifdef WITH_VISU3D_QGLVIEWER
          if( enable_visu )
            {
              viewer << SetMode3D((*abegin2).className(), "Basic" );
            }
#endif
          if( enable_obj )
            {
              board << SetMode3D((K.unsigns(*abegin2)).className(), "Basic" );
            }


          for ( unsigned int i = 0; i < results.size(); ++i )
            {
#ifdef WITH_VISU3D_QGLVIEWER
              if( enable_visu )
                {
                  viewer << CustomColors3D( Color::Black, cmap_grad( results[ i ] ));
                  viewer << *abegin2;
                }
#endif

              if( enable_obj )
                {
                  board << CustomColors3D( Color::Black, cmap_grad( results[ i ] ));
                  board      << K.unsigns(*abegin2);
                }

              if( enable_dat )
                {
                  Point kCoords = K.uKCoords(K.unsigns(*abegin2));
                  outDat << kCoords[0] << " " << kCoords[1] << " " << kCoords[2] <<  " " <<  results[i] << std::endl;
                }

              ++abegin2;
            }
        }
      else
        {
          typedef Z3i::Space::RealVector Quantity;
          std::vector< Quantity > results;
          std::back_insert_iterator< std::vector< Quantity > > resultsIterator( results );

          if( mode.compare("prindir1") == 0 )
            {
              typedef functors::IIFirstPrincipalDirectionFunctor<Z3i::Space> MyIICurvatureFunctor;
              typedef IntegralInvariantCovarianceEstimator<Z3i::KSpace, Predicate, MyIICurvatureFunctor> MyIIEstimator;

              MyIICurvatureFunctor functor;
              functor.init( h, re_convolution_kernel );

              MyIIEstimator estimator( functor );
              estimator.attach( K, predicate );
              estimator.setParams( re_convolution_kernel/h );
              estimator.init( h, abegin, aend );

              estimator.eval( abegin, aend, resultsIterator );
            }
          else if( mode.compare("prindir2") == 0 )
            {
              typedef functors::IISecondPrincipalDirectionFunctor<Z3i::Space> MyIICurvatureFunctor;
              typedef IntegralInvariantCovarianceEstimator<Z3i::KSpace, Predicate, MyIICurvatureFunctor> MyIIEstimator;

              MyIICurvatureFunctor functor;
              functor.init( h, re_convolution_kernel );

              MyIIEstimator estimator( functor );
              estimator.attach( K, predicate );
              estimator.setParams( re_convolution_kernel/h );
              estimator.init( h, abegin, aend );

              estimator.eval( abegin, aend, resultsIterator );
            }
          else
            if( mode.compare("normal") == 0 )
              {
                typedef functors::IINormalDirectionFunctor<Z3i::Space> MyIICurvatureFunctor;
                typedef IntegralInvariantCovarianceEstimator<Z3i::KSpace, Predicate, MyIICurvatureFunctor> MyIIEstimator;

                MyIICurvatureFunctor functor;
                functor.init( h, re_convolution_kernel );

                MyIIEstimator estimator( functor );
                estimator.attach( K, predicate );
                estimator.setParams( re_convolution_kernel/h );
                estimator.init( h, abegin, aend );

                estimator.eval( abegin, aend, resultsIterator );
              }



          ///Visualizaton / export

#ifdef WITH_VISU3D_QGLVIEWER
          if( enable_visu )
            {
              viewer << SetMode3D(K.uCell( K.sKCoords(*abegin2) ).className(), "Basic" );
            }
#endif

          if( enable_obj )
            {
              board << SetMode3D(K.uCell( K.sKCoords(*abegin2) ).className(), "Basic" );
            }

          for ( unsigned int i = 0; i < results.size(); ++i )
            {
              DGtal::Dimension kDim = K.sOrthDir( *abegin2 );
              SCell outer = K.sIndirectIncident( *abegin2, kDim);
              if ( predicate(embedder(outer)) )
                {
                  outer = K.sDirectIncident( *abegin2, kDim);
                }

              Cell unsignedSurfel = K.uCell( K.sKCoords(*abegin2) );

#ifdef WITH_VISU3D_QGLVIEWER
              if( enable_visu )
                {
                  viewer << CustomColors3D( DGtal::Color(255,255,255,255),
                                            DGtal::Color(255,255,255,255))
                         << unsignedSurfel;
                }
#endif

              if( enable_obj )
                {
                  board << CustomColors3D( DGtal::Color(255,255,255,255),
                                           DGtal::Color(255,255,255,255))
                        << unsignedSurfel;
                }

              if( enable_dat )
                {
                  Point kCoords = K.uKCoords(K.unsigns(*abegin2));
                  outDat << kCoords[0] << " " << kCoords[1] << " " << kCoords[2] << " "
                         << results[i][0] << " " << results[i][1] << " " << results[i][2]
                         << std::endl;
                }

              RealPoint center = embedder( outer );

#ifdef WITH_VISU3D_QGLVIEWER
              if( enable_visu )
                {
                  if( mode.compare("prindir1") == 0 )
                    {
                      viewer.setLineColor( AXIS_COLOR_BLUE );
                    }
                  else if( mode.compare("prindir2") == 0 )
                    {
                      viewer.setLineColor( AXIS_COLOR_RED );
                    }
                  else if( mode.compare("normal") == 0 )
                    {
                      viewer.setLineColor( AXIS_COLOR_GREEN );
                    }


                  viewer.addLine (
                                  RealPoint(
                                            center[0] -  0.5 * results[i][0],
                                            center[1] -  0.5 * results[i][1],
                                            center[2] -  0.5 * results[i][2]
                                            ),
                                  RealPoint(
                                            center[0] +  0.5 * results[i][0],
                                            center[1] +  0.5 * results[i][1],
                                            center[2] +  0.5 * results[i][2]
                                            ),
                                  AXIS_LINESIZE );
                }
#endif

              if( enable_obj )
                {
                  if( mode.compare("prindir1") == 0 )
                    {
                      board.setFillColor( AXIS_COLOR_BLUE );
                    }
                  else if( mode.compare("prindir2") == 0 )
                    {
                      board.setFillColor( AXIS_COLOR_RED );
                    }
                  else if( mode.compare("normal") == 0 )
                    {
                      board.setFillColor( AXIS_COLOR_GREEN );
                    }

                  board.addCylinder (
                                     RealPoint(
                                               center[0] -  0.5 * results[i][0],
                                               center[1] -  0.5 * results[i][1],
                                               center[2] -  0.5 * results[i][2]),
                                     RealPoint(
                                               center[0] +  0.5 * results[i][0],
                                               center[1] +  0.5 * results[i][1],
                                               center[2] +  0.5 * results[i][2]),
                                     0.2 );
                }

              ++abegin2;
            }
        }
      trace.endBlock();
    }

#ifdef WITH_VISU3D_QGLVIEWER
  if( enable_visu )
    {
      viewer << Viewer3D<>::updateDisplay;
    }
#endif
  if( enable_obj )
    {
      trace.info()<< "Exporting object: " << export_obj_filename << " ...";
      board.saveOBJ(export_obj_filename,normalization);
      trace.info() << "[done]" << std::endl;
    }
  if( enable_dat )
    {
      outDat.close();
    }

#ifdef WITH_VISU3D_QGLVIEWER
  if( enable_visu )
    {
      return application.exec();
    }
#endif

  return 0;
}
int main()
{
  const double h = 1;
  const double radiusBall = 12.0;
  
  trace.beginBlock( "Make parametric shape..." );
  
  typedef Ball3D< Z3i::Space > Shape;
  
  RealPoint center( 0.0, 0.0, 0.0 );
  Shape ball( center, radiusBall );
  RealPoint centerBis(18.0,0.0,0.0);
  Shape ballBis( centerBis, radiusBall);
  trace.endBlock();
  
  trace.beginBlock( "Make digital shape..." );
  typedef GaussDigitizer< Z3i::Space, Shape > DigitalShape;
  typedef DigitalShape::Domain Domain;
  
  DigitalShape digitalBall;
  digitalBall.attach( ball );
  digitalBall.init( ball.getLowerBound() - Z3i::RealPoint( 1.0, 1.0, 1.0 ),
                   ball.getUpperBound() + Z3i::RealPoint( 1.0, 1.0, 1.0 ),
                   h );
  DigitalShape digitalBallBis;
  digitalBallBis.attach( ballBis );
  digitalBallBis.init( ballBis.getLowerBound() - Z3i::RealPoint( 1.0, 1.0, 1.0 ),
                      ballBis.getUpperBound() + Z3i::RealPoint( 1.0, 1.0, 1.0 ),
                      h );
  
  Domain domain( digitalBall.getDomain().lowerBound().inf( digitalBallBis.getDomain().lowerBound() ),
                 digitalBall.getDomain().upperBound().sup( digitalBallBis.getDomain().upperBound() ));
  
  Z3i::KSpace kspace;
  kspace.init( domain.lowerBound(), domain.upperBound(), true );
  trace.info()<< domain <<std::endl;
  trace.endBlock();
  
  trace.beginBlock( "Make first digital surface..." );
  typedef LightImplicitDigitalSurface< Z3i::KSpace, DigitalShape > LightDigitalSurface;
  typedef DigitalSurface< LightDigitalSurface > DigitalSurface;
  typedef Z3i::KSpace::Surfel Surfel;
  
  Surfel bel = Surfaces< Z3i::KSpace >::findABel( kspace, digitalBall, 500 );
  SurfelAdjacency< Z3i::KSpace::dimension > surfelAdjacency( true );
  LightDigitalSurface lightDigitalSurface( kspace, digitalBall, surfelAdjacency, bel );
  DigitalSurface digitalSurface( lightDigitalSurface );

  bel = Surfaces< Z3i::KSpace >::findABel( kspace, digitalBallBis, 500 );
  LightDigitalSurface lightDigitalSurfaceBis( kspace, digitalBallBis, surfelAdjacency, bel );
  DigitalSurface digitalSurfaceBis( lightDigitalSurfaceBis );

  
  trace.endBlock();

  std::set<Surfel> storage;
  trace.beginBlock("Exporting the first ball");
  std::ofstream handle("output.csv");
  for(auto cell : digitalSurface)
  {
    const Point p = kspace.sKCoords(cell);
    
    if (p[0] < -7.5) continue;
    
    const KSpace::Sign sign = kspace.sSign(cell);
    for (int dim=0; dim<3; dim++)
      handle << p[dim] << " ";
    handle << ( sign == KSpace::POS ) << " ";
    for (int dim=0; dim<3; ++dim)
      handle << (p-center)[dim]<<" ";
    handle << std::endl;
    //std::cout << "exporting "<< cell<<std::endl;
    storage.insert( cell );
  }
  trace.endBlock();
  
  
  trace.beginBlock("Exporting the second ball");
  for(auto cell : digitalSurfaceBis)
  {
  
    if (storage.find(cell) != storage.end())
      continue;
    
    const Point p = kspace.sKCoords(cell);
    const KSpace::Sign sign = kspace.sSign(cell);
    // "2.0*" to map centerbis to Kspace
    const RealPoint normal = (p-2.0*centerBis).getNormalized();
    for (int dim=0; dim<3; dim++)
      handle << p[dim] << " ";
    handle << ( sign == KSpace::POS ) << " ";
    for (int dim=0; dim<3; ++dim)
      handle << normal[dim] <<" ";
    handle << std::endl;
    //std::cout << "exporting "<< cell<<std::endl;
  }
  trace.endBlock();
  
  
  handle.close();
  
  return 0;
}
int main( int argc, char** argv )
{
	const double h = 1;
	const double radiusBall = 12.0;
	const double radiusII = 6;
	const double trueAreaSurface = 4.0*M_PI*radiusBall*radiusBall;

	trace.beginBlock( "Make parametric shape..." );

	typedef Ball3D< Z3i::Space > Shape;

	Z3i::RealPoint center( 0.0, 0.0, 0.0 );
	Shape ball( center, radiusBall );

	trace.endBlock();

	trace.beginBlock( "Make digital shape..." );

	typedef GaussDigitizer< Z3i::Space, Shape > DigitalShape;
	typedef DigitalShape::Domain Domain;

	DigitalShape digitalBall;
	digitalBall.attach( ball );
	digitalBall.init( ball.getLowerBound() - Z3i::RealPoint( 1.0, 1.0, 1.0 ), 
					  ball.getUpperBound() + Z3i::RealPoint( 1.0, 1.0, 1.0 ),
					  h );
	Domain domain = digitalBall.getDomain();
	Z3i::KSpace kspace;
	kspace.init( domain.lowerBound(), domain.upperBound(), true );

	trace.endBlock();

	trace.beginBlock( "Make digital surface..." );

	typedef LightImplicitDigitalSurface< Z3i::KSpace, DigitalShape > LightDigitalSurface;
	typedef DigitalSurface< LightDigitalSurface > DigitalSurface;
	typedef DepthFirstVisitor< DigitalSurface > DepthFirstVisitor;
	typedef GraphVisitorRange< DepthFirstVisitor > GraphVisitorRange;
	typedef GraphVisitorRange::ConstIterator SurfelConstIterator;
	typedef Z3i::KSpace::Surfel Surfel;

	Surfel bel = Surfaces< Z3i::KSpace >::findABel( kspace, digitalBall, 500 );
	SurfelAdjacency< Z3i::KSpace::dimension > surfelAdjacency( true );
	LightDigitalSurface lightDigitalSurface( kspace, digitalBall, surfelAdjacency, bel );
	DigitalSurface digitalSurface( lightDigitalSurface );

	GraphVisitorRange graphVisitorRange( new DepthFirstVisitor( digitalSurface, *digitalSurface.begin() ) );
	SurfelConstIterator sbegin = graphVisitorRange.begin();
    SurfelConstIterator send = graphVisitorRange.end();
    std::vector< Surfel > v_border;
    while( sbegin != send )
    {
    	v_border.push_back( *sbegin );
    	++sbegin;
    }

	trace.endBlock();

	trace.beginBlock( "Computation with normal estimation ..." );

	typedef IIGeometricFunctors::IINormalDirectionFunctor< Z3i::Space > NormalFunctor;
	typedef IntegralInvariantCovarianceEstimator< Z3i::KSpace, DigitalShape, NormalFunctor > IINormalEstimator;

	NormalFunctor normalFunctor;
	IINormalEstimator normalEstimator( normalFunctor );
	normalEstimator.attach( kspace, digitalBall );
	normalEstimator.setParams( radiusII / h );
	normalEstimator.init( h, v_border.begin(), v_border.end() );

	double areaSurfaceEstimated = 0.0;

	for( unsigned int i_position = 0; i_position < v_border.size(); ++i_position )
	{
		Z3i::RealPoint normalEstimated = normalEstimator.eval( &(v_border[i_position]) );
		Z3i::RealPoint normalSurfel = kspace.sKCoords( kspace.sDirectIncident( v_border[i_position], kspace.sOrthDir( v_border[i_position] ))) - kspace.sKCoords( v_border[i_position] ); 
		normalEstimated = normalEstimated.getNormalized();
		areaSurfaceEstimated += std::abs( normalEstimated.dot( normalSurfel )) * h * h;
	}

	trace.endBlock();

	trace.info() << "Area Surface estimated : " << areaSurfaceEstimated << std::endl;
	trace.info() << "True areaSurface : " << trueAreaSurface << std::endl;
	trace.info() << "Ratio : " << areaSurfaceEstimated / trueAreaSurface << std::endl;

	return 0;
}