HumanoidLocalization.cpp
Go to the documentation of this file.
00001 /*
00002  * 6D localization for humanoid robots
00003  *
00004  * Copyright 2009-2012 Armin Hornung, University of Freiburg
00005  * http://www.ros.org/wiki/humanoid_localization
00006  *
00007  *
00008  * This program is free software: you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation, version 3.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00019  */
00020 
00021 #include <humanoid_localization/HumanoidLocalization.h>
00022 #include <iostream>
00023 #include <pcl/keypoints/uniform_sampling.h>
00024 
00025 #include <pcl_ros/transforms.h>
00026 
00027 #if PCL_VERSION_COMPARE(>=,1,7,0)
00028   #include<pcl_conversions/pcl_conversions.h>
00029 #endif
00030 
00031 // simple timing benchmark output
00032 #define _BENCH_TIME 0
00033 
00034 namespace humanoid_localization{
00035 HumanoidLocalization::HumanoidLocalization(unsigned randomSeed)
00036 :
00037 m_rngEngine(randomSeed),
00038 m_rngNormal(m_rngEngine, NormalDistributionT(0.0, 1.0)),
00039 m_rngUniform(m_rngEngine, UniformDistributionT(0.0, 1.0)),
00040 m_nh(),m_privateNh("~"),
00041 m_odomFrameId("odom"), m_targetFrameId("odom"), m_baseFrameId("torso"), m_baseFootprintId("base_footprint"), m_globalFrameId("map"),
00042 m_useRaycasting(true), m_initFromTruepose(false), m_numParticles(500),
00043 m_sensorSampleDist(0.2),
00044 m_nEffFactor(1.0), m_minParticleWeight(0.0),
00045 m_bestParticleIdx(-1), m_lastIMUMsgBuffer(5),
00046 m_bestParticleAsMean(true),
00047 m_receivedSensorData(false), m_initialized(false), m_initGlobal(false), m_paused(false),
00048 m_syncedTruepose(false),
00049 m_observationThresholdTrans(0.1), m_observationThresholdRot(M_PI/6),
00050 m_observationThresholdHeadYawRot(0.5), m_observationThresholdHeadPitchRot(0.3),
00051 m_temporalSamplingRange(0.1), m_transformTolerance(0.1),
00052 m_groundFilterPointCloud(true), m_groundFilterDistance(0.04),
00053 m_groundFilterAngle(0.15), m_groundFilterPlaneDistance(0.07),
00054 m_sensorSampleDistGroundFactor(3),
00055 m_headYawRotationLastScan(0.0), m_headPitchRotationLastScan(0.0),
00056 m_useIMU(false),
00057 m_constrainMotionZ (false), m_constrainMotionRP(false), m_useTimer(false), m_timerPeriod(0.1)
00058 {
00059 
00060    m_latest_transform.setData (tf::Transform(tf::createIdentityQuaternion()) );
00061   // raycasting or endpoint model?
00062   m_privateNh.param("use_raycasting", m_useRaycasting, m_useRaycasting);
00063 
00064   m_privateNh.param("odom_frame_id", m_odomFrameId, m_odomFrameId);
00065   m_privateNh.param("target_frame_id", m_targetFrameId, m_targetFrameId);
00066   m_privateNh.param("base_frame_id", m_baseFrameId, m_baseFrameId);
00067   m_privateNh.param("base_footprint_id", m_baseFootprintId, m_baseFootprintId);
00068   m_privateNh.param("global_frame_id", m_globalFrameId, m_globalFrameId);
00069   m_privateNh.param("init_from_truepose", m_initFromTruepose, m_initFromTruepose);
00070   m_privateNh.param("init_global", m_initGlobal, m_initGlobal);
00071   m_privateNh.param("best_particle_as_mean", m_bestParticleAsMean, m_bestParticleAsMean);
00072   m_privateNh.param("num_particles", m_numParticles, m_numParticles);
00073   m_privateNh.param("neff_factor", m_nEffFactor, m_nEffFactor);
00074   m_privateNh.param("min_particle_weight", m_minParticleWeight, m_minParticleWeight);
00075 
00076   m_privateNh.param("initial_pose/x", m_initPose(0), 0.0);
00077   m_privateNh.param("initial_pose/y", m_initPose(1), 0.0);
00078   m_privateNh.param("initial_pose/z", m_initPose(2), 0.32); // hip height when standing
00079   m_privateNh.param("initial_pose/roll", m_initPose(3), 0.0);
00080   m_privateNh.param("initial_pose/pitch", m_initPose(4), 0.0);
00081   m_privateNh.param("initial_pose/yaw", m_initPose(5), 0.0);
00082   m_privateNh.param("initial_pose_real_zrp", m_initPoseRealZRP, false);
00083 
00084   m_privateNh.param("initial_std/x", m_initNoiseStd(0), 0.1); // 0.1
00085   m_privateNh.param("initial_std/y", m_initNoiseStd(1), 0.1); // 0.1
00086   m_privateNh.param("initial_std/z", m_initNoiseStd(2), 0.02); // 0.02
00087   m_privateNh.param("initial_std/roll", m_initNoiseStd(3), 0.04); // 0.04
00088   m_privateNh.param("initial_std/pitch", m_initNoiseStd(4), 0.04); // 0.04
00089   m_privateNh.param("initial_std_yaw", m_initNoiseStd(5), M_PI/12); // M_PI/12
00090 
00091   if (m_privateNh.hasParam("num_sensor_beams"))
00092     ROS_WARN("Parameter \"num_sensor_beams\" is no longer used, use \"sensor_sampling_dist\" instead");
00093 
00094   // laser observation model parameters:
00095   m_privateNh.param("sensor_sampling_dist", m_sensorSampleDist, m_sensorSampleDist);
00096   m_privateNh.param("max_range", m_filterMaxRange, 30.0);
00097   m_privateNh.param("min_range", m_filterMinRange, 0.05);
00098   ROS_DEBUG("Using a range filter of %f to %f", m_filterMinRange, m_filterMaxRange);
00099 
00100   m_privateNh.param("update_min_trans", m_observationThresholdTrans, m_observationThresholdTrans);
00101   m_privateNh.param("update_min_rot", m_observationThresholdRot, m_observationThresholdRot);
00102   m_privateNh.param("update_min_head_yaw", m_observationThresholdHeadYawRot, m_observationThresholdHeadYawRot);
00103   m_privateNh.param("update_min_head_pitch", m_observationThresholdHeadPitchRot, m_observationThresholdHeadPitchRot);
00104   m_privateNh.param("temporal_sampling_range", m_temporalSamplingRange, m_temporalSamplingRange);
00105   m_privateNh.param("transform_tolerance", m_transformTolerance, m_transformTolerance);
00106 
00107   m_privateNh.param("use_imu", m_useIMU, m_useIMU);
00108   m_privateNh.param("constrain_motion_z", m_constrainMotionZ, m_constrainMotionZ);
00109   m_privateNh.param("constrain_motion_rp", m_constrainMotionRP, m_constrainMotionRP);
00110 
00111   // point cloud observation model parameters
00112   m_privateNh.param("ground_filter_point_cloud", m_groundFilterPointCloud, m_groundFilterPointCloud);
00113   m_privateNh.param("ground_filter_distance", m_groundFilterDistance, m_groundFilterDistance);
00114   m_privateNh.param("ground_filter_angle", m_groundFilterAngle, m_groundFilterAngle);
00115   m_privateNh.param("ground_filter_plane_distance", m_groundFilterPlaneDistance, m_groundFilterPlaneDistance);
00116   m_privateNh.param("sensor_sampling_dist_ground_factor", m_sensorSampleDistGroundFactor, m_sensorSampleDistGroundFactor);
00117 
00118   m_privateNh.param("use_timer", m_useTimer, m_useTimer);
00119   m_privateNh.param("timer_period", m_timerPeriod, m_timerPeriod);
00120 
00121   // motion model parameters
00122 
00123   m_motionModel = boost::shared_ptr<MotionModel>(new MotionModel(&m_privateNh, &m_rngEngine, &m_tfListener, m_odomFrameId, m_baseFrameId));
00124 
00125   if (m_useRaycasting){
00126     m_mapModel = boost::shared_ptr<MapModel>(new OccupancyMap(&m_privateNh));
00127     m_observationModel = boost::shared_ptr<ObservationModel>(new RaycastingModel(&m_privateNh, m_mapModel, &m_rngEngine));
00128   } else{
00129 #ifndef SKIP_ENDPOINT_MODEL
00130     //m_mapModel = boost::shared_ptr<MapModel>(new DistanceMap(&m_privateNh));
00131     m_mapModel = boost::shared_ptr<MapModel>(new OccupancyMap(&m_privateNh));
00132     m_observationModel = boost::shared_ptr<ObservationModel>(new EndpointModel(&m_privateNh, m_mapModel, &m_rngEngine));
00133 #else
00134     ROS_FATAL("EndpointModel not compiled due to missing dynamicEDT3D");
00135     exit(-1);
00136 #endif
00137   }
00138 
00139 
00140   m_particles.resize(m_numParticles);
00141   m_poseArray.poses.resize(m_numParticles);
00142   m_poseArray.header.frame_id = m_globalFrameId;
00143   m_tfListener.clear();
00144 
00145 
00146   // publishers can be advertised first, before needed:
00147   m_posePub = m_nh.advertise<geometry_msgs::PoseWithCovarianceStamped>("pose", 10);
00148   m_poseEvalPub = m_nh.advertise<geometry_msgs::PoseWithCovarianceStamped>("pose_eval", 10);
00149   m_poseOdomPub = m_privateNh.advertise<geometry_msgs::PoseStamped>("pose_odom_sync", 10);
00150   m_poseArrayPub = m_privateNh.advertise<geometry_msgs::PoseArray>("particlecloud", 10);
00151   m_bestPosePub = m_privateNh.advertise<geometry_msgs::PoseArray>("best_particle", 10);
00152   m_nEffPub = m_privateNh.advertise<std_msgs::Float32>("n_eff", 10);
00153   m_filteredPointCloudPub = m_privateNh.advertise<sensor_msgs::PointCloud2>("filtered_cloud", 1);
00154 
00155 
00156   //TODO Propagate particles independent of sensor callback
00157   reset();
00158 
00159   // ROS subscriptions last:
00160   m_globalLocSrv = m_nh.advertiseService("global_localization", &HumanoidLocalization::globalLocalizationCallback, this);
00161 
00162   // subscription on laser, tf message filter
00163   m_laserSub = new message_filters::Subscriber<sensor_msgs::LaserScan>(m_nh, "scan", 100);
00164   m_laserFilter = new tf::MessageFilter<sensor_msgs::LaserScan>(*m_laserSub, m_tfListener, m_odomFrameId, 100);
00165   m_laserFilter->registerCallback(boost::bind(&HumanoidLocalization::laserCallback, this, _1));
00166 
00167   // subscription on point cloud, tf message filter
00168   m_pointCloudSub = new message_filters::Subscriber<sensor_msgs::PointCloud2>(m_nh, "point_cloud", 100);
00169   m_pointCloudFilter = new tf::MessageFilter<sensor_msgs::PointCloud2>(*m_pointCloudSub, m_tfListener, m_odomFrameId, 100);
00170   m_pointCloudFilter->registerCallback(boost::bind(&HumanoidLocalization::pointCloudCallback, this, _1));
00171 
00172   // subscription on init pose, tf message filter
00173   m_initPoseSub = new message_filters::Subscriber<geometry_msgs::PoseWithCovarianceStamped>(m_nh, "initialpose", 2);
00174   m_initPoseFilter = new tf::MessageFilter<geometry_msgs::PoseWithCovarianceStamped>(*m_initPoseSub, m_tfListener, m_globalFrameId, 2);
00175   m_initPoseFilter->registerCallback(boost::bind(&HumanoidLocalization::initPoseCallback, this, _1));
00176 
00177 
00178   m_pauseIntegrationSub = m_privateNh.subscribe("pause_localization", 1, &HumanoidLocalization::pauseLocalizationCallback, this);
00179   m_pauseLocSrv = m_privateNh.advertiseService("pause_localization_srv", &HumanoidLocalization::pauseLocalizationSrvCallback, this);
00180   m_resumeLocSrv = m_privateNh.advertiseService("resume_localization_srv", &HumanoidLocalization::resumeLocalizationSrvCallback, this);
00181 
00182   if (m_useIMU)
00183     m_imuSub = m_nh.subscribe("imu", 5, &HumanoidLocalization::imuCallback, this);
00184   if (m_useTimer)
00185   {
00186      m_timer = m_nh.createTimer(ros::Duration(m_timerPeriod), &HumanoidLocalization::timerCallback, this);
00187      ROS_INFO("Using timer with a period of %4f s", m_timerPeriod);
00188   }
00189 
00190   ROS_INFO("NaoLocalization initialized with %d particles.", m_numParticles);
00191 }
00192 
00193 HumanoidLocalization::~HumanoidLocalization() {
00194 
00195   delete m_laserFilter;
00196   delete m_laserSub;
00197 
00198   delete m_pointCloudFilter;
00199   delete m_pointCloudSub;
00200 
00201   delete m_initPoseFilter;
00202   delete m_initPoseSub;
00203 
00204 }
00205 
00206 void HumanoidLocalization::timerCallback(const ros::TimerEvent & e){
00207    ros::Time transformExpiration = e.current_real + ros::Duration(m_transformTolerance);
00208    tf::StampedTransform tmp_tf_stamped(m_latest_transform, transformExpiration, m_globalFrameId, m_targetFrameId);
00209    m_tfBroadcaster.sendTransform(tmp_tf_stamped);
00210 }
00211 
00212 
00213 void HumanoidLocalization::reset(){
00214 
00215 #if defined(_BENCH_TIME)
00216   ros::WallTime startTime = ros::WallTime::now();
00217 #endif
00218 
00219   if (m_initGlobal){
00220     this->initGlobal();
00221   } else {
00222     geometry_msgs::PoseWithCovarianceStampedPtr posePtr(new geometry_msgs::PoseWithCovarianceStamped());
00223 
00224     if (m_initFromTruepose){ // useful for evaluation, when ground truth available:
00225       geometry_msgs::PoseStamped truePose;
00226       tf::Stamped<tf::Pose> truePoseTF;
00227       tf::Stamped<tf::Pose> ident (tf::Transform(tf::createIdentityQuaternion(), tf::Vector3(0,0,0)), ros::Time::now(), "torso_real"); // TODO: param
00228 
00229       ros::Time lookupTime = ros::Time::now();
00230       while(m_nh.ok() && !m_tfListener.waitForTransform(m_globalFrameId, ident.frame_id_, lookupTime, ros::Duration(1.0))){
00231         ROS_WARN("Waiting for transform %s --> %s for ground truth initialization failed, trying again...", m_globalFrameId.c_str(), ident.frame_id_.c_str());
00232         lookupTime = ros::Time::now();
00233       }
00234       ident.stamp_ = lookupTime;
00235 
00236       m_tfListener.transformPose(m_globalFrameId, ident, truePoseTF);
00237       tf::poseStampedTFToMsg(truePoseTF, truePose);
00238       tf::poseTFToMsg(truePoseTF, posePtr->pose.pose);
00239       posePtr->header = truePose.header;
00240 
00241 
00242       // initial covariance acc. to params
00243       for(int j=0; j < 6; ++j){
00244         for (int i = 0; i < 6; ++i){
00245           if (i == j)
00246             posePtr->pose.covariance.at(i*6 +j) = m_initNoiseStd(i) * m_initNoiseStd(i);
00247           else
00248             posePtr->pose.covariance.at(i*6 +j) = 0.0;
00249         }
00250       }
00251 
00252     } else{
00253       posePtr.reset(new geometry_msgs::PoseWithCovarianceStamped());
00254       for (int i = 0; i < 6; ++i){
00255         posePtr->pose.covariance.at(i*6 +i) = m_initNoiseStd(i) * m_initNoiseStd(i);
00256       }
00257 
00258       double roll, pitch, z;
00259       initZRP(z, roll, pitch);
00260 
00261 
00262 
00263       posePtr->pose.pose.position.x = m_initPose(0);
00264       posePtr->pose.pose.position.y = m_initPose(1);
00265       posePtr->pose.pose.position.z = z;
00266       tf::Quaternion quat;
00267       quat.setRPY(roll, pitch, m_initPose(5));
00268       tf::quaternionTFToMsg(quat, posePtr->pose.pose.orientation);
00269 
00270     }
00271 
00272     this->initPoseCallback(posePtr);
00273 
00274   }
00275 
00276 
00277 
00278 
00279 
00280 #if defined(_BENCH_TIME)
00281   double dt = (ros::WallTime::now() - startTime).toSec();
00282   ROS_INFO_STREAM("Initialization of "<< m_numParticles << " particles took "
00283                   << dt << "s (="<<dt/m_numParticles<<"s/particle)");
00284 #endif
00285 
00286 
00287 }
00288 
00289 
00290 void HumanoidLocalization::initZRP(double& z, double& roll, double& pitch){
00291   if(m_initPoseRealZRP) {
00292     // Get latest pose height
00293     tf::Stamped<tf::Pose> lastOdomPose;
00294     double poseHeight;
00295     if(m_motionModel->getLastOdomPose(lastOdomPose) &&
00296         lookupPoseHeight(lastOdomPose.stamp_, poseHeight)) {
00297       z = poseHeight;
00298     } else {
00299       ROS_WARN("Could not determine current pose height, falling back to init_pose_z");
00300       z = m_initPose(2);
00301     }
00302 
00303     // Get latest roll and pitch
00304     if(!m_lastIMUMsgBuffer.empty()) {
00305       getRP(m_lastIMUMsgBuffer.back().orientation, roll, pitch);
00306     } else {
00307       ROS_WARN("Could not determine current roll and pitch, falling back to init_pose_{roll,pitch}");
00308       roll = m_initPose(3);
00309       pitch = m_initPose(4);
00310     }
00311   } else {
00312     // Use pose height, roll and pitch from init_pose_{z,roll,pitch} parameters
00313     z = m_initPose(2);
00314     roll = m_initPose(3);
00315     pitch = m_initPose(4);
00316   }
00317 
00318 
00319 }
00320 void HumanoidLocalization::laserCallback(const sensor_msgs::LaserScanConstPtr& msg){
00321   ROS_DEBUG("Laser received (time: %f)", msg->header.stamp.toSec());
00322   
00323   if (!m_initialized){
00324     ROS_WARN("Localization not initialized yet, skipping laser callback.");
00325     return;
00326   }
00327 
00328   double timediff = (msg->header.stamp - m_lastLaserTime).toSec();
00329   if (m_receivedSensorData && timediff < 0){
00330     ROS_WARN("Ignoring received laser data that is %f s older than previous data!", timediff);
00331     return;
00332   }
00333   
00334 
00336   tf::Stamped<tf::Pose> odomPose;
00337   // check if odometry available, skip scan if not.
00338   if (!m_motionModel->lookupOdomPose(msg->header.stamp, odomPose))
00339      return;
00340 
00341 
00342   bool sensor_integrated = false;
00343   if (!m_paused && (!m_receivedSensorData || isAboveMotionThreshold(odomPose))) {
00344 
00345      // convert laser to point cloud first:
00346      PointCloud pc_filtered;
00347      std::vector<float> laserRangesSparse;
00348      prepareLaserPointCloud(msg, pc_filtered, laserRangesSparse);
00349 
00350      sensor_integrated = localizeWithMeasurement(pc_filtered, laserRangesSparse, msg->range_max);
00351 
00352   } 
00353 
00354   if(!sensor_integrated){ // no laser integration: propagate particles forward by full interval
00355 
00356      // relative odom transform to last odomPose
00357      tf::Transform odomTransform = m_motionModel->computeOdomTransform(odomPose);
00358      m_motionModel->applyOdomTransform(m_particles, odomTransform);
00359      constrainMotion(odomPose);
00360   }
00361   else
00362   {
00363      m_lastLocalizedPose = odomPose;
00364   }
00365 
00366   m_motionModel->storeOdomPose(odomPose);
00367   publishPoseEstimate(msg->header.stamp, sensor_integrated);
00368   m_lastLaserTime = msg->header.stamp; 
00369 }
00370 
00371 void HumanoidLocalization::constrainMotion(const tf::Pose& odomPose){
00372   // skip if nothing to do:
00373   if (!m_constrainMotionZ && !m_constrainMotionRP)
00374     return;
00375 
00376   // reset z according to current odomPose:
00377   double z = odomPose.getOrigin().getZ();
00378   double odomRoll, odomPitch, uselessYaw;
00379   odomPose.getBasis().getRPY(odomRoll, odomPitch, uselessYaw);
00380 
00381 #pragma omp parallel for
00382   for (unsigned i=0; i < m_particles.size(); ++i){
00383     if (m_constrainMotionZ){
00384       tf::Vector3 pos = m_particles[i].pose.getOrigin();
00385       double floor_z = m_mapModel->getFloorHeight(m_particles[i].pose);
00386       pos.setZ(z+floor_z);
00387       m_particles[i].pose.setOrigin(pos);
00388     }
00389 
00390     if (m_constrainMotionRP){
00391       double yaw =  tf::getYaw(m_particles[i].pose.getRotation());
00392       m_particles[i].pose.setRotation(tf::createQuaternionFromRPY(odomRoll, odomPitch, yaw));
00393 
00394     }
00395   }
00396 }
00397 
00398 bool HumanoidLocalization::isAboveMotionThreshold(const tf::Pose& odomPose){
00399   tf::Transform odomTransform = m_lastLocalizedPose.inverse() * odomPose;
00400 
00401   double yaw, pitch, roll;
00402   odomTransform.getBasis().getRPY(roll, pitch, yaw);
00403 
00404   return (odomTransform.getOrigin().length() >= m_observationThresholdTrans
00405       || std::abs(yaw) >= m_observationThresholdRot);
00406 }
00407 
00408 bool HumanoidLocalization::localizeWithMeasurement(const PointCloud& pc_filtered, const std::vector<float>& ranges, double max_range){
00409   ros::WallTime startTime = ros::WallTime::now();
00410 #if PCL_VERSION_COMPARE(>=,1,7,0)
00411   ros::Time t = pcl_conversions::fromPCL(pc_filtered.header).stamp;
00412 #else
00413   ros::Time t = pc_filtered.header.stamp;
00414 #endif
00415   // apply motion model with temporal sampling:
00416   m_motionModel->applyOdomTransformTemporal(m_particles, t, m_temporalSamplingRange);
00417   
00418   // constrain to ground plane, if desired:
00419   tf::Stamped<tf::Transform> odomPose;
00420   if (!m_motionModel->lookupOdomPose(t, odomPose))
00421     return false;
00422   constrainMotion(odomPose);
00423 
00424   // transformation from torso frame to sensor
00425   // this takes the latest tf, assumes that torso to sensor did not change over temp. sampling!
00426   tf::StampedTransform localSensorFrame;
00427   if (!m_motionModel->lookupLocalTransform(pc_filtered.header.frame_id, t, localSensorFrame))
00428     return false;
00429 
00430   tf::Transform torsoToSensor(localSensorFrame.inverse());
00431   
00432 //### Particles in log-form from here...
00433   toLogForm();
00434 
00435   // skip pose integration if z, roll and pitch constrained to floor by odometry
00436   if (!(m_constrainMotionRP && m_constrainMotionZ)){
00437     bool imuMsgOk = false;
00438     double angleX, angleY;
00439     if(m_useIMU) {
00440       ros::Time imuStamp;
00441       imuMsgOk = getImuMsg(t, imuStamp, angleX, angleY);
00442     } else {
00443       tf::Stamped<tf::Pose> lastOdomPose;
00444       if(m_motionModel->lookupOdomPose(t, lastOdomPose)) {
00445         double dropyaw;
00446         lastOdomPose.getBasis().getRPY(angleX, angleY, dropyaw);
00447         imuMsgOk = true;
00448       }
00449     }
00450 
00451     tf::StampedTransform footprintToTorso;
00452     // integrated pose (z, roll, pitch) meas. only if data OK:
00453     if(imuMsgOk) {
00454       if (!m_motionModel->lookupLocalTransform(m_baseFootprintId, t, footprintToTorso)) {
00455         ROS_WARN("Could not obtain pose height in localization, skipping Pose integration");
00456       } else {
00457         m_observationModel->integratePoseMeasurement(m_particles, angleX, angleY, footprintToTorso);
00458       }
00459     } else {
00460       ROS_WARN("Could not obtain roll and pitch measurement, skipping Pose integration");
00461     }
00462   }
00463 
00464   m_filteredPointCloudPub.publish(pc_filtered);
00465   m_observationModel->integrateMeasurement(m_particles, pc_filtered, ranges, max_range, torsoToSensor);
00466 
00467   // TODO: verify poses before measurements, ignore particles then
00468   m_mapModel->verifyPoses(m_particles);
00469 
00470   // normalize weights and transform back from log:
00471   normalizeWeights();
00472   //### Particles back in regular form now
00473 
00474   double nEffParticles = nEff();
00475 
00476   std_msgs::Float32 nEffMsg;
00477   nEffMsg.data = nEffParticles;
00478   m_nEffPub.publish(nEffMsg);
00479 
00480   if (nEffParticles <= m_nEffFactor*m_particles.size()){ // selective resampling
00481     ROS_INFO("Resampling, nEff=%f, numParticles=%zd", nEffParticles, m_particles.size());
00482     resample();
00483   } else {
00484     ROS_INFO("Skipped resampling, nEff=%f, numParticles=%zd", nEffParticles, m_particles.size());
00485   }
00486 
00487   m_receivedSensorData = true;
00488 
00489   double dt = (ros::WallTime::now() - startTime).toSec();
00490   ROS_INFO_STREAM("Observations for "<< m_numParticles << " particles took "
00491                   << dt << "s (="<<dt/m_numParticles<<"s/particle)");
00492 
00493   return true;
00494 }
00495 
00496 void HumanoidLocalization::prepareLaserPointCloud(const sensor_msgs::LaserScanConstPtr& laser, PointCloud& pc, std::vector<float>& ranges) const{
00497   unsigned numBeams = laser->ranges.size();
00498   // skip every n-th scan:
00499   //unsigned step = computeBeamStep(numBeams);
00500   // build complete pointcloud:
00501   unsigned step = 1;
00502 
00503 
00504 
00505   // prepare laser message:
00506   unsigned int numBeamsSkipped = 0;
00507 
00508   // range_min of laser is also used to filter out wrong messages:
00509   double laserMin = std::max(double(laser->range_min), m_filterMinRange);
00510 
00511   // (range_max readings stay, will be used in the sensor model)
00512 
00513   ranges.reserve(50);
00514 
00515   // build a point cloud
00516 #if PCL_VERSION_COMPARE(>=,1,7,0)
00517   pcl_conversions::toPCL(laser->header, pc.header);
00518 #else
00519   pc.header = laser->header;
00520 #endif
00521   pc.points.reserve(50);
00522   for (unsigned beam_idx = 0; beam_idx < numBeams; beam_idx+= step){
00523     float range = laser->ranges[beam_idx];
00524     if (range >= laserMin && range <= m_filterMaxRange){
00525       double laserAngle = laser->angle_min + beam_idx * laser->angle_increment;
00526       tf::Transform laserAngleRotation(tf::Quaternion(tf::Vector3(0.0, 0.0, 1.0), laserAngle));
00527       tf::Vector3 laserEndpointTrans(range, 0.0, 0.0);
00528       tf::Vector3 pt(laserAngleRotation * laserEndpointTrans);
00529 
00530       pc.points.push_back(pcl::PointXYZ(pt.x(), pt.y(), pt.z()));
00531       ranges.push_back(range);
00532 
00533     } else{
00534       numBeamsSkipped++;
00535     }
00536 
00537   }
00538   pc.height = 1;
00539   pc.width = pc.points.size();
00540   pc.is_dense = true;
00541 
00542   // uniform sampling:
00543   pcl::UniformSampling<pcl::PointXYZ> uniformSampling;
00544   pcl::PointCloud<pcl::PointXYZ>::Ptr cloudPtr;
00545   cloudPtr.reset(new pcl::PointCloud<pcl::PointXYZ> (pc));
00546   uniformSampling.setInputCloud(cloudPtr);
00547   uniformSampling.setRadiusSearch(m_sensorSampleDist);
00548   pcl::PointCloud<int> sampledIndices;
00549   uniformSampling.compute(sampledIndices);
00550   pcl::copyPointCloud(*cloudPtr, sampledIndices.points, pc);
00551   // adjust "ranges" array to contain the same points:
00552   std::vector<float> rangesSparse;
00553   rangesSparse.resize(sampledIndices.size());
00554   for (size_t i = 0; i < rangesSparse.size(); ++i){
00555     rangesSparse[i] = ranges[sampledIndices.points[i]];
00556   }
00557   ranges = rangesSparse;
00558   ROS_INFO("Laser PointCloud subsampled: %zu from %zu (%u out of valid range)", pc.size(), cloudPtr->size(), numBeamsSkipped);
00559 }
00560 
00561 int HumanoidLocalization::filterUniform(const PointCloud & cloud_in, PointCloud & cloud_out, int numSamples) const{
00562   int numPoints = static_cast<int>(cloud_in.size() );
00563   numSamples = std::min( numSamples, numPoints);
00564   std::vector<unsigned int> indices;
00565   indices.reserve( numPoints );
00566   for (int i=0; i<numPoints; ++i)
00567     indices.push_back(i);
00568   random_shuffle ( indices.begin(), indices.end());
00569 
00570   cloud_out.reserve( cloud_out.size() + numSamples );
00571   for ( int i = 0; i < numSamples; ++i)
00572   {
00573     cloud_out.push_back( cloud_in.at(indices[i]));
00574   }
00575   return numSamples;
00576 }
00577 
00578 
00579 
00580 void HumanoidLocalization::filterGroundPlane(const PointCloud& pc, PointCloud& ground, PointCloud& nonground, double groundFilterDistance, double groundFilterAngle, double groundFilterPlaneDistance){
00581    ground.header = pc.header;
00582    nonground.header = pc.header;
00583 
00584    if (pc.size() < 50){
00585       ROS_WARN("Pointcloud in HumanoidLocalization::filterGroundPlane too small, skipping ground plane extraction");
00586       nonground = pc;
00587    } else {
00588       // plane detection for ground plane removal:
00589       pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
00590       pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
00591 
00592       // Create the segmentation object and set up:
00593       pcl::SACSegmentation<pcl::PointXYZ> seg;
00594       seg.setOptimizeCoefficients (true);
00595       // TODO: maybe a filtering based on the surface normals might be more robust / accurate?
00596       seg.setModelType(pcl::SACMODEL_PERPENDICULAR_PLANE);
00597       seg.setMethodType(pcl::SAC_RANSAC);
00598       seg.setMaxIterations(200);
00599       seg.setDistanceThreshold (groundFilterDistance);
00600       seg.setAxis(Eigen::Vector3f(0,0,1));
00601       seg.setEpsAngle(groundFilterAngle);
00602 
00603 
00604       PointCloud cloud_filtered(pc);
00605       // Create the filtering object
00606       pcl::ExtractIndices<pcl::PointXYZ> extract;
00607       bool groundPlaneFound = false;
00608 
00609       while(cloud_filtered.size() > 10 && !groundPlaneFound){
00610          seg.setInputCloud(cloud_filtered.makeShared());
00611          seg.segment (*inliers, *coefficients);
00612          if (inliers->indices.size () == 0){
00613             ROS_INFO("PCL segmentation did not find any plane.");
00614 
00615             break;
00616          }
00617 
00618          extract.setInputCloud(cloud_filtered.makeShared());
00619          extract.setIndices(inliers);
00620 
00621          if (std::abs(coefficients->values.at(3)) < groundFilterPlaneDistance){
00622             ROS_DEBUG("Ground plane found: %zu/%zu inliers. Coeff: %f %f %f %f", inliers->indices.size(), cloud_filtered.size(),
00623                   coefficients->values.at(0), coefficients->values.at(1), coefficients->values.at(2), coefficients->values.at(3));
00624             extract.setNegative (false);
00625             extract.filter (ground);
00626 
00627             // remove ground points from full pointcloud:
00628             // workaround for PCL bug:
00629             if(inliers->indices.size() != cloud_filtered.size()){
00630                extract.setNegative(true);
00631                PointCloud cloud_out;
00632                extract.filter(cloud_out);
00633                nonground += cloud_out;
00634                cloud_filtered = cloud_out;
00635             }
00636 
00637             groundPlaneFound = true;
00638          } else{
00639             ROS_DEBUG("Horizontal plane (not ground) found: %zu/%zu inliers. Coeff: %f %f %f %f", inliers->indices.size(), cloud_filtered.size(),
00640                   coefficients->values.at(0), coefficients->values.at(1), coefficients->values.at(2), coefficients->values.at(3));
00641             pcl::PointCloud<pcl::PointXYZ> cloud_out;
00642             extract.setNegative (false);
00643             extract.filter(cloud_out);
00644             nonground +=cloud_out;
00645             // debug
00646             //            pcl::PCDWriter writer;
00647             //            writer.write<pcl::PointXYZ>("nonground_plane.pcd",cloud_out, false);
00648 
00649             // remove current plane from scan for next iteration:
00650             // workaround for PCL bug:
00651             if(inliers->indices.size() != cloud_filtered.size()){
00652                extract.setNegative(true);
00653                cloud_out.points.clear();
00654                extract.filter(cloud_out);
00655                cloud_filtered = cloud_out;
00656             } else{
00657                cloud_filtered.points.clear();
00658             }
00659          }
00660 
00661       }
00662       // TODO: also do this if overall starting pointcloud too small?
00663       if (!groundPlaneFound){ // no plane found or remaining points too small
00664          ROS_WARN("No ground plane found in scan");
00665 
00666          // do a rough fitlering on height to prevent spurious obstacles
00667          pcl::PassThrough<pcl::PointXYZ> second_pass;
00668          second_pass.setFilterFieldName("z");
00669          second_pass.setFilterLimits(-groundFilterPlaneDistance, groundFilterPlaneDistance);
00670          second_pass.setInputCloud(pc.makeShared());
00671          second_pass.filter(ground);
00672 
00673          second_pass.setFilterLimitsNegative (true);
00674          second_pass.filter(nonground);
00675       }
00676 
00677       // debug:
00678       //        pcl::PCDWriter writer;
00679       //        if (pc_ground.size() > 0)
00680       //          writer.write<pcl::PointXYZ>("ground.pcd",pc_ground, false);
00681       //        if (pc_nonground.size() > 0)
00682       //          writer.write<pcl::PointXYZ>("nonground.pcd",pc_nonground, false);
00683    }
00684 }
00685 
00686 
00687 
00688 void HumanoidLocalization::prepareGeneralPointCloud(const sensor_msgs::PointCloud2::ConstPtr& msg, PointCloud& pc, std::vector<float>& ranges) const{
00689 
00690     pc.clear();
00691     // lookup Transfrom Sensor to BaseFootprint
00692     tf::StampedTransform sensorToBaseFootprint;
00693     try{
00694       m_tfListener.waitForTransform(m_baseFootprintId, msg->header.frame_id, msg->header.stamp, ros::Duration(0.2));
00695       m_tfListener.lookupTransform(m_baseFootprintId, msg->header.frame_id, msg->header.stamp, sensorToBaseFootprint);
00696 
00697 
00698     }catch(tf::TransformException& ex){
00699       ROS_ERROR_STREAM( "Transform error for pointCloudCallback: " << ex.what() << ", quitting callback.\n");
00700       return;
00701     }
00702 
00703     /*** filter PointCloud and fill pc and ranges ***/
00704 
00705     // pass-through filter to get rid of near and far ranges
00706     pcl::PassThrough<pcl::PointXYZ> pass;
00707     pcl::PointCloud<pcl::PointXYZ>::Ptr pcd_tmp(new pcl::PointCloud<pcl::PointXYZ>());
00708 #if PCL_VERSION_COMPARE(>=,1,7,0)
00709     pcl::PCLPointCloud2 pcd2_tmp;
00710     pcl_conversions::toPCL(*msg, pcd2_tmp);
00711     pcl::fromPCLPointCloud2(pcd2_tmp, *pcd_tmp);
00712 #else
00713     pcl::fromROSMsg(*msg, *pcd_tmp);
00714 #endif
00715     pass.setInputCloud (pcd_tmp);
00716     pass.setFilterFieldName ("z");
00717     pass.setFilterLimits (m_filterMinRange, m_filterMaxRange);
00718     pass.filter (pc);
00719 
00720     // identify ground plane
00721     PointCloud ground, nonground;
00722     if (m_groundFilterPointCloud)
00723     {
00724         Eigen::Matrix4f matSensorToBaseFootprint, matBaseFootprintToSensor;
00725         pcl_ros::transformAsMatrix(sensorToBaseFootprint, matSensorToBaseFootprint);
00726         pcl_ros::transformAsMatrix(sensorToBaseFootprint.inverse(), matBaseFootprintToSensor);
00727         // TODO:Why transform the point cloud and not just the normal vector?
00728         pcl::transformPointCloud(pc, pc, matSensorToBaseFootprint );
00729         filterGroundPlane(pc, ground, nonground, m_groundFilterDistance, m_groundFilterAngle, m_groundFilterPlaneDistance);
00730 
00731         // clear pc again and refill it based on classification
00732         pc.clear();
00733         pcl::PointCloud<int> sampledIndices;
00734 
00735         int numFloorPoints = 0;
00736         if (ground.size() > 0){ // check for 0 size, otherwise PCL crashes
00737           // transform clouds back to sensor for integration
00738           pcl::transformPointCloud(ground, ground, matBaseFootprintToSensor);
00739           voxelGridSampling(ground, sampledIndices, m_sensorSampleDist*m_sensorSampleDistGroundFactor);
00740           pcl::copyPointCloud(ground, sampledIndices.points, pc);
00741           numFloorPoints = sampledIndices.size();
00742         }
00743 
00744         //int numNonFloorPoints = filterUniform( nonground, pc, m_numNonFloorPoints );
00745         int numNonFloorPoints = 0;
00746         if (nonground.size() > 0){ // check for 0 size, otherwise PCL crashes
00747           // transform clouds back to sensor for integration
00748           pcl::transformPointCloud(nonground, nonground, matBaseFootprintToSensor);
00749           voxelGridSampling( nonground, sampledIndices, m_sensorSampleDist);
00750           pcl::copyPointCloud( nonground, sampledIndices.points, nonground);
00751           numNonFloorPoints = sampledIndices.size();
00752           pc += nonground;
00753         }
00754 
00755         //TODO improve sampling?
00756 
00757 
00758         ROS_INFO("PointCloudGroundFiltering done. Added %d non-ground points and %d ground points (from %zu). Cloud size is %zu", numNonFloorPoints, numFloorPoints, ground.size(), pc.size());
00759         // create sparse ranges..
00760         ranges.resize(pc.size());
00761         for (unsigned int i=0; i<pc.size(); ++i)
00762         {
00763            pcl::PointXYZ p = pc.at(i);
00764            ranges[i] = sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
00765         }
00766 
00767     }
00768     else
00769     {
00770        ROS_INFO("Starting uniform sampling");
00771        //ROS_ERROR("No ground filtering is not implemented yet!");
00772        // uniform sampling:
00773        pcl::PointCloud<int> sampledIndices;
00774        voxelGridSampling(pc, sampledIndices,  m_sensorSampleDist);
00775        pcl::copyPointCloud(pc, sampledIndices.points, pc);
00776 
00777        // adjust "ranges" array to contain the same points:
00778        ranges.resize(sampledIndices.size());
00779        for (size_t i = 0; i < ranges.size(); ++i){
00780           pcl::PointXYZ p = pc[i]; 
00781           ranges[i] = sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
00782           //rangesSparse[i] = ranges[sampledIndices.points[i]];
00783           //ranges[i] = sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
00784        }
00785        ROS_INFO("Done.");
00786 
00787 
00788     }
00789     return;
00790 
00791 }
00792 
00793 void HumanoidLocalization::voxelGridSampling(const PointCloud & pc, pcl::PointCloud<int> & sampledIndices, double search_radius) const
00794 {
00795    pcl::UniformSampling<pcl::PointXYZ> uniformSampling;
00796    pcl::PointCloud<pcl::PointXYZ>::Ptr cloudPtr;
00797    cloudPtr.reset(new pcl::PointCloud<pcl::PointXYZ> (pc)); // TODO: Check if this is a shallow copy..
00798    uniformSampling.setInputCloud(cloudPtr);
00799    uniformSampling.setRadiusSearch(search_radius);
00800    uniformSampling.compute(sampledIndices);
00801 }
00802 
00803 void HumanoidLocalization::pointCloudCallback(const sensor_msgs::PointCloud2::ConstPtr& msg) {
00804   ROS_DEBUG("PointCloud received (time: %f)", msg->header.stamp.toSec());
00805 
00806   if (!m_initialized){
00807     ROS_WARN("Loclization not initialized yet, skipping PointCloud callback.");
00808     return;
00809   }
00810 
00811   double timediff = (msg->header.stamp - m_lastPointCloudTime).toSec();
00812   if (m_receivedSensorData && timediff < 0){
00813     ROS_WARN("Ignoring received PointCloud data that is %f s older than previous data!", timediff);
00814     return;
00815   }
00816 
00817 
00819   tf::Stamped<tf::Pose> odomPose;
00820   // check if odometry available, skip scan if not.
00821   if (!m_motionModel->lookupOdomPose(msg->header.stamp, odomPose))
00822     return;
00823 
00824 
00825   bool sensor_integrated = false;
00826 
00827   // TODO #1: Make this nicer: head rotations for integration check
00828   // TODO #2: Initialization of m_headYawRotationLastScan, etc needs to be set correctly
00829   bool isAboveHeadMotionThreshold = false;
00830   double headYaw, headPitch, headRoll;
00831   tf::StampedTransform torsoToSensor;
00832   if (!m_motionModel->lookupLocalTransform(msg->header.frame_id, msg->header.stamp, torsoToSensor))
00833       return; //TODO: should we apply applyOdomTransformTemporal, before returning
00834 
00835   // TODO #3: Invert transform?: tf::Transform torsoToSensor(localSensorFrame.inverse());
00836 
00837   torsoToSensor.getBasis().getRPY(headRoll, headPitch, headYaw);
00838   double headYawRotationSinceScan = std::abs(headYaw - m_headYawRotationLastScan);
00839   double headPitchRotationSinceScan = std::abs(headPitch - m_headPitchRotationLastScan);
00840 
00841   if (headYawRotationSinceScan>= m_observationThresholdHeadYawRot || headPitchRotationSinceScan >= m_observationThresholdHeadPitchRot)
00842       isAboveHeadMotionThreshold = true;
00843   // end #1
00844 
00845   if (!m_paused && (!m_receivedSensorData || isAboveHeadMotionThreshold || isAboveMotionThreshold(odomPose))) {
00846 
00847     // convert laser to point cloud first:
00848     PointCloud pc_filtered;
00849     std::vector<float> rangesSparse;
00850     prepareGeneralPointCloud(msg, pc_filtered, rangesSparse);
00851 
00852     double maxRange = 10.0; // TODO #4: What is a maxRange for pointClouds? NaN? maxRange is expected to be a double and integrateMeasurement checks rangesSparse[i] > maxRange
00853     ROS_DEBUG("Updating Pose Estimate from a PointCloud with %zu points and %zu ranges", pc_filtered.size(), rangesSparse.size());
00854     sensor_integrated = localizeWithMeasurement(pc_filtered, rangesSparse, maxRange);
00855    
00856   } 
00857   if(!sensor_integrated){ // no observation necessary: propagate particles forward by full interval
00858      // relative odom transform to last odomPose
00859      tf::Transform odomTransform = m_motionModel->computeOdomTransform(odomPose);
00860      m_motionModel->applyOdomTransform(m_particles, odomTransform);
00861      constrainMotion(odomPose);
00862   }
00863   else{
00864      m_lastLocalizedPose = odomPose;
00865      // TODO #1
00866      m_headYawRotationLastScan = headYaw;
00867      m_headPitchRotationLastScan = headPitch;
00868   }
00869 
00870   m_motionModel->storeOdomPose(odomPose);
00871   publishPoseEstimate(msg->header.stamp, sensor_integrated);
00872   m_lastPointCloudTime = msg->header.stamp;
00873   ROS_DEBUG("PointCloud callback complete.");
00874 }
00875 
00876 void HumanoidLocalization::imuCallback(const sensor_msgs::ImuConstPtr& msg){
00877   m_lastIMUMsgBuffer.push_back(*msg);
00878 }
00879 
00880 bool HumanoidLocalization::getImuMsg(const ros::Time& stamp, ros::Time& imuStamp, double& angleX, double& angleY) const {
00881   if(m_lastIMUMsgBuffer.empty())
00882     return false;
00883 
00884   typedef boost::circular_buffer<sensor_msgs::Imu>::const_iterator ItT;
00885   const double maxAge = 0.2;
00886   double closestOlderStamp = std::numeric_limits<double>::max();
00887   double closestNewerStamp = std::numeric_limits<double>::max();
00888   ItT closestOlder = m_lastIMUMsgBuffer.end(), closestNewer = m_lastIMUMsgBuffer.end();
00889   for(ItT it = m_lastIMUMsgBuffer.begin(); it != m_lastIMUMsgBuffer.end(); it++) {
00890     const double age = (stamp - it->header.stamp).toSec();
00891     if(age >= 0.0 && age < closestOlderStamp) {
00892       closestOlderStamp = age;
00893       closestOlder = it;
00894     } else if(age < 0.0 && -age < closestNewerStamp) {
00895       closestNewerStamp = -age;
00896       closestNewer = it;
00897     }
00898   }
00899 
00900   if(closestOlderStamp < maxAge && closestNewerStamp < maxAge && closestOlderStamp + closestNewerStamp > 0.0) {
00901     // Linear interpolation
00902     const double weightOlder = closestNewerStamp / (closestNewerStamp + closestOlderStamp);
00903     const double weightNewer = 1.0 - weightOlder;
00904     imuStamp = ros::Time(weightOlder * closestOlder->header.stamp.toSec()
00905                           + weightNewer * closestNewer->header.stamp.toSec());
00906     double olderX, olderY, newerX, newerY;
00907     getRP(closestOlder->orientation, olderX, olderY);
00908     getRP(closestNewer->orientation, newerX, newerY);
00909     angleX   = weightOlder * olderX  + weightNewer * newerX;
00910     angleY   = weightOlder * olderY + weightNewer * newerY;
00911     ROS_DEBUG("Msg: %.3f, Interpolate [%.3f .. %.3f .. %.3f]\n", stamp.toSec(), closestOlder->header.stamp.toSec(),
00912               imuStamp.toSec(), closestNewer->header.stamp.toSec());
00913     return true;
00914   } else if(closestOlderStamp < maxAge || closestNewerStamp < maxAge) {
00915     // Return closer one
00916     ItT it = (closestOlderStamp < closestNewerStamp) ? closestOlder : closestNewer;
00917     imuStamp = it->header.stamp;
00918     getRP(it->orientation, angleX, angleY);
00919     return true;
00920   } else {
00921     if(closestOlderStamp < closestNewerStamp)
00922       ROS_WARN("Closest IMU message is %.2f seconds too old, skipping pose integration", closestOlderStamp);
00923     else
00924       ROS_WARN("Closest IMU message is %.2f seconds too new, skipping pose integration", closestNewerStamp);
00925     return false;
00926   }
00927 }
00928 
00929 void HumanoidLocalization::initPoseCallback(const geometry_msgs::PoseWithCovarianceStampedConstPtr& msg){
00930   tf::Pose pose;
00931   tf::poseMsgToTF(msg->pose.pose, pose);
00932 
00933   if (msg->header.frame_id != m_globalFrameId){
00934     ROS_WARN("Frame ID of \"initialpose\" (%s) is different from the global frame %s", msg->header.frame_id.c_str(), m_globalFrameId.c_str());
00935   }
00936 
00937   std::vector<double> heights;
00938   double poseHeight = 0.0;
00939   if (std::abs(pose.getOrigin().getZ()) < 0.01){
00940     m_mapModel->getHeightlist(pose.getOrigin().getX(), pose.getOrigin().getY(), 0.6, heights);
00941     if (heights.size() == 0){
00942       ROS_WARN("No ground level to stand on found at map position, assuming 0");
00943       heights.push_back(0.0);
00944     }
00945 
00946     bool poseHeightOk = false;
00947     if(m_initPoseRealZRP) {
00948       ros::Time stamp(msg->header.stamp);
00949       if(stamp.isZero()) {
00950         // Header stamp is not set (e.g. RViz), use stamp from latest pose message instead
00951         tf::Stamped<tf::Pose> lastOdomPose;
00952         m_motionModel->getLastOdomPose(lastOdomPose);
00953         stamp = lastOdomPose.stamp_;
00954       }
00955       poseHeightOk = lookupPoseHeight(stamp, poseHeight);
00956       if(!poseHeightOk) {
00957         ROS_WARN("Could not determine current pose height, falling back to init_pose_z");
00958       }
00959     }
00960     if(!poseHeightOk) {
00961       ROS_INFO("Use pose height from init_pose_z");
00962       poseHeight = m_initPose(2);
00963     }
00964   }
00965 
00966 
00967   Matrix6d initCov;
00968   if ((std::abs(msg->pose.covariance.at(6*0+0) - 0.25) < 0.1) && (std::abs(msg->pose.covariance.at(6*1+1) -0.25) < 0.1)
00969       && (std::abs(msg->pose.covariance.at(6*3+3) - M_PI/12.0 * M_PI/12.0)<0.1)){
00970     ROS_INFO("Covariance originates from RViz, using default parameters instead");
00971     initCov = Matrix6d::Zero();
00972     initCov.diagonal() = m_initNoiseStd.cwiseProduct(m_initNoiseStd);
00973 
00974     // manually set r&p, rviz values are 0
00975     bool ok = false;
00976     const double yaw = tf::getYaw(pose.getRotation());
00977     if(m_initPoseRealZRP) {
00978       bool useOdometry = true;
00979       if(m_useIMU) {
00980         if(m_lastIMUMsgBuffer.empty()) {
00981           ROS_WARN("Could not determine current roll and pitch because IMU message buffer is empty.");
00982         } else {
00983           double roll, pitch;
00984           if(msg->header.stamp.isZero()) {
00985             // Header stamp is not set (e.g. RViz), use stamp from latest IMU message instead
00986             getRP(m_lastIMUMsgBuffer.back().orientation, roll, pitch);
00987             ok = true;
00988           } else {
00989             ros::Time imuStamp;
00990             ok = getImuMsg(msg->header.stamp, imuStamp, roll, pitch);
00991           }
00992           if(ok) {
00993             ROS_INFO("roll and pitch not set in initPoseCallback, use IMU values (roll = %f, pitch = %f) instead", roll, pitch);
00994             pose.setRotation(tf::createQuaternionFromRPY(roll, pitch, yaw));
00995             useOdometry = false;
00996           } else {
00997             ROS_WARN("Could not determine current roll and pitch from IMU, falling back to odometry roll and pitch");
00998             useOdometry = true;
00999           }
01000         }
01001       }
01002 
01003       if(useOdometry) {
01004         double roll, pitch, dropyaw;
01005         tf::Stamped<tf::Pose> lastOdomPose;
01006         ok = m_motionModel->getLastOdomPose(lastOdomPose);
01007         if(ok) {
01008           lastOdomPose.getBasis().getRPY(roll, pitch, dropyaw);
01009           pose.setRotation(tf::createQuaternionFromRPY(roll, pitch, yaw));
01010           ROS_INFO("roll and pitch not set in initPoseCallback, use odometry values (roll = %f, pitch = %f) instead", roll, pitch);
01011         } else {
01012           ROS_WARN("Could not determine current roll and pitch from odometry, falling back to init_pose_{roll,pitch} parameters");
01013         }
01014       }
01015     }
01016 
01017     if(!ok) {
01018       ROS_INFO("roll and pitch not set in initPoseCallback, use init_pose_{roll,pitch} parameters instead");
01019       pose.setRotation(tf::createQuaternionFromRPY(m_initPose(3), m_initPose(4), yaw));
01020     }
01021   } else{
01022     for(int j=0; j < initCov.cols(); ++j){
01023       for (int i = 0; i < initCov.rows(); ++i){
01024         initCov(i,j) = msg->pose.covariance.at(i*initCov.cols() +j);
01025       }
01026     }
01027   }
01028 
01029   // sample from initial pose covariance:
01030   Matrix6d initCovL = initCov.llt().matrixL();
01031   tf::Transform transformNoise; // transformation on original pose from noise
01032   unsigned idx = 0;
01033   for(Particles::iterator it = m_particles.begin(); it != m_particles.end(); ++it){
01034     Vector6d poseNoise;
01035     for (unsigned i = 0; i < 6; ++i){
01036       poseNoise(i) = m_rngNormal();
01037     }
01038     Vector6d poseCovNoise = initCovL * poseNoise; // is now drawn according to covariance noise
01039     // if a variance is set to 0 => no noise!
01040     for (unsigned i = 0; i < 6; ++i){
01041       if (std::abs(initCov(i,i)) < 0.00001)
01042         poseCovNoise(i) = 0.0;
01043     }
01044 
01045 
01046     transformNoise.setOrigin(tf::Vector3(poseCovNoise(0), poseCovNoise(1), poseCovNoise(2)));
01047     tf::Quaternion q;
01048     q.setRPY(poseCovNoise(3), poseCovNoise(4),poseCovNoise(5));
01049 
01050     transformNoise.setRotation(q);
01051     it->pose = pose;
01052 
01053     if (heights.size() > 0){
01054       // distribute particles evenly between levels:
01055       it->pose.getOrigin().setZ(heights.at(int(double(idx)/m_particles.size() * heights.size())) + poseHeight);
01056     }
01057 
01058     it->pose *= transformNoise;
01059 
01060     it->weight = 1.0/m_particles.size();
01061     idx++;
01062   }
01063 
01064   ROS_INFO("Pose reset around mean (%f %f %f)", pose.getOrigin().getX(), pose.getOrigin().getY(), pose.getOrigin().getZ());
01065 
01066   // reset internal state:
01067   m_motionModel->reset();
01068   // force integration of next laser data:
01069   m_receivedSensorData = false;
01070   m_initialized = true;
01071 
01072 
01073   // Fix "0" time warning (when initializing pose from RViz)
01074   ros::Time stampPublish = msg->header.stamp;
01075   if (stampPublish.isZero()){
01076     tf::Stamped<tf::Pose> lastOdomPose;
01077     m_motionModel->getLastOdomPose(lastOdomPose);
01078     stampPublish = lastOdomPose.stamp_;
01079     if (stampPublish.isZero())
01080        stampPublish = ros::Time::now();
01081   
01082   }
01083   
01084   publishPoseEstimate(stampPublish, false);
01085 }
01086 
01087 
01088 
01089 
01090 bool HumanoidLocalization::globalLocalizationCallback(std_srvs::Empty::Request& req, std_srvs::Empty::Response& res)
01091 {
01092 
01093   initGlobal();
01094 
01095   return true;
01096 }
01097 
01098 void HumanoidLocalization::normalizeWeights() {
01099 
01100   double wmin = std::numeric_limits<double>::max();
01101   double wmax = -std::numeric_limits<double>::max();
01102 
01103   for (unsigned i=0; i < m_particles.size(); ++i){
01104     double weight = m_particles[i].weight;
01105     assert (!isnan(weight));
01106     if (weight < wmin)
01107       wmin = weight;
01108     if (weight > wmax){
01109       wmax = weight;
01110       m_bestParticleIdx = i;
01111     }
01112   }
01113   if (wmin > wmax){
01114     ROS_ERROR_STREAM("Error in weights: min=" << wmin <<", max="<<wmax<<", 1st particle weight="<< m_particles[1].weight<< std::endl);
01115 
01116   }
01117 
01118   double min_normalized_value;
01119   if (m_minParticleWeight > 0.0)
01120     min_normalized_value = std::max(log(m_minParticleWeight), wmin - wmax);
01121   else
01122     min_normalized_value = wmin - wmax;
01123 
01124   double max_normalized_value = 0.0; // = log(1.0);
01125   double dn = max_normalized_value-min_normalized_value;
01126   double dw = wmax-wmin;
01127   if (dw == 0.0) dw = 1;
01128   double scale = dn/dw;
01129   if (scale < 0.0){
01130     ROS_WARN("normalizeWeights: scale is %f < 0, dw=%f, dn=%f", scale, dw, dn );
01131   }
01132   double offset = -wmax*scale;
01133   double weights_sum = 0.0;
01134 
01135 #pragma omp parallel
01136   {
01137 
01138 #pragma omp for
01139     for (unsigned i = 0; i < m_particles.size(); ++i){
01140       double w = m_particles[i].weight;
01141       w = exp(scale*w+offset);
01142       assert(!isnan(w));
01143       m_particles[i].weight = w;
01144 #pragma omp atomic
01145       weights_sum += w;
01146     }
01147 
01148     assert(weights_sum > 0.0);
01149     // normalize sum to 1:
01150 #pragma omp for
01151     for (unsigned i = 0; i < m_particles.size(); ++i){
01152       m_particles[i].weight /= weights_sum;
01153     }
01154 
01155   }
01156 }
01157 
01158 double HumanoidLocalization::getCumParticleWeight() const{
01159   double cumWeight=0.0;
01160 
01161   //compute the cumulative weights
01162   for (Particles::const_iterator it = m_particles.begin(); it != m_particles.end(); ++it){
01163     cumWeight += it->weight;
01164   }
01165 
01166   return cumWeight;
01167 }
01168 
01169 void HumanoidLocalization::resample(unsigned numParticles){
01170 
01171   if (numParticles <= 0)
01172     numParticles = m_numParticles;
01173 
01174   //compute the interval
01175   double interval=getCumParticleWeight()/numParticles;
01176 
01177   //compute the initial target weight
01178   double target=interval*m_rngUniform();
01179 
01180   //compute the resampled indexes
01181   double cumWeight=0;
01182   std::vector<unsigned> indices(numParticles);
01183 
01184   unsigned n=0;
01185   for (unsigned i = 0; i < m_particles.size(); ++i){
01186     cumWeight += m_particles[i].weight;
01187     while(cumWeight > target && n < numParticles){
01188       if (m_bestParticleIdx >= 0 && i == unsigned(m_bestParticleIdx)){
01189         m_bestParticleIdx = n;
01190       }
01191 
01192       indices[n++]=i;
01193       target+=interval;
01194     }
01195   }
01196   // indices now contains the indices to draw from the particles distribution
01197 
01198   Particles oldParticles = m_particles;
01199   m_particles.resize(numParticles);
01200   m_poseArray.poses.resize(numParticles);
01201   double newWeight = 1.0/numParticles;
01202 #pragma omp parallel for
01203   for (unsigned i = 0; i < numParticles; ++i){
01204     m_particles[i].pose = oldParticles[indices[i]].pose;
01205     m_particles[i].weight = newWeight;
01206   }
01207 }
01208 
01209 void HumanoidLocalization::initGlobal(){
01210   ROS_INFO("Initializing with uniform distribution");
01211 
01212   double roll, pitch, z;
01213   initZRP(z, roll, pitch);
01214 
01215   m_mapModel->initGlobal(m_particles, z, roll, pitch, m_initNoiseStd, m_rngUniform, m_rngNormal);
01216 
01217 
01218   ROS_INFO("Global localization done");
01219   m_motionModel->reset();
01220   m_receivedSensorData = false;
01221   m_initialized = true;
01222 
01223   publishPoseEstimate(ros::Time::now(), false);
01224 
01225 }
01226 
01227 void HumanoidLocalization::publishPoseEstimate(const ros::Time& time, bool publish_eval){
01228 
01230   // send all hypotheses as arrows:
01232 
01233   m_poseArray.header.stamp = time;
01234 
01235   if (m_poseArray.poses.size() != m_particles.size())
01236     m_poseArray.poses.resize(m_particles.size());
01237 
01238 #pragma omp parallel for
01239   for (unsigned i = 0; i < m_particles.size(); ++i){
01240     tf::poseTFToMsg(m_particles[i].pose, m_poseArray.poses[i]);
01241   }
01242 
01243   m_poseArrayPub.publish(m_poseArray);
01244 
01246   // send best particle as pose and one array:
01248   geometry_msgs::PoseWithCovarianceStamped p;
01249   p.header.stamp = time;
01250   p.header.frame_id = m_globalFrameId;
01251 
01252   tf::Pose bestParticlePose;
01253   if (m_bestParticleAsMean)
01254     bestParticlePose = getMeanParticlePose();
01255   else
01256     bestParticlePose = getBestParticlePose();
01257 
01258   tf::poseTFToMsg(bestParticlePose,p.pose.pose);
01259   m_posePub.publish(p);
01260 
01261   if (publish_eval){
01262     m_poseEvalPub.publish(p);
01263   }
01264 
01265   geometry_msgs::PoseArray bestPose;
01266   bestPose.header = p.header;
01267   bestPose.poses.resize(1);
01268   tf::poseTFToMsg(bestParticlePose, bestPose.poses[0]);
01269   m_bestPosePub.publish(bestPose);
01270 
01272   // send incremental odom pose (synced to localization)
01274   tf::Stamped<tf::Pose> lastOdomPose;
01275   if (m_motionModel->getLastOdomPose(lastOdomPose)){
01276     geometry_msgs::PoseStamped odomPoseMsg;
01277     tf::poseStampedTFToMsg(lastOdomPose, odomPoseMsg);
01278     m_poseOdomPub.publish(odomPoseMsg);
01279   }
01280 
01281 
01283   // Send tf target->map (where target is typically odom)
01284   tf::Stamped<tf::Pose> targetToMapTF;
01285   try{
01286     tf::Stamped<tf::Pose> baseToMapTF(bestParticlePose.inverse(),time, m_baseFrameId);
01287     m_tfListener.transformPose(m_targetFrameId, baseToMapTF, targetToMapTF); // typically target == odom
01288   } catch (const tf::TransformException& e){
01289     ROS_WARN("Failed to subtract base to %s transform, will not publish pose estimate: %s", m_targetFrameId.c_str(), e.what());
01290     return;
01291   }
01292 
01293   tf::Transform latestTF(tf::Quaternion(targetToMapTF.getRotation()), tf::Point(targetToMapTF.getOrigin()));
01294 
01295   // We want to send a transform that is good up until a
01296   // tolerance time so that odom can be used
01297   // see ROS amcl_node
01298 
01299   ros::Duration transformTolerance(m_transformTolerance);
01300   ros::Time transformExpiration = (time + transformTolerance);
01301 
01302   tf::StampedTransform tmp_tf_stamped(latestTF.inverse(), transformExpiration, m_globalFrameId, m_targetFrameId);
01303   m_latest_transform = tmp_tf_stamped;
01304 
01305   m_tfBroadcaster.sendTransform(tmp_tf_stamped);
01306 
01307 }
01308 
01309 unsigned HumanoidLocalization::getBestParticleIdx() const{
01310   if (m_bestParticleIdx < 0 || m_bestParticleIdx >= m_numParticles){
01311     ROS_WARN("Index (%d) of best particle not valid, using 0 instead", m_bestParticleIdx);
01312     return 0;
01313   }
01314 
01315   return m_bestParticleIdx;
01316 }
01317 
01318 tf::Pose HumanoidLocalization::getParticlePose(unsigned particleIdx) const{
01319   return m_particles.at(particleIdx).pose;
01320 }
01321 
01322 tf::Pose HumanoidLocalization::getBestParticlePose() const{
01323   return getParticlePose(getBestParticleIdx());
01324 }
01325 
01326 tf::Pose HumanoidLocalization::getMeanParticlePose() const{
01327   tf::Pose meanPose = tf::Pose::getIdentity();
01328 
01329   double totalWeight = 0.0;
01330 
01331   meanPose.setBasis(tf::Matrix3x3(0,0,0,0,0,0,0,0,0));
01332   for (Particles::const_iterator it = m_particles.begin(); it != m_particles.end(); ++it){
01333     meanPose.getOrigin() += it->pose.getOrigin() * it->weight;
01334     meanPose.getBasis()[0] += it->pose.getBasis()[0];
01335     meanPose.getBasis()[1] += it->pose.getBasis()[1];
01336     meanPose.getBasis()[2] += it->pose.getBasis()[2];
01337     totalWeight += it->weight;
01338   }
01339   assert(!isnan(totalWeight));
01340 
01341   //assert(totalWeight == 1.0);
01342 
01343   // just in case weights are not normalized:
01344   meanPose.getOrigin() /= totalWeight;
01345   // TODO: only rough estimate of mean rotation, asserts normalized weights!
01346   meanPose.getBasis() = meanPose.getBasis().scaled(tf::Vector3(1.0/m_numParticles, 1.0/m_numParticles, 1.0/m_numParticles));
01347 
01348   // Apparently we need to normalize again
01349   meanPose.setRotation(meanPose.getRotation().normalized());
01350 
01351   return meanPose;
01352 }
01353 
01354 double HumanoidLocalization::nEff() const{
01355 
01356   double sqrWeights=0.0;
01357   for (Particles::const_iterator it=m_particles.begin(); it!=m_particles.end(); ++it){
01358     sqrWeights+=(it->weight * it->weight);
01359   }
01360 
01361   if (sqrWeights > 0.0)
01362     return 1./sqrWeights;
01363   else
01364     return 0.0;
01365 }
01366 
01367 void HumanoidLocalization::toLogForm(){
01368   // TODO: linear offset needed?
01369 #pragma omp parallel for
01370   for (unsigned i = 0; i < m_particles.size(); ++i){
01371     assert(m_particles[i].weight > 0.0);
01372     m_particles[i].weight = log(m_particles[i].weight);
01373   }
01374 }
01375 
01376 void HumanoidLocalization::pauseLocalizationCallback(const std_msgs::BoolConstPtr& msg){
01377   if (msg->data){
01378     if (!m_paused){
01379       m_paused = true;
01380       ROS_INFO("Localization paused");
01381     } else{
01382       ROS_WARN("Received a msg to pause localizatzion, but is already paused.");
01383     }
01384   } else{
01385     if (m_paused){
01386       m_paused = false;
01387       ROS_INFO("Localization resumed");
01388       // force laser integration:
01389       m_receivedSensorData = false;
01390     } else {
01391       ROS_WARN("Received a msg to resume localization, is not paused.");
01392     }
01393 
01394   }
01395 
01396 }
01397 
01398 bool HumanoidLocalization::pauseLocalizationSrvCallback(std_srvs::Empty::Request& req, std_srvs::Empty::Response& res)
01399 {
01400   if (!m_paused){
01401     m_paused = true;
01402     ROS_INFO("Localization paused");
01403   } else{
01404     ROS_WARN("Received a request to pause localizatzion, but is already paused.");
01405   }
01406 
01407   return true;
01408 }
01409 
01410 bool HumanoidLocalization::resumeLocalizationSrvCallback(std_srvs::Empty::Request& req, std_srvs::Empty::Response& res)
01411 {
01412   if (m_paused){
01413     m_paused = false;
01414     ROS_INFO("Localization resumed");
01415     // force next laser integration:
01416     m_receivedSensorData = false;
01417   } else {
01418     ROS_WARN("Received a request to resume localization, but is not paused.");
01419   }
01420 
01421   return true;
01422 }
01423 
01424 bool HumanoidLocalization::lookupPoseHeight(const ros::Time& t, double& poseHeight) const{
01425   tf::StampedTransform tf;
01426   if (m_motionModel->lookupLocalTransform(m_baseFootprintId, t, tf)){
01427     poseHeight = tf.getOrigin().getZ();
01428     return true;
01429   } else
01430     return false;
01431 }
01432 
01433 }
01434 


humanoid_localization
Author(s): Armin Hornung, Stefan Osswald, Daniel Maier
autogenerated on Sat Jun 8 2019 20:21:10