bool CameraLidarTransformer::transformServiceCallback(
    navigator_msgs::CameraToLidarTransform::Request &req,
    navigator_msgs::CameraToLidarTransform::Response &res) {
  visualization_msgs::MarkerArray markers;
  sensor_msgs::PointCloud2ConstPtr scloud =
      lidarCache.getElemAfterTime(req.header.stamp);
  if (!scloud) {
    res.success = false;
    res.error =  navigator_msgs::CameraToLidarTransform::Response::CLOUD_NOT_FOUND;
    return true;
  }
  geometry_msgs::TransformStamped transform =
      tfBuffer.lookupTransform(req.header.frame_id, "velodyne", req.header.stamp);
  sensor_msgs::PointCloud2 cloud_transformed;
  tf2::doTransform(*scloud, cloud_transformed, transform);
#ifdef DO_ROS_DEBUG
  cv::Mat debug_image(camera_info.height, camera_info.width, CV_8UC3,
                      cv::Scalar(0));
#endif
  Eigen::Matrix3d matrixFindPlaneA;
  matrixFindPlaneA << 0, 0, 0, 0, 0, 0, 0, 0, 0;
  Eigen::Vector3d matrixFindPlaneB(0, 0, 0);

  cv::circle(debug_image, cv::Point(req.point.x, req.point.y), 8,
             cv::Scalar(255, 0, 0), -1);
  //Tracks the closest lidar point to the requested camera point
  double minDistance = std::numeric_limits<double>::max();
  for (auto ii = 0, jj = 0; ii < cloud_transformed.width;
       ++ii, jj += cloud_transformed.point_step) {
    floatConverter x, y, z, i;
    for (int kk = 0; kk < 4; ++kk) {
      x.data[kk] = cloud_transformed.data[jj + kk];
      y.data[kk] = cloud_transformed.data[jj + 4 + kk];
      z.data[kk] = cloud_transformed.data[jj + 8 + kk];
      i.data[kk] = cloud_transformed.data[jj + 16 + kk];
    }
    cam_model.fromCameraInfo(camera_info);
    if (int(z.f) < 0 || int(z.f) > 30) continue;
    cv::Point2d point = cam_model.project3dToPixel(cv::Point3d(x.f, y.f, z.f));

    if (int(point.x) > 0 && int(point.x) < camera_info.width &&
        int(point.y) > 0 && int(point.y) < camera_info.height) {
#ifdef DO_ROS_DEBUG
      visualization_msgs::Marker marker_point;
      marker_point.header = req.header;
      marker_point.header.seq = 0;
      marker_point.header.frame_id = req.header.frame_id;
      marker_point.id = (int)ii;
      marker_point.type = visualization_msgs::Marker::CUBE;
      marker_point.pose.position.x = x.f;
      marker_point.pose.position.y = y.f;
      marker_point.pose.position.z = z.f;
      marker_point.scale.x = 1;
      marker_point.scale.y = 0.1;
      marker_point.scale.z = 0.1;
      marker_point.color.a = 1.0;
      marker_point.color.r = 0.0;
      marker_point.color.g = 1.0;
      marker_point.color.b = 0.0;
      //marker_point.lifetime = ros::Duration(0.5);
      markers.markers.push_back(marker_point);

      // Draw
      if (int(point.x - 20) > 0 && int(point.x + 20) < camera_info.width &&
          int(point.y - 20) > 0 && int(point.y + 20) < camera_info.height) {
        cv::circle(debug_image, point, 3,
                   cv::Scalar(0, 0, 255 * std::min(1.0, z.f / 20.0)), -1);
      }
#endif
      double distance = sqrt(pow(point.x - req.point.x ,2) + pow(point.y - req.point.y,2) ); //Distance (2D) from request point to projected lidar point
      if (distance < req.tolerance) {
#ifdef DO_ROS_DEBUG
        if (int(point.x - 20) > 0 && int(point.x + 20) < camera_info.width &&
            int(point.y - 20) > 0 && int(point.y + 20) < camera_info.height) {
          cv::circle(debug_image, point, 3, cv::Scalar(0, 255, 0), -1);
        }
#endif
        matrixFindPlaneA(0, 0) += x.f * x.f;
        matrixFindPlaneA(1, 0) += y.f * x.f;
        matrixFindPlaneA(2, 0) += x.f;
        matrixFindPlaneA(0, 1) += x.f * y.f;
        matrixFindPlaneA(1, 1) += y.f * y.f;
        matrixFindPlaneA(2, 1) += y.f;
        matrixFindPlaneA(0, 2) += x.f;
        matrixFindPlaneA(1, 2) += y.f;
        matrixFindPlaneA(2, 2) += 1;
        matrixFindPlaneB(0, 0) -= x.f * z.f;
        matrixFindPlaneB(1, 0) -= y.f * z.f;
        matrixFindPlaneB(2, 0) -= z.f;
        
        geometry_msgs::Point geo_point;
        geo_point.x = x.f;
        geo_point.y = y.f;
        geo_point.z = z.f;
        if (distance < minDistance)
        {
          res.closest = geo_point;
          minDistance = distance;
        }
        res.transformed.push_back(geo_point);
      }
    }
  }
  if (res.transformed.size() < 1) {
    res.success = false;
    res.error = navigator_msgs::CameraToLidarTransform::Response::NO_POINTS_FOUND;
    return true;
  }
  
  //Filter out lidar points behind the plane (I don't think this will work, no good way to do this)
  // ~double min_z = (*std::min_element(res.transformed.begin(),res.transformed.end(), [=] (const geometry_msgs::Point& a,const geometry_msgs::Point& b) {
    // ~return a.z < b.z && a.z > MIN_Z;
  // ~})).z;
  // ~res.transformed.erase(std::remove_if(res.transformed.begin(),res.transformed.end(),[min_z] (const geometry_msgs::Point& p) {
    // ~return (p.z - min_z) < MAX_Z_ERROR; 
  // ~}),res.transformed.end());

  res.distance = res.closest.z;
  Eigen::Vector3d normalvec = matrixFindPlaneA.colPivHouseholderQr()
                                  .solve(matrixFindPlaneB)
                                  .normalized();
  geometry_msgs::Vector3 normalvec_ros;
  normalvec_ros.x = normalvec(0, 0);
  normalvec_ros.y = normalvec(1, 0);
  normalvec_ros.z = normalvec(2, 0);
  res.normal = normalvec_ros;
  std::cout << "Plane solution: " << normalvec << std::endl;

#ifdef DO_ROS_DEBUG
  //Create marker for normal
  visualization_msgs::Marker marker_normal;
  marker_normal.header = req.header;
  marker_normal.header.seq = 0;
  marker_normal.header.frame_id =  req.header.frame_id;
  marker_normal.id = 3000;
  marker_normal.type = visualization_msgs::Marker::ARROW;

  geometry_msgs::Point sdp_normalvec_ros;
  sdp_normalvec_ros.x = normalvec_ros.x + res.transformed[0].x;
  sdp_normalvec_ros.y = normalvec_ros.y + res.transformed[0].y;
  sdp_normalvec_ros.z = normalvec_ros.z + res.transformed[0].z;
  marker_normal.points.push_back(res.closest);
  marker_normal.points.push_back(sdp_normalvec_ros);

  marker_normal.scale.x = 0.1;
  marker_normal.scale.y = 0.5;
  marker_normal.scale.z = 0.5;
  marker_normal.color.a = 1.0;
  marker_normal.color.r = 1.0;
  marker_normal.color.g = 0.0;
  marker_normal.color.b = 0.0;
  markers.markers.push_back(marker_normal);
  
  //Publish 3D debug market
  pubMarkers.publish(markers);
  
  //Publish debug image
  cv_bridge::CvImage ros_debug_image;
  ros_debug_image.encoding = "bgr8";
  ros_debug_image.image = debug_image.clone();
  points_debug_publisher.publish(ros_debug_image.toImageMsg());
#endif
  res.success = true;
  return true;
}
  void patchArrayCallback(const samplereturn_msgs::PatchArrayConstPtr& msg)
  {
    samplereturn_msgs::NamedPointArray points_out;
    points_out.header = msg->header;
    bool enable_debug = (pub_debug_image.getNumSubscribers() != 0);
    cv::Mat debug_image;
    if (enable_debug) {
      debug_image = cv::Mat::zeros(msg->cam_info.height,
          msg->cam_info.width, CV_8UC3);
    }
    for (size_t i = 0; i < msg->patch_array.size(); i++) {
      cv_bridge::CvImageConstPtr cv_ptr_mask, cv_ptr_img;
      sensor_msgs::ImageConstPtr msg_mask(&(msg->patch_array[i].mask), boost::serialization::null_deleter());
      sensor_msgs::ImageConstPtr msg_img(&(msg->patch_array[i].image), boost::serialization::null_deleter());
      try {
        cv_ptr_mask = cv_bridge::toCvShare(msg_mask, "mono8");
      }
      catch (cv_bridge::Exception& e) {
        ROS_ERROR("cv_bridge mask exception: %s", e.what());
      }
      try {
        cv_ptr_img = cv_bridge::toCvShare(msg_img, "rgb8");
      }
      catch (cv_bridge::Exception& e) {
        ROS_ERROR("cv_bridge image exception: %s", e.what());
      }
      if (enable_debug) {
        cv_ptr_img->image.copyTo(debug_image(cv::Rect(msg->patch_array[i].image_roi.x_offset,
                msg->patch_array[i].image_roi.y_offset,
                msg->patch_array[i].image_roi.width,
                msg->patch_array[i].image_roi.height)));
      }

      // check foreground/background distance
      samplereturn::ColorModel cm(cv_ptr_img->image, cv_ptr_mask->image);
      samplereturn::HueHistogram hh_inner = cm.getInnerHueHistogram(config_.min_color_saturation, config_.low_saturation_limit, config_.high_saturation_limit);
      samplereturn::HueHistogram hh_outer = cm.getOuterHueHistogram(config_.min_color_saturation, config_.low_saturation_limit, config_.high_saturation_limit);
      double background_distance = hh_inner.distance(hh_outer);
    
      // compare background to fence color
      std::vector<std::tuple<double, double>> edges;
      edges.push_back(std::make_tuple(0, config_.max_fence_hue));
      samplereturn::HueHistogram hh_fence_measured = cm.getOuterHueHistogram(config_.min_fence_color_saturation,0.0, config_.fence_high_saturation_limit);
      samplereturn::HueHistogramExemplar hh_fence_exemplar = samplereturn::ColorModel::getColoredSampleModel(edges, 0.0, config_.fence_high_saturation_limit);
      double fence_distance = hh_fence_exemplar.distance(hh_fence_measured);

      // Check inner hist against targets, either well-saturated in the specified
      // hue range, or unsaturated with a high value (metal and pre-cached)
      edges.clear();
      edges.push_back(std::make_tuple(0, config_.max_target_hue));
      edges.push_back(std::make_tuple(config_.min_target_hue, 180));
      samplereturn::HueHistogramExemplar hh_colored_sample = samplereturn::ColorModel::getColoredSampleModel(edges, config_.low_saturation_limit, config_.high_saturation_limit);
      samplereturn::HueHistogramExemplar hh_value_sample = samplereturn::ColorModel::getValuedSampleModel(config_.low_saturation_limit, config_.high_saturation_limit);
      double hue_exemplar_distance = hh_colored_sample.distance(hh_inner);
      double value_exemplar_distance = hh_value_sample.distance(hh_inner);

      if (enable_debug) {
          int x,y,h;
          x = msg->patch_array[i].image_roi.x_offset;
          y = msg->patch_array[i].image_roi.y_offset;
          //w = msg->patch_array[i].image_roi.width;
          h = msg->patch_array[i].image_roi.height;
          hh_inner.draw_histogram(debug_image, x, y, config_.debug_font_scale);
          hh_outer.draw_histogram(debug_image, x, y + h + 25*config_.debug_font_scale, config_.debug_font_scale);
          char edist[100];
          snprintf(edist, 100, "b:%3.2f h: %3.2f v:%3.2f f:%3.2f", background_distance, hue_exemplar_distance, value_exemplar_distance, fence_distance);
          cv::putText(debug_image,edist,cv::Point2d(x+70, y + h + 50*config_.debug_font_scale),
                  cv::FONT_HERSHEY_SIMPLEX, config_.debug_font_scale, cv::Scalar(255,0,0),4,cv::LINE_8);
      }

      // is a sample if high enough bg/fg distance, high enough fence distance,
      // and close to one of the exemplars
      bool is_sample = ((background_distance>config_.min_inner_outer_distance) &&
                        (fence_distance>config_.min_fence_distance) &&
                        ((hue_exemplar_distance<config_.max_exemplar_distance) ||
                         (value_exemplar_distance<config_.max_exemplar_distance)));

      if (!is_sample)
      {
          if(enable_debug)
          {
              // Nix region
              int x,y,w,h;
              x = msg->patch_array[i].image_roi.x_offset;
              y = msg->patch_array[i].image_roi.y_offset;
              w = msg->patch_array[i].image_roi.width;
              h = msg->patch_array[i].image_roi.height;
              cv::line(debug_image,
                      cv::Point2f(x, y),
                      cv::Point2f(x + w, y + h), cv::Scalar(255,0,0), 20);
              cv::line(debug_image,
                      cv::Point2f(x + w, y),
                      cv::Point2f(x, y + h), cv::Scalar(255,0,0), 20);
          }
          continue;
      }

      samplereturn_msgs::NamedPoint np_msg;
      np_msg.header.stamp = msg->header.stamp;
      np_msg.header.frame_id = msg->patch_array[i].world_point.header.frame_id;
      np_msg.point = msg->patch_array[i].world_point.point;
      hh_inner.to_msg(&np_msg.model.hue);
      np_msg.sensor_frame = msg->header.frame_id;
      np_msg.name = (hue_exemplar_distance < value_exemplar_distance) ?
          std::string("Hue object") : std::string("Value object");

      if(is_sample && config_.compute_grip_angle)
      {
          cv::RotatedRect griprect;
          if(samplereturn::computeGripAngle(cv_ptr_mask->image, &griprect, &np_msg.grip_angle) &&
                  enable_debug)
          {
              griprect.center += cv::Point2f(
                      msg->patch_array[i].image_roi.x_offset,
                      msg->patch_array[i].image_roi.y_offset);
              samplereturn::drawGripRect(debug_image, griprect);
          }
      }

      points_out.points.push_back(np_msg);
      
    }
    pub_named_points.publish(points_out);
    if (enable_debug) {
      sensor_msgs::ImagePtr debug_image_msg =
        cv_bridge::CvImage(msg->header,"rgb8",debug_image).toImageMsg();
      pub_debug_image.publish(debug_image_msg);
    }
  }
  void patchArrayCallback(const samplereturn_msgs::PatchArrayConstPtr& msg)
  {
    samplereturn_msgs::NamedPointArray points_out;
    points_out.header = msg->header;
    bool enable_debug = (pub_debug_image.getNumSubscribers() != 0);
    cv::Mat debug_image;
    if (enable_debug) {
      debug_image = cv::Mat::zeros(msg->cam_info.height,
          msg->cam_info.width, CV_8UC3);
    }
    for (size_t i = 0; i < msg->patch_array.size(); i++) {
      cv_bridge::CvImageConstPtr cv_ptr_mask, cv_ptr_img;
      sensor_msgs::ImageConstPtr msg_mask(&(msg->patch_array[i].mask), boost::serialization::null_deleter());
      sensor_msgs::ImageConstPtr msg_img(&(msg->patch_array[i].image), boost::serialization::null_deleter());
      try {
        cv_ptr_mask = cv_bridge::toCvShare(msg_mask, "mono8");
      }
      catch (cv_bridge::Exception& e) {
        ROS_ERROR("cv_bridge mask exception: %s", e.what());
      }
      try {
        cv_ptr_img = cv_bridge::toCvShare(msg_img, "rgb8");
      }
      catch (cv_bridge::Exception& e) {
        ROS_ERROR("cv_bridge image exception: %s", e.what());
      }
      if (enable_debug) {
        cv_ptr_img->image.copyTo(debug_image(cv::Rect(msg->patch_array[i].image_roi.x_offset,
                msg->patch_array[i].image_roi.y_offset,
                msg->patch_array[i].image_roi.width,
                msg->patch_array[i].image_roi.height)));
      }

      cv::Mat Lab;
      cv::cvtColor(cv_ptr_img->image, Lab, cv::COLOR_RGB2Lab);
      cv::Mat point(1,1,CV_32FC3);
      point = cv::mean(Lab, cv_ptr_mask->image);
      cv::Mat scaled_point = (point.reshape(1) - mean_)/std_;
      cv::Mat prediction;
      svm->predict(scaled_point, prediction, cv::ml::StatModel::RAW_OUTPUT);
      float distance = prediction.at<float>(0);

      if (enable_debug) {
          int x,y,h;
          x = msg->patch_array[i].image_roi.x_offset;
          y = msg->patch_array[i].image_roi.y_offset;
          //w = msg->patch_array[i].image_roi.width;
          h = msg->patch_array[i].image_roi.height;
          char edist[100];
          snprintf(edist, 100, "d:%3.2f", distance);
          cv::putText(debug_image,edist,cv::Point2d(x+70, y + h + 50*config_.debug_font_scale),
                  cv::FONT_HERSHEY_SIMPLEX, config_.debug_font_scale, cv::Scalar(255,0,0),4,cv::LINE_8);
      }

      bool is_sample = distance<0;

      if(!is_sample)
      {
          if(enable_debug)
          {
              // Nix region
              int x,y,w,h;
              x = msg->patch_array[i].image_roi.x_offset;
              y = msg->patch_array[i].image_roi.y_offset;
              w = msg->patch_array[i].image_roi.width;
              h = msg->patch_array[i].image_roi.height;
              cv::line(debug_image,
                      cv::Point2f(x, y),
                      cv::Point2f(x + w, y + h), cv::Scalar(255,0,0), 20);
              cv::line(debug_image,
                      cv::Point2f(x + w, y),
                      cv::Point2f(x, y + h), cv::Scalar(255,0,0), 20);
          }
          continue;
      }

      samplereturn_msgs::NamedPoint np_msg;

      if(config_.compute_grip_angle)
      {
          cv::RotatedRect griprect;
          if(samplereturn::computeGripAngle(cv_ptr_mask->image, &griprect, &np_msg.grip_angle) &&
                  enable_debug)
          {
              griprect.center += cv::Point2f(
                      msg->patch_array[i].image_roi.x_offset,
                      msg->patch_array[i].image_roi.y_offset);
              samplereturn::drawGripRect(debug_image, griprect);
          }
      }

      np_msg.header.stamp = msg->header.stamp;
      np_msg.header.frame_id = msg->patch_array[i].world_point.header.frame_id;
      np_msg.point = msg->patch_array[i].world_point.point;
      np_msg.sensor_frame = msg->header.frame_id;
      np_msg.name = "SVM";
      points_out.points.push_back(np_msg);
    }
    pub_named_points.publish(points_out);
    if (enable_debug) {
      sensor_msgs::ImagePtr debug_image_msg =
        cv_bridge::CvImage(msg->header,"rgb8",debug_image).toImageMsg();
      pub_debug_image.publish(debug_image_msg);
    }
  }