/*
 * Software License Agreement (BSD License)
 *
 *  Point Cloud Library (PCL) - www.pointclouds.org
 *  Copyright (c) 2010-2012, Willow Garage, Inc.
 *
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the copyright holder(s) nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id$
 *
 */
#include <gtest/gtest.h>
#include <pcl/io/pcd_io.h>
#include "boost.h"
#include <pcl/sample_consensus/sac.h>
#include <pcl/sample_consensus/lmeds.h>
#include <pcl/sample_consensus/ransac.h>
#include <pcl/sample_consensus/rransac.h>
#include <pcl/sample_consensus/msac.h>
#include <pcl/sample_consensus/rmsac.h>
#include <pcl/sample_consensus/mlesac.h>
#include <pcl/sample_consensus/sac_model.h>
#include <pcl/sample_consensus/sac_model_plane.h>
#include <pcl/sample_consensus/sac_model_sphere.h>
#include <pcl/sample_consensus/sac_model_cone.h>
#include <pcl/sample_consensus/sac_model_cylinder.h>
#include <pcl/sample_consensus/sac_model_circle.h>
#include <pcl/sample_consensus/sac_model_circle3d.h>
#include <pcl/sample_consensus/sac_model_line.h>
#include <pcl/sample_consensus/sac_model_normal_plane.h>
#include <pcl/sample_consensus/sac_model_normal_sphere.h>
#include <pcl/sample_consensus/sac_model_parallel_plane.h>
#include <pcl/sample_consensus/sac_model_normal_parallel_plane.h>
#include <pcl/features/normal_3d.h>

using namespace pcl;
using namespace pcl::io;
using namespace std;

typedef SampleConsensusModel<PointXYZ>::Ptr SampleConsensusModelPtr;
typedef SampleConsensusModelPlane<PointXYZ>::Ptr SampleConsensusModelPlanePtr;
typedef SampleConsensusModelSphere<PointXYZ>::Ptr SampleConsensusModelSpherePtr;
typedef SampleConsensusModelCylinder<PointXYZ, Normal>::Ptr SampleConsensusModelCylinderPtr;
typedef SampleConsensusModelCone<PointXYZ, Normal>::Ptr SampleConsensusModelConePtr;
typedef SampleConsensusModelCircle2D<PointXYZ>::Ptr SampleConsensusModelCircle2DPtr;
typedef SampleConsensusModelCircle3D<PointXYZ>::Ptr SampleConsensusModelCircle3DPtr;
typedef SampleConsensusModelLine<PointXYZ>::Ptr SampleConsensusModelLinePtr;
typedef SampleConsensusModelNormalPlane<PointXYZ, Normal>::Ptr SampleConsensusModelNormalPlanePtr;
typedef SampleConsensusModelNormalSphere<PointXYZ, Normal>::Ptr SampleConsensusModelNormalSpherePtr;
typedef SampleConsensusModelParallelPlane<PointXYZ>::Ptr SampleConsensusModelParallelPlanePtr;
typedef SampleConsensusModelNormalParallelPlane<PointXYZ, Normal>::Ptr SampleConsensusModelNormalParallelPlanePtr;

PointCloud<PointXYZ>::Ptr cloud_ (new PointCloud<PointXYZ> ());
PointCloud<Normal>::Ptr normals_ (new PointCloud<Normal> ());
vector<int> indices_;
float plane_coeffs_[] = {-0.8964f, -0.5868f, -1.208f};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

template<typename ModelType, typename SacType>
void verifyPlaneSac (ModelType & model, SacType & sac, unsigned int inlier_number = 2000, float tol = 1e-1f,
                     float refined_tol = 1e-1f, float proj_tol = 1e-3f)
{
  // Algorithm tests
  bool result = sac.computeModel ();
  ASSERT_EQ (result, true);

  std::vector<int> sample;
  sac.getModel (sample);
  EXPECT_EQ (int (sample.size ()), 3);

  std::vector<int> inliers;
  sac.getInliers (inliers);
  EXPECT_GE (int (inliers.size ()), inlier_number);

  Eigen::VectorXf coeff;
  sac.getModelCoefficients (coeff);
  EXPECT_EQ (int (coeff.size ()), 4);
  EXPECT_NEAR (coeff[0]/coeff[3], plane_coeffs_[0], tol);
  EXPECT_NEAR (coeff[1]/coeff[3], plane_coeffs_[1], tol);
  EXPECT_NEAR (coeff[2]/coeff[3], plane_coeffs_[2], tol);


  Eigen::VectorXf coeff_refined;
  model->optimizeModelCoefficients (inliers, coeff, coeff_refined);
  EXPECT_EQ (int (coeff_refined.size ()), 4);
  EXPECT_NEAR (coeff_refined[0]/coeff_refined[3], plane_coeffs_[0], refined_tol);
  EXPECT_NEAR (coeff_refined[1]/coeff_refined[3], plane_coeffs_[1], refined_tol);
  // This test fails in Windows (VS 2010) -- not sure why yet -- relaxing the constraint from 1e-2 to 1e-1
  // This test fails in MacOS too -- not sure why yet -- disabling
  //EXPECT_NEAR (coeff_refined[2]/coeff_refined[3], plane_coeffs_[2], refined_tol);

  // Projection tests
  PointCloud<PointXYZ> proj_points;
  model->projectPoints (inliers, coeff_refined, proj_points);
  EXPECT_NEAR (proj_points.points[20].x,  1.1266, proj_tol);
  EXPECT_NEAR (proj_points.points[20].y,  0.0152, proj_tol);
  EXPECT_NEAR (proj_points.points[20].z, -0.0156, proj_tol);

  EXPECT_NEAR (proj_points.points[30].x,  1.1843, proj_tol);
  EXPECT_NEAR (proj_points.points[30].y, -0.0635, proj_tol);
  EXPECT_NEAR (proj_points.points[30].z, -0.0201, proj_tol);

  EXPECT_NEAR (proj_points.points[50].x,  1.0749, proj_tol);
  EXPECT_NEAR (proj_points.points[50].y, -0.0586, proj_tol);
  EXPECT_NEAR (proj_points.points[50].z,  0.0587, refined_tol);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (SampleConsensusModelPlane, Base)
{
  // Create a shared plane model pointer directly
  SampleConsensusModelPlanePtr model (new SampleConsensusModelPlane<PointXYZ> (cloud_));

  // Basic tests
  PointCloud<PointXYZ>::ConstPtr cloud = model->getInputCloud ();
  ASSERT_EQ (cloud->points.size (), cloud_->points.size ());

  model->setInputCloud (cloud);
  cloud = model->getInputCloud ();
  ASSERT_EQ (cloud->points.size (), cloud_->points.size ());

  boost::shared_ptr<vector<int> > indices = model->getIndices ();
  ASSERT_EQ (indices->size (), indices_.size ());
  model->setIndices (indices_);
  indices = model->getIndices ();
  ASSERT_EQ (indices->size (), indices_.size ());
  model->setIndices (indices);
  indices = model->getIndices ();
  ASSERT_EQ (indices->size (), indices_.size ());
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, Base)
{
  // Create a shared plane model pointer directly
  SampleConsensusModelPlanePtr model (new SampleConsensusModelPlane<PointXYZ> (cloud_));

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  // Basic tests
  ASSERT_EQ (sac.getDistanceThreshold (), 0.03);
  sac.setDistanceThreshold (0.03);
  ASSERT_EQ (sac.getDistanceThreshold (), 0.03);

  sac.setProbability (0.99);
  ASSERT_EQ (sac.getProbability (), 0.99);

  sac.setMaxIterations (10000);
  ASSERT_EQ (sac.getMaxIterations (), 10000);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelPlane)
{
  srand (0);
  // Create a shared plane model pointer directly
  SampleConsensusModelPlanePtr model (new SampleConsensusModelPlane<PointXYZ> (cloud_));

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  verifyPlaneSac(model, sac);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (LMedS, SampleConsensusModelPlane)
{
  srand (0);
  // Create a shared plane model pointer directly
  SampleConsensusModelPlanePtr model (new SampleConsensusModelPlane<PointXYZ> (cloud_));

  // Create the LMedS object
  LeastMedianSquares<PointXYZ> sac (model, 0.03);

  verifyPlaneSac(model, sac);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (MSAC, SampleConsensusModelPlane)
{
  srand (0);
  // Create a shared plane model pointer directly
  SampleConsensusModelPlanePtr model (new SampleConsensusModelPlane<PointXYZ> (cloud_));

  // Create the MSAC object
  MEstimatorSampleConsensus<PointXYZ> sac (model, 0.03);

  verifyPlaneSac (model, sac);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RRANSAC, SampleConsensusModelPlane)
{
  srand (0);
  // Create a shared plane model pointer directly
  SampleConsensusModelPlanePtr model (new SampleConsensusModelPlane<PointXYZ> (cloud_));

  // Create the RRANSAC object
  RandomizedRandomSampleConsensus<PointXYZ> sac (model, 0.03);

  sac.setFractionNrPretest (10.0);
  ASSERT_EQ (sac.getFractionNrPretest (), 10.0);

  verifyPlaneSac(model, sac, 600, 1.0f, 1.0f, 0.01f);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RMSAC, SampleConsensusModelPlane)
{
  srand (0);
  // Create a shared plane model pointer directly
  SampleConsensusModelPlanePtr model (new SampleConsensusModelPlane<PointXYZ> (cloud_));

  // Create the RMSAC object
  RandomizedMEstimatorSampleConsensus<PointXYZ> sac (model, 0.03);

  sac.setFractionNrPretest (10.0);
  ASSERT_EQ (sac.getFractionNrPretest (), 10.0);

  verifyPlaneSac(model, sac, 600, 1.0f, 1.0f, 0.01f);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelNormalParallelPlane)
{
  srand (0);
  // Use a custom point cloud for these tests until we need something better
  PointCloud<PointXYZ> cloud;
  PointCloud<Normal> normals;
  cloud.points.resize (10);
  normals.resize (10);

  for (unsigned idx = 0; idx < cloud.size (); ++idx)
  {
    cloud.points[idx].x = static_cast<float> ((rand () % 200) - 100);
    cloud.points[idx].y = static_cast<float> ((rand () % 200) - 100);
    cloud.points[idx].z = 0.0f;

    normals.points[idx].normal_x = 0.0f;
    normals.points[idx].normal_y = 0.0f;
    normals.points[idx].normal_z = 1.0f;
  }

  // Create a shared plane model pointer directly
  SampleConsensusModelNormalParallelPlanePtr model (new SampleConsensusModelNormalParallelPlane<PointXYZ, Normal> (cloud.makeShared ()));
  model->setInputNormals (normals.makeShared ());

  const float max_angle_rad = 0.01f;
  const float angle_eps = 0.001f;
  model->setEpsAngle (max_angle_rad);

  // Test true axis
  {
    model->setAxis (Eigen::Vector3f (0, 0, 1));

    RandomSampleConsensus<PointXYZ> sac (model, 0.03);
    sac.computeModel();

    std::vector<int> inliers;
    sac.getInliers (inliers);
    ASSERT_EQ (inliers.size (), cloud.size ());
  }

  // test axis slightly in valid range
  {
    model->setAxis (Eigen::Vector3f(0, sin(max_angle_rad * (1 - angle_eps)), cos(max_angle_rad * (1 - angle_eps))));
    RandomSampleConsensus<PointXYZ> sac (model, 0.03);
    sac.computeModel();

    std::vector<int> inliers;
    sac.getInliers (inliers);
    ASSERT_EQ (inliers.size (), cloud.size ());
  }

  // test axis slightly out of valid range
  {
    model->setAxis (Eigen::Vector3f(0, sin(max_angle_rad * (1 + angle_eps)), cos(max_angle_rad * (1 + angle_eps))));
    RandomSampleConsensus<PointXYZ> sac (model, 0.03);
    sac.computeModel();

    std::vector<int> inliers;
    sac.getInliers (inliers);
    ASSERT_EQ (inliers.size (), 0);
  }
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (MLESAC, SampleConsensusModelPlane)
{
  srand (0);
  // Create a shared plane model pointer directly
  SampleConsensusModelPlanePtr model (new SampleConsensusModelPlane<PointXYZ> (cloud_));

  // Create the MSAC object
  MaximumLikelihoodSampleConsensus<PointXYZ> sac (model, 0.03);

  verifyPlaneSac(model, sac, 1000, 0.3f, 0.2f, 0.01f);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelSphere)
{
  srand (0);

  // Use a custom point cloud for these tests until we need something better
  PointCloud<PointXYZ> cloud;
  cloud.points.resize (10);
  cloud.points[0].x = 1.7068f; cloud.points[0].y = 1.0684f; cloud.points[0].z = 2.2147f;
  cloud.points[1].x = 2.4708f; cloud.points[1].y = 2.3081f; cloud.points[1].z = 1.1736f;
  cloud.points[2].x = 2.7609f; cloud.points[2].y = 1.9095f; cloud.points[2].z = 1.3574f;
  cloud.points[3].x = 2.8016f; cloud.points[3].y = 1.6704f; cloud.points[3].z = 1.5009f;
  cloud.points[4].x = 1.8517f; cloud.points[4].y = 2.0276f; cloud.points[4].z = 1.0112f;
  cloud.points[5].x = 1.8726f; cloud.points[5].y = 1.3539f; cloud.points[5].z = 2.7523f;
  cloud.points[6].x = 2.5179f; cloud.points[6].y = 2.3218f; cloud.points[6].z = 1.2074f;
  cloud.points[7].x = 2.4026f; cloud.points[7].y = 2.5114f; cloud.points[7].z = 2.7588f;
  cloud.points[8].x = 2.6999f; cloud.points[8].y = 2.5606f; cloud.points[8].z = 1.5571f;
  cloud.points[9].x = 0.0f;    cloud.points[9].y = 0.0f;    cloud.points[9].z = 0.0f;

  // Create a shared sphere model pointer directly
  SampleConsensusModelSpherePtr model (new SampleConsensusModelSphere<PointXYZ> (cloud.makeShared ()));

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  // Algorithm tests
  bool result = sac.computeModel ();
  ASSERT_EQ (result, true);

  std::vector<int> sample;
  sac.getModel (sample);
  EXPECT_EQ (int (sample.size ()), 4);

  std::vector<int> inliers;
  sac.getInliers (inliers);
  EXPECT_EQ (int (inliers.size ()), 9);

  Eigen::VectorXf coeff;
  sac.getModelCoefficients (coeff);
  EXPECT_EQ (int (coeff.size ()), 4);
  EXPECT_NEAR (coeff[0]/coeff[3], 2,  1e-2);
  EXPECT_NEAR (coeff[1]/coeff[3], 2,  1e-2);
  EXPECT_NEAR (coeff[2]/coeff[3], 2,  1e-2);

  Eigen::VectorXf coeff_refined;
  model->optimizeModelCoefficients (inliers, coeff, coeff_refined);
  EXPECT_EQ (int (coeff_refined.size ()), 4);
  EXPECT_NEAR (coeff_refined[0]/coeff_refined[3], 2,  1e-2);
  EXPECT_NEAR (coeff_refined[1]/coeff_refined[3], 2,  1e-2);
  EXPECT_NEAR (coeff_refined[2]/coeff_refined[3], 2,  1e-2);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelNormalSphere)
{
  srand (0);

  // Use a custom point cloud for these tests until we need something better
  PointCloud<PointXYZ> cloud;
  PointCloud<Normal> normals;
  cloud.points.resize (27); normals.points.resize (27);
  cloud.points[0].x = -0.014695f; cloud.points[0].y =  0.009549f; cloud.points[0].z = 0.954775f; 
  cloud.points[1].x =  0.014695f; cloud.points[1].y =  0.009549f; cloud.points[1].z = 0.954775f; 
  cloud.points[2].x = -0.014695f; cloud.points[2].y =  0.040451f; cloud.points[2].z = 0.954775f; 
  cloud.points[3].x =  0.014695f; cloud.points[3].y =  0.040451f; cloud.points[3].z = 0.954775f; 
  cloud.points[4].x = -0.009082f; cloud.points[4].y = -0.015451f; cloud.points[4].z = 0.972049f; 
  cloud.points[5].x =  0.009082f; cloud.points[5].y = -0.015451f; cloud.points[5].z = 0.972049f; 
  cloud.points[6].x = -0.038471f; cloud.points[6].y =  0.009549f; cloud.points[6].z = 0.972049f; 
  cloud.points[7].x =  0.038471f; cloud.points[7].y =  0.009549f; cloud.points[7].z = 0.972049f; 
  cloud.points[8].x = -0.038471f; cloud.points[8].y =  0.040451f; cloud.points[8].z = 0.972049f; 
  cloud.points[9].x =  0.038471f; cloud.points[9].y =  0.040451f; cloud.points[9].z = 0.972049f; 
  cloud.points[10].x = -0.009082f; cloud.points[10].y =  0.065451f; cloud.points[10].z = 0.972049f; 
  cloud.points[11].x =  0.009082f; cloud.points[11].y =  0.065451f; cloud.points[11].z = 0.972049f; 
  cloud.points[12].x = -0.023776f; cloud.points[12].y = -0.015451f; cloud.points[12].z = 0.982725f; 
  cloud.points[13].x =  0.023776f; cloud.points[13].y = -0.015451f; cloud.points[13].z = 0.982725f; 
  cloud.points[14].x = -0.023776f; cloud.points[14].y =  0.065451f; cloud.points[14].z = 0.982725f; 
  cloud.points[15].x =  0.023776f; cloud.points[15].y =  0.065451f; cloud.points[15].z = 0.982725f; 
  cloud.points[16].x = -0.000000f; cloud.points[16].y = -0.025000f; cloud.points[16].z = 1.000000f; 
  cloud.points[17].x =  0.000000f; cloud.points[17].y = -0.025000f; cloud.points[17].z = 1.000000f; 
  cloud.points[18].x = -0.029389f; cloud.points[18].y = -0.015451f; cloud.points[18].z = 1.000000f; 
  cloud.points[19].x =  0.029389f; cloud.points[19].y = -0.015451f; cloud.points[19].z = 1.000000f; 
  cloud.points[20].x = -0.047553f; cloud.points[20].y =  0.009549f; cloud.points[20].z = 1.000000f; 
  cloud.points[21].x =  0.047553f; cloud.points[21].y =  0.009549f; cloud.points[21].z = 1.000000f; 
  cloud.points[22].x = -0.047553f; cloud.points[22].y =  0.040451f; cloud.points[22].z = 1.000000f; 
  cloud.points[23].x =  0.047553f; cloud.points[23].y =  0.040451f; cloud.points[23].z = 1.000000f; 
  cloud.points[24].x = -0.029389f; cloud.points[24].y =  0.065451f; cloud.points[24].z = 1.000000f; 
  cloud.points[25].x =  0.029389f; cloud.points[25].y =  0.065451f; cloud.points[25].z = 1.000000f; 
  cloud.points[26].x =  0.000000f; cloud.points[26].y =  0.075000f; cloud.points[26].z = 1.000000f; 
  
  normals.points[0].normal[0] = -0.293893f; normals.points[0].normal[1] =  -0.309017f; normals.points[0].normal[2] =  -0.904509f; 
  normals.points[1].normal[0] =  0.293893f; normals.points[1].normal[1] =  -0.309017f; normals.points[1].normal[2] =  -0.904508f; 
  normals.points[2].normal[0] = -0.293893f; normals.points[2].normal[1] =   0.309017f; normals.points[2].normal[2] =  -0.904509f; 
  normals.points[3].normal[0] =  0.293893f; normals.points[3].normal[1] =   0.309017f; normals.points[3].normal[2] =  -0.904508f; 
  normals.points[4].normal[0] = -0.181636f; normals.points[4].normal[1] =  -0.809017f; normals.points[4].normal[2] =  -0.559017f; 
  normals.points[5].normal[0] =  0.181636f; normals.points[5].normal[1] =  -0.809017f; normals.points[5].normal[2] =  -0.559017f; 
  normals.points[6].normal[0] = -0.769421f; normals.points[6].normal[1] =  -0.309017f; normals.points[6].normal[2] =  -0.559017f; 
  normals.points[7].normal[0] =  0.769421f; normals.points[7].normal[1] =  -0.309017f; normals.points[7].normal[2] =  -0.559017f; 
  normals.points[8].normal[0] = -0.769421f; normals.points[8].normal[1] =   0.309017f; normals.points[8].normal[2] =  -0.559017f; 
  normals.points[9].normal[0] =  0.769421f; normals.points[9].normal[1] =   0.309017f; normals.points[9].normal[2] =  -0.559017f; 
  normals.points[10].normal[0] = -0.181636f; normals.points[10].normal[1] =  0.809017f; normals.points[10].normal[2] =  -0.559017f; 
  normals.points[11].normal[0] =  0.181636f; normals.points[11].normal[1] =  0.809017f; normals.points[11].normal[2] =  -0.559017f; 
  normals.points[12].normal[0] = -0.475528f; normals.points[12].normal[1] = -0.809017f; normals.points[12].normal[2] =  -0.345491f; 
  normals.points[13].normal[0] =  0.475528f; normals.points[13].normal[1] = -0.809017f; normals.points[13].normal[2] =  -0.345491f; 
  normals.points[14].normal[0] = -0.475528f; normals.points[14].normal[1] =  0.809017f; normals.points[14].normal[2] =  -0.345491f; 
  normals.points[15].normal[0] =  0.475528f; normals.points[15].normal[1] =  0.809017f; normals.points[15].normal[2] =  -0.345491f; 
  normals.points[16].normal[0] = -0.000000f; normals.points[16].normal[1] = -1.000000f; normals.points[16].normal[2] =  0.000000f; 
  normals.points[17].normal[0] =  0.000000f; normals.points[17].normal[1] = -1.000000f; normals.points[17].normal[2] =  0.000000f; 
  normals.points[18].normal[0] = -0.587785f; normals.points[18].normal[1] = -0.809017f; normals.points[18].normal[2] =  0.000000f; 
  normals.points[19].normal[0] =  0.587785f; normals.points[19].normal[1] = -0.809017f; normals.points[19].normal[2] =  0.000000f; 
  normals.points[20].normal[0] = -0.951057f; normals.points[20].normal[1] = -0.309017f; normals.points[20].normal[2] =  0.000000f; 
  normals.points[21].normal[0] =  0.951057f; normals.points[21].normal[1] = -0.309017f; normals.points[21].normal[2] =  0.000000f; 
  normals.points[22].normal[0] = -0.951057f; normals.points[22].normal[1] =  0.309017f; normals.points[22].normal[2] =  0.000000f; 
  normals.points[23].normal[0] =  0.951057f; normals.points[23].normal[1] =  0.309017f; normals.points[23].normal[2] =  0.000000f; 
  normals.points[24].normal[0] = -0.587785f; normals.points[24].normal[1] =  0.809017f; normals.points[24].normal[2] =  0.000000f; 
  normals.points[25].normal[0] =  0.587785f; normals.points[25].normal[1] =  0.809017f; normals.points[25].normal[2] =  0.000000f; 
  normals.points[26].normal[0] =  0.000000f; normals.points[26].normal[1] =  1.000000f; normals.points[26].normal[2] =  0.000000f; 
  
  // Create a shared sphere model pointer directly
  SampleConsensusModelNormalSpherePtr model (new SampleConsensusModelNormalSphere<PointXYZ, Normal> (cloud.makeShared ()));
  model->setInputNormals(normals.makeShared ());

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  // Algorithm tests
  bool result = sac.computeModel ();
  ASSERT_EQ (result, true); 

  std::vector<int> sample;
  sac.getModel (sample);
  EXPECT_EQ (int (sample.size ()), 4);

  std::vector<int> inliers;
  sac.getInliers (inliers);
  EXPECT_EQ (int (inliers.size ()), 27);

  Eigen::VectorXf coeff;
  sac.getModelCoefficients (coeff);
  EXPECT_EQ (int (coeff.size ()), 4);
  EXPECT_NEAR (coeff[0], 0.0,   1e-2);
  EXPECT_NEAR (coeff[1], 0.025, 1e-2);
  EXPECT_NEAR (coeff[2], 1.0,   1e-2);
  EXPECT_NEAR (coeff[3], 0.05,  1e-2);
  Eigen::VectorXf coeff_refined;
  model->optimizeModelCoefficients (inliers, coeff, coeff_refined);
  EXPECT_EQ (int (coeff_refined.size ()), 4);
  EXPECT_NEAR (coeff_refined[0], 0.0,   1e-2);
  EXPECT_NEAR (coeff_refined[1], 0.025, 1e-2);
  EXPECT_NEAR (coeff_refined[2], 1.0,   1e-2);
  EXPECT_NEAR (coeff_refined[3], 0.05,  1e-2);	 
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelCone)
{
  srand (0);

  // Use a custom point cloud for these tests until we need something better
  PointCloud<PointXYZ> cloud;
  PointCloud<Normal> normals;
  cloud.points.resize (31); normals.points.resize (31);

  cloud.points[0].x = -0.011247f; cloud.points[0].y = 0.200000f; cloud.points[0].z = 0.965384f; 
  cloud.points[1].x =  0.000000f; cloud.points[1].y = 0.200000f; cloud.points[1].z = 0.963603f; 
  cloud.points[2].x =  0.011247f; cloud.points[2].y = 0.200000f; cloud.points[2].z = 0.965384f; 
  cloud.points[3].x = -0.016045f; cloud.points[3].y = 0.175000f; cloud.points[3].z = 0.977916f; 
  cloud.points[4].x = -0.008435f; cloud.points[4].y = 0.175000f; cloud.points[4].z = 0.974038f; 
  cloud.points[5].x =  0.004218f; cloud.points[5].y = 0.175000f; cloud.points[5].z = 0.973370f; 
  cloud.points[6].x =  0.016045f; cloud.points[6].y = 0.175000f; cloud.points[6].z = 0.977916f; 
  cloud.points[7].x = -0.025420f; cloud.points[7].y = 0.200000f; cloud.points[7].z = 0.974580f; 
  cloud.points[8].x =  0.025420f; cloud.points[8].y = 0.200000f; cloud.points[8].z = 0.974580f; 
  cloud.points[9].x = -0.012710f; cloud.points[9].y = 0.150000f; cloud.points[9].z = 0.987290f; 
  cloud.points[10].x = -0.005624f; cloud.points[10].y = 0.150000f; cloud.points[10].z = 0.982692f; 
  cloud.points[11].x =  0.002812f; cloud.points[11].y = 0.150000f; cloud.points[11].z = 0.982247f; 
  cloud.points[12].x =  0.012710f; cloud.points[12].y = 0.150000f; cloud.points[12].z = 0.987290f; 
  cloud.points[13].x = -0.022084f; cloud.points[13].y = 0.175000f; cloud.points[13].z = 0.983955f; 
  cloud.points[14].x =  0.022084f; cloud.points[14].y = 0.175000f; cloud.points[14].z = 0.983955f; 
  cloud.points[15].x = -0.034616f; cloud.points[15].y = 0.200000f; cloud.points[15].z = 0.988753f; 
  cloud.points[16].x =  0.034616f; cloud.points[16].y = 0.200000f; cloud.points[16].z = 0.988753f; 
  cloud.points[17].x = -0.006044f; cloud.points[17].y = 0.125000f; cloud.points[17].z = 0.993956f; 
  cloud.points[18].x =  0.004835f; cloud.points[18].y = 0.125000f; cloud.points[18].z = 0.993345f; 
  cloud.points[19].x = -0.017308f; cloud.points[19].y = 0.150000f; cloud.points[19].z = 0.994376f; 
  cloud.points[20].x =  0.017308f; cloud.points[20].y = 0.150000f; cloud.points[20].z = 0.994376f; 
  cloud.points[21].x = -0.025962f; cloud.points[21].y = 0.175000f; cloud.points[21].z = 0.991565f; 
  cloud.points[22].x =  0.025962f; cloud.points[22].y = 0.175000f; cloud.points[22].z = 0.991565f; 
  cloud.points[23].x = -0.009099f; cloud.points[23].y = 0.125000f; cloud.points[23].z = 1.000000f; 
  cloud.points[24].x =  0.009099f; cloud.points[24].y = 0.125000f; cloud.points[24].z = 1.000000f; 
  cloud.points[25].x = -0.018199f; cloud.points[25].y = 0.150000f; cloud.points[25].z = 1.000000f; 
  cloud.points[26].x =  0.018199f; cloud.points[26].y = 0.150000f; cloud.points[26].z = 1.000000f; 
  cloud.points[27].x = -0.027298f; cloud.points[27].y = 0.175000f; cloud.points[27].z = 1.000000f; 
  cloud.points[28].x =  0.027298f; cloud.points[28].y = 0.175000f; cloud.points[28].z = 1.000000f; 
  cloud.points[29].x = -0.036397f; cloud.points[29].y = 0.200000f; cloud.points[29].z = 1.000000f; 
  cloud.points[30].x =  0.036397f; cloud.points[30].y = 0.200000f; cloud.points[30].z = 1.000000f; 

  normals.points[0].normal[0] = -0.290381f; normals.points[0].normal[1] =  -0.342020f; normals.points[0].normal[2] =  -0.893701f; 
  normals.points[1].normal[0] =  0.000000f; normals.points[1].normal[1] =  -0.342020f; normals.points[1].normal[2] =  -0.939693f; 
  normals.points[2].normal[0] =  0.290381f; normals.points[2].normal[1] =  -0.342020f; normals.points[2].normal[2] =  -0.893701f; 
  normals.points[3].normal[0] = -0.552338f; normals.points[3].normal[1] =  -0.342020f; normals.points[3].normal[2] =  -0.760227f; 
  normals.points[4].normal[0] = -0.290381f; normals.points[4].normal[1] =  -0.342020f; normals.points[4].normal[2] =  -0.893701f; 
  normals.points[5].normal[0] =  0.145191f; normals.points[5].normal[1] =  -0.342020f; normals.points[5].normal[2] =  -0.916697f; 
  normals.points[6].normal[0] =  0.552337f; normals.points[6].normal[1] =  -0.342020f; normals.points[6].normal[2] =  -0.760227f; 
  normals.points[7].normal[0] = -0.656282f; normals.points[7].normal[1] =  -0.342020f; normals.points[7].normal[2] =  -0.656283f; 
  normals.points[8].normal[0] =  0.656282f; normals.points[8].normal[1] =  -0.342020f; normals.points[8].normal[2] =  -0.656283f; 
  normals.points[9].normal[0] = -0.656283f; normals.points[9].normal[1] =  -0.342020f; normals.points[9].normal[2] =  -0.656282f; 
  normals.points[10].normal[0] = -0.290381f; normals.points[10].normal[1] =  -0.342020f; normals.points[10].normal[2] =  -0.893701f; 
  normals.points[11].normal[0] =  0.145191f; normals.points[11].normal[1] =  -0.342020f; normals.points[11].normal[2] =  -0.916697f; 
  normals.points[12].normal[0] =  0.656282f; normals.points[12].normal[1] =  -0.342020f; normals.points[12].normal[2] =  -0.656282f; 
  normals.points[13].normal[0] = -0.760228f; normals.points[13].normal[1] =  -0.342020f; normals.points[13].normal[2] =  -0.552337f; 
  normals.points[14].normal[0] =  0.760228f; normals.points[14].normal[1] =  -0.342020f; normals.points[14].normal[2] =  -0.552337f; 
  normals.points[15].normal[0] = -0.893701f; normals.points[15].normal[1] =  -0.342020f; normals.points[15].normal[2] =  -0.290380f; 
  normals.points[16].normal[0] =  0.893701f; normals.points[16].normal[1] =  -0.342020f; normals.points[16].normal[2] =  -0.290380f; 
  normals.points[17].normal[0] = -0.624162f; normals.points[17].normal[1] =  -0.342020f; normals.points[17].normal[2] =  -0.624162f; 
  normals.points[18].normal[0] =  0.499329f; normals.points[18].normal[1] =  -0.342020f; normals.points[18].normal[2] =  -0.687268f; 
  normals.points[19].normal[0] = -0.893701f; normals.points[19].normal[1] =  -0.342020f; normals.points[19].normal[2] =  -0.290380f; 
  normals.points[20].normal[0] =  0.893701f; normals.points[20].normal[1] =  -0.342020f; normals.points[20].normal[2] =  -0.290380f; 
  normals.points[21].normal[0] = -0.893701f; normals.points[21].normal[1] =  -0.342020f; normals.points[21].normal[2] =  -0.290381f; 
  normals.points[22].normal[0] =  0.893701f; normals.points[22].normal[1] =  -0.342020f; normals.points[22].normal[2] =  -0.290381f; 
  normals.points[23].normal[0] = -0.939693f; normals.points[23].normal[1] =  -0.342020f; normals.points[23].normal[2] =  0.000000f; 
  normals.points[24].normal[0] =  0.939693f; normals.points[24].normal[1] =  -0.342020f; normals.points[24].normal[2] =  0.000000f; 
  normals.points[25].normal[0] = -0.939693f; normals.points[25].normal[1] =  -0.342020f; normals.points[25].normal[2] =  0.000000f; 
  normals.points[26].normal[0] =  0.939693f; normals.points[26].normal[1] =  -0.342020f; normals.points[26].normal[2] =  0.000000f; 
  normals.points[27].normal[0] = -0.939693f; normals.points[27].normal[1] =  -0.342020f; normals.points[27].normal[2] =  0.000000f; 
  normals.points[28].normal[0] =  0.939693f; normals.points[28].normal[1] =  -0.342020f; normals.points[28].normal[2] =  0.000000f; 
  normals.points[29].normal[0] = -0.939693f; normals.points[29].normal[1] =  -0.342020f; normals.points[29].normal[2] =  0.000000f; 
  normals.points[30].normal[0] =  0.939693f; normals.points[30].normal[1] =  -0.342020f; normals.points[30].normal[2] =  0.000000f; 


  // Create a shared cylinder model pointer directly
  SampleConsensusModelConePtr model (new SampleConsensusModelCone<PointXYZ, Normal> (cloud.makeShared ()));
  model->setInputNormals (normals.makeShared ());

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  // Algorithm tests
  bool result = sac.computeModel ();
  ASSERT_EQ (result, true);

  std::vector<int> sample;
  sac.getModel (sample);
  EXPECT_EQ (int (sample.size ()), 3);

  std::vector<int> inliers;
  sac.getInliers (inliers);
  EXPECT_EQ (int (inliers.size ()), 31);

  Eigen::VectorXf coeff;
  sac.getModelCoefficients (coeff);
  EXPECT_EQ (int (coeff.size ()), 7);
  EXPECT_NEAR (coeff[0],  0, 1e-2);
  EXPECT_NEAR (coeff[1],  0.1,  1e-2);
  EXPECT_NEAR (coeff[6],  0.349066, 1e-2);

  Eigen::VectorXf coeff_refined;
  model->optimizeModelCoefficients (inliers, coeff, coeff_refined);
  EXPECT_EQ (int (coeff_refined.size ()), 7);
  EXPECT_NEAR (coeff_refined[6], 0.349066 , 1e-2);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelCylinder)
{
  srand (0);

  // Use a custom point cloud for these tests until we need something better
  PointCloud<PointXYZ> cloud;
  PointCloud<Normal> normals;
  cloud.points.resize (20); normals.points.resize (20);

  cloud.points[0].x =  -0.499902f; cloud.points[0].y =  2.199701f; cloud.points[0].z =  0.000008f;
  cloud.points[1].x =  -0.875397f; cloud.points[1].y =  2.030177f; cloud.points[1].z =  0.050104f;
  cloud.points[2].x =  -0.995875f; cloud.points[2].y =  1.635973f; cloud.points[2].z =  0.099846f;
  cloud.points[3].x =  -0.779523f; cloud.points[3].y =  1.285527f; cloud.points[3].z =  0.149961f;
  cloud.points[4].x =  -0.373285f; cloud.points[4].y =  1.216488f; cloud.points[4].z =  0.199959f;
  cloud.points[5].x =  -0.052893f; cloud.points[5].y =  1.475973f; cloud.points[5].z =  0.250101f;
  cloud.points[6].x =  -0.036558f; cloud.points[6].y =  1.887591f; cloud.points[6].z =  0.299839f;
  cloud.points[7].x =  -0.335048f; cloud.points[7].y =  2.171994f; cloud.points[7].z =  0.350001f;
  cloud.points[8].x =  -0.745456f; cloud.points[8].y =  2.135528f; cloud.points[8].z =  0.400072f;
  cloud.points[9].x =  -0.989282f; cloud.points[9].y =  1.803311f; cloud.points[9].z =  0.449983f;
  cloud.points[10].x = -0.900651f; cloud.points[10].y = 1.400701f; cloud.points[10].z = 0.500126f;
  cloud.points[11].x = -0.539658f; cloud.points[11].y = 1.201468f; cloud.points[11].z = 0.550079f;
  cloud.points[12].x = -0.151875f; cloud.points[12].y = 1.340951f; cloud.points[12].z = 0.599983f;
  cloud.points[13].x = -0.000724f; cloud.points[13].y = 1.724373f; cloud.points[13].z = 0.649882f;
  cloud.points[14].x = -0.188573f; cloud.points[14].y = 2.090983f; cloud.points[14].z = 0.699854f;
  cloud.points[15].x = -0.587925f; cloud.points[15].y = 2.192257f; cloud.points[15].z = 0.749956f;
  cloud.points[16].x = -0.927724f; cloud.points[16].y = 1.958846f; cloud.points[16].z = 0.800008f;
  cloud.points[17].x = -0.976888f; cloud.points[17].y = 1.549655f; cloud.points[17].z = 0.849970f;
  cloud.points[18].x = -0.702003f; cloud.points[18].y = 1.242707f; cloud.points[18].z = 0.899954f;
  cloud.points[19].x = -0.289916f; cloud.points[19].y = 1.246296f; cloud.points[19].z = 0.950075f;

  normals.points[0].normal[0] =   0.000098f; normals.points[0].normal[1] =   1.000098f; normals.points[0].normal[2] =   0.000008f;
  normals.points[1].normal[0] =  -0.750891f; normals.points[1].normal[1] =   0.660413f; normals.points[1].normal[2] =   0.000104f;
  normals.points[2].normal[0] =  -0.991765f; normals.points[2].normal[1] =  -0.127949f; normals.points[2].normal[2] =  -0.000154f;
  normals.points[3].normal[0] =  -0.558918f; normals.points[3].normal[1] =  -0.829439f; normals.points[3].normal[2] =  -0.000039f;
  normals.points[4].normal[0] =   0.253627f; normals.points[4].normal[1] =  -0.967447f; normals.points[4].normal[2] =  -0.000041f;
  normals.points[5].normal[0] =   0.894105f; normals.points[5].normal[1] =  -0.447965f; normals.points[5].normal[2] =   0.000101f;
  normals.points[6].normal[0] =   0.926852f; normals.points[6].normal[1] =   0.375543f; normals.points[6].normal[2] =  -0.000161f;
  normals.points[7].normal[0] =   0.329948f; normals.points[7].normal[1] =   0.943941f; normals.points[7].normal[2] =   0.000001f;
  normals.points[8].normal[0] =  -0.490966f; normals.points[8].normal[1] =   0.871203f; normals.points[8].normal[2] =   0.000072f;
  normals.points[9].normal[0] =  -0.978507f; normals.points[9].normal[1] =   0.206425f; normals.points[9].normal[2] =  -0.000017f;
  normals.points[10].normal[0] = -0.801227f; normals.points[10].normal[1] = -0.598534f; normals.points[10].normal[2] =  0.000126f;
  normals.points[11].normal[0] = -0.079447f; normals.points[11].normal[1] = -0.996697f; normals.points[11].normal[2] =  0.000079f;
  normals.points[12].normal[0] =  0.696154f; normals.points[12].normal[1] = -0.717889f; normals.points[12].normal[2] = -0.000017f;
  normals.points[13].normal[0] =  0.998685f; normals.points[13].normal[1] =  0.048502f; normals.points[13].normal[2] = -0.000118f;
  normals.points[14].normal[0] =  0.622933f; normals.points[14].normal[1] =  0.782133f; normals.points[14].normal[2] = -0.000146f;
  normals.points[15].normal[0] = -0.175948f; normals.points[15].normal[1] =  0.984480f; normals.points[15].normal[2] = -0.000044f;
  normals.points[16].normal[0] = -0.855476f; normals.points[16].normal[1] =  0.517824f; normals.points[16].normal[2] =  0.000008f;
  normals.points[17].normal[0] = -0.953769f; normals.points[17].normal[1] = -0.300571f; normals.points[17].normal[2] = -0.000030f;
  normals.points[18].normal[0] = -0.404035f; normals.points[18].normal[1] = -0.914700f; normals.points[18].normal[2] = -0.000046f;
  normals.points[19].normal[0] =  0.420154f; normals.points[19].normal[1] = -0.907445f; normals.points[19].normal[2] =  0.000075f;

  // Create a shared cylinder model pointer directly
  SampleConsensusModelCylinderPtr model (new SampleConsensusModelCylinder<PointXYZ, Normal> (cloud.makeShared ()));
  model->setInputNormals (normals.makeShared ());

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  // Algorithm tests
  bool result = sac.computeModel ();
  ASSERT_EQ (result, true);

  std::vector<int> sample;
  sac.getModel (sample);
  EXPECT_EQ (int (sample.size ()), 2);

  std::vector<int> inliers;
  sac.getInliers (inliers);
  EXPECT_EQ (int (inliers.size ()), 20);

  Eigen::VectorXf coeff;
  sac.getModelCoefficients (coeff);
  EXPECT_EQ (int (coeff.size ()), 7);
  EXPECT_NEAR (coeff[0], -0.5, 1e-3);
  EXPECT_NEAR (coeff[1],  1.7,  1e-3);
  EXPECT_NEAR (coeff[6],  0.5, 1e-3);

  Eigen::VectorXf coeff_refined;
  model->optimizeModelCoefficients (inliers, coeff, coeff_refined);
  EXPECT_EQ (int (coeff_refined.size ()), 7);
  EXPECT_NEAR (coeff_refined[6], 0.5, 1e-3);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelCircle2D)
{
  srand (0);

  // Use a custom point cloud for these tests until we need something better
  PointCloud<PointXYZ> cloud;
  cloud.points.resize (18);

  cloud.points[0].x = 3.587751f;  cloud.points[0].y = -4.190982f;  cloud.points[0].z = 0.0f;
  cloud.points[1].x = 3.808883f;  cloud.points[1].y = -4.412265f;  cloud.points[1].z = 0.0f;
  cloud.points[2].x = 3.587525f;  cloud.points[2].y = -5.809143f;  cloud.points[2].z = 0.0f;
  cloud.points[3].x = 2.999913f;  cloud.points[3].y = -5.999980f;  cloud.points[3].z = 0.0f;
  cloud.points[4].x = 2.412224f;  cloud.points[4].y = -5.809090f;  cloud.points[4].z = 0.0f;
  cloud.points[5].x = 2.191080f;  cloud.points[5].y = -5.587682f;  cloud.points[5].z = 0.0f;
  cloud.points[6].x = 2.048941f;  cloud.points[6].y = -5.309003f;  cloud.points[6].z = 0.0f;
  cloud.points[7].x = 2.000397f;  cloud.points[7].y = -4.999944f;  cloud.points[7].z = 0.0f;
  cloud.points[8].x = 2.999953f;  cloud.points[8].y = -6.000056f;  cloud.points[8].z = 0.0f;
  cloud.points[9].x = 2.691127f;  cloud.points[9].y = -5.951136f;  cloud.points[9].z = 0.0f;
  cloud.points[10].x = 2.190892f; cloud.points[10].y = -5.587838f; cloud.points[10].z = 0.0f;
  cloud.points[11].x = 2.048874f; cloud.points[11].y = -5.309052f; cloud.points[11].z = 0.0f;
  cloud.points[12].x = 1.999990f; cloud.points[12].y = -5.000147f; cloud.points[12].z = 0.0f;
  cloud.points[13].x = 2.049026f; cloud.points[13].y = -4.690918f; cloud.points[13].z = 0.0f;
  cloud.points[14].x = 2.190956f; cloud.points[14].y = -4.412162f; cloud.points[14].z = 0.0f;
  cloud.points[15].x = 2.412231f; cloud.points[15].y = -4.190918f; cloud.points[15].z = 0.0f;
  cloud.points[16].x = 2.691027f; cloud.points[16].y = -4.049060f; cloud.points[16].z = 0.0f;
  cloud.points[17].x = 2.0f;      cloud.points[17].y = -3.0f;      cloud.points[17].z = 0.0f;

  // Create a shared 2d circle model pointer directly
  SampleConsensusModelCircle2DPtr model (new SampleConsensusModelCircle2D<PointXYZ> (cloud.makeShared ()));

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  // Algorithm tests
  bool result = sac.computeModel ();
  ASSERT_EQ (result, true);

  std::vector<int> sample;
  sac.getModel (sample);
  EXPECT_EQ (int (sample.size ()), 3);

  std::vector<int> inliers;
  sac.getInliers (inliers);
  EXPECT_EQ (int (inliers.size ()), 17);

  Eigen::VectorXf coeff;
  sac.getModelCoefficients (coeff);
  EXPECT_EQ (int (coeff.size ()), 3);
  EXPECT_NEAR (coeff[0],  3, 1e-3);
  EXPECT_NEAR (coeff[1], -5, 1e-3);
  EXPECT_NEAR (coeff[2],  1, 1e-3);

  Eigen::VectorXf coeff_refined;
  model->optimizeModelCoefficients (inliers, coeff, coeff_refined);
  EXPECT_EQ (int (coeff_refined.size ()), 3);
  EXPECT_NEAR (coeff_refined[0],  3, 1e-3);
  EXPECT_NEAR (coeff_refined[1], -5, 1e-3);
  EXPECT_NEAR (coeff_refined[2],  1, 1e-3);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelCircle3D)
{
  srand (0);

  // Use a custom point cloud for these tests until we need something better
  PointCloud<PointXYZ> cloud;
  cloud.points.resize (20);

  cloud.points[0].x = 1.0f;  	    cloud.points[0].y = 5.0f;        cloud.points[0].z = -2.9000001f;
  cloud.points[1].x = 1.034202f;    cloud.points[1].y = 5.0f;        cloud.points[1].z = -2.9060307f;
  cloud.points[2].x = 1.0642787f;   cloud.points[2].y = 5.0f;        cloud.points[2].z = -2.9233956f;
  cloud.points[3].x = 1.0866026f;   cloud.points[3].y = 5.0f;  	     cloud.points[3].z = -2.95f;
  cloud.points[4].x = 1.0984808f;   cloud.points[4].y = 5.0f;  	     cloud.points[4].z = -2.9826353f;
  cloud.points[5].x = 1.0984808f;   cloud.points[5].y = 5.0f;        cloud.points[5].z = -3.0173647f;
  cloud.points[6].x = 1.0866026f;   cloud.points[6].y = 5.0f;  	     cloud.points[6].z = -3.05f;
  cloud.points[7].x = 1.0642787f;   cloud.points[7].y = 5.0f;  	     cloud.points[7].z = -3.0766044f;
  cloud.points[8].x = 1.034202f;    cloud.points[8].y = 5.0f;  	     cloud.points[8].z = -3.0939693f;
  cloud.points[9].x = 1.0f;         cloud.points[9].y = 5.0f;  	     cloud.points[9].z = -3.0999999f;
  cloud.points[10].x = 0.96579796f; cloud.points[10].y = 5.0f; 	     cloud.points[10].z = -3.0939693f;
  cloud.points[11].x = 0.93572122f; cloud.points[11].y = 5.0f; 	     cloud.points[11].z = -3.0766044f;
  cloud.points[12].x = 0.91339743f; cloud.points[12].y = 5.0f; 	     cloud.points[12].z = -3.05f;
  cloud.points[13].x = 0.90151924f; cloud.points[13].y = 5.0f; 	     cloud.points[13].z = -3.0173647f;
  cloud.points[14].x = 0.90151924f; cloud.points[14].y = 5.0f; 	     cloud.points[14].z = -2.9826353f;
  cloud.points[15].x = 0.91339743f; cloud.points[15].y = 5.0f; 	     cloud.points[15].z = -2.95f;
  cloud.points[16].x = 0.93572122f; cloud.points[16].y = 5.0f; 	     cloud.points[16].z = -2.9233956f;
  cloud.points[17].x = 0.96579796f; cloud.points[17].y = 5.0;        cloud.points[17].z = -2.9060307f;
  cloud.points[18].x = 0.85000002f; cloud.points[18].y = 4.8499999f; cloud.points[18].z = -3.1500001f;
  cloud.points[19].x = 1.15f; 	    cloud.points[19].y = 5.1500001f; cloud.points[19].z = -2.8499999f;

  // Create a shared 3d circle model pointer directly
  SampleConsensusModelCircle3DPtr model (new SampleConsensusModelCircle3D<PointXYZ> (cloud.makeShared ()));

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  // Algorithm tests
  bool result = sac.computeModel ();
  ASSERT_EQ (result, true);

  std::vector<int> sample;
  sac.getModel (sample);
  EXPECT_EQ (int (sample.size ()), 3);

  std::vector<int> inliers;
  sac.getInliers (inliers);
  EXPECT_EQ (int (inliers.size ()), 18);

  Eigen::VectorXf coeff;
  sac.getModelCoefficients (coeff);
  EXPECT_EQ (int (coeff.size ()), 7);
  EXPECT_NEAR (coeff[0],  1, 1e-3);
  EXPECT_NEAR (coeff[1],  5, 1e-3);
  EXPECT_NEAR (coeff[2], -3, 1e-3);
  EXPECT_NEAR (coeff[3],0.1, 1e-3);
  EXPECT_NEAR (coeff[4],  0, 1e-3);
  EXPECT_NEAR (coeff[5], -1, 1e-3);
  EXPECT_NEAR (coeff[6],  0, 1e-3);

  Eigen::VectorXf coeff_refined;
  model->optimizeModelCoefficients (inliers, coeff, coeff_refined);
  EXPECT_EQ (int (coeff_refined.size ()), 7);
  EXPECT_NEAR (coeff_refined[0],  1, 1e-3);
  EXPECT_NEAR (coeff_refined[1],  5, 1e-3);
  EXPECT_NEAR (coeff_refined[2], -3, 1e-3);
  EXPECT_NEAR (coeff_refined[3],0.1, 1e-3);
  EXPECT_NEAR (coeff_refined[4],  0, 1e-3);
  EXPECT_NEAR (coeff_refined[5], -1, 1e-3);
  EXPECT_NEAR (coeff_refined[6],  0, 1e-3);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelLine)
{
  srand (0);

  // Use a custom point cloud for these tests until we need something better
  PointCloud<PointXYZ> cloud;
  cloud.points.resize (10);

  cloud.points[0].x = 1.0f;  cloud.points[0].y = 2.0f;  cloud.points[0].z = 3.0f;
  cloud.points[1].x = 4.0f;  cloud.points[1].y = 5.0f;  cloud.points[1].z = 6.0f;
  cloud.points[2].x = 7.0f;  cloud.points[2].y = 8.0f;  cloud.points[2].z = 9.0f;
  cloud.points[3].x = 10.0f; cloud.points[3].y = 11.0f; cloud.points[3].z = 12.0f;
  cloud.points[4].x = 13.0f; cloud.points[4].y = 14.0f; cloud.points[4].z = 15.0f;
  cloud.points[5].x = 16.0f; cloud.points[5].y = 17.0f; cloud.points[5].z = 18.0f;
  cloud.points[6].x = 19.0f; cloud.points[6].y = 20.0f; cloud.points[6].z = 21.0f;
  cloud.points[7].x = 22.0f; cloud.points[7].y = 23.0f; cloud.points[7].z = 24.0f;
  cloud.points[8].x = -5.0f; cloud.points[8].y = 1.57f; cloud.points[8].z = 0.75f;
  cloud.points[9].x = 4.0f;  cloud.points[9].y = 2.0f;  cloud.points[9].z = 3.0f;

  // Create a shared line model pointer directly
  SampleConsensusModelLinePtr model (new SampleConsensusModelLine<PointXYZ> (cloud.makeShared ()));

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.001);

  // Algorithm tests
  bool result = sac.computeModel ();
  ASSERT_EQ (result, true);

  std::vector<int> sample;
  sac.getModel (sample);
  EXPECT_EQ (int (sample.size ()), 2);

  std::vector<int> inliers;
  sac.getInliers (inliers);
  EXPECT_EQ (int (inliers.size ()), 8);

  Eigen::VectorXf coeff;
  sac.getModelCoefficients (coeff);
  EXPECT_EQ (int (coeff.size ()), 6);
  EXPECT_NEAR (coeff[4]/coeff[3], 1, 1e-4);
  EXPECT_NEAR (coeff[5]/coeff[3], 1, 1e-4);

  Eigen::VectorXf coeff_refined;
  model->optimizeModelCoefficients (inliers, coeff, coeff_refined);
  EXPECT_EQ (int (coeff_refined.size ()), 6);
  EXPECT_NEAR (coeff[4]/coeff[3], 1, 1e-4);
  EXPECT_NEAR (coeff[5]/coeff[3], 1, 1e-4);

  // Projection tests
  PointCloud<PointXYZ> proj_points;
  model->projectPoints (inliers, coeff_refined, proj_points);

  EXPECT_NEAR (proj_points.points[2].x, 7.0, 1e-4);
  EXPECT_NEAR (proj_points.points[2].y, 8.0, 1e-4);
  EXPECT_NEAR (proj_points.points[2].z, 9.0, 1e-4);

  EXPECT_NEAR (proj_points.points[3].x, 10.0, 1e-4);
  EXPECT_NEAR (proj_points.points[3].y, 11.0, 1e-4);
  EXPECT_NEAR (proj_points.points[3].z, 12.0, 1e-4);

  EXPECT_NEAR (proj_points.points[5].x, 16.0, 1e-4);
  EXPECT_NEAR (proj_points.points[5].y, 17.0, 1e-4);
  EXPECT_NEAR (proj_points.points[5].z, 18.0, 1e-4);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TEST (RANSAC, SampleConsensusModelNormalPlane)
{
  srand (0);
  // Create a shared plane model pointer directly
  SampleConsensusModelNormalPlanePtr model (new SampleConsensusModelNormalPlane<PointXYZ, Normal> (cloud_));
  model->setInputNormals (normals_);
  model->setNormalDistanceWeight (0.01);
  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> sac (model, 0.03);

  verifyPlaneSac (model, sac);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Test if RANSAC finishes within a second.
TEST (SAC, InfiniteLoop)
{
  const unsigned point_count = 100;
  PointCloud<PointXYZ> cloud;
  cloud.points.resize (point_count);
  for (unsigned pIdx = 0; pIdx < point_count; ++pIdx)
  {
    cloud.points[pIdx].x = static_cast<float> (pIdx);
    cloud.points[pIdx].y = 0.0;
    cloud.points[pIdx].z = 0.0;
  }

  boost::posix_time::time_duration delay(0,0,1,0);
  boost::function<bool ()> sac_function;
  SampleConsensusModelSpherePtr model (new SampleConsensusModelSphere<PointXYZ> (cloud.makeShared ()));

  // Create the RANSAC object
  RandomSampleConsensus<PointXYZ> ransac (model, 0.03);
  sac_function = boost::bind (&RandomSampleConsensus<PointXYZ>::computeModel, &ransac, 0);
  boost::thread thread1 (sac_function);
  ASSERT_TRUE(thread1.timed_join(delay));

  // Create the LMSAC object
  LeastMedianSquares<PointXYZ> lmsac (model, 0.03);
  sac_function = boost::bind (&LeastMedianSquares<PointXYZ>::computeModel, &lmsac, 0);
  boost::thread thread2 (sac_function);
  ASSERT_TRUE(thread2.timed_join(delay));

  // Create the MSAC object
  MEstimatorSampleConsensus<PointXYZ> mesac (model, 0.03);
  sac_function = boost::bind (&MEstimatorSampleConsensus<PointXYZ>::computeModel, &mesac, 0);
  boost::thread thread3 (sac_function);
  ASSERT_TRUE(thread3.timed_join(delay));

  // Create the RRSAC object
  RandomizedRandomSampleConsensus<PointXYZ> rrsac (model, 0.03);
  sac_function = boost::bind (&RandomizedRandomSampleConsensus<PointXYZ>::computeModel, &rrsac, 0);
  boost::thread thread4 (sac_function);
  ASSERT_TRUE(thread4.timed_join(delay));

  // Create the RMSAC object
  RandomizedMEstimatorSampleConsensus<PointXYZ> rmsac (model, 0.03);
  sac_function = boost::bind (&RandomizedMEstimatorSampleConsensus<PointXYZ>::computeModel, &rmsac, 0);
  boost::thread thread5 (sac_function);
  ASSERT_TRUE(thread5.timed_join(delay));

  // Create the MLESAC object
  MaximumLikelihoodSampleConsensus<PointXYZ> mlesac (model, 0.03);
  sac_function = boost::bind (&MaximumLikelihoodSampleConsensus<PointXYZ>::computeModel, &mlesac, 0);
  boost::thread thread6 (sac_function);
  ASSERT_TRUE(thread6.timed_join(delay));
}

/* ---[ */
int
  main (int argc, char** argv)
{
  if (argc < 2)
  {
    std::cerr << "No test file given. Please download `sac_plane_test.pcd` and pass its path to the test." << std::endl;
    return (-1);
  }

  // Load a standard PCD file from disk
  pcl::PCLPointCloud2 cloud_blob;
  if (loadPCDFile (argv[1], cloud_blob) < 0)
  {
    std::cerr << "Failed to read test file. Please download `sac_plane_test.pcd` and pass its path to the test." << std::endl;
    return (-1);
  }
  fromPCLPointCloud2 (cloud_blob, *cloud_);

  indices_.resize (cloud_->points.size ());
  for (size_t i = 0; i < indices_.size (); ++i) { indices_[i] = int (i); }

  // Estimate surface normals
  NormalEstimation<PointXYZ, Normal> n;
  search::Search<PointXYZ>::Ptr tree (new search::KdTree<PointXYZ>);
  tree->setInputCloud (cloud_);
  n.setInputCloud (cloud_);
  boost::shared_ptr<vector<int> > indicesptr (new vector<int> (indices_));
  n.setIndices (indicesptr);
  n.setSearchMethod (tree);
  n.setRadiusSearch (0.02);    // Use 2cm radius to estimate the normals
  n.compute (*normals_);

  testing::InitGoogleTest (&argc, argv);
  return (RUN_ALL_TESTS ());
}
/* ]--- */
