void RosPlaneSegmenter::segmentPlaneIfNotExist(const sensor_msgs::PointCloud2 &input_cloud)
{
	if (!initialized_)
	{
		std::cerr << "ERROR, RosPlaneSegmenter is not initialized yet.\n";
		return;
	}
	else if (!this->ready)
	{
		ROS_INFO("Performing plane segmentation");
		// perform table segmentation
		CloudXYZRGBA::Ptr cloud(new CloudXYZRGBA());
		fromROSMsg(input_cloud,*cloud);
		CloudXYZRGBA::Ptr cloud_filtered (new CloudXYZRGBA());
		std::string table_tf_parent;
		tf::StampedTransform transform;

		if (!listener_->frameExists(table_tf_name_))
		{
			ROS_WARN("Fail to find table frame [%s]",table_tf_name_.c_str());
			return;
		}

		listener_->getParent(table_tf_name_,ros::Time(0),table_tf_parent);
		if (use_rosbag_ || listener_->waitForTransform(table_tf_parent,table_tf_name_,ros::Time::now(),ros::Duration(1.5)))
		{
			ROS_INFO("Found table frame [%s]",table_tf_name_.c_str());
			listener_->lookupTransform(table_tf_parent,table_tf_name_,ros::Time(0),transform);
			Eigen::Affine3d box_pose;
			tf::transformTFToEigen(transform, box_pose);
			Eigen::Affine3f box_pose_float(box_pose);
			cloud_filtered = this->cropBox(*cloud, box_pose_float, Eigen::Vector3f(0.5,0.5,0.5));
		}
		else
		{
			ROS_WARN("Fail to find table frame [%s]",table_tf_name_.c_str());
			return;
		}

		CloudXYZRGBA::Ptr table_hull;

		if (use_tf_surface_)
		{
			Eigen::Affine3d plane_pose;
			tf::transformTFToEigen(transform, plane_pose);
			Eigen::Affine3f plane_pose_float(plane_pose);
			table_hull = this->generatePlaneConvexHullFromPoseXYplane(plane_pose_float,0.5);
		}
		else
		{
			table_hull = this->generatePlaneConvexHull(*cloud_filtered);
		}

		if (table_hull->size() > 0)
		{
			this->setPlaneConvexHull(*table_hull);

			if (update_table_) writer_.write<pcl::PointXYZRGBA> (load_table_path_, *table_hull, true);
		}
	}
}
  void VoxelGridDownsampleManager::pointCB(const sensor_msgs::PointCloud2ConstPtr &input)
  {
    try {
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr output_cloud (new pcl::PointCloud<pcl::PointXYZRGB>);
    if (grid_.size() == 0) {
      ROS_INFO("the number of registered grids is 0, skipping");
      return;
    }
    fromROSMsg(*input, *cloud);
    for (size_t i = 0; i < grid_.size(); i++)
    {
      visualization_msgs::Marker::ConstPtr target_grid = grid_[i];
      // not yet tf is supported
      int id = target_grid->id;
      // solve tf with ros::Time 0.0

      // transform pointcloud to the frame_id of target_grid
      pcl::PointCloud<pcl::PointXYZRGB>::Ptr transformed_cloud (new pcl::PointCloud<pcl::PointXYZRGB>);
      pcl_ros::transformPointCloud(target_grid->header.frame_id,
                                   *cloud,
                                   *transformed_cloud,
                                   tf_listener);
      double center_x = target_grid->pose.position.x;
      double center_y = target_grid->pose.position.y;
      double center_z = target_grid->pose.position.z;
      double range_x = target_grid->scale.x * 1.0; // for debug
      double range_y = target_grid->scale.y * 1.0;
      double range_z = target_grid->scale.z * 1.0;
      double min_x = center_x - range_x / 2.0;
      double max_x = center_x + range_x / 2.0;
      double min_y = center_y - range_y / 2.0;
      double max_y = center_y + range_y / 2.0;
      double min_z = center_z - range_z / 2.0;
      double max_z = center_z + range_z / 2.0;
      double resolution = target_grid->color.r;
      // filter order: x -> y -> z -> downsample
      pcl::PassThrough<pcl::PointXYZRGB> pass_x;
      pass_x.setFilterFieldName("x");
      pass_x.setFilterLimits(min_x, max_x);
      
      pcl::PassThrough<pcl::PointXYZRGB> pass_y;
      pass_y.setFilterFieldName("y");
      pass_y.setFilterLimits(min_y, max_y);
      pcl::PassThrough<pcl::PointXYZRGB> pass_z;
      pass_z.setFilterFieldName("z");
      pass_z.setFilterLimits(min_z, max_z);

      ROS_INFO_STREAM(id << " filter x: " << min_x << " - " << max_x);
      ROS_INFO_STREAM(id << " filter y: " << min_y << " - " << max_y);
      ROS_INFO_STREAM(id << " filter z: " << min_z << " - " << max_z);
      
      pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_after_x (new pcl::PointCloud<pcl::PointXYZRGB>);
      pass_x.setInputCloud (transformed_cloud);
      pass_x.filter(*cloud_after_x);

      pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_after_y (new pcl::PointCloud<pcl::PointXYZRGB>);
      pass_y.setInputCloud (cloud_after_x);
      pass_y.filter(*cloud_after_y);

      pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_after_z (new pcl::PointCloud<pcl::PointXYZRGB>);
      pass_z.setInputCloud (cloud_after_y);
      pass_z.filter(*cloud_after_z);

      // downsample
      pcl::VoxelGrid<pcl::PointXYZRGB> sor;
      sor.setInputCloud (cloud_after_z);
      sor.setLeafSize (resolution, resolution, resolution);
      pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZRGB>);
      sor.filter (*cloud_filtered);

      // reverse transform
      pcl::PointCloud<pcl::PointXYZRGB>::Ptr reverse_transformed_cloud (new pcl::PointCloud<pcl::PointXYZRGB>);
      pcl_ros::transformPointCloud(input->header.frame_id,
                                   *cloud_filtered,
                                   *reverse_transformed_cloud,
                                   tf_listener);
      
      // adding the output into *output_cloud
      // tmp <- cloud_filtered + output_cloud
      // output_cloud <- tmp
      //pcl::PointCloud<pcl::PointXYZRGB>::Ptr tmp (new pcl::PointCloud<pcl::PointXYZRGB>);
      //pcl::concatenatePointCloud (*cloud_filtered, *output_cloud, tmp);
      //output_cloud = tmp;
      ROS_INFO_STREAM(id << " includes " << reverse_transformed_cloud->points.size() << " points");
      for (size_t i = 0; i < reverse_transformed_cloud->points.size(); i++) {
        output_cloud->points.push_back(reverse_transformed_cloud->points[i]);
      }
    }
    sensor_msgs::PointCloud2 out;
    toROSMsg(*output_cloud, out);
    out.header = input->header;
    pub_.publish(out);          // for debugging

    // for concatenater
    size_t cluster_num = output_cloud->points.size() / max_points_ + 1;
    ROS_INFO_STREAM("encoding into " << cluster_num << " clusters");
    for (size_t i = 0; i < cluster_num; i++) {
      size_t start_index = max_points_ * i;
      size_t end_index = max_points_ * (i + 1) > output_cloud->points.size() ?
        output_cloud->points.size(): max_points_ * (i + 1);
      sensor_msgs::PointCloud2 cluster_out_ros;
      pcl::PointCloud<pcl::PointXYZRGB>::Ptr
        cluster_out_pcl(new pcl::PointCloud<pcl::PointXYZRGB>);
      cluster_out_pcl->points.resize(end_index - start_index);
      // build cluster_out_pcl
      ROS_INFO_STREAM("make cluster from " << start_index << " to " << end_index);
      for (size_t j = start_index; j < end_index; j++) {
        cluster_out_pcl->points[j - start_index] = output_cloud->points[j];
      }
      // conevrt cluster_out_pcl into ros msg
      toROSMsg(*cluster_out_pcl, cluster_out_ros);
      jsk_pcl_ros::SlicedPointCloud publish_point_cloud;
      cluster_out_ros.header = input->header;
      publish_point_cloud.point_cloud = cluster_out_ros;
      publish_point_cloud.slice_index = i;
      publish_point_cloud.sequence_id = sequence_id_;
      pub_encoded_.publish(publish_point_cloud);
      ros::Duration(1.0 / rate_).sleep();
    }
    }
    catch (std::runtime_error e) { // catch any error
      NODELET_WARN_STREAM("error has occured in VoxelGridDownsampleManager but ignore it: " << e.what());
      ros::Duration(1.0 / rate_).sleep();
    }
  }
    template<class T> void filter (const sensor_msgs::PointCloud2::ConstPtr &input,
                                   const PCLIndicesMsg::ConstPtr &indices) {
      pcl::PointCloud<T> pcl_input_cloud, output;
      fromROSMsg(*input, pcl_input_cloud);
      boost::mutex::scoped_lock lock (mutex_);
      std::vector<int> ex_indices;
      ex_indices.resize(0);

      int width = input->width;
      int height = input->height;
      int ox, oy, sx, sy;

      sx = step_x_;
      ox = sx/2;
      if(height == 1) {
        sy = 1;
        oy = 0;
      } else {
        sy = step_y_;
        oy = sy/2;
      }

      if (indices) {
        std::vector<int> flags;
        flags.resize(width*height);

        //std::vector<int>::iterator it;
        //for(it = indices->begin(); it != indices->end(); it++)
        //flags[*it] = 1;
        for(unsigned int i = 0; i < indices->indices.size(); i++) {
          flags[indices->indices.at(i)] = 1;
        }
        for(int y = oy; y < height; y += sy) {
          for(int x = ox; x < width; x += sx) {
            if (flags[y*width + x] == 1) {
              ex_indices.push_back(y*width + x); // just use points in indices
            }
          }
        }
      } else {
        for(int y = oy; y < height; y += sy) {
          for(int x = ox; x < width; x += sx) {
            ex_indices.push_back(y*width + x);
          }
        }
      }
      pcl::ExtractIndices<T> extract;
      extract.setInputCloud (pcl_input_cloud.makeShared());
      extract.setIndices (boost::make_shared <std::vector<int> > (ex_indices));
      extract.setNegative (false);
      extract.filter (output);

      if (output.points.size() > 0) {
        sensor_msgs::PointCloud2 ros_out;
        toROSMsg(output, ros_out);
        ros_out.header = input->header;
        ros_out.width = (width - ox)/sx;
        if((width - ox)%sx) ros_out.width += 1;
        ros_out.height = (height - oy)/sy;
        if((height - oy)%sy) ros_out.height += 1;
        ros_out.row_step = ros_out.point_step * ros_out.width;
        ros_out.is_dense = input->is_dense;
#if DEBUG
        JSK_NODELET_INFO("%dx%d (%d %d)(%d %d) -> %dx%d %d", width,height, ox, oy, sx, sy,
                 ros_out.width, ros_out.height, ex_indices.size());
#endif
        pub_.publish(ros_out);
        JSK_NODELET_INFO("%s:: input header stamp is [%f]", getName().c_str(),
                     input->header.stamp.toSec());
        JSK_NODELET_INFO("%s:: output header stamp is [%f]", getName().c_str(),
                     ros_out.header.stamp.toSec());
      }
      
    }
// callback function when the object descriptors are received
// this will also trigger the recognition if all the other keypoints and descriptors have been received
void object_descriptor_shot352_cb (const object_recognition::Shot352_bundle::Ptr input)
{
	ros::NodeHandle nh_param("~");
	nh_param.param<double>("maximum_descriptor_distance" , max_descr_dist_ , 0.25 );

	// check if world was already processed
	if (world_descriptors_shot352 == NULL)
	{
		ROS_WARN("Received object descriptors before having a world pointcloud to compare");
		return;
	}
	// check if the stored world descriptors can be assinged to the stored keypoints
	if ((int)world_keypoints->size() != (int)world_descriptors_shot352->size())
	{
		ROS_WARN("Received %i descriptors and %i keypoints for the world. Number must be equal", (int)world_descriptors_shot352->size(), (int)world_keypoints->size());
		return;
	}
	// check if the received object descriptors can be assigned to the stored keypoints
	if ((int)object_keypoints->size() != (int)input->descriptors.size())
	{
		ROS_WARN("Received %i descriptors and %i keypoints for the object. Number must be equal", (int)input->descriptors.size(), (int)object_keypoints->size());
		return;
	}
	
	// Debug output 
	ROS_INFO("Received %i descriptors for the world and %i for the object", (int)world_descriptors_shot352->size(), (int)input->descriptors.size());

	object_descriptors_shot352 = DescriptorCloudShot352::Ptr (new DescriptorCloudShot352 ());
	fromROSMsg(*input, *object_descriptors_shot352);
	//
	//  Find Object-World Correspondences with KdTree
	//
	cout << "... finding correspondences ..." << endl;
	pcl::CorrespondencesPtr object_world_corrs (new pcl::Correspondences ());
	
	pcl::KdTreeFLANN<SHOT352> match_search;
	match_search.setInputCloud (object_descriptors_shot352);

	// For each world keypoint descriptor
	// find nearest neighbor into the object keypoints descriptor cloud 
	// and add it to the correspondences vector
	for (size_t i = 0; i < world_descriptors_shot352->size (); ++i)
	{
		std::vector<int> neigh_indices (1);
		std::vector<float> neigh_sqr_dists (1);
		if (!pcl_isfinite (world_descriptors_shot352->at (i).descriptor[0])) //skipping NaNs
		{
			continue;
		}
		int found_neighs = match_search.nearestKSearch (world_descriptors_shot352->at (i), 1, neigh_indices, neigh_sqr_dists);
		// add match only if the squared descriptor distance is less than 0.25 
		// SHOT descriptor distances are between 0 and 1 by design
		if(found_neighs == 1 && neigh_sqr_dists[0] < (float)max_descr_dist_) 
		{
			pcl::Correspondence corr (neigh_indices[0], static_cast<int> (i), neigh_sqr_dists[0]);
			object_world_corrs->push_back (corr);
		}
	}
	std::cout << "Correspondences found: " << object_world_corrs->size () << std::endl;
	
	//
	// all keypoints and descriptors were found, no match the correspondences to the real object!
	//
	cluster(object_world_corrs);
}
// Callback function when the world descriptors are received
void world_descriptor_shot1344_cb (const object_recognition::Shot1344_bundle::Ptr input)
{
	world_descriptors_shot1344 = DescriptorCloudShot1344::Ptr (new DescriptorCloudShot1344 ());
	fromROSMsg(*input, *world_descriptors_shot1344);
}
// Callback function when the world descriptors are received
void world_descriptor_shot352_cb (const object_recognition::Shot352_bundle::Ptr input)
{
	world_descriptors_shot352 = DescriptorCloudShot352::Ptr (new DescriptorCloudShot352 ());
	fromROSMsg(*input, *world_descriptors_shot352);
}
// callback function when the object descriptors are received
// this will also trigger the recognition if all the other keypoints and descriptors have been received
void object_descriptor_shot1344_cb (const object_recognition::Shot1344_bundle::Ptr input)
{
	ros::NodeHandle nh_param("~");
	nh_param.param<double>("maximum_descriptor_distance" , max_descr_dist_ , 0.25 );
	// check if world was already processed
	if (world_descriptors_shot1344 == NULL)
	{
		ROS_WARN("Received object descriptors before having a world pointcloud to compare");
		return;
	}
	// check if the stored world descriptors can be assinged to the stored keypoints
	if ((int)world_keypoints->size() != (int)world_descriptors_shot1344->size())
	{
		ROS_WARN("Received %i descriptors and %i keypoints for the world. Number must be equal", (int)world_descriptors_shot1344->size(), (int)world_keypoints->size());
		return;
	}
	// check if the received object descriptors can be assigned to the stored keypoints
	if ((int)object_keypoints->size() != (int)input->descriptors.size())
	{
		ROS_WARN("Received %i descriptors and %i keypoints for the object. Number must be equal", (int)input->descriptors.size(), (int)object_keypoints->size());
		return;
	}
	
	object_descriptors_shot1344 = DescriptorCloudShot1344::Ptr (new DescriptorCloudShot1344 ());
	fromROSMsg(*input, *object_descriptors_shot1344);

	// Debug output 
	ROS_INFO("Received %i descriptors for the world and %i for the object", (int)world_descriptors_shot1344->size(), (int)input->descriptors.size());

	//
	//  Find Object-World Correspondences with KdTree
	//
	cout << "... finding correspondences ..." << endl;
	pcl::CorrespondencesPtr object_world_corrs (new pcl::Correspondences ());
	
	pcl::KdTreeFLANN<SHOT1344> match_search;
	match_search.setInputCloud (object_descriptors_shot1344);
		
	// For each world keypoint descriptor
	// find nearest neighbor into the object keypoints descriptor cloud 
	// and add it to the correspondences vector
	for (size_t i = 0; i < world_descriptors_shot1344->size (); ++i)
	{
		std::vector<int> neigh_indices (1);
		std::vector<float> neigh_sqr_dists (1);
		if (!pcl_isfinite (world_descriptors_shot1344->at (i).descriptor[0])) //skipping NaNs
		{
			continue;
		}
		int found_neighs = match_search.nearestKSearch (world_descriptors_shot1344->at (i), 1, neigh_indices, neigh_sqr_dists);
		// add match only if the squared descriptor distance is less than max_descr_dist_ 
		// SHOT descriptor distances are between 0 and 1 by design
		if(found_neighs == 1 && neigh_sqr_dists[0] < (float)max_descr_dist_) 
		{
			pcl::Correspondence corr (neigh_indices[0], static_cast<int> (i), neigh_sqr_dists[0]);

			// check if new correspondence is better than any previous at this point
			bool found_better_result = false;
			for (int j = 0; j < object_world_corrs->size(); ++j)
			{
				// is the found neigbor the same one like in the correspondence j
				if (object_world_corrs->at(j).index_query == neigh_indices[0])
				{
					// do not add a new correspondence later
					found_better_result = true;
					// is the new distance smaller? (that means better)
					if (neigh_sqr_dists[0] < object_world_corrs->at(j).distance)
					{
						// replace correspondence with better one
						object_world_corrs->at(j) = corr;
					}
					else
						// break out of inner loop to save time and try next keypoint
						break;
				}
			}
			// if this is a new correspondence, add a new correspondence at the end
			if (!found_better_result)
				object_world_corrs->push_back (corr);	
		}
	}
	std::cout << "Correspondences found: " << object_world_corrs->size () << std::endl;


	//
	// all correspondences were found, now match the correspondences to the real object!
	//
	cluster(object_world_corrs);
}
void 
cloud_cb (const sensor_msgs::PointCloud2ConstPtr& input)
{
//	std::cout << "\n\n----------------Received point cloud!-----------------\n";

	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr downsampled (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_planar (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_objects (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_red (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_green (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_blue (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_yellow (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_concat_clusters (new pcl::PointCloud<pcl::PointXYZRGB>);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_concat_hulls (new pcl::PointCloud<pcl::PointXYZRGB>);
	sensor_msgs::PointCloud2 downsampled2, planar2, objects2, filtered2, red2, green2, blue2, yellow2, concat_clusters2, concat_hulls2;
	std::vector<pcl::PointCloud<pcl::PointXYZRGB>::Ptr> color_clouds, cluster_clouds, hull_clouds;
	std::vector<pcl::PointXYZRGB> labels;
	
//	fromROSMsg(*input, *cloud);
//	pub_input.publish(*input);

	// Downsample the input PointCloud
	pcl::VoxelGrid<sensor_msgs::PointCloud2> sor;
	sor.setInputCloud (input);
//	sor.setLeafSize (0.01, 0.01, 0.01);	//play around with leafsize (more samples, better resolution?)
	sor.setLeafSize (0.001, 0.001, 0.001);
	sor.filter (downsampled2);
	fromROSMsg(downsampled2,*downsampled);
	pub_downsampled.publish (downsampled2);	
	
	// Segment the largest planar component from the downsampled cloud
	pcl::SACSegmentation<pcl::PointXYZRGB> seg;
	pcl::ModelCoefficients::Ptr coeffs (new pcl::ModelCoefficients ());
	pcl::PointIndices::Ptr inliers (new pcl::PointIndices ());
	seg.setOptimizeCoefficients (true);
	seg.setModelType (pcl::SACMODEL_PLANE);
	seg.setMethodType (pcl::SAC_RANSAC);
	seg.setMaxIterations (100);
	seg.setDistanceThreshold (0.0085);
	seg.setInputCloud (downsampled);
	seg.segment (*inliers, *coeffs);
	
	// Extract the planar inliers from the input cloud
	pcl::ExtractIndices<pcl::PointXYZRGB> extract; 
	extract.setInputCloud (downsampled);
	extract.setIndices (inliers);
	extract.setNegative (false);
	extract.filter (*cloud_planar);
//	toROSMsg(*cloud_planar,planar2);
//	pub_planar.publish (planar2);

	// Remove the planar inliers, extract the rest
	extract.setNegative (true);
	extract.filter (*cloud_objects);
//	toROSMsg(*cloud_objects,objects2);
//	pub_objects.publish (objects2);

	// PassThrough Filter
	pcl::PassThrough<pcl::PointXYZRGB> pass;
	pass.setInputCloud (cloud_objects);
	pass.setFilterFieldName ("z");	//all duplos in pcd1
	pass.setFilterLimits (0.8, 1.0);
	pass.filter (*cloud_filtered);
	toROSMsg(*cloud_filtered,filtered2);
	pub_filtered.publish (filtered2);


//don't passthrough filter, does color filter work too? (cloud_red has many points in top right off the table)

	// Segment filtered PointCloud by color (red, green, blue, yellow)
	for (size_t i = 0 ; i < cloud_filtered->points.size () ; i++)
	{
		if ( (int(cloud_filtered->points[i].r) > 2*int(cloud_filtered->points[i].g)) && (cloud_filtered->points[i].r > cloud_filtered->points[i].b) )
			cloud_red->points.push_back(cloud_filtered->points[i]);
		if ( (cloud_filtered->points[i].g > cloud_filtered->points[i].r) && (cloud_filtered->points[i].g > cloud_filtered->points[i].b) )
			cloud_green->points.push_back(cloud_filtered->points[i]);
		if ( (cloud_filtered->points[i].b > cloud_filtered->points[i].r) && (cloud_filtered->points[i].b > cloud_filtered->points[i].g) )
			cloud_blue->points.push_back(cloud_filtered->points[i]);
		if ( (cloud_filtered->points[i].r > cloud_filtered->points[i].g) && (int(cloud_filtered->points[i].g) - int(cloud_filtered->points[i].b) > 30) )
			cloud_yellow->points.push_back(cloud_filtered->points[i]);
	}
	cloud_red->header.frame_id = "base_link";
	cloud_red->width = cloud_red->points.size ();
	cloud_red->height = 1;
	color_clouds.push_back(cloud_red);
	toROSMsg(*cloud_red,red2);
	pub_red.publish (red2);
	cloud_green->header.frame_id = "base_link";
	cloud_green->width = cloud_green->points.size ();
	cloud_green->height = 1;
	color_clouds.push_back(cloud_green);
	toROSMsg(*cloud_green,green2);
	pub_green.publish (green2);
	cloud_blue->header.frame_id = "base_link";
	cloud_blue->width = cloud_blue->points.size ();
	cloud_blue->height = 1;
	color_clouds.push_back(cloud_blue);
	toROSMsg(*cloud_blue,blue2);
	pub_blue.publish (blue2);
	cloud_yellow->header.frame_id = "base_link";
	cloud_yellow->width = cloud_yellow->points.size ();
	cloud_yellow->height = 1;
	color_clouds.push_back(cloud_yellow);
	toROSMsg(*cloud_yellow,yellow2);
	pub_yellow.publish (yellow2);
	

	// Extract Euclidian clusters from color-segmented PointClouds
	int j(0), num_red (0), num_green(0), num_blue(0), num_yellow(0);
	for (size_t cit = 0 ; cit < color_clouds.size() ; cit++)
	{
		pcl::KdTree<pcl::PointXYZRGB>::Ptr tree (new pcl::KdTreeFLANN<pcl::PointXYZRGB>);
		tree->setInputCloud (color_clouds[cit]);
		std::vector<pcl::PointIndices> cluster_indices;
		pcl::EuclideanClusterExtraction<pcl::PointXYZRGB> ec;
		ec.setClusterTolerance (0.0075); // 0.01
//		ec.setMinClusterSize (12);
//		ec.setMaxClusterSize (75);
		ec.setMinClusterSize (100);
		ec.setMaxClusterSize (4000);
		ec.setSearchMethod (tree);
		ec.setInputCloud (color_clouds[cit]);
		ec.extract (cluster_indices);

		for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it)
		{	
			pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_cluster (new pcl::PointCloud<pcl::PointXYZRGB>);
			for (std::vector<int>::const_iterator pit = it->indices.begin () ; pit != it->indices.end () ; pit++)
				cloud_cluster->points.push_back (color_clouds[cit]->points[*pit]);
			cloud_cluster->width = cloud_cluster->points.size ();
			cloud_cluster->height = 1;
			cloud_cluster->is_dense = true;
			cloud_cluster->header.frame_id = "base_link";
			cluster_clouds.push_back(cloud_cluster);
			labels.push_back(cloud_cluster->points[0]);
			if (cit == 0)	num_red++;
			if (cit == 1)	num_green++;
			if (cit == 2)	num_blue++;
			if (cit == 3)	num_yellow++;
			
			// Create ConvexHull for cluster (keep points on perimeter of cluster)
			pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_hull (new pcl::PointCloud<pcl::PointXYZRGB>);
  			pcl::ConvexHull<pcl::PointXYZRGB> chull;
  			chull.setInputCloud (cloud_cluster);
  			chull.reconstruct (*cloud_hull);
			cloud_hull->width = cloud_hull->points.size ();
			cloud_hull->height = 1;
			cloud_hull->header.frame_id = "base_link";
			hull_clouds.push_back(cloud_hull);

			j++;
		}
	}
	std::cout << "Number of RED clusters: " << num_red << std::endl;
	std::cout << "Number of GREEN clusters: " << num_green << std::endl;
	std::cout << "Number of BLUE clusters: " << num_blue << std::endl;
	std::cout << "Number of YELLOW clusters: " << num_yellow << std::endl;
	std::cout << "TOTAL number of clusters: " << j << std::endl;
	
	// Concatenate PointCloud clusters and convex hulls
	for (size_t k = 0 ; k < cluster_clouds.size() ; k++)
	{
		for (size_t l = 0 ; l < cluster_clouds[k]->size() ; l++)
		{
			cloud_concat_clusters->points.push_back(cluster_clouds[k]->points[l]);
			cloud_concat_hulls->points.push_back(hull_clouds[k]->points[l]);
		}
	}
	cloud_concat_clusters->header.frame_id = "base_link";
	cloud_concat_clusters->width = cloud_concat_clusters->points.size ();
	cloud_concat_clusters->height = 1;
	toROSMsg(*cloud_concat_clusters,concat_clusters2);
	pub_concat_clusters.publish (concat_clusters2);
	cloud_concat_hulls->header.frame_id = "base_link";
	cloud_concat_hulls->width = cloud_concat_hulls->points.size ();
	cloud_concat_hulls->height = 1;
	toROSMsg(*cloud_concat_hulls,concat_hulls2);
	pub_concat_hulls.publish (concat_hulls2);

	// Estimate the volume of each cluster
	double height, width;
	std::vector <double> heights, widths;
	std::vector <int>  height_ids, width_ids;
	for (size_t k = 0 ; k < cluster_clouds.size() ; k++)
	{
		// Calculate cluster height
		double tallest(0), shortest(1000), widest(0) ;
		for (size_t l = 0 ; l < cluster_clouds[k]->size() ; l++)
		{
			double point_to_plane_dist;
			point_to_plane_dist = 	   coeffs->values[0] * cluster_clouds[k]->points[l].x + 
						   coeffs->values[1] * cluster_clouds[k]->points[l].y +
						   coeffs->values[2] * cluster_clouds[k]->points[l].z + coeffs->values[3] / 
						   sqrt( pow(coeffs->values[0], 2) + pow(coeffs->values[1], 2)+ pow(coeffs->values[2], 2) );
			if (point_to_plane_dist < 0)		point_to_plane_dist = 0;
			if (point_to_plane_dist > tallest)	tallest = point_to_plane_dist;
			if (point_to_plane_dist < shortest)	shortest = point_to_plane_dist;
		}
		// Calculate cluster width
		for (size_t m = 0 ; m < hull_clouds[k]->size() ; m++)
		{	
			double parallel_vector_dist;
			for (size_t n = m ; n < hull_clouds[k]->size()-1 ; n++)
			{
				parallel_vector_dist = sqrt( pow(hull_clouds[k]->points[m].x - hull_clouds[k]->points[n+1].x,2) + 
  							     pow(hull_clouds[k]->points[m].y - hull_clouds[k]->points[n+1].y,2) +
							     pow(hull_clouds[k]->points[m].z - hull_clouds[k]->points[n+1].z,2) );
				if (parallel_vector_dist > widest)	widest = parallel_vector_dist;	
			}
		}
		// Classify block heights (error +/- .005m)
		height = (shortest < .01) ? tallest : tallest - shortest;	//check for stacked blocks	
		heights.push_back(height);
		if (height > .020 && height < .032)		height_ids.push_back(0);		//0: standing flat
		else if (height > .036 && height < .043)	height_ids.push_back(1);		//1: standing side
		else if (height > .064)				height_ids.push_back(2);		//2: standing long
		else						height_ids.push_back(-1);		//height not classified
		// Classify block widths (error +/- .005m)
		width = widest;
		widths.push_back(widest);
		if (width > .032-.005 && width < .0515+.005)		width_ids.push_back(1);		//1: short
		else if (width > .065-.005 && width < .0763+.005)	width_ids.push_back(2);		//2: medium
		else if (width > .1275-.005 && width < .1554+.005)	width_ids.push_back(4);		//4: long
		else						width_ids.push_back(-1);		//width not classified
	}

	// Classify block size using width information
	std::vector<int> block_ids, idx_1x1, idx_1x2, idx_1x4, idx_unclassified;
	int num_1x1(0), num_1x2(0), num_1x4(0), num_unclassified(0);
	for (size_t p = 0 ; p < width_ids.size() ; p++)	
	{
		if (width_ids[p] == 1)		
		{
			block_ids.push_back(1);		//block is 1x1
			idx_1x1.push_back(p);
			num_1x1++;
		}
		else if (width_ids[p] == 2)	
		{
			block_ids.push_back(2);         //block is 1x2
			idx_1x2.push_back(p);
			num_1x2++;
		}
		else if (width_ids[p] == 4)     
		{
			block_ids.push_back(4);         //block is 1x4
			idx_1x4.push_back(p);
			num_1x4++;
		}
		else	
		{
			block_ids.push_back(-1);	//block not classified
			idx_unclassified.push_back(p);
			num_unclassified++;
		}
	}

	// Determine Duplos of the same size
	std::cout << "\nThere are " << num_1x1 << " blocks of size 1x1 ";
	if (num_1x1>0)	std::cout << "(cluster index: ";
	for (size_t q = 0 ; q < idx_1x1.size() ; q++)
		std::cout << idx_1x1[q] << ", ";
	if (num_1x1>0)  std::cout << ")";
	std::cout << "\nThere are " << num_1x2 << " blocks of size 1x2 ";
        if (num_1x2>0)  std::cout << "(cluster index: ";
	for (size_t q = 0 ; q < idx_1x2.size() ; q++)
		std::cout << idx_1x2[q] << ", ";
	if (num_1x2>0)  std::cout << ")";
	std::cout << "\nThere are " << num_1x4 << " blocks of size 1x4 ";
        if (num_1x4>0)  std::cout << "(cluster index: ";
	for (size_t q = 0 ; q < idx_1x4.size() ; q++)
		std::cout << idx_1x4[q] << ", ";
	if (num_1x4>0)  std::cout << ")";
	std::cout << "\nThere are " << num_unclassified << " unclassified blocks ";
        if (num_unclassified>0)  std::cout << "(cluster index: ";
	for (size_t q = 0 ; q < idx_unclassified.size() ; q++)
		std::cout << idx_unclassified[q] << ", ";
	if (num_unclassified>0)  std::cout << ")";
	std::cout << "\n\n\n";
	return;
}