示例#1
0
void
pcl::VoxelGridLabel::applyFilter (PointCloud &output)
{
  // Has the input dataset been set already?
  if (!input_)
  {
    PCL_WARN ("[pcl::%s::applyFilter] No input dataset given!\n", getClassName ().c_str ());
    output.width = output.height = 0;
    output.points.clear ();
    return;
  }

  // Copy the header (and thus the frame_id) + allocate enough space for points
  output.height       = 1;                    // downsampling breaks the organized structure
  output.is_dense     = true;                 // we filter out invalid points

  Eigen::Vector4f min_p, max_p;
  // Get the minimum and maximum dimensions
  if (!filter_field_name_.empty ()) // If we don't want to process the entire cloud...
    getMinMax3D<pcl::PointXYZRGBL>(input_, filter_field_name_, static_cast<float> (filter_limit_min_), static_cast<float> (filter_limit_max_), min_p, max_p, filter_limit_negative_);
  else
    getMinMax3D<pcl::PointXYZRGBL>(*input_, min_p, max_p);

  // Compute the minimum and maximum bounding box values
  min_b_[0] = static_cast<int> (floor (min_p[0] * inverse_leaf_size_[0]));
  max_b_[0] = static_cast<int> (floor (max_p[0] * inverse_leaf_size_[0]));
  min_b_[1] = static_cast<int> (floor (min_p[1] * inverse_leaf_size_[1]));
  max_b_[1] = static_cast<int> (floor (max_p[1] * inverse_leaf_size_[1]));
  min_b_[2] = static_cast<int> (floor (min_p[2] * inverse_leaf_size_[2]));
  max_b_[2] = static_cast<int> (floor (max_p[2] * inverse_leaf_size_[2]));

  // Compute the number of divisions needed along all axis
  div_b_ = max_b_ - min_b_ + Eigen::Vector4i::Ones ();
  div_b_[3] = 0;

  // Set up the division multiplier
  divb_mul_ = Eigen::Vector4i (1, div_b_[0], div_b_[0] * div_b_[1], 0);

  int centroid_size = 4;
  if (downsample_all_data_)
    centroid_size = boost::mpl::size<FieldList>::value;

  // ---[ RGB special case
  std::vector<sensor_msgs::PointField> fields;
  int rgba_index = -1;
  rgba_index = pcl::getFieldIndex (*input_, "rgb", fields);
  if (rgba_index == -1)
    rgba_index = pcl::getFieldIndex (*input_, "rgba", fields);
  if (rgba_index >= 0)
  {
    rgba_index = fields[rgba_index].offset;
    centroid_size += 3;
  }

  // ---[ Label special case
  int label_index = -1;
  label_index = pcl::getFieldIndex (*input_, "label", fields);

  std::vector<cloud_point_index_idx> index_vector;
  index_vector.reserve(input_->points.size());

  // If we don't want to process the entire cloud, but rather filter points far away from the viewpoint first...
  if (!filter_field_name_.empty ())
  {
    // Get the distance field index
    std::vector<sensor_msgs::PointField> fields;
    int distance_idx = pcl::getFieldIndex (*input_, filter_field_name_, fields);
    if (distance_idx == -1)
      PCL_WARN ("[pcl::%s::applyFilter] Invalid filter field name. Index is %d.\n", getClassName ().c_str (), distance_idx);

    // First pass: go over all points and insert them into the index_vector vector
    // with calculated idx. Points with the same idx value will contribute to the
    // same point of resulting CloudPoint
    for (unsigned int cp = 0; cp < static_cast<unsigned int> (input_->points.size ()); ++cp)
    {
      if (!input_->is_dense)
        // Check if the point is invalid
        if (!pcl_isfinite (input_->points[cp].x) || 
            !pcl_isfinite (input_->points[cp].y) || 
            !pcl_isfinite (input_->points[cp].z))
          continue;

      // Get the distance value
      const uint8_t* pt_data = reinterpret_cast<const uint8_t*> (&input_->points[cp]);
      float distance_value = 0;
      memcpy (&distance_value, pt_data + fields[distance_idx].offset, sizeof (float));

      if (filter_limit_negative_)
      {
        // Use a threshold for cutting out points which inside the interval
        if ((distance_value < filter_limit_max_) && (distance_value > filter_limit_min_))
          continue;
      }
      else
      {
        // Use a threshold for cutting out points which are too close/far away
        if ((distance_value > filter_limit_max_) || (distance_value < filter_limit_min_))
          continue;
      }
      
      int ijk0 = static_cast<int> (floor (input_->points[cp].x * inverse_leaf_size_[0]) - min_b_[0]);
      int ijk1 = static_cast<int> (floor (input_->points[cp].y * inverse_leaf_size_[1]) - min_b_[1]);
      int ijk2 = static_cast<int> (floor (input_->points[cp].z * inverse_leaf_size_[2]) - min_b_[2]);

      // Compute the centroid leaf index
      int idx = ijk0 * divb_mul_[0] + ijk1 * divb_mul_[1] + ijk2 * divb_mul_[2];
      index_vector.push_back (cloud_point_index_idx (static_cast<unsigned int> (idx), cp));
    }
  }
  // No distance filtering, process all data
  else
  {
    // First pass: go over all points and insert them into the index_vector vector
    // with calculated idx. Points with the same idx value will contribute to the
    // same point of resulting CloudPoint
    for (unsigned int cp = 0; cp < static_cast<unsigned int> (input_->points.size ()); ++cp)
    {
      if (!input_->is_dense)
        // Check if the point is invalid
        if (!pcl_isfinite (input_->points[cp].x) || 
            !pcl_isfinite (input_->points[cp].y) || 
            !pcl_isfinite (input_->points[cp].z))
          continue;

      int ijk0 = static_cast<int> (floor (input_->points[cp].x * inverse_leaf_size_[0]) - min_b_[0]);
      int ijk1 = static_cast<int> (floor (input_->points[cp].y * inverse_leaf_size_[1]) - min_b_[1]);
      int ijk2 = static_cast<int> (floor (input_->points[cp].z * inverse_leaf_size_[2]) - min_b_[2]);

      // Compute the centroid leaf index
      int idx = ijk0 * divb_mul_[0] + ijk1 * divb_mul_[1] + ijk2 * divb_mul_[2];
      index_vector.push_back (cloud_point_index_idx (static_cast<unsigned int> (idx), cp));
    }
  }

  // Second pass: sort the index_vector vector using value representing target cell as index
  // in effect all points belonging to the same output cell will be next to each other
  std::sort (index_vector.begin (), index_vector.end (), std::less<cloud_point_index_idx> ());

  // Third pass: count output cells
  // we need to skip all the same, adjacenent idx values
  unsigned int total = 0;
  unsigned int index = 0;
  while (index < index_vector.size ()) 
  {
    unsigned int i = index + 1;
    while (i < index_vector.size () && index_vector[i].idx == index_vector[index].idx) 
      ++i;
    ++total;
    index = i;
  }

  // Fourth pass: compute centroids, insert them into their final position
  output.points.resize (total);
  if (save_leaf_layout_)
  {
    try
    { 
      // Resizing won't reset old elements to -1.  If leaf_layout_ has been used previously, it needs to be re-initialized to -1
      uint32_t new_layout_size = div_b_[0]*div_b_[1]*div_b_[2];
      //This is the number of elements that need to be re-initialized to -1
      uint32_t reinit_size = std::min (static_cast<unsigned int> (new_layout_size), static_cast<unsigned int> (leaf_layout_.size()));
      for (uint32_t i = 0; i < reinit_size; i++)
      {
        leaf_layout_[i] = -1;
      }        
      leaf_layout_.resize (new_layout_size, -1);           
    }
    catch (std::bad_alloc&)
    {
      throw PCLException("VoxelGrid bin size is too low; impossible to allocate memory for layout", 
        "voxel_grid.hpp", "applyFilter");	
    }
    catch (std::length_error&)
    {
      throw PCLException("VoxelGrid bin size is too low; impossible to allocate memory for layout", 
        "voxel_grid.hpp", "applyFilter");	
    }
  }
  
  index = 0;
  Eigen::VectorXf centroid = Eigen::VectorXf::Zero (centroid_size);
  Eigen::VectorXf temporary = Eigen::VectorXf::Zero (centroid_size);

  for (unsigned int cp = 0; cp < index_vector.size ();)
  {
    std::map<int, int> labels;
    
    // calculate centroid - sum values from all input points, that have the same idx value in index_vector array
    if (!downsample_all_data_) 
    {
      centroid[0] = input_->points[index_vector[cp].cloud_point_index].x;
      centroid[1] = input_->points[index_vector[cp].cloud_point_index].y;
      centroid[2] = input_->points[index_vector[cp].cloud_point_index].z;
    }
    else 
    {
      // ---[ RGB special case
      if (rgba_index >= 0)
      {
        // Fill r/g/b data, assuming that the order is BGRA
        pcl::RGB rgb;
        memcpy (&rgb, reinterpret_cast<const char*> (&input_->points[index_vector[cp].cloud_point_index]) + rgba_index, sizeof (RGB));
        centroid[centroid_size-3] = rgb.r;
        centroid[centroid_size-2] = rgb.g;
        centroid[centroid_size-1] = rgb.b;
      }

      // ---[ Label special case
      if (label_index >= 0)
      {
        // store the label in a map data structure
        uint32_t label = input_->points[index_vector[cp].cloud_point_index].label;
        std::map<int, int>::iterator it = labels.find (label);
        if (it == labels.end ())
          labels.insert (labels.begin (), std::pair<int, int> (label, 1));
        else
          it->second = it->second++;
      }

      pcl::for_each_type <FieldList> (NdCopyPointEigenFunctor <pcl::PointXYZRGBL> (input_->points[index_vector[cp].cloud_point_index], centroid));
    }

    unsigned int i = cp + 1;
    while (i < index_vector.size () && index_vector[i].idx == index_vector[cp].idx) 
    {
      if (!downsample_all_data_) 
      {
        centroid[0] += input_->points[index_vector[i].cloud_point_index].x;
        centroid[1] += input_->points[index_vector[i].cloud_point_index].y;
        centroid[2] += input_->points[index_vector[i].cloud_point_index].z;
      }
      else 
      {
        // ---[ RGB special case
        if (rgba_index >= 0)
        {
          // Fill r/g/b data, assuming that the order is BGRA
          pcl::RGB rgb;
          memcpy (&rgb, reinterpret_cast<const char*> (&input_->points[index_vector[i].cloud_point_index]) + rgba_index, sizeof (RGB));
          temporary[centroid_size-3] = rgb.r;
          temporary[centroid_size-2] = rgb.g;
          temporary[centroid_size-1] = rgb.b;
        }
        pcl::for_each_type <FieldList> (NdCopyPointEigenFunctor <pcl::PointXYZRGBL> (input_->points[index_vector[i].cloud_point_index], temporary));
        centroid += temporary;
      }
      ++i;
    }

    // index is centroid final position in resulting PointCloud
    if (save_leaf_layout_)
      leaf_layout_[index_vector[cp].idx] = index;

    centroid /= static_cast<float> (i - cp);

    // store centroid
    // Do we need to process all the fields?
    if (!downsample_all_data_) 
    {
      output.points[index].x = centroid[0];
      output.points[index].y = centroid[1];
      output.points[index].z = centroid[2];
    }
    else 
    {
      pcl::for_each_type<FieldList> (pcl::NdCopyEigenPointFunctor <pcl::PointXYZRGBL> (centroid, output.points[index]));
      // ---[ RGB special case
      if (rgba_index >= 0) 
      {
        // pack r/g/b into rgb
        float r = centroid[centroid_size-3], g = centroid[centroid_size-2], b = centroid[centroid_size-1];
        int rgb = (static_cast<int> (r) << 16) | (static_cast<int> (g) << 8) | static_cast<int> (b);
        memcpy (reinterpret_cast<char*> (&output.points[index]) + rgba_index, &rgb, sizeof (float));
      }

      if (label_index >= 0)
      {
        // find the label with the highest occurrence
        std::map<int, int>::iterator it = labels.begin ();
        int n_occurrence = it->second;
        int label = it->first;
        if (it != labels.end ())
        {
          for (it = labels.begin (); it != labels.end (); it++)
          {
            if (n_occurrence < it->second)
            {
              n_occurrence = it->second;
              label = it->first;
            }
          }
        }
        output.points[index].label = label;
      }
    }
    cp = i;
    ++index;
  }
  output.width = static_cast<uint32_t> (output.points.size ());
}
示例#2
0
void
pcl::VoxelGrid<sensor_msgs::PointCloud2>::applyFilter (PointCloud2 &output)
{
    // If fields x/y/z are not present, we cannot downsample
    if (x_idx_ == -1 || y_idx_ == -1 || z_idx_ == -1)
    {
        PCL_ERROR ("[pcl::%s::applyFilter] Input dataset doesn't have x-y-z coordinates!\n", getClassName ().c_str ());
        output.width = output.height = 0;
        output.data.clear ();
        return;
    }
    int nr_points  = input_->width * input_->height;

    // Copy the header (and thus the frame_id) + allocate enough space for points
    output.height         = 1;                    // downsampling breaks the organized structure
    if (downsample_all_data_)
    {
        output.fields       = input_->fields;
        output.point_step   = input_->point_step;
    }
    else
    {
        output.fields.resize (4);

        output.fields[0] = input_->fields[x_idx_];
        output.fields[0].offset = 0;

        output.fields[1] = input_->fields[y_idx_];
        output.fields[1].offset = 4;

        output.fields[2] = input_->fields[z_idx_];
        output.fields[2].offset = 8;

        output.fields[3].name = "rgba";
        output.fields[3].offset = 12;
        output.fields[3].datatype = sensor_msgs::PointField::FLOAT32;

        output.point_step = 16;
    }
    output.is_bigendian = input_->is_bigendian;
    output.row_step     = input_->row_step;
    output.is_dense     = true;                 // we filter out invalid points

    Eigen::Vector4f min_p, max_p;
    // Get the minimum and maximum dimensions
    if (!filter_field_name_.empty ()) // If we don't want to process the entire cloud...
        getMinMax3D (input_, x_idx_, y_idx_, z_idx_, filter_field_name_,
                     static_cast<float> (filter_limit_min_),
                     static_cast<float> (filter_limit_max_), min_p, max_p, filter_limit_negative_);
    else
        getMinMax3D (input_, x_idx_, y_idx_, z_idx_, min_p, max_p);

    // Compute the minimum and maximum bounding box values
    min_b_[0] = static_cast<int> (floor (min_p[0] * inverse_leaf_size_[0]));
    max_b_[0] = static_cast<int> (floor (max_p[0] * inverse_leaf_size_[0]));
    min_b_[1] = static_cast<int> (floor (min_p[1] * inverse_leaf_size_[1]));
    max_b_[1] = static_cast<int> (floor (max_p[1] * inverse_leaf_size_[1]));
    min_b_[2] = static_cast<int> (floor (min_p[2] * inverse_leaf_size_[2]));
    max_b_[2] = static_cast<int> (floor (max_p[2] * inverse_leaf_size_[2]));

    // Compute the number of divisions needed along all axis
    div_b_ = max_b_ - min_b_ + Eigen::Vector4i::Ones ();
    div_b_[3] = 0;

    std::vector<cloud_point_index_idx> index_vector;
    index_vector.reserve (nr_points);

    // Create the first xyz_offset, and set up the division multiplier
    Eigen::Array4i xyz_offset (input_->fields[x_idx_].offset,
                               input_->fields[y_idx_].offset,
                               input_->fields[z_idx_].offset,
                               0);
    divb_mul_ = Eigen::Vector4i (1, div_b_[0], div_b_[0] * div_b_[1], 0);
    Eigen::Vector4f pt  = Eigen::Vector4f::Zero ();

    int centroid_size = 4;
    if (downsample_all_data_)
        centroid_size = static_cast<int> (input_->fields.size ());

    int rgba_index = -1;

    // ---[ RGB special case
    // if the data contains "rgba" or "rgb", add an extra field for r/g/b in centroid
    for (int d = 0; d < centroid_size; ++d)
    {
        if (input_->fields[d].name == std::string ("rgba") || input_->fields[d].name == std::string ("rgb"))
        {
            rgba_index = d;
            centroid_size += 3;
            break;
        }
    }

    // If we don't want to process the entire cloud, but rather filter points far away from the viewpoint first...
    if (!filter_field_name_.empty ())
    {
        // Get the distance field index
        int distance_idx = pcl::getFieldIndex (*input_, filter_field_name_);

        // @todo fixme
        if (input_->fields[distance_idx].datatype != sensor_msgs::PointField::FLOAT32)
        {
            PCL_ERROR ("[pcl::%s::applyFilter] Distance filtering requested, but distances are not float/double in the dataset! Only FLOAT32/FLOAT64 distances are supported right now.\n", getClassName ().c_str ());
            output.width = output.height = 0;
            output.data.clear ();
            return;
        }

        // First pass: go over all points and insert them into the index_vector vector
        // with calculated idx. Points with the same idx value will contribute to the
        // same point of resulting CloudPoint
        float distance_value = 0;
        for (int cp = 0; cp < nr_points; ++cp)
        {
            int point_offset = cp * input_->point_step;
            // Get the distance value
            memcpy (&distance_value, &input_->data[point_offset + input_->fields[distance_idx].offset], sizeof (float));

            if (filter_limit_negative_)
            {
                // Use a threshold for cutting out points which inside the interval
                if (distance_value < filter_limit_max_ && distance_value > filter_limit_min_)
                {
                    xyz_offset += input_->point_step;
                    continue;
                }
            }
            else
            {
                // Use a threshold for cutting out points which are too close/far away
                if (distance_value > filter_limit_max_ || distance_value < filter_limit_min_)
                {
                    xyz_offset += input_->point_step;
                    continue;
                }
            }

            // Unoptimized memcpys: assume fields x, y, z are in random order
            memcpy (&pt[0], &input_->data[xyz_offset[0]], sizeof (float));
            memcpy (&pt[1], &input_->data[xyz_offset[1]], sizeof (float));
            memcpy (&pt[2], &input_->data[xyz_offset[2]], sizeof (float));

            // Check if the point is invalid
            if (!pcl_isfinite (pt[0]) ||
                    !pcl_isfinite (pt[1]) ||
                    !pcl_isfinite (pt[2]))
            {
                xyz_offset += input_->point_step;
                continue;
            }

            int ijk0 = static_cast<int> (floor (pt[0] * inverse_leaf_size_[0]) - min_b_[0]);
            int ijk1 = static_cast<int> (floor (pt[1] * inverse_leaf_size_[1]) - min_b_[1]);
            int ijk2 = static_cast<int> (floor (pt[2] * inverse_leaf_size_[2]) - min_b_[2]);
            // Compute the centroid leaf index
            int idx = ijk0 * divb_mul_[0] + ijk1 * divb_mul_[1] + ijk2 * divb_mul_[2];
            index_vector.push_back (cloud_point_index_idx (idx, cp));

            xyz_offset += input_->point_step;
        }
    }
    // No distance filtering, process all data
    else
    {
        // First pass: go over all points and insert them into the right leaf
        for (int cp = 0; cp < nr_points; ++cp)
        {
            // Unoptimized memcpys: assume fields x, y, z are in random order
            memcpy (&pt[0], &input_->data[xyz_offset[0]], sizeof (float));
            memcpy (&pt[1], &input_->data[xyz_offset[1]], sizeof (float));
            memcpy (&pt[2], &input_->data[xyz_offset[2]], sizeof (float));

            // Check if the point is invalid
            if (!pcl_isfinite (pt[0]) ||
                    !pcl_isfinite (pt[1]) ||
                    !pcl_isfinite (pt[2]))
            {
                xyz_offset += input_->point_step;
                continue;
            }

            int ijk0 = static_cast<int> (floor (pt[0] * inverse_leaf_size_[0]) - min_b_[0]);
            int ijk1 = static_cast<int> (floor (pt[1] * inverse_leaf_size_[1]) - min_b_[1]);
            int ijk2 = static_cast<int> (floor (pt[2] * inverse_leaf_size_[2]) - min_b_[2]);
            // Compute the centroid leaf index
            int idx = ijk0 * divb_mul_[0] + ijk1 * divb_mul_[1] + ijk2 * divb_mul_[2];
            index_vector.push_back (cloud_point_index_idx (idx, cp));
            xyz_offset += input_->point_step;
        }
    }

    // Second pass: sort the index_vector vector using value representing target cell as index
    // in effect all points belonging to the same output cell will be next to each other
    std::sort (index_vector.begin (), index_vector.end (), std::less<cloud_point_index_idx> ());

    // Third pass: count output cells
    // we need to skip all the same, adjacenent idx values
    unsigned int total=0;
    unsigned int index=0;
    while (index < index_vector.size ())
    {
        unsigned int i = index + 1;
        while (i < index_vector.size () && index_vector[i].idx == index_vector[index].idx)
            ++i;
        ++total;
        index = i;
    }

    // Fourth pass: compute centroids, insert them into their final position
    output.width = total;
    output.row_step = output.point_step * output.width;
    output.data.resize (output.width * output.point_step);

    if (save_leaf_layout_)
    {
        try
        {
            leaf_layout_.resize (div_b_[0]*div_b_[1]*div_b_[2], -1);
        }
        catch (std::bad_alloc&)
        {
            throw PCLException("VoxelGrid bin size is too low; impossible to allocate memory for layout",
                               "voxel_grid.cpp", "applyFilter");
        }
        catch (std::length_error&)
        {
            throw PCLException("VoxelGrid bin size is too low; impossible to allocate memory for layout",
                               "voxel_grid.cpp", "applyFilter");
        }
    }

    // If we downsample each field, the {x,y,z}_idx_ offsets should correspond in input_ and output
    if (downsample_all_data_)
        xyz_offset = Eigen::Array4i (output.fields[x_idx_].offset,
                                     output.fields[y_idx_].offset,
                                     output.fields[z_idx_].offset,
                                     0);
    else
        // If not, we must have created a new xyzw cloud
        xyz_offset = Eigen::Array4i (0, 4, 8, 12);

    index=0;
    Eigen::VectorXf centroid = Eigen::VectorXf::Zero (centroid_size);
    Eigen::VectorXf temporary = Eigen::VectorXf::Zero (centroid_size);

    for (unsigned int cp = 0; cp < index_vector.size ();)
    {
        int point_offset = index_vector[cp].cloud_point_index * input_->point_step;
        // Do we need to process all the fields?
        if (!downsample_all_data_)
        {
            memcpy (&pt[0], &input_->data[point_offset+input_->fields[x_idx_].offset], sizeof (float));
            memcpy (&pt[1], &input_->data[point_offset+input_->fields[y_idx_].offset], sizeof (float));
            memcpy (&pt[2], &input_->data[point_offset+input_->fields[z_idx_].offset], sizeof (float));
            centroid[0] = pt[0];
            centroid[1] = pt[1];
            centroid[2] = pt[2];
            centroid[3] = 0;
        }
        else
        {
            // ---[ RGB special case
            // fill extra r/g/b centroid field
            if (rgba_index >= 0)
            {
                pcl::RGB rgb;
                memcpy (&rgb, &input_->data[point_offset + input_->fields[rgba_index].offset], sizeof (RGB));
                centroid[centroid_size-3] = rgb.r;
                centroid[centroid_size-2] = rgb.g;
                centroid[centroid_size-1] = rgb.b;
            }
            // Copy all the fields
            for (unsigned int d = 0; d < input_->fields.size (); ++d)
                memcpy (&centroid[d], &input_->data[point_offset + input_->fields[d].offset], field_sizes_[d]);
        }

        unsigned int i = cp + 1;
        while (i < index_vector.size () && index_vector[i].idx == index_vector[cp].idx)
        {
            int point_offset = index_vector[i].cloud_point_index * input_->point_step;
            if (!downsample_all_data_)
            {
                memcpy (&pt[0], &input_->data[point_offset+input_->fields[x_idx_].offset], sizeof (float));
                memcpy (&pt[1], &input_->data[point_offset+input_->fields[y_idx_].offset], sizeof (float));
                memcpy (&pt[2], &input_->data[point_offset+input_->fields[z_idx_].offset], sizeof (float));
                centroid[0] += pt[0];
                centroid[1] += pt[1];
                centroid[2] += pt[2];
            }
            else
            {
                // ---[ RGB special case
                // fill extra r/g/b centroid field
                if (rgba_index >= 0)
                {
                    pcl::RGB rgb;
                    memcpy (&rgb, &input_->data[point_offset + input_->fields[rgba_index].offset], sizeof (RGB));
                    temporary[centroid_size-3] = rgb.r;
                    temporary[centroid_size-2] = rgb.g;
                    temporary[centroid_size-1] = rgb.b;
                }
                // Copy all the fields
                for (unsigned int d = 0; d < input_->fields.size (); ++d)
                    memcpy (&temporary[d], &input_->data[point_offset + input_->fields[d].offset], field_sizes_[d]);
                centroid+=temporary;
            }
            ++i;
        }

        // Save leaf layout information for fast access to cells relative to current position
        if (save_leaf_layout_)
            leaf_layout_[index_vector[cp].idx] = index;

        // Normalize the centroid
        centroid /= static_cast<float> (i - cp);

        // Do we need to process all the fields?
        if (!downsample_all_data_)
        {
            // Copy the data
            memcpy (&output.data[xyz_offset[0]], &centroid[0], sizeof (float));
            memcpy (&output.data[xyz_offset[1]], &centroid[1], sizeof (float));
            memcpy (&output.data[xyz_offset[2]], &centroid[2], sizeof (float));
            xyz_offset += output.point_step;
        }
        else
        {
            int point_offset = index * output.point_step;
            // Copy all the fields
            for (size_t d = 0; d < output.fields.size (); ++d)
                memcpy (&output.data[point_offset + output.fields[d].offset], &centroid[d], field_sizes_[d]);

            // ---[ RGB special case
            // full extra r/g/b centroid field
            if (rgba_index >= 0)
            {
                float r = centroid[centroid_size-3], g = centroid[centroid_size-2], b = centroid[centroid_size-1];
                int rgb = (static_cast<int> (r) << 16) | (static_cast<int> (g) << 8) | static_cast<int> (b);
                memcpy (&output.data[point_offset + output.fields[rgba_index].offset], &rgb, sizeof (float));
            }
        }
        cp = i;
        ++index;
    }
}