From b572bc11911b443941db286623edd9c9d41cc4bc Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 22 Jul 2016 15:11:08 -0400 Subject: [PATCH 01/78] rtabmap node: Added ExactTime policy for RGBD+laser callbacks --- src/CoreWrapper.cpp | 130 +++++++++++++++++++++++++++++++------------- src/CoreWrapper.h | 26 +++++++++ 2 files changed, 118 insertions(+), 38 deletions(-) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 9c82fb72..45793c79 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -2708,17 +2708,31 @@ void CoreWrapper::setupCallbacks( if(subscribeScan2d) { scanSub_.subscribe(nh, "scan", 1); - depthScanSync_ = new message_filters::Synchronizer( - MyDepthScanSyncPolicy(queueSize), - *imageSubs_[0], - odomSub_, - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scanSub_); - depthScanSync_->registerCallback(boost::bind(&CoreWrapper::depthScanCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", + if(approxSync) + { + depthScanSync_ = new message_filters::Synchronizer( + MyDepthScanSyncPolicy(queueSize), + *imageSubs_[0], + odomSub_, + *imageDepthSubs_[0], + *cameraInfoSubs_[0], + scanSub_); + depthScanSync_->registerCallback(boost::bind(&CoreWrapper::depthScanCallback, this, _1, _2, _3, _4, _5)); + } + else + { + depthScanExactSync_ = new message_filters::Synchronizer( + MyDepthScanExactSyncPolicy(queueSize), + *imageSubs_[0], + odomSub_, + *imageDepthSubs_[0], + *cameraInfoSubs_[0], + scanSub_); + depthScanExactSync_->registerCallback(boost::bind(&CoreWrapper::depthScanCallback, this, _1, _2, _3, _4, _5)); + } + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", ros::this_node::getName().c_str(), + approxSync?"approx":"exact", imageSubs_[0]->getTopic().c_str(), imageDepthSubs_[0]->getTopic().c_str(), cameraInfoSubs_[0]->getTopic().c_str(), @@ -2728,17 +2742,31 @@ void CoreWrapper::setupCallbacks( else if(subscribeScan3d) { scan3dSub_.subscribe(nh, "scan_cloud", 1); - depthScan3dSync_ = new message_filters::Synchronizer( - MyDepthScan3dSyncPolicy(queueSize), - *imageSubs_[0], - odomSub_, - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scan3dSub_); - depthScan3dSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", + if(approxSync) + { + depthScan3dSync_ = new message_filters::Synchronizer( + MyDepthScan3dSyncPolicy(queueSize), + *imageSubs_[0], + odomSub_, + *imageDepthSubs_[0], + *cameraInfoSubs_[0], + scan3dSub_); + depthScan3dSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dCallback, this, _1, _2, _3, _4, _5)); + } + else + { + depthScan3dExactSync_ = new message_filters::Synchronizer( + MyDepthScan3dExactSyncPolicy(queueSize), + *imageSubs_[0], + odomSub_, + *imageDepthSubs_[0], + *cameraInfoSubs_[0], + scan3dSub_); + depthScan3dExactSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dCallback, this, _1, _2, _3, _4, _5)); + } + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", ros::this_node::getName().c_str(), + approxSync?"approx":"exact", imageSubs_[0]->getTopic().c_str(), imageDepthSubs_[0]->getTopic().c_str(), cameraInfoSubs_[0]->getTopic().c_str(), @@ -2808,16 +2836,29 @@ void CoreWrapper::setupCallbacks( if(subscribeScan2d) { scanSub_.subscribe(nh, "scan", 1); - depthScanTFSync_ = new message_filters::Synchronizer( - MyDepthScanTFSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scanSub_); - depthScanTFSync_->registerCallback(boost::bind(&CoreWrapper::depthScanTFCallback, this, _1, _2, _3, _4)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s", + if(approxSync) + { + depthScanTFSync_ = new message_filters::Synchronizer( + MyDepthScanTFSyncPolicy(queueSize), + *imageSubs_[0], + *imageDepthSubs_[0], + *cameraInfoSubs_[0], + scanSub_); + depthScanTFSync_->registerCallback(boost::bind(&CoreWrapper::depthScanTFCallback, this, _1, _2, _3, _4)); + } + else + { + depthScanTFExactSync_ = new message_filters::Synchronizer( + MyDepthScanTFExactSyncPolicy(queueSize), + *imageSubs_[0], + *imageDepthSubs_[0], + *cameraInfoSubs_[0], + scanSub_); + depthScanTFExactSync_->registerCallback(boost::bind(&CoreWrapper::depthScanTFCallback, this, _1, _2, _3, _4)); + } + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", ros::this_node::getName().c_str(), + approxSync?"approx":"exact", imageSubs_[0]->getTopic().c_str(), imageDepthSubs_[0]->getTopic().c_str(), cameraInfoSubs_[0]->getTopic().c_str(), @@ -2826,16 +2867,29 @@ void CoreWrapper::setupCallbacks( else if(subscribeScan3d) { scan3dSub_.subscribe(nh, "scan_cloud", 1); - depthScan3dTFSync_ = new message_filters::Synchronizer( - MyDepthScan3dTFSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scan3dSub_); - depthScan3dTFSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dTFCallback, this, _1, _2, _3, _4)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s", + if(approxSync) + { + depthScan3dTFSync_ = new message_filters::Synchronizer( + MyDepthScan3dTFSyncPolicy(queueSize), + *imageSubs_[0], + *imageDepthSubs_[0], + *cameraInfoSubs_[0], + scan3dSub_); + depthScan3dTFSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dTFCallback, this, _1, _2, _3, _4)); + } + else + { + depthScan3dTFExactSync_ = new message_filters::Synchronizer( + MyDepthScan3dTFExactSyncPolicy(queueSize), + *imageSubs_[0], + *imageDepthSubs_[0], + *cameraInfoSubs_[0], + scan3dSub_); + depthScan3dTFExactSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dTFCallback, this, _1, _2, _3, _4)); + } + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", ros::this_node::getName().c_str(), + approxSync?"approx":"exact", imageSubs_[0]->getTopic().c_str(), imageDepthSubs_[0]->getTopic().c_str(), cameraInfoSubs_[0]->getTopic().c_str(), diff --git a/src/CoreWrapper.h b/src/CoreWrapper.h index e9815ab5..ddac1c92 100644 --- a/src/CoreWrapper.h +++ b/src/CoreWrapper.h @@ -323,6 +323,13 @@ private: sensor_msgs::CameraInfo, sensor_msgs::LaserScan> MyDepthScanSyncPolicy; message_filters::Synchronizer * depthScanSync_; + typedef message_filters::sync_policies::ExactTime< + sensor_msgs::Image, + nav_msgs::Odometry, + sensor_msgs::Image, + sensor_msgs::CameraInfo, + sensor_msgs::LaserScan> MyDepthScanExactSyncPolicy; + message_filters::Synchronizer * depthScanExactSync_; typedef message_filters::sync_policies::ApproximateTime< sensor_msgs::Image, @@ -331,6 +338,13 @@ private: sensor_msgs::CameraInfo, sensor_msgs::PointCloud2> MyDepthScan3dSyncPolicy; message_filters::Synchronizer * depthScan3dSync_; + typedef message_filters::sync_policies::ExactTime< + sensor_msgs::Image, + nav_msgs::Odometry, + sensor_msgs::Image, + sensor_msgs::CameraInfo, + sensor_msgs::PointCloud2> MyDepthScan3dExactSyncPolicy; + message_filters::Synchronizer * depthScan3dExactSync_; typedef message_filters::sync_policies::ApproximateTime< sensor_msgs::Image, @@ -396,6 +410,12 @@ private: sensor_msgs::CameraInfo, sensor_msgs::LaserScan> MyDepthScanTFSyncPolicy; message_filters::Synchronizer * depthScanTFSync_; + typedef message_filters::sync_policies::ExactTime< + sensor_msgs::Image, + sensor_msgs::Image, + sensor_msgs::CameraInfo, + sensor_msgs::LaserScan> MyDepthScanTFExactSyncPolicy; + message_filters::Synchronizer * depthScanTFExactSync_; typedef message_filters::sync_policies::ApproximateTime< sensor_msgs::Image, @@ -403,6 +423,12 @@ private: sensor_msgs::CameraInfo, sensor_msgs::PointCloud2> MyDepthScan3dTFSyncPolicy; message_filters::Synchronizer * depthScan3dTFSync_; + typedef message_filters::sync_policies::ExactTime< + sensor_msgs::Image, + sensor_msgs::Image, + sensor_msgs::CameraInfo, + sensor_msgs::PointCloud2> MyDepthScan3dTFExactSyncPolicy; + message_filters::Synchronizer * depthScan3dTFExactSync_; typedef message_filters::sync_policies::ApproximateTime< sensor_msgs::Image, From 500fe3b8aef9a0cb036800e6c2bf88d4f10cae2b Mon Sep 17 00:00:00 2001 From: Di Zeng Date: Wed, 27 Jul 2016 16:44:48 -0700 Subject: [PATCH 02/78] moved all the header file into include --- CMakeLists.txt | 2 +- {src => include/rtabmap_ros}/CoreWrapper.h | 0 {src => include/rtabmap_ros}/GuiWrapper.h | 0 {src => include/rtabmap_ros}/MapsManager.h | 0 {src/nodelets => include/rtabmap_ros}/OdometryROS.h | 0 {src => include/rtabmap_ros}/PreferencesDialogROS.h | 0 src/CoreNode.cpp | 2 +- src/CoreWrapper.cpp | 2 +- src/GuiNode.cpp | 2 +- src/GuiWrapper.cpp | 5 ++--- src/MapAssemblerNode.cpp | 2 +- src/MapsManager.cpp | 2 +- src/{nodelets => }/OdometryROS.cpp | 2 +- src/PreferencesDialogROS.cpp | 2 +- src/nodelets/rgbd_odometry.cpp | 4 ++-- src/nodelets/stereo_odometry.cpp | 2 +- 16 files changed, 13 insertions(+), 14 deletions(-) rename {src => include/rtabmap_ros}/CoreWrapper.h (100%) rename {src => include/rtabmap_ros}/GuiWrapper.h (100%) rename {src => include/rtabmap_ros}/MapsManager.h (100%) rename {src/nodelets => include/rtabmap_ros}/OdometryROS.h (100%) rename {src => include/rtabmap_ros}/PreferencesDialogROS.h (100%) rename src/{nodelets => }/OdometryROS.cpp (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24eb11f6..1171f470 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,7 +159,7 @@ SET(rtabmap_ros_lib_src src/nodelets/disparity_to_depth.cpp src/nodelets/obstacles_detection.cpp src/nodelets/point_cloud_aggregator.cpp - src/nodelets/OdometryROS.cpp + src/OdometryROS.cpp src/MsgConversion.cpp src/MapsManager.cpp ) diff --git a/src/CoreWrapper.h b/include/rtabmap_ros/CoreWrapper.h similarity index 100% rename from src/CoreWrapper.h rename to include/rtabmap_ros/CoreWrapper.h diff --git a/src/GuiWrapper.h b/include/rtabmap_ros/GuiWrapper.h similarity index 100% rename from src/GuiWrapper.h rename to include/rtabmap_ros/GuiWrapper.h diff --git a/src/MapsManager.h b/include/rtabmap_ros/MapsManager.h similarity index 100% rename from src/MapsManager.h rename to include/rtabmap_ros/MapsManager.h diff --git a/src/nodelets/OdometryROS.h b/include/rtabmap_ros/OdometryROS.h similarity index 100% rename from src/nodelets/OdometryROS.h rename to include/rtabmap_ros/OdometryROS.h diff --git a/src/PreferencesDialogROS.h b/include/rtabmap_ros/PreferencesDialogROS.h similarity index 100% rename from src/PreferencesDialogROS.h rename to include/rtabmap_ros/PreferencesDialogROS.h diff --git a/src/CoreNode.cpp b/src/CoreNode.cpp index 4429c125..df608f12 100644 --- a/src/CoreNode.cpp +++ b/src/CoreNode.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "CoreWrapper.h" +#include "rtabmap_ros/CoreWrapper.h" #include #include #include diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 9c82fb72..54df64f3 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "CoreWrapper.h" +#include "rtabmap_ros/CoreWrapper.h" #include #include diff --git a/src/GuiNode.cpp b/src/GuiNode.cpp index 4190ca3a..6fd74d2f 100644 --- a/src/GuiNode.cpp +++ b/src/GuiNode.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "GuiWrapper.h" +#include "rtabmap_ros/GuiWrapper.h" #include "rtabmap/utilite/ULogger.h" #include diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index 5682ae71..0b8c1db2 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "GuiWrapper.h" +#include "rtabmap_ros/GuiWrapper.h" #include #include @@ -54,8 +54,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "rtabmap_ros/GetMap.h" #include "rtabmap_ros/SetGoal.h" #include "rtabmap_ros/SetLabel.h" - -#include "PreferencesDialogROS.h" +#include "rtabmap_ros/PreferencesDialogROS.h" #include #include diff --git a/src/MapAssemblerNode.cpp b/src/MapAssemblerNode.cpp index a8055d53..8e7c295d 100644 --- a/src/MapAssemblerNode.cpp +++ b/src/MapAssemblerNode.cpp @@ -28,7 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "rtabmap_ros/MapData.h" #include "rtabmap_ros/MsgConversion.h" -#include "MapsManager.h" +#include "rtabmap_ros/MapsManager.h" #include #include #include diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index c6f2012b..7b1e2b9c 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "MapsManager.h" +#include "rtabmap_ros/MapsManager.h" #include #include diff --git a/src/nodelets/OdometryROS.cpp b/src/OdometryROS.cpp similarity index 99% rename from src/nodelets/OdometryROS.cpp rename to src/OdometryROS.cpp index 317265db..5c41c4e2 100644 --- a/src/nodelets/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "OdometryROS.h" +#include "rtabmap_ros/OdometryROS.h" #include #include diff --git a/src/PreferencesDialogROS.cpp b/src/PreferencesDialogROS.cpp index dbd7957f..d93e7152 100644 --- a/src/PreferencesDialogROS.cpp +++ b/src/PreferencesDialogROS.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "PreferencesDialogROS.h" +#include "rtabmap_ros/PreferencesDialogROS.h" #include #include #include diff --git a/src/nodelets/rgbd_odometry.cpp b/src/nodelets/rgbd_odometry.cpp index b0f480cf..8492b018 100644 --- a/src/nodelets/rgbd_odometry.cpp +++ b/src/nodelets/rgbd_odometry.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "OdometryROS.h" +#include #include #include @@ -208,7 +208,7 @@ private: return; } - ros::Time stamp = image->header.stamp>depth->header.stamp?image->header.stamp:depth->header.stamp; + ros::Time stamp = image->header.stamp > depth->header.stamp? image->header.stamp : depth->header.stamp; Transform localTransform = getTransform(this->frameId(), image->header.frame_id, stamp); if(localTransform.isNull()) diff --git a/src/nodelets/stereo_odometry.cpp b/src/nodelets/stereo_odometry.cpp index de1a1657..3c8bca95 100644 --- a/src/nodelets/stereo_odometry.cpp +++ b/src/nodelets/stereo_odometry.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "OdometryROS.h" +#include "rtabmap_ros/OdometryROS.h" #include "pluginlib/class_list_macros.h" #include "nodelet/nodelet.h" From 0f134d7ba45306586ce8d343afb7ab10f062b0c0 Mon Sep 17 00:00:00 2001 From: Di Zeng Date: Thu, 28 Jul 2016 12:09:18 -0700 Subject: [PATCH 03/78] changed dynamic cast into type function --- src/OdometryROS.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index 5c41c4e2..cd5e0eba 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -426,7 +426,7 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) } // local map / reference frame - if(odomLocalMap_.getNumSubscribers() && dynamic_cast(odometry_)) + if(odomLocalMap_.getNumSubscribers() && odometry_->isF2M()) { pcl::PointCloud cloud; const std::multimap & map = ((OdometryF2M*)odometry_)->getMap().getWords3(); @@ -443,7 +443,8 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) if(odomLastFrame_.getNumSubscribers()) { - if(dynamic_cast(odometry_)) + // check which type of Odometry is using + if(odometry_->isF2M()) // If it's Frame to Map Odometry { const std::multimap & words3 = ((OdometryF2M*)odometry_)->getLastFrame().getWords3(); if(words3.size()) @@ -463,10 +464,10 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) odomLastFrame_.publish(cloudMsg); } } - else + else if(odometry_->isF2F()) // if Using Frame to Frame Odometry { - //Frame to Frame const Signature & refFrame = ((OdometryF2F*)odometry_)->getRefFrame(); + if(refFrame.getWords3().size()) { pcl::PointCloud cloud; @@ -482,6 +483,8 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) cloudMsg.header.frame_id = odomFrameId_; odomLastFrame_.publish(cloudMsg); } + }else{ + NODELET_ERROR("ERROR, Wrong Type of Odometry, Shouldn't happen"); } } } @@ -549,7 +552,7 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) bool OdometryROS::isOdometryF2M() const { - return dynamic_cast(odometry_) != 0; + return odometry_->isF2M(); } bool OdometryROS::reset(std_srvs::Empty::Request&, std_srvs::Empty::Response&) From e5eba34d200da3d6b06152918f45183a45ebaf88 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 1 Aug 2016 15:27:50 -0400 Subject: [PATCH 04/78] Updated pull request https://github.com/introlab/rtabmap_ros/pull/104 to use Odometry::getType() --- include/rtabmap_ros/OdometryROS.h | 1 - src/OdometryROS.cpp | 11 +++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/include/rtabmap_ros/OdometryROS.h b/include/rtabmap_ros/OdometryROS.h index 3432fd26..421fb61e 100644 --- a/include/rtabmap_ros/OdometryROS.h +++ b/include/rtabmap_ros/OdometryROS.h @@ -70,7 +70,6 @@ public: const rtabmap::ParametersMap & parameters() const {return parameters_;} const tf::TransformListener & tfListener() const {return tfListener_;} bool isPaused() const {return paused_;} - bool isOdometryF2M() const; rtabmap::Transform getTransform(const std::string & fromFrameId, const std::string & toFrameId, const ros::Time & stamp) const; protected: diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index cd5e0eba..bb1d1e23 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -426,7 +426,7 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) } // local map / reference frame - if(odomLocalMap_.getNumSubscribers() && odometry_->isF2M()) + if(odomLocalMap_.getNumSubscribers() && odometry_->getType() == Odometry::kTypeF2M) { pcl::PointCloud cloud; const std::multimap & map = ((OdometryF2M*)odometry_)->getMap().getWords3(); @@ -444,7 +444,7 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) if(odomLastFrame_.getNumSubscribers()) { // check which type of Odometry is using - if(odometry_->isF2M()) // If it's Frame to Map Odometry + if(odometry_->getType() == Odometry::kTypeF2M) // If it's Frame to Map Odometry { const std::multimap & words3 = ((OdometryF2M*)odometry_)->getLastFrame().getWords3(); if(words3.size()) @@ -464,7 +464,7 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) odomLastFrame_.publish(cloudMsg); } } - else if(odometry_->isF2F()) // if Using Frame to Frame Odometry + else if(odometry_->getType() == Odometry::kTypeF2F) // if Using Frame to Frame Odometry { const Signature & refFrame = ((OdometryF2F*)odometry_)->getRefFrame(); @@ -550,11 +550,6 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) NODELET_INFO( "Odom: quality=%d, std dev=%fm, update time=%fs", info.inliers, pose.isNull()?0.0f:std::sqrt(info.variance), (ros::WallTime::now()-time).toSec()); } -bool OdometryROS::isOdometryF2M() const -{ - return odometry_->isF2M(); -} - bool OdometryROS::reset(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { NODELET_INFO( "visual_odometry: reset odom!"); From f50ffcd979fe595f5599aa751ed0f4fd46c14b78 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 2 Aug 2016 11:32:18 -0400 Subject: [PATCH 05/78] added test_map_optimizer.launch --- launch/tests/test_map_optimizer.launch | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 launch/tests/test_map_optimizer.launch diff --git a/launch/tests/test_map_optimizer.launch b/launch/tests/test_map_optimizer.launch new file mode 100644 index 00000000..82adaba2 --- /dev/null +++ b/launch/tests/test_map_optimizer.launch @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file From 3210f805fc72cd7190547406acca1c65ce36fdf6 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 2 Aug 2016 11:33:44 -0400 Subject: [PATCH 06/78] remove tabs in test_map_optimizer.launch --- launch/tests/test_map_optimizer.launch | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launch/tests/test_map_optimizer.launch b/launch/tests/test_map_optimizer.launch index 82adaba2..0dc85727 100644 --- a/launch/tests/test_map_optimizer.launch +++ b/launch/tests/test_map_optimizer.launch @@ -1,10 +1,10 @@ + --delete_db_on_start + --RGBD/OptimizeMaxError 0 + --Optimizer/Iterations 0 + --RGBD/ProximityBySpace false"/> From 30ed81a32c7e6c0176916dcd2373461801782433 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 5 Aug 2016 14:04:23 -0400 Subject: [PATCH 07/78] Added missing some sync policy initializations --- src/CoreWrapper.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 00d2b739..39cee2c9 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -104,7 +104,9 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) depthSync_(0), depthExactSync_(0), depthScanSync_(0), + depthScanExactSync_(0), depthScan3dSync_(0), + depthScan3dExactSync_(0), stereoScanSync_(0), stereoScan3dSync_(0), stereoApproxSync_(0), @@ -114,6 +116,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) depthTFExactSync_(0), depthScanTFSync_(0), depthScan3dTFSync_(0), + depthScan3dTFExactSync_(0), stereoScanTFSync_(0), stereoScan3dTFSync_(0), stereoApproxTFSync_(0), @@ -473,8 +476,12 @@ CoreWrapper::~CoreWrapper() delete depthExactSync_; if(depthScanSync_) delete depthScanSync_; + if(depthScanExactSync_) + delete depthScanExactSync_; if(depthScan3dSync_) delete depthScan3dSync_; + if(depthScan3dExactSync_) + delete depthScan3dExactSync_; if(stereoScanSync_) delete stereoScanSync_; if(stereoScan3dSync_) @@ -491,8 +498,12 @@ CoreWrapper::~CoreWrapper() delete depthTFExactSync_; if(depthScanTFSync_) delete depthScanTFSync_; + if(depthScanTFExactSync_) + delete depthScanTFExactSync_; if(depthScan3dTFSync_) delete depthScan3dTFSync_; + if(depthScan3dTFExactSync_) + delete depthScan3dTFExactSync_; if(stereoScanTFSync_) delete stereoScanTFSync_; if(stereoScan3dTFSync_) From 8ad683736bef0692e21ea45f8994a6f7998cf736 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 5 Aug 2016 14:22:45 -0400 Subject: [PATCH 08/78] rtabmapviz: using twist covariance instead of pose covariance --- src/CoreWrapper.cpp | 2 +- src/GuiWrapper.cpp | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 39cee2c9..8cc27530 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -681,7 +681,7 @@ bool CoreWrapper::commonOdomUpdate(const nav_msgs::OdometryConstPtr & odomMsg) // Only update variance if odom is not null if(!odom.isNull()) { - // using MIN in case of 3DoF mapping (maybe not parameters are set, except x and yaw for the twist) + // using MIN in case of 3DoF mapping (maybe no parameters are set, except x and yaw for the twist) float transVariance = uMax3(odomMsg->twist.covariance[0], MIN(odomMsg->twist.covariance[7], BAD_COVARIANCE), MIN(odomMsg->twist.covariance[14], BAD_COVARIANCE)); float rotVariance = uMax3(MIN(odomMsg->twist.covariance[21],BAD_COVARIANCE), MIN(odomMsg->twist.covariance[28], BAD_COVARIANCE), odomMsg->twist.covariance[35]); if(uIsFinite(rotVariance) && rotVariance > rotVariance_) diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index 0b8c1db2..026f3289 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -566,15 +566,15 @@ void GuiWrapper::commonDepthCallback( cv::Mat covariance = cv::Mat::eye(6,6,CV_64FC1); if(odomMsg.get()) { - UASSERT(odomMsg->pose.covariance.size() == 36); - if(!(odomMsg->pose.covariance[0] == 0 && - odomMsg->pose.covariance[7] == 0 && - odomMsg->pose.covariance[14] == 0 && - odomMsg->pose.covariance[21] == 0 && - odomMsg->pose.covariance[28] == 0 && - odomMsg->pose.covariance[35] == 0)) + UASSERT(odomMsg->twist.covariance.size() == 36); + if(odomMsg->twist.covariance[0] != 0 && + odomMsg->twist.covariance[7] != 0 && + odomMsg->twist.covariance[14] != 0 && + odomMsg->twist.covariance[21] != 0 && + odomMsg->twist.covariance[28] != 0 && + odomMsg->twist.covariance[35] != 0) { - covariance = cv::Mat(6,6,CV_64FC1,(void*)odomMsg->pose.covariance.data()).clone(); + covariance = cv::Mat(6,6,CV_64FC1,(void*)odomMsg->twist.covariance.data()).clone(); } } if(odomHeader.frame_id.empty()) @@ -814,15 +814,15 @@ void GuiWrapper::commonStereoCallback( cv::Mat covariance = cv::Mat::eye(6,6,CV_64FC1); if(odomMsg.get()) { - UASSERT(odomMsg->pose.covariance.size() == 36); - if(!(odomMsg->pose.covariance[0] == 0 && - odomMsg->pose.covariance[7] == 0 && - odomMsg->pose.covariance[14] == 0 && - odomMsg->pose.covariance[21] == 0 && - odomMsg->pose.covariance[28] == 0 && - odomMsg->pose.covariance[35] == 0)) + UASSERT(odomMsg->twist.covariance.size() == 36); + if(odomMsg->twist.covariance[0] != 0 && + odomMsg->twist.covariance[7] != 0 && + odomMsg->twist.covariance[14] != 0 && + odomMsg->twist.covariance[21] != 0 && + odomMsg->twist.covariance[28] != 0 && + odomMsg->twist.covariance[35] != 0) { - covariance = cv::Mat(6,6,CV_64FC1,(void*)odomMsg->pose.covariance.data()).clone(); + covariance = cv::Mat(6,6,CV_64FC1,(void*)odomMsg->twist.covariance.data()).clone(); } } if(odomHeader.frame_id.empty()) From c040d29f2de971890244699738eea9c800a41a01 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 5 Aug 2016 14:31:18 -0400 Subject: [PATCH 09/78] Fixed database not saved on exit (bug from commit 30ed81a32c7e6c0176916dcd2373461801782433) --- src/CoreWrapper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 8cc27530..d539f7e3 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -115,6 +115,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) depthTFSync_(0), depthTFExactSync_(0), depthScanTFSync_(0), + depthScanTFExactSync_(0), depthScan3dTFSync_(0), depthScan3dTFExactSync_(0), stereoScanTFSync_(0), From 4bbf2802c30bbb556d517bce10439c93db031091 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 5 Aug 2016 15:59:00 -0400 Subject: [PATCH 10/78] stereo_mapping.launch: fixed rgbd xml tag error --- launch/stereo_mapping.launch | 1 - 1 file changed, 1 deletion(-) diff --git a/launch/stereo_mapping.launch b/launch/stereo_mapping.launch index 6cbad067..78517c7e 100644 --- a/launch/stereo_mapping.launch +++ b/launch/stereo_mapping.launch @@ -44,7 +44,6 @@ - From 328f6520b2d83754580e51c2073288b9f1191e56 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 5 Aug 2016 16:18:47 -0400 Subject: [PATCH 11/78] Updated main launch files --- launch/rgbd_mapping.launch | 8 +++++--- launch/rtabmap.launch | 3 +++ launch/stereo_mapping.launch | 8 +++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/launch/rgbd_mapping.launch b/launch/rgbd_mapping.launch index 9cc04de7..60630b19 100644 --- a/launch/rgbd_mapping.launch +++ b/launch/rgbd_mapping.launch @@ -15,8 +15,8 @@ - - + + @@ -37,6 +37,7 @@ + @@ -68,7 +69,8 @@ - + + diff --git a/launch/rtabmap.launch b/launch/rtabmap.launch index 3d3f509f..33f6b5e1 100644 --- a/launch/rtabmap.launch +++ b/launch/rtabmap.launch @@ -63,6 +63,7 @@ + @@ -124,6 +125,7 @@ + @@ -158,6 +160,7 @@ + diff --git a/launch/stereo_mapping.launch b/launch/stereo_mapping.launch index 78517c7e..71487894 100644 --- a/launch/stereo_mapping.launch +++ b/launch/stereo_mapping.launch @@ -15,8 +15,8 @@ - - + + @@ -39,6 +39,7 @@ + @@ -74,7 +75,8 @@ - + + From 050cf91df3e8e76671738ed6671d256bbfe6f48a Mon Sep 17 00:00:00 2001 From: matlabbe Date: Sun, 7 Aug 2016 20:55:59 -0400 Subject: [PATCH 12/78] Added icp_odometry and rgbdicp_odometry nodes --- CMakeLists.txt | 10 +- include/rtabmap_ros/OdometryROS.h | 10 +- launch/tests/test_icp_odometry.launch | 53 ++++ nodelet_plugins.xml | 16 + package.xml | 2 +- src/CoreWrapper.cpp | 81 ++++- src/GuiWrapper.cpp | 7 +- src/ICPOdometryNode.cpp | 78 +++++ src/OdometryROS.cpp | 53 +++- src/RGBDICPOdometryNode.cpp | 78 +++++ src/nodelets/icp_odometry.cpp | 211 +++++++++++++ src/nodelets/rgbd_odometry.cpp | 32 +- src/nodelets/rgbdicp_odometry.cpp | 408 ++++++++++++++++++++++++++ src/nodelets/stereo_odometry.cpp | 14 +- 14 files changed, 1020 insertions(+), 33 deletions(-) create mode 100644 launch/tests/test_icp_odometry.launch create mode 100644 src/ICPOdometryNode.cpp create mode 100644 src/RGBDICPOdometryNode.cpp create mode 100644 src/nodelets/icp_odometry.cpp create mode 100644 src/nodelets/rgbdicp_odometry.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1171f470..4edc154d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ find_package(rviz) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) -find_package(RTABMap 0.11.8 REQUIRED) +find_package(RTABMap 0.11.9 REQUIRED) find_package(OpenCV REQUIRED) @@ -151,6 +151,8 @@ SET(Libraries SET(rtabmap_ros_lib_src src/nodelets/rgbd_odometry.cpp src/nodelets/stereo_odometry.cpp + src/nodelets/rgbdicp_odometry.cpp + src/nodelets/icp_odometry.cpp src/nodelets/data_throttle.cpp src/nodelets/stereo_throttle.cpp src/nodelets/data_odom_sync.cpp @@ -268,6 +270,12 @@ target_link_libraries(rgbd_odometry ${Libraries}) add_executable(stereo_odometry src/StereoOdometryNode.cpp) target_link_libraries(stereo_odometry ${Libraries}) +add_executable(rgbdicp_odometry src/RGBDICPOdometryNode.cpp) +target_link_libraries(rgbdicp_odometry ${Libraries}) + +add_executable(icp_odometry src/ICPOdometryNode.cpp) +target_link_libraries(icp_odometry ${Libraries}) + add_executable(map_optimizer src/MapOptimizerNode.cpp) target_link_libraries(map_optimizer rtabmap_ros ${Libraries}) diff --git a/include/rtabmap_ros/OdometryROS.h b/include/rtabmap_ros/OdometryROS.h index 421fb61e..50150d6f 100644 --- a/include/rtabmap_ros/OdometryROS.h +++ b/include/rtabmap_ros/OdometryROS.h @@ -51,7 +51,7 @@ class OdometryROS : public nodelet::Nodelet { public: - OdometryROS(bool stereo); + OdometryROS(bool stereoParams, bool visParams, bool icpParams); virtual ~OdometryROS(); void processData(const rtabmap::SensorData & data, const ros::Time & stamp); @@ -68,16 +68,17 @@ public: const std::string & frameId() const {return frameId_;} const std::string & odomFrameId() const {return odomFrameId_;} const rtabmap::ParametersMap & parameters() const {return parameters_;} - const tf::TransformListener & tfListener() const {return tfListener_;} bool isPaused() const {return paused_;} rtabmap::Transform getTransform(const std::string & fromFrameId, const std::string & toFrameId, const ros::Time & stamp) const; protected: virtual void flushCallbacks() = 0; + tf::TransformListener & tfListener() {return tfListener_;} private: virtual void onInit(); virtual void onOdomInit() = 0; + virtual void updateParameters(rtabmap::ParametersMap & parameters) {} private: rtabmap::Odometry * odometry_; @@ -86,6 +87,7 @@ private: std::string frameId_; std::string odomFrameId_; std::string groundTruthFrameId_; + std::string guessFrameId_; bool publishTf_; bool waitForTransform_; double waitForTransformDuration_; @@ -111,7 +113,9 @@ private: bool paused_; int resetCountdown_; int resetCurrentCount_; - bool stereo_; + bool stereoParams_; + bool visParams_; + bool icpParams_; }; } diff --git a/launch/tests/test_icp_odometry.launch b/launch/tests/test_icp_odometry.launch new file mode 100644 index 00000000..bb4f2a79 --- /dev/null +++ b/launch/tests/test_icp_odometry.launch @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nodelet_plugins.xml b/nodelet_plugins.xml index 58e29dd7..98fb28ec 100644 --- a/nodelet_plugins.xml +++ b/nodelet_plugins.xml @@ -15,6 +15,22 @@ This is my nodelet. + + + + This is my nodelet. + + + + + + This is my nodelet. + + rtabmap_ros - 0.11.8 + 0.11.9 RTAB-Map's ros-pkg. RTAB-Map is a RGB-D SLAM approach with real-time constraints. Mathieu Labbe Mathieu Labbe diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index d539f7e3..2418e2d0 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -790,10 +790,11 @@ Transform CoreWrapper::getTransform(const std::string & fromFrameId, const std:: if(waitForTransform_ && !stamp.isZero() && waitForTransformDuration_>0.0) { //if(!tfBuffer_.canTransform(fromFrameId, toFrameId, stamp, ros::Duration(1))) - if(!tfListener_.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransformDuration_))) + std::string errorMsg; + if(!tfListener_.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransformDuration_), ros::Duration(0.01), &errorMsg)) { - ROS_WARN("rtabmap: Could not get transform from %s to %s after %f seconds (for stamp=%f)!", - fromFrameId.c_str(), toFrameId.c_str(), waitForTransformDuration_, stamp.toSec()); + ROS_WARN("rtabmap: Could not get transform from %s to %s after %f seconds (for stamp=%f)! Error=\"%s\"", + fromFrameId.c_str(), toFrameId.c_str(), waitForTransformDuration_, stamp.toSec(), errorMsg.c_str()); return transform; } } @@ -1040,10 +1041,41 @@ void CoreWrapper::commonDepthCallback( break; } } + + // sync with odometry stamp + Transform localScanTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); + if(localScanTransform.isNull()) + { + ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + return; + } + Transform laserOdomT = localScanTransform; + if(lastPoseStamp_ != scan3dMsg->header.stamp) + { + if(!odomT.isNull()) + { + Transform sensorT = getTransform(odomFrameId, frameId_, scan3dMsg->header.stamp); + if(sensorT.isNull()) + { + ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " + "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), lastPoseStamp_.toSec()); + } + else + { + laserOdomT = odomT.inverse() * sensorT * localScanTransform; + } + + } + } + if(containNormals) { pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*scan3dMsg, *pclScan); + if(!laserOdomT.isIdentity()) + { + pclScan = util3d::transformPointCloud(pclScan, laserOdomT); + } scan = util3d::laserScanFromPointCloud(*pclScan); } else @@ -1051,6 +1083,11 @@ void CoreWrapper::commonDepthCallback( pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*scan3dMsg, *pclScan); + if(!laserOdomT.isIdentity()) + { + pclScan = util3d::transformPointCloud(pclScan, laserOdomT); + } + if(scanCloudNormalK_ > 0) { //compute normals @@ -1205,10 +1242,43 @@ void CoreWrapper::commonStereoCallback( break; } } + + // sync with odometry stamp + Transform localScanTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); + if(localScanTransform.isNull()) + { + ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + return; + } + Transform laserOdomT = localScanTransform; + if(lastPoseStamp_ != scan3dMsg->header.stamp) + { + if(!odomT.isNull()) + { + Transform sensorT = getTransform(odomFrameId, frameId_, scan3dMsg->header.stamp); + if(sensorT.isNull()) + { + ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " + "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), lastPoseStamp_.toSec()); + } + else + { + laserOdomT = odomT.inverse() * sensorT * localScanTransform; + } + + } + } + if(containNormals) { pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*scan3dMsg, *pclScan); + + if(!laserOdomT.isIdentity()) + { + pclScan = util3d::transformPointCloud(pclScan, laserOdomT); + } + scan = util3d::laserScanFromPointCloud(*pclScan); } else @@ -1216,6 +1286,11 @@ void CoreWrapper::commonStereoCallback( pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*scan3dMsg, *pclScan); + if(!laserOdomT.isIdentity()) + { + pclScan = util3d::transformPointCloud(pclScan, laserOdomT); + } + if(scanCloudNormalK_ > 0) { //compute normals diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index 026f3289..a3f51128 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -482,10 +482,11 @@ Transform GuiWrapper::getTransform(const std::string & fromFrameId, const std::s if(waitForTransform_ && !stamp.isZero() && waitForTransformDuration_ > 0.0) { //if(!tfBuffer_.canTransform(fromFrameId, toFrameId, stamp, ros::Duration(1))) - if(!tfListener_.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransformDuration_))) + std::string errorMsg; + if(!tfListener_.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransformDuration_), ros::Duration(0.01), &errorMsg)) { - ROS_WARN("rtabmapviz: Could not get transform from %s to %s after %f seconds (for stamp=%f)!", - fromFrameId.c_str(), toFrameId.c_str(), waitForTransformDuration_, stamp.toSec()); + ROS_WARN("rtabmapviz: Could not get transform from %s to %s after %f seconds (for stamp=%f)! Error=\"%s\".", + fromFrameId.c_str(), toFrameId.c_str(), waitForTransformDuration_, stamp.toSec(), errorMsg.c_str()); return transform; } } diff --git a/src/ICPOdometryNode.cpp b/src/ICPOdometryNode.cpp new file mode 100644 index 00000000..1cc106f3 --- /dev/null +++ b/src/ICPOdometryNode.cpp @@ -0,0 +1,78 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ros/ros.h" +#include "nodelet/loader.h" +#include +#include + +int main(int argc, char **argv) +{ + ULogger::setType(ULogger::kTypeConsole); + ULogger::setLevel(ULogger::kWarning); + ros::init(argc, argv, "icp_odometry"); + + // process "--params" argument + nodelet::V_string nargv; + for(int i=1;ifirst + " = \"" + iter->second + "\""; + std::cout << + str << + std::setw(60 - str.size()) << + " [" << + rtabmap::Parameters::getDescription(iter->first).c_str() << + "]" << + std::endl; + } + ROS_WARN("Node will now exit after showing default odometry parameters because " + "argument \"--params\" is detected!"); + exit(0); + } + else if(strcmp(argv[i], "--udebug") == 0) + { + ULogger::setLevel(ULogger::kDebug); + } + else if(strcmp(argv[i], "--uinfo") == 0) + { + ULogger::setLevel(ULogger::kInfo); + } + nargv.push_back(argv[i]); + } + + nodelet::Loader nodelet; + nodelet::M_string remap(ros::names::getRemappings()); + std::string nodelet_name = ros::this_node::getName(); + nodelet.load(nodelet_name, "rtabmap_ros/icp_odometry", remap, nargv); + ros::spin(); + return 0; +} diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index bb1d1e23..cbe28697 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -55,11 +55,12 @@ using namespace rtabmap; namespace rtabmap_ros { -OdometryROS::OdometryROS(bool stereo) : +OdometryROS::OdometryROS(bool stereoParams, bool visParams, bool icpParams) : odometry_(0), frameId_("base_link"), odomFrameId_("odom"), groundTruthFrameId_(""), + guessFrameId_(""), publishTf_(true), waitForTransform_(true), waitForTransformDuration_(0.1), // 100 ms @@ -68,7 +69,9 @@ OdometryROS::OdometryROS(bool stereo) : paused_(false), resetCountdown_(0), resetCurrentCount_(0), - stereo_(stereo) + stereoParams_(stereoParams), + visParams_(visParams), + icpParams_(icpParams) { } @@ -112,10 +115,13 @@ void OdometryROS::onInit() pnh.param("config_path", configPath, configPath); pnh.param("publish_null_when_lost", publishNullWhenLost_, publishNullWhenLost_); pnh.param("guess_from_tf", guessFromTf_, guessFromTf_); + pnh.param("guess_frame_id", guessFrameId_, frameId_); - if(publishTf_ && guessFromTf_) + if(publishTf_ && guessFromTf_ && guessFrameId_.compare(frameId_) == 0) { - NODELET_WARN( "\"publish_tf\" and \"guess_from_tf\" cannot be used at the same time. \"guess_from_tf\" is disabled."); + NODELET_WARN( "\"publish_tf\" and \"guess_from_tf\" cannot be used " + "at the same time if \"guess_frame_id\" and \"frame_id\" " + "are the same frame (value=\"%s\"). \"guess_from_tf\" is disabled.", frameId_.c_str()); guessFromTf_ = false; } @@ -159,7 +165,7 @@ void OdometryROS::onInit() //parameters - parameters_ = Parameters::getDefaultOdometryParameters(stereo_); + parameters_ = Parameters::getDefaultOdometryParameters(stereoParams_, visParams_, icpParams_); if(!configPath.empty()) { if(UFile::exists(configPath.c_str())) @@ -267,6 +273,9 @@ void OdometryROS::onInit() Parameters::parse(parameters_, Parameters::kOdomResetCountdown(), resetCountdown_); parameters_.at(Parameters::kOdomResetCountdown()) = "0"; // use modified reset countdown here + + this->updateParameters(parameters_); + odometry_ = Odometry::create(parameters_); if(!initialPose.isIdentity()) { @@ -295,10 +304,11 @@ Transform OdometryROS::getTransform(const std::string & fromFrameId, const std:: if(waitForTransform_ && !stamp.isZero() && waitForTransformDuration_ > 0.0) { //if(!tfBuffer_.canTransform(fromFrameId, toFrameId, stamp, ros::Duration(1))) - if(!tfListener_.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransformDuration_))) + std::string errorMsg; + if(!tfListener_.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransformDuration_), ros::Duration(0.01), &errorMsg)) { - NODELET_WARN( "odometry: Could not get transform from %s to %s (stamp=%f) after %f seconds (\"wait_for_transform_duration\"=%f)!", - fromFrameId.c_str(), toFrameId.c_str(), stamp.toSec(), waitForTransformDuration_, waitForTransformDuration_); + NODELET_WARN( "odometry: Could not get transform from %s to %s (stamp=%f) after %f seconds (\"wait_for_transform_duration\"=%f)! Error=\"%s\"", + fromFrameId.c_str(), toFrameId.c_str(), stamp.toSec(), waitForTransformDuration_, waitForTransformDuration_, errorMsg.c_str()); return transform; } } @@ -336,8 +346,9 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) Transform guess; if(guessFromTf_) { - Transform previousPose = this->getTransform(odomFrameId_, frameId_, ros::Time(odometry_->previousStamp())); - Transform pose = this->getTransform(odomFrameId_, frameId_, stamp); + ROS_WARN("Time previous=%f new=%f", odometry_->previousStamp(), stamp.toSec()); + Transform previousPose = this->getTransform(odomFrameId_, guessFrameId_, ros::Time(odometry_->previousStamp())); + Transform pose = this->getTransform(odomFrameId_, guessFrameId_, stamp); if(!previousPose.isNull() && !pose.isNull()) { guess = previousPose.inverse() * pose; @@ -352,6 +363,11 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) } NODELET_WARN( "TF Guess %s", guess.prettyPrint().c_str());*/ } + else + { + ROS_ERROR("\"guess_from_tf\" is true, but guess cannot be computed between frames \"%s\" -> \"%s\". Aborting odometry update...", odomFrameId_.c_str(), guessFrameId_.c_str()); + return; + } } // process data @@ -547,7 +563,22 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) odomInfoPub_.publish(infoMsg); } - NODELET_INFO( "Odom: quality=%d, std dev=%fm, update time=%fs", info.inliers, pose.isNull()?0.0f:std::sqrt(info.variance), (ros::WallTime::now()-time).toSec()); + if(visParams_) + { + if(icpParams_) + { + NODELET_INFO( "Odom: quality=%d, ratio=%f, std dev=%fm, update time=%fs", info.inliers, info.icpInliersRatio, pose.isNull()?0.0f:std::sqrt(info.variance), (ros::WallTime::now()-time).toSec()); + } + else + { + NODELET_INFO( "Odom: quality=%d, std dev=%fm, update time=%fs", info.inliers, pose.isNull()?0.0f:std::sqrt(info.variance), (ros::WallTime::now()-time).toSec()); + } + } + else + { + NODELET_INFO( "Odom: ratio=%f, std dev=%fm, update time=%fs", info.icpInliersRatio, pose.isNull()?0.0f:std::sqrt(info.variance), (ros::WallTime::now()-time).toSec()); + } + } bool OdometryROS::reset(std_srvs::Empty::Request&, std_srvs::Empty::Response&) diff --git a/src/RGBDICPOdometryNode.cpp b/src/RGBDICPOdometryNode.cpp new file mode 100644 index 00000000..e5fae1de --- /dev/null +++ b/src/RGBDICPOdometryNode.cpp @@ -0,0 +1,78 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ros/ros.h" +#include "nodelet/loader.h" +#include +#include + +int main(int argc, char **argv) +{ + ULogger::setType(ULogger::kTypeConsole); + ULogger::setLevel(ULogger::kWarning); + ros::init(argc, argv, "rgbdicp_odometry"); + + // process "--params" argument + nodelet::V_string nargv; + for(int i=1;ifirst + " = \"" + iter->second + "\""; + std::cout << + str << + std::setw(60 - str.size()) << + " [" << + rtabmap::Parameters::getDescription(iter->first).c_str() << + "]" << + std::endl; + } + ROS_WARN("Node will now exit after showing default odometry parameters because " + "argument \"--params\" is detected!"); + exit(0); + } + else if(strcmp(argv[i], "--udebug") == 0) + { + ULogger::setLevel(ULogger::kDebug); + } + else if(strcmp(argv[i], "--uinfo") == 0) + { + ULogger::setLevel(ULogger::kInfo); + } + nargv.push_back(argv[i]); + } + + nodelet::Loader nodelet; + nodelet::M_string remap(ros::names::getRemappings()); + std::string nodelet_name = ros::this_node::getName(); + nodelet.load(nodelet_name, "rtabmap_ros/rgbdicp_odometry", remap, nargv); + ros::spin(); + return 0; +} diff --git a/src/nodelets/icp_odometry.cpp b/src/nodelets/icp_odometry.cpp new file mode 100644 index 00000000..a046ede1 --- /dev/null +++ b/src/nodelets/icp_odometry.cpp @@ -0,0 +1,211 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include +#include + +#include + +#include +#include +#include + +#include "rtabmap_ros/MsgConversion.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace rtabmap; + +namespace rtabmap_ros +{ + +class ICPOdometry : public rtabmap_ros::OdometryROS +{ +public: + ICPOdometry() : + OdometryROS(false, false, true), + scanCloudMaxPoints_(0), + scanCloudNormalK_(0) + { + } + + virtual ~ICPOdometry() + { + } + +private: + + virtual void onOdomInit() + { + ros::NodeHandle & nh = getNodeHandle(); + ros::NodeHandle & pnh = getPrivateNodeHandle(); + + bool approxSync = true; + bool subscribeScanCloud = false; + pnh.param("approx_sync", approxSync, approxSync); + pnh.param("scan_cloud_max_points", scanCloudMaxPoints_, scanCloudMaxPoints_); + pnh.param("scan_cloud_normal_k", scanCloudNormalK_, scanCloudNormalK_); + + scan_sub_ = nh.subscribe("scan", 1, &ICPOdometry::callbackScan, this); + cloud_sub_ = nh.subscribe("scan_cloud", 1, &ICPOdometry::callbackCloud, this); + } + + virtual void updateParameters(ParametersMap & parameters) + { + //make sure we are using Reg/Strategy=0 + ParametersMap::iterator iter = parameters.find(Parameters::kRegStrategy()); + if(iter != parameters.end() && iter->second.compare("0") != 0) + { + ROS_WARN("ICP odometry works only with \"Reg/Strategy\"=1. Ignoring value %s.", iter->second.c_str()); + } + uInsert(parameters, ParametersPair(Parameters::kRegStrategy(), "1")); + } + + void callbackScan(const sensor_msgs::LaserScanConstPtr& scanMsg) + { + // make sure the frame of the laser is updated too + if(getTransform(this->frameId(), + scanMsg->header.frame_id, + scanMsg->header.stamp + ros::Duration().fromSec(scanMsg->ranges.size()*scanMsg->time_increment)).isNull()) + { + ROS_ERROR("TF of received laser scan topic at time %fs is not set, aborting odometry update.", scanMsg->header.stamp.toSec()); + return; + } + + //transform in frameId_ frame + sensor_msgs::PointCloud2 scanOut; + laser_geometry::LaserProjection projection; + projection.transformLaserScanToPointCloud(this->frameId(), *scanMsg, scanOut, this->tfListener()); + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(scanOut, *pclScan); + + cv::Mat scan = util3d::laserScan2dFromPointCloud(*pclScan); + + rtabmap::SensorData data( + scan, + (int)scanMsg->ranges.size(), + scanMsg->range_max, + cv::Mat(), + cv::Mat(), + CameraModel(), + 0, + rtabmap_ros::timestampFromROS(scanMsg->header.stamp)); + + this->processData(data, scanMsg->header.stamp); + } + + void callbackCloud(const sensor_msgs::PointCloud2ConstPtr& cloudMsg) + { + cv::Mat scan; + bool containNormals = false; + for(unsigned int i=0; ifields.size(); ++i) + { + if(cloudMsg->fields[i].name.compare("normal_x") == 0) + { + containNormals = true; + break; + } + } + + Transform localScanTransform = getTransform(this->frameId(), cloudMsg->header.frame_id, cloudMsg->header.stamp); + if(localScanTransform.isNull()) + { + ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", cloudMsg->header.stamp.toSec()); + return; + } + + if(containNormals) + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*cloudMsg, *pclScan); + if(!localScanTransform.isIdentity()) + { + pclScan = util3d::transformPointCloud(pclScan, localScanTransform); + } + scan = util3d::laserScanFromPointCloud(*pclScan); + } + else + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*cloudMsg, *pclScan); + + if(!localScanTransform.isIdentity()) + { + pclScan = util3d::transformPointCloud(pclScan, localScanTransform); + } + + if(scanCloudNormalK_ > 0) + { + //compute normals + pcl::PointCloud::Ptr normals = util3d::computeNormals(pclScan, scanCloudNormalK_); + pcl::PointCloud::Ptr pclScanNormal(new pcl::PointCloud); + pcl::concatenateFields(*pclScan, *normals, *pclScanNormal); + scan = util3d::laserScanFromPointCloud(*pclScanNormal); + } + else + { + scan = util3d::laserScanFromPointCloud(*pclScan); + } + } + + rtabmap::SensorData data( + scan, + scanCloudMaxPoints_, + 0, + cv::Mat(), + cv::Mat(), + CameraModel(), + 0, + rtabmap_ros::timestampFromROS(cloudMsg->header.stamp)); + + this->processData(data, cloudMsg->header.stamp); + } + +protected: + virtual void flushCallbacks() + { + // flush callbacks + } + +private: + ros::Subscriber scan_sub_; + ros::Subscriber cloud_sub_; + int scanCloudMaxPoints_; + int scanCloudNormalK_; +}; + +PLUGINLIB_EXPORT_CLASS(rtabmap_ros::ICPOdometry, nodelet::Nodelet); + +} diff --git a/src/nodelets/rgbd_odometry.cpp b/src/nodelets/rgbd_odometry.cpp index 8492b018..f74dd76c 100644 --- a/src/nodelets/rgbd_odometry.cpp +++ b/src/nodelets/rgbd_odometry.cpp @@ -49,6 +49,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include using namespace rtabmap; @@ -59,7 +60,7 @@ class RGBDOdometry : public rtabmap_ros::OdometryROS { public: RGBDOdometry() : - OdometryROS(false), + OdometryROS(false, true, false), approxSync_(0), exactSync_(0), sync2_(0), @@ -132,15 +133,6 @@ private: image_depth2_sub_.subscribe(depth1_it, depth1_nh.resolveName("image"), 1, hintsDepth1); info2_sub_.subscribe(rgb1_nh, "camera_info", 1); - NODELET_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - image_mono_sub_.getTopic().c_str(), - image_depth_sub_.getTopic().c_str(), - info_sub_.getTopic().c_str(), - image_mono2_sub_.getTopic().c_str(), - image_depth2_sub_.getTopic().c_str(), - info2_sub_.getTopic().c_str()); - sync2_ = new message_filters::Synchronizer( MySync2Policy(queueSize_), image_mono_sub_, @@ -150,6 +142,14 @@ private: image_depth2_sub_, info2_sub_); sync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2, _3, _4, _5, _6)); + NODELET_INFO("\n%s subscribed to (approx sync):\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", + ros::this_node::getName().c_str(), + image_mono_sub_.getTopic().c_str(), + image_depth_sub_.getTopic().c_str(), + info_sub_.getTopic().c_str(), + image_mono2_sub_.getTopic().c_str(), + image_depth2_sub_.getTopic().c_str(), + info2_sub_.getTopic().c_str()); } else { @@ -186,6 +186,17 @@ private: } } + virtual void updateParameters(ParametersMap & parameters) + { + //make sure we are using Reg/Strategy=0 + ParametersMap::iterator iter = parameters.find(Parameters::kRegStrategy()); + if(iter != parameters.end() && iter->second.compare("0") != 0) + { + ROS_WARN("RGBD odometry works only with \"Reg/Strategy\"=0. Ignoring value %s.", iter->second.c_str()); + } + uInsert(parameters, ParametersPair(Parameters::kRegStrategy(), "0")); + } + void callback( const sensor_msgs::ImageConstPtr& image, const sensor_msgs::ImageConstPtr& depth, @@ -208,6 +219,7 @@ private: return; } + // use the highest stamp to make sure that there will be no future interpolation required when synchronized with another node ros::Time stamp = image->header.stamp > depth->header.stamp? image->header.stamp : depth->header.stamp; Transform localTransform = getTransform(this->frameId(), image->header.frame_id, stamp); diff --git a/src/nodelets/rgbdicp_odometry.cpp b/src/nodelets/rgbdicp_odometry.cpp new file mode 100644 index 00000000..3431b8d6 --- /dev/null +++ b/src/nodelets/rgbdicp_odometry.cpp @@ -0,0 +1,408 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "rtabmap_ros/MsgConversion.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace rtabmap; + +namespace rtabmap_ros +{ + +class RGBDICPOdometry : public rtabmap_ros::OdometryROS +{ +public: + RGBDICPOdometry() : + OdometryROS(false, true, true), + approxScanSync_(0), + exactScanSync_(0), + approxCloudSync_(0), + exactCloudSync_(0), + queueSize_(5), + scanCloudMaxPoints_(0), + scanCloudNormalK_(0) + { + } + + virtual ~RGBDICPOdometry() + { + if(approxScanSync_) + { + delete approxScanSync_; + } + if(exactScanSync_) + { + delete exactScanSync_; + } + if(approxCloudSync_) + { + delete approxCloudSync_; + } + if(exactCloudSync_) + { + delete exactCloudSync_; + } + } + +private: + + virtual void onOdomInit() + { + ros::NodeHandle & nh = getNodeHandle(); + ros::NodeHandle & pnh = getPrivateNodeHandle(); + + bool approxSync = true; + bool subscribeScanCloud = false; + pnh.param("approx_sync", approxSync, approxSync); + pnh.param("queue_size", queueSize_, queueSize_); + pnh.param("subscribe_scan_cloud", subscribeScanCloud, subscribeScanCloud); + pnh.param("scan_cloud_max_points", scanCloudMaxPoints_, scanCloudMaxPoints_); + pnh.param("scan_cloud_normal_k", scanCloudNormalK_, scanCloudNormalK_); + + ros::NodeHandle rgb_nh(nh, "rgb"); + ros::NodeHandle depth_nh(nh, "depth"); + ros::NodeHandle rgb_pnh(pnh, "rgb"); + ros::NodeHandle depth_pnh(pnh, "depth"); + image_transport::ImageTransport rgb_it(rgb_nh); + image_transport::ImageTransport depth_it(depth_nh); + image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); + image_transport::TransportHints hintsDepth("raw", ros::TransportHints(), depth_pnh); + + image_mono_sub_.subscribe(rgb_it, rgb_nh.resolveName("image"), 1, hintsRgb); + image_depth_sub_.subscribe(depth_it, depth_nh.resolveName("image"), 1, hintsDepth); + info_sub_.subscribe(rgb_nh, "camera_info", 1); + + if(subscribeScanCloud) + { + cloud_sub_.subscribe(nh, "scan_cloud", 1); + if(approxSync) + { + approxCloudSync_ = new message_filters::Synchronizer(MyApproxCloudSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_, cloud_sub_); + approxCloudSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackCloud, this, _1, _2, _3, _4)); + } + else + { + exactCloudSync_ = new message_filters::Synchronizer(MyExactCloudSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_, cloud_sub_); + exactCloudSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackCloud, this, _1, _2, _3, _4)); + } + + NODELET_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s, \n %s", + ros::this_node::getName().c_str(), + approxSync?"approx":"exact", + image_mono_sub_.getTopic().c_str(), + image_depth_sub_.getTopic().c_str(), + info_sub_.getTopic().c_str(), + cloud_sub_.getTopic().c_str()); + } + else + { + scan_sub_.subscribe(nh, "scan", 1); + if(approxSync) + { + approxScanSync_ = new message_filters::Synchronizer(MyApproxScanSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_, scan_sub_); + approxScanSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackScan, this, _1, _2, _3, _4)); + } + else + { + exactScanSync_ = new message_filters::Synchronizer(MyExactScanSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_, scan_sub_); + exactScanSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackScan, this, _1, _2, _3, _4)); + } + + NODELET_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s, \n %s", + ros::this_node::getName().c_str(), + approxSync?"approx":"exact", + image_mono_sub_.getTopic().c_str(), + image_depth_sub_.getTopic().c_str(), + info_sub_.getTopic().c_str(), + scan_sub_.getTopic().c_str()); + } + } + + virtual void updateParameters(ParametersMap & parameters) + { + //make sure we are using Reg/Strategy=0 + ParametersMap::iterator iter = parameters.find(Parameters::kRegStrategy()); + if(iter != parameters.end() && iter->second.compare("0") != 0) + { + ROS_WARN("RGBDICP odometry works only with \"Reg/Strategy\"=2. Ignoring value %s.", iter->second.c_str()); + } + uInsert(parameters, ParametersPair(Parameters::kRegStrategy(), "2")); + } + + void callback( + const sensor_msgs::ImageConstPtr& image, + const sensor_msgs::ImageConstPtr& depth, + const sensor_msgs::CameraInfoConstPtr& cameraInfo) + { + sensor_msgs::LaserScanConstPtr scanMsg; + sensor_msgs::PointCloud2ConstPtr cloudMsg; + callbackCommon(image, depth, cameraInfo, scanMsg, cloudMsg); + } + + void callbackScan( + const sensor_msgs::ImageConstPtr& image, + const sensor_msgs::ImageConstPtr& depth, + const sensor_msgs::CameraInfoConstPtr& cameraInfo, + const sensor_msgs::LaserScanConstPtr& scanMsg) + { + sensor_msgs::PointCloud2ConstPtr cloudMsg; + callbackCommon(image, depth, cameraInfo, scanMsg, cloudMsg); + } + + void callbackCloud( + const sensor_msgs::ImageConstPtr& image, + const sensor_msgs::ImageConstPtr& depth, + const sensor_msgs::CameraInfoConstPtr& cameraInfo, + const sensor_msgs::PointCloud2ConstPtr& cloudMsg) + { + sensor_msgs::LaserScanConstPtr scanMsg; + callbackCommon(image, depth, cameraInfo, scanMsg, cloudMsg); + } + + void callbackCommon( + const sensor_msgs::ImageConstPtr& image, + const sensor_msgs::ImageConstPtr& depth, + const sensor_msgs::CameraInfoConstPtr& cameraInfo, + const sensor_msgs::LaserScanConstPtr& scanMsg, + const sensor_msgs::PointCloud2ConstPtr& cloudMsg) + { + if(!this->isPaused()) + { + if(!(image->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) ==0 || + image->encoding.compare(sensor_msgs::image_encodings::MONO8) ==0 || + image->encoding.compare(sensor_msgs::image_encodings::MONO16) ==0 || + image->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || + image->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || + !(depth->encoding.compare(sensor_msgs::image_encodings::TYPE_16UC1)==0 || + depth->encoding.compare(sensor_msgs::image_encodings::TYPE_32FC1)==0 || + depth->encoding.compare(sensor_msgs::image_encodings::MONO16)==0)) + { + NODELET_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8 (mono8 " + "recommended) and image_depth=16UC1,32FC1,mono16. Types detected: %s %s", + image->encoding.c_str(), depth->encoding.c_str()); + return; + } + + // use the highest stamp to make sure that there will be no future interpolation required when synchronized with another node + ros::Time stamp = image->header.stamp > depth->header.stamp? image->header.stamp : depth->header.stamp; + if(scanMsg.get() != 0) + { + if(stamp < scanMsg->header.stamp) + { + stamp = scanMsg->header.stamp; + } + } + else if(cloudMsg.get() != 0) + { + if(stamp < cloudMsg->header.stamp) + { + stamp = cloudMsg->header.stamp; + } + } + + Transform localTransform = getTransform(this->frameId(), image->header.frame_id, stamp); + if(localTransform.isNull()) + { + return; + } + + if(image->data.size() && depth->data.size() && cameraInfo->K[4] != 0) + { + rtabmap::CameraModel rtabmapModel = rtabmap_ros::cameraModelFromROS(*cameraInfo, localTransform); + cv_bridge::CvImagePtr ptrImage = cv_bridge::toCvCopy(image, image->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1)==0?"":"mono8"); + cv_bridge::CvImagePtr ptrDepth = cv_bridge::toCvCopy(depth); + + cv::Mat scan; + if(scanMsg.get() != 0) + { + // make sure the frame of the laser is updated too + if(getTransform(this->frameId(), + scanMsg->header.frame_id, + scanMsg->header.stamp + ros::Duration().fromSec(scanMsg->ranges.size()*scanMsg->time_increment)).isNull()) + { + ROS_ERROR("TF of received laser scan topic at time %fs is not set, aborting odometry update.", scanMsg->header.stamp.toSec()); + return; + } + + //transform in frameId_ frame + sensor_msgs::PointCloud2 scanOut; + laser_geometry::LaserProjection projection; + projection.transformLaserScanToPointCloud(this->frameId(), *scanMsg, scanOut, this->tfListener()); + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(scanOut, *pclScan); + + scan = util3d::laserScan2dFromPointCloud(*pclScan); + } + else if(cloudMsg.get() != 0) + { + bool containNormals = false; + for(unsigned int i=0; ifields.size(); ++i) + { + if(cloudMsg->fields[i].name.compare("normal_x") == 0) + { + containNormals = true; + break; + } + } + Transform localScanTransform = getTransform(this->frameId(), cloudMsg->header.frame_id, cloudMsg->header.stamp); + if(localScanTransform.isNull()) + { + ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", cloudMsg->header.stamp.toSec()); + return; + } + + if(containNormals) + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*cloudMsg, *pclScan); + if(!localScanTransform.isIdentity()) + { + pclScan = util3d::transformPointCloud(pclScan, localScanTransform); + } + scan = util3d::laserScanFromPointCloud(*pclScan); + } + else + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*cloudMsg, *pclScan); + + if(!localScanTransform.isIdentity()) + { + pclScan = util3d::transformPointCloud(pclScan, localScanTransform); + } + + if(scanCloudNormalK_ > 0) + { + //compute normals + pcl::PointCloud::Ptr normals = util3d::computeNormals(pclScan, scanCloudNormalK_); + pcl::PointCloud::Ptr pclScanNormal(new pcl::PointCloud); + pcl::concatenateFields(*pclScan, *normals, *pclScanNormal); + scan = util3d::laserScanFromPointCloud(*pclScanNormal); + } + else + { + scan = util3d::laserScanFromPointCloud(*pclScan); + } + } + } + + rtabmap::SensorData data( + scan, + scanMsg.get() != 0?(int)scanMsg->ranges.size():cloudMsg.get() != 0?scanCloudMaxPoints_:0, + scanMsg.get() != 0?scanMsg->range_max:0, + ptrImage->image, + ptrDepth->image, + rtabmapModel, + 0, + rtabmap_ros::timestampFromROS(stamp)); + + this->processData(data, stamp); + } + } + } + +protected: + virtual void flushCallbacks() + { + // flush callbacks + if(approxScanSync_) + { + delete approxScanSync_; + approxScanSync_ = new message_filters::Synchronizer(MyApproxScanSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_, scan_sub_); + approxScanSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackScan, this, _1, _2, _3, _4)); + } + if(exactScanSync_) + { + delete exactScanSync_; + exactScanSync_ = new message_filters::Synchronizer(MyExactScanSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_, scan_sub_); + exactScanSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackScan, this, _1, _2, _3, _4)); + } + if(approxCloudSync_) + { + delete approxCloudSync_; + approxCloudSync_ = new message_filters::Synchronizer(MyApproxCloudSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_, cloud_sub_); + approxCloudSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackCloud, this, _1, _2, _3, _4)); + } + if(exactCloudSync_) + { + delete exactCloudSync_; + exactCloudSync_ = new message_filters::Synchronizer(MyExactCloudSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_, cloud_sub_); + exactCloudSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackCloud, this, _1, _2, _3, _4)); + } + } + +private: + image_transport::SubscriberFilter image_mono_sub_; + image_transport::SubscriberFilter image_depth_sub_; + message_filters::Subscriber info_sub_; + message_filters::Subscriber scan_sub_; + message_filters::Subscriber cloud_sub_; + typedef message_filters::sync_policies::ApproximateTime MyApproxScanSyncPolicy; + message_filters::Synchronizer * approxScanSync_; + typedef message_filters::sync_policies::ApproximateTime MyExactScanSyncPolicy; + message_filters::Synchronizer * exactScanSync_; + typedef message_filters::sync_policies::ApproximateTime MyApproxCloudSyncPolicy; + message_filters::Synchronizer * approxCloudSync_; + typedef message_filters::sync_policies::ApproximateTime MyExactCloudSyncPolicy; + message_filters::Synchronizer * exactCloudSync_; + int queueSize_; + int scanCloudMaxPoints_; + int scanCloudNormalK_; +}; + +PLUGINLIB_EXPORT_CLASS(rtabmap_ros::RGBDICPOdometry, nodelet::Nodelet); + +} diff --git a/src/nodelets/stereo_odometry.cpp b/src/nodelets/stereo_odometry.cpp index 3c8bca95..23620d2e 100644 --- a/src/nodelets/stereo_odometry.cpp +++ b/src/nodelets/stereo_odometry.cpp @@ -47,6 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include using namespace rtabmap; @@ -57,7 +58,7 @@ class StereoOdometry : public rtabmap_ros::OdometryROS { public: StereoOdometry() : - rtabmap_ros::OdometryROS(true), + rtabmap_ros::OdometryROS(true, true, false), approxSync_(0), exactSync_(0), queueSize_(5) @@ -122,6 +123,17 @@ private: cameraInfoRight_.getTopic().c_str()); } + virtual void updateParameters(ParametersMap & parameters) + { + //make sure we are using Reg/Strategy=0 + ParametersMap::iterator iter = parameters.find(Parameters::kRegStrategy()); + if(iter != parameters.end() && iter->second.compare("0") != 0) + { + ROS_WARN("Stereo odometry works only with \"Reg/Strategy\"=0. Ignoring value %s.", iter->second.c_str()); + } + uInsert(parameters, ParametersPair(Parameters::kRegStrategy(), "0")); + } + void callback( const sensor_msgs::ImageConstPtr& imageRectLeft, const sensor_msgs::ImageConstPtr& imageRectRight, From bc76e4ec4e2d0ad440cedd73dd6d365f8cdadb35 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 8 Aug 2016 11:21:02 -0400 Subject: [PATCH 13/78] removed a warning msg --- src/OdometryROS.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index cbe28697..8c633e51 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -346,7 +346,6 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) Transform guess; if(guessFromTf_) { - ROS_WARN("Time previous=%f new=%f", odometry_->previousStamp(), stamp.toSec()); Transform previousPose = this->getTransform(odomFrameId_, guessFrameId_, ros::Time(odometry_->previousStamp())); Transform pose = this->getTransform(odomFrameId_, guessFrameId_, stamp); if(!previousPose.isNull() && !pose.isNull()) From d6377eb5135acfca4d2a4dc3e6429513bc6187cd Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 8 Aug 2016 14:37:32 -0400 Subject: [PATCH 14/78] OdomInfo msg: added local scan map field. OdometryROS: publishing odom_local_scan_map --- include/rtabmap_ros/OdometryROS.h | 1 + msg/OdomInfo.msg | 5 +++++ src/MsgConversion.cpp | 6 ++++++ src/OdometryROS.cpp | 21 +++++++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/include/rtabmap_ros/OdometryROS.h b/include/rtabmap_ros/OdometryROS.h index 50150d6f..768ab57c 100644 --- a/include/rtabmap_ros/OdometryROS.h +++ b/include/rtabmap_ros/OdometryROS.h @@ -98,6 +98,7 @@ private: ros::Publisher odomPub_; ros::Publisher odomInfoPub_; ros::Publisher odomLocalMap_; + ros::Publisher odomLocalScanMap_; ros::Publisher odomLastFrame_; ros::ServiceServer resetSrv_; ros::ServiceServer resetToPoseSrv_; diff --git a/msg/OdomInfo.msg b/msg/OdomInfo.msg index e3107457..0162088a 100644 --- a/msg/OdomInfo.msg +++ b/msg/OdomInfo.msg @@ -30,6 +30,7 @@ int32 inliers float32 variance int32 features int32 localMapSize +int32 localScanMapSize float32 timeEstimation float32 timeParticleFiltering float32 stamp @@ -52,3 +53,7 @@ int32[] cornerInliers geometry_msgs/Transform transform geometry_msgs/Transform transformFiltered +# compressed local scan map data +# use rtabmap::util3d::uncompressData() from "rtabmap/core/util3d.h" +uint8[] localScanMap + diff --git a/src/MsgConversion.cpp b/src/MsgConversion.cpp index 6d1ff691..ec2bec02 100644 --- a/src/MsgConversion.cpp +++ b/src/MsgConversion.cpp @@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include #include @@ -711,6 +712,7 @@ rtabmap::OdometryInfo odomInfoFromROS(const rtabmap_ros::OdomInfo & msg) info.features = msg.features; info.inliers = msg.inliers; info.localMapSize = msg.localMapSize; + info.localScanMapSize = msg.localScanMapSize; info.timeEstimation = msg.timeEstimation; info.variance = msg.variance; info.timeParticleFiltering = msg.timeParticleFiltering; @@ -742,6 +744,8 @@ rtabmap::OdometryInfo odomInfoFromROS(const rtabmap_ros::OdomInfo & msg) info.localMap.insert(std::make_pair(msg.localMapKeys[i], point3fFromROS(msg.localMapValues[i]))); } + info.localScanMap = rtabmap::uncompressData(msg.localScanMap); + return info; } @@ -752,6 +756,7 @@ void odomInfoToROS(const rtabmap::OdometryInfo & info, rtabmap_ros::OdomInfo & m msg.features = info.features; msg.inliers = info.inliers; msg.localMapSize = info.localMapSize; + msg.localScanMapSize = info.localScanMapSize; msg.timeEstimation = info.timeEstimation; msg.variance = info.variance; msg.timeParticleFiltering = info.timeParticleFiltering; @@ -777,6 +782,7 @@ void odomInfoToROS(const rtabmap::OdometryInfo & info, rtabmap_ros::OdomInfo & m msg.localMapKeys = uKeys(info.localMap); points3fToROS(uValues(info.localMap), msg.localMapValues); + msg.localScanMap = rtabmap::compressData(info.localScanMap); } } diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index 8c633e51..2e0db238 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -39,6 +39,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include #include @@ -98,6 +99,7 @@ void OdometryROS::onInit() odomPub_ = nh.advertise("odom", 1); odomInfoPub_ = nh.advertise("odom_info", 1); odomLocalMap_ = nh.advertise("odom_local_map", 1); + odomLocalScanMap_ = nh.advertise("odom_local_scan_map", 1); odomLastFrame_ = nh.advertise("odom_last_frame", 1); Transform initialPose = Transform::getIdentity(); @@ -502,6 +504,25 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) NODELET_ERROR("ERROR, Wrong Type of Odometry, Shouldn't happen"); } } + + if(odomLocalScanMap_.getNumSubscribers() && !info.localScanMap.empty()) + { + sensor_msgs::PointCloud2 cloudMsg; + if(info.localScanMap.channels() == 6) + { + pcl::PointCloud::Ptr cloud = util3d::laserScanToPointCloudNormal(info.localScanMap); + pcl::toROSMsg(*cloud, cloudMsg); + } + else + { + pcl::PointCloud::Ptr cloud = util3d::laserScanToPointCloud(info.localScanMap); + pcl::toROSMsg(*cloud, cloudMsg); + } + + cloudMsg.header.stamp = stamp; // use corresponding time stamp to image + cloudMsg.header.frame_id = odomFrameId_; + odomLocalScanMap_.publish(cloudMsg); + } } else if(publishNullWhenLost_) { From 26de167d75d1b46fef4cd0b0baa4316c0e6efee0 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 10 Aug 2016 15:57:03 -0400 Subject: [PATCH 15/78] icp_odometry: cleanup... --- src/nodelets/icp_odometry.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/nodelets/icp_odometry.cpp b/src/nodelets/icp_odometry.cpp index a046ede1..72aeee4b 100644 --- a/src/nodelets/icp_odometry.cpp +++ b/src/nodelets/icp_odometry.cpp @@ -72,9 +72,6 @@ private: ros::NodeHandle & nh = getNodeHandle(); ros::NodeHandle & pnh = getPrivateNodeHandle(); - bool approxSync = true; - bool subscribeScanCloud = false; - pnh.param("approx_sync", approxSync, approxSync); pnh.param("scan_cloud_max_points", scanCloudMaxPoints_, scanCloudMaxPoints_); pnh.param("scan_cloud_normal_k", scanCloudNormalK_, scanCloudNormalK_); From 0930c816ba214031b6f4787e8ea7391620c3d71c Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 11 Aug 2016 09:58:48 -0400 Subject: [PATCH 16/78] Update MapOptimizerNode.cpp --- src/MapOptimizerNode.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MapOptimizerNode.cpp b/src/MapOptimizerNode.cpp index 64e5522a..18d3b531 100644 --- a/src/MapOptimizerNode.cpp +++ b/src/MapOptimizerNode.cpp @@ -254,10 +254,10 @@ public: { optimizedPoses = poses; } - else if(poses.size() || constraints.size()) + else if(poses.size() == 0 && constraints.size()) { - ROS_ERROR("map_optimizer: Poses=%d and edges=%d (poses must " - "not be null if there are edges, and edges must be null if poses <= 1)", + ROS_ERROR("map_optimizer: Poses=%d and edges=%d: poses must " + "not be null if there are edges.", (int)poses.size(), (int)constraints.size()); } From 3ae00d6ad94b496d2dbd3b09e70ecc066291f6b0 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 11 Aug 2016 14:58:49 -0400 Subject: [PATCH 17/78] rtabmap: added "ground_truth_base_frame_id" parameter --- include/rtabmap_ros/CoreWrapper.h | 1 + src/CoreWrapper.cpp | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/rtabmap_ros/CoreWrapper.h b/include/rtabmap_ros/CoreWrapper.h index ddac1c92..a4421773 100644 --- a/include/rtabmap_ros/CoreWrapper.h +++ b/include/rtabmap_ros/CoreWrapper.h @@ -268,6 +268,7 @@ private: std::string mapFrameId_; std::string odomFrameId_; std::string groundTruthFrameId_; + std::string groundTruthBaseFrameId_; std::string configPath_; std::string databasePath_; bool waitForTransform_; diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 2418e2d0..c18ca3cb 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -88,6 +88,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) mapFrameId_("map"), odomFrameId_(""), groundTruthFrameId_(""), // e.g., "world" + groundTruthBaseFrameId_(""), // e.g., "base_link_gt" configPath_(""), databasePath_(UDirectory::homeDir()+"/.ros/"+rtabmap::Parameters::getDefaultDatabaseName()), waitForTransform_(true), @@ -179,6 +180,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) pnh.param("map_frame_id", mapFrameId_, mapFrameId_); pnh.param("odom_frame_id", odomFrameId_, odomFrameId_); // set to use odom from TF pnh.param("ground_truth_frame_id", groundTruthFrameId_, groundTruthFrameId_); + pnh.param("ground_truth_base_frame_id", groundTruthBaseFrameId_, frameId_); pnh.param("depth_cameras", depthCameras, depthCameras); pnh.param("queue_size", queueSize, queueSize); if(pnh.hasParam("stereo_approx_sync") && !pnh.hasParam("approx_sync")) @@ -225,6 +227,10 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) { groundTruthFrameId_ = tfPrefix+"/"+groundTruthFrameId_; } + if(!groundTruthBaseFrameId_.empty()) + { + groundTruthBaseFrameId_ = tfPrefix+"/"+groundTruthBaseFrameId_; + } // keep worldFrameId_ without prefix as it should be global } @@ -240,7 +246,9 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) } if(!groundTruthFrameId_.empty()) { - ROS_INFO("rtabmap: ground_truth_frame_id = %s", groundTruthFrameId_.c_str()); + ROS_INFO("rtabmap: ground_truth_frame_id = %s -> ground_truth_base_frame_id = %s", + groundTruthFrameId_.c_str(), + groundTruthBaseFrameId_.c_str()); } ROS_INFO("rtabmap: map_frame_id = %s", mapFrameId_.c_str()); ROS_INFO("rtabmap: queue_size = %d", queueSize); @@ -1114,7 +1122,7 @@ void CoreWrapper::commonDepthCallback( Transform groundTruthPose; if(!groundTruthFrameId_.empty()) { - groundTruthPose = getTransform(groundTruthFrameId_, frameId_, stamp); + groundTruthPose = getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, stamp); } SensorData data(scan, @@ -1340,7 +1348,7 @@ void CoreWrapper::commonStereoCallback( Transform groundTruthPose; if(!groundTruthFrameId_.empty()) { - groundTruthPose = getTransform(groundTruthFrameId_, frameId_, stamp); + groundTruthPose = getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, stamp); } SensorData data(scan, From b980adf8a6dd577c7399e8d6f9bb3931936e7ed5 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 12 Aug 2016 11:37:29 -0400 Subject: [PATCH 18/78] Added missing descriptors in NodeData msg --- msg/NodeData.msg | 6 +++++- src/MsgConversion.cpp | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/msg/NodeData.msg b/msg/NodeData.msg index 2265fd71..f9ce1d0d 100644 --- a/msg/NodeData.msg +++ b/msg/NodeData.msg @@ -42,4 +42,8 @@ uint8[] userData # std::multimap int32[] wordIds KeyPoint[] wordKpts -sensor_msgs/PointCloud2 wordPts \ No newline at end of file +sensor_msgs/PointCloud2 wordPts + +# compressed descriptors +# use rtabmap::util3d::uncompressData() from "rtabmap/core/util3d.h" +uint8[] descriptors diff --git a/src/MsgConversion.cpp b/src/MsgConversion.cpp index ec2bec02..9d8f8431 100644 --- a/src/MsgConversion.cpp +++ b/src/MsgConversion.cpp @@ -505,11 +505,14 @@ rtabmap::Signature nodeDataFromROS(const rtabmap_ros::NodeData & msg) //Features stuff... std::multimap words; std::multimap words3D; + std::multimap wordsDescriptors; pcl::PointCloud cloud; + cv::Mat descriptors; if(msg.wordPts.data.size() && msg.wordPts.height*msg.wordPts.width == msg.wordIds.size()) { pcl::fromROSMsg(msg.wordPts, cloud); + descriptors = rtabmap::uncompressData(msg.descriptors); } for(unsigned int i=0; isecond.cols, + signature.getWordsDescriptors().begin()->second.type()); + index = 0; + bool valid = true; + for(std::multimap::const_iterator jter=signature.getWordsDescriptors().begin(); + jter!=signature.getWordsDescriptors().end() && valid; + ++jter) + { + if(jter->second.cols == descriptors.cols && + jter->second.type() == descriptors.type()) + { + jter->second.copyTo(descriptors.row(index++)); + } + else + { + valid = false; + ROS_ERROR("Some descriptors have different type/size! Cannot copy them..."); + } + } + + if(valid) + { + msg.descriptors = rtabmap::compressData(descriptors); + } + } + else if(signature.getWordsDescriptors().size()) + { + ROS_ERROR("Words and descriptors must have the same size (%d vs %d)!", + (int)signature.getWords().size(), + (int)signature.getWordsDescriptors().size()); + } } rtabmap::Signature nodeInfoFromROS(const rtabmap_ros::NodeData & msg) From ececc79cc8a5ce95d35a2d11cf79af02cd1e6c56 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Sun, 14 Aug 2016 15:01:13 -0400 Subject: [PATCH 19/78] MapsManager: Added "grid_footprint_radius" parameter to clear obstacles under the path in the occupancy grid map --- include/rtabmap_ros/MapsManager.h | 1 + src/MapsManager.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/rtabmap_ros/MapsManager.h b/include/rtabmap_ros/MapsManager.h index def6173c..6c1cb72e 100644 --- a/include/rtabmap_ros/MapsManager.h +++ b/include/rtabmap_ros/MapsManager.h @@ -103,6 +103,7 @@ private: double gridCellSize_; double gridSize_; bool gridEroded_; + double footprintRadius_; bool gridUnknownSpaceFilled_; double gridMaxUnknownSpaceFilledRange_; double mapFilterRadius_; diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index 7b1e2b9c..32f2cb6c 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -76,6 +76,7 @@ MapsManager::MapsManager(bool usePublicNamespace) : gridCellSize_(0.05), // meters gridSize_(0), // meters gridEroded_(false), + footprintRadius_(0.0), gridUnknownSpaceFilled_(false), gridMaxUnknownSpaceFilledRange_(6.0), mapFilterRadius_(0.0), @@ -140,6 +141,7 @@ MapsManager::MapsManager(bool usePublicNamespace) : } pnh.param("grid_size", gridSize_, gridSize_); // m pnh.param("grid_eroded", gridEroded_, gridEroded_); + pnh.param("grid_footprint_radius", footprintRadius_, footprintRadius_); pnh.param("grid_unknown_space_filled", gridUnknownSpaceFilled_, gridUnknownSpaceFilled_); pnh.param("grid_unknown_space_filled_max_range", gridMaxUnknownSpaceFilledRange_, gridMaxUnknownSpaceFilledRange_); @@ -1125,7 +1127,8 @@ cv::Mat MapsManager::generateProjMap( gridCellSize_, xMin, yMin, gridSize_, - gridEroded_); + gridEroded_, + footprintRadius_); } cv::Mat MapsManager::generateGridMap( @@ -1141,7 +1144,8 @@ cv::Mat MapsManager::generateGridMap( gridCellSize_, xMin, yMin, gridSize_, - gridEroded_); + gridEroded_, + footprintRadius_); return map; } From 2b9400e4a1d861d93df29550c0c1c6f86e8cb965 Mon Sep 17 00:00:00 2001 From: Mathieu Labbe Date: Mon, 15 Aug 2016 18:44:45 -0400 Subject: [PATCH 20/78] Added obstacle_detection_indoor nodelet. obstacles_detection nodelet now publish ground/obstacle clouds in original frame of the input topic. --- CMakeLists.txt | 2 + nodelet_plugins.xml | 9 + src/nodelets/obstacles_detection.cpp | 21 +- src/nodelets/obstacles_detection_indoor.cpp | 267 ++++++++++++++++++++ 4 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 src/nodelets/obstacles_detection_indoor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4edc154d..33c3d21f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,7 @@ SET(rtabmap_ros_lib_src src/nodelets/point_cloud_xyz.cpp src/nodelets/disparity_to_depth.cpp src/nodelets/obstacles_detection.cpp + src/nodelets/obstacles_detection_indoor.cpp src/nodelets/point_cloud_aggregator.cpp src/OdometryROS.cpp src/MsgConversion.cpp @@ -400,3 +401,4 @@ ENDIF(costmap_2d_FOUND) ## Add folders to be run by python nosetests # catkin_add_nosetests(test) + diff --git a/nodelet_plugins.xml b/nodelet_plugins.xml index 98fb28ec..7277518b 100644 --- a/nodelet_plugins.xml +++ b/nodelet_plugins.xml @@ -87,6 +87,14 @@ This is my nodelet. + + + + This is my nodelet. + + + diff --git a/src/nodelets/obstacles_detection.cpp b/src/nodelets/obstacles_detection.cpp index 0e7dc91e..1eab4d2b 100644 --- a/src/nodelets/obstacles_detection.cpp +++ b/src/nodelets/obstacles_detection.cpp @@ -293,6 +293,20 @@ private: *obstaclesCloud += *obstacles2; } } + + if(!localTransform.isIdentity()) + { + //transform back in topic frame + rtabmap::Transform localTransformInv = localTransform.inverse(); + if(groundCloud->size()) + { + groundCloud = rtabmap::util3d::transformPointCloud(groundCloud, localTransformInv); + } + if(obstaclesCloud->size()) + { + obstaclesCloud = rtabmap::util3d::transformPointCloud(obstaclesCloud, localTransformInv); + } + } } } @@ -300,8 +314,7 @@ private: { sensor_msgs::PointCloud2 rosCloud; pcl::toROSMsg(*groundCloud, rosCloud); - rosCloud.header.stamp = cloudMsg->header.stamp; - rosCloud.header.frame_id = frameId_; + rosCloud.header = cloudMsg->header; //publish the message groundPub_.publish(rosCloud); @@ -311,8 +324,7 @@ private: { sensor_msgs::PointCloud2 rosCloud; pcl::toROSMsg(*obstaclesCloud, rosCloud); - rosCloud.header.stamp = cloudMsg->header.stamp; - rosCloud.header.frame_id = frameId_; + rosCloud.header = cloudMsg->header; //publish the message obstaclesPub_.publish(rosCloud); @@ -357,3 +369,4 @@ private: PLUGINLIB_EXPORT_CLASS(rtabmap_ros::ObstaclesDetection, nodelet::Nodelet); } + diff --git a/src/nodelets/obstacles_detection_indoor.cpp b/src/nodelets/obstacles_detection_indoor.cpp new file mode 100644 index 00000000..4becd750 --- /dev/null +++ b/src/nodelets/obstacles_detection_indoor.cpp @@ -0,0 +1,267 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "rtabmap/core/util3d_filtering.h" +#include "rtabmap/core/util3d_transforms.h" + +namespace rtabmap_ros +{ + +class ObstaclesDetectionIndoor : public nodelet::Nodelet +{ +public: + ObstaclesDetectionIndoor() : + frameId_(""), + minObstaclesHeight_(0.0), // if >0.0 -> disabled + maxObstaclesHeight_(0.0), // if <=0.0 -> disabled + minGroundHeight_(-0.05), + maxGroundHeight_(0.05), + waitForTransform_(false), + projVoxelSize_(0.01), + noiseFilterRadius_(0.0), // if<=0.0 -> disabled + noiseFilterMinNeighbors_(5) + {} + + virtual ~ObstaclesDetectionIndoor() + {} + +private: + virtual void onInit() + { + ros::NodeHandle & nh = getNodeHandle(); + ros::NodeHandle & pnh = getPrivateNodeHandle(); + + int queueSize = 10; + pnh.param("queue_size", queueSize, queueSize); + pnh.param("frame_id", frameId_, frameId_); + + pnh.param("min_obstacles_height", minObstaclesHeight_, minObstaclesHeight_); + pnh.param("max_obstacles_height", maxObstaclesHeight_, maxObstaclesHeight_); + pnh.param("min_ground_height", minGroundHeight_, minGroundHeight_); + pnh.param("max_ground_height", maxGroundHeight_, maxGroundHeight_); + pnh.param("noise_filter_radius", noiseFilterRadius_, noiseFilterRadius_); + pnh.param("noise_filter_min_neighbors", noiseFilterMinNeighbors_, noiseFilterMinNeighbors_); + pnh.param("wait_for_transform", waitForTransform_, waitForTransform_); + pnh.param("proj_voxel_size", projVoxelSize_, projVoxelSize_); + + cloudSub_ = nh.subscribe("cloud", 1, &ObstaclesDetectionIndoor::callback, this); + + groundPub_ = nh.advertise("ground", 1); + obstaclesPub_ = nh.advertise("obstacles", 1); + projObstaclesPub_ = nh.advertise("proj_obstacles", 1); + } + + + + void callback(const sensor_msgs::PointCloud2ConstPtr & cloudMsg) + { + ros::WallTime time = ros::WallTime::now(); + + if (groundPub_.getNumSubscribers() == 0 && obstaclesPub_.getNumSubscribers() == 0 && projObstaclesPub_.getNumSubscribers() == 0) + { + // no one wants the results + return; + } + + rtabmap::Transform localTransform; + try + { + if(waitForTransform_) + { + if(!tfListener_.waitForTransform(frameId_, cloudMsg->header.frame_id, cloudMsg->header.stamp, ros::Duration(1))) + { + NODELET_ERROR("Could not get transform from %s to %s after 1 second!", frameId_.c_str(), cloudMsg->header.frame_id.c_str()); + return; + } + } + tf::StampedTransform tmp; + tfListener_.lookupTransform(frameId_, cloudMsg->header.frame_id, cloudMsg->header.stamp, tmp); + localTransform = rtabmap_ros::transformFromTF(tmp); + } + catch(tf::TransformException & ex) + { + NODELET_ERROR("%s",ex.what()); + return; + } + + pcl::PointCloud::Ptr originalCloud(new pcl::PointCloud); + pcl::fromROSMsg(*cloudMsg, *originalCloud); + + pcl::PointCloud::Ptr obstaclesCloud(new pcl::PointCloud); + pcl::PointCloud::Ptr groundCloud(new pcl::PointCloud); + pcl::PointCloud::Ptr projectedObstaclesCloud(new pcl::PointCloud); + + if(originalCloud->size()) + { + originalCloud = rtabmap::util3d::transformPointCloud(originalCloud, localTransform); + + pcl::IndicesPtr indices(new std::vector); + indices->resize(originalCloud->size()); + for(unsigned int i=0; isize(); ++i) + { + indices->at(i) = i; + } + + // segmentation + pcl::IndicesPtr groundIndices = rtabmap::util3d::passThrough(originalCloud, indices, "z", minGroundHeight_, maxGroundHeight_); + pcl::IndicesPtr obstacleIndices = rtabmap::util3d::extractIndices(originalCloud, groundIndices, true); + + if(minObstaclesHeight_ <= 0.0 || maxObstaclesHeight_ > 0.0) + { + // std::numeric_limits::lowest() exists only for c++11 + obstacleIndices = rtabmap::util3d::passThrough(originalCloud, obstacleIndices, "z", + minObstaclesHeight_>0.0?std::numeric_limits::min():minObstaclesHeight_, + maxObstaclesHeight_<=0.0?std::numeric_limits::max():maxObstaclesHeight_); + } + + // Do optional radius filtering to remove some noise + if(noiseFilterRadius_ > 0.0 && noiseFilterMinNeighbors_ > 0) + { + if(groundIndices->size()) + { + groundIndices = rtabmap::util3d::radiusFiltering(originalCloud, groundIndices, noiseFilterRadius_, noiseFilterMinNeighbors_); + } + if(obstacleIndices->size()) + { + obstacleIndices = rtabmap::util3d::radiusFiltering(originalCloud, obstacleIndices, noiseFilterRadius_, noiseFilterMinNeighbors_); + } + } + + if(projObstaclesPub_.getNumSubscribers() && obstacleIndices->size()) + { + projectedObstaclesCloud->resize(obstacleIndices->size()); + for(unsigned int i=0; isize(); ++i) + { + projectedObstaclesCloud->points[i] = originalCloud->at(obstacleIndices->at(i)); + projectedObstaclesCloud->points[i].z = 0; + } + + if(projVoxelSize_ > 0.0) + { + projectedObstaclesCloud = rtabmap::util3d::voxelize(projectedObstaclesCloud, projVoxelSize_); + } + } + + if(!localTransform.isIdentity()) + { + //transform back in topic frame + rtabmap::Transform localTransformInv = localTransform.inverse(); + if(groundIndices->size()) + { + pcl::transformPointCloud(*originalCloud, *groundIndices, *groundCloud, localTransformInv.toEigen3f()); + } + if(obstacleIndices->size()) + { + pcl::transformPointCloud(*originalCloud, *obstacleIndices, *obstaclesCloud, localTransformInv.toEigen3f()); + } + } + else + { + if(groundIndices->size()) + { + pcl::copyPointCloud(*originalCloud, *groundIndices, *groundCloud); + } + if(obstacleIndices->size()) + { + pcl::copyPointCloud(*originalCloud, *obstacleIndices, *obstaclesCloud); + } + } + } + + if(groundPub_.getNumSubscribers()) + { + sensor_msgs::PointCloud2 rosCloud; + pcl::toROSMsg(*groundCloud, rosCloud); + rosCloud.header = cloudMsg->header; + + //publish the message + groundPub_.publish(rosCloud); + } + + if(obstaclesPub_.getNumSubscribers()) + { + sensor_msgs::PointCloud2 rosCloud; + pcl::toROSMsg(*obstaclesCloud, rosCloud); + rosCloud.header = cloudMsg->header; + + //publish the message + obstaclesPub_.publish(rosCloud); + } + + if(projObstaclesPub_.getNumSubscribers()) + { + sensor_msgs::PointCloud2 rosCloud; + pcl::toROSMsg(*projectedObstaclesCloud, rosCloud); + rosCloud.header.stamp = cloudMsg->header.stamp; + rosCloud.header.frame_id = frameId_; + + //publish the message + projObstaclesPub_.publish(rosCloud); + } + + NODELET_DEBUG("Obstacle segmentation time = %f s", (ros::WallTime::now() - time).toSec()); + } + +private: + std::string frameId_; + double minObstaclesHeight_; + double maxObstaclesHeight_; + double minGroundHeight_; + double maxGroundHeight_; + bool waitForTransform_; + double projVoxelSize_; + double noiseFilterRadius_; + int noiseFilterMinNeighbors_; + + tf::TransformListener tfListener_; + + ros::Publisher groundPub_; + ros::Publisher obstaclesPub_; + ros::Publisher projObstaclesPub_; + + ros::Subscriber cloudSub_; +}; + +PLUGINLIB_EXPORT_CLASS(rtabmap_ros::ObstaclesDetectionIndoor, nodelet::Nodelet); +} + + From 75d0711d703c4971d567b2c3bc7b316e9f6560f4 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 19 Aug 2016 15:31:15 -0400 Subject: [PATCH 21/78] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 29df0d5e..0eb1e7ba 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ $ git pull origin master $ cd build $ make $ make install +// Do "sudo make install" if you installed rtabmap in "/usr/local" $ roscd rtabmap_ros $ git pull origin master From 41158c5a263ae6704da4f129036bf2cb5ce04032 Mon Sep 17 00:00:00 2001 From: lesterlo Date: Fri, 26 Aug 2016 16:38:37 +0800 Subject: [PATCH 22/78] 1. Fix compile (link) error of missing ros image_geometry --- CMakeLists.txt | 4 ++-- package.xml | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33c3d21f..1c6614ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ find_package(catkin REQUIRED COMPONENTS cv_bridge roscpp rospy sensor_msgs std_msgs std_srvs nav_msgs geometry_msgs visualization_msgs image_transport tf tf_conversions tf2_ros eigen_conversions laser_geometry pcl_conversions pcl_ros nodelet dynamic_reconfigure message_filters class_loader - genmsg stereo_msgs move_base_msgs + genmsg stereo_msgs move_base_msgs image_geometry ) # Optional components @@ -123,7 +123,7 @@ catkin_package( CATKIN_DEPENDS cv_bridge roscpp rospy sensor_msgs std_msgs std_srvs nav_msgs geometry_msgs visualization_msgs image_transport tf tf_conversions tf2_ros eigen_conversions laser_geometry pcl_conversions pcl_ros nodelet dynamic_reconfigure message_filters class_loader - stereo_msgs move_base_msgs ${optional_dependencies} + stereo_msgs move_base_msgs image_geometry ${optional_dependencies} DEPENDS RTABMap OpenCV ) diff --git a/package.xml b/package.xml index 2e46aeb0..c815632b 100644 --- a/package.xml +++ b/package.xml @@ -39,7 +39,8 @@ move_base_msgs costmap_2d octomap_ros - + image_geometry + cv_bridge roscpp rospy @@ -68,6 +69,7 @@ move_base_msgs costmap_2d octomap_ros + image_geometry libpcl-all-dev From 2f6f874542b4b5d2b99d273e9189a7e0fa80530a Mon Sep 17 00:00:00 2001 From: matlabbe Date: Sun, 21 Aug 2016 19:38:28 -0400 Subject: [PATCH 23/78] 0.11.10: Updated LaserScanInfo interface. Updated NodeData msg with occupancy grids and laser scan local transform. MapsManager is now using directly the occupancy grids saved in nodes for 3D cloud, octomap and grid map. --- CMakeLists.txt | 2 +- include/rtabmap_ros/CoreWrapper.h | 11 +- include/rtabmap_ros/MapsManager.h | 53 +- msg/NodeData.msg | 8 + package.xml | 2 +- src/CoreNode.cpp | 5 +- src/CoreWrapper.cpp | 313 +++++---- src/GuiWrapper.cpp | 148 +++- src/MapAssemblerNode.cpp | 3 - src/MapsManager.cpp | 1037 +++++++++++------------------ src/MsgConversion.cpp | 83 ++- src/nodelets/icp_odometry.cpp | 22 +- src/nodelets/rgbdicp_odometry.cpp | 25 +- 13 files changed, 800 insertions(+), 912 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c6614ec..76b532dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ find_package(rviz) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) -find_package(RTABMap 0.11.9 REQUIRED) +find_package(RTABMap 0.11.10 REQUIRED) find_package(OpenCV REQUIRED) diff --git a/include/rtabmap_ros/CoreWrapper.h b/include/rtabmap_ros/CoreWrapper.h index a4421773..c6d6e7d5 100644 --- a/include/rtabmap_ros/CoreWrapper.h +++ b/include/rtabmap_ros/CoreWrapper.h @@ -77,6 +77,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include typedef actionlib::SimpleActionClient MoveBaseClient; +namespace rtabmap { +class StereoDense; +} + class CoreWrapper { public: @@ -226,7 +230,8 @@ private: bool setLogInfo(std_srvs::Empty::Request&, std_srvs::Empty::Response&); bool setLogWarn(std_srvs::Empty::Request&, std_srvs::Empty::Response&); bool setLogError(std_srvs::Empty::Request&, std_srvs::Empty::Response&); - bool getMapCallback(rtabmap_ros::GetMap::Request& req, rtabmap_ros::GetMap::Response& res); + bool getMapDataCallback(rtabmap_ros::GetMap::Request& req, rtabmap_ros::GetMap::Response& res); + bool getMapCallback(nav_msgs::GetMap::Request &req, nav_msgs::GetMap::Response &res); bool getProjMapCallback(nav_msgs::GetMap::Request &req, nav_msgs::GetMap::Response &res); bool getGridMapCallback(nav_msgs::GetMap::Request &req, nav_msgs::GetMap::Response &res); bool publishMapCallback(rtabmap_ros::PublishMap::Request&, rtabmap_ros::PublishMap::Response&); @@ -239,7 +244,7 @@ private: bool octomapFullCallback(octomap_msgs::GetOctomap::Request &req, octomap_msgs::GetOctomap::Response &res); #endif - rtabmap::ParametersMap loadParameters(const std::string & configFile); + void loadParameters(const std::string & configFile, rtabmap::ParametersMap & parameters); void saveParameters(const std::string & configFile); void publishLoop(double tfDelay, double tfTolerance); @@ -489,6 +494,7 @@ private: ros::ServiceServer setLogErrorSrv_; ros::ServiceServer getMapDataSrv_; ros::ServiceServer getProjMapSrv_; + ros::ServiceServer getMapSrv_; ros::ServiceServer getGridMapSrv_; ros::ServiceServer publishMapDataSrv_; ros::ServiceServer setGoalSrv_; @@ -504,6 +510,7 @@ private: boost::thread* transformThread_; + bool stereoToDepth_; float rate_; bool createIntermediateNodes_; ros::Time time_; diff --git a/include/rtabmap_ros/MapsManager.h b/include/rtabmap_ros/MapsManager.h index 6c1cb72e..f665bb69 100644 --- a/include/rtabmap_ros/MapsManager.h +++ b/include/rtabmap_ros/MapsManager.h @@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define MAPSMANAGER_H_ #include +#include #include #include #include @@ -37,6 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace rtabmap { class OctoMap; class Memory; +class OccupancyGrid; } // namespace rtabmap @@ -46,6 +48,8 @@ public: virtual ~MapsManager(); void clear(); bool hasSubscribers() const; + void backwardCompatibilityParameters(rtabmap::ParametersMap & parameters) const; + void setParameters(const rtabmap::ParametersMap & parameters); std::map getFilteredPoses( const std::map & poses); @@ -53,10 +57,7 @@ public: std::map updateMapCaches( const std::map & poses, const rtabmap::Memory * memory, - bool updateCloud, - bool updateProj, bool updateGrid, - bool updateScan, bool updateOctomap, const std::map & signatures = std::map()); @@ -65,12 +66,6 @@ public: const ros::Time & stamp, const std::string & mapFrameId); - cv::Mat generateProjMap( - const std::map & filteredPoses, - float & xMin, - float & yMin, - float & gridCellSize); - cv::Mat generateGridMap( const std::map & filteredPoses, float & xMin, @@ -81,37 +76,20 @@ public: private: // mapping stuff - int cloudDecimation_; - double cloudMaxDepth_; - double cloudMinDepth_; - double cloudVoxelSize_; - double cloudFloorCullingHeight_; - double cloudCeilingCullingHeight_; bool cloudOutputVoxelized_; - bool cloudFrustumCulling_; - double cloudNoiseFilteringRadius_; - int cloudNoiseFilteringMinNeighbors_; - int scanDecimation_; - double scanVoxelSize_; - bool scanOutputVoxelized_; - double projMaxGroundAngle_; - int projMinClusterSize_; - double projMaxObstaclesHeight_; - double projMaxGroundHeight_; - bool projDetectFlatObstacles_; - bool projMapFrame_; double gridCellSize_; + bool gridIncremental_; double gridSize_; bool gridEroded_; double footprintRadius_; - bool gridUnknownSpaceFilled_; - double gridMaxUnknownSpaceFilledRange_; double mapFilterRadius_; double mapFilterAngle_; bool mapCacheCleanup_; bool negativePosesIgnored_; ros::Publisher cloudMapPub_; + ros::Publisher cloudGroundPub_; + ros::Publisher cloudObstaclesPub_; ros::Publisher projMapPub_; ros::Publisher gridMapPub_; ros::Publisher scanMapPub_; @@ -121,15 +99,22 @@ private: ros::Publisher octoMapEmptySpace_; ros::Publisher octoMapProj_; - std::map::Ptr > clouds_; - std::map::Ptr > scans_; - std::map > cameraModels_; - std::map > projMaps_; // + std::map assembledGroundPoses_; + std::map assembledObstaclePoses_; + pcl::PointCloud::Ptr assembledObstacles_; + pcl::PointCloud::Ptr assembledGround_; + + std::map gridPoses_; + cv::Mat gridMap_; std::map > gridMaps_; // + std::map gridMapsViewpoints_; + + rtabmap::OccupancyGrid * occupancyGrid_; rtabmap::OctoMap * octomap_; int octomapTreeDepth_; - bool octomapGroundIsObstacle_; + + rtabmap::ParametersMap parameters_; }; #endif /* MAPSMANAGER_H_ */ diff --git a/msg/NodeData.msg b/msg/NodeData.msg index f9ce1d0d..ed560c50 100644 --- a/msg/NodeData.msg +++ b/msg/NodeData.msg @@ -33,11 +33,19 @@ geometry_msgs/Transform[] localTransform uint8[] laserScan int32 laserScanMaxPts float32 laserScanMaxRange +geometry_msgs/Transform laserScanLocalTransform # compressed user data # use rtabmap::util3d::uncompressData() from "rtabmap/core/util3d.h" uint8[] userData +# compressed occupancy grid +# use rtabmap::util3d::uncompressData() from "rtabmap/core/util3d.h" +uint8[] grid_ground +uint8[] grid_obstacles +float32 grid_cell_size +Point3f grid_view_point + # std::multimap # std::multimap int32[] wordIds diff --git a/package.xml b/package.xml index c815632b..6eab9a07 100644 --- a/package.xml +++ b/package.xml @@ -1,7 +1,7 @@ rtabmap_ros - 0.11.9 + 0.11.10 RTAB-Map's ros-pkg. RTAB-Map is a RGB-D SLAM approach with real-time constraints. Mathieu Labbe Mathieu Labbe diff --git a/src/CoreNode.cpp b/src/CoreNode.cpp index df608f12..eacf6972 100644 --- a/src/CoreNode.cpp +++ b/src/CoreNode.cpp @@ -51,9 +51,8 @@ int main(int argc, char** argv) else if(strcmp(argv[i], "--params") == 0 || strcmp(argv[i], "--params-all") == 0) { rtabmap::ParametersMap parameters = rtabmap::Parameters::getDefaultParameters(); - uInsert(parameters, - std::make_pair(rtabmap::Parameters::kRtabmapWorkingDirectory(), - UDirectory::homeDir()+"/.ros")); // change default to ~/.ros + uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kRGBDCreateOccupancyGrid(), "true")); // default true in ROS + uInsert(parameters, rtabmap::ParametersPair(rtabmap::Parameters::kRtabmapWorkingDirectory(), UDirectory::homeDir()+"/.ros")); // change default to ~/.ros if(strcmp(argv[i], "--params") == 0) { diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index c18ca3cb..2cb8f85a 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -52,6 +52,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include @@ -124,6 +125,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) stereoApproxTFSync_(0), stereoExactTFSync_(0), transformThread_(0), + stereoToDepth_(false), rate_(Parameters::defaultRtabmapDetectionRate()), createIntermediateNodes_(Parameters::defaultRtabmapCreateIntermediateNodes()), time_(ros::Time::now()), @@ -208,6 +210,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) pnh.param("scan_cloud_max_points", scanCloudMaxPoints_, scanCloudMaxPoints_); pnh.param("scan_cloud_normal_k", scanCloudNormalK_, scanCloudNormalK_); pnh.param("flip_scan", flipScan_, flipScan_); + pnh.param("stereo_to_depth", stereoToDepth_, stereoToDepth_); if(!tfPrefix.empty()) { @@ -285,12 +288,15 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) databasePath_ = UDirectory::currentDir(true) + databasePath_; } + ParametersMap allParameters = Parameters::getDefaultParameters(); + uInsert(allParameters, ParametersPair(Parameters::kRGBDCreateOccupancyGrid(), "true")); // default true in ROS + uInsert(allParameters, ParametersPair(Parameters::kRtabmapWorkingDirectory(), UDirectory::homeDir()+"/.ros")); // change default to ~/.ros + // load parameters - parameters_ = loadParameters(configPath_); + loadParameters(configPath_, parameters_); // update parameters with user input parameters (private) - uInsert(parameters_, std::make_pair(Parameters::kRtabmapWorkingDirectory(), UDirectory::homeDir()+"/.ros")); // change default to ~/.ros - for(ParametersMap::iterator iter=parameters_.begin(); iter!=parameters_.end(); ++iter) + for(ParametersMap::iterator iter=allParameters.begin(); iter!=allParameters.end(); ++iter) { std::string vStr; bool vBool; @@ -299,31 +305,31 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) if(pnh.getParam(iter->first, vStr)) { ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), vStr.c_str()); - iter->second = vStr; if(iter->first.compare(Parameters::kRtabmapWorkingDirectory()) == 0) { - iter->second = uReplaceChar(iter->second, '~', UDirectory::homeDir()); + vStr = uReplaceChar(vStr, '~', UDirectory::homeDir()); } else if(iter->first.compare(Parameters::kKpDictionaryPath()) == 0) { - iter->second = uReplaceChar(iter->second, '~', UDirectory::homeDir()); + vStr = uReplaceChar(vStr, '~', UDirectory::homeDir()); } + uInsert(parameters_, ParametersPair(iter->first, vStr)); } else if(pnh.getParam(iter->first, vBool)) { ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uBool2Str(vBool).c_str()); - iter->second = uBool2Str(vBool); + uInsert(parameters_, ParametersPair(iter->first, uBool2Str(vBool))); } else if(pnh.getParam(iter->first, vDouble)) { ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vDouble).c_str()); - iter->second = uNumber2Str(vDouble); + uInsert(parameters_, ParametersPair(iter->first, uNumber2Str(vDouble))); } else if(pnh.getParam(iter->first, vInt)) { ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vInt).c_str()); - iter->second = uNumber2Str(vInt); + uInsert(parameters_, ParametersPair(iter->first, uNumber2Str(vInt))); } } @@ -345,7 +351,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) if(iter->second.first) { // can be migrated - parameters_.at(iter->second.second)= vStr; + uInsert(parameters_, ParametersPair(iter->second.second, vStr)); ROS_WARN("Rtabmap: Parameter name changed: \"%s\" -> \"%s\". Please update your launch file accordingly. Value \"%s\" is still set to the new parameter name.", iter->first.c_str(), iter->second.second.c_str(), vStr.c_str()); } @@ -365,6 +371,25 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) } } + // Backward compatibility (MapsManager) + mapsManager_.backwardCompatibilityParameters(parameters_); + + if((subscribeScan2d || subscribeScan3d) && parameters_.find(Parameters::kGridFromDepth()) == parameters_.end()) + { + ROS_WARN("Setting \"%s\" parameter to false (default true) as \"subscribe_scan\" or \"subscribe_scan_cloud\" is " + "true. The occupancy grid map will be constructed from " + "laser scans. To get occupancy grid map from cloud projection, set \"%s\" " + "to true. To suppress this warning, " + "add ", + Parameters::kGridFromDepth().c_str(), + Parameters::kGridFromDepth().c_str(), + Parameters::kGridFromDepth().c_str()); + parameters_.insert(ParametersPair(Parameters::kGridFromDepth(), "false")); + } + + // Add all other parameters (not copied if already exists) + parameters_.insert(allParameters.begin(), allParameters.end()); + // set public parameters nh.setParam("is_rtabmap_paused", paused_); for(ParametersMap::iterator iter=parameters_.begin(); iter!=parameters_.end(); ++iter) @@ -391,9 +416,9 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) if(!subscribeDepth && !subscribeStereo) { ROS_WARN("ROS param subscribe_depth and subscribe_stereo are false, but RTAB-Map " - "parameter \"RGBD/Enabled\" is true! Please set subscribe_depth or subscribe_stereo " - "to true to use rtabmap node for RGB-D SLAM, or set \"RGBD/Enabled\" to false for loop closure " - "detection on images-only."); + "parameter \"%s\" is true! Please set subscribe_depth or subscribe_stereo " + "to true to use rtabmap node for RGB-D SLAM, or set \"%s\" to false for loop closure " + "detection on images-only.", Parameters::kRGBDEnabled().c_str(), Parameters::kRGBDEnabled().c_str()); } } @@ -419,6 +444,12 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) ROS_INFO("rtabmap: database_path parameter not set, the map will not be saved."); } + mapsManager_.setParameters(parameters_); + if(subscribeStereo) + { + ROS_INFO("rtabmap: stereo_to_depth = %s", stereoToDepth_?"true":"false"); + } + // Init RTAB-Map rtabmap_.init(parameters_, databasePath_); @@ -436,7 +467,8 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) backupDatabase_ = nh.advertiseService("backup", &CoreWrapper::backupDatabaseCallback, this); setModeLocalizationSrv_ = nh.advertiseService("set_mode_localization", &CoreWrapper::setModeLocalizationCallback, this); setModeMappingSrv_ = nh.advertiseService("set_mode_mapping", &CoreWrapper::setModeMappingCallback, this); - getMapDataSrv_ = nh.advertiseService("get_map", &CoreWrapper::getMapCallback, this); + getMapDataSrv_ = nh.advertiseService("get_map_data", &CoreWrapper::getMapDataCallback, this); + getMapSrv_ = nh.advertiseService("get_map", &CoreWrapper::getMapCallback, this); getGridMapSrv_ = nh.advertiseService("get_grid_map", &CoreWrapper::getGridMapCallback, this); getProjMapSrv_ = nh.advertiseService("get_proj_map", &CoreWrapper::getProjMapCallback, this); publishMapDataSrv_ = nh.advertiseService("publish_map", &CoreWrapper::publishMapCallback, this); @@ -550,9 +582,8 @@ CoreWrapper::~CoreWrapper() printf("rtabmap: Saving database/long-term memory... (located at %s)\n", databasePath_.c_str()); } -ParametersMap CoreWrapper::loadParameters(const std::string & configFile) +void CoreWrapper::loadParameters(const std::string & configFile, ParametersMap & parameters) { - ParametersMap parameters = Parameters::getDefaultParameters(); if(!configFile.empty()) { ROS_INFO("Loading parameters from %s", configFile.c_str()); @@ -562,9 +593,6 @@ ParametersMap CoreWrapper::loadParameters(const std::string & configFile) } Parameters::readINI(configFile.c_str(), parameters); } - // otherwise take default parameters - - return parameters; } void CoreWrapper::saveParameters(const std::string & configFile) @@ -993,12 +1021,14 @@ void CoreWrapper::commonDepthCallback( } cv::Mat scan; + Transform scanLocalTransform = Transform::getIdentity(); if(scan2dMsg.get() != 0) { // make sure the frame of the laser is updated too - if(getTransform(frameId_, + scanLocalTransform = getTransform(frameId_, scan2dMsg->header.frame_id, - scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)).isNull()) + scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)); + if(scanLocalTransform.isNull()) { ROS_ERROR("TF of received laser scan topic at time %fs is not set, aborting rtabmap update.", scan2dMsg->header.stamp.toSec()); return; @@ -1007,7 +1037,7 @@ void CoreWrapper::commonDepthCallback( //transform in frameId_ frame sensor_msgs::PointCloud2 scanOut; laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(frameId_, *scan2dMsg, scanOut, tfListener_); + projection.transformLaserScanToPointCloud(scan2dMsg->header.frame_id, *scan2dMsg, scanOut, tfListener_); pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(scanOut, *pclScan); @@ -1024,8 +1054,7 @@ void CoreWrapper::commonDepthCallback( } else { - Transform t = odomT.inverse() * sensorT; - pclScan = util3d::transformPointCloud(pclScan, t); + scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; } } @@ -1051,13 +1080,12 @@ void CoreWrapper::commonDepthCallback( } // sync with odometry stamp - Transform localScanTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); - if(localScanTransform.isNull()) + scanLocalTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); + if(scanLocalTransform.isNull()) { ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); return; } - Transform laserOdomT = localScanTransform; if(lastPoseStamp_ != scan3dMsg->header.stamp) { if(!odomT.isNull()) @@ -1070,7 +1098,7 @@ void CoreWrapper::commonDepthCallback( } else { - laserOdomT = odomT.inverse() * sensorT * localScanTransform; + scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; } } @@ -1080,10 +1108,6 @@ void CoreWrapper::commonDepthCallback( { pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*scan3dMsg, *pclScan); - if(!laserOdomT.isIdentity()) - { - pclScan = util3d::transformPointCloud(pclScan, laserOdomT); - } scan = util3d::laserScanFromPointCloud(*pclScan); } else @@ -1091,11 +1115,6 @@ void CoreWrapper::commonDepthCallback( pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*scan3dMsg, *pclScan); - if(!laserOdomT.isIdentity()) - { - pclScan = util3d::transformPointCloud(pclScan, laserOdomT); - } - if(scanCloudNormalK_ > 0) { //compute normals @@ -1126,8 +1145,10 @@ void CoreWrapper::commonDepthCallback( } SensorData data(scan, - scan2dMsg.get() != 0?(int)scan2dMsg->ranges.size():(genScan_?genMaxScanPts:scan3dMsg.get() != 0?scanCloudMaxPoints_:0), - scan2dMsg.get() != 0?scan2dMsg->range_max:(genScan_?genScanMaxDepth_:0.0f), + LaserScanInfo( + scan2dMsg.get() != 0?(int)scan2dMsg->ranges.size():(genScan_?genMaxScanPts:scan3dMsg.get() != 0?scanCloudMaxPoints_:0), + scan2dMsg.get() != 0?scan2dMsg->range_max:(genScan_?genScanMaxDepth_:0.0f), + scanLocalTransform), rgb, depth, cameraModels, @@ -1167,6 +1188,77 @@ void CoreWrapper::commonStereoCallback( return; } + cv_bridge::CvImagePtr ptrLeftImage, ptrRightImage; + if(leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || + leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) + { + ptrLeftImage = cv_bridge::toCvCopy(leftImageMsg, "mono8"); + } + else + { + ptrLeftImage = cv_bridge::toCvCopy(leftImageMsg, "bgr8"); + } + ptrRightImage = cv_bridge::toCvCopy(rightImageMsg, "mono8"); + + Transform localTransform = getTransform(frameId_, leftImageMsg->header.frame_id, leftImageMsg->header.stamp); + if(localTransform.isNull()) + { + return; + } + + rtabmap::StereoCameraModel stereoModel = rtabmap_ros::stereoCameraModelFromROS(*leftCamInfoMsg, *rightCamInfoMsg, localTransform); + + if(stereoModel.baseline() > 10.0) + { + static bool shown = false; + if(!shown) + { + ROS_WARN("Detected baseline (%f m) is quite large! Is your " + "right camera_info P(0,3) correctly set? Note that " + "baseline=-P(0,3)/P(0,0). This warning is printed only once.", + stereoModel.baseline()); + shown = true; + } + } + + if(stereoToDepth_) + { + // cv::stereoBM() see "$ rosrun rtabmap_ros rtabmap --params | grep StereoBM" for parameters + cv::Mat disparity = util2d::disparityFromStereoImages(ptrLeftImage->image, ptrRightImage->image, parameters_); + if(disparity.empty()) + { + ROS_ERROR("Could not compute disparity image (\"stereo_to_depth\" is true)!"); + return; + } + cv::Mat depth = util2d::depthFromDisparity( + disparity, + stereoModel.left().fx(), + stereoModel.baseline()); + + if(depth.empty()) + { + ROS_ERROR("Could not compute depth image (\"stereo_to_depth\" is true)!"); + return; + } + UASSERT(depth.type() == CV_16UC1 || depth.type() == CV_32FC1); + + // move to common depth callback + cv_bridge::CvImage imgDepth; + if(depth.type() == CV_16UC1) + { + imgDepth.encoding = sensor_msgs::image_encodings::TYPE_16UC1; + } + else // CV_32FC1 + { + imgDepth.encoding = sensor_msgs::image_encodings::TYPE_32FC1; + } + imgDepth.image = depth; + sensor_msgs::ImagePtr depthMsg = imgDepth.toImageMsg(); + depthMsg->header = leftImageMsg->header; + + commonDepthCallback(odomFrameId, leftImageMsg, depthMsg, leftCamInfoMsg, scan2dMsg, scan3dMsg); + } + //for sync transform Transform odomT = getTransform(odomFrameId, frameId_, lastPoseStamp_); if(odomT.isNull() && !odomFrameId_.empty()) @@ -1175,12 +1267,6 @@ void CoreWrapper::commonStereoCallback( odomFrameId.c_str(), frameId_.c_str()); } - Transform localTransform = getTransform(frameId_, leftImageMsg->header.frame_id, leftImageMsg->header.stamp); - if(localTransform.isNull()) - { - return; - } - // sync with odometry stamp if(lastPoseStamp_ != leftImageMsg->header.stamp) { @@ -1196,12 +1282,14 @@ void CoreWrapper::commonStereoCallback( } cv::Mat scan; + Transform scanLocalTransform = Transform::getIdentity(); if(scan2dMsg.get() != 0) { // make sure the frame of the laser is updated too - if(getTransform(frameId_, + scanLocalTransform = getTransform(frameId_, scan2dMsg->header.frame_id, - scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)).isNull()) + scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)); + if(scanLocalTransform.isNull()) { ROS_ERROR("TF of received laser scan topic at time %fs is not set, aborting rtabmap update.", scan2dMsg->header.stamp.toSec()); return; @@ -1211,7 +1299,7 @@ void CoreWrapper::commonStereoCallback( sensor_msgs::PointCloud2 scanOut; laser_geometry::LaserProjection projection; //projection.transformLaserScanToPointCloud(frameId_, *scanMsg, scanOut, tfBuffer_); - projection.transformLaserScanToPointCloud(frameId_, *scan2dMsg, scanOut, tfListener_); + projection.transformLaserScanToPointCloud(scan2dMsg->header.frame_id, *scan2dMsg, scanOut, tfListener_); pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(scanOut, *pclScan); @@ -1225,9 +1313,7 @@ void CoreWrapper::commonStereoCallback( { return; } - Transform t = odomT.inverse() * sensorT; - pclScan = util3d::transformPointCloud(pclScan, t); - + scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; } } @@ -1252,13 +1338,12 @@ void CoreWrapper::commonStereoCallback( } // sync with odometry stamp - Transform localScanTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); - if(localScanTransform.isNull()) + scanLocalTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); + if(scanLocalTransform.isNull()) { ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); return; } - Transform laserOdomT = localScanTransform; if(lastPoseStamp_ != scan3dMsg->header.stamp) { if(!odomT.isNull()) @@ -1271,7 +1356,7 @@ void CoreWrapper::commonStereoCallback( } else { - laserOdomT = odomT.inverse() * sensorT * localScanTransform; + scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; } } @@ -1282,11 +1367,6 @@ void CoreWrapper::commonStereoCallback( pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*scan3dMsg, *pclScan); - if(!laserOdomT.isIdentity()) - { - pclScan = util3d::transformPointCloud(pclScan, laserOdomT); - } - scan = util3d::laserScanFromPointCloud(*pclScan); } else @@ -1294,11 +1374,6 @@ void CoreWrapper::commonStereoCallback( pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*scan3dMsg, *pclScan); - if(!laserOdomT.isIdentity()) - { - pclScan = util3d::transformPointCloud(pclScan, laserOdomT); - } - if(scanCloudNormalK_ > 0) { //compute normals @@ -1314,33 +1389,6 @@ void CoreWrapper::commonStereoCallback( } } - cv_bridge::CvImagePtr ptrLeftImage, ptrRightImage; - if(leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) - { - ptrLeftImage = cv_bridge::toCvCopy(leftImageMsg, "mono8"); - } - else - { - ptrLeftImage = cv_bridge::toCvCopy(leftImageMsg, "bgr8"); - } - ptrRightImage = cv_bridge::toCvCopy(rightImageMsg, "mono8"); - - rtabmap::StereoCameraModel stereoModel = rtabmap_ros::stereoCameraModelFromROS(*leftCamInfoMsg, *rightCamInfoMsg, localTransform); - - if(stereoModel.baseline() > 10.0) - { - static bool shown = false; - if(!shown) - { - ROS_WARN("Detected baseline (%f m) is quite large! Is your " - "right camera_info P(0,3) correctly set? Note that " - "baseline=-P(0,3)/P(0,0). This warning is printed only once.", - stereoModel.baseline()); - shown = true; - } - } - ros::Time stamp = scan2dMsg.get() != 0?scan2dMsg->header.stamp: scan3dMsg.get() != 0?scan3dMsg->header.stamp: leftImageMsg->header.stamp; @@ -1352,8 +1400,10 @@ void CoreWrapper::commonStereoCallback( } SensorData data(scan, - scan2dMsg.get() != 0?(int)scan2dMsg->ranges.size():scan3dMsg.get() != 0?scanCloudMaxPoints_:0, - scan2dMsg.get() != 0?scan2dMsg->range_max:0, + LaserScanInfo( + scan2dMsg.get() != 0?(int)scan2dMsg->ranges.size():scan3dMsg.get() != 0?scanCloudMaxPoints_:0, + scan2dMsg.get() != 0?scan2dMsg->range_max:0, + scanLocalTransform), ptrLeftImage->image, ptrRightImage->image, stereoModel, @@ -1621,9 +1671,6 @@ void CoreWrapper::process( rtabmap_.getMemory(), false, false, - false, - false, - false, tmpSignature); timeUpdateMaps = timer.ticks(); @@ -1916,6 +1963,7 @@ bool CoreWrapper::updateRtabmapCallback(std_srvs::Empty::Request&, std_srvs::Emp ROS_INFO("RTAB-Map rate detection = %f Hz", rate_); } rtabmap_.parseParameters(parameters_); + mapsManager_.setParameters(parameters_); return true; } @@ -2039,7 +2087,7 @@ bool CoreWrapper::setLogError(std_srvs::Empty::Request&, std_srvs::Empty::Respon return true; } -bool CoreWrapper::getMapCallback(rtabmap_ros::GetMap::Request& req, rtabmap_ros::GetMap::Response& res) +bool CoreWrapper::getMapDataCallback(rtabmap_ros::GetMap::Request& req, rtabmap_ros::GetMap::Response& res) { ROS_INFO("rtabmap: Getting map (global=%s optimized=%s graphOnly=%s)...", req.global?"true":"false", @@ -2083,65 +2131,41 @@ bool CoreWrapper::getMapCallback(rtabmap_ros::GetMap::Request& req, rtabmap_ros: bool CoreWrapper::getProjMapCallback(nav_msgs::GetMap::Request &req, nav_msgs::GetMap::Response &res) { - std::map filteredPoses; - filteredPoses = mapsManager_.updateMapCaches( - rtabmap_.getLocalOptimizedPoses(), - rtabmap_.getMemory(), - false, - true, - false, - false, - false); - if(filteredPoses.size()) + if(parameters_.find(Parameters::kGridFromDepth()) != parameters_.end() && + !uStr2Bool(parameters_.at(Parameters::kGridFromDepth()))) { - // create the projection map - float xMin=0.0f, yMin=0.0f, gridCellSize = 0.05f; - cv::Mat pixels = mapsManager_.generateProjMap(filteredPoses, xMin, yMin, gridCellSize); - - if(!pixels.empty()) - { - //init - res.map.info.resolution = gridCellSize; - res.map.info.origin.position.x = 0.0; - res.map.info.origin.position.y = 0.0; - res.map.info.origin.position.z = 0.0; - res.map.info.origin.orientation.x = 0.0; - res.map.info.origin.orientation.y = 0.0; - res.map.info.origin.orientation.z = 0.0; - res.map.info.origin.orientation.w = 1.0; - - res.map.info.width = pixels.cols; - res.map.info.height = pixels.rows; - res.map.info.origin.position.x = xMin; - res.map.info.origin.position.y = yMin; - res.map.data.resize(res.map.info.width * res.map.info.height); - - memcpy(res.map.data.data(), pixels.data, res.map.info.width * res.map.info.height); - - res.map.header.frame_id = mapFrameId_; - res.map.header.stamp = ros::Time::now(); - return true; - } + ROS_WARN("/get_proj_map service is deprecated! Call /get_grid_map service " + "instead with . " + "Do \"$ rosrun rtabmap_ros rtabmap --params | grep Grid\" to see " + "all occupancy grid parameters.", + Parameters::kGridFromDepth().c_str()); } - return false; + else + { + ROS_WARN("/get_proj_map service is deprecated! Call /get_grid_map service instead."); + } + return getGridMapCallback(req, res); } bool CoreWrapper::getGridMapCallback(nav_msgs::GetMap::Request &req, nav_msgs::GetMap::Response &res) +{ + ROS_WARN("/get_grid_map service is deprecated! Call /get_map service instead."); + return getMapCallback(req, res); +} + +bool CoreWrapper::getMapCallback(nav_msgs::GetMap::Request &req, nav_msgs::GetMap::Response &res) { std::map filteredPoses; filteredPoses = mapsManager_.updateMapCaches( rtabmap_.getLocalOptimizedPoses(), rtabmap_.getMemory(), - false, - false, true, - false, false); if(filteredPoses.size()) { // create the grid map float xMin=0.0f, yMin=0.0f, gridCellSize = 0.05f; - cv::Mat pixels = mapsManager_.generateProjMap(filteredPoses, xMin, yMin, gridCellSize); + cv::Mat pixels = mapsManager_.generateGridMap(filteredPoses, xMin, yMin, gridCellSize); if(!pixels.empty()) { @@ -2247,9 +2271,6 @@ bool CoreWrapper::publishMapCallback(rtabmap_ros::PublishMap::Request& req, rtab rtabmap_.getMemory(), false, false, - false, - false, - false, signatures); } else @@ -2715,7 +2736,7 @@ bool CoreWrapper::octomapBinaryCallback( res.map.header.stamp = ros::Time::now(); std::map poses = rtabmap_.getLocalOptimizedPoses(); - poses = mapsManager_.updateMapCaches(poses, rtabmap_.getMemory(), false, false, false, false, true); + poses = mapsManager_.updateMapCaches(poses, rtabmap_.getMemory(), false, true); const rtabmap::OctoMap * octomap = mapsManager_.getOctomap(); bool success = octomap->octree()->size() && octomap_msgs::binaryMapToMsg(*octomap->octree(), res.map); @@ -2731,7 +2752,7 @@ bool CoreWrapper::octomapFullCallback( res.map.header.stamp = ros::Time::now(); std::map poses = rtabmap_.getLocalOptimizedPoses(); - poses = mapsManager_.updateMapCaches(poses, rtabmap_.getMemory(), false, false, false, false, true); + poses = mapsManager_.updateMapCaches(poses, rtabmap_.getMemory(), false, true); const rtabmap::OctoMap * octomap = mapsManager_.getOctomap(); bool success = octomap->octree()->size() && octomap_msgs::fullMapToMsg(*octomap->octree(), res.map); diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index a3f51128..51f1919c 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -407,9 +407,9 @@ void GuiWrapper::handleEvent(UEvent * anEvent) getMapSrv.request.global = cmdEvent->value1().toBool(); getMapSrv.request.optimized = cmdEvent->value2().toBool(); getMapSrv.request.graphOnly = cmdEvent->value3().toBool(); - if(!ros::service::call("get_map", getMapSrv)) + if(!ros::service::call("get_map_data", getMapSrv)) { - ROS_WARN("Can't call \"get_map\" service"); + ROS_WARN("Can't call \"get_map_data\" service"); this->post(new RtabmapEvent3DMap(1)); // service error } else @@ -588,6 +588,7 @@ void GuiWrapper::commonDepthCallback( cv::Mat depth; std::vector cameraModels; cv::Mat scan; + Transform scanLocalTransform = Transform::getIdentity(); rtabmap::OdometryInfo info; bool ignoreData = false; @@ -693,15 +694,20 @@ void GuiWrapper::commonDepthCallback( if(scan2dMsg.get() != 0) { // make sure the frame of the laser is updated too - if(getTransform(frameId_, scan2dMsg->header.frame_id, scan2dMsg->header.stamp).isNull()) + scanLocalTransform = getTransform( + frameId_, + scan2dMsg->header.frame_id, + scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)); + if(scanLocalTransform.isNull()) { + ROS_ERROR("TF of received scan at time %fs is not set, aborting rtabmapviz update.", scan2dMsg->header.stamp.toSec()); return; } //transform in frameId_ frame sensor_msgs::PointCloud2 scanOut; laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(frameId_, *scan2dMsg, scanOut, tfListener_); + projection.transformLaserScanToPointCloud(scan2dMsg->header.frame_id, *scan2dMsg, scanOut, tfListener_); pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(scanOut, *pclScan); @@ -715,18 +721,62 @@ void GuiWrapper::commonDepthCallback( { return; } - Transform t = odomT.inverse() * sensorT; - pclScan = util3d::transformPointCloud(pclScan, t); - + scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; } } scan = util3d::laserScanFromPointCloud(*pclScan); } else if(scan3dMsg.get() != 0) { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - scan = util3d::laserScanFromPointCloud(*pclScan); + bool containNormals = false; + for(unsigned int i=0; ifields.size(); ++i) + { + if(scan3dMsg->fields[i].name.compare("normal_x") == 0) + { + containNormals = true; + break; + } + } + + // sync with odometry stamp + scanLocalTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); + if(scanLocalTransform.isNull()) + { + ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + return; + } + if(odomHeader.stamp != scan3dMsg->header.stamp) + { + if(!odomT.isNull()) + { + Transform sensorT = getTransform(odomHeader.frame_id, frameId_, scan3dMsg->header.stamp); + if(sensorT.isNull()) + { + ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " + "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), odomHeader.stamp.toSec()); + } + else + { + scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; + } + + } + } + + if(containNormals) + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*scan3dMsg, *pclScan); + + scan = util3d::laserScanFromPointCloud(*pclScan); + } + else + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*scan3dMsg, *pclScan); + + scan = util3d::laserScanFromPointCloud(*pclScan); + } } if(odomInfoMsg.get()) @@ -749,8 +799,10 @@ void GuiWrapper::commonDepthCallback( rtabmap::OdometryEvent odomEvent( rtabmap::SensorData( scan, - scan2dMsg.get()?(int)scan2dMsg->ranges.size():0, - scan2dMsg.get()?(int)scan2dMsg->range_max:0, + LaserScanInfo( + scan2dMsg.get()?(int)scan2dMsg->ranges.size():0, + scan2dMsg.get()?(int)scan2dMsg->range_max:0, + scanLocalTransform), rgb, depth, cameraModels, @@ -854,6 +906,7 @@ void GuiWrapper::commonStereoCallback( cv::Mat left; cv::Mat right; cv::Mat scan; + Transform scanLocalTransform = Transform::getIdentity(); rtabmap::StereoCameraModel stereoModel; rtabmap::OdometryInfo info; bool ignoreData = false; @@ -898,15 +951,20 @@ void GuiWrapper::commonStereoCallback( if(scan2dMsg.get() != 0) { // make sure the frame of the laser is updated too - if(getTransform(frameId_, scan2dMsg->header.frame_id, scan2dMsg->header.stamp).isNull()) + scanLocalTransform = getTransform( + frameId_, + scan2dMsg->header.frame_id, + scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)); + if(scanLocalTransform.isNull()) { + ROS_ERROR("TF of received scan at time %fs is not set, aborting rtabmapviz update.", scan2dMsg->header.stamp.toSec()); return; } //transform in frameId_ frame sensor_msgs::PointCloud2 scanOut; laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(frameId_, *scan2dMsg, scanOut, tfListener_); + projection.transformLaserScanToPointCloud(scan2dMsg->header.frame_id, *scan2dMsg, scanOut, tfListener_); pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(scanOut, *pclScan); @@ -920,18 +978,62 @@ void GuiWrapper::commonStereoCallback( { return; } - Transform t = odomT.inverse() * sensorT; - pclScan = util3d::transformPointCloud(pclScan, t); - + scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; } } scan = util3d::laserScan2dFromPointCloud(*pclScan); } else if(scan3dMsg.get() != 0) { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - scan = util3d::laserScanFromPointCloud(*pclScan); + bool containNormals = false; + for(unsigned int i=0; ifields.size(); ++i) + { + if(scan3dMsg->fields[i].name.compare("normal_x") == 0) + { + containNormals = true; + break; + } + } + + // sync with odometry stamp + scanLocalTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); + if(scanLocalTransform.isNull()) + { + ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + return; + } + if(odomHeader.stamp != scan3dMsg->header.stamp) + { + if(!odomT.isNull()) + { + Transform sensorT = getTransform(odomHeader.frame_id, frameId_, scan3dMsg->header.stamp); + if(sensorT.isNull()) + { + ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " + "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), odomHeader.stamp.toSec()); + } + else + { + scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; + } + + } + } + + if(containNormals) + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*scan3dMsg, *pclScan); + + scan = util3d::laserScanFromPointCloud(*pclScan); + } + else + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*scan3dMsg, *pclScan); + + scan = util3d::laserScanFromPointCloud(*pclScan); + } } if(odomInfoMsg.get()) @@ -954,8 +1056,10 @@ void GuiWrapper::commonStereoCallback( rtabmap::OdometryEvent odomEvent( rtabmap::SensorData( scan, - scan2dMsg.get()?(int)scan2dMsg->ranges.size():0, - scan2dMsg.get()?(int)scan2dMsg->range_max:0, + LaserScanInfo( + scan2dMsg.get()?(int)scan2dMsg->ranges.size():0, + scan2dMsg.get()?(int)scan2dMsg->range_max:0, + scanLocalTransform), left, right, stereoModel, diff --git a/src/MapAssemblerNode.cpp b/src/MapAssemblerNode.cpp index 8e7c295d..ce63ff4a 100644 --- a/src/MapAssemblerNode.cpp +++ b/src/MapAssemblerNode.cpp @@ -99,9 +99,6 @@ public: 0, false, false, - false, - false, - false, nodes_); mapsManager_.publishMaps(poses, msg->header.stamp, msg->header.frame_id); diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index 32f2cb6c..3c99afae 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -38,6 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include @@ -47,6 +48,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP #include +#include #include #endif #endif @@ -54,96 +56,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace rtabmap; MapsManager::MapsManager(bool usePublicNamespace) : - cloudDecimation_(4), - cloudMaxDepth_(4.0), // meters - cloudMinDepth_(0.0), // meters - cloudVoxelSize_(0.05), // meters - cloudFloorCullingHeight_(0.0), - cloudCeilingCullingHeight_(0.0), - cloudOutputVoxelized_(false), - cloudFrustumCulling_(false), - cloudNoiseFilteringRadius_(0.0), - cloudNoiseFilteringMinNeighbors_(5), - scanDecimation_(0), - scanVoxelSize_(0.0), - scanOutputVoxelized_(false), - projMaxGroundAngle_(45.0), // degrees - projMinClusterSize_(20), - projMaxObstaclesHeight_(2.0), // meters (<=0 disabled) - projMaxGroundHeight_(0.0), // meters (<=0 disabled, only works if proj_detect_flat_obstacles is true) - projDetectFlatObstacles_(false), - projMapFrame_(false), + cloudOutputVoxelized_(true), gridCellSize_(0.05), // meters + gridIncremental_(false), gridSize_(0), // meters gridEroded_(false), footprintRadius_(0.0), - gridUnknownSpaceFilled_(false), - gridMaxUnknownSpaceFilledRange_(6.0), mapFilterRadius_(0.0), mapFilterAngle_(30.0), // degrees mapCacheCleanup_(true), - negativePosesIgnored_(false), + negativePosesIgnored_(true), + assembledObstacles_(new pcl::PointCloud), + assembledGround_(new pcl::PointCloud), + occupancyGrid_(new OccupancyGrid), octomap_(0), - octomapTreeDepth_(16), - octomapGroundIsObstacle_(false) + octomapTreeDepth_(16) { ros::NodeHandle nh; ros::NodeHandle pnh("~"); - // cloud map stuff - pnh.param("cloud_decimation", cloudDecimation_, cloudDecimation_); - pnh.param("cloud_max_depth", cloudMaxDepth_, cloudMaxDepth_); - pnh.param("cloud_min_depth", cloudMinDepth_, cloudMinDepth_); - pnh.param("cloud_voxel_size", cloudVoxelSize_, cloudVoxelSize_); - pnh.param("cloud_floor_culling_height", cloudFloorCullingHeight_, cloudFloorCullingHeight_); - pnh.param("cloud_ceiling_culling_height", cloudCeilingCullingHeight_, cloudCeilingCullingHeight_); - if(cloudFloorCullingHeight_ > 0 && - cloudCeilingCullingHeight_ > 0 && - cloudCeilingCullingHeight_ < cloudFloorCullingHeight_) - { - ROS_WARN("\"cloud_floor_culling_height\" should be lower than \"cloud_ceiling_culling_height\", setting \"cloud_ceiling_culling_height\" to 0 (disabled)."); - cloudCeilingCullingHeight_ = 0; - } - pnh.param("cloud_output_voxelized", cloudOutputVoxelized_, cloudOutputVoxelized_); - pnh.param("cloud_frustum_culling", cloudFrustumCulling_, cloudFrustumCulling_); - pnh.param("cloud_noise_filtering_radius", cloudNoiseFilteringRadius_, cloudNoiseFilteringRadius_); - pnh.param("cloud_noise_filtering_min_neighbors", cloudNoiseFilteringMinNeighbors_, cloudNoiseFilteringMinNeighbors_); - - // scan map stuff - pnh.param("scan_decimation", scanDecimation_, scanDecimation_); - pnh.param("scan_voxel_size", scanVoxelSize_, scanVoxelSize_); - pnh.param("scan_output_voxelized", scanOutputVoxelized_, scanOutputVoxelized_); - - //projection map stuff - pnh.param("proj_max_ground_angle", projMaxGroundAngle_, projMaxGroundAngle_); - pnh.param("proj_min_cluster_size", projMinClusterSize_, projMinClusterSize_); - if(pnh.hasParam("proj_max_height") && !pnh.hasParam("proj_max_obstacles_height")) - { - ROS_WARN("Parameter \"proj_max_height\" has been renamed " - "to \"proj_max_obstacles_height\"! Your value is still copied to " - "corresponding parameter."); - pnh.param("proj_max_height", projMaxObstaclesHeight_, projMaxObstaclesHeight_); - } - else - { - pnh.param("proj_max_obstacles_height", projMaxObstaclesHeight_, projMaxObstaclesHeight_); - } - pnh.param("proj_max_ground_height", projMaxGroundHeight_, projMaxGroundHeight_); - pnh.param("proj_detect_flat_obstacles", projDetectFlatObstacles_, projDetectFlatObstacles_); - pnh.param("proj_map_frame", projMapFrame_, projMapFrame_); - // common grid map stuff pnh.param("grid_cell_size", gridCellSize_, gridCellSize_); // m if(gridCellSize_ <= 0) { ROS_FATAL("\"grid_cell_size\" (%f) should be greater than 0!", gridCellSize_); } + occupancyGrid_->setCellSize(gridCellSize_); + + pnh.param("grid_incremental", gridIncremental_, gridIncremental_); // m pnh.param("grid_size", gridSize_, gridSize_); // m pnh.param("grid_eroded", gridEroded_, gridEroded_); pnh.param("grid_footprint_radius", footprintRadius_, footprintRadius_); - pnh.param("grid_unknown_space_filled", gridUnknownSpaceFilled_, gridUnknownSpaceFilled_); - pnh.param("grid_unknown_space_filled_max_range", gridMaxUnknownSpaceFilledRange_, gridMaxUnknownSpaceFilledRange_); // common map stuff pnh.param("map_filter_radius", mapFilterRadius_, mapFilterRadius_); @@ -151,6 +95,17 @@ MapsManager::MapsManager(bool usePublicNamespace) : pnh.param("map_cleanup", mapCacheCleanup_, mapCacheCleanup_); pnh.param("map_negative_poses_ignored", negativePosesIgnored_, negativePosesIgnored_); + if(pnh.hasParam("scan_output_voxelized")) + { + ROS_WARN("Parameter \"scan_output_voxelized\" has been " + "removed. Use \"cloud_output_voxelized\" instead."); + if(!pnh.hasParam("cloud_output_voxelized")) + { + pnh.getParam("scan_output_voxelized", cloudOutputVoxelized_); + } + } + pnh.param("cloud_output_voxelized", cloudOutputVoxelized_, cloudOutputVoxelized_); + #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP octomap_ = new OctoMap(gridCellSize_); @@ -165,7 +120,6 @@ MapsManager::MapsManager(bool usePublicNamespace) : ROS_WARN("octomap_tree_depth cannot be negative, set to 16 instead"); octomapTreeDepth_ = 16; } - pnh.param("octomap_ground_is_obstacle", octomapGroundIsObstacle_, octomapGroundIsObstacle_); #endif #endif @@ -176,43 +130,32 @@ MapsManager::MapsManager(bool usePublicNamespace) : pnh.param("latch", latch, latch); // mapping topics - if(usePublicNamespace) - { - cloudMapPub_ = nh.advertise("cloud_map", 1, latch); - projMapPub_ = nh.advertise("proj_map", 1, latch); - gridMapPub_ = nh.advertise("grid_map", 1, latch); - scanMapPub_ = nh.advertise("scan_map", 1, latch); + ros::NodeHandle nht(usePublicNamespace?"":"~"); + gridMapPub_ = nht.advertise("grid_map", 1, latch); + cloudMapPub_ = nht.advertise("cloud_map", 1, latch); + cloudObstaclesPub_ = nht.advertise("cloud_obstacles", 1, latch); + cloudGroundPub_ = nht.advertise("cloud_ground", 1, latch); + + // deprecated + projMapPub_ = nht.advertise("proj_map", 1, latch); + scanMapPub_ = nht.advertise("scan_map", 1, latch); + #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP - octoMapPubBin_ = nh.advertise("octomap_binary", 1, latch); - octoMapPubFull_ = nh.advertise("octomap_full", 1, latch); - octoMapCloud_ = nh.advertise("octomap_cloud", 1, latch); - octoMapEmptySpace_ = nh.advertise("octomap_empty_space", 1, latch); - octoMapProj_ = nh.advertise("octomap_proj", 1, latch); + octoMapPubBin_ = nht.advertise("octomap_binary", 1, latch); + octoMapPubFull_ = nht.advertise("octomap_full", 1, latch); + octoMapCloud_ = nht.advertise("octomap_occupied_space", 1, latch); + octoMapEmptySpace_ = nht.advertise("octomap_empty_space", 1, latch); + octoMapProj_ = nht.advertise("octomap_grid", 1, latch); #endif #endif - } - else - { - cloudMapPub_ = pnh.advertise("cloud_map", 1, latch); - projMapPub_ = pnh.advertise("proj_map", 1, latch); - gridMapPub_ = pnh.advertise("grid_map", 1, latch); - scanMapPub_ = pnh.advertise("scan_map", 1, latch); -#ifdef WITH_OCTOMAP_ROS -#ifdef RTABMAP_OCTOMAP - octoMapPubBin_ = pnh.advertise("octomap_binary", 1, latch); - octoMapPubFull_ = pnh.advertise("octomap_full", 1, latch); - octoMapCloud_ = pnh.advertise("octomap_cloud", 1, latch); - octoMapEmptySpace_ = pnh.advertise("octomap_cloud_ground", 1, latch); - octoMapProj_ = pnh.advertise("octomap_proj", 1, latch); -#endif -#endif - } } MapsManager::~MapsManager() { clear(); + delete occupancyGrid_; + #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP if(octomap_) @@ -224,12 +167,106 @@ MapsManager::~MapsManager() { #endif } +void parameterMoved( + ros::NodeHandle & nh, + const std::string & rosName, + const std::string & parameterName, + ParametersMap & parameters) +{ + if(nh.hasParam(rosName)) + { + ParametersMap::const_iterator iter = Parameters::getDefaultParameters().find(parameterName); + if(iter != Parameters::getDefaultParameters().end()) + { + ROS_WARN("Parameter \"%s\" has moved from " + "rtabmap_ros to rtabmap library. Use " + "parameter \"%s\" instead. The value is still " + "copied to new parameter name.", + rosName.c_str(), + parameterName.c_str()); + std::string type = Parameters::getType(parameterName); + if(type.compare("float") || type.compare("double")) + { + double v = uStr2Double(iter->second); + nh.getParam(rosName, v); + parameters.insert(ParametersPair(parameterName, uNumber2Str(v))); + } + else if(type.compare("int") || type.compare("unsigned int")) + { + int v = uStr2Int(iter->second); + nh.getParam(rosName, v); + parameters.insert(ParametersPair(parameterName, uNumber2Str(v))); + } + else + { + ROS_ERROR("Not handled type \"%s\" for parameter \"%s\"", type.c_str(), parameterName.c_str()); + } + } + else + { + ROS_ERROR("Parameter \"%s\" not found in default parameters.", parameterName.c_str()); + } + } +} + +void MapsManager::backwardCompatibilityParameters(ParametersMap & parameters) const +{ + ros::NodeHandle pnh("~"); + + // removed + if(pnh.hasParam("cloud_frustum_culling")) + { + ROS_WARN("Parameter \"cloud_frustum_culling\" has been removed. OctoMap topics " + "already do it. You can remove it from your launch file."); + } + + // moved + parameterMoved(pnh, "cloud_decimation", Parameters::kGridDepthDecimation(), parameters); + parameterMoved(pnh, "cloud_max_depth", Parameters::kGridDepthMax(), parameters); + parameterMoved(pnh, "cloud_min_depth", Parameters::kGridDepthMin(), parameters); + parameterMoved(pnh, "cloud_voxel_size", Parameters::kGridCellSize(), parameters); + parameterMoved(pnh, "cloud_floor_culling_height", Parameters::kGridMaxGroundHeight(), parameters); + parameterMoved(pnh, "cloud_ceiling_culling_height", Parameters::kGridMaxObstacleHeight(), parameters); + parameterMoved(pnh, "cloud_noise_filtering_radius", Parameters::kGridNoiseFilteringRadius(), parameters); + parameterMoved(pnh, "cloud_noise_filtering_min_neighbors", Parameters::kGridNoiseFilteringMinNeighbors(), parameters); + parameterMoved(pnh, "scan_decimation", Parameters::kGridScanDecimation(), parameters); + parameterMoved(pnh, "scan_voxel_size", Parameters::kGridCellSize(), parameters); + parameterMoved(pnh, "proj_max_ground_angle", Parameters::kGridMaxGroundAngle(), parameters); + parameterMoved(pnh, "proj_min_cluster_size", Parameters::kGridMinClusterSize(), parameters); + parameterMoved(pnh, "proj_max_height", Parameters::kGridMaxObstacleHeight(), parameters); + parameterMoved(pnh, "proj_max_obstacles_height", Parameters::kGridMaxObstacleHeight(), parameters); + parameterMoved(pnh, "proj_max_ground_height", Parameters::kGridMaxGroundHeight(), parameters); + + parameterMoved(pnh, "proj_detect_flat_obstacles", Parameters::kGridFlatObstacleDetected(), parameters); + parameterMoved(pnh, "proj_map_frame", Parameters::kGridMapFrameProjection(), parameters); + parameterMoved(pnh, "grid_unknown_space_filled", Parameters::kGridScan2dUnknownSpaceFilled(), parameters); + parameterMoved(pnh, "grid_unknown_space_filled_max_range", Parameters::kGridScan2dMaxFilledRange(), parameters); + +#ifdef WITH_OCTOMAP_ROS +#ifdef RTABMAP_OCTOMAP + parameterMoved(pnh, "octomap_ground_is_obstacle", Parameters::kGrid3DGroundIsObstacle(), parameters); +#endif +#endif +} + +void MapsManager::setParameters(const rtabmap::ParametersMap & parameters) +{ + parameters_ = parameters; + + // don't use grid cell size from parameters as we use grid_cell_size ros param + uInsert(parameters_, ParametersPair(Parameters::kGridCellSize(), uNumber2Str(gridCellSize_))); + occupancyGrid_->parseParameters(parameters_); +} + void MapsManager::clear() { - clouds_.clear(); - cameraModels_.clear(); - projMaps_.clear(); gridMaps_.clear(); + gridMapsViewpoints_.clear(); + assembledGround_->clear(); + assembledObstacles_->clear(); + assembledGroundPoses_.clear(); + assembledObstaclePoses_.clear(); + occupancyGrid_->clear(); #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP octomap_->clear(); @@ -240,6 +277,8 @@ void MapsManager::clear() bool MapsManager::hasSubscribers() const { return cloudMapPub_.getNumSubscribers() != 0 || + cloudObstaclesPub_.getNumSubscribers() != 0 || + cloudGroundPub_.getNumSubscribers() != 0 || projMapPub_.getNumSubscribers() != 0 || gridMapPub_.getNumSubscribers() != 0 || scanMapPub_.getNumSubscribers() != 0 || @@ -264,20 +303,14 @@ std::map MapsManager::getFilteredPoses(const std::map MapsManager::updateMapCaches( const std::map & poses, const rtabmap::Memory * memory, - bool updateCloud, - bool updateProj, bool updateGrid, - bool updateScan, bool updateOctomap, const std::map & signatures) { - if(!updateCloud && !updateProj && !updateGrid && !updateScan && !updateOctomap) + if(!updateGrid && !updateOctomap) { // all false, udpate only those where we have subscribers - updateCloud = cloudMapPub_.getNumSubscribers() != 0; - updateProj = projMapPub_.getNumSubscribers() != 0; - updateGrid = gridMapPub_.getNumSubscribers() != 0; - updateScan = scanMapPub_.getNumSubscribers() != 0; + updateGrid = this->hasSubscribers(); updateOctomap = octoMapPubBin_.getNumSubscribers() != 0 || octoMapPubFull_.getNumSubscribers() != 0 || @@ -305,7 +338,7 @@ std::map MapsManager::updateMapCaches( std::map filteredPoses; // update cache - if(updateCloud || updateProj || updateGrid || updateScan || updateOctomap) + if(updateGrid || updateOctomap) { // filter nodes if(mapFilterRadius_ > 0.0) @@ -349,29 +382,14 @@ std::map MapsManager::updateMapCaches( bool longUpdate = false; if(filteredPoses.size() > 20) { - if(updateCloud && clouds_.size() < 5) + if(updateGrid && gridMaps_.size() < 5) { - ROS_WARN("Many clouds should be created (~%d), this may take a while to update the map(s)...", int(filteredPoses.size()-clouds_.size())); - longUpdate = true; - } - else if(updateProj && projMaps_.size() < 5) - { - ROS_WARN("Many occupancy grid map from projections should be created (~%d), this may take a while to update the map(s)...", int(filteredPoses.size()-projMaps_.size())); - longUpdate = true; - } - else if(updateGrid && gridMaps_.size() < 5) - { - ROS_WARN("Many occupancy grid map from laser scans should be created (~%d), this may take a while to update the map(s)...", int(filteredPoses.size()-gridMaps_.size())); - longUpdate = true; - } - else if(updateScan && scans_.size() < 5) - { - ROS_WARN("Many scans should be created (~%d), this may take a while to update the map(s)...", int(filteredPoses.size()-scans_.size())); + ROS_WARN("Many occupancy grids should be loaded (~%d), this may take a while to update the map(s)...", int(filteredPoses.size()-octomap_->addedNodes().size())); longUpdate = true; } #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP - else if(updateOctomap && octomap_->addedNodes().size() < 5) + if(updateOctomap && octomap_->addedNodes().size() < 5) { ROS_WARN("Many clouds should be added to octomap (~%d), this may take a while to update the map(s)...", int(filteredPoses.size()-octomap_->addedNodes().size())); longUpdate = true; @@ -385,27 +403,7 @@ std::map MapsManager::updateMapCaches( if(!iter->second.isNull()) { rtabmap::SensorData data; - bool rgbDepthRequired = updateCloud && (iter->first < 0 || !uContains(clouds_, iter->first)); - bool depthRequired = updateProj && (iter->first < 0 || !uContains(projMaps_, iter->first)); - bool gridRequired = updateGrid && (iter->first < 0 || !uContains(gridMaps_, iter->first)); - bool scanRequired = updateScan && (iter->first < 0 || !uContains(scans_, iter->first)); - -#ifdef WITH_OCTOMAP_ROS -#ifdef RTABMAP_OCTOMAP - if(!rgbDepthRequired) - { - rgbDepthRequired = updateOctomap && - (iter->first < 0 || - octomap_->addedNodes().empty() || - iter->first > octomap_->addedNodes().rbegin()->first); - } -#endif -#endif - - if(rgbDepthRequired || - depthRequired || - scanRequired || - gridRequired) + if((updateGrid || updateOctomap) && (iter->first < 0 || !uContains(gridMaps_, iter->first))) { UDEBUG("Data required for %d", iter->first); std::map::const_iterator findIter = signatures.find(iter->first); @@ -415,277 +413,84 @@ std::map MapsManager::updateMapCaches( } else if(memory) { - data = memory->getSignatureDataConst(iter->first); + data = memory->getSignatureDataConst(iter->first, false, false, false, true); } - } - if(data.id() != 0) - { - if(!(data.imageCompressed().empty() && data.imageRaw().empty()) && - !(data.depthOrRightCompressed().empty() && data.depthOrRightRaw().empty()) && - (data.cameraModels().size() || data.stereoCameraModel().isValidForProjection())) + if(data.id() != 0) { - // Which data should we decompress? - cv::Mat image, depth, scan; + cv::Mat ground, obstacles; data.uncompressData( - (rgbDepthRequired||data.stereoCameraModel().isValidForProjection()) ? &image:0, - (rgbDepthRequired||depthRequired) ? &depth:0, - scanRequired||gridRequired?&scan:0); + 0, + 0, + 0, + 0, + &ground, + &obstacles); - pcl::PointCloud::Ptr cloudRGB; - pcl::PointCloud::Ptr cloudXYZ; - if(rgbDepthRequired) + UDEBUG("Adding grid map %d to cache...", iter->first); + + cv::Point3f viewPoint; + if(iter->first > 0 || data.gridCellSize()) { - UDEBUG("rgbDepthRequired"); - if(!image.empty() && !depth.empty()) + viewPoint = data.gridViewPoint(); + gridMaps_.insert(std::make_pair(iter->first, std::make_pair(ground, obstacles))); + gridMapsViewpoints_.insert(std::make_pair(iter->first, viewPoint)); + } + else + { + // generate tmp occupancy grid for negative ids + // we need the signature + std::map::const_iterator findIter = signatures.find(iter->first); + if(findIter != signatures.end()) { - pcl::IndicesPtr validIndices(new std::vector); - cloudRGB = util3d::cloudRGBFromSensorData( - data, - cloudDecimation_, - cloudMaxDepth_, - cloudMinDepth_, - validIndices.get()); - if(cloudVoxelSize_) - { - cloudRGB = util3d::voxelize(cloudRGB, validIndices, cloudVoxelSize_); - } - if(cloudRGB->size() && cloudNoiseFilteringRadius_ > 0.0 && cloudNoiseFilteringMinNeighbors_ > 0) - { - pcl::IndicesPtr indices = rtabmap::util3d::radiusFiltering(cloudRGB, cloudNoiseFilteringRadius_, cloudNoiseFilteringMinNeighbors_); - pcl::PointCloud::Ptr tmp(new pcl::PointCloud); - pcl::copyPointCloud(*cloudRGB, *indices, *tmp); - cloudRGB = tmp; - } + // normally data should be already uncompressed for negative ids + occupancyGrid_->createLocalMap(findIter->second, ground, obstacles, viewPoint); + gridMaps_.insert(std::make_pair(iter->first, std::make_pair(ground, obstacles))); + gridMapsViewpoints_.insert(std::make_pair(iter->first, viewPoint)); } else { - ROS_ERROR("RGB or Depth image not found (node=%d)!", iter->first); + ROS_WARN("%d signature not found in cache?!?!?", iter->first); } } - else if(depthRequired) + if(ground.cols || obstacles.cols) { - UDEBUG("depthRequired"); - if( !depth.empty()) - { - pcl::IndicesPtr validIndices(new std::vector); - cloudXYZ = util3d::cloudFromSensorData( - data, - cloudDecimation_, - cloudMaxDepth_, - cloudMinDepth_, - validIndices.get()); // use gridCellSize since this cloud is only for the projection map - UASSERT(gridCellSize_ > 0); - cloudXYZ = util3d::voxelize(cloudXYZ, validIndices, gridCellSize_); - if(cloudXYZ->size() && cloudNoiseFilteringRadius_ > 0.0 && cloudNoiseFilteringMinNeighbors_ > 0) - { - pcl::IndicesPtr indices = rtabmap::util3d::radiusFiltering(cloudXYZ, cloudNoiseFilteringRadius_, cloudNoiseFilteringMinNeighbors_); - pcl::PointCloud::Ptr tmp(new pcl::PointCloud); - pcl::copyPointCloud(*cloudXYZ, *indices, *tmp); - cloudXYZ = tmp; - } - } - else - { - ROS_ERROR("RGB or Depth image not found (node=%d)!", iter->first); - } - } - - if(cloudRGB.get()) - { - uInsert(clouds_, std::make_pair(iter->first, cloudRGB)); - - // Make sure that image size is set in camera models. - // The camera models are used when cloud_frustum_culling=true. - std::vector models; - if(data.stereoCameraModel().isValidForProjection()) - { - //insert only the left camera model - rtabmap::CameraModel model = data.stereoCameraModel().left(); - model.setImageSize(cv::Size(data.imageRaw().cols, data.imageRaw().rows)); - models.push_back(model); - } - else if(data.cameraModels().size()) - { - UASSERT_MSG(data.imageRaw().cols % data.cameraModels().size() == 0, - uFormat("data.imageRaw().cols=%d data.cameraModels().size()=%d", - data.imageRaw().cols, (int)data.cameraModels().size()).c_str()); - - models.resize(data.cameraModels().size()); - for(unsigned int i=0; ifirst, models)); - } - - if(depthRequired || updateOctomap) - { - UDEBUG("Creating proj map / octomap for %d...", iter->first); - cv::Mat ground, obstacles; - if(cloudRGB.get()) - { - pcl::PointCloud::Ptr cloudClipped = cloudRGB; - if(cloudClipped->size() && projMaxObstaclesHeight_ > 0) - { - cloudClipped = util3d::passThrough(cloudClipped, "z", std::numeric_limits::min(), projMaxObstaclesHeight_); - } - if(cloudClipped->size() && gridCellSize_ > cloudVoxelSize_) - { - cloudClipped = util3d::voxelize(cloudClipped, gridCellSize_); - } - if(cloudClipped->size()) - { - // add pose rotation without yaw - float roll, pitch, yaw; - iter->second.getEulerAngles(roll, pitch, yaw); - cloudClipped = util3d::transformPointCloud(cloudClipped, Transform(0,0,projMapFrame_?iter->second.z():0, roll, pitch, 0)); - - pcl::IndicesPtr groundIndices, obstaclesIndices; - util3d::segmentObstaclesFromGround( - cloudClipped, - groundIndices, - obstaclesIndices, - 20, - projMaxGroundAngle_*M_PI/180.0, - gridCellSize_*2.0f, - projMinClusterSize_, - projDetectFlatObstacles_, - projMaxGroundHeight_); - - pcl::PointCloud::Ptr groundCloud(new pcl::PointCloud); - pcl::PointCloud::Ptr obstaclesCloud(new pcl::PointCloud); - - if(groundIndices->size()) - { - pcl::copyPointCloud(*cloudClipped, *groundIndices, *groundCloud); - } - - if(obstaclesIndices->size()) - { - pcl::copyPointCloud(*cloudClipped, *obstaclesIndices, *obstaclesCloud); - } - - if(updateProj) - { - util3d::occupancy2DFromGroundObstacles( - groundCloud, - obstaclesCloud, - ground, - obstacles, - gridCellSize_); - uInsert(projMaps_, std::make_pair(iter->first, std::make_pair(ground, obstacles))); - } - -#ifdef WITH_OCTOMAP_ROS -#ifdef RTABMAP_OCTOMAP - if(updateOctomap) - { - Transform tinv = Transform(0,0,projMapFrame_?iter->second.z():0, roll, pitch, 0).inverse(); - groundCloud = util3d::transformPointCloud(groundCloud, tinv); - obstaclesCloud = util3d::transformPointCloud(obstaclesCloud, tinv); - if(octomapGroundIsObstacle_) - { - *obstaclesCloud += *groundCloud; - groundCloud->clear(); - } - octomap_->addToCache(iter->first, groundCloud, obstaclesCloud); - } -#endif -#endif - } - } - else if(updateProj && cloudXYZ.get()) - { - pcl::PointCloud::Ptr cloudClipped = cloudXYZ; - if(cloudClipped->size() && projMaxObstaclesHeight_ > 0) - { - cloudClipped = util3d::passThrough(cloudClipped, "z", std::numeric_limits::min(), projMaxObstaclesHeight_); - } - if(cloudClipped->size()) - { - // add pose rotation without yaw - float roll, pitch, yaw; - iter->second.getEulerAngles(roll, pitch, yaw); - cloudClipped = util3d::transformPointCloud(cloudClipped, Transform(0,0,projMapFrame_?iter->second.z():0, roll, pitch, 0)); - - pcl::IndicesPtr groundIndices, obstaclesIndices; - util3d::segmentObstaclesFromGround( - cloudClipped, - groundIndices, - obstaclesIndices, - 20, - projMaxGroundAngle_*M_PI/180.0, - gridCellSize_*2.0f, - projMinClusterSize_, - projDetectFlatObstacles_, - projMaxGroundHeight_); - - util3d::occupancy2DFromGroundObstacles( - cloudClipped, - groundIndices, - obstaclesIndices, - ground, - obstacles, - gridCellSize_); - uInsert(projMaps_, std::make_pair(iter->first, std::make_pair(ground, obstacles))); - } - } - } - - if(scanRequired || gridRequired) - { - if(scan.cols && (scanRequired || scanVoxelSize_ > 0.0 || scanDecimation_ > 1)) - { - if(scanDecimation_ > 1) - { - scan = util3d::downsample(scan, scanDecimation_); - } - - if(scanRequired || scanVoxelSize_ > 0.0) - { - pcl::PointCloud::Ptr scanCloud = util3d::laserScanToPointCloud(scan); - if(scanVoxelSize_ > 0.0) - { - scanCloud = util3d::voxelize(scanCloud, scanVoxelSize_); - if(gridRequired && scan.type() == CV_32FC2) - { - scan = util3d::laserScan2dFromPointCloud(*scanCloud); - } - } - - if(scanRequired) - { - uInsert(scans_, std::make_pair(iter->first, scanCloud)); - } - } - } - - if(gridRequired && scan.type() == CV_32FC2) - { - cv::Mat ground, obstacles; - util3d::occupancy2DFromLaserScan( - scan, - ground, - obstacles, - gridCellSize_, - data.id() < 0 || gridUnknownSpaceFilled_, - data.laserScanMaxRange()>gridMaxUnknownSpaceFilledRange_?gridMaxUnknownSpaceFilledRange_:data.laserScanMaxRange()); - uInsert(gridMaps_, std::make_pair(iter->first, std::make_pair(ground, obstacles))); - } + occupancyGrid_->addToCache(iter->first, ground, obstacles); } } else { - ROS_ERROR("Some data missing for node %d to update the maps (image=%d, depth=%d, camera=%d)", - iter->first, - !(data.imageCompressed().empty() && data.imageRaw().empty())?1:0, - !(data.depthOrRightCompressed().empty() && data.depthOrRightRaw().empty())?1:0, - (data.cameraModels().size() || data.stereoCameraModel().isValidForProjection())?1:0); + ROS_ERROR("Data missing for node %d to update the maps", iter->first); } } + +#ifdef WITH_OCTOMAP_ROS +#ifdef RTABMAP_OCTOMAP + if(updateOctomap && + (iter->first < 0 || + octomap_->addedNodes().empty() || + iter->first > octomap_->addedNodes().rbegin()->first)) + { + std::map >::iterator mter = gridMaps_.find(iter->first); + std::map::iterator pter = gridMapsViewpoints_.find(iter->first); + if(mter != gridMaps_.end() && pter!=gridMapsViewpoints_.end()) + { + if((mter->second.first.empty() || mter->second.first.channels() > 2) && + (mter->second.second.empty() || mter->second.second.channels() > 2)) + { + octomap_->addToCache(iter->first, mter->second.first, mter->second.second, pter->second); + } + else if(!mter->second.first.empty() && !mter->second.second.empty()) + { + ROS_WARN("Node %d: Cannot update octomap with 2D occupancy grids. " + "Do \"$ rosrun rtabmap_ros rtabmap --params | grep Grid\" to see " + "all occupancy grid parameters.", + iter->first); + } + } + } +#endif +#endif } else { @@ -703,50 +508,12 @@ std::map MapsManager::updateMapCaches( } #endif #endif - - // cleanup not used nodes - UDEBUG("Cleanup not used nodes"); - for(std::map::Ptr >::iterator iter=clouds_.begin(); - iter!=clouds_.end();) - { - if(!uContains(poses, iter->first)) - { - clouds_.erase(iter++); - } - else - { - ++iter; - } - } - for(std::map::Ptr >::iterator iter=scans_.begin(); - iter!=scans_.end();) - { - if(!uContains(poses, iter->first)) - { - scans_.erase(iter++); - } - else - { - ++iter; - } - } - for(std::map >::iterator iter=projMaps_.begin(); - iter!=projMaps_.end();) - { - if(!uContains(poses, iter->first)) - { - projMaps_.erase(iter++); - } - else - { - ++iter; - } - } for(std::map >::iterator iter=gridMaps_.begin(); iter!=gridMaps_.end();) { if(!uContains(poses, iter->first)) { + UASSERT(gridMapsViewpoints_.erase(iter->first) != 0); gridMaps_.erase(iter++); } else @@ -754,18 +521,6 @@ std::map MapsManager::updateMapCaches( ++iter; } } - for(std::map >::iterator iter=cameraModels_.begin(); - iter!=cameraModels_.end();) - { - if(!uContains(poses, iter->first)) - { - cameraModels_.erase(iter++); - } - else - { - ++iter; - } - } if(longUpdate) { @@ -784,108 +539,168 @@ void MapsManager::publishMaps( UDEBUG("Publishing maps..."); // publish maps - if(cloudMapPub_.getNumSubscribers()) + if(cloudMapPub_.getNumSubscribers() || + scanMapPub_.getNumSubscribers() || + cloudObstaclesPub_.getNumSubscribers() || + cloudGroundPub_.getNumSubscribers()) { // generate the assembled cloud! UTimer time; - pcl::PointCloud::Ptr assembledCloud(new pcl::PointCloud); - int count = 0; - std::list > negativePoses; + + if(scanMapPub_.getNumSubscribers()) + { + if(parameters_.find(Parameters::kGridFromDepth()) != parameters_.end() && + uStr2Bool(parameters_.at(Parameters::kGridFromDepth()))) + { + ROS_WARN("/scan_map topic is deprecated! Subscribe to /cloud_map topic " + "instead with . " + "Do \"$ rosrun rtabmap_ros rtabmap --params | grep Grid\" to see " + "all occupancy grid parameters.", + Parameters::kGridFromDepth().c_str()); + } + else + { + ROS_WARN("/scan_map topic is deprecated! Subscribe to /cloud_map topic instead."); + } + } + + // detect if the graph has changed, if so, recreate the clouds + bool graphGroundChanged = false; + bool graphObstacleChanged = false; + bool updateGround = cloudMapPub_.getNumSubscribers() || + scanMapPub_.getNumSubscribers() || + cloudGroundPub_.getNumSubscribers(); + bool updateObstacles = cloudMapPub_.getNumSubscribers() || + scanMapPub_.getNumSubscribers() || + cloudObstaclesPub_.getNumSubscribers(); + for(std::map::const_iterator iter=poses.begin(); iter!=poses.end(); ++iter) + { + std::map::const_iterator jter; + if(updateGround) + { + jter = assembledGroundPoses_.find(iter->first); + if(jter != assembledGroundPoses_.end()) + { + UASSERT(!iter->second.isNull() && !jter->second.isNull()); + if(iter->second.getDistanceSquared(jter->second) > 0.0001) + { + graphGroundChanged = true; + } + } + } + if(updateObstacles) + { + jter = assembledObstaclePoses_.find(iter->first); + if(jter != assembledObstaclePoses_.end()) + { + UASSERT(!iter->second.isNull() && !jter->second.isNull()); + if(iter->second.getDistanceSquared(jter->second) > 0.0001) + { + graphObstacleChanged = true; + } + } + } + } + int countObstacles = 0; + int countGrounds = 0; + if(graphGroundChanged) + { + assembledGround_->clear(); + assembledGroundPoses_.clear(); + } + if(graphObstacleChanged) + { + assembledObstacles_->clear(); + assembledObstaclePoses_.clear(); + } for(std::map::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter) { if(iter->first > 0) { - std::map::Ptr >::iterator jter = clouds_.find(iter->first); - if(jter != clouds_.end()) + std::map >::iterator jter = gridMaps_.find(iter->first); + if(updateGround && + (graphGroundChanged || assembledGroundPoses_.find(iter->first) == assembledGroundPoses_.end())) { - pcl::PointCloud::Ptr transformed = util3d::transformPointCloud(jter->second, iter->second); - *assembledCloud+=*transformed; - ++count; + assembledGroundPoses_.insert(*iter); + if(jter!=gridMaps_.end() && jter->second.first.cols) + { + pcl::PointCloud::Ptr transformed = util3d::laserScanToPointCloudRGB(jter->second.first, iter->second); + *assembledGround_+=*transformed; + ++countGrounds; + } + } + if(updateObstacles && + (graphObstacleChanged || assembledObstaclePoses_.find(iter->first) == assembledObstaclePoses_.end())) + { + assembledObstaclePoses_.insert(*iter); + if(jter!=gridMaps_.end() && jter->second.second.cols) + { + pcl::PointCloud::Ptr transformed = util3d::laserScanToPointCloudRGB(jter->second.second, iter->second); + *assembledObstacles_+=*transformed; + ++countObstacles; + } } } - else + } + + if(cloudOutputVoxelized_) + { + UASSERT(gridCellSize_ > 0.0); + if(countGrounds && assembledGround_->size()) { - negativePoses.push_back(*iter); + assembledGround_ = util3d::voxelize(assembledGround_, gridCellSize_); } - } - - for(std::list >::reverse_iterator iter=negativePoses.rbegin(); iter!=negativePoses.rend(); ++iter) - { - std::map::Ptr >::iterator jter = clouds_.find(iter->first); - - if(jter != clouds_.end() && jter->second->size()) + if(countObstacles && assembledObstacles_->size()) { - std::map >::iterator kter = cameraModels_.find(iter->first); - if(cloudFrustumCulling_ && kter != cameraModels_.end() && assembledCloud->size()) - { - for(unsigned int i=0; isecond.size(); ++i) - { - if(kter->second[i].isValidForProjection()) - { - assembledCloud = util3d::frustumFiltering( - assembledCloud, - iter->second, // FIXME: should include camera local transform - kter->second[i].horizontalFOV(), - kter->second[i].verticalFOV(), - 0.0f, - cloudMaxDepth_>0.0?cloudMaxDepth_:999999., - true); - //ROS_INFO("Frustum culling %d ->%d", size, (int)assembledCloud->size()); - - pcl::PointCloud::Ptr transformed = util3d::transformPointCloud(jter->second, iter->second); - *assembledCloud+=*transformed; - ++count; - } - } - } - else - { - pcl::PointCloud::Ptr transformed = util3d::transformPointCloud(jter->second, iter->second); - if(assembledCloud->size()) - { - *assembledCloud+=*transformed; - } - else - { - assembledCloud = transformed; - } - ++count; - } + assembledObstacles_ = util3d::voxelize(assembledObstacles_, gridCellSize_); } } - if(assembledCloud->size() && (cloudFloorCullingHeight_ > 0.0 || cloudCeilingCullingHeight_ > 0.0)) - { - assembledCloud = util3d::passThrough(assembledCloud, "z", - cloudFloorCullingHeight_>0.0?cloudFloorCullingHeight_:-999.0, - cloudCeilingCullingHeight_>0.0 && (cloudFloorCullingHeight_<=0.0 || cloudCeilingCullingHeight_>cloudFloorCullingHeight_)?cloudCeilingCullingHeight_:999.0); - } + ROS_INFO("Assembled %d obstacle and %d ground clouds (%d points, %fs)", + countObstacles, countGrounds, (int)(assembledGround_->size() + assembledObstacles_->size()), time.ticks()); - if(assembledCloud->size() && cloudVoxelSize_ > 0 && cloudOutputVoxelized_) - { - assembledCloud = util3d::voxelize(assembledCloud, cloudVoxelSize_); - } - - ROS_INFO("Assembled %d clouds (%fs)", count, time.ticks()); - - if(assembledCloud->size()) + if(cloudGroundPub_.getNumSubscribers()) { sensor_msgs::PointCloud2::Ptr cloudMsg(new sensor_msgs::PointCloud2); - pcl::toROSMsg(*assembledCloud, *cloudMsg); + pcl::toROSMsg(*assembledGround_, *cloudMsg); cloudMsg->header.stamp = stamp; cloudMsg->header.frame_id = mapFrameId; - cloudMapPub_.publish(cloudMsg); + cloudGroundPub_.publish(cloudMsg); } - else if(poses.size() - negativePoses.size()) + if(cloudObstaclesPub_.getNumSubscribers()) { - ROS_WARN("Cloud map is empty! (poses=%d clouds=%d)", (int)poses.size(), (int)clouds_.size()); + sensor_msgs::PointCloud2::Ptr cloudMsg(new sensor_msgs::PointCloud2); + pcl::toROSMsg(*assembledObstacles_, *cloudMsg); + cloudMsg->header.stamp = stamp; + cloudMsg->header.frame_id = mapFrameId; + cloudObstaclesPub_.publish(cloudMsg); + } + if(cloudMapPub_.getNumSubscribers() || scanMapPub_.getNumSubscribers()) + { + pcl::PointCloud cloud = *assembledObstacles_ + *assembledGround_; + sensor_msgs::PointCloud2::Ptr cloudMsg(new sensor_msgs::PointCloud2); + pcl::toROSMsg(cloud, *cloudMsg); + cloudMsg->header.stamp = stamp; + cloudMsg->header.frame_id = mapFrameId; + + if(cloudMapPub_.getNumSubscribers()) + { + cloudMapPub_.publish(cloudMsg); + } + if(scanMapPub_.getNumSubscribers()) + { + scanMapPub_.publish(cloudMsg); + } } } else if(mapCacheCleanup_) { - clouds_.clear(); - cameraModels_.clear(); + assembledGround_->clear(); + assembledObstacles_->clear(); + assembledGroundPoses_.clear(); + assembledObstaclePoses_.clear(); } + #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP if(octoMapPubBin_.getNumSubscribers() || @@ -913,14 +728,14 @@ void MapsManager::publishMaps( if(octoMapCloud_.getNumSubscribers() || octoMapEmptySpace_.getNumSubscribers()) { sensor_msgs::PointCloud2 msg; - pcl::IndicesPtr obstacles(new std::vector); - pcl::IndicesPtr ground(new std::vector); - pcl::PointCloud::Ptr cloud = octomap_->createCloud(octomapTreeDepth_, obstacles.get(), ground.get()); + pcl::IndicesPtr obstacleIndices(new std::vector); + pcl::IndicesPtr emptyIndices(new std::vector); + pcl::PointCloud::Ptr cloud = octomap_->createCloud(octomapTreeDepth_, obstacleIndices.get(), emptyIndices.get()); if(octoMapCloud_.getNumSubscribers()) { pcl::PointCloud cloudObstacles; - pcl::copyPointCloud(*cloud, *obstacles, cloudObstacles); + pcl::copyPointCloud(*cloud, *obstacleIndices, cloudObstacles); pcl::toROSMsg(cloudObstacles, msg); msg.header.frame_id = mapFrameId; msg.header.stamp = stamp; @@ -928,9 +743,9 @@ void MapsManager::publishMaps( } if(octoMapEmptySpace_.getNumSubscribers()) { - pcl::PointCloud cloudGround; - pcl::copyPointCloud(*cloud, *ground, cloudGround); - pcl::toROSMsg(cloudGround, msg); + pcl::PointCloud cloudEmptySpace; + pcl::copyPointCloud(*cloud, *emptyIndices, cloudEmptySpace); + pcl::toROSMsg(cloudEmptySpace, msg); msg.header.frame_id = mapFrameId; msg.header.stamp = stamp; octoMapEmptySpace_.publish(msg); @@ -970,109 +785,40 @@ void MapsManager::publishMaps( } else if(poses.size()) { - ROS_WARN("Projection map is empty! (proj maps=%d)", (int)projMaps_.size()); + ROS_WARN("Octomap projection map is empty! (poses=%d octomap nodes=%d). " + "Make sure you activated \"%s\" and \"%s\" to true. " + "See \"$ rosrun rtabmap_ros rtabmap --params | grep Grid\" for more info.", + (int)poses.size(), (int)octomap_->octree()->size(), + Parameters::kGrid3D().c_str(), Parameters::kGridFromDepth().c_str()); } } } - else + else if(mapCacheCleanup_) { octomap_->clear(); } #endif #endif - - if(scanMapPub_.getNumSubscribers()) + if(gridMapPub_.getNumSubscribers() || projMapPub_.getNumSubscribers()) { - // generate the assembled scan cloud! - UTimer time; - pcl::PointCloud::Ptr assembledCloud(new pcl::PointCloud); - int count = 0; - std::list > negativePoses; - for(std::map::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter) + if(projMapPub_.getNumSubscribers()) { - if(iter->first > 0) + if(parameters_.find(Parameters::kGridFromDepth()) != parameters_.end() && + !uStr2Bool(parameters_.at(Parameters::kGridFromDepth()))) { - std::map::Ptr >::iterator jter = scans_.find(iter->first); - if(jter != scans_.end() && jter->second->size()) - { - pcl::PointCloud::Ptr transformed = util3d::transformPointCloud(jter->second, iter->second); - *assembledCloud+=*transformed; - ++count; - } + ROS_WARN("/proj_map topic is deprecated! Subscribe to /grid_map topic " + "instead with . " + "Do \"$ rosrun rtabmap_ros rtabmap --params | grep Grid\" to see " + "all occupancy grid parameters.", + Parameters::kGridFromDepth().c_str()); } - // negative poses are not used - } - - if(assembledCloud->size()) - { - if(assembledCloud->size() && scanVoxelSize_ > 0 && scanOutputVoxelized_) + else { - assembledCloud = util3d::voxelize(assembledCloud, scanVoxelSize_); + ROS_WARN("/proj_map topic is deprecated! Subscribe to /grid_map topic instead."); } - - ROS_INFO("Assembled %d scans (%fs)", count, time.ticks()); - - sensor_msgs::PointCloud2::Ptr cloudMsg(new sensor_msgs::PointCloud2); - pcl::toROSMsg(*assembledCloud, *cloudMsg); - cloudMsg->header.stamp = stamp; - cloudMsg->header.frame_id = mapFrameId; - scanMapPub_.publish(cloudMsg); } - else if(poses.size()) - { - ROS_WARN("Scan map is empty! (poses=%d, scans=%d)", (int)poses.size(), (int)scans_.size()); - } - } - else if(mapCacheCleanup_) - { - scans_.clear(); - } - if(projMapPub_.getNumSubscribers()) - { - // create the projection map - float xMin=0.0f, yMin=0.0f, gridCellSize = 0.05f; - cv::Mat pixels = this->generateProjMap(poses, xMin, yMin, gridCellSize); - - if(!pixels.empty()) - { - //init - nav_msgs::OccupancyGrid map; - map.info.resolution = gridCellSize; - map.info.origin.position.x = 0.0; - map.info.origin.position.y = 0.0; - map.info.origin.position.z = 0.0; - map.info.origin.orientation.x = 0.0; - map.info.origin.orientation.y = 0.0; - map.info.origin.orientation.z = 0.0; - map.info.origin.orientation.w = 1.0; - - map.info.width = pixels.cols; - map.info.height = pixels.rows; - map.info.origin.position.x = xMin; - map.info.origin.position.y = yMin; - map.data.resize(map.info.width * map.info.height); - - memcpy(map.data.data(), pixels.data, map.info.width * map.info.height); - - map.header.frame_id = mapFrameId; - map.header.stamp = stamp; - - projMapPub_.publish(map); - } - else if(poses.size()) - { - ROS_WARN("Projection map is empty! (proj maps=%d)", (int)projMaps_.size()); - } - } - else if(mapCacheCleanup_) - { - projMaps_.clear(); - } - - if(gridMapPub_.getNumSubscribers()) - { // create the grid map float xMin=0.0f, yMin=0.0f, gridCellSize = 0.05f; cv::Mat pixels = this->generateGridMap(poses, xMin, yMin, gridCellSize); @@ -1101,36 +847,28 @@ void MapsManager::publishMaps( map.header.frame_id = mapFrameId; map.header.stamp = stamp; - gridMapPub_.publish(map); + if(gridMapPub_.getNumSubscribers()) + { + gridMapPub_.publish(map); + } + if(projMapPub_.getNumSubscribers()) + { + projMapPub_.publish(map); + } } else if(poses.size()) { ROS_WARN("Grid map is empty! (local maps=%d)", (int)gridMaps_.size()); } } - else if(mapCacheCleanup_) + + if(!this->hasSubscribers() && mapCacheCleanup_) { gridMaps_.clear(); + gridMapsViewpoints_.clear(); } } -cv::Mat MapsManager::generateProjMap( - const std::map & poses, - float & xMin, - float & yMin, - float & gridCellSize) -{ - gridCellSize = gridCellSize_; - return util3d::create2DMapFromOccupancyLocalMaps( - poses, - projMaps_, - gridCellSize_, - xMin, yMin, - gridSize_, - gridEroded_, - footprintRadius_); -} - cv::Mat MapsManager::generateGridMap( const std::map & poses, float & xMin, @@ -1138,14 +876,23 @@ cv::Mat MapsManager::generateGridMap( float & gridCellSize) { gridCellSize = gridCellSize_; - cv::Mat map = util3d::create2DMapFromOccupancyLocalMaps( - poses, - gridMaps_, - gridCellSize_, - xMin, yMin, - gridSize_, - gridEroded_, - footprintRadius_); + cv::Mat map; + if(gridIncremental_) + { + occupancyGrid_->update(poses, gridSize_, footprintRadius_); + map = occupancyGrid_->getMap(xMin, yMin); + } + else + { + map = util3d::create2DMapFromOccupancyLocalMaps( + poses, + gridMaps_, + gridCellSize_, + xMin, yMin, + gridSize_, + gridEroded_, + footprintRadius_); + } return map; } diff --git a/src/MsgConversion.cpp b/src/MsgConversion.cpp index 9d8f8431..fba69cc9 100644 --- a/src/MsgConversion.cpp +++ b/src/MsgConversion.cpp @@ -332,16 +332,42 @@ rtabmap::CameraModel cameraModelFromROS( const sensor_msgs::CameraInfo & camInfo, const rtabmap::Transform & localTransform) { - image_geometry::PinholeCameraModel model; - model.fromCameraInfo(camInfo); + cv::Mat D; + if(camInfo.D.size()) + { + D = cv::Mat(1, camInfo.D.size(), CV_64FC1); + memcpy(D.data, camInfo.D.data(), D.cols*sizeof(double)); + } + + cv:: Mat K; + UASSERT(camInfo.K.empty() || camInfo.K.size() == 9); + if(!camInfo.K.empty()) + { + K = cv::Mat(3, 3, CV_64FC1); + memcpy(K.data, camInfo.K.elems, 9*sizeof(double)); + } + + cv:: Mat R; + UASSERT(camInfo.R.empty() || camInfo.R.size() == 9); + if(!camInfo.R.empty()) + { + R = cv::Mat(3, 3, CV_64FC1); + memcpy(R.data, camInfo.R.elems, 9*sizeof(double)); + } + + cv:: Mat P; + UASSERT(camInfo.P.empty() || camInfo.P.size() == 12); + if(!camInfo.P.empty()) + { + P = cv::Mat(3, 4, CV_64FC1); + memcpy(P.data, camInfo.P.elems, 12*sizeof(double)); + } + return rtabmap::CameraModel( - model.fx(), - model.fy(), - model.cx(), - model.cy(), - localTransform, - 0.0, - cv::Size(model.fullResolution().width, model.fullResolution().height)); + "ros", + cv::Size(camInfo.width, camInfo.height), + K, D, R, P, + localTransform); } void cameraModelToROS( const rtabmap::CameraModel & model, @@ -401,16 +427,11 @@ rtabmap::StereoCameraModel stereoCameraModelFromROS( const sensor_msgs::CameraInfo & rightCamInfo, const rtabmap::Transform & localTransform) { - image_geometry::StereoCameraModel model; - model.fromCameraInfo(leftCamInfo, rightCamInfo); return rtabmap::StereoCameraModel( - model.left().fx(), - model.left().fy(), - model.left().cx(), - model.left().cy(), - model.baseline(), - localTransform, - cv::Size(model.left().fullResolution().width, model.left().fullResolution().height)); + "ros", + cameraModelFromROS(leftCamInfo, localTransform), + cameraModelFromROS(rightCamInfo, localTransform), + rtabmap::Transform()); } void mapDataFromROS( @@ -587,8 +608,10 @@ rtabmap::Signature nodeDataFromROS(const rtabmap_ros::NodeData & msg) stereoModel.isValidForProjection()? rtabmap::SensorData( compressedMatFromBytes(msg.laserScan), - msg.laserScanMaxPts, - msg.laserScanMaxRange, + rtabmap::LaserScanInfo( + msg.laserScanMaxPts, + msg.laserScanMaxRange, + transformFromGeometryMsg(msg.laserScanLocalTransform)), compressedMatFromBytes(msg.image), compressedMatFromBytes(msg.depth), stereoModel, @@ -597,8 +620,10 @@ rtabmap::Signature nodeDataFromROS(const rtabmap_ros::NodeData & msg) compressedMatFromBytes(msg.userData)): rtabmap::SensorData( compressedMatFromBytes(msg.laserScan), - msg.laserScanMaxPts, - msg.laserScanMaxRange, + rtabmap::LaserScanInfo( + msg.laserScanMaxPts, + msg.laserScanMaxRange, + transformFromGeometryMsg(msg.laserScanLocalTransform)), compressedMatFromBytes(msg.image), compressedMatFromBytes(msg.depth), models, @@ -608,6 +633,11 @@ rtabmap::Signature nodeDataFromROS(const rtabmap_ros::NodeData & msg) s.setWords(words); s.setWords3(words3D); s.setWordsDescriptors(wordsDescriptors); + s.sensorData().setOccupancyGrid( + compressedMatFromBytes(msg.grid_ground), + compressedMatFromBytes(msg.grid_obstacles), + msg.grid_cell_size, + point3fFromROS(msg.grid_view_point)); return s; } void nodeDataToROS(const rtabmap::Signature & signature, rtabmap_ros::NodeData & msg) @@ -624,8 +654,13 @@ void nodeDataToROS(const rtabmap::Signature & signature, rtabmap_ros::NodeData & compressedMatToBytes(signature.sensorData().depthOrRightCompressed(), msg.depth); compressedMatToBytes(signature.sensorData().laserScanCompressed(), msg.laserScan); compressedMatToBytes(signature.sensorData().userDataCompressed(), msg.userData); - msg.laserScanMaxPts = signature.sensorData().laserScanMaxPts(); - msg.laserScanMaxRange = signature.sensorData().laserScanMaxRange(); + compressedMatToBytes(signature.sensorData().gridGroundCellsCompressed(), msg.grid_ground); + compressedMatToBytes(signature.sensorData().gridObstacleCellsCompressed(), msg.grid_obstacles); + point3fToROS(signature.sensorData().gridViewPoint(), msg.grid_view_point); + msg.grid_cell_size = signature.sensorData().gridCellSize(); + msg.laserScanMaxPts = signature.sensorData().laserScanInfo().maxPoints(); + msg.laserScanMaxRange = signature.sensorData().laserScanInfo().maxRange(); + transformToGeometryMsg(signature.sensorData().laserScanInfo().localTransform(), msg.laserScanLocalTransform); msg.baseline = 0; if(signature.sensorData().cameraModels().size()) { diff --git a/src/nodelets/icp_odometry.cpp b/src/nodelets/icp_odometry.cpp index 72aeee4b..6c96fcb2 100644 --- a/src/nodelets/icp_odometry.cpp +++ b/src/nodelets/icp_odometry.cpp @@ -93,9 +93,10 @@ private: void callbackScan(const sensor_msgs::LaserScanConstPtr& scanMsg) { // make sure the frame of the laser is updated too - if(getTransform(this->frameId(), + Transform localScanTransform = getTransform(this->frameId(), scanMsg->header.frame_id, - scanMsg->header.stamp + ros::Duration().fromSec(scanMsg->ranges.size()*scanMsg->time_increment)).isNull()) + scanMsg->header.stamp + ros::Duration().fromSec(scanMsg->ranges.size()*scanMsg->time_increment)); + if(localScanTransform.isNull()) { ROS_ERROR("TF of received laser scan topic at time %fs is not set, aborting odometry update.", scanMsg->header.stamp.toSec()); return; @@ -104,7 +105,7 @@ private: //transform in frameId_ frame sensor_msgs::PointCloud2 scanOut; laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(this->frameId(), *scanMsg, scanOut, this->tfListener()); + projection.transformLaserScanToPointCloud(scanMsg->header.frame_id, *scanMsg, scanOut, this->tfListener()); pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(scanOut, *pclScan); @@ -112,8 +113,7 @@ private: rtabmap::SensorData data( scan, - (int)scanMsg->ranges.size(), - scanMsg->range_max, + LaserScanInfo((int)scanMsg->ranges.size(), scanMsg->range_max, localScanTransform), cv::Mat(), cv::Mat(), CameraModel(), @@ -147,10 +147,6 @@ private: { pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*cloudMsg, *pclScan); - if(!localScanTransform.isIdentity()) - { - pclScan = util3d::transformPointCloud(pclScan, localScanTransform); - } scan = util3d::laserScanFromPointCloud(*pclScan); } else @@ -158,11 +154,6 @@ private: pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*cloudMsg, *pclScan); - if(!localScanTransform.isIdentity()) - { - pclScan = util3d::transformPointCloud(pclScan, localScanTransform); - } - if(scanCloudNormalK_ > 0) { //compute normals @@ -179,8 +170,7 @@ private: rtabmap::SensorData data( scan, - scanCloudMaxPoints_, - 0, + LaserScanInfo(scanCloudMaxPoints_, 0, localScanTransform), cv::Mat(), cv::Mat(), CameraModel(), diff --git a/src/nodelets/rgbdicp_odometry.cpp b/src/nodelets/rgbdicp_odometry.cpp index 3431b8d6..80f0de44 100644 --- a/src/nodelets/rgbdicp_odometry.cpp +++ b/src/nodelets/rgbdicp_odometry.cpp @@ -266,12 +266,14 @@ private: cv_bridge::CvImagePtr ptrDepth = cv_bridge::toCvCopy(depth); cv::Mat scan; + Transform localScanTransform = Transform::getIdentity(); if(scanMsg.get() != 0) { // make sure the frame of the laser is updated too - if(getTransform(this->frameId(), + localScanTransform = getTransform(this->frameId(), scanMsg->header.frame_id, - scanMsg->header.stamp + ros::Duration().fromSec(scanMsg->ranges.size()*scanMsg->time_increment)).isNull()) + scanMsg->header.stamp + ros::Duration().fromSec(scanMsg->ranges.size()*scanMsg->time_increment)); + if(localScanTransform.isNull()) { ROS_ERROR("TF of received laser scan topic at time %fs is not set, aborting odometry update.", scanMsg->header.stamp.toSec()); return; @@ -280,7 +282,7 @@ private: //transform in frameId_ frame sensor_msgs::PointCloud2 scanOut; laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(this->frameId(), *scanMsg, scanOut, this->tfListener()); + projection.transformLaserScanToPointCloud(scanMsg->header.frame_id, *scanMsg, scanOut, this->tfListener()); pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(scanOut, *pclScan); @@ -297,7 +299,7 @@ private: break; } } - Transform localScanTransform = getTransform(this->frameId(), cloudMsg->header.frame_id, cloudMsg->header.stamp); + localScanTransform = getTransform(this->frameId(), cloudMsg->header.frame_id, cloudMsg->header.stamp); if(localScanTransform.isNull()) { ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", cloudMsg->header.stamp.toSec()); @@ -308,10 +310,6 @@ private: { pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*cloudMsg, *pclScan); - if(!localScanTransform.isIdentity()) - { - pclScan = util3d::transformPointCloud(pclScan, localScanTransform); - } scan = util3d::laserScanFromPointCloud(*pclScan); } else @@ -319,11 +317,6 @@ private: pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(*cloudMsg, *pclScan); - if(!localScanTransform.isIdentity()) - { - pclScan = util3d::transformPointCloud(pclScan, localScanTransform); - } - if(scanCloudNormalK_ > 0) { //compute normals @@ -341,8 +334,10 @@ private: rtabmap::SensorData data( scan, - scanMsg.get() != 0?(int)scanMsg->ranges.size():cloudMsg.get() != 0?scanCloudMaxPoints_:0, - scanMsg.get() != 0?scanMsg->range_max:0, + LaserScanInfo( + scanMsg.get() != 0?(int)scanMsg->ranges.size():cloudMsg.get() != 0?scanCloudMaxPoints_:0, + scanMsg.get() != 0?scanMsg->range_max:0, + localScanTransform), ptrImage->image, ptrDepth->image, rtabmapModel, From 7323e58e8d8509bd49b623bdd79ac2e0ea58eb28 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 23 Aug 2016 10:22:28 -0400 Subject: [PATCH 24/78] MapsManager: added "cloud_subtract_filtering" and "cloud_subtract_filtering_min_neighbors" parameters --- include/rtabmap_ros/MapsManager.h | 7 + src/CoreWrapper.cpp | 12 +- src/MapsManager.cpp | 244 +++++++++++++++++++++++++++++- 3 files changed, 251 insertions(+), 12 deletions(-) diff --git a/include/rtabmap_ros/MapsManager.h b/include/rtabmap_ros/MapsManager.h index f665bb69..0fba3bc5 100644 --- a/include/rtabmap_ros/MapsManager.h +++ b/include/rtabmap_ros/MapsManager.h @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include #include @@ -77,6 +78,8 @@ public: private: // mapping stuff bool cloudOutputVoxelized_; + bool cloudSubtractFiltering_; + int cloudSubtractFilteringMinNeighbors_; double gridCellSize_; bool gridIncremental_; double gridSize_; @@ -103,6 +106,10 @@ private: std::map assembledObstaclePoses_; pcl::PointCloud::Ptr assembledObstacles_; pcl::PointCloud::Ptr assembledGround_; + rtabmap::FlannIndex assembledGroundIndex_; + rtabmap::FlannIndex assembledObstacleIndex_; + std::map::Ptr > groundClouds_; + std::map::Ptr > obstacleClouds_; std::map gridPoses_; cv::Mat gridMap_; diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 2cb8f85a..202cad18 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -242,7 +242,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) depthCameras = 1; } - ROS_INFO("rtabmap: frame_id = %s", frameId_.c_str()); + ROS_INFO("rtabmap: frame_id = %s", frameId_.c_str()); if(!odomFrameId_.empty()) { ROS_INFO("rtabmap: odom_frame_id = %s", odomFrameId_.c_str()); @@ -253,12 +253,12 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) groundTruthFrameId_.c_str(), groundTruthBaseFrameId_.c_str()); } - ROS_INFO("rtabmap: map_frame_id = %s", mapFrameId_.c_str()); - ROS_INFO("rtabmap: queue_size = %d", queueSize); - ROS_INFO("rtabmap: tf_delay = %f", tfDelay); - ROS_INFO("rtabmap: tf_tolerance = %f", tfTolerance); + ROS_INFO("rtabmap: map_frame_id = %s", mapFrameId_.c_str()); + ROS_INFO("rtabmap: queue_size = %d", queueSize); + ROS_INFO("rtabmap: tf_delay = %f", tfDelay); + ROS_INFO("rtabmap: tf_tolerance = %f", tfTolerance); ROS_INFO("rtabmap: depth_cameras = %d", depthCameras); - ROS_INFO("rtabmap: approx_sync = %s", approxSync?"true":"false"); + ROS_INFO("rtabmap: approx_sync = %s", approxSync?"true":"false"); infoPub_ = nh.advertise("info", 1); mapDataPub_ = nh.advertise("mapData", 1); diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index 3c99afae..2dd0bbf5 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -40,6 +40,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + #include #include @@ -57,6 +59,8 @@ using namespace rtabmap; MapsManager::MapsManager(bool usePublicNamespace) : cloudOutputVoxelized_(true), + cloudSubtractFiltering_(false), + cloudSubtractFilteringMinNeighbors_(2), gridCellSize_(0.05), // meters gridIncremental_(false), gridSize_(0), // meters @@ -105,6 +109,22 @@ MapsManager::MapsManager(bool usePublicNamespace) : } } pnh.param("cloud_output_voxelized", cloudOutputVoxelized_, cloudOutputVoxelized_); + pnh.param("cloud_subtract_filtering", cloudSubtractFiltering_, cloudSubtractFiltering_); + pnh.param("cloud_subtract_filtering_min_neighbors", cloudSubtractFilteringMinNeighbors_, cloudSubtractFilteringMinNeighbors_); + + std::string name = ros::this_node::getName(); + ROS_INFO("%s(maps): grid_cell_size = %f", name.c_str(), gridCellSize_); + ROS_INFO("%s(maps): grid_incremental = %s", name.c_str(), gridIncremental_?"true":"false"); + ROS_INFO("%s(maps): grid_size = %f", name.c_str(), gridSize_); + ROS_INFO("%s(maps): grid_eroded = %s", name.c_str(), gridEroded_?"true":"false"); + ROS_INFO("%s(maps): grid_footprint_radius = %f", name.c_str(), footprintRadius_); + ROS_INFO("%s(maps): map_filter_radius = %f", name.c_str(), mapFilterRadius_); + ROS_INFO("%s(maps): map_filter_angle = %f", name.c_str(), mapFilterAngle_); + ROS_INFO("%s(maps): map_cleanup = %s", name.c_str(), mapCacheCleanup_?"true":"false"); + ROS_INFO("%s(maps): map_negative_poses_ignored = %s", name.c_str(), negativePosesIgnored_?"true":"false"); + ROS_INFO("%s(maps): cloud_output_voxelized = %s", name.c_str(), cloudOutputVoxelized_?"true":"false"); + ROS_INFO("%s(maps): cloud_subtract_filtering = %s", name.c_str(), cloudSubtractFiltering_?"true":"false"); + ROS_INFO("%s(maps): cloud_subtract_filtering_min_neighbors = %d", name.c_str(), cloudSubtractFilteringMinNeighbors_); #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP @@ -120,6 +140,7 @@ MapsManager::MapsManager(bool usePublicNamespace) : ROS_WARN("octomap_tree_depth cannot be negative, set to 16 instead"); octomapTreeDepth_ = 16; } + ROS_INFO("%s(maps): octomap_tree_depth = %d", name.c_str(), octomapTreeDepth_); #endif #endif @@ -266,6 +287,10 @@ void MapsManager::clear() assembledObstacles_->clear(); assembledGroundPoses_.clear(); assembledObstaclePoses_.clear(); + assembledGroundIndex_.release(); + assembledObstacleIndex_.release(); + groundClouds_.clear(); + obstacleClouds_.clear(); occupancyGrid_->clear(); #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP @@ -522,6 +547,32 @@ std::map MapsManager::updateMapCaches( } } + for(std::map::Ptr >::iterator iter=groundClouds_.begin(); + iter!=groundClouds_.end();) + { + if(!uContains(poses, iter->first)) + { + groundClouds_.erase(iter++); + } + else + { + ++iter; + } + } + + for(std::map::Ptr >::iterator iter=obstacleClouds_.begin(); + iter!=obstacleClouds_.end();) + { + if(!uContains(poses, iter->first)) + { + obstacleClouds_.erase(iter++); + } + else + { + ++iter; + } + } + if(longUpdate) { ROS_WARN("Map(s) updated!"); @@ -531,6 +582,33 @@ std::map MapsManager::updateMapCaches( return filteredPoses; } +pcl::PointCloud::Ptr subtractFiltering( + const pcl::PointCloud::Ptr & cloud, + const rtabmap::FlannIndex & substractCloudIndex, + float radiusSearch, + int minNeighborsInRadius) +{ + UASSERT(minNeighborsInRadius > 0); + UASSERT(substractCloudIndex.indexedFeatures()); + + pcl::PointCloud::Ptr output(new pcl::PointCloud); + output->resize(cloud->size()); + int oi = 0; // output iterator + for(unsigned int i=0; isize(); ++i) + { + std::vector > kIndices; + std::vector > kDistances; + cv::Mat pt = (cv::Mat_(1, 3) << cloud->at(i).x, cloud->at(i).y, cloud->at(i).z); + substractCloudIndex.radiusSearch(pt, kIndices, kDistances, radiusSearch, minNeighborsInRadius, 32, 0, false); + if(kIndices.size() == 1 && kIndices[0].size() < minNeighborsInRadius) + { + output->at(oi++) = cloud->at(i); + } + } + output->resize(oi); + return output; +} + void MapsManager::publishMaps( const std::map & poses, const ros::Time & stamp, @@ -603,40 +681,190 @@ void MapsManager::publishMaps( } int countObstacles = 0; int countGrounds = 0; + int previousIndexedGroundSize = assembledGroundIndex_.indexedFeatures(); + int previousIndexedObstacleSize = assembledObstacleIndex_.indexedFeatures(); if(graphGroundChanged) { + int previousSize = assembledGround_->size(); assembledGround_->clear(); + assembledGround_->reserve(previousSize); assembledGroundPoses_.clear(); + assembledGroundIndex_.release(); } if(graphObstacleChanged) { + int previousSize = assembledObstacles_->size(); assembledObstacles_->clear(); + assembledObstacles_->reserve(previousSize); assembledObstaclePoses_.clear(); + assembledObstacleIndex_.release(); } + + if(graphGroundChanged || graphObstacleChanged) + { + UTimer t; + cv::Mat tmpGroundPts; + cv::Mat tmpObstaclePts; + for(std::map::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter) + { + if(iter->first > 0) + { + if(updateGround && + (graphGroundChanged || assembledGroundPoses_.find(iter->first) == assembledGroundPoses_.end())) + { + assembledGroundPoses_.insert(*iter); + std::map::Ptr >::iterator kter=groundClouds_.find(iter->first); + if(kter != groundClouds_.end() && kter->second->size()) + { + pcl::PointCloud::Ptr transformed = util3d::transformPointCloud(kter->second, iter->second); + *assembledGround_+=*transformed; + if(cloudSubtractFiltering_) + { + for(unsigned int i=0; isize(); ++i) + { + if(tmpGroundPts.empty()) + { + tmpGroundPts = (cv::Mat_(1, 3) << transformed->at(i).x, transformed->at(i).y, transformed->at(i).z); + tmpGroundPts.reserve(previousIndexedGroundSize>0?previousIndexedGroundSize:100); + } + else + { + cv::Mat pt = (cv::Mat_(1, 3) << transformed->at(i).x, transformed->at(i).y, transformed->at(i).z); + tmpGroundPts.push_back(pt); + } + } + } + ++countGrounds; + } + } + if(updateObstacles && + (graphObstacleChanged || assembledObstaclePoses_.find(iter->first) == assembledObstaclePoses_.end())) + { + assembledObstaclePoses_.insert(*iter); + std::map::Ptr >::iterator kter=obstacleClouds_.find(iter->first); + if(kter != obstacleClouds_.end() && kter->second->size()) + { + pcl::PointCloud::Ptr transformed = util3d::transformPointCloud(kter->second, iter->second); + *assembledObstacles_+=*transformed; + if(cloudSubtractFiltering_) + { + for(unsigned int i=0; isize(); ++i) + { + if(tmpObstaclePts.empty()) + { + tmpObstaclePts = (cv::Mat_(1, 3) << transformed->at(i).x, transformed->at(i).y, transformed->at(i).z); + tmpObstaclePts.reserve(previousIndexedObstacleSize>0?previousIndexedObstacleSize:100); + } + else + { + cv::Mat pt = (cv::Mat_(1, 3) << transformed->at(i).x, transformed->at(i).y, transformed->at(i).z); + tmpObstaclePts.push_back(pt); + } + } + } + ++countObstacles; + } + } + } + } + double addingPointsTime = t.ticks(); + + if(graphGroundChanged && !tmpGroundPts.empty()) + { + assembledGroundIndex_.buildKDTreeSingleIndex(tmpGroundPts, 15); + } + if(graphObstacleChanged && !tmpObstaclePts.empty()) + { + assembledObstacleIndex_.buildKDTreeSingleIndex(tmpObstaclePts, 15); + } + double indexingTime = t.ticks(); + UINFO("Graph changed! Time recreating clouds (%d ground, %d obstacles) = %f s (indexing %fs)", countGrounds, countObstacles, addingPointsTime+indexingTime, indexingTime); + } + for(std::map::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter) { if(iter->first > 0) { std::map >::iterator jter = gridMaps_.find(iter->first); - if(updateGround && - (graphGroundChanged || assembledGroundPoses_.find(iter->first) == assembledGroundPoses_.end())) + if(updateGround && assembledGroundPoses_.find(iter->first) == assembledGroundPoses_.end()) { assembledGroundPoses_.insert(*iter); if(jter!=gridMaps_.end() && jter->second.first.cols) { pcl::PointCloud::Ptr transformed = util3d::laserScanToPointCloudRGB(jter->second.first, iter->second); - *assembledGround_+=*transformed; + pcl::PointCloud::Ptr subtractedCloud = transformed; + if(cloudSubtractFiltering_) + { + if(assembledGroundIndex_.indexedFeatures()) + { + subtractedCloud = subtractFiltering(transformed, assembledGroundIndex_, gridCellSize_, cloudSubtractFilteringMinNeighbors_); + } + if(subtractedCloud->size()) + { + UDEBUG("Adding ground %d pts=%d/%d (index=%d)", iter->first, subtractedCloud->size(), transformed->size(), assembledGroundIndex_.indexedFeatures()); + cv::Mat pts(subtractedCloud->size(), 3, CV_32FC1); + for(unsigned int i=0; isize(); ++i) + { + pts.at(i, 0) = subtractedCloud->at(i).x; + pts.at(i, 1) = subtractedCloud->at(i).y; + pts.at(i, 2) = subtractedCloud->at(i).z; + } + if(!assembledGroundIndex_.isBuilt()) + { + assembledGroundIndex_.buildKDTreeSingleIndex(pts, 15); + } + else + { + assembledGroundIndex_.addPoints(pts); + } + } + } + groundClouds_.insert(std::make_pair(iter->first, util3d::transformPointCloud(subtractedCloud, iter->second.inverse()))); + if(subtractedCloud->size()) + { + *assembledGround_+=*subtractedCloud; + } ++countGrounds; } } - if(updateObstacles && - (graphObstacleChanged || assembledObstaclePoses_.find(iter->first) == assembledObstaclePoses_.end())) + if(updateObstacles && assembledObstaclePoses_.find(iter->first) == assembledObstaclePoses_.end()) { assembledObstaclePoses_.insert(*iter); if(jter!=gridMaps_.end() && jter->second.second.cols) { pcl::PointCloud::Ptr transformed = util3d::laserScanToPointCloudRGB(jter->second.second, iter->second); - *assembledObstacles_+=*transformed; + pcl::PointCloud::Ptr subtractedCloud = transformed; + if(cloudSubtractFiltering_) + { + if(assembledObstacleIndex_.indexedFeatures()) + { + subtractedCloud = subtractFiltering(transformed, assembledObstacleIndex_, gridCellSize_, cloudSubtractFilteringMinNeighbors_); + } + if(subtractedCloud->size()) + { + UDEBUG("Adding obstacle %d pts=%d/%d (index=%d)", iter->first, subtractedCloud->size(), transformed->size(), assembledObstacleIndex_.indexedFeatures()); + cv::Mat pts(subtractedCloud->size(), 3, CV_32FC1); + for(unsigned int i=0; isize(); ++i) + { + pts.at(i, 0) = subtractedCloud->at(i).x; + pts.at(i, 1) = subtractedCloud->at(i).y; + pts.at(i, 2) = subtractedCloud->at(i).z; + } + if(!assembledObstacleIndex_.isBuilt()) + { + assembledObstacleIndex_.buildKDTreeSingleIndex(pts, 15); + } + else + { + assembledObstacleIndex_.addPoints(pts); + } + } + } + obstacleClouds_.insert(std::make_pair(iter->first, util3d::transformPointCloud(subtractedCloud, iter->second.inverse()))); + if(subtractedCloud->size()) + { + *assembledObstacles_+=*subtractedCloud; + } ++countObstacles; } } @@ -699,6 +927,10 @@ void MapsManager::publishMaps( assembledObstacles_->clear(); assembledGroundPoses_.clear(); assembledObstaclePoses_.clear(); + assembledGroundIndex_.release(); + assembledObstacleIndex_.release(); + groundClouds_.clear(); + obstacleClouds_.clear(); } #ifdef WITH_OCTOMAP_ROS From 02decf639dc6f781f911e99003441c0bf51c281b Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 24 Aug 2016 12:17:12 -0400 Subject: [PATCH 25/78] Refactoring common depth/stereo/laser msgs conversion into shared methods in MsgConversion.h between CoreWrapper.cpp and GuiWrapper.cpp (avoiding some duplicated codes) --- include/rtabmap_ros/GuiWrapper.h | 1 - include/rtabmap_ros/MsgConversion.h | 70 ++++ src/CoreWrapper.cpp | 526 ++++++---------------------- src/GuiWrapper.cpp | 405 ++++----------------- src/MsgConversion.cpp | 440 +++++++++++++++++++++++ 5 files changed, 697 insertions(+), 745 deletions(-) diff --git a/include/rtabmap_ros/GuiWrapper.h b/include/rtabmap_ros/GuiWrapper.h index b9ba729f..24f15cfa 100644 --- a/include/rtabmap_ros/GuiWrapper.h +++ b/include/rtabmap_ros/GuiWrapper.h @@ -258,7 +258,6 @@ private: const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); void processRequestedMap(const rtabmap_ros::MapData & map); - rtabmap::Transform getTransform(const std::string & fromFrameId, const std::string & toFrameId, const ros::Time & stamp) const; private: rtabmap::MainWindow * mainWindow_; diff --git a/include/rtabmap_ros/MsgConversion.h b/include/rtabmap_ros/MsgConversion.h index 9af81a23..b0eaaa93 100644 --- a/include/rtabmap_ros/MsgConversion.h +++ b/include/rtabmap_ros/MsgConversion.h @@ -29,9 +29,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define MSGCONVERSION_H_ #include +#include #include #include #include +#include +#include #include #include @@ -139,6 +142,73 @@ void odomInfoToROS(const rtabmap::OdometryInfo & info, rtabmap_ros::OdomInfo & m inline double timestampFromROS(const ros::Time & stamp) {return double(stamp.sec) + double(stamp.nsec)/1000000000.0;} +// common stuff +rtabmap::Transform getTransform( + const std::string & fromFrameId, + const std::string & toFrameId, + const ros::Time & stamp, + tf::TransformListener & listener, + double waitForTransform); + + +// get moving transform accordingly to a fixed frame. For example get +// transform of /base_link between two stamps accordingly to /odom frame. +rtabmap::Transform getTransform( + const std::string & sourceTargetFrame, + const std::string & fixedFrame, + const ros::Time & stampSource, + const ros::Time & stampTarget, + tf::TransformListener & listener, + double waitForTransform); + +bool convertRGBDMsgs( + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, + const std::string & frameId, + const std::string & odomFrameId, + const ros::Time & odomStamp, + cv::Mat & rgb, + cv::Mat & depth, + std::vector & cameraModels, + tf::TransformListener & listener, + double waitForTransform); + +bool convertStereoMsg( + const sensor_msgs::ImageConstPtr& leftImageMsg, + const sensor_msgs::ImageConstPtr& rightImageMsg, + const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, + const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, + const std::string & frameId, + const std::string & odomFrameId, + const ros::Time & odomStamp, + cv::Mat & left, + cv::Mat & right, + rtabmap::StereoCameraModel & stereoModel, + tf::TransformListener & listener, + double waitForTransform); + +bool convertScanMsg( + const sensor_msgs::LaserScanConstPtr& scan2dMsg, + const std::string & frameId, + const std::string & odomFrameId, + const ros::Time & odomStamp, + cv::Mat & scan, + rtabmap::Transform & scanLocalTransform, + tf::TransformListener & listener, + double waitForTransform); + +bool convertScan3dMsg( + const sensor_msgs::PointCloud2ConstPtr & scan3dMsg, + const std::string & frameId, + const std::string & odomFrameId, + const ros::Time & odomStamp, + int scanCloudNormalK, + cv::Mat & scan, + rtabmap::Transform & scanLocalTransform, + tf::TransformListener & listener, + double waitForTransform); + } #endif /* MSGCONVERSION_H_ */ diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 202cad18..8dc42ce9 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -29,12 +29,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include -#include -#include + #include #include #include +#include +#include #include @@ -48,15 +48,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #include #include -#include - -#include - -#include #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP @@ -870,196 +864,70 @@ void CoreWrapper::commonDepthCallback( const sensor_msgs::LaserScanConstPtr& scan2dMsg, const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) { - UASSERT(imageMsgs.size()>0 && - imageMsgs.size() == depthMsgs.size() && - imageMsgs.size() == cameraInfoMsgs.size()); - - //for sync transform - Transform odomT = getTransform(odomFrameId, frameId_, lastPoseStamp_); - if(odomT.isNull() && !odomFrameId_.empty()) - { - ROS_WARN("Could not get TF transform from %s to %s, sensors will not be synchronized with odometry pose.", - odomFrameId.c_str(), frameId_.c_str()); - } - - int imageWidth = imageMsgs[0]->width; - int imageHeight = imageMsgs[0]->height; - int cameraCount = imageMsgs.size(); cv::Mat rgb; cv::Mat depth; - pcl::PointCloud scanCloud2d; - std::vector cameraModels; - int genMaxScanPts = 0; - for(unsigned int i=0; i cameraModels; + if(!rtabmap_ros::convertRGBDMsgs( + imageMsgs, + depthMsgs, + cameraInfoMsgs, + frameId_, + odomFrameId, + lastPoseStamp_, + rgb, + depth, + cameraModels, + tfListener_, + waitForTransform_?waitForTransformDuration_:0.0)) { - if(!(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) == 0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) ==0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) ==0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || - !(depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_16UC1) == 0 || - depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_32FC1) == 0 || - depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0)) - { - ROS_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8 and image_depth=32FC1,16UC1,mono16"); - return; - } + ROS_ERROR("Could not convert rgb/depth msgs! Aborting rtabmap update..."); + return; + } - UASSERT_MSG(imageMsgs[i]->width == imageWidth && imageMsgs[i]->height == imageHeight, - uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", - imageWidth, - imageMsgs[i]->width, - imageHeight, - imageMsgs[i]->height).c_str()); - UASSERT_MSG(depthMsgs[i]->width == imageWidth && depthMsgs[i]->height == imageHeight, - uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", - imageWidth, - depthMsgs[i]->width, - imageHeight, - depthMsgs[i]->height).c_str()); + UASSERT(uContains(parameters_, rtabmap::Parameters::kMemSaveDepth16Format())); + if(depth.type() == CV_32FC1 && uStr2Bool(parameters_.at(Parameters::kMemSaveDepth16Format()))) + { + depth = rtabmap::util2d::cvtDepthFromFloat(depth); + static bool shown = false; + if(!shown) + { + ROS_WARN("Save depth data to 16 bits format: depth type detected is " + "32FC1, use 16UC1 depth format to avoid this conversion " + "(or set parameter \"Mem/SaveDepth16Format=false\" to use " + "32bits format). This message is only printed once..."); + shown = true; + } + } - Transform localTransform = getTransform(frameId_, depthMsgs[i]->header.frame_id, depthMsgs[i]->header.stamp); - if(localTransform.isNull()) - { - ROS_ERROR("TF of received depth image %d at time %fs is not set, aborting rtabmap update.", i, depthMsgs[i]->header.stamp.toSec()); - return; - } - // sync with odometry stamp - if(lastPoseStamp_ != depthMsgs[i]->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomFrameId, frameId_, depthMsgs[i]->header.stamp); - if(sensorT.isNull()) - { - ROS_WARN("Could not get odometry value for depth image %d stamp (%fs). Latest odometry " - "stamp is %fs. The depth image pose will not be synchronized with odometry.", i, depthMsgs[i]->header.stamp.toSec(), lastPoseStamp_.toSec()); - } - else - { - localTransform = odomT.inverse() * sensorT * localTransform; - } - } - } - - cv_bridge::CvImageConstPtr ptrImage; - if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1)==0) - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i]); - } - else if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i], "mono8"); - } - else - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i], "bgr8"); - } - cv_bridge::CvImageConstPtr ptrDepth = cv_bridge::toCvShare(depthMsgs[i]); - cv::Mat subDepth = ptrDepth->image; - UASSERT(uContains(parameters_, Parameters::kMemSaveDepth16Format())); - if(subDepth.type() == CV_32FC1 && uStr2Bool(parameters_.at(Parameters::kMemSaveDepth16Format()))) - { - subDepth = util2d::cvtDepthFromFloat(subDepth); - static bool shown = false; - if(!shown) - { - ROS_WARN("Save depth data to 16 bits format: depth type detected is " - "32FC1, use 16UC1 depth format to avoid this conversion " - "(or set parameter \"Mem/SaveDepth16Format=false\" to use " - "32bits format). This message is only printed once..."); - shown = true; - } - } - - // initialize - if(rgb.empty()) - { - rgb = cv::Mat(imageHeight, imageWidth*cameraCount, ptrImage->image.type()); - } - if(depth.empty()) - { - depth = cv::Mat(imageHeight, imageWidth*cameraCount, subDepth.type()); - } - - if(ptrImage->image.type() == rgb.type()) - { - ptrImage->image.copyTo(cv::Mat(rgb, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); - } - else - { - ROS_ERROR("Some RGB images are not the same type!"); - return; - } - - if(subDepth.type() == depth.type()) - { - subDepth.copyTo(cv::Mat(depth, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); - } - else - { - ROS_ERROR("Some Depth images are not the same type!"); - return; - } - - cameraModels.push_back(rtabmap_ros::cameraModelFromROS(*cameraInfoMsgs[i], localTransform)); - - if(scan2dMsg.get() == 0 && genScan_) - { - scanCloud2d += util3d::laserScanFromDepthImage( - subDepth, - cameraModels.back().fx(), - cameraModels.back().fy(), - cameraModels.back().cx(), - cameraModels.back().cy(), - genScanMaxDepth_, - genScanMinDepth_, - localTransform); - genMaxScanPts += subDepth.cols; - } + pcl::PointCloud scanCloud2d; + bool genMaxScanPts = 0; + if(scan2dMsg.get() == 0 && scan3dMsg.get() == 0 && genScan_) + { + scanCloud2d += util3d::laserScanFromDepthImages( + depth, + cameraModels, + genScanMaxDepth_, + genScanMinDepth_); + genMaxScanPts += depth.cols; } cv::Mat scan; Transform scanLocalTransform = Transform::getIdentity(); if(scan2dMsg.get() != 0) { - // make sure the frame of the laser is updated too - scanLocalTransform = getTransform(frameId_, - scan2dMsg->header.frame_id, - scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)); - if(scanLocalTransform.isNull()) + if(!rtabmap_ros::convertScanMsg( + scan2dMsg, + frameId_, + odomFrameId, + lastPoseStamp_, + scan, + scanLocalTransform, + tfListener_, + waitForTransform_?waitForTransformDuration_:0)) { - ROS_ERROR("TF of received laser scan topic at time %fs is not set, aborting rtabmap update.", scan2dMsg->header.stamp.toSec()); + ROS_ERROR("Could not convert laser scan msg! Aborting rtabmap update..."); return; } - - //transform in frameId_ frame - sensor_msgs::PointCloud2 scanOut; - laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(scan2dMsg->header.frame_id, *scan2dMsg, scanOut, tfListener_); - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(scanOut, *pclScan); - - // sync with odometry stamp - if(lastPoseStamp_ != scan2dMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomFrameId, frameId_, scan2dMsg->header.stamp); - if(sensorT.isNull()) - { - ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " - "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan2dMsg->header.stamp.toSec(), lastPoseStamp_.toSec()); - } - else - { - scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; - } - - } - } - scan = util3d::laserScan2dFromPointCloud(*pclScan); if(flipScan_) { cv::Mat flipScan; @@ -1069,79 +937,30 @@ void CoreWrapper::commonDepthCallback( } else if(scan3dMsg.get() != 0) { - bool containNormals = false; - for(unsigned int i=0; ifields.size(); ++i) + if(!rtabmap_ros::convertScan3dMsg( + scan3dMsg, + frameId_, + odomFrameId, + lastPoseStamp_, + scanCloudNormalK_, + scan, + scanLocalTransform, + tfListener_, + waitForTransform_?waitForTransformDuration_:0)) { - if(scan3dMsg->fields[i].name.compare("normal_x") == 0) - { - containNormals = true; - break; - } - } - - // sync with odometry stamp - scanLocalTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); - if(scanLocalTransform.isNull()) - { - ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + ROS_ERROR("Could not convert 3d laser scan msg! Aborting rtabmap update..."); return; } - if(lastPoseStamp_ != scan3dMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomFrameId, frameId_, scan3dMsg->header.stamp); - if(sensorT.isNull()) - { - ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " - "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), lastPoseStamp_.toSec()); - } - else - { - scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; - } - - } - } - - if(containNormals) - { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - scan = util3d::laserScanFromPointCloud(*pclScan); - } - else - { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - - if(scanCloudNormalK_ > 0) - { - //compute normals - pcl::PointCloud::Ptr normals = util3d::computeNormals(pclScan, scanCloudNormalK_); - pcl::PointCloud::Ptr pclScanNormal(new pcl::PointCloud); - pcl::concatenateFields(*pclScan, *normals, *pclScanNormal); - scan = util3d::laserScanFromPointCloud(*pclScanNormal); - } - else - { - scan = util3d::laserScanFromPointCloud(*pclScan); - } - } } else if(scanCloud2d.size()) { scan = util3d::laserScan2dFromPointCloud(scanCloud2d); } - ros::Time stamp = scan2dMsg.get() != 0?scan2dMsg->header.stamp: - scan3dMsg.get() != 0?scan3dMsg->header.stamp: - depthMsgs[0]->header.stamp; - Transform groundTruthPose; if(!groundTruthFrameId_.empty()) { - groundTruthPose = getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, stamp); + groundTruthPose = getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, lastPoseStamp_); } SensorData data(scan, @@ -1153,10 +972,10 @@ void CoreWrapper::commonDepthCallback( depth, cameraModels, lastPoseIntermediate_?-1:imageMsgs[0]->header.seq, - rtabmap_ros::timestampFromROS(stamp)); + rtabmap_ros::timestampFromROS(lastPoseStamp_)); data.setGroundTruth(groundTruthPose); - process(stamp, + process(lastPoseStamp_, data, lastPose_, odomFrameId, @@ -1175,62 +994,41 @@ void CoreWrapper::commonStereoCallback( const sensor_msgs::LaserScanConstPtr& scan2dMsg, const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) { - if(!(leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || - !(rightImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - rightImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0 || - rightImageMsg->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || - rightImageMsg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0)) + + cv::Mat left; + cv::Mat right; + StereoCameraModel stereoModel; + if(!rtabmap_ros::convertStereoMsg( + leftImageMsg, + rightImageMsg, + leftCamInfoMsg, + rightCamInfoMsg, + frameId_, + odomFrameId, + lastPoseStamp_, + left, + right, + stereoModel, + tfListener_, + waitForTransform_?waitForTransformDuration_:0.0)) { - ROS_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8"); + ROS_ERROR("Could not convert stereo msgs! Aborting rtabmap update..."); return; } - cv_bridge::CvImagePtr ptrLeftImage, ptrRightImage; - if(leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) - { - ptrLeftImage = cv_bridge::toCvCopy(leftImageMsg, "mono8"); - } - else - { - ptrLeftImage = cv_bridge::toCvCopy(leftImageMsg, "bgr8"); - } - ptrRightImage = cv_bridge::toCvCopy(rightImageMsg, "mono8"); - - Transform localTransform = getTransform(frameId_, leftImageMsg->header.frame_id, leftImageMsg->header.stamp); - if(localTransform.isNull()) - { - return; - } - - rtabmap::StereoCameraModel stereoModel = rtabmap_ros::stereoCameraModelFromROS(*leftCamInfoMsg, *rightCamInfoMsg, localTransform); - - if(stereoModel.baseline() > 10.0) - { - static bool shown = false; - if(!shown) - { - ROS_WARN("Detected baseline (%f m) is quite large! Is your " - "right camera_info P(0,3) correctly set? Note that " - "baseline=-P(0,3)/P(0,0). This warning is printed only once.", - stereoModel.baseline()); - shown = true; - } - } - if(stereoToDepth_) { // cv::stereoBM() see "$ rosrun rtabmap_ros rtabmap --params | grep StereoBM" for parameters - cv::Mat disparity = util2d::disparityFromStereoImages(ptrLeftImage->image, ptrRightImage->image, parameters_); + cv::Mat disparity = rtabmap::util2d::disparityFromStereoImages( + left, + right, + parameters_); if(disparity.empty()) { ROS_ERROR("Could not compute disparity image (\"stereo_to_depth\" is true)!"); return; } - cv::Mat depth = util2d::depthFromDisparity( + cv::Mat depth = rtabmap::util2d::depthFromDisparity( disparity, stereoModel.left().fx(), stereoModel.baseline()); @@ -1259,65 +1057,23 @@ void CoreWrapper::commonStereoCallback( commonDepthCallback(odomFrameId, leftImageMsg, depthMsg, leftCamInfoMsg, scan2dMsg, scan3dMsg); } - //for sync transform - Transform odomT = getTransform(odomFrameId, frameId_, lastPoseStamp_); - if(odomT.isNull() && !odomFrameId_.empty()) - { - ROS_WARN("Could not get TF transform from %s to %s, sensors will not be synchronized with odometry pose.", - odomFrameId.c_str(), frameId_.c_str()); - } - - // sync with odometry stamp - if(lastPoseStamp_ != leftImageMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomFrameId, frameId_, leftImageMsg->header.stamp); - if(sensorT.isNull()) - { - return; - } - localTransform = odomT.inverse() * sensorT * localTransform; - } - } - cv::Mat scan; Transform scanLocalTransform = Transform::getIdentity(); if(scan2dMsg.get() != 0) { - // make sure the frame of the laser is updated too - scanLocalTransform = getTransform(frameId_, - scan2dMsg->header.frame_id, - scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)); - if(scanLocalTransform.isNull()) + if(!rtabmap_ros::convertScanMsg( + scan2dMsg, + frameId_, + odomFrameId, + lastPoseStamp_, + scan, + scanLocalTransform, + tfListener_, + waitForTransform_?waitForTransformDuration_:0)) { - ROS_ERROR("TF of received laser scan topic at time %fs is not set, aborting rtabmap update.", scan2dMsg->header.stamp.toSec()); + ROS_ERROR("Could not convert laser scan msg! Aborting rtabmap update..."); return; } - - //transform in frameId_ frame - sensor_msgs::PointCloud2 scanOut; - laser_geometry::LaserProjection projection; - //projection.transformLaserScanToPointCloud(frameId_, *scanMsg, scanOut, tfBuffer_); - projection.transformLaserScanToPointCloud(scan2dMsg->header.frame_id, *scan2dMsg, scanOut, tfListener_); - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(scanOut, *pclScan); - - // sync with odometry stamp - if(lastPoseStamp_ != scan2dMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomFrameId, frameId_, scan2dMsg->header.stamp); - if(sensorT.isNull()) - { - return; - } - scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; - } - } - - scan = util3d::laserScan2dFromPointCloud(*pclScan); if(flipScan_) { cv::Mat flipScan; @@ -1327,76 +1083,26 @@ void CoreWrapper::commonStereoCallback( } else if(scan3dMsg.get() != 0) { - bool containNormals = false; - for(unsigned int i=0; ifields.size(); ++i) + if(!rtabmap_ros::convertScan3dMsg( + scan3dMsg, + frameId_, + odomFrameId, + lastPoseStamp_, + scanCloudNormalK_, + scan, + scanLocalTransform, + tfListener_, + waitForTransform_?waitForTransformDuration_:0)) { - if(scan3dMsg->fields[i].name.compare("normal_x") == 0) - { - containNormals = true; - break; - } - } - - // sync with odometry stamp - scanLocalTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); - if(scanLocalTransform.isNull()) - { - ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + ROS_ERROR("Could not convert 3d laser scan msg! Aborting rtabmap update..."); return; } - if(lastPoseStamp_ != scan3dMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomFrameId, frameId_, scan3dMsg->header.stamp); - if(sensorT.isNull()) - { - ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " - "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), lastPoseStamp_.toSec()); - } - else - { - scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; - } - - } - } - - if(containNormals) - { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - - scan = util3d::laserScanFromPointCloud(*pclScan); - } - else - { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - - if(scanCloudNormalK_ > 0) - { - //compute normals - pcl::PointCloud::Ptr normals = util3d::computeNormals(pclScan, scanCloudNormalK_); - pcl::PointCloud::Ptr pclScanNormal(new pcl::PointCloud); - pcl::concatenateFields(*pclScan, *normals, *pclScanNormal); - scan = util3d::laserScanFromPointCloud(*pclScanNormal); - } - else - { - scan = util3d::laserScanFromPointCloud(*pclScan); - } - } } - ros::Time stamp = scan2dMsg.get() != 0?scan2dMsg->header.stamp: - scan3dMsg.get() != 0?scan3dMsg->header.stamp: - leftImageMsg->header.stamp; - Transform groundTruthPose; if(!groundTruthFrameId_.empty()) { - groundTruthPose = getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, stamp); + groundTruthPose = getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, lastPoseStamp_); } SensorData data(scan, @@ -1404,14 +1110,14 @@ void CoreWrapper::commonStereoCallback( scan2dMsg.get() != 0?(int)scan2dMsg->ranges.size():scan3dMsg.get() != 0?scanCloudMaxPoints_:0, scan2dMsg.get() != 0?scan2dMsg->range_max:0, scanLocalTransform), - ptrLeftImage->image, - ptrRightImage->image, + left, + right, stereoModel, lastPoseIntermediate_?-1:leftImageMsg->header.seq, - rtabmap_ros::timestampFromROS(stamp)); + rtabmap_ros::timestampFromROS(lastPoseStamp_)); data.setGroundTruth(groundTruthPose); - process(stamp, + process(lastPoseStamp_, data, lastPose_, odomFrameId, diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index 51f1919c..91407cd5 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -29,10 +29,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include #include -#include #include #include @@ -56,10 +54,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "rtabmap_ros/SetLabel.h" #include "rtabmap_ros/PreferencesDialogROS.h" -#include -#include -#include - float max3( const float& a, const float& b, const float& c) { float m=a>b?a:b; @@ -473,35 +467,6 @@ void GuiWrapper::handleEvent(UEvent * anEvent) } } -Transform GuiWrapper::getTransform(const std::string & fromFrameId, const std::string & toFrameId, const ros::Time & stamp) const -{ - // TF ready? - Transform transform; - try - { - if(waitForTransform_ && !stamp.isZero() && waitForTransformDuration_ > 0.0) - { - //if(!tfBuffer_.canTransform(fromFrameId, toFrameId, stamp, ros::Duration(1))) - std::string errorMsg; - if(!tfListener_.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransformDuration_), ros::Duration(0.01), &errorMsg)) - { - ROS_WARN("rtabmapviz: Could not get transform from %s to %s after %f seconds (for stamp=%f)! Error=\"%s\".", - fromFrameId.c_str(), toFrameId.c_str(), waitForTransformDuration_, stamp.toSec(), errorMsg.c_str()); - return transform; - } - } - - tf::StampedTransform tmp; - tfListener_.lookupTransform(fromFrameId, toFrameId, stamp, tmp); - transform = rtabmap_ros::transformFromTF(tmp); - } - catch(tf::TransformException & ex) - { - ROS_WARN("%s",ex.what()); - } - return transform; -} - void GuiWrapper::commonDepthCallback( const nav_msgs::OdometryConstPtr & odomMsg, const sensor_msgs::ImageConstPtr& imageMsg, @@ -563,7 +528,7 @@ void GuiWrapper::commonDepthCallback( odomHeader.frame_id = odomFrameId_; } - Transform odomT = getTransform(odomHeader.frame_id, frameId_, odomHeader.stamp); + Transform odomT = rtabmap_ros::getTransform(odomHeader.frame_id, frameId_, odomHeader.stamp, tfListener_, waitForTransform_?waitForTransformDuration_:0); cv::Mat covariance = cv::Mat::eye(6,6,CV_64FC1); if(odomMsg.get()) { @@ -600,183 +565,56 @@ void GuiWrapper::commonDepthCallback( if(imageMsgs[0].get() && depthMsgs[0].get()) { - int imageWidth = imageMsgs[0]->width; - int imageHeight = imageMsgs[0]->height; - int cameraCount = imageMsgs.size(); - pcl::PointCloud scanCloud; - for(unsigned int i=0; iencoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) ==0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) ==0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) ==0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || - !(depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_16UC1) == 0 || - depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_32FC1) == 0 || - depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0)) - { - ROS_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8 and image_depth=32FC1,16UC1,mono16"); - return; - } - UASSERT(imageMsgs[i]->width == imageWidth && imageMsgs[i]->height == imageHeight); - UASSERT(depthMsgs[i]->width == imageWidth && depthMsgs[i]->height == imageHeight); - - Transform localTransform = getTransform(frameId_, depthMsgs[i]->header.frame_id, depthMsgs[i]->header.stamp); - if(localTransform.isNull()) - { - return; - } - // sync with odometry stamp - if(odomHeader.stamp != depthMsgs[i]->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomHeader.frame_id, frameId_, depthMsgs[i]->header.stamp); - if(sensorT.isNull()) - { - return; - } - localTransform = odomT.inverse() * sensorT * localTransform; - } - } - - cv_bridge::CvImageConstPtr ptrImage; - if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1)==0) - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i]); - } - else if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i], "mono8"); - } - else - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i], "bgr8"); - } - cv_bridge::CvImageConstPtr ptrDepth = cv_bridge::toCvShare(depthMsgs[i]); - cv::Mat subDepth = ptrDepth->image; - - // initialize - if(rgb.empty()) - { - rgb = cv::Mat(imageHeight, imageWidth*cameraCount, ptrImage->image.type()); - } - if(depth.empty()) - { - depth = cv::Mat(imageHeight, imageWidth*cameraCount, subDepth.type()); - } - - if(ptrImage->image.type() == rgb.type()) - { - ptrImage->image.copyTo(cv::Mat(rgb, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); - } - else - { - ROS_ERROR("Some RGB images are not the same type!"); - return; - } - - if(subDepth.type() == depth.type()) - { - subDepth.copyTo(cv::Mat(depth, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); - } - else - { - ROS_ERROR("Some Depth images are not the same type!"); - return; - } - - cameraModels.push_back(rtabmap_ros::cameraModelFromROS(*cameraInfoMsgs[i], localTransform)); + ROS_ERROR("Could not convert rgb/depth msgs! Aborting rtabmapviz update..."); + return; } } if(scan2dMsg.get() != 0) { - // make sure the frame of the laser is updated too - scanLocalTransform = getTransform( + if(!rtabmap_ros::convertScanMsg( + scan2dMsg, frameId_, - scan2dMsg->header.frame_id, - scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)); - if(scanLocalTransform.isNull()) + odomHeader.frame_id, + odomHeader.stamp, + scan, + scanLocalTransform, + tfListener_, + waitForTransform_?waitForTransformDuration_:0)) { - ROS_ERROR("TF of received scan at time %fs is not set, aborting rtabmapviz update.", scan2dMsg->header.stamp.toSec()); + ROS_ERROR("Could not convert laser scan msg! Aborting rtabmapviz update..."); return; } - - //transform in frameId_ frame - sensor_msgs::PointCloud2 scanOut; - laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(scan2dMsg->header.frame_id, *scan2dMsg, scanOut, tfListener_); - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(scanOut, *pclScan); - - // sync with odometry stamp - if(odomHeader.stamp != scan2dMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomHeader.frame_id, frameId_, scan2dMsg->header.stamp); - if(sensorT.isNull()) - { - return; - } - scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; - } - } - scan = util3d::laserScanFromPointCloud(*pclScan); } else if(scan3dMsg.get() != 0) { - bool containNormals = false; - for(unsigned int i=0; ifields.size(); ++i) + if(!rtabmap_ros::convertScan3dMsg( + scan3dMsg, + frameId_, + odomHeader.frame_id, + odomHeader.stamp, + 0, + scan, + scanLocalTransform, + tfListener_, + waitForTransform_?waitForTransformDuration_:0)) { - if(scan3dMsg->fields[i].name.compare("normal_x") == 0) - { - containNormals = true; - break; - } - } - - // sync with odometry stamp - scanLocalTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); - if(scanLocalTransform.isNull()) - { - ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + ROS_ERROR("Could not convert 3d laser scan msg! Aborting rtabmapviz update..."); return; } - if(odomHeader.stamp != scan3dMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomHeader.frame_id, frameId_, scan3dMsg->header.stamp); - if(sensorT.isNull()) - { - ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " - "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), odomHeader.stamp.toSec()); - } - else - { - scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; - } - - } - } - - if(containNormals) - { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - - scan = util3d::laserScanFromPointCloud(*pclScan); - } - else - { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - - scan = util3d::laserScanFromPointCloud(*pclScan); - } } if(odomInfoMsg.get()) @@ -825,22 +663,6 @@ void GuiWrapper::commonStereoCallback( const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) { - UASSERT(leftImageMsg.get() && rightImageMsg.get()); - UASSERT(leftCamInfoMsg.get() && rightCamInfoMsg.get()); - - if(!(leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || - !(rightImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - rightImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0 || - rightImageMsg->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || - rightImageMsg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0)) - { - ROS_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8"); - return; - } - std_msgs::Header odomHeader; if(odomMsg.get()) { @@ -863,7 +685,7 @@ void GuiWrapper::commonStereoCallback( odomHeader.frame_id = odomFrameId_; } - Transform odomT = getTransform(odomHeader.frame_id, frameId_, odomHeader.stamp); + Transform odomT = rtabmap_ros::getTransform(odomHeader.frame_id, frameId_, odomHeader.stamp, tfListener_, waitForTransform_?waitForTransformDuration_:0); cv::Mat covariance = cv::Mat::eye(6,6,CV_64FC1); if(odomMsg.get()) { @@ -884,25 +706,6 @@ void GuiWrapper::commonStereoCallback( return; } - Transform localTransform = getTransform(frameId_, leftCamInfoMsg->header.frame_id, leftCamInfoMsg->header.stamp); - if(localTransform.isNull()) - { - return; - } - // sync with odometry stamp - if(odomHeader.stamp != leftCamInfoMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomHeader.frame_id, frameId_, leftCamInfoMsg->header.stamp); - if(sensorT.isNull()) - { - return; - } - localTransform = odomT.inverse() * sensorT * localTransform; - } - } - cv::Mat left; cv::Mat right; cv::Mat scan; @@ -918,122 +721,56 @@ void GuiWrapper::commonStereoCallback( { lastOdomInfoUpdateTime_ = UTimer::now(); - stereoModel = rtabmap_ros::stereoCameraModelFromROS(*leftCamInfoMsg, *rightCamInfoMsg, localTransform); - - if(stereoModel.baseline() > 10.0) + if(!rtabmap_ros::convertStereoMsg( + leftImageMsg, + rightImageMsg, + leftCamInfoMsg, + rightCamInfoMsg, + frameId_, + odomHeader.frame_id, + odomHeader.stamp, + left, + right, + stereoModel, + tfListener_, + waitForTransform_?waitForTransformDuration_:0.0)) { - static bool shown = false; - if(!shown) - { - ROS_WARN("Detected baseline (%f m) is quite large! Is your " - "right camera_info P(0,3) correctly set? Note that " - "baseline=-P(0,3)/P(0,0). This warning is printed only once.", - stereoModel.baseline()); - shown = true; - } + ROS_ERROR("Could not convert stereo msgs! Aborting rtabmapviz update..."); + return; } - // left - cv_bridge::CvImageConstPtr ptrImage; - if(leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) - { - left = cv_bridge::toCvCopy(leftImageMsg, "mono8")->image; - } - else - { - left = cv_bridge::toCvCopy(leftImageMsg, "bgr8")->image; - } - - // right - right = cv_bridge::toCvCopy(rightImageMsg, "mono8")->image; - if(scan2dMsg.get() != 0) { - // make sure the frame of the laser is updated too - scanLocalTransform = getTransform( + if(!rtabmap_ros::convertScanMsg( + scan2dMsg, frameId_, - scan2dMsg->header.frame_id, - scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment)); - if(scanLocalTransform.isNull()) + odomHeader.frame_id, + odomHeader.stamp, + scan, + scanLocalTransform, + tfListener_, + waitForTransform_?waitForTransformDuration_:0)) { - ROS_ERROR("TF of received scan at time %fs is not set, aborting rtabmapviz update.", scan2dMsg->header.stamp.toSec()); + ROS_ERROR("Could not convert laser scan msg! Aborting rtabmapviz update..."); return; } - - //transform in frameId_ frame - sensor_msgs::PointCloud2 scanOut; - laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(scan2dMsg->header.frame_id, *scan2dMsg, scanOut, tfListener_); - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(scanOut, *pclScan); - - // sync with odometry stamp - if(odomHeader.stamp != scan2dMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomHeader.frame_id, frameId_, scan2dMsg->header.stamp); - if(sensorT.isNull()) - { - return; - } - scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; - } - } - scan = util3d::laserScan2dFromPointCloud(*pclScan); } else if(scan3dMsg.get() != 0) { - bool containNormals = false; - for(unsigned int i=0; ifields.size(); ++i) + if(!rtabmap_ros::convertScan3dMsg( + scan3dMsg, + frameId_, + odomHeader.frame_id, + odomHeader.stamp, + 0, + scan, + scanLocalTransform, + tfListener_, + waitForTransform_?waitForTransformDuration_:0)) { - if(scan3dMsg->fields[i].name.compare("normal_x") == 0) - { - containNormals = true; - break; - } - } - - // sync with odometry stamp - scanLocalTransform = getTransform(frameId_, scan3dMsg->header.frame_id, scan3dMsg->header.stamp); - if(scanLocalTransform.isNull()) - { - ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + ROS_ERROR("Could not convert 3d laser scan msg! Aborting rtabmapviz update..."); return; } - if(odomHeader.stamp != scan3dMsg->header.stamp) - { - if(!odomT.isNull()) - { - Transform sensorT = getTransform(odomHeader.frame_id, frameId_, scan3dMsg->header.stamp); - if(sensorT.isNull()) - { - ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " - "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), odomHeader.stamp.toSec()); - } - else - { - scanLocalTransform = odomT.inverse() * sensorT * scanLocalTransform; - } - - } - } - - if(containNormals) - { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - - scan = util3d::laserScanFromPointCloud(*pclScan); - } - else - { - pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); - pcl::fromROSMsg(*scan3dMsg, *pclScan); - - scan = util3d::laserScanFromPointCloud(*pclScan); - } } if(odomInfoMsg.get()) diff --git a/src/MsgConversion.cpp b/src/MsgConversion.cpp index fba69cc9..592688c7 100644 --- a/src/MsgConversion.cpp +++ b/src/MsgConversion.cpp @@ -39,6 +39,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include +#include +#include namespace rtabmap_ros { @@ -864,4 +868,440 @@ void odomInfoToROS(const rtabmap::OdometryInfo & info, rtabmap_ros::OdomInfo & m msg.localScanMap = rtabmap::compressData(info.localScanMap); } +rtabmap::Transform getTransform( + const std::string & fromFrameId, + const std::string & toFrameId, + const ros::Time & stamp, + tf::TransformListener & listener, + double waitForTransform) +{ + // TF ready? + rtabmap::Transform transform; + try + { + if(waitForTransform > 0.0 && !stamp.isZero()) + { + //if(!tfBuffer_.canTransform(fromFrameId, toFrameId, stamp, ros::Duration(1))) + std::string errorMsg; + if(!listener.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransform), ros::Duration(0.01), &errorMsg)) + { + ROS_WARN("Could not get transform from %s to %s after %f seconds (for stamp=%f)! Error=\"%s\".", + fromFrameId.c_str(), toFrameId.c_str(), waitForTransform, stamp.toSec(), errorMsg.c_str()); + return transform; + } + } + + tf::StampedTransform tmp; + listener.lookupTransform(fromFrameId, toFrameId, stamp, tmp); + transform = rtabmap_ros::transformFromTF(tmp); + } + catch(tf::TransformException & ex) + { + ROS_WARN("%s",ex.what()); + } + return transform; +} + +// get moving transform accordingly to a fixed frame. For example get +// transform between moving /base_link between two stamps accordingly to /odom frame. +rtabmap::Transform getTransform( + const std::string & sourceTargetFrame, + const std::string & fixedFrame, + const ros::Time & stampSource, + const ros::Time & stampTarget, + tf::TransformListener & listener, + double waitForTransform) +{ + // TF ready? + rtabmap::Transform transform; + try + { + ros::Time stamp = stampSource>stampTarget?stampSource:stampTarget; + if(waitForTransform > 0.0 && !stamp.isZero()) + { + std::string errorMsg; + if(!listener.waitForTransform(sourceTargetFrame, fixedFrame, stamp, ros::Duration(waitForTransform), ros::Duration(0.01), &errorMsg)) + { + ROS_WARN("Could not get transform from %s to %s accordingly to %s after %f seconds (for stamps=%f -> %f)! Error=\"%s\".", + sourceTargetFrame.c_str(), sourceTargetFrame.c_str(), fixedFrame.c_str(), waitForTransform, stampSource.toSec(), stampTarget.toSec(), errorMsg.c_str()); + return transform; + } + } + + tf::StampedTransform tmp; + listener.lookupTransform(sourceTargetFrame, stampTarget, sourceTargetFrame, stampSource, fixedFrame, tmp); + transform = rtabmap_ros::transformFromTF(tmp); + } + catch(tf::TransformException & ex) + { + ROS_WARN("%s",ex.what()); + } + return transform; +} + +bool convertRGBDMsgs( + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, + const std::string & frameId, + const std::string & odomFrameId, + const ros::Time & odomStamp, + cv::Mat & rgb, + cv::Mat & depth, + std::vector & cameraModels, + tf::TransformListener & listener, + double waitForTransform) +{ + UASSERT(imageMsgs.size()>0 && + imageMsgs.size() == depthMsgs.size() && + imageMsgs.size() == cameraInfoMsgs.size()); + + int imageWidth = imageMsgs[0]->width; + int imageHeight = imageMsgs[0]->height; + int cameraCount = imageMsgs.size(); + for(unsigned int i=0; iencoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) == 0 || + imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) ==0 || + imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) ==0 || + imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || + imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || + !(depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_16UC1) == 0 || + depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_32FC1) == 0 || + depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0)) + { + ROS_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8 and image_depth=32FC1,16UC1,mono16"); + return false; + } + + UASSERT_MSG(imageMsgs[i]->width == imageWidth && imageMsgs[i]->height == imageHeight, + uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", + imageWidth, + imageMsgs[i]->width, + imageHeight, + imageMsgs[i]->height).c_str()); + UASSERT_MSG(depthMsgs[i]->width == imageWidth && depthMsgs[i]->height == imageHeight, + uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", + imageWidth, + depthMsgs[i]->width, + imageHeight, + depthMsgs[i]->height).c_str()); + + rtabmap::Transform localTransform = rtabmap_ros::getTransform(frameId, depthMsgs[i]->header.frame_id, depthMsgs[i]->header.stamp, listener, waitForTransform); + if(localTransform.isNull()) + { + ROS_ERROR("TF of received depth image %d at time %fs is not set!", i, depthMsgs[i]->header.stamp.toSec()); + return false; + } + // sync with odometry stamp + if(odomStamp != depthMsgs[i]->header.stamp) + { + rtabmap::Transform sensorT = getTransform( + frameId, + odomFrameId, + odomStamp, + depthMsgs[i]->header.stamp, + listener, + waitForTransform); + if(sensorT.isNull()) + { + ROS_WARN("Could not get odometry value for depth image stamp (%fs). Latest odometry " + "stamp is %fs. The depth image pose will not be synchronized with odometry.", depthMsgs[i]->header.stamp.toSec(), odomStamp.toSec()); + } + else + { + localTransform = sensorT * localTransform; + } + } + + cv_bridge::CvImageConstPtr ptrImage; + if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1)==0) + { + ptrImage = cv_bridge::toCvShare(imageMsgs[i]); + } + else if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || + imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) + { + ptrImage = cv_bridge::toCvShare(imageMsgs[i], "mono8"); + } + else + { + ptrImage = cv_bridge::toCvShare(imageMsgs[i], "bgr8"); + } + cv_bridge::CvImageConstPtr ptrDepth = cv_bridge::toCvShare(depthMsgs[i]); + cv::Mat subDepth = ptrDepth->image; + + // initialize + if(rgb.empty()) + { + rgb = cv::Mat(imageHeight, imageWidth*cameraCount, ptrImage->image.type()); + } + if(depth.empty()) + { + depth = cv::Mat(imageHeight, imageWidth*cameraCount, subDepth.type()); + } + + if(ptrImage->image.type() == rgb.type()) + { + ptrImage->image.copyTo(cv::Mat(rgb, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); + } + else + { + ROS_ERROR("Some RGB images are not the same type!"); + return false; + } + + if(subDepth.type() == depth.type()) + { + subDepth.copyTo(cv::Mat(depth, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); + } + else + { + ROS_ERROR("Some Depth images are not the same type!"); + return false; + } + + cameraModels.push_back(rtabmap_ros::cameraModelFromROS(*cameraInfoMsgs[i], localTransform)); + } + return true; +} + +bool convertStereoMsg( + const sensor_msgs::ImageConstPtr& leftImageMsg, + const sensor_msgs::ImageConstPtr& rightImageMsg, + const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, + const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, + const std::string & frameId, + const std::string & odomFrameId, + const ros::Time & odomStamp, + cv::Mat & left, + cv::Mat & right, + rtabmap::StereoCameraModel & stereoModel, + tf::TransformListener & listener, + double waitForTransform) +{ + UASSERT(leftImageMsg.get() && rightImageMsg.get()); + UASSERT(leftCamInfoMsg.get() && rightCamInfoMsg.get()); + + if(!(leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || + leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0 || + leftImageMsg->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || + leftImageMsg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || + !(rightImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || + rightImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0 || + rightImageMsg->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || + rightImageMsg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0)) + { + ROS_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8"); + return false; + } + + if(leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || + leftImageMsg->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) + { + left = cv_bridge::toCvCopy(leftImageMsg, "mono8")->image; + } + else + { + left = cv_bridge::toCvCopy(leftImageMsg, "bgr8")->image; + } + right = cv_bridge::toCvCopy(rightImageMsg, "mono8")->image; + + rtabmap::Transform localTransform = getTransform(frameId, leftImageMsg->header.frame_id, leftImageMsg->header.stamp, listener, waitForTransform); + if(localTransform.isNull()) + { + return false; + } + // sync with odometry stamp + if(odomStamp != leftImageMsg->header.stamp) + { + rtabmap::Transform sensorT = getTransform( + frameId, + odomFrameId, + odomStamp, + leftImageMsg->header.stamp, + listener, + waitForTransform); + if(sensorT.isNull()) + { + ROS_WARN("Could not get odometry value for stereo msg stamp (%fs). Latest odometry " + "stamp is %fs. The stereo image pose will not be synchronized with odometry.", leftImageMsg->header.stamp.toSec(), odomStamp.toSec()); + } + else + { + localTransform = sensorT * localTransform; + } + } + + stereoModel = rtabmap_ros::stereoCameraModelFromROS(*leftCamInfoMsg, *rightCamInfoMsg, localTransform); + + if(stereoModel.baseline() > 10.0) + { + static bool shown = false; + if(!shown) + { + ROS_WARN("Detected baseline (%f m) is quite large! Is your " + "right camera_info P(0,3) correctly set? Note that " + "baseline=-P(0,3)/P(0,0). You may need to calibrate your camera. " + "This warning is printed only once.", + stereoModel.baseline()); + shown = true; + } + } + return true; +} + +bool convertScanMsg( + const sensor_msgs::LaserScanConstPtr& scan2dMsg, + const std::string & frameId, + const std::string & odomFrameId, + const ros::Time & odomStamp, + cv::Mat & scan, + rtabmap::Transform & scanLocalTransform, + tf::TransformListener & listener, + double waitForTransform) +{ + // make sure the frame of the laser is updated too + rtabmap::Transform tmpT = getTransform( + odomFrameId, + scan2dMsg->header.frame_id, + scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment), + listener, + waitForTransform); + if(tmpT.isNull()) + { + return false; + } + + scanLocalTransform = getTransform( + frameId, + scan2dMsg->header.frame_id, + scan2dMsg->header.stamp, + listener, + waitForTransform); + if(scanLocalTransform.isNull()) + { + return false; + } + + //transform in frameId_ frame + sensor_msgs::PointCloud2 scanOut; + laser_geometry::LaserProjection projection; + projection.transformLaserScanToPointCloud(odomFrameId, *scan2dMsg, scanOut, listener); + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(scanOut, *pclScan); + + //transform back in laser frame + rtabmap::Transform laserToOdom = getTransform( + scan2dMsg->header.frame_id, + odomFrameId, + scan2dMsg->header.stamp, + listener, + waitForTransform); + if(laserToOdom.isNull()) + { + return false; + } + + // sync with odometry stamp + if(odomStamp != scan2dMsg->header.stamp) + { + rtabmap::Transform sensorT = getTransform( + frameId, + odomFrameId, + odomStamp, + scan2dMsg->header.stamp, + listener, + waitForTransform); + if(sensorT.isNull()) + { + ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " + "stamp is %fs. The laser scan pose will not be synchronized with odometry.", scan2dMsg->header.stamp.toSec(), odomStamp.toSec()); + } + else + { + scanLocalTransform = sensorT * scanLocalTransform; + } + } + scan = rtabmap::util3d::laserScan2dFromPointCloud(*pclScan, laserToOdom); // put back in laser frame + return true; +} + +bool convertScan3dMsg( + const sensor_msgs::PointCloud2ConstPtr & scan3dMsg, + const std::string & frameId, + const std::string & odomFrameId, + const ros::Time & odomStamp, + int scanCloudNormalK, + cv::Mat & scan, + rtabmap::Transform & scanLocalTransform, + tf::TransformListener & listener, + double waitForTransform) +{ + bool containNormals = false; + for(unsigned int i=0; ifields.size(); ++i) + { + if(scan3dMsg->fields[i].name.compare("normal_x") == 0) + { + containNormals = true; + break; + } + } + + scanLocalTransform = getTransform(frameId, scan3dMsg->header.frame_id, scan3dMsg->header.stamp, listener, waitForTransform); + if(scanLocalTransform.isNull()) + { + ROS_ERROR("TF of received scan cloud at time %fs is not set, aborting rtabmap update.", scan3dMsg->header.stamp.toSec()); + return false; + } + + // sync with odometry stamp + if(odomStamp != scan3dMsg->header.stamp) + { + rtabmap::Transform sensorT = getTransform( + frameId, + odomFrameId, + odomStamp, + scan3dMsg->header.stamp, + listener, + waitForTransform); + if(sensorT.isNull()) + { + ROS_WARN("Could not get odometry value for laser scan stamp (%fs). Latest odometry " + "stamp is %fs. The 3d laser scan pose will not be synchronized with odometry.", scan3dMsg->header.stamp.toSec(), odomStamp.toSec()); + } + else + { + scanLocalTransform = sensorT * scanLocalTransform; + } + } + + if(containNormals) + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*scan3dMsg, *pclScan); + + scan = rtabmap::util3d::laserScanFromPointCloud(*pclScan); + } + else + { + pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); + pcl::fromROSMsg(*scan3dMsg, *pclScan); + + if(scanCloudNormalK > 0) + { + //compute normals + pcl::PointCloud::Ptr normals = rtabmap::util3d::computeNormals(pclScan, scanCloudNormalK); + pcl::PointCloud::Ptr pclScanNormal(new pcl::PointCloud); + pcl::concatenateFields(*pclScan, *normals, *pclScanNormal); + scan = rtabmap::util3d::laserScanFromPointCloud(*pclScanNormal); + } + else + { + scan = rtabmap::util3d::laserScanFromPointCloud(*pclScan); + } + } + return true; +} + } From 66c2af5959ff85608c38bcc40d9d279aa711159f Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 25 Aug 2016 14:33:06 -0400 Subject: [PATCH 26/78] Added "odom_sensor_sync" ros parameter (rtabmap and rtabmapviz nodes) --- include/rtabmap_ros/CoreWrapper.h | 2 +- include/rtabmap_ros/GuiWrapper.h | 1 + launch/demo/demo_robot_mapping.launch | 1 - launch/demo/demo_stereo_outdoor.launch | 2 + launch/tests/rgbdslam_datasets.launch | 2 + src/CoreWrapper.cpp | 74 +++++++++----------------- src/GuiWrapper.cpp | 14 ++--- src/MsgConversion.cpp | 16 +++--- src/nodelets/stereo_odometry.cpp | 1 - src/rviz/MapCloudDisplay.cpp | 12 ++--- 10 files changed, 54 insertions(+), 71 deletions(-) diff --git a/include/rtabmap_ros/CoreWrapper.h b/include/rtabmap_ros/CoreWrapper.h index c6d6e7d5..dfc57706 100644 --- a/include/rtabmap_ros/CoreWrapper.h +++ b/include/rtabmap_ros/CoreWrapper.h @@ -100,7 +100,6 @@ private: bool commonOdomUpdate(const nav_msgs::OdometryConstPtr & odomMsg); bool commonOdomTFUpdate(const ros::Time & stamp); // TF odom - rtabmap::Transform getTransform(const std::string & fromFrameId, const std::string & toFrameId, const ros::Time & stamp) const; void commonDepthCallback( const std::string & odomFrameId, @@ -511,6 +510,7 @@ private: boost::thread* transformThread_; bool stereoToDepth_; + bool odomSensorSync_; float rate_; bool createIntermediateNodes_; ros::Time time_; diff --git a/include/rtabmap_ros/GuiWrapper.h b/include/rtabmap_ros/GuiWrapper.h index 24f15cfa..eb54dd6c 100644 --- a/include/rtabmap_ros/GuiWrapper.h +++ b/include/rtabmap_ros/GuiWrapper.h @@ -269,6 +269,7 @@ private: std::string odomFrameId_; bool waitForTransform_; double waitForTransformDuration_; + bool odomSensorSync_; tf::TransformListener tfListener_; message_filters::Subscriber infoTopic_; diff --git a/launch/demo/demo_robot_mapping.launch b/launch/demo/demo_robot_mapping.launch index 0cbd6e56..2955d5ab 100644 --- a/launch/demo/demo_robot_mapping.launch +++ b/launch/demo/demo_robot_mapping.launch @@ -40,7 +40,6 @@ - diff --git a/launch/demo/demo_stereo_outdoor.launch b/launch/demo/demo_stereo_outdoor.launch index fcc376da..0a8d2115 100644 --- a/launch/demo/demo_stereo_outdoor.launch +++ b/launch/demo/demo_stereo_outdoor.launch @@ -77,6 +77,8 @@ + + diff --git a/launch/tests/rgbdslam_datasets.launch b/launch/tests/rgbdslam_datasets.launch index 66a01b20..7af0a7f4 100644 --- a/launch/tests/rgbdslam_datasets.launch +++ b/launch/tests/rgbdslam_datasets.launch @@ -62,6 +62,8 @@ + + diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 8dc42ce9..e4a32df8 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include @@ -120,6 +121,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) stereoExactTFSync_(0), transformThread_(0), stereoToDepth_(false), + odomSensorSync_(false), rate_(Parameters::defaultRtabmapDetectionRate()), createIntermediateNodes_(Parameters::defaultRtabmapCreateIntermediateNodes()), time_(ros::Time::now()), @@ -168,6 +170,10 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) subscribeDepth = true; } } + if(subscribeStereo) + { + approxSync = false; // default for stereo: exact sync + } pnh.param("config_path", configPath_, configPath_); pnh.param("database_path", databasePath_, databasePath_); @@ -205,6 +211,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) pnh.param("scan_cloud_normal_k", scanCloudNormalK_, scanCloudNormalK_); pnh.param("flip_scan", flipScan_, flipScan_); pnh.param("stereo_to_depth", stereoToDepth_, stereoToDepth_); + pnh.param("odom_sensor_sync", odomSensorSync_, odomSensorSync_); if(!tfPrefix.empty()) { @@ -253,6 +260,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) ROS_INFO("rtabmap: tf_tolerance = %f", tfTolerance); ROS_INFO("rtabmap: depth_cameras = %d", depthCameras); ROS_INFO("rtabmap: approx_sync = %s", approxSync?"true":"false"); + ROS_INFO("rtabmap: odom_sensor_sync = %s", odomSensorSync_?"true":"false"); infoPub_ = nh.advertise("info", 1); mapDataPub_ = nh.advertise("mapData", 1); @@ -762,7 +770,7 @@ bool CoreWrapper::commonOdomTFUpdate(const ros::Time & stamp) if(!paused_) { // Odom TF ready? - Transform odom = getTransform(odomFrameId_, frameId_, stamp); + Transform odom = rtabmap_ros::getTransform(odomFrameId_, frameId_, stamp, tfListener_, waitForTransform_?waitForTransformDuration_:0.0); if(odom.isNull()) { return false; @@ -811,35 +819,6 @@ bool CoreWrapper::commonOdomTFUpdate(const ros::Time & stamp) return false; } -Transform CoreWrapper::getTransform(const std::string & fromFrameId, const std::string & toFrameId, const ros::Time & stamp) const -{ - // TF ready? - Transform transform; - try - { - if(waitForTransform_ && !stamp.isZero() && waitForTransformDuration_>0.0) - { - //if(!tfBuffer_.canTransform(fromFrameId, toFrameId, stamp, ros::Duration(1))) - std::string errorMsg; - if(!tfListener_.waitForTransform(fromFrameId, toFrameId, stamp, ros::Duration(waitForTransformDuration_), ros::Duration(0.01), &errorMsg)) - { - ROS_WARN("rtabmap: Could not get transform from %s to %s after %f seconds (for stamp=%f)! Error=\"%s\"", - fromFrameId.c_str(), toFrameId.c_str(), waitForTransformDuration_, stamp.toSec(), errorMsg.c_str()); - return transform; - } - } - - tf::StampedTransform tmp; - tfListener_.lookupTransform(fromFrameId, toFrameId, stamp, tmp); - transform = rtabmap_ros::transformFromTF(tmp); - } - catch(tf::TransformException & ex) - { - ROS_WARN("%s",ex.what()); - } - return transform; -} - void CoreWrapper::commonDepthCallback( const std::string & odomFrameId, const sensor_msgs::ImageConstPtr& imageMsg, @@ -872,7 +851,7 @@ void CoreWrapper::commonDepthCallback( depthMsgs, cameraInfoMsgs, frameId_, - odomFrameId, + odomSensorSync_?odomFrameId:"", lastPoseStamp_, rgb, depth, @@ -899,26 +878,26 @@ void CoreWrapper::commonDepthCallback( } } + cv::Mat scan; + Transform scanLocalTransform = Transform::getIdentity(); pcl::PointCloud scanCloud2d; bool genMaxScanPts = 0; if(scan2dMsg.get() == 0 && scan3dMsg.get() == 0 && genScan_) { - scanCloud2d += util3d::laserScanFromDepthImages( + scanCloud2d = util3d::laserScanFromDepthImages( depth, cameraModels, genScanMaxDepth_, genScanMinDepth_); genMaxScanPts += depth.cols; + scan = util3d::laserScan2dFromPointCloud(scanCloud2d); } - - cv::Mat scan; - Transform scanLocalTransform = Transform::getIdentity(); - if(scan2dMsg.get() != 0) + else if(scan2dMsg.get() != 0) { if(!rtabmap_ros::convertScanMsg( scan2dMsg, frameId_, - odomFrameId, + odomSensorSync_?odomFrameId:"", lastPoseStamp_, scan, scanLocalTransform, @@ -940,7 +919,7 @@ void CoreWrapper::commonDepthCallback( if(!rtabmap_ros::convertScan3dMsg( scan3dMsg, frameId_, - odomFrameId, + odomSensorSync_?odomFrameId:"", lastPoseStamp_, scanCloudNormalK_, scan, @@ -952,15 +931,11 @@ void CoreWrapper::commonDepthCallback( return; } } - else if(scanCloud2d.size()) - { - scan = util3d::laserScan2dFromPointCloud(scanCloud2d); - } Transform groundTruthPose; if(!groundTruthFrameId_.empty()) { - groundTruthPose = getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, lastPoseStamp_); + groundTruthPose = rtabmap_ros::getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, lastPoseStamp_, tfListener_, waitForTransform_?waitForTransformDuration_:0.0); } SensorData data(scan, @@ -1004,7 +979,7 @@ void CoreWrapper::commonStereoCallback( leftCamInfoMsg, rightCamInfoMsg, frameId_, - odomFrameId, + odomSensorSync_?odomFrameId:"", lastPoseStamp_, left, right, @@ -1064,7 +1039,7 @@ void CoreWrapper::commonStereoCallback( if(!rtabmap_ros::convertScanMsg( scan2dMsg, frameId_, - odomFrameId, + odomSensorSync_?odomFrameId:"", lastPoseStamp_, scan, scanLocalTransform, @@ -1086,7 +1061,7 @@ void CoreWrapper::commonStereoCallback( if(!rtabmap_ros::convertScan3dMsg( scan3dMsg, frameId_, - odomFrameId, + odomSensorSync_?odomFrameId:"", lastPoseStamp_, scanCloudNormalK_, scan, @@ -1102,7 +1077,7 @@ void CoreWrapper::commonStereoCallback( Transform groundTruthPose; if(!groundTruthFrameId_.empty()) { - groundTruthPose = getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, lastPoseStamp_); + groundTruthPose = rtabmap_ros::getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, lastPoseStamp_, tfListener_, waitForTransform_?waitForTransformDuration_:0.0); } SensorData data(scan, @@ -1459,7 +1434,8 @@ void CoreWrapper::process( { timeRtabmap = timer.ticks(); } - ROS_INFO("rtabmap: Rate=%.2fs, Limit=%.3fs, RTAB-Map=%.4fs, Maps update=%.4fs pub=%.4fs (local map=%d, WM=%d)", + ROS_INFO("rtabmap (%d): Rate=%.2fs, Limit=%.3fs, RTAB-Map=%.4fs, Maps update=%.4fs pub=%.4fs (local map=%d, WM=%d)", + rtabmap_.getLastLocationId(), rate_>0?1.0f/rate_:0, rtabmap_.getTimeThreshold()/1000.0f, timeRtabmap, @@ -1609,7 +1585,7 @@ void CoreWrapper::goalCallback(const geometry_msgs::PoseStampedConstPtr & msg) // transform goal in /map frame if(mapFrameId_.compare(msg->header.frame_id) != 0) { - Transform t = this->getTransform(mapFrameId_, msg->header.frame_id, msg->header.stamp); + Transform t = rtabmap_ros::getTransform(mapFrameId_, msg->header.frame_id, msg->header.stamp, tfListener_, waitForTransform_?waitForTransformDuration_:0.0); if(t.isNull()) { ROS_ERROR("Cannot transform goal pose from \"%s\" frame to \"%s\" frame!", diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index 91407cd5..799b6fa9 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -65,6 +65,7 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : frameId_("base_link"), waitForTransform_(true), waitForTransformDuration_(0.2), // 200 ms + odomSensorSync_(false), cameraNodeName_(""), lastOdomInfoUpdateTime_(0), depthScanSync_(0), @@ -131,6 +132,7 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : pnh.param("tf_prefix", tfPrefix, tfPrefix); pnh.param("wait_for_transform", waitForTransform_, waitForTransform_); pnh.param("wait_for_transform_duration", waitForTransformDuration_, waitForTransformDuration_); + pnh.param("odom_sensor_sync", odomSensorSync_, odomSensorSync_); pnh.param("camera_node_name", cameraNodeName_, cameraNodeName_); // used to pause the rtabmap_ros/camera when pausing the process pnh.param("init_cache_path", initCachePath, initCachePath); if(initCachePath.size()) @@ -570,7 +572,7 @@ void GuiWrapper::commonDepthCallback( depthMsgs, cameraInfoMsgs, frameId_, - odomHeader.frame_id, + odomSensorSync_?odomHeader.frame_id:"", odomHeader.stamp, rgb, depth, @@ -588,7 +590,7 @@ void GuiWrapper::commonDepthCallback( if(!rtabmap_ros::convertScanMsg( scan2dMsg, frameId_, - odomHeader.frame_id, + odomSensorSync_?odomHeader.frame_id:"", odomHeader.stamp, scan, scanLocalTransform, @@ -604,7 +606,7 @@ void GuiWrapper::commonDepthCallback( if(!rtabmap_ros::convertScan3dMsg( scan3dMsg, frameId_, - odomHeader.frame_id, + odomSensorSync_?odomHeader.frame_id:"", odomHeader.stamp, 0, scan, @@ -727,7 +729,7 @@ void GuiWrapper::commonStereoCallback( leftCamInfoMsg, rightCamInfoMsg, frameId_, - odomHeader.frame_id, + odomSensorSync_?odomHeader.frame_id:"", odomHeader.stamp, left, right, @@ -744,7 +746,7 @@ void GuiWrapper::commonStereoCallback( if(!rtabmap_ros::convertScanMsg( scan2dMsg, frameId_, - odomHeader.frame_id, + odomSensorSync_?odomHeader.frame_id:"", odomHeader.stamp, scan, scanLocalTransform, @@ -760,7 +762,7 @@ void GuiWrapper::commonStereoCallback( if(!rtabmap_ros::convertScan3dMsg( scan3dMsg, frameId_, - odomHeader.frame_id, + odomSensorSync_?odomHeader.frame_id:"", odomHeader.stamp, 0, scan, diff --git a/src/MsgConversion.cpp b/src/MsgConversion.cpp index 592688c7..befcfdf0 100644 --- a/src/MsgConversion.cpp +++ b/src/MsgConversion.cpp @@ -994,7 +994,7 @@ bool convertRGBDMsgs( return false; } // sync with odometry stamp - if(odomStamp != depthMsgs[i]->header.stamp) + if(!odomFrameId.empty() && odomStamp != depthMsgs[i]->header.stamp) { rtabmap::Transform sensorT = getTransform( frameId, @@ -1010,6 +1010,7 @@ bool convertRGBDMsgs( } else { + //ROS_WARN("RGBD correction = %s (time diff=%fs)", sensorT.prettyPrint().c_str(), fabs(depthMsgs[i]->header.stamp.toSec()-odomStamp.toSec())); localTransform = sensorT * localTransform; } } @@ -1113,7 +1114,7 @@ bool convertStereoMsg( return false; } // sync with odometry stamp - if(odomStamp != leftImageMsg->header.stamp) + if(!odomFrameId.empty() && odomStamp != leftImageMsg->header.stamp) { rtabmap::Transform sensorT = getTransform( frameId, @@ -1163,7 +1164,7 @@ bool convertScanMsg( { // make sure the frame of the laser is updated too rtabmap::Transform tmpT = getTransform( - odomFrameId, + odomFrameId.empty()?frameId:odomFrameId, scan2dMsg->header.frame_id, scan2dMsg->header.stamp + ros::Duration().fromSec(scan2dMsg->ranges.size()*scan2dMsg->time_increment), listener, @@ -1187,14 +1188,14 @@ bool convertScanMsg( //transform in frameId_ frame sensor_msgs::PointCloud2 scanOut; laser_geometry::LaserProjection projection; - projection.transformLaserScanToPointCloud(odomFrameId, *scan2dMsg, scanOut, listener); + projection.transformLaserScanToPointCloud(odomFrameId.empty()?frameId:odomFrameId, *scan2dMsg, scanOut, listener); pcl::PointCloud::Ptr pclScan(new pcl::PointCloud); pcl::fromROSMsg(scanOut, *pclScan); //transform back in laser frame rtabmap::Transform laserToOdom = getTransform( scan2dMsg->header.frame_id, - odomFrameId, + odomFrameId.empty()?frameId:odomFrameId, scan2dMsg->header.stamp, listener, waitForTransform); @@ -1204,7 +1205,7 @@ bool convertScanMsg( } // sync with odometry stamp - if(odomStamp != scan2dMsg->header.stamp) + if(!odomFrameId.empty() && odomStamp != scan2dMsg->header.stamp) { rtabmap::Transform sensorT = getTransform( frameId, @@ -1220,6 +1221,7 @@ bool convertScanMsg( } else { + //ROS_WARN("scan correction = %s (time diff=%fs)", sensorT.prettyPrint().c_str(), fabs(scan2dMsg->header.stamp.toSec()-odomStamp.toSec())); scanLocalTransform = sensorT * scanLocalTransform; } } @@ -1256,7 +1258,7 @@ bool convertScan3dMsg( } // sync with odometry stamp - if(odomStamp != scan3dMsg->header.stamp) + if(!odomFrameId.empty() && odomStamp != scan3dMsg->header.stamp) { rtabmap::Transform sensorT = getTransform( frameId, diff --git a/src/nodelets/stereo_odometry.cpp b/src/nodelets/stereo_odometry.cpp index 23620d2e..3ff9b57e 100644 --- a/src/nodelets/stereo_odometry.cpp +++ b/src/nodelets/stereo_odometry.cpp @@ -86,7 +86,6 @@ private: bool approxSync = false; pnh.param("approx_sync", approxSync, approxSync); pnh.param("queue_size", queueSize_, queueSize_); - NODELET_INFO("Approximate time sync = %s", approxSync?"true":"false"); ros::NodeHandle left_nh(nh, "left"); ros::NodeHandle right_nh(nh, "right"); diff --git a/src/rviz/MapCloudDisplay.cpp b/src/rviz/MapCloudDisplay.cpp index b178f6bb..8cf7cd4b 100644 --- a/src/rviz/MapCloudDisplay.cpp +++ b/src/rviz/MapCloudDisplay.cpp @@ -485,7 +485,7 @@ void MapCloudDisplay::downloadMap() ros::NodeHandle nh; QMessageBox * messageBox = new QMessageBox( QMessageBox::NoIcon, - tr("Calling \"%1\" service...").arg(nh.resolveName("rtabmap/get_map").c_str()), + tr("Calling \"%1\" service...").arg(nh.resolveName("rtabmap/get_map_data").c_str()), tr("Downloading the map... please wait (rviz could become gray!)"), QMessageBox::NoButton); messageBox->setAttribute(Qt::WA_DeleteOnClose, true); @@ -493,18 +493,18 @@ void MapCloudDisplay::downloadMap() QApplication::processEvents(); uSleep(100); // hack make sure the text in the QMessageBox is shown... QApplication::processEvents(); - if(!ros::service::call("rtabmap/get_map", getMapSrv)) + if(!ros::service::call("rtabmap/get_map_data", getMapSrv)) { ROS_ERROR("MapCloudDisplay: Can't call \"%s\" service. " "Tip: if rtabmap node is not in rtabmap namespace, you can remap the service " "to \"get_map\" in the launch " - "file like: .", - nh.resolveName("rtabmap/get_map").c_str()); + "file like: .", + nh.resolveName("rtabmap/get_map_data").c_str()); messageBox->setText(tr("MapCloudDisplay: Can't call \"%1\" service. " "Tip: if rtabmap node is not in rtabmap namespace, you can remap the service " "to \"get_map\" in the launch " - "file like: ."). - arg(nh.resolveName("rtabmap/get_map").c_str())); + "file like: ."). + arg(nh.resolveName("rtabmap/get_map_data").c_str())); } else { From 15aaf93d6f30198958709452bdd4c05673980099 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 29 Aug 2016 10:46:35 -0400 Subject: [PATCH 27/78] updated sensor_fusion_kinect_brick.launch with "uid" argument --- launch/tests/sensor_fusion_kinect_brick.launch | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launch/tests/sensor_fusion_kinect_brick.launch b/launch/tests/sensor_fusion_kinect_brick.launch index 282b66a1..ba585de5 100644 --- a/launch/tests/sensor_fusion_kinect_brick.launch +++ b/launch/tests/sensor_fusion_kinect_brick.launch @@ -1,6 +1,7 @@ + @@ -12,7 +13,7 @@ - + From 9b603e57d2c25f07d191a99f14c13ef35e6ea9a5 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 29 Aug 2016 11:03:20 -0400 Subject: [PATCH 28/78] Updated test_map_optimizer.launch with map_assembler node --- launch/tests/test_map_optimizer.launch | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/launch/tests/test_map_optimizer.launch b/launch/tests/test_map_optimizer.launch index 0dc85727..466a6c7d 100644 --- a/launch/tests/test_map_optimizer.launch +++ b/launch/tests/test_map_optimizer.launch @@ -13,7 +13,12 @@ + - + + + + + \ No newline at end of file From 62ef687abd368e59b7bcc3e2e6e1b44f93010faf Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 29 Aug 2016 15:06:00 -0400 Subject: [PATCH 29/78] Updated error msg when graph cannot be computed from id --- src/CoreWrapper.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index e4a32df8..9b58920d 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -1554,7 +1554,14 @@ void CoreWrapper::goalCommonCallback( } else if(pose.isNull()) { - ROS_ERROR("Planning: Node id should be > 0 !"); + if(id > 0) + { + ROS_ERROR("Planning: Could not plan to node %d! The node is not in map's graph (look for warnings before this message for more details).", id); + } + else + { + ROS_ERROR("Planning: Node id should be > 0 !"); + } } else { From 425a78298da1987d82637061fd0aed8df724430f Mon Sep 17 00:00:00 2001 From: Mathieu Labbe Date: Mon, 29 Aug 2016 15:56:42 -0400 Subject: [PATCH 30/78] Fixed negative grids not overwritten --- src/MapsManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index 2dd0bbf5..d38333d5 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -470,8 +470,8 @@ std::map MapsManager::updateMapCaches( { // normally data should be already uncompressed for negative ids occupancyGrid_->createLocalMap(findIter->second, ground, obstacles, viewPoint); - gridMaps_.insert(std::make_pair(iter->first, std::make_pair(ground, obstacles))); - gridMapsViewpoints_.insert(std::make_pair(iter->first, viewPoint)); + uInsert(gridMaps_, std::make_pair(iter->first, std::make_pair(ground, obstacles))); + uInsert(gridMapsViewpoints_, std::make_pair(iter->first, viewPoint)); } else { From 42661b6673b296fdc1d9d3e5255a5377cfdc4122 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 29 Aug 2016 16:54:31 -0400 Subject: [PATCH 31/78] MapsManager: always fill unknown cells for negative 2d scans --- src/MapsManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index d38333d5..e8b48312 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -276,6 +276,10 @@ void MapsManager::setParameters(const rtabmap::ParametersMap & parameters) // don't use grid cell size from parameters as we use grid_cell_size ros param uInsert(parameters_, ParametersPair(Parameters::kGridCellSize(), uNumber2Str(gridCellSize_))); + + // For negative laser scans, always fill empty space + uInsert(parameters_, ParametersPair(Parameters::kGridScan2dUnknownSpaceFilled(), "true")); + occupancyGrid_->parseParameters(parameters_); } From da2b87bb286588d0b0fe2d985f3328e2c6433b85 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 29 Aug 2016 17:15:01 -0400 Subject: [PATCH 32/78] removed "flip_scan" parameter (now detect directly with tf) --- src/CoreWrapper.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 9b58920d..7f8ff81f 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -95,7 +95,6 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) genScanMinDepth_(0.0), scanCloudMaxPoints_(0), scanCloudNormalK_(0), - flipScan_(false), mapToOdom_(rtabmap::Transform::getIdentity()), mapsManager_(true), depthSync_(0), @@ -209,9 +208,14 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) pnh.param("gen_scan_min_depth", genScanMinDepth_, genScanMinDepth_); pnh.param("scan_cloud_max_points", scanCloudMaxPoints_, scanCloudMaxPoints_); pnh.param("scan_cloud_normal_k", scanCloudNormalK_, scanCloudNormalK_); - pnh.param("flip_scan", flipScan_, flipScan_); pnh.param("stereo_to_depth", stereoToDepth_, stereoToDepth_); pnh.param("odom_sensor_sync", odomSensorSync_, odomSensorSync_); + if(pnh.hasParam("flip_scan")) + { + ROS_WARN("Parameter \"flip_scan\" doesn't exist anymore. Rtabmap now " + "detects automatically if the laser is upside down with /tf, then if so, it " + "switches scan values."); + } if(!tfPrefix.empty()) { @@ -907,7 +911,8 @@ void CoreWrapper::commonDepthCallback( ROS_ERROR("Could not convert laser scan msg! Aborting rtabmap update..."); return; } - if(flipScan_) + Transform zAxis(0,0,1,0,0,0); + if((scanLocalTransform.rotation()*zAxis).z() < 0) { cv::Mat flipScan; cv::flip(scan, flipScan, 1); @@ -1049,7 +1054,8 @@ void CoreWrapper::commonStereoCallback( ROS_ERROR("Could not convert laser scan msg! Aborting rtabmap update..."); return; } - if(flipScan_) + Transform zAxis(0,0,1,0,0,0); + if((scanLocalTransform.rotation()*zAxis).z() < 0) { cv::Mat flipScan; cv::flip(scan, flipScan, 1); From 750fd61daa3487e3bda4f7d439b254560e4d46e8 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 30 Aug 2016 18:36:06 -0400 Subject: [PATCH 33/78] Refactored obstacles_detection nodelet to use OccupancyGrid segmentation approach (so using exactly the same parameter names as rtabmap node for the map) --- CMakeLists.txt | 2 +- include/rtabmap_ros/CoreWrapper.h | 1 - launch/tests/test_obstacles_detection.launch | 3 - nodelet_plugins.xml | 4 +- src/nodelets/obstacles_detection.cpp | 380 ++++++++++--------- src/nodelets/obstacles_detection_indoor.cpp | 267 ------------- src/nodelets/obstacles_detection_old.cpp | 357 +++++++++++++++++ 7 files changed, 562 insertions(+), 452 deletions(-) delete mode 100644 src/nodelets/obstacles_detection_indoor.cpp create mode 100644 src/nodelets/obstacles_detection_old.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 76b532dc..28f47a8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,7 +160,7 @@ SET(rtabmap_ros_lib_src src/nodelets/point_cloud_xyz.cpp src/nodelets/disparity_to_depth.cpp src/nodelets/obstacles_detection.cpp - src/nodelets/obstacles_detection_indoor.cpp + src/nodelets/obstacles_detection_old.cpp src/nodelets/point_cloud_aggregator.cpp src/OdometryROS.cpp src/MsgConversion.cpp diff --git a/include/rtabmap_ros/CoreWrapper.h b/include/rtabmap_ros/CoreWrapper.h index dfc57706..f8a9935c 100644 --- a/include/rtabmap_ros/CoreWrapper.h +++ b/include/rtabmap_ros/CoreWrapper.h @@ -283,7 +283,6 @@ private: double genScanMinDepth_; int scanCloudMaxPoints_; int scanCloudNormalK_; - bool flipScan_; rtabmap::Transform mapToOdom_; boost::mutex mapToOdomMutex_; diff --git a/launch/tests/test_obstacles_detection.launch b/launch/tests/test_obstacles_detection.launch index acaf83bc..fba3ca09 100644 --- a/launch/tests/test_obstacles_detection.launch +++ b/launch/tests/test_obstacles_detection.launch @@ -1,8 +1,6 @@ - - @@ -23,7 +21,6 @@ - diff --git a/nodelet_plugins.xml b/nodelet_plugins.xml index 7277518b..8c1781cd 100644 --- a/nodelet_plugins.xml +++ b/nodelet_plugins.xml @@ -88,8 +88,8 @@ - This is my nodelet. diff --git a/src/nodelets/obstacles_detection.cpp b/src/nodelets/obstacles_detection.cpp index 1eab4d2b..0c808054 100644 --- a/src/nodelets/obstacles_detection.cpp +++ b/src/nodelets/obstacles_detection.cpp @@ -36,28 +36,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include - -#include -#include #include -#include "rtabmap/core/util3d.h" -#include "rtabmap/core/util3d_filtering.h" -#include "rtabmap/core/util3d_mapping.h" -#include "rtabmap/core/util3d_transforms.h" +#include "rtabmap/core/OccupancyGrid.h" +#include "rtabmap/utilite/UStl.h" namespace rtabmap_ros { @@ -67,51 +50,154 @@ class ObstaclesDetection : public nodelet::Nodelet public: ObstaclesDetection() : frameId_("base_link"), - normalKSearch_(20), - groundNormalAngle_(M_PI_4), - clusterRadius_(0.05), - minClusterSize_(20), - maxObstaclesHeight_(0.0), // if<=0.0 -> disabled - maxGroundHeight_(0.0), // if<=0.0 -> disabled, used only if detect_flat_obstacles is true - segmentFlatObstacles_(false), - waitForTransform_(false), - optimizeForCloseObjects_(false), - projVoxelSize_(0.01) + waitForTransform_(false) {} virtual ~ObstaclesDetection() {} private: + + void parameterMoved( + ros::NodeHandle & nh, + const std::string & rosName, + const std::string & parameterName, + rtabmap::ParametersMap & parameters) + { + if(nh.hasParam(rosName)) + { + rtabmap::ParametersMap gridParameters = rtabmap::Parameters::getDefaultParameters("Grid"); + rtabmap::ParametersMap::const_iterator iter =gridParameters.find(parameterName); + if(iter != gridParameters.end()) + { + NODELET_ERROR("obstacles_detection: Parameter \"%s\" has moved from " + "rtabmap_ros to rtabmap library. Use " + "parameter \"%s\" instead. The value is still " + "copied to new parameter name.", + rosName.c_str(), + parameterName.c_str()); + std::string type = rtabmap::Parameters::getType(parameterName); + if(type.compare("float") || type.compare("double")) + { + double v = uStr2Double(iter->second); + nh.getParam(rosName, v); + parameters.insert(rtabmap::ParametersPair(parameterName, uNumber2Str(v))); + } + else if(type.compare("int") || type.compare("unsigned int")) + { + int v = uStr2Int(iter->second); + nh.getParam(rosName, v); + parameters.insert(rtabmap::ParametersPair(parameterName, uNumber2Str(v))); + } + else + { + NODELET_ERROR("Not handled type \"%s\" for parameter \"%s\"", type.c_str(), parameterName.c_str()); + } + } + else + { + NODELET_ERROR("Parameter \"%s\" not found in default parameters.", parameterName.c_str()); + } + } + } + virtual void onInit() { + ROS_DEBUG("_"); // not sure why, but all NODELET_*** log are not shown if a normal ROS_*** is not called!? ros::NodeHandle & nh = getNodeHandle(); ros::NodeHandle & pnh = getPrivateNodeHandle(); int queueSize = 10; pnh.param("queue_size", queueSize, queueSize); pnh.param("frame_id", frameId_, frameId_); - pnh.param("normal_k", normalKSearch_, normalKSearch_); - pnh.param("ground_normal_angle", groundNormalAngle_, groundNormalAngle_); - if(pnh.hasParam("normal_estimation_radius") && !pnh.hasParam("cluster_radius")) - { - NODELET_WARN("Parameter \"normal_estimation_radius\" has been renamed " - "to \"cluster_radius\"! Your value is still copied to " - "corresponding parameter. Instead of normal radius, nearest neighbors count " - "\"normal_k\" is used instead (default 20)."); - pnh.param("normal_estimation_radius", clusterRadius_, clusterRadius_); - } - else - { - pnh.param("cluster_radius", clusterRadius_, clusterRadius_); - } - pnh.param("min_cluster_size", minClusterSize_, minClusterSize_); - pnh.param("max_obstacles_height", maxObstaclesHeight_, maxObstaclesHeight_); - pnh.param("max_ground_height", maxGroundHeight_, maxGroundHeight_); - pnh.param("detect_flat_obstacles", segmentFlatObstacles_, segmentFlatObstacles_); + pnh.param("map_frame_id", mapFrameId_, mapFrameId_); pnh.param("wait_for_transform", waitForTransform_, waitForTransform_); - pnh.param("optimize_for_close_objects", optimizeForCloseObjects_, optimizeForCloseObjects_); - pnh.param("proj_voxel_size", projVoxelSize_, projVoxelSize_); + + if(pnh.hasParam("optimize_for_close_objects")) + { + NODELET_ERROR("\"optimize_for_close_objects\" parameter doesn't exist " + "anymore. Use rtabmap_ros/obstacles_detection_old nodelet to use " + "the old interface."); + } + + rtabmap::ParametersMap parameters; + + // Backward compatibility + for(std::map >::const_iterator iter=rtabmap::Parameters::getRemovedParameters().begin(); + iter!=rtabmap::Parameters::getRemovedParameters().end(); + ++iter) + { + std::string vStr; + if(pnh.getParam(iter->first, vStr)) + { + if(iter->second.first) + { + // can be migrated + uInsert(parameters, rtabmap::ParametersPair(iter->second.second, vStr)); + NODELET_ERROR("obstacles_detection: Parameter name changed: \"%s\" -> \"%s\". Please update your launch file accordingly. Value \"%s\" is still set to the new parameter name.", + iter->first.c_str(), iter->second.second.c_str(), vStr.c_str()); + } + else + { + if(iter->second.second.empty()) + { + NODELET_ERROR("obstacles_detection: Parameter \"%s\" doesn't exist anymore!", + iter->first.c_str()); + } + else + { + NODELET_ERROR("obstacles_detection: Parameter \"%s\" doesn't exist anymore! You may look at this similar parameter: \"%s\"", + iter->first.c_str(), iter->second.second.c_str()); + } + } + } + } + + rtabmap::ParametersMap gridParameters2 = rtabmap::Parameters::getDefaultParameters(); + rtabmap::ParametersMap gridParameters = rtabmap::Parameters::getDefaultParameters("Grid"); + for(rtabmap::ParametersMap::iterator iter=gridParameters.begin(); iter!=gridParameters.end(); ++iter) + { + std::string vStr; + bool vBool; + int vInt; + double vDouble; + if(pnh.getParam(iter->first, vStr)) + { + NODELET_INFO("obstacles_detection: Setting parameter \"%s\"=\"%s\"", iter->first.c_str(), vStr.c_str()); + iter->second = vStr; + } + else if(pnh.getParam(iter->first, vBool)) + { + NODELET_INFO("obstacles_detection: Setting parameter \"%s\"=\"%s\"", iter->first.c_str(), uBool2Str(vBool).c_str()); + iter->second = uBool2Str(vBool); + } + else if(pnh.getParam(iter->first, vDouble)) + { + NODELET_INFO("obstacles_detection: Setting parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vDouble).c_str()); + iter->second = uNumber2Str(vDouble); + } + else if(pnh.getParam(iter->first, vInt)) + { + NODELET_INFO("obstacles_detection: Setting parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vInt).c_str()); + iter->second = uNumber2Str(vInt); + } + } + uInsert(parameters, gridParameters); + parameterMoved(pnh, "proj_voxel_size", rtabmap::Parameters::kGridCellSize(), parameters); + parameterMoved(pnh, "ground_normal_angle", rtabmap::Parameters::kGridMaxGroundAngle(), parameters); + parameterMoved(pnh, "min_cluster_size", rtabmap::Parameters::kGridMinClusterSize(), parameters); + parameterMoved(pnh, "normal_estimation_radius", rtabmap::Parameters::kGridClusterRadius(), parameters); + parameterMoved(pnh, "cluster_radius", rtabmap::Parameters::kGridClusterRadius(), parameters); + parameterMoved(pnh, "max_obstacles_height", rtabmap::Parameters::kGridMaxObstacleHeight(), parameters); + parameterMoved(pnh, "max_ground_height", rtabmap::Parameters::kGridMaxGroundHeight(), parameters); + parameterMoved(pnh, "detect_flat_obstacles", rtabmap::Parameters::kGridFlatObstacleDetected(), parameters); + parameterMoved(pnh, "normal_k", rtabmap::Parameters::kGridNormalK(), parameters); + + UASSERT(uContains(parameters, rtabmap::Parameters::kGridMapFrameProjection())); + if(uStr2Bool(parameters.at(rtabmap::Parameters::kGridMapFrameProjection())) && mapFrameId_.empty()) + { + NODELET_ERROR("obstacles_detection: Parameter \"%s\" is true but map_frame_id is not set!", rtabmap::Parameters::kGridMapFrameProjection().c_str()); + } cloudSub_ = nh.subscribe("cloud", 1, &ObstaclesDetection::callback, this); @@ -153,8 +239,32 @@ private: return; } - pcl::PointCloud::Ptr originalCloud(new pcl::PointCloud); - pcl::fromROSMsg(*cloudMsg, *originalCloud); + rtabmap::Transform pose = rtabmap::Transform::getIdentity(); + if(!mapFrameId_.empty()) + { + try + { + if(waitForTransform_) + { + if(!tfListener_.waitForTransform(mapFrameId_, frameId_, cloudMsg->header.stamp, ros::Duration(1))) + { + NODELET_ERROR("Could not get transform from %s to %s after 1 second!", mapFrameId_.c_str(), frameId_.c_str()); + return; + } + } + tf::StampedTransform tmp; + tfListener_.lookupTransform(mapFrameId_, frameId_, cloudMsg->header.stamp, tmp); + pose = rtabmap_ros::transformFromTF(tmp); + } + catch(tf::TransformException & ex) + { + NODELET_ERROR("%s",ex.what()); + return; + } + } + + pcl::PointCloud::Ptr inputCloud(new pcl::PointCloud); + pcl::fromROSMsg(*cloudMsg, *inputCloud); //Common variables for all strategies pcl::IndicesPtr ground, obstacles; @@ -162,136 +272,56 @@ private: pcl::PointCloud::Ptr groundCloud(new pcl::PointCloud); pcl::PointCloud::Ptr obstaclesCloudWithoutFlatSurfaces(new pcl::PointCloud); - if(originalCloud->size()) + if(inputCloud->size()) { - originalCloud = rtabmap::util3d::transformPointCloud(originalCloud, localTransform); - if(maxObstaclesHeight_ > 0) - { - // std::numeric_limits::lowest() exists only for c++11 - originalCloud = rtabmap::util3d::passThrough(originalCloud, "z", std::numeric_limits::min(), maxObstaclesHeight_); - } + inputCloud = rtabmap::util3d::transformPointCloud(inputCloud, localTransform); - if(originalCloud->size()) + pcl::IndicesPtr flatObstacles(new std::vector); + pcl::PointCloud::Ptr cloud = grid_.segmentCloud( + inputCloud, + pcl::IndicesPtr(new std::vector), + pose, + cv::Point3f(localTransform.x(), localTransform.y(), localTransform.z()), + ground, + obstacles, + &flatObstacles); + + if(cloud->size() && (ground->size() || obstacles->size())) { - if(!optimizeForCloseObjects_) + if(groundPub_.getNumSubscribers() && + ground.get() && ground->size()) { - // This is the default strategy - pcl::IndicesPtr flatObstacles(new std::vector); - rtabmap::util3d::segmentObstaclesFromGround( - originalCloud, - ground, - obstacles, - normalKSearch_, - groundNormalAngle_, - clusterRadius_, - minClusterSize_, - segmentFlatObstacles_, - maxGroundHeight_, - &flatObstacles); - - if(groundPub_.getNumSubscribers() && - ground.get() && ground->size()) - { - pcl::copyPointCloud(*originalCloud, *ground, *groundCloud); - } - - if((obstaclesPub_.getNumSubscribers() || projObstaclesPub_.getNumSubscribers()) && - obstacles.get() && obstacles->size()) - { - // remove flat obstacles from obstacles - std::set flatObstaclesSet; - if(projObstaclesPub_.getNumSubscribers()) - { - flatObstaclesSet.insert(flatObstacles->begin(), flatObstacles->end()); - } - - obstaclesCloud->resize(obstacles->size()); - obstaclesCloudWithoutFlatSurfaces->resize(obstacles->size()); - - int oi=0; - for(unsigned int i=0; isize(); ++i) - { - obstaclesCloud->points[i] = originalCloud->at(obstacles->at(i)); - if(flatObstaclesSet.size() == 0 || - flatObstaclesSet.find(obstacles->at(i))==flatObstaclesSet.end()) - { - obstaclesCloudWithoutFlatSurfaces->points[oi] = obstaclesCloud->points[i]; - obstaclesCloudWithoutFlatSurfaces->points[oi].z = 0; - ++oi; - } - - } - - obstaclesCloudWithoutFlatSurfaces->resize(oi); - if(obstaclesCloudWithoutFlatSurfaces->size() && projVoxelSize_ > 0.0) - { - obstaclesCloudWithoutFlatSurfaces = rtabmap::util3d::voxelize(obstaclesCloudWithoutFlatSurfaces, projVoxelSize_); - } - } + pcl::copyPointCloud(*cloud, *ground, *groundCloud); } - else + + if((obstaclesPub_.getNumSubscribers() || projObstaclesPub_.getNumSubscribers()) && + obstacles.get() && obstacles->size()) { - // in this case optimizeForCloseObject_ is true: - // we divide the floor point cloud into two subsections, one for all potential floor points up to 1m - // one for potential floor points further away than 1m. - // For the points at closer range, we use a smaller normal estimation radius and ground normal angle, - // which allows to detect smaller objects, without increasing the number of false positive. - // For all other points, we use a bigger normal estimation radius (* 3.) and tolerance for the - // grond normal angle (* 2.). - - pcl::PointCloud::Ptr originalCloud_near = rtabmap::util3d::passThrough(originalCloud, "x", std::numeric_limits::min(), 1.); - pcl::PointCloud::Ptr originalCloud_far = rtabmap::util3d::passThrough(originalCloud, "x", 1., std::numeric_limits::max()); - - // Part 1: segment floor and obstacles near the robot - rtabmap::util3d::segmentObstaclesFromGround( - originalCloud_near, - ground, - obstacles, - normalKSearch_, - groundNormalAngle_, - clusterRadius_, - minClusterSize_, - segmentFlatObstacles_, - maxGroundHeight_); - - if(groundPub_.getNumSubscribers() && ground.get() && ground->size()) + // remove flat obstacles from obstacles + std::set flatObstaclesSet; + if(projObstaclesPub_.getNumSubscribers()) { - pcl::copyPointCloud(*originalCloud_near, *ground, *groundCloud); - ground->clear(); + flatObstaclesSet.insert(flatObstacles->begin(), flatObstacles->end()); } - if((obstaclesPub_.getNumSubscribers() || projObstaclesPub_.getNumSubscribers()) && obstacles.get() && obstacles->size()) + obstaclesCloud->resize(obstacles->size()); + obstaclesCloudWithoutFlatSurfaces->resize(obstacles->size()); + + int oi=0; + for(unsigned int i=0; isize(); ++i) { - pcl::copyPointCloud(*originalCloud_near, *obstacles, *obstaclesCloud); - obstacles->clear(); + obstaclesCloud->points[i] = cloud->at(obstacles->at(i)); + if(flatObstaclesSet.size() == 0 || + flatObstaclesSet.find(obstacles->at(i))==flatObstaclesSet.end()) + { + obstaclesCloudWithoutFlatSurfaces->points[oi] = obstaclesCloud->points[i]; + obstaclesCloudWithoutFlatSurfaces->points[oi].z = 0; + ++oi; + } + } - // Part 2: segment floor and obstacles far from the robot - rtabmap::util3d::segmentObstaclesFromGround( - originalCloud_far, - ground, - obstacles, - normalKSearch_, - 2.*groundNormalAngle_, - 3.*clusterRadius_, - minClusterSize_, - segmentFlatObstacles_, - maxGroundHeight_); - - if(groundPub_.getNumSubscribers() && ground.get() && ground->size()) - { - pcl::PointCloud::Ptr groundCloud2 (new pcl::PointCloud); - pcl::copyPointCloud(*originalCloud_far, *ground, *groundCloud2); - *groundCloud += *groundCloud2; - } - - - if((obstaclesPub_.getNumSubscribers() || projObstaclesPub_.getNumSubscribers()) && obstacles.get() && obstacles->size()) - { - pcl::PointCloud::Ptr obstacles2(new pcl::PointCloud); - pcl::copyPointCloud(*originalCloud_far, *obstacles, *obstacles2); - *obstaclesCloud += *obstacles2; - } + obstaclesCloudWithoutFlatSurfaces->resize(oi); } if(!localTransform.isIdentity()) @@ -346,16 +376,10 @@ private: private: std::string frameId_; - int normalKSearch_; - double groundNormalAngle_; - double clusterRadius_; - int minClusterSize_; - double maxObstaclesHeight_; - double maxGroundHeight_; - bool segmentFlatObstacles_; + std::string mapFrameId_; bool waitForTransform_; - bool optimizeForCloseObjects_; - double projVoxelSize_; + + rtabmap::OccupancyGrid grid_; tf::TransformListener tfListener_; diff --git a/src/nodelets/obstacles_detection_indoor.cpp b/src/nodelets/obstacles_detection_indoor.cpp deleted file mode 100644 index 4becd750..00000000 --- a/src/nodelets/obstacles_detection_indoor.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/* -Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Universite de Sherbrooke nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include - -#include - -#include "rtabmap/core/util3d_filtering.h" -#include "rtabmap/core/util3d_transforms.h" - -namespace rtabmap_ros -{ - -class ObstaclesDetectionIndoor : public nodelet::Nodelet -{ -public: - ObstaclesDetectionIndoor() : - frameId_(""), - minObstaclesHeight_(0.0), // if >0.0 -> disabled - maxObstaclesHeight_(0.0), // if <=0.0 -> disabled - minGroundHeight_(-0.05), - maxGroundHeight_(0.05), - waitForTransform_(false), - projVoxelSize_(0.01), - noiseFilterRadius_(0.0), // if<=0.0 -> disabled - noiseFilterMinNeighbors_(5) - {} - - virtual ~ObstaclesDetectionIndoor() - {} - -private: - virtual void onInit() - { - ros::NodeHandle & nh = getNodeHandle(); - ros::NodeHandle & pnh = getPrivateNodeHandle(); - - int queueSize = 10; - pnh.param("queue_size", queueSize, queueSize); - pnh.param("frame_id", frameId_, frameId_); - - pnh.param("min_obstacles_height", minObstaclesHeight_, minObstaclesHeight_); - pnh.param("max_obstacles_height", maxObstaclesHeight_, maxObstaclesHeight_); - pnh.param("min_ground_height", minGroundHeight_, minGroundHeight_); - pnh.param("max_ground_height", maxGroundHeight_, maxGroundHeight_); - pnh.param("noise_filter_radius", noiseFilterRadius_, noiseFilterRadius_); - pnh.param("noise_filter_min_neighbors", noiseFilterMinNeighbors_, noiseFilterMinNeighbors_); - pnh.param("wait_for_transform", waitForTransform_, waitForTransform_); - pnh.param("proj_voxel_size", projVoxelSize_, projVoxelSize_); - - cloudSub_ = nh.subscribe("cloud", 1, &ObstaclesDetectionIndoor::callback, this); - - groundPub_ = nh.advertise("ground", 1); - obstaclesPub_ = nh.advertise("obstacles", 1); - projObstaclesPub_ = nh.advertise("proj_obstacles", 1); - } - - - - void callback(const sensor_msgs::PointCloud2ConstPtr & cloudMsg) - { - ros::WallTime time = ros::WallTime::now(); - - if (groundPub_.getNumSubscribers() == 0 && obstaclesPub_.getNumSubscribers() == 0 && projObstaclesPub_.getNumSubscribers() == 0) - { - // no one wants the results - return; - } - - rtabmap::Transform localTransform; - try - { - if(waitForTransform_) - { - if(!tfListener_.waitForTransform(frameId_, cloudMsg->header.frame_id, cloudMsg->header.stamp, ros::Duration(1))) - { - NODELET_ERROR("Could not get transform from %s to %s after 1 second!", frameId_.c_str(), cloudMsg->header.frame_id.c_str()); - return; - } - } - tf::StampedTransform tmp; - tfListener_.lookupTransform(frameId_, cloudMsg->header.frame_id, cloudMsg->header.stamp, tmp); - localTransform = rtabmap_ros::transformFromTF(tmp); - } - catch(tf::TransformException & ex) - { - NODELET_ERROR("%s",ex.what()); - return; - } - - pcl::PointCloud::Ptr originalCloud(new pcl::PointCloud); - pcl::fromROSMsg(*cloudMsg, *originalCloud); - - pcl::PointCloud::Ptr obstaclesCloud(new pcl::PointCloud); - pcl::PointCloud::Ptr groundCloud(new pcl::PointCloud); - pcl::PointCloud::Ptr projectedObstaclesCloud(new pcl::PointCloud); - - if(originalCloud->size()) - { - originalCloud = rtabmap::util3d::transformPointCloud(originalCloud, localTransform); - - pcl::IndicesPtr indices(new std::vector); - indices->resize(originalCloud->size()); - for(unsigned int i=0; isize(); ++i) - { - indices->at(i) = i; - } - - // segmentation - pcl::IndicesPtr groundIndices = rtabmap::util3d::passThrough(originalCloud, indices, "z", minGroundHeight_, maxGroundHeight_); - pcl::IndicesPtr obstacleIndices = rtabmap::util3d::extractIndices(originalCloud, groundIndices, true); - - if(minObstaclesHeight_ <= 0.0 || maxObstaclesHeight_ > 0.0) - { - // std::numeric_limits::lowest() exists only for c++11 - obstacleIndices = rtabmap::util3d::passThrough(originalCloud, obstacleIndices, "z", - minObstaclesHeight_>0.0?std::numeric_limits::min():minObstaclesHeight_, - maxObstaclesHeight_<=0.0?std::numeric_limits::max():maxObstaclesHeight_); - } - - // Do optional radius filtering to remove some noise - if(noiseFilterRadius_ > 0.0 && noiseFilterMinNeighbors_ > 0) - { - if(groundIndices->size()) - { - groundIndices = rtabmap::util3d::radiusFiltering(originalCloud, groundIndices, noiseFilterRadius_, noiseFilterMinNeighbors_); - } - if(obstacleIndices->size()) - { - obstacleIndices = rtabmap::util3d::radiusFiltering(originalCloud, obstacleIndices, noiseFilterRadius_, noiseFilterMinNeighbors_); - } - } - - if(projObstaclesPub_.getNumSubscribers() && obstacleIndices->size()) - { - projectedObstaclesCloud->resize(obstacleIndices->size()); - for(unsigned int i=0; isize(); ++i) - { - projectedObstaclesCloud->points[i] = originalCloud->at(obstacleIndices->at(i)); - projectedObstaclesCloud->points[i].z = 0; - } - - if(projVoxelSize_ > 0.0) - { - projectedObstaclesCloud = rtabmap::util3d::voxelize(projectedObstaclesCloud, projVoxelSize_); - } - } - - if(!localTransform.isIdentity()) - { - //transform back in topic frame - rtabmap::Transform localTransformInv = localTransform.inverse(); - if(groundIndices->size()) - { - pcl::transformPointCloud(*originalCloud, *groundIndices, *groundCloud, localTransformInv.toEigen3f()); - } - if(obstacleIndices->size()) - { - pcl::transformPointCloud(*originalCloud, *obstacleIndices, *obstaclesCloud, localTransformInv.toEigen3f()); - } - } - else - { - if(groundIndices->size()) - { - pcl::copyPointCloud(*originalCloud, *groundIndices, *groundCloud); - } - if(obstacleIndices->size()) - { - pcl::copyPointCloud(*originalCloud, *obstacleIndices, *obstaclesCloud); - } - } - } - - if(groundPub_.getNumSubscribers()) - { - sensor_msgs::PointCloud2 rosCloud; - pcl::toROSMsg(*groundCloud, rosCloud); - rosCloud.header = cloudMsg->header; - - //publish the message - groundPub_.publish(rosCloud); - } - - if(obstaclesPub_.getNumSubscribers()) - { - sensor_msgs::PointCloud2 rosCloud; - pcl::toROSMsg(*obstaclesCloud, rosCloud); - rosCloud.header = cloudMsg->header; - - //publish the message - obstaclesPub_.publish(rosCloud); - } - - if(projObstaclesPub_.getNumSubscribers()) - { - sensor_msgs::PointCloud2 rosCloud; - pcl::toROSMsg(*projectedObstaclesCloud, rosCloud); - rosCloud.header.stamp = cloudMsg->header.stamp; - rosCloud.header.frame_id = frameId_; - - //publish the message - projObstaclesPub_.publish(rosCloud); - } - - NODELET_DEBUG("Obstacle segmentation time = %f s", (ros::WallTime::now() - time).toSec()); - } - -private: - std::string frameId_; - double minObstaclesHeight_; - double maxObstaclesHeight_; - double minGroundHeight_; - double maxGroundHeight_; - bool waitForTransform_; - double projVoxelSize_; - double noiseFilterRadius_; - int noiseFilterMinNeighbors_; - - tf::TransformListener tfListener_; - - ros::Publisher groundPub_; - ros::Publisher obstaclesPub_; - ros::Publisher projObstaclesPub_; - - ros::Subscriber cloudSub_; -}; - -PLUGINLIB_EXPORT_CLASS(rtabmap_ros::ObstaclesDetectionIndoor, nodelet::Nodelet); -} - - diff --git a/src/nodelets/obstacles_detection_old.cpp b/src/nodelets/obstacles_detection_old.cpp new file mode 100644 index 00000000..40bcd474 --- /dev/null +++ b/src/nodelets/obstacles_detection_old.cpp @@ -0,0 +1,357 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +#include "rtabmap/core/util3d.h" +#include "rtabmap/core/util3d_filtering.h" +#include "rtabmap/core/util3d_mapping.h" +#include "rtabmap/core/util3d_transforms.h" + +namespace rtabmap_ros +{ + +class ObstaclesDetectionOld : public nodelet::Nodelet +{ +public: + ObstaclesDetectionOld() : + frameId_("base_link"), + normalKSearch_(20), + groundNormalAngle_(M_PI_4), + clusterRadius_(0.05), + minClusterSize_(20), + maxObstaclesHeight_(0.0), // if<=0.0 -> disabled + maxGroundHeight_(0.0), // if<=0.0 -> disabled, used only if detect_flat_obstacles is true + segmentFlatObstacles_(false), + waitForTransform_(false), + optimizeForCloseObjects_(false), + projVoxelSize_(0.01) + {} + + virtual ~ObstaclesDetectionOld() + {} + +private: + virtual void onInit() + { + ros::NodeHandle & nh = getNodeHandle(); + ros::NodeHandle & pnh = getPrivateNodeHandle(); + + int queueSize = 10; + pnh.param("queue_size", queueSize, queueSize); + pnh.param("frame_id", frameId_, frameId_); + pnh.param("normal_k", normalKSearch_, normalKSearch_); + pnh.param("ground_normal_angle", groundNormalAngle_, groundNormalAngle_); + if(pnh.hasParam("normal_estimation_radius") && !pnh.hasParam("cluster_radius")) + { + NODELET_WARN("Parameter \"normal_estimation_radius\" has been renamed " + "to \"cluster_radius\"! Your value is still copied to " + "corresponding parameter. Instead of normal radius, nearest neighbors count " + "\"normal_k\" is used instead (default 20)."); + pnh.param("normal_estimation_radius", clusterRadius_, clusterRadius_); + } + else + { + pnh.param("cluster_radius", clusterRadius_, clusterRadius_); + } + pnh.param("min_cluster_size", minClusterSize_, minClusterSize_); + pnh.param("max_obstacles_height", maxObstaclesHeight_, maxObstaclesHeight_); + pnh.param("max_ground_height", maxGroundHeight_, maxGroundHeight_); + pnh.param("detect_flat_obstacles", segmentFlatObstacles_, segmentFlatObstacles_); + pnh.param("wait_for_transform", waitForTransform_, waitForTransform_); + pnh.param("optimize_for_close_objects", optimizeForCloseObjects_, optimizeForCloseObjects_); + pnh.param("proj_voxel_size", projVoxelSize_, projVoxelSize_); + + cloudSub_ = nh.subscribe("cloud", 1, &ObstaclesDetectionOld::callback, this); + + groundPub_ = nh.advertise("ground", 1); + obstaclesPub_ = nh.advertise("obstacles", 1); + projObstaclesPub_ = nh.advertise("proj_obstacles", 1); + } + + + + void callback(const sensor_msgs::PointCloud2ConstPtr & cloudMsg) + { + ros::WallTime time = ros::WallTime::now(); + + if (groundPub_.getNumSubscribers() == 0 && obstaclesPub_.getNumSubscribers() == 0 && projObstaclesPub_.getNumSubscribers() == 0) + { + // no one wants the results + return; + } + + rtabmap::Transform localTransform; + try + { + if(waitForTransform_) + { + if(!tfListener_.waitForTransform(frameId_, cloudMsg->header.frame_id, cloudMsg->header.stamp, ros::Duration(1))) + { + NODELET_ERROR("Could not get transform from %s to %s after 1 second!", frameId_.c_str(), cloudMsg->header.frame_id.c_str()); + return; + } + } + tf::StampedTransform tmp; + tfListener_.lookupTransform(frameId_, cloudMsg->header.frame_id, cloudMsg->header.stamp, tmp); + localTransform = rtabmap_ros::transformFromTF(tmp); + } + catch(tf::TransformException & ex) + { + NODELET_ERROR("%s",ex.what()); + return; + } + + pcl::PointCloud::Ptr originalCloud(new pcl::PointCloud); + pcl::fromROSMsg(*cloudMsg, *originalCloud); + + //Common variables for all strategies + pcl::IndicesPtr ground, obstacles; + pcl::PointCloud::Ptr obstaclesCloud(new pcl::PointCloud); + pcl::PointCloud::Ptr groundCloud(new pcl::PointCloud); + pcl::PointCloud::Ptr obstaclesCloudWithoutFlatSurfaces(new pcl::PointCloud); + + if(originalCloud->size()) + { + originalCloud = rtabmap::util3d::transformPointCloud(originalCloud, localTransform); + if(maxObstaclesHeight_ > 0) + { + // std::numeric_limits::lowest() exists only for c++11 + originalCloud = rtabmap::util3d::passThrough(originalCloud, "z", std::numeric_limits::min(), maxObstaclesHeight_); + } + + if(originalCloud->size()) + { + if(!optimizeForCloseObjects_) + { + // This is the default strategy + pcl::IndicesPtr flatObstacles(new std::vector); + rtabmap::util3d::segmentObstaclesFromGround( + originalCloud, + ground, + obstacles, + normalKSearch_, + groundNormalAngle_, + clusterRadius_, + minClusterSize_, + segmentFlatObstacles_, + maxGroundHeight_, + &flatObstacles); + + if(groundPub_.getNumSubscribers() && + ground.get() && ground->size()) + { + pcl::copyPointCloud(*originalCloud, *ground, *groundCloud); + } + + if((obstaclesPub_.getNumSubscribers() || projObstaclesPub_.getNumSubscribers()) && + obstacles.get() && obstacles->size()) + { + // remove flat obstacles from obstacles + std::set flatObstaclesSet; + if(projObstaclesPub_.getNumSubscribers()) + { + flatObstaclesSet.insert(flatObstacles->begin(), flatObstacles->end()); + } + + obstaclesCloud->resize(obstacles->size()); + obstaclesCloudWithoutFlatSurfaces->resize(obstacles->size()); + + int oi=0; + for(unsigned int i=0; isize(); ++i) + { + obstaclesCloud->points[i] = originalCloud->at(obstacles->at(i)); + if(flatObstaclesSet.size() == 0 || + flatObstaclesSet.find(obstacles->at(i))==flatObstaclesSet.end()) + { + obstaclesCloudWithoutFlatSurfaces->points[oi] = obstaclesCloud->points[i]; + obstaclesCloudWithoutFlatSurfaces->points[oi].z = 0; + ++oi; + } + + } + + obstaclesCloudWithoutFlatSurfaces->resize(oi); + if(obstaclesCloudWithoutFlatSurfaces->size() && projVoxelSize_ > 0.0) + { + obstaclesCloudWithoutFlatSurfaces = rtabmap::util3d::voxelize(obstaclesCloudWithoutFlatSurfaces, projVoxelSize_); + } + } + } + else + { + // in this case optimizeForCloseObject_ is true: + // we divide the floor point cloud into two subsections, one for all potential floor points up to 1m + // one for potential floor points further away than 1m. + // For the points at closer range, we use a smaller normal estimation radius and ground normal angle, + // which allows to detect smaller objects, without increasing the number of false positive. + // For all other points, we use a bigger normal estimation radius (* 3.) and tolerance for the + // grond normal angle (* 2.). + + pcl::PointCloud::Ptr originalCloud_near = rtabmap::util3d::passThrough(originalCloud, "x", std::numeric_limits::min(), 1.); + pcl::PointCloud::Ptr originalCloud_far = rtabmap::util3d::passThrough(originalCloud, "x", 1., std::numeric_limits::max()); + + // Part 1: segment floor and obstacles near the robot + rtabmap::util3d::segmentObstaclesFromGround( + originalCloud_near, + ground, + obstacles, + normalKSearch_, + groundNormalAngle_, + clusterRadius_, + minClusterSize_, + segmentFlatObstacles_, + maxGroundHeight_); + + if(groundPub_.getNumSubscribers() && ground.get() && ground->size()) + { + pcl::copyPointCloud(*originalCloud_near, *ground, *groundCloud); + ground->clear(); + } + + if((obstaclesPub_.getNumSubscribers() || projObstaclesPub_.getNumSubscribers()) && obstacles.get() && obstacles->size()) + { + pcl::copyPointCloud(*originalCloud_near, *obstacles, *obstaclesCloud); + obstacles->clear(); + } + + // Part 2: segment floor and obstacles far from the robot + rtabmap::util3d::segmentObstaclesFromGround( + originalCloud_far, + ground, + obstacles, + normalKSearch_, + 2.*groundNormalAngle_, + 3.*clusterRadius_, + minClusterSize_, + segmentFlatObstacles_, + maxGroundHeight_); + + if(groundPub_.getNumSubscribers() && ground.get() && ground->size()) + { + pcl::PointCloud::Ptr groundCloud2 (new pcl::PointCloud); + pcl::copyPointCloud(*originalCloud_far, *ground, *groundCloud2); + *groundCloud += *groundCloud2; + } + + + if((obstaclesPub_.getNumSubscribers() || projObstaclesPub_.getNumSubscribers()) && obstacles.get() && obstacles->size()) + { + pcl::PointCloud::Ptr obstacles2(new pcl::PointCloud); + pcl::copyPointCloud(*originalCloud_far, *obstacles, *obstacles2); + *obstaclesCloud += *obstacles2; + } + } + + if(!localTransform.isIdentity()) + { + //transform back in topic frame + rtabmap::Transform localTransformInv = localTransform.inverse(); + if(groundCloud->size()) + { + groundCloud = rtabmap::util3d::transformPointCloud(groundCloud, localTransformInv); + } + if(obstaclesCloud->size()) + { + obstaclesCloud = rtabmap::util3d::transformPointCloud(obstaclesCloud, localTransformInv); + } + } + } + } + + if(groundPub_.getNumSubscribers()) + { + sensor_msgs::PointCloud2 rosCloud; + pcl::toROSMsg(*groundCloud, rosCloud); + rosCloud.header = cloudMsg->header; + + //publish the message + groundPub_.publish(rosCloud); + } + + if(obstaclesPub_.getNumSubscribers()) + { + sensor_msgs::PointCloud2 rosCloud; + pcl::toROSMsg(*obstaclesCloud, rosCloud); + rosCloud.header = cloudMsg->header; + + //publish the message + obstaclesPub_.publish(rosCloud); + } + + if(projObstaclesPub_.getNumSubscribers()) + { + sensor_msgs::PointCloud2 rosCloud; + pcl::toROSMsg(*obstaclesCloudWithoutFlatSurfaces, rosCloud); + rosCloud.header.stamp = cloudMsg->header.stamp; + rosCloud.header.frame_id = frameId_; + + //publish the message + projObstaclesPub_.publish(rosCloud); + } + + NODELET_DEBUG("Obstacles segmentation time = %f s", (ros::WallTime::now() - time).toSec()); + } + +private: + std::string frameId_; + int normalKSearch_; + double groundNormalAngle_; + double clusterRadius_; + int minClusterSize_; + double maxObstaclesHeight_; + double maxGroundHeight_; + bool segmentFlatObstacles_; + bool waitForTransform_; + bool optimizeForCloseObjects_; + double projVoxelSize_; + + tf::TransformListener tfListener_; + + ros::Publisher groundPub_; + ros::Publisher obstaclesPub_; + ros::Publisher projObstaclesPub_; + + ros::Subscriber cloudSub_; +}; + +PLUGINLIB_EXPORT_CLASS(rtabmap_ros::ObstaclesDetectionOld, nodelet::Nodelet); +} + + From d0772d91ace1e3d85d6b114d1a4d923a59801f43 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 30 Aug 2016 19:18:15 -0400 Subject: [PATCH 34/78] obstacles_detection: fixed grid parameters ignored --- src/nodelets/obstacles_detection.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nodelets/obstacles_detection.cpp b/src/nodelets/obstacles_detection.cpp index 0c808054..7a834733 100644 --- a/src/nodelets/obstacles_detection.cpp +++ b/src/nodelets/obstacles_detection.cpp @@ -199,6 +199,8 @@ private: NODELET_ERROR("obstacles_detection: Parameter \"%s\" is true but map_frame_id is not set!", rtabmap::Parameters::kGridMapFrameProjection().c_str()); } + grid_.parseParameters(parameters); + cloudSub_ = nh.subscribe("cloud", 1, &ObstaclesDetection::callback, this); groundPub_ = nh.advertise("ground", 1); From 8f5c50b3f03850b7b2880b51172290ad90c7c510 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 1 Sep 2016 20:33:42 -0400 Subject: [PATCH 35/78] MapsManager: added "octomap_occupancy_thr" --- include/rtabmap_ros/MapsManager.h | 1 + src/MapsManager.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/rtabmap_ros/MapsManager.h b/include/rtabmap_ros/MapsManager.h index 0fba3bc5..d4af081f 100644 --- a/include/rtabmap_ros/MapsManager.h +++ b/include/rtabmap_ros/MapsManager.h @@ -120,6 +120,7 @@ private: rtabmap::OctoMap * octomap_; int octomapTreeDepth_; + double octomapOccupancyThr_; rtabmap::ParametersMap parameters_; }; diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index e8b48312..2c38c7f5 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -69,12 +69,13 @@ MapsManager::MapsManager(bool usePublicNamespace) : mapFilterRadius_(0.0), mapFilterAngle_(30.0), // degrees mapCacheCleanup_(true), - negativePosesIgnored_(true), + negativePosesIgnored_(false), assembledObstacles_(new pcl::PointCloud), assembledGround_(new pcl::PointCloud), occupancyGrid_(new OccupancyGrid), octomap_(0), - octomapTreeDepth_(16) + octomapTreeDepth_(16), + octomapOccupancyThr_(0.5) { ros::NodeHandle nh; @@ -128,7 +129,9 @@ MapsManager::MapsManager(bool usePublicNamespace) : #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP - octomap_ = new OctoMap(gridCellSize_); + pnh.param("octomap_occupancy_thr", octomapOccupancyThr_, octomapOccupancyThr_); + UASSERT(octomapOccupancyThr_>=0.0 && octomapOccupancyThr_<=1.0); + octomap_ = new OctoMap(gridCellSize_, octomapOccupancyThr_); pnh.param("octomap_tree_depth", octomapTreeDepth_, octomapTreeDepth_); if(octomapTreeDepth_ > 16) { @@ -141,6 +144,7 @@ MapsManager::MapsManager(bool usePublicNamespace) : octomapTreeDepth_ = 16; } ROS_INFO("%s(maps): octomap_tree_depth = %d", name.c_str(), octomapTreeDepth_); + ROS_INFO("%s(maps): octomap_occupancy_thr = %f", name.c_str(), octomapOccupancyThr_); #endif #endif From 3b6daf71898d8c82c9e23df68825692838324f75 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Sat, 3 Sep 2016 14:30:10 -0400 Subject: [PATCH 36/78] data_recorder.launch: fixed approx_sync default to false for rgb-d input --- launch/data_recorder.launch | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launch/data_recorder.launch b/launch/data_recorder.launch index 4a210253..ddf02002 100644 --- a/launch/data_recorder.launch +++ b/launch/data_recorder.launch @@ -5,7 +5,8 @@ - + + @@ -41,7 +42,8 @@ - + + @@ -52,7 +54,7 @@ - + From babb8c85090814cf2d68d886aa2776a2dc78aa1b Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 26 Sep 2016 16:37:31 -0400 Subject: [PATCH 37/78] Added rtabmap_ros/RGBDImage and rtabmap_ros/UserData topics. Refactored input messages synchronization of rtabmap and rtabmapviz nodes. "depth_cameras" parameter is replaced with "rgbd_cameras" parameter. Multiple camera is handled through rtabmap_ros/RGBDImage input topics (same for rgbd_odometry node). --- CMakeLists.txt | 10 + include/rtabmap_ros/CommonDataSubscriber.h | 244 ++++ .../rtabmap_ros/CommonDataSubscriberDefines.h | 194 +++ include/rtabmap_ros/CoreWrapper.h | 328 +---- include/rtabmap_ros/GuiWrapper.h | 388 +----- include/rtabmap_ros/MsgConversion.h | 15 +- launch/demo/demo_two_kinects.launch | 79 +- launch/tests/test_rgbd_image.launch | 46 + msg/NodeData.msg | 2 + msg/RGBDImage.msg | 12 + msg/UserData.msg | 12 + nodelet_plugins.xml | 8 + src/CommonDataSubscriber.cpp | 365 ++++++ src/CoreNode.cpp | 2 +- src/CoreWrapper.cpp | 990 ++------------- src/GuiNode.cpp | 2 +- src/GuiWrapper.cpp | 1118 +---------------- src/MsgConversion.cpp | 181 ++- src/impl/CommonDataSubscriberDepth.cpp | 369 ++++++ src/impl/CommonDataSubscriberRGBD.cpp | 386 ++++++ src/impl/CommonDataSubscriberRGBD2.cpp | 385 ++++++ src/impl/CommonDataSubscriberStereo.cpp | 138 ++ src/nodelets/rgbd_odometry.cpp | 423 +++---- src/nodelets/rgbd_sync.cpp | 165 +++ 24 files changed, 2942 insertions(+), 2920 deletions(-) create mode 100644 include/rtabmap_ros/CommonDataSubscriber.h create mode 100644 include/rtabmap_ros/CommonDataSubscriberDefines.h create mode 100644 launch/tests/test_rgbd_image.launch create mode 100644 msg/RGBDImage.msg create mode 100644 msg/UserData.msg create mode 100644 src/CommonDataSubscriber.cpp create mode 100644 src/impl/CommonDataSubscriberDepth.cpp create mode 100644 src/impl/CommonDataSubscriberRGBD.cpp create mode 100644 src/impl/CommonDataSubscriberRGBD2.cpp create mode 100644 src/impl/CommonDataSubscriberStereo.cpp create mode 100644 src/nodelets/rgbd_sync.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 28f47a8a..b2998e1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,8 @@ add_message_files( Point2f.msg Point3f.msg Goal.msg + RGBDImage.msg + UserData.msg ) ## Generate services in the 'srv' folder @@ -162,9 +164,15 @@ SET(rtabmap_ros_lib_src src/nodelets/obstacles_detection.cpp src/nodelets/obstacles_detection_old.cpp src/nodelets/point_cloud_aggregator.cpp + src/nodelets/rgbd_sync.cpp src/OdometryROS.cpp src/MsgConversion.cpp src/MapsManager.cpp + src/CommonDataSubscriber.cpp + src/impl/CommonDataSubscriberDepth.cpp + src/impl/CommonDataSubscriberStereo.cpp + src/impl/CommonDataSubscriberRGBD.cpp + src/impl/CommonDataSubscriberRGBD2.cpp ) # If costmap_2d is found, add the plugin @@ -327,6 +335,7 @@ install(TARGETS rtabmap rtabmapviz rgbd_odometry + rgbdicp_odometry stereo_odometry map_assembler map_optimizer @@ -341,6 +350,7 @@ install(TARGETS rtabmap_ros rtabmap rgbd_odometry + rgbdicp_odometry stereo_odometry map_assembler map_optimizer diff --git a/include/rtabmap_ros/CommonDataSubscriber.h b/include/rtabmap_ros/CommonDataSubscriber.h new file mode 100644 index 00000000..f84a2152 --- /dev/null +++ b/include/rtabmap_ros/CommonDataSubscriber.h @@ -0,0 +1,244 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef INCLUDE_RTABMAP_ROS_COMMONDATASUBSCRIBER_H_ +#define INCLUDE_RTABMAP_ROS_COMMONDATASUBSCRIBER_H_ + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace rtabmap_ros { + +class CommonDataSubscriber { +public: + CommonDataSubscriber(); + virtual ~CommonDataSubscriber(); + + bool isSubscribedToDepth() const {return subscribedToDepth_;} + bool isSubscribedToStereo() const {return subscribedToStereo_;} + bool isSubscribedToRGBD() const {return subscribedToRGBD_;} + bool isSubscribedToScan2d() const {return subscribedToScan2d_;} + bool isSubscribedToScan3d() const {return subscribedToScan3d_;} + bool isSubscribedToOdomInfo() const {return subscribedToOdomInfo_;} + bool isDataSubscribed() const {return isSubscribedToDepth() || isSubscribedToStereo() || isSubscribedToRGBD();} + int getQueueSize() const {return queueSize_;} + +protected: + virtual void commonDepthCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, + const sensor_msgs::LaserScanConstPtr& scanMsg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) = 0; + virtual void commonStereoCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const sensor_msgs::ImageConstPtr& leftImageMsg, + const sensor_msgs::ImageConstPtr& rightImageMsg, + const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, + const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, + const sensor_msgs::LaserScanConstPtr& scanMsg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) = 0; + + void commonSingleDepthCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const cv_bridge::CvImageConstPtr & imageMsg, + const cv_bridge::CvImageConstPtr & depthMsg, + const sensor_msgs::CameraInfo & cameraInfoMsg, + const sensor_msgs::LaserScanConstPtr& scanMsg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg); + +private: + void setupDepthCallbacks( + bool subscribeOdom, + bool subscribeUserData, + bool subscribeScan2d, + bool subscribeScan3d, + bool subscribeOdomInfo, + int queueSize, + bool approxSync); + void setupStereoCallbacks( + bool subscribeOdom, + bool subscribeOdomInfo, + int queueSize, + bool approxSync); + void setupRGBDCallbacks( + bool subscribeOdom, + bool subscribeUserData, + bool subscribeScan2d, + bool subscribeScan3d, + bool subscribeOdomInfo, + int queueSize, + bool approxSync); + void setupRGBD2Callbacks( + bool subscribeOdom, + bool subscribeUserData, + bool subscribeScan2d, + bool subscribeScan3d, + bool subscribeOdomInfo, + int queueSize, + bool approxSync); + +private: + int queueSize_; + bool subscribedToDepth_; + bool subscribedToStereo_; + bool subscribedToRGBD_; + bool subscribedToScan2d_; + bool subscribedToScan3d_; + bool subscribedToOdomInfo_; + + //for depth callback + image_transport::SubscriberFilter imageSub_; + image_transport::SubscriberFilter imageDepthSub_; + message_filters::Subscriber cameraInfoSub_; + + //for rgbd callback + ros::Subscriber rgbdSub_; + std::vector*> rgbdSubs_; + + //stereo callback + image_transport::SubscriberFilter imageRectLeft_; + image_transport::SubscriberFilter imageRectRight_; + message_filters::Subscriber cameraInfoLeft_; + message_filters::Subscriber cameraInfoRight_; + + message_filters::Subscriber odomSub_; + message_filters::Subscriber userDataSub_; + message_filters::Subscriber scanSub_; + message_filters::Subscriber scan3dSub_; + message_filters::Subscriber odomInfoSub_; + + // RGB + Depth + DATA_SYNCS3(depth, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo); + DATA_SYNCS4(depthScan2d, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::LaserScan); + DATA_SYNCS4(depthScan3d, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::PointCloud2); + DATA_SYNCS4(depthInfo, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, rtabmap_ros::OdomInfo); + + // RGB + Depth + Odom + DATA_SYNCS4(depthOdom, nav_msgs::Odometry, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo); + DATA_SYNCS5(depthOdomScan2d, nav_msgs::Odometry, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::LaserScan); + DATA_SYNCS5(depthOdomScan3d, nav_msgs::Odometry, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::PointCloud2); + DATA_SYNCS5(depthOdomInfo, nav_msgs::Odometry, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, rtabmap_ros::OdomInfo); + + // RGB + Depth + User Data + DATA_SYNCS4(depthData, rtabmap_ros::UserData, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo); + DATA_SYNCS5(depthDataScan2d, rtabmap_ros::UserData, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::LaserScan); + DATA_SYNCS5(depthDataScan3d, rtabmap_ros::UserData, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::PointCloud2); + DATA_SYNCS5(depthDataInfo, rtabmap_ros::UserData, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, rtabmap_ros::OdomInfo); + + // RGB + Depth + Odom + User Data + DATA_SYNCS5(depthOdomData, nav_msgs::Odometry, rtabmap_ros::UserData, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo); + DATA_SYNCS6(depthOdomDataScan2d, nav_msgs::Odometry, rtabmap_ros::UserData, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::LaserScan); + DATA_SYNCS6(depthOdomDataScan3d, nav_msgs::Odometry, rtabmap_ros::UserData, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::PointCloud2); + DATA_SYNCS6(depthOdomDataInfo, nav_msgs::Odometry, rtabmap_ros::UserData, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, rtabmap_ros::OdomInfo); + + // Stereo + DATA_SYNCS4(stereo, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::CameraInfo); + DATA_SYNCS5(stereoInfo, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::CameraInfo, rtabmap_ros::OdomInfo); + + // Stereo + Odom + DATA_SYNCS5(stereoOdom, nav_msgs::Odometry, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::CameraInfo); + DATA_SYNCS6(stereoOdomInfo, nav_msgs::Odometry, sensor_msgs::Image, sensor_msgs::Image, sensor_msgs::CameraInfo, sensor_msgs::CameraInfo, rtabmap_ros::OdomInfo); + + // 1 RGBD + void rgbdCallback(const rtabmap_ros::RGBDImageConstPtr&); + DATA_SYNCS2(rgbdScan2d, rtabmap_ros::RGBDImage, sensor_msgs::LaserScan); + DATA_SYNCS2(rgbdScan3d, rtabmap_ros::RGBDImage, sensor_msgs::PointCloud2); + DATA_SYNCS2(rgbdInfo, rtabmap_ros::RGBDImage, rtabmap_ros::OdomInfo); + + // 1 RGBD + Odom + DATA_SYNCS2(rgbdOdom, nav_msgs::Odometry, rtabmap_ros::RGBDImage); + DATA_SYNCS3(rgbdOdomScan2d, nav_msgs::Odometry, rtabmap_ros::RGBDImage, sensor_msgs::LaserScan); + DATA_SYNCS3(rgbdOdomScan3d, nav_msgs::Odometry, rtabmap_ros::RGBDImage, sensor_msgs::PointCloud2); + DATA_SYNCS3(rgbdOdomInfo, nav_msgs::Odometry, rtabmap_ros::RGBDImage, rtabmap_ros::OdomInfo); + + // 1 RGBD + User Data + DATA_SYNCS2(rgbdData, rtabmap_ros::UserData, rtabmap_ros::RGBDImage); + DATA_SYNCS3(rgbdDataScan2d, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, sensor_msgs::LaserScan); + DATA_SYNCS3(rgbdDataScan3d, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, sensor_msgs::PointCloud2); + DATA_SYNCS3(rgbdDataInfo, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::OdomInfo); + + // 1 RGBD + Odom + User Data + DATA_SYNCS3(rgbdOdomData, nav_msgs::Odometry, rtabmap_ros::UserData, rtabmap_ros::RGBDImage); + DATA_SYNCS4(rgbdOdomDataScan2d, nav_msgs::Odometry, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, sensor_msgs::LaserScan); + DATA_SYNCS4(rgbdOdomDataScan3d, nav_msgs::Odometry, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, sensor_msgs::PointCloud2); + DATA_SYNCS4(rgbdOdomDataInfo, nav_msgs::Odometry, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::OdomInfo); + + // 2 RGBD + DATA_SYNCS2(rgbd2, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage); + DATA_SYNCS3(rgbd2Scan2d, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, sensor_msgs::LaserScan); + DATA_SYNCS3(rgbd2Scan3d, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, sensor_msgs::PointCloud2); + DATA_SYNCS3(rgbd2Info, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, rtabmap_ros::OdomInfo); + + // 2 RGBD + Odom + DATA_SYNCS3(rgbd2Odom, nav_msgs::Odometry, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage); + DATA_SYNCS4(rgbd2OdomScan2d, nav_msgs::Odometry, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, sensor_msgs::LaserScan); + DATA_SYNCS4(rgbd2OdomScan3d, nav_msgs::Odometry, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, sensor_msgs::PointCloud2); + DATA_SYNCS4(rgbd2OdomInfo, nav_msgs::Odometry, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, rtabmap_ros::OdomInfo); + + // 2 RGBD + User Data + DATA_SYNCS3(rgbd2Data, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage); + DATA_SYNCS4(rgbd2DataScan2d, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, sensor_msgs::LaserScan); + DATA_SYNCS4(rgbd2DataScan3d, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, sensor_msgs::PointCloud2); + DATA_SYNCS4(rgbd2DataInfo, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, rtabmap_ros::OdomInfo); + + // 2 RGBD + Odom + User Data + DATA_SYNCS4(rgbd2OdomData, nav_msgs::Odometry, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage); + DATA_SYNCS5(rgbd2OdomDataScan2d, nav_msgs::Odometry, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, sensor_msgs::LaserScan); + DATA_SYNCS5(rgbd2OdomDataScan3d, nav_msgs::Odometry, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, sensor_msgs::PointCloud2); + DATA_SYNCS5(rgbd2OdomDataInfo, nav_msgs::Odometry, rtabmap_ros::UserData, rtabmap_ros::RGBDImage, rtabmap_ros::RGBDImage, rtabmap_ros::OdomInfo); + +}; + +} /* namespace rtabmap_ros */ + +#endif /* INCLUDE_RTABMAP_ROS_COMMONDATASUBSCRIBER_H_ */ diff --git a/include/rtabmap_ros/CommonDataSubscriberDefines.h b/include/rtabmap_ros/CommonDataSubscriberDefines.h new file mode 100644 index 00000000..8d5da016 --- /dev/null +++ b/include/rtabmap_ros/CommonDataSubscriberDefines.h @@ -0,0 +1,194 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef INCLUDE_RTABMAP_ROS_COMMONDATASUBSCRIBERIMPL_H_ +#define INCLUDE_RTABMAP_ROS_COMMONDATASUBSCRIBERIMPL_H_ + + +#define DATA_SYNC2(PREFIX, SYNC_NAME, MSG0, MSG1) \ + typedef message_filters::sync_policies::SYNC_NAME##Time PREFIX##SYNC_NAME##SyncPolicy; \ + message_filters::Synchronizer * PREFIX##SYNC_NAME##Sync_; + +#define DATA_SYNCS2(PREFIX, MSG0, MSG1) \ + DATA_SYNC2(PREFIX, Approximate, MSG0, MSG1) \ + DATA_SYNC2(PREFIX, Exact, MSG0, MSG1) \ + void PREFIX##Callback(const MSG0##ConstPtr&, const MSG1##ConstPtr&); + +#define DATA_SYNC3(PREFIX, SYNC_NAME, MSG0, MSG1, MSG2) \ + typedef message_filters::sync_policies::SYNC_NAME##Time PREFIX##SYNC_NAME##SyncPolicy; \ + message_filters::Synchronizer * PREFIX##SYNC_NAME##Sync_; + +#define DATA_SYNCS3(PREFIX, MSG0, MSG1, MSG2) \ + DATA_SYNC3(PREFIX, Approximate, MSG0, MSG1, MSG2) \ + DATA_SYNC3(PREFIX, Exact, MSG0, MSG1, MSG2) \ + void PREFIX##Callback(const MSG0##ConstPtr&, const MSG1##ConstPtr&, const MSG2##ConstPtr&); \ + +#define DATA_SYNC4(PREFIX, SYNC_NAME, MSG0, MSG1, MSG2, MSG3) \ + typedef message_filters::sync_policies::SYNC_NAME##Time PREFIX##SYNC_NAME##SyncPolicy; \ + message_filters::Synchronizer * PREFIX##SYNC_NAME##Sync_; + +#define DATA_SYNCS4(PREFIX, MSG0, MSG1, MSG2, MSG3) \ + DATA_SYNC4(PREFIX, Approximate, MSG0, MSG1, MSG2, MSG3) \ + DATA_SYNC4(PREFIX, Exact, MSG0, MSG1, MSG2, MSG3) \ + void PREFIX##Callback(const MSG0##ConstPtr&, const MSG1##ConstPtr&, const MSG2##ConstPtr&, const MSG3##ConstPtr&); \ + +#define DATA_SYNC5(PREFIX, SYNC_NAME, MSG0, MSG1, MSG2, MSG3, MSG4) \ + typedef message_filters::sync_policies::SYNC_NAME##Time PREFIX##SYNC_NAME##SyncPolicy; \ + message_filters::Synchronizer * PREFIX##SYNC_NAME##Sync_; + +#define DATA_SYNCS5(PREFIX, MSG0, MSG1, MSG2, MSG3, MSG4) \ + DATA_SYNC5(PREFIX, Approximate, MSG0, MSG1, MSG2, MSG3, MSG4) \ + DATA_SYNC5(PREFIX, Exact, MSG0, MSG1, MSG2, MSG3, MSG4) \ + void PREFIX##Callback(const MSG0##ConstPtr&, const MSG1##ConstPtr&, const MSG2##ConstPtr&, const MSG3##ConstPtr&, const MSG4##ConstPtr&); \ + +#define DATA_SYNC6(PREFIX, SYNC_NAME, MSG0, MSG1, MSG2, MSG3, MSG4, MSG5) \ + typedef message_filters::sync_policies::SYNC_NAME##Time PREFIX##SYNC_NAME##SyncPolicy; \ + message_filters::Synchronizer * PREFIX##SYNC_NAME##Sync_; + +#define DATA_SYNCS6(PREFIX, MSG0, MSG1, MSG2, MSG3, MSG4, MSG5) \ + DATA_SYNC6(PREFIX, Approximate, MSG0, MSG1, MSG2, MSG3, MSG4, MSG5) \ + DATA_SYNC6(PREFIX, Exact, MSG0, MSG1, MSG2, MSG3, MSG4, MSG5) \ + void PREFIX##Callback(const MSG0##ConstPtr&, const MSG1##ConstPtr&, const MSG2##ConstPtr&, const MSG3##ConstPtr&, const MSG4##ConstPtr&, const MSG5##ConstPtr&); \ + +// Constructor +#define SYNC_INIT(PREFIX) \ + PREFIX##ApproximateSync_(0), \ + PREFIX##ExactSync_(0) + +// Destructor +#define SYNC_DEL(PREFIX) \ + if(PREFIX##ApproximateSync_) delete PREFIX##ApproximateSync_; \ + if(PREFIX##ExactSync_) delete PREFIX##ExactSync_; + +// Sync declarations +#define SYNC_DECL2(PREFIX, APPROX, QUEUE_SIZE, SUB0, SUB1) \ + if(APPROX) \ + { \ + PREFIX##ApproximateSync_ = new message_filters::Synchronizer( \ + PREFIX##ApproximateSyncPolicy(QUEUE_SIZE), SUB0, SUB1); \ + PREFIX##ApproximateSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2)); \ + } \ + else \ + { \ + PREFIX##ExactSync_ = new message_filters::Synchronizer( \ + PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1); \ + PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2)); \ + } \ + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s", \ + ros::this_node::getName().c_str(), \ + APPROX?"approx":"exact", \ + SUB0.getTopic().c_str(), \ + SUB1.getTopic().c_str()); + +#define SYNC_DECL3(PREFIX, APPROX, QUEUE_SIZE, SUB0, SUB1, SUB2) \ + if(APPROX) \ + { \ + PREFIX##ApproximateSync_ = new message_filters::Synchronizer( \ + PREFIX##ApproximateSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2); \ + PREFIX##ApproximateSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3)); \ + } \ + else \ + { \ + PREFIX##ExactSync_ = new message_filters::Synchronizer( \ + PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2); \ + PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3)); \ + } \ + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s", \ + ros::this_node::getName().c_str(), \ + APPROX?"approx":"exact", \ + SUB0.getTopic().c_str(), \ + SUB1.getTopic().c_str(), \ + SUB2.getTopic().c_str()); + +#define SYNC_DECL4(PREFIX, APPROX, QUEUE_SIZE, SUB0, SUB1, SUB2, SUB3) \ + if(APPROX) \ + { \ + PREFIX##ApproximateSync_ = new message_filters::Synchronizer( \ + PREFIX##ApproximateSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3); \ + PREFIX##ApproximateSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4)); \ + } \ + else \ + { \ + PREFIX##ExactSync_ = new message_filters::Synchronizer( \ + PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3); \ + PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4)); \ + } \ + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", \ + ros::this_node::getName().c_str(), \ + APPROX?"approx":"exact", \ + SUB0.getTopic().c_str(), \ + SUB1.getTopic().c_str(), \ + SUB2.getTopic().c_str(), \ + SUB3.getTopic().c_str()); + +#define SYNC_DECL5(PREFIX, APPROX, QUEUE_SIZE, SUB0, SUB1, SUB2, SUB3, SUB4) \ + if(APPROX) \ + { \ + PREFIX##ApproximateSync_ = new message_filters::Synchronizer( \ + PREFIX##ApproximateSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3, SUB4); \ + PREFIX##ApproximateSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4, _5)); \ + } \ + else \ + { \ + PREFIX##ExactSync_ = new message_filters::Synchronizer( \ + PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3, SUB4); \ + PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4, _5)); \ + } \ + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", \ + ros::this_node::getName().c_str(), \ + approxSync?"approx":"exact", \ + SUB0.getTopic().c_str(), \ + SUB1.getTopic().c_str(), \ + SUB2.getTopic().c_str(), \ + SUB3.getTopic().c_str(), \ + SUB4.getTopic().c_str()); + +#define SYNC_DECL6(PREFIX, APPROX, QUEUE_SIZE, SUB0, SUB1, SUB2, SUB3, SUB4, SUB5) \ + if(APPROX) \ + { \ + PREFIX##ApproximateSync_ = new message_filters::Synchronizer( \ + PREFIX##ApproximateSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3, SUB4, SUB5); \ + PREFIX##ApproximateSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4, _5, _6)); \ + } \ + else \ + { \ + PREFIX##ExactSync_ = new message_filters::Synchronizer( \ + PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3, SUB4, SUB5); \ + PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4, _5, _6)); \ + } \ + ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", \ + ros::this_node::getName().c_str(), \ + APPROX?"approx":"exact", \ + SUB0.getTopic().c_str(), \ + SUB1.getTopic().c_str(), \ + SUB2.getTopic().c_str(), \ + SUB3.getTopic().c_str(), \ + SUB4.getTopic().c_str(), \ + SUB5.getTopic().c_str()); + + +#endif /* INCLUDE_RTABMAP_ROS_COMMONDATASUBSCRIBERIMPL_H_ */ diff --git a/include/rtabmap_ros/CoreWrapper.h b/include/rtabmap_ros/CoreWrapper.h index f8a9935c..153cdbc4 100644 --- a/include/rtabmap_ros/CoreWrapper.h +++ b/include/rtabmap_ros/CoreWrapper.h @@ -38,11 +38,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include -#include -#include -#include -#include #include #include @@ -54,17 +49,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "rtabmap_ros/SetGoal.h" #include "rtabmap_ros/SetLabel.h" #include "rtabmap_ros/Goal.h" +#include "rtabmap_ros/CommonDataSubscriber.h" #include "MapsManager.h" -#include -#include -#include -#include - -#include -#include - #ifdef WITH_OCTOMAP_ROS #include #endif @@ -81,128 +69,48 @@ namespace rtabmap { class StereoDense; } -class CoreWrapper +namespace rtabmap_ros { + +class CoreWrapper : public CommonDataSubscriber { public: CoreWrapper(bool deleteDbOnStart, const rtabmap::ParametersMap & parameters); virtual ~CoreWrapper(); private: - void setupCallbacks( - bool subscribeDepth, - bool subscribeScan2d, - bool subscribeScan3d, - bool subscribeStereo, - int queueSize, - bool approxSync, - int depthCameras); - void defaultCallback(const sensor_msgs::ImageConstPtr & imageMsg); // no odom - bool commonOdomUpdate(const nav_msgs::OdometryConstPtr & odomMsg); - bool commonOdomTFUpdate(const ros::Time & stamp); // TF odom + bool odomUpdate(const nav_msgs::OdometryConstPtr & odomMsg); + bool odomTFUpdate(const ros::Time & stamp); // TF odom - void commonDepthCallback( - const std::string & odomFrameId, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + virtual void commonDepthCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, const sensor_msgs::LaserScanConstPtr& scanMsg, - const sensor_msgs::PointCloud2ConstPtr& scan3dMsg); - void commonDepthCallback( - const std::string & odomFrameId, - const std::vector & imageMsgs, - const std::vector & depthMsgs, - const std::vector & cameraInfoMsgs, - const sensor_msgs::LaserScanConstPtr& scanMsg, - const sensor_msgs::PointCloud2ConstPtr& scan3dMsg); - void commonStereoCallback( - const std::string & odomFrameId, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg); + void commonDepthCallbackImpl( + const std::string & odomFrameId, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, + const sensor_msgs::LaserScanConstPtr& scan2dMsg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg); + virtual void commonStereoCallback( + const nav_msgs::OdometryConstPtr & odomMsg, const sensor_msgs::ImageConstPtr& leftImageMsg, const sensor_msgs::ImageConstPtr& rightImageMsg, const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, const sensor_msgs::LaserScanConstPtr& scanMsg, - const sensor_msgs::PointCloud2ConstPtr& scan3dMsg); + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg); - // with odom msg - void depthCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg); - void depthScanCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg); - void depthScan3dCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg); - void stereoCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const nav_msgs::OdometryConstPtr & odomMsg); - void stereoScanCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg); - void stereoScan3dCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg); - void depth2Callback( - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& image1Msg, - const sensor_msgs::ImageConstPtr& imageDepth1Msg, - const sensor_msgs::CameraInfoConstPtr& camInfo1Msg, - const sensor_msgs::ImageConstPtr& image2Msg, - const sensor_msgs::ImageConstPtr& imageDept2hMsg, - const sensor_msgs::CameraInfoConstPtr& camInfo2Msg); - - // without odom, when TF is used for odom - void depthTFCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg); - void depthScanTFCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg); - void depthScan3dTFCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg); - void stereoTFCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg); - void stereoScanTFCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg); - void stereoScan3dTFCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg); + void defaultCallback(const sensor_msgs::ImageConstPtr & imageMsg); // no odom void goalCommonCallback(int id, const std::string & label, const rtabmap::Transform & pose, const ros::Time & stamp, double * planningTime = 0); void goalCallback(const geometry_msgs::PoseStampedConstPtr & msg); @@ -269,8 +177,8 @@ private: rtabmap::ParametersMap parameters_; std::string frameId_; - std::string mapFrameId_; std::string odomFrameId_; + std::string mapFrameId_; std::string groundTruthFrameId_; std::string groundTruthBaseFrameId_; std::string configPath_; @@ -302,179 +210,6 @@ private: ros::Publisher globalPathPub_; ros::Publisher localPathPub_; - // for loop closure detection only - image_transport::Subscriber defaultSub_; - - //for depth callback - std::vector imageSubs_; - std::vector imageDepthSubs_; - std::vector*> cameraInfoSubs_; - - //stereo callback - image_transport::SubscriberFilter imageRectLeft_; - image_transport::SubscriberFilter imageRectRight_; - message_filters::Subscriber cameraInfoLeft_; - message_filters::Subscriber cameraInfoRight_; - - message_filters::Subscriber odomSub_; - message_filters::Subscriber scanSub_; - message_filters::Subscriber scan3dSub_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::LaserScan> MyDepthScanSyncPolicy; - message_filters::Synchronizer * depthScanSync_; - typedef message_filters::sync_policies::ExactTime< - sensor_msgs::Image, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::LaserScan> MyDepthScanExactSyncPolicy; - message_filters::Synchronizer * depthScanExactSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::PointCloud2> MyDepthScan3dSyncPolicy; - message_filters::Synchronizer * depthScan3dSync_; - typedef message_filters::sync_policies::ExactTime< - sensor_msgs::Image, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::PointCloud2> MyDepthScan3dExactSyncPolicy; - message_filters::Synchronizer * depthScan3dExactSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthSyncPolicy; - message_filters::Synchronizer * depthSync_; - typedef message_filters::sync_policies::ExactTime< - sensor_msgs::Image, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthExactSyncPolicy; - message_filters::Synchronizer * depthExactSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo, - sensor_msgs::LaserScan, - nav_msgs::Odometry> MyStereoScanSyncPolicy; - message_filters::Synchronizer * stereoScanSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo, - sensor_msgs::PointCloud2, - nav_msgs::Odometry> MyStereoScan3dSyncPolicy; - message_filters::Synchronizer * stereoScan3dSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo, - nav_msgs::Odometry> MyStereoApproxSyncPolicy; - message_filters::Synchronizer * stereoApproxSync_; - - typedef message_filters::sync_policies::ExactTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo, - nav_msgs::Odometry> MyStereoExactSyncPolicy; - message_filters::Synchronizer * stereoExactSync_; - - typedef message_filters::sync_policies::ApproximateTime< - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepth2SyncPolicy; - message_filters::Synchronizer * depth2Sync_; - - // without odom, when TF is used for odom - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::LaserScan> MyDepthScanTFSyncPolicy; - message_filters::Synchronizer * depthScanTFSync_; - typedef message_filters::sync_policies::ExactTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::LaserScan> MyDepthScanTFExactSyncPolicy; - message_filters::Synchronizer * depthScanTFExactSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::PointCloud2> MyDepthScan3dTFSyncPolicy; - message_filters::Synchronizer * depthScan3dTFSync_; - typedef message_filters::sync_policies::ExactTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::PointCloud2> MyDepthScan3dTFExactSyncPolicy; - message_filters::Synchronizer * depthScan3dTFExactSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthTFSyncPolicy; - message_filters::Synchronizer * depthTFSync_; - typedef message_filters::sync_policies::ExactTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthTFExactSyncPolicy; - message_filters::Synchronizer * depthTFExactSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo, - sensor_msgs::LaserScan> MyStereoScanTFSyncPolicy; - message_filters::Synchronizer * stereoScanTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo, - sensor_msgs::PointCloud2> MyStereoScan3dTFSyncPolicy; - message_filters::Synchronizer * stereoScan3dTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoApproxTFSyncPolicy; - message_filters::Synchronizer * stereoApproxTFSync_; - - typedef message_filters::sync_policies::ExactTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoExactTFSyncPolicy; - message_filters::Synchronizer * stereoExactTFSync_; - tf2_ros::TransformBroadcaster tfBroadcaster_; tf::TransformListener tfListener_; @@ -508,6 +243,9 @@ private: boost::thread* transformThread_; + // for loop closure detection only + image_transport::Subscriber defaultSub_; + bool stereoToDepth_; bool odomSensorSync_; float rate_; @@ -516,5 +254,7 @@ private: ros::Time previousStamp_; }; +} + #endif /* COREWRAPPER_H_ */ diff --git a/include/rtabmap_ros/GuiWrapper.h b/include/rtabmap_ros/GuiWrapper.h index eb54dd6c..07483638 100644 --- a/include/rtabmap_ros/GuiWrapper.h +++ b/include/rtabmap_ros/GuiWrapper.h @@ -39,21 +39,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include -#include -#include -#include -#include #include #include -#include -#include -#include -#include - -#include -#include +#include namespace rtabmap { @@ -62,7 +51,9 @@ namespace rtabmap class QApplication; -class GuiWrapper : public UEventsHandler +namespace rtabmap_ros { + +class GuiWrapper : public UEventsHandler, public CommonDataSubscriber { public: GuiWrapper(int & argc, char** argv); @@ -76,32 +67,16 @@ private: void goalPathCallback(const rtabmap_ros::GoalConstPtr & goalMsg, const nav_msgs::PathConstPtr & pathMsg); void goalReachedCallback(const std_msgs::BoolConstPtr & value); - void setupCallbacks( - bool subscribeDepth, - bool subscribeLaserScan2d, - bool subscribeLaserScan3d, - bool subscribeOdomInfo, - bool subscribeStereo, - int queueSize, - int depthCameras); - - void commonDepthCallback( + virtual void commonDepthCallback( const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, const sensor_msgs::LaserScanConstPtr& scan2dMsg, const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg); - void commonDepthCallback( - const nav_msgs::OdometryConstPtr & odomMsg, - const std::vector & imageMsgs, - const std::vector & depthMsgs, - const std::vector & cameraInfoMsgs, - const sensor_msgs::LaserScanConstPtr& scan2dMsg, - const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, - const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg); - void commonStereoCallback( + virtual void commonStereoCallback( const nav_msgs::OdometryConstPtr & odomMsg, const sensor_msgs::ImageConstPtr& leftImageMsg, const sensor_msgs::ImageConstPtr& rightImageMsg, @@ -113,150 +88,6 @@ private: void defaultCallback(const nav_msgs::OdometryConstPtr & odomMsg); - // With odom msg - void depthCallback( - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg); - void depth2Callback( - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& image1Msg, - const sensor_msgs::ImageConstPtr& depth1Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo1Msg, - const sensor_msgs::ImageConstPtr& image2Msg, - const sensor_msgs::ImageConstPtr& depth2Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo2Msg); - void depthOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg); - void depthOdomInfo2Callback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& image1Msg, - const sensor_msgs::ImageConstPtr& depth1Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo1Msg, - const sensor_msgs::ImageConstPtr& image2Msg, - const sensor_msgs::ImageConstPtr& depth2Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo2Msg); - void depthScanCallback( - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg); - void depthScanOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg); - void depthScan3dCallback( - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg); - void depthScan3dOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg); - - void stereoScanCallback( - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void stereoScanOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void stereoScan3dCallback( - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void stereoScan3dOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void stereoOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void stereoCallback( - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - - // with TF - void depthTFCallback(const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs::CameraInfoConstPtr& camInfoMsg); - void depthOdomInfoTFCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg); - void depthScanTFCallback( - const sensor_msgs::LaserScanConstPtr& scanMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs:: CameraInfoConstPtr& camInfoMsg); - void depthScan3dTFCallback( - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& imageDepthMsg, - const sensor_msgs:: CameraInfoConstPtr& camInfoMsg); - - void stereoScanTFCallback( - const sensor_msgs::LaserScanConstPtr& scanMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void stereoScan3dTFCallback( - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void stereoOdomInfoTFCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void stereoTFCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg); - void processRequestedMap(const rtabmap_ros::MapData & map); private: @@ -280,18 +111,6 @@ private: ros::Subscriber goalReachedTopic_; ros::Subscriber defaultSub_; // odometry only - std::vector imageSubs_; - std::vector imageDepthSubs_; - std::vector* > cameraInfoSubs_; - message_filters::Subscriber odomSub_; - message_filters::Subscriber odomInfoSub_; - message_filters::Subscriber scanSub_; - message_filters::Subscriber scan3dSub_; - - image_transport::SubscriberFilter imageRectLeft_; - image_transport::SubscriberFilter imageRectRight_; - message_filters::Subscriber cameraInfoLeft_; - message_filters::Subscriber cameraInfoRight_; typedef message_filters::sync_policies::ExactTime< rtabmap_ros::Info, @@ -302,191 +121,8 @@ private: rtabmap_ros::Goal, nav_msgs::Path> MyGoalPathSyncPolicy; message_filters::Synchronizer * goalPathSync_; - - // with odom msg - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::LaserScan, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthScanSyncPolicy; - message_filters::Synchronizer * depthScanSync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - sensor_msgs::LaserScan, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthScanOdomInfoSyncPolicy; - message_filters::Synchronizer * depthScanOdomInfoSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::PointCloud2, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthScan3dSyncPolicy; - message_filters::Synchronizer * depthScan3dSync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - sensor_msgs::PointCloud2, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthScan3dOdomInfoSyncPolicy; - message_filters::Synchronizer * depthScan3dOdomInfoSync_; - - typedef message_filters::sync_policies::ApproximateTime< - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthSyncPolicy; - message_filters::Synchronizer * depthSync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthOdomInfoSyncPolicy; - message_filters::Synchronizer * depthOdomInfoSync_; - - typedef message_filters::sync_policies::ApproximateTime< - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoSyncPolicy; - message_filters::Synchronizer * stereoSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::LaserScan, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoScanSyncPolicy; - message_filters::Synchronizer * stereoScanSync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - sensor_msgs::LaserScan, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoScanOdomInfoSyncPolicy; - message_filters::Synchronizer * stereoScanOdomInfoSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::PointCloud2, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoScan3dSyncPolicy; - message_filters::Synchronizer * stereoScan3dSync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - sensor_msgs::PointCloud2, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoScan3dOdomInfoSyncPolicy; - message_filters::Synchronizer * stereoScan3dOdomInfoSync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoOdomInfoSyncPolicy; - message_filters::Synchronizer * stereoOdomInfoSync_; - - typedef message_filters::sync_policies::ApproximateTime< - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepth2SyncPolicy; - message_filters::Synchronizer * depth2Sync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - nav_msgs::Odometry, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthOdomInfo2SyncPolicy; - message_filters::Synchronizer * depthOdomInfo2Sync_; - - // with odom TF - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::LaserScan, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthScanTFSyncPolicy; - message_filters::Synchronizer * depthScanTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::PointCloud2, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthScan3dTFSyncPolicy; - message_filters::Synchronizer * depthScan3dTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthTFSyncPolicy; - message_filters::Synchronizer * depthTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo> MyDepthOdomInfoTFSyncPolicy; - message_filters::Synchronizer * depthOdomInfoTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoTFSyncPolicy; - message_filters::Synchronizer * stereoTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::LaserScan, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoScanTFSyncPolicy; - message_filters::Synchronizer * stereoScanTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - sensor_msgs::PointCloud2, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoScan3dTFSyncPolicy; - message_filters::Synchronizer * stereoScan3dTFSync_; - - typedef message_filters::sync_policies::ApproximateTime< - rtabmap_ros::OdomInfo, - sensor_msgs::Image, - sensor_msgs::Image, - sensor_msgs::CameraInfo, - sensor_msgs::CameraInfo> MyStereoOdomInfoTFSyncPolicy; - message_filters::Synchronizer * stereoOdomInfoTFSync_; }; +} + #endif /* GUIWRAPPER_H_ */ diff --git a/include/rtabmap_ros/MsgConversion.h b/include/rtabmap_ros/MsgConversion.h index b0eaaa93..64449815 100644 --- a/include/rtabmap_ros/MsgConversion.h +++ b/include/rtabmap_ros/MsgConversion.h @@ -38,6 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include @@ -55,6 +56,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include namespace rtabmap_ros { @@ -67,6 +70,9 @@ rtabmap::Transform transformFromGeometryMsg(const geometry_msgs::Transform & msg void transformToPoseMsg(const rtabmap::Transform & transform, geometry_msgs::Pose & msg); rtabmap::Transform transformFromPoseMsg(const geometry_msgs::Pose & msg); +void toCvCopy(const rtabmap_ros::RGBDImage & image, cv_bridge::CvImagePtr & rgb, cv_bridge::CvImagePtr & depth); +void toCvShare(const rtabmap_ros::RGBDImageConstPtr & image, cv_bridge::CvImageConstPtr & rgb, cv_bridge::CvImageConstPtr & depth); + // copy data void compressedMatToBytes(const cv::Mat & compressed, std::vector & bytes); cv::Mat compressedMatFromBytes(const std::vector & bytes, bool copy = true); @@ -140,6 +146,9 @@ void nodeInfoToROS(const rtabmap::Signature & signature, rtabmap_ros::NodeData & rtabmap::OdometryInfo odomInfoFromROS(const rtabmap_ros::OdomInfo & msg); void odomInfoToROS(const rtabmap::OdometryInfo & info, rtabmap_ros::OdomInfo & msg); +cv::Mat userDataFromROS(const rtabmap_ros::UserData & dataMsg); +void userDataToROS(const cv::Mat & data, rtabmap_ros::UserData & dataMsg, bool compress); + inline double timestampFromROS(const ros::Time & stamp) {return double(stamp.sec) + double(stamp.nsec)/1000000000.0;} // common stuff @@ -162,9 +171,9 @@ rtabmap::Transform getTransform( double waitForTransform); bool convertRGBDMsgs( - const std::vector & imageMsgs, - const std::vector & depthMsgs, - const std::vector & cameraInfoMsgs, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, const std::string & frameId, const std::string & odomFrameId, const ros::Time & odomStamp, diff --git a/launch/demo/demo_two_kinects.launch b/launch/demo/demo_two_kinects.launch index e08f430e..ee29615e 100644 --- a/launch/demo/demo_two_kinects.launch +++ b/launch/demo/demo_two_kinects.launch @@ -1,7 +1,7 @@ - + @@ -46,21 +46,32 @@ + + + + + + + + + + + + + + + + - - - - - - - + + - + @@ -75,65 +86,37 @@ - - + + + - - - - - - - + + + - + + - + - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - diff --git a/launch/tests/test_rgbd_image.launch b/launch/tests/test_rgbd_image.launch new file mode 100644 index 00000000..8d423622 --- /dev/null +++ b/launch/tests/test_rgbd_image.launch @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/msg/NodeData.msg b/msg/NodeData.msg index ed560c50..c817e062 100644 --- a/msg/NodeData.msg +++ b/msg/NodeData.msg @@ -24,6 +24,8 @@ float32[] fx float32[] fy float32[] cx float32[] cy +float32[] width +float32[] height float32 baseline # local transform (/base_link -> /camera_link) geometry_msgs/Transform[] localTransform diff --git a/msg/RGBDImage.msg b/msg/RGBDImage.msg new file mode 100644 index 00000000..f88bafdc --- /dev/null +++ b/msg/RGBDImage.msg @@ -0,0 +1,12 @@ + +Header header + +sensor_msgs/CameraInfo cameraInfo + +# Raw +sensor_msgs/Image rgb +sensor_msgs/Image depth + +# Compressed +sensor_msgs/CompressedImage rgbCompressed +sensor_msgs/CompressedImage depthCompressed \ No newline at end of file diff --git a/msg/UserData.msg b/msg/UserData.msg new file mode 100644 index 00000000..005d9b66 --- /dev/null +++ b/msg/UserData.msg @@ -0,0 +1,12 @@ + +Header header + +# OpenCV matrix containing the user data. A matrix of type CV_8UC1 +# with 1 row is considered to be compressed (with rtabmap::compressData() method). +# If you have one dimension unsigned 8 bits uncompressed data, make sure to transpose it +# (to have multiple rows instead of multiple columns) in order to be detected as +# not compressed. +uint32 rows +uint32 cols +uint32 type +uint8[] data \ No newline at end of file diff --git a/nodelet_plugins.xml b/nodelet_plugins.xml index 8c1781cd..3fad43f3 100644 --- a/nodelet_plugins.xml +++ b/nodelet_plugins.xml @@ -103,6 +103,14 @@ This is my nodelet. + + + + This is my nodelet. + + diff --git a/src/CommonDataSubscriber.cpp b/src/CommonDataSubscriber.cpp new file mode 100644 index 00000000..c3c054aa --- /dev/null +++ b/src/CommonDataSubscriber.cpp @@ -0,0 +1,365 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +namespace rtabmap_ros { + + +CommonDataSubscriber::CommonDataSubscriber() : + queueSize_(10), + subscribedToDepth_(true), + subscribedToStereo_(false), + subscribedToRGBD_(false), + subscribedToScan2d_(false), + subscribedToScan3d_(false), + subscribedToOdomInfo_(false), + + // RGB + Depth + SYNC_INIT(depth), + SYNC_INIT(depthScan2d), + SYNC_INIT(depthScan3d), + SYNC_INIT(depthInfo), + + // RGB + Depth + Odom + SYNC_INIT(depthOdom), + SYNC_INIT(depthOdomScan2d), + SYNC_INIT(depthOdomScan3d), + SYNC_INIT(depthOdomInfo), + + // RGB + Depth + User Data + SYNC_INIT(depthData), + SYNC_INIT(depthDataScan2d), + SYNC_INIT(depthDataScan3d), + SYNC_INIT(depthDataInfo), + + // RGB + Depth + Odom + User Data + SYNC_INIT(depthOdomData), + SYNC_INIT(depthOdomDataScan2d), + SYNC_INIT(depthOdomDataScan3d), + SYNC_INIT(depthOdomDataInfo), + + // Stereo + SYNC_INIT(stereo), + SYNC_INIT(stereoInfo), + + // Stereo + Odom + SYNC_INIT(stereoOdom), + SYNC_INIT(stereoOdomInfo), + + // 1 RGBD + SYNC_INIT(rgbdScan2d), + SYNC_INIT(rgbdScan3d), + SYNC_INIT(rgbdInfo), + + // 1 RGBD + Odom + SYNC_INIT(rgbdOdom), + SYNC_INIT(rgbdOdomScan2d), + SYNC_INIT(rgbdOdomScan3d), + SYNC_INIT(rgbdOdomInfo), + + // 1 RGBD + User Data + SYNC_INIT(rgbdData), + SYNC_INIT(rgbdDataScan2d), + SYNC_INIT(rgbdDataScan3d), + SYNC_INIT(rgbdDataInfo), + + // 1 RGBD + Odom + User Data + SYNC_INIT(rgbdOdomData), + SYNC_INIT(rgbdOdomDataScan2d), + SYNC_INIT(rgbdOdomDataScan3d), + SYNC_INIT(rgbdOdomDataInfo), + + // 2 RGBD + SYNC_INIT(rgbd2), + SYNC_INIT(rgbd2Scan2d), + SYNC_INIT(rgbd2Scan3d), + SYNC_INIT(rgbd2Info), + + // 2 RGBD + Odom + SYNC_INIT(rgbd2Odom), + SYNC_INIT(rgbd2OdomScan2d), + SYNC_INIT(rgbd2OdomScan3d), + SYNC_INIT(rgbd2OdomInfo), + + // 2 RGBD + User Data + SYNC_INIT(rgbd2Data), + SYNC_INIT(rgbd2DataScan2d), + SYNC_INIT(rgbd2DataScan3d), + SYNC_INIT(rgbd2DataInfo), + + // 2 RGBD + Odom + User Data + SYNC_INIT(rgbd2OdomData), + SYNC_INIT(rgbd2OdomDataScan2d), + SYNC_INIT(rgbd2OdomDataScan3d), + SYNC_INIT(rgbd2OdomDataInfo) + +{ + ros::NodeHandle pnh("~"); + + bool subscribeScan2d = false; + bool subscribeScan3d = false; + bool subscribeOdomInfo = false; + bool subscribeUserData = false; + int rgbdCameras = 1; + bool approxSync = true; + + // ROS related parameters (private) + pnh.param("subscribe_depth", subscribedToDepth_, subscribedToDepth_); + if(pnh.getParam("subscribe_laserScan", subscribeScan2d) && subscribeScan2d) + { + ROS_WARN("rtabmap: \"subscribe_laserScan\" parameter is deprecated, use \"subscribe_scan\" instead. The scan topic is still subscribed."); + } + pnh.param("subscribe_scan", subscribeScan2d, subscribeScan2d); + pnh.param("subscribe_scan_cloud", subscribeScan3d, subscribeScan3d); + pnh.param("subscribe_stereo", subscribedToStereo_, subscribedToStereo_); + pnh.param("subscribe_rgbd", subscribedToRGBD_, subscribedToRGBD_); + pnh.param("subscribe_odom_info", subscribeOdomInfo, subscribeOdomInfo); + pnh.param("subscribe_user_data", subscribeUserData, subscribeUserData); + if(subscribedToDepth_ && subscribedToStereo_) + { + ROS_WARN("rtabmap: Parameters subscribe_depth and subscribe_stereo cannot be true at the same time. Parameter subscribe_depth is set to false."); + subscribedToDepth_ = false; + } + if(subscribedToDepth_ && subscribedToRGBD_) + { + ROS_WARN("rtabmap: Parameters subscribe_depth and subscribe_rgbd cannot be true at the same time. Parameter subscribe_depth is set to false."); + subscribedToDepth_ = false; + } + if(subscribedToStereo_ && subscribedToRGBD_) + { + ROS_WARN("rtabmap: Parameters subscribe_stereo and subscribe_rgbd cannot be true at the same time. Parameter subscribe_stereo is set to false."); + subscribedToStereo_ = false; + } + if(subscribeScan2d && subscribeScan3d) + { + ROS_WARN("rtabmap: Parameters subscribe_scan and subscribe_scan_cloud cannot be true at the same time. Parameter subscribe_scan_cloud is set to false."); + subscribeScan3d = false; + } + if(subscribeScan2d || subscribeScan3d) + { + if(!subscribedToDepth_ && !subscribedToStereo_ && !subscribedToRGBD_) + { + ROS_WARN("When subscribing to laser scan, you should subscribe to depth, stereo or rgbd too. Subscribing to depth by default..."); + subscribedToDepth_ = true; + } + } + if(subscribedToStereo_) + { + approxSync = false; // default for stereo: exact sync + } + + std::string odomFrameId; + pnh.getParam("odom_frame_id", odomFrameId); + pnh.param("rgbd_cameras", rgbdCameras, rgbdCameras); + if(pnh.hasParam("depth_cameras")) + { + ROS_ERROR("\"depth_cameras\" parameter doesn't exist anymore. It is replaced by \"rgbd_cameras\" used when \"subscribe_rgbd\" is true."); + } + pnh.param("queue_size", queueSize_, queueSize_); + if(pnh.hasParam("stereo_approx_sync") && !pnh.hasParam("approx_sync")) + { + ROS_WARN("Parameter \"stereo_approx_sync\" has been renamed " + "to \"approx_sync\"! Your value is still copied to " + "corresponding parameter."); + pnh.param("stereo_approx_sync", approxSync, approxSync); + } + else + { + pnh.param("approx_sync", approxSync, approxSync); + } + + if(rgbdCameras <= 0 && subscribedToRGBD_) + { + rgbdCameras = 1; + } + + ROS_INFO("%s: queue_size = %d", ros::this_node::getName().c_str(), queueSize_); + ROS_INFO("%s: rgbd_cameras = %d", ros::this_node::getName().c_str(), rgbdCameras); + ROS_INFO("%s: approx_sync = %s", ros::this_node::getName().c_str(), approxSync?"true":"false"); + + bool subscribeOdom = odomFrameId.empty(); + if(subscribedToDepth_) + { + setupDepthCallbacks( + subscribeOdom, + subscribeUserData, + subscribeScan2d, + subscribeScan3d, + subscribeOdomInfo, + queueSize_, + approxSync); + } + else if(subscribedToStereo_) + { + setupStereoCallbacks( + subscribeOdom, + subscribeOdomInfo, + queueSize_, + approxSync); + } + else if(subscribedToRGBD_) + { + if(rgbdCameras == 2) + { + setupRGBD2Callbacks( + subscribeOdom, + subscribeUserData, + subscribeScan2d, + subscribeScan3d, + subscribeOdomInfo, + queueSize_, + approxSync); + } + else + { + setupRGBDCallbacks( + subscribeOdom, + subscribeUserData, + subscribeScan2d, + subscribeScan3d, + subscribeOdomInfo, + queueSize_, + approxSync); + } + } +} + +CommonDataSubscriber::~CommonDataSubscriber() +{ + // RGB + Depth + SYNC_DEL(depth); + SYNC_DEL(depthScan2d); + SYNC_DEL(depthScan3d); + SYNC_DEL(depthInfo); + + // RGB + Depth + Odom + SYNC_DEL(depthOdom); + SYNC_DEL(depthOdomScan2d); + SYNC_DEL(depthOdomScan3d); + SYNC_DEL(depthOdomInfo); + + // RGB + Depth + User Data + SYNC_DEL(depthData); + SYNC_DEL(depthDataScan2d); + SYNC_DEL(depthDataScan3d); + SYNC_DEL(depthDataInfo); + + // RGB + Depth + Odom + User Data + SYNC_DEL(depthOdomData); + SYNC_DEL(depthOdomDataScan2d); + SYNC_DEL(depthOdomDataScan3d); + SYNC_DEL(depthOdomDataInfo); + + // Stereo + SYNC_DEL(stereo); + SYNC_DEL(stereoInfo); + + // Stereo + Odom + SYNC_DEL(stereoOdom); + SYNC_DEL(stereoOdomInfo); + + // 1 RGBD + SYNC_DEL(rgbdScan2d); + SYNC_DEL(rgbdScan3d); + SYNC_DEL(rgbdInfo); + + // 1 RGBD + Odom + SYNC_DEL(rgbdOdom); + SYNC_DEL(rgbdOdomScan2d); + SYNC_DEL(rgbdOdomScan3d); + SYNC_DEL(rgbdOdomInfo); + + // 1 RGBD + User Data + SYNC_DEL(rgbdData); + SYNC_DEL(rgbdDataScan2d); + SYNC_DEL(rgbdDataScan3d); + SYNC_DEL(rgbdDataInfo); + + // 1 RGBD + Odom + User Data + SYNC_DEL(rgbdOdomData); + SYNC_DEL(rgbdOdomDataScan2d); + SYNC_DEL(rgbdOdomDataScan3d); + SYNC_DEL(rgbdOdomDataInfo); + + // 2 RGBD + SYNC_DEL(rgbd2); + SYNC_DEL(rgbd2Scan2d); + SYNC_DEL(rgbd2Scan3d); + SYNC_DEL(rgbd2Info); + + // 2 RGBD + Odom + SYNC_DEL(rgbd2Odom); + SYNC_DEL(rgbd2OdomScan2d); + SYNC_DEL(rgbd2OdomScan3d); + SYNC_DEL(rgbd2OdomInfo); + + // 2 RGBD + User Data + SYNC_DEL(rgbd2Data); + SYNC_DEL(rgbd2DataScan2d); + SYNC_DEL(rgbd2DataScan3d); + SYNC_DEL(rgbd2DataInfo); + + // 2 RGBD + Odom + User Data + SYNC_DEL(rgbd2OdomData); + SYNC_DEL(rgbd2OdomDataScan2d); + SYNC_DEL(rgbd2OdomDataScan3d); + SYNC_DEL(rgbd2OdomDataInfo); + + for(unsigned int i=0; i imageMsgs; + std::vector depthMsgs; + std::vector cameraInfoMsgs; + if(imageMsg.get()) + { + imageMsgs.push_back(imageMsg); + } + if(depthMsg.get()) + { + depthMsgs.push_back(depthMsg); + } + cameraInfoMsgs.push_back(cameraInfoMsg); + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} + +} /* namespace rtabmap_ros */ diff --git a/src/CoreNode.cpp b/src/CoreNode.cpp index eacf6972..03dd7c3c 100644 --- a/src/CoreNode.cpp +++ b/src/CoreNode.cpp @@ -89,7 +89,7 @@ int main(int argc, char** argv) rtabmap::ParametersMap parameters = rtabmap::Parameters::parseArguments(argc, argv); - CoreWrapper * rtabmap = new CoreWrapper(deleteDbOnStart, parameters); + rtabmap_ros::CoreWrapper * rtabmap = new rtabmap_ros::CoreWrapper(deleteDbOnStart, parameters); ROS_INFO("rtabmap %s started...", RTABMAP_VERSION); ros::spin(); diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 7f8ff81f..cb181dfa 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -73,6 +73,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace rtabmap; +namespace rtabmap_ros { + CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) : paused_(false), lastPose_(Transform::getIdentity()), @@ -81,8 +83,8 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) transVariance_(0), latestNodeWasReached_(false), frameId_("base_link"), - mapFrameId_("map"), odomFrameId_(""), + mapFrameId_("map"), groundTruthFrameId_(""), // e.g., "world" groundTruthBaseFrameId_(""), // e.g., "base_link_gt" configPath_(""), @@ -97,27 +99,6 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) scanCloudNormalK_(0), mapToOdom_(rtabmap::Transform::getIdentity()), mapsManager_(true), - depthSync_(0), - depthExactSync_(0), - depthScanSync_(0), - depthScanExactSync_(0), - depthScan3dSync_(0), - depthScan3dExactSync_(0), - stereoScanSync_(0), - stereoScan3dSync_(0), - stereoApproxSync_(0), - stereoExactSync_(0), - depth2Sync_(0), - depthTFSync_(0), - depthTFExactSync_(0), - depthScanTFSync_(0), - depthScanTFExactSync_(0), - depthScan3dTFSync_(0), - depthScan3dTFExactSync_(0), - stereoScanTFSync_(0), - stereoScan3dTFSync_(0), - stereoApproxTFSync_(0), - stereoExactTFSync_(0), transformThread_(0), stereoToDepth_(false), odomSensorSync_(false), @@ -130,70 +111,24 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) ros::NodeHandle nh; ros::NodeHandle pnh("~"); - bool subscribeScan2d = false; - bool subscribeScan3d = false; - bool subscribeDepth = true; - bool subscribeStereo = false; - int depthCameras = 1; - int queueSize = 10; bool publishTf = true; double tfDelay = 0.05; // 20 Hz double tfTolerance = 0.1; // 100 ms std::string tfPrefix = ""; - bool approxSync = true; - - // ROS related parameters (private) - pnh.param("subscribe_depth", subscribeDepth, subscribeDepth); - if(pnh.getParam("subscribe_laserScan", subscribeScan2d) && subscribeScan2d) - { - ROS_WARN("rtabmap: \"subscribe_laserScan\" parameter is deprecated, use \"subscribe_scan\" instead. The scan topic is still subscribed."); - } - pnh.param("subscribe_scan", subscribeScan2d, subscribeScan2d); - pnh.param("subscribe_scan_cloud", subscribeScan3d, subscribeScan3d); - pnh.param("subscribe_stereo", subscribeStereo, subscribeStereo); - if(subscribeDepth && subscribeStereo) - { - ROS_WARN("rtabmap: Parameters subscribe_depth and subscribe_stereo cannot be true at the same time. Parameter subscribe_depth is set to false."); - subscribeDepth = false; - } - if(subscribeScan2d && subscribeScan3d) - { - ROS_WARN("rtabmap: Parameters subscribe_scan and subscribe_scan_cloud cannot be true at the same time. Parameter subscribe_scan_cloud is set to false."); - subscribeScan3d = false; - } - if(subscribeScan2d || subscribeScan3d) - { - if(!subscribeDepth && !subscribeStereo) - { - ROS_WARN("When subscribing to laser scan, you should subscribe to depth or stereo too. Subscribing to depth by default..."); - subscribeDepth = true; - } - } - if(subscribeStereo) - { - approxSync = false; // default for stereo: exact sync - } pnh.param("config_path", configPath_, configPath_); pnh.param("database_path", databasePath_, databasePath_); pnh.param("frame_id", frameId_, frameId_); - pnh.param("map_frame_id", mapFrameId_, mapFrameId_); pnh.param("odom_frame_id", odomFrameId_, odomFrameId_); // set to use odom from TF + pnh.param("map_frame_id", mapFrameId_, mapFrameId_); pnh.param("ground_truth_frame_id", groundTruthFrameId_, groundTruthFrameId_); pnh.param("ground_truth_base_frame_id", groundTruthBaseFrameId_, frameId_); - pnh.param("depth_cameras", depthCameras, depthCameras); - pnh.param("queue_size", queueSize, queueSize); - if(pnh.hasParam("stereo_approx_sync") && !pnh.hasParam("approx_sync")) + if(pnh.hasParam("depth_cameras") && !pnh.hasParam("depth_cameras")) { - ROS_WARN("Parameter \"stereo_approx_sync\" has been renamed " - "to \"approx_sync\"! Your value is still copied to " - "corresponding parameter."); - pnh.param("stereo_approx_sync", approxSync, approxSync); - } - else - { - pnh.param("approx_sync", approxSync, approxSync); + ROS_ERROR("\"depth_cameras\" parameter doesn't exist " + "anymore! It is replaced by \"rgbd_cameras\" parameter " + "used when \"subscribe_rgbd\" is true"); } pnh.param("publish_tf", publishTf, publishTf); @@ -242,11 +177,6 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) // keep worldFrameId_ without prefix as it should be global } - if(depthCameras <= 0 && subscribeDepth) - { - depthCameras = 1; - } - ROS_INFO("rtabmap: frame_id = %s", frameId_.c_str()); if(!odomFrameId_.empty()) { @@ -259,11 +189,8 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) groundTruthBaseFrameId_.c_str()); } ROS_INFO("rtabmap: map_frame_id = %s", mapFrameId_.c_str()); - ROS_INFO("rtabmap: queue_size = %d", queueSize); ROS_INFO("rtabmap: tf_delay = %f", tfDelay); ROS_INFO("rtabmap: tf_tolerance = %f", tfTolerance); - ROS_INFO("rtabmap: depth_cameras = %d", depthCameras); - ROS_INFO("rtabmap: approx_sync = %s", approxSync?"true":"false"); ROS_INFO("rtabmap: odom_sensor_sync = %s", odomSensorSync_?"true":"false"); infoPub_ = nh.advertise("info", 1); @@ -380,7 +307,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) // Backward compatibility (MapsManager) mapsManager_.backwardCompatibilityParameters(parameters_); - if((subscribeScan2d || subscribeScan3d) && parameters_.find(Parameters::kGridFromDepth()) == parameters_.end()) + if((this->isSubscribedToScan2d() || this->isSubscribedToScan3d()) && parameters_.find(Parameters::kGridFromDepth()) == parameters_.end()) { ROS_WARN("Setting \"%s\" parameter to false (default true) as \"subscribe_scan\" or \"subscribe_scan_cloud\" is " "true. The occupancy grid map will be constructed from " @@ -419,10 +346,10 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) if(isRGBD) { // RGBD SLAM - if(!subscribeDepth && !subscribeStereo) + if(!this->isSubscribedToDepth() && !this->isSubscribedToStereo() && !this->isSubscribedToRGBD()) { - ROS_WARN("ROS param subscribe_depth and subscribe_stereo are false, but RTAB-Map " - "parameter \"%s\" is true! Please set subscribe_depth or subscribe_stereo " + ROS_WARN("ROS param subscribe_depth, subscribe_stereo and subscribe_rgbd are false, but RTAB-Map " + "parameter \"%s\" is true! Please set subscribe_depth, subscribe_stereo or subscribe_rgbd " "to true to use rtabmap node for RGB-D SLAM, or set \"%s\" to false for loop closure " "detection on images-only.", Parameters::kRGBDEnabled().c_str(), Parameters::kRGBDEnabled().c_str()); } @@ -451,7 +378,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) } mapsManager_.setParameters(parameters_); - if(subscribeStereo) + if(this->isSubscribedToStereo()) { ROS_INFO("rtabmap: stereo_to_depth = %s", stereoToDepth_?"true":"false"); } @@ -494,8 +421,6 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) setLogWarnSrv_ = pnh.advertiseService("log_warning", &CoreWrapper::setLogWarn, this); setLogErrorSrv_ = pnh.advertiseService("log_error", &CoreWrapper::setLogError, this); - setupCallbacks(subscribeDepth, subscribeScan2d, subscribeScan3d, subscribeStereo, queueSize, approxSync, depthCameras); - int optimizeIterations = 0; Parameters::parse(parameters_, Parameters::kOptimizerIterations(), optimizeIterations); if(publishTf && optimizeIterations != 0) @@ -507,6 +432,17 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) UWARN("Graph optimization is disabled (%s=0), the tf between frame \"%s\" and odometry frame will not be published. You can safely ignore this warning if you are using map_optimizer node.", Parameters::kOptimizerIterations().c_str(), mapFrameId_.c_str()); } + + if(!this->isDataSubscribed()) + { + ros::NodeHandle rgb_nh(nh, "rgb"); + ros::NodeHandle rgb_pnh(pnh, "rgb"); + image_transport::ImageTransport rgb_it(rgb_nh); + image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); + defaultSub_ = rgb_it.subscribe("image", 1, &CoreWrapper::defaultCallback, this); + + ROS_INFO("\n%s subscribed to:\n %s", ros::this_node::getName().c_str(), defaultSub_.getTopic().c_str()); + } } CoreWrapper::~CoreWrapper() @@ -517,65 +453,6 @@ CoreWrapper::~CoreWrapper() delete transformThread_; } - if(depthSync_) - delete depthSync_; - if(depthExactSync_) - delete depthExactSync_; - if(depthScanSync_) - delete depthScanSync_; - if(depthScanExactSync_) - delete depthScanExactSync_; - if(depthScan3dSync_) - delete depthScan3dSync_; - if(depthScan3dExactSync_) - delete depthScan3dExactSync_; - if(stereoScanSync_) - delete stereoScanSync_; - if(stereoScan3dSync_) - delete stereoScan3dSync_; - if(stereoApproxSync_) - delete stereoApproxSync_; - if(stereoExactSync_) - delete stereoExactSync_; - if(depth2Sync_) - delete depth2Sync_; - if(depthTFSync_) - delete depthTFSync_; - if(depthTFExactSync_) - delete depthTFExactSync_; - if(depthScanTFSync_) - delete depthScanTFSync_; - if(depthScanTFExactSync_) - delete depthScanTFExactSync_; - if(depthScan3dTFSync_) - delete depthScan3dTFSync_; - if(depthScan3dTFExactSync_) - delete depthScan3dTFExactSync_; - if(stereoScanTFSync_) - delete stereoScanTFSync_; - if(stereoScan3dTFSync_) - delete stereoScan3dTFSync_; - if(stereoApproxTFSync_) - delete stereoApproxTFSync_; - if(stereoExactTFSync_) - delete stereoExactTFSync_; - - for(unsigned int i=0; isaveParameters(configPath_); ros::NodeHandle nh; @@ -704,7 +581,7 @@ void CoreWrapper::defaultCallback(const sensor_msgs::ImageConstPtr & imageMsg) } } -bool CoreWrapper::commonOdomUpdate(const nav_msgs::OdometryConstPtr & odomMsg) +bool CoreWrapper::odomUpdate(const nav_msgs::OdometryConstPtr & odomMsg) { if(!paused_) { @@ -769,7 +646,7 @@ bool CoreWrapper::commonOdomUpdate(const nav_msgs::OdometryConstPtr & odomMsg) return false; } -bool CoreWrapper::commonOdomTFUpdate(const ros::Time & stamp) +bool CoreWrapper::odomTFUpdate(const ros::Time & stamp) { if(!paused_) { @@ -824,28 +701,64 @@ bool CoreWrapper::commonOdomTFUpdate(const ros::Time & stamp) } void CoreWrapper::commonDepthCallback( - const std::string & odomFrameId, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg, - const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) -{ - std::vector imageMsgs; - std::vector depthMsgs; - std::vector cameraInfoMsgs; - imageMsgs.push_back(imageMsg); - depthMsgs.push_back(depthMsg); - cameraInfoMsgs.push_back(cameraInfoMsg); - commonDepthCallback(odomFrameId, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg); -} -void CoreWrapper::commonDepthCallback( - const std::string & odomFrameId, - const std::vector & imageMsgs, - const std::vector & depthMsgs, - const std::vector & cameraInfoMsgs, + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, const sensor_msgs::LaserScanConstPtr& scan2dMsg, - const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + std::string odomFrameId = odomFrameId_; + if(odomMsg.get()) + { + odomFrameId = odomMsg->header.frame_id; + if(!odomUpdate(odomMsg)) + { + return; + } + } + else if(scan2dMsg.get()) + { + if(!odomTFUpdate(scan2dMsg->header.stamp)) + { + return; + } + } + else if(scan3dMsg.get()) + { + if(!odomTFUpdate(scan3dMsg->header.stamp)) + { + return; + } + } + else + { + if(imageMsgs.size() == 0 || imageMsgs[0].get() == 0 || !odomTFUpdate(imageMsgs[0]->header.stamp)) + { + return; + } + } + commonDepthCallbackImpl(odomFrameId, + userDataMsg, + imageMsgs, + depthMsgs, + cameraInfoMsgs, + scan2dMsg, + scan3dMsg, + odomInfoMsg); +} + +void CoreWrapper::commonDepthCallbackImpl( + const std::string & odomFrameId, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, + const sensor_msgs::LaserScanConstPtr& scan2dMsg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) { cv::Mat rgb; cv::Mat depth; @@ -943,6 +856,11 @@ void CoreWrapper::commonDepthCallback( groundTruthPose = rtabmap_ros::getTransform(groundTruthFrameId_, groundTruthBaseFrameId_, lastPoseStamp_, tfListener_, waitForTransform_?waitForTransformDuration_:0.0); } + cv::Mat userData; + if(userDataMsg.get()) + { + userData = rtabmap_ros::userDataFromROS(*userDataMsg); + } SensorData data(scan, LaserScanInfo( scan2dMsg.get() != 0?(int)scan2dMsg->ranges.size():(genScan_?genMaxScanPts:scan3dMsg.get() != 0?scanCloudMaxPoints_:0), @@ -952,7 +870,8 @@ void CoreWrapper::commonDepthCallback( depth, cameraModels, lastPoseIntermediate_?-1:imageMsgs[0]->header.seq, - rtabmap_ros::timestampFromROS(lastPoseStamp_)); + rtabmap_ros::timestampFromROS(lastPoseStamp_), + userData); data.setGroundTruth(groundTruthPose); process(lastPoseStamp_, @@ -966,14 +885,45 @@ void CoreWrapper::commonDepthCallback( } void CoreWrapper::commonStereoCallback( - const std::string & odomFrameId, + const nav_msgs::OdometryConstPtr & odomMsg, const sensor_msgs::ImageConstPtr& leftImageMsg, const sensor_msgs::ImageConstPtr& rightImageMsg, const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, const sensor_msgs::LaserScanConstPtr& scan2dMsg, - const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) { + std::string odomFrameId = odomFrameId_; + if(odomMsg.get()) + { + odomFrameId = odomMsg->header.frame_id; + if(!odomUpdate(odomMsg)) + { + return; + } + } + else if(scan2dMsg.get()) + { + if(!odomTFUpdate(scan2dMsg->header.stamp)) + { + return; + } + } + else if(scan3dMsg.get()) + { + if(!odomTFUpdate(scan3dMsg->header.stamp)) + { + return; + } + } + else + { + if(leftImageMsg.get() == 0 || !odomTFUpdate(leftImageMsg->header.stamp)) + { + return; + } + } cv::Mat left; cv::Mat right; @@ -1021,20 +971,26 @@ void CoreWrapper::commonStereoCallback( UASSERT(depth.type() == CV_16UC1 || depth.type() == CV_32FC1); // move to common depth callback - cv_bridge::CvImage imgDepth; + cv_bridge::CvImagePtr imgDepth(new cv_bridge::CvImage); if(depth.type() == CV_16UC1) { - imgDepth.encoding = sensor_msgs::image_encodings::TYPE_16UC1; + imgDepth->encoding = sensor_msgs::image_encodings::TYPE_16UC1; } else // CV_32FC1 { - imgDepth.encoding = sensor_msgs::image_encodings::TYPE_32FC1; + imgDepth->encoding = sensor_msgs::image_encodings::TYPE_32FC1; } - imgDepth.image = depth; - sensor_msgs::ImagePtr depthMsg = imgDepth.toImageMsg(); - depthMsg->header = leftImageMsg->header; + imgDepth->image = depth; + imgDepth->header = leftImageMsg->header; + std::vector rgbImages(1); + std::vector depthImages(1); + std::vector cameraInfos(1); + rgbImages[0] = cv_bridge::toCvShare(leftImageMsg); + depthImages[0] = imgDepth; + cameraInfos[0] = *leftCamInfoMsg; - commonDepthCallback(odomFrameId, leftImageMsg, depthMsg, leftCamInfoMsg, scan2dMsg, scan3dMsg); + commonDepthCallbackImpl(odomFrameId, rtabmap_ros::UserDataConstPtr(), rgbImages, depthImages, cameraInfos, scan2dMsg, scan3dMsg, odomInfoMsg); + return; } cv::Mat scan; @@ -1109,210 +1065,6 @@ void CoreWrapper::commonStereoCallback( transVariance_ = 0; } -void CoreWrapper::depthCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - if(!commonOdomUpdate(odomMsg)) - { - return; - } - - sensor_msgs::LaserScanConstPtr scanMsg; // Null - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null - commonDepthCallback(odomMsg->header.frame_id, imageMsg, depthMsg, cameraInfoMsg, scanMsg, scan3dMsg); -} -void CoreWrapper::depthScanCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg) -{ - if(!commonOdomUpdate(odomMsg)) - { - return; - } - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null - commonDepthCallback(odomMsg->header.frame_id, imageMsg, depthMsg, cameraInfoMsg, scanMsg, scan3dMsg); -} -void CoreWrapper::depthScan3dCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg) -{ - if(!commonOdomUpdate(odomMsg)) - { - return; - } - sensor_msgs::LaserScanConstPtr scan2dMsg; // Null - commonDepthCallback(odomMsg->header.frame_id, imageMsg, depthMsg, cameraInfoMsg, scan2dMsg, scanMsg); -} -void CoreWrapper::stereoCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const nav_msgs::OdometryConstPtr & odomMsg) -{ - if(!commonOdomUpdate(odomMsg)) - { - return; - } - - sensor_msgs::LaserScanConstPtr scanMsg; // Null - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null - commonStereoCallback(odomMsg->header.frame_id, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scanMsg, scan3dMsg); -} -void CoreWrapper::stereoScanCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg) -{ - if(!commonOdomUpdate(odomMsg)) - { - return; - } - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null - commonStereoCallback(odomMsg->header.frame_id, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scanMsg, scan3dMsg); -} -void CoreWrapper::stereoScan3dCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg) -{ - if(!commonOdomUpdate(odomMsg)) - { - return; - } - sensor_msgs::LaserScanConstPtr scan2dMsg; // Null - commonStereoCallback(odomMsg->header.frame_id, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scan2dMsg, scanMsg); -} - -void CoreWrapper::depth2Callback( - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& image1Msg, - const sensor_msgs::ImageConstPtr& depth1Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo1Msg, - const sensor_msgs::ImageConstPtr& image2Msg, - const sensor_msgs::ImageConstPtr& depth2Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo2Msg) -{ - if(!commonOdomUpdate(odomMsg)) - { - return; - } - - std::vector imageMsgs; - std::vector depthMsgs; - std::vector cameraInfoMsgs; - imageMsgs.push_back(image1Msg); - imageMsgs.push_back(image2Msg); - depthMsgs.push_back(depth1Msg); - depthMsgs.push_back(depth2Msg); - cameraInfoMsgs.push_back(cameraInfo1Msg); - cameraInfoMsgs.push_back(cameraInfo2Msg); - - sensor_msgs::LaserScanConstPtr scanMsg; // Null - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null - commonDepthCallback(odomMsg->header.frame_id, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg); -} - - -void CoreWrapper::depthTFCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - if(!commonOdomTFUpdate(depthMsg->header.stamp)) - { - return; - } - sensor_msgs::LaserScanConstPtr scanMsg; // Null - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null - commonDepthCallback(odomFrameId_, imageMsg, depthMsg, cameraInfoMsg, scanMsg, scan3dMsg); -} -void CoreWrapper::depthScanTFCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg) -{ - if(!commonOdomTFUpdate(scanMsg->header.stamp)) - { - return; - } - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null - commonDepthCallback(odomFrameId_, imageMsg, depthMsg, cameraInfoMsg, scanMsg, scan3dMsg); -} -void CoreWrapper::depthScan3dTFCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg) -{ - if(!commonOdomTFUpdate(scanMsg->header.stamp)) - { - return; - } - sensor_msgs::LaserScanConstPtr scan2dMsg; // Null - commonDepthCallback(odomFrameId_, imageMsg, depthMsg, cameraInfoMsg, scan2dMsg, scanMsg); -} -void CoreWrapper::stereoTFCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg) -{ - if(!commonOdomTFUpdate(leftImageMsg->header.stamp)) - { - return; - } - - sensor_msgs::LaserScanConstPtr scanMsg; // null - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null - commonStereoCallback(odomFrameId_, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scanMsg, scan3dMsg); -} -void CoreWrapper::stereoScanTFCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg) -{ - if(!commonOdomTFUpdate(leftImageMsg->header.stamp)) - { - return; - } - sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null - commonStereoCallback(odomFrameId_, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scanMsg, scan3dMsg); -} - -void CoreWrapper::stereoScan3dTFCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg) -{ - if(!commonOdomTFUpdate(leftImageMsg->header.stamp)) - { - return; - } - sensor_msgs::LaserScanConstPtr scan2dMsg; // null - commonStereoCallback(odomFrameId_, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scan2dMsg, scanMsg); -} - void CoreWrapper::process( const ros::Time & stamp, const SensorData & data, @@ -1790,7 +1542,7 @@ bool CoreWrapper::getMapDataCallback(rtabmap_ros::GetMap::Request& req, rtabmap_ req.graphOnly?"true":"false"); std::map signatures; std::map poses; - std::multimap constraints; + std::multimap constraints; if(req.graphOnly) { @@ -1899,7 +1651,7 @@ bool CoreWrapper::publishMapCallback(rtabmap_ros::PublishMap::Request& req, rtab (req.graphOnly && labelsPub_.getNumSubscribers())) { std::map poses; - std::multimap constraints; + std::multimap constraints; std::map signatures; if(req.graphOnly) @@ -2456,470 +2208,4 @@ bool CoreWrapper::octomapFullCallback( #endif #endif -/** - * exclusive callbacks: - * image - * image + depth - * image + scan - * image + depth + scan - * Which callback is called depends on - * the combination of these options: - * bool subscribe_laserScan - * bool subscribe_depth - */ -void CoreWrapper::setupCallbacks( - bool subscribeDepth, - bool subscribeScan2d, - bool subscribeScan3d, - bool subscribeStereo, - int queueSize, - bool approxSync, - int depthCameras) -{ - ros::NodeHandle nh; // public - ros::NodeHandle pnh("~"); // private - - if(subscribeDepth) - { - UASSERT(depthCameras >= 1 && depthCameras <= 2); - UASSERT_MSG(depthCameras == 1 || !(subscribeScan2d || subscribeScan3d || !odomFrameId_.empty()), "Not yet supported!"); - - imageSubs_.resize(depthCameras); - imageDepthSubs_.resize(depthCameras); - cameraInfoSubs_.resize(depthCameras); - for(int i=0; i1) - { - rgbPrefix += uNumber2Str(i); - depthPrefix += uNumber2Str(i); - } - ros::NodeHandle rgb_nh(nh, rgbPrefix); - ros::NodeHandle depth_nh(nh, depthPrefix); - ros::NodeHandle rgb_pnh(pnh, rgbPrefix); - ros::NodeHandle depth_pnh(pnh, depthPrefix); - image_transport::ImageTransport rgb_it(rgb_nh); - image_transport::ImageTransport depth_it(depth_nh); - image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); - image_transport::TransportHints hintsDepth("raw", ros::TransportHints(), depth_pnh); - - imageSubs_[i] = new image_transport::SubscriberFilter; - imageDepthSubs_[i] = new image_transport::SubscriberFilter; - cameraInfoSubs_[i] = new message_filters::Subscriber; - imageSubs_[i]->subscribe(rgb_it, rgb_nh.resolveName("image"), 1, hintsRgb); - imageDepthSubs_[i]->subscribe(depth_it, depth_nh.resolveName("image"), 1, hintsDepth); - cameraInfoSubs_[i]->subscribe(rgb_nh, "camera_info", 1); - } - - if(odomFrameId_.empty()) - { - odomSub_.subscribe(nh, "odom", 1); - if(subscribeScan2d) - { - scanSub_.subscribe(nh, "scan", 1); - if(approxSync) - { - depthScanSync_ = new message_filters::Synchronizer( - MyDepthScanSyncPolicy(queueSize), - *imageSubs_[0], - odomSub_, - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scanSub_); - depthScanSync_->registerCallback(boost::bind(&CoreWrapper::depthScanCallback, this, _1, _2, _3, _4, _5)); - } - else - { - depthScanExactSync_ = new message_filters::Synchronizer( - MyDepthScanExactSyncPolicy(queueSize), - *imageSubs_[0], - odomSub_, - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scanSub_); - depthScanExactSync_->registerCallback(boost::bind(&CoreWrapper::depthScanCallback, this, _1, _2, _3, _4, _5)); - } - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str(), - scanSub_.getTopic().c_str()); - } - else if(subscribeScan3d) - { - scan3dSub_.subscribe(nh, "scan_cloud", 1); - if(approxSync) - { - depthScan3dSync_ = new message_filters::Synchronizer( - MyDepthScan3dSyncPolicy(queueSize), - *imageSubs_[0], - odomSub_, - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scan3dSub_); - depthScan3dSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dCallback, this, _1, _2, _3, _4, _5)); - } - else - { - depthScan3dExactSync_ = new message_filters::Synchronizer( - MyDepthScan3dExactSyncPolicy(queueSize), - *imageSubs_[0], - odomSub_, - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scan3dSub_); - depthScan3dExactSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dCallback, this, _1, _2, _3, _4, _5)); - } - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str(), - scan3dSub_.getTopic().c_str()); - } - else //!subscribeLaserScan - { - if(depthCameras > 1) - { - depth2Sync_ = new message_filters::Synchronizer( - MyDepth2SyncPolicy(queueSize), - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - *imageSubs_[1], - *imageDepthSubs_[1], - *cameraInfoSubs_[1]); - depth2Sync_->registerCallback(boost::bind(&CoreWrapper::depth2Callback, this, _1, _2, _3, _4, _5, _6, _7)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - imageSubs_[1]->getTopic().c_str(), - imageDepthSubs_[1]->getTopic().c_str(), - cameraInfoSubs_[1]->getTopic().c_str(), - odomSub_.getTopic().c_str()); - } - else - { - if(approxSync) - { - depthSync_ = new message_filters::Synchronizer( - MyDepthSyncPolicy(queueSize), - *imageSubs_[0], - odomSub_, - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthSync_->registerCallback(boost::bind(&CoreWrapper::depthCallback, this, _1, _2, _3, _4)); - } - else - { - depthExactSync_ = new message_filters::Synchronizer( - MyDepthExactSyncPolicy(queueSize), - *imageSubs_[0], - odomSub_, - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthExactSync_->registerCallback(boost::bind(&CoreWrapper::depthCallback, this, _1, _2, _3, _4)); - } - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str()); - } - } - } - else - { - // use odom from TF, so subscribe to sensors only - if(subscribeScan2d) - { - scanSub_.subscribe(nh, "scan", 1); - if(approxSync) - { - depthScanTFSync_ = new message_filters::Synchronizer( - MyDepthScanTFSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scanSub_); - depthScanTFSync_->registerCallback(boost::bind(&CoreWrapper::depthScanTFCallback, this, _1, _2, _3, _4)); - } - else - { - depthScanTFExactSync_ = new message_filters::Synchronizer( - MyDepthScanTFExactSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scanSub_); - depthScanTFExactSync_->registerCallback(boost::bind(&CoreWrapper::depthScanTFCallback, this, _1, _2, _3, _4)); - } - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - scanSub_.getTopic().c_str()); - } - else if(subscribeScan3d) - { - scan3dSub_.subscribe(nh, "scan_cloud", 1); - if(approxSync) - { - depthScan3dTFSync_ = new message_filters::Synchronizer( - MyDepthScan3dTFSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scan3dSub_); - depthScan3dTFSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dTFCallback, this, _1, _2, _3, _4)); - } - else - { - depthScan3dTFExactSync_ = new message_filters::Synchronizer( - MyDepthScan3dTFExactSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - scan3dSub_); - depthScan3dTFExactSync_->registerCallback(boost::bind(&CoreWrapper::depthScan3dTFCallback, this, _1, _2, _3, _4)); - } - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - scan3dSub_.getTopic().c_str()); - } - else //!subscribeLaserScan - { - if(approxSync) - { - depthTFSync_ = new message_filters::Synchronizer( - MyDepthTFSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthTFSync_->registerCallback(boost::bind(&CoreWrapper::depthTFCallback, this, _1, _2, _3)); - } - else - { - depthTFExactSync_ = new message_filters::Synchronizer( - MyDepthTFExactSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthTFExactSync_->registerCallback(boost::bind(&CoreWrapper::depthTFCallback, this, _1, _2, _3)); - } - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str()); - } - } - } - else if(subscribeStereo) - { - ros::NodeHandle left_nh(nh, "left"); - ros::NodeHandle right_nh(nh, "right"); - ros::NodeHandle left_pnh(pnh, "left"); - ros::NodeHandle right_pnh(pnh, "right"); - image_transport::ImageTransport left_it(left_nh); - image_transport::ImageTransport right_it(right_nh); - image_transport::TransportHints hintsLeft("raw", ros::TransportHints(), left_pnh); - image_transport::TransportHints hintsRight("raw", ros::TransportHints(), right_pnh); - - imageRectLeft_.subscribe(left_it, left_nh.resolveName("image_rect"), 1, hintsLeft); - imageRectRight_.subscribe(right_it, right_nh.resolveName("image_rect"), 1, hintsRight); - cameraInfoLeft_.subscribe(left_nh, "camera_info", 1); - cameraInfoRight_.subscribe(right_nh, "camera_info", 1); - - if(odomFrameId_.empty()) - { - odomSub_.subscribe(nh, "odom", 1); - if(subscribeScan2d) - { - scanSub_.subscribe(nh, "scan", 1); - stereoScanSync_ = new message_filters::Synchronizer( - MyStereoScanSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_, - scanSub_, - odomSub_); - stereoScanSync_->registerCallback(boost::bind(&CoreWrapper::stereoScanCallback, this, _1, _2, _3, _4, _5, _6)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str(), - scanSub_.getTopic().c_str()); - } - else if(subscribeScan3d) - { - scan3dSub_.subscribe(nh, "scan_cloud", 1); - stereoScan3dSync_ = new message_filters::Synchronizer( - MyStereoScan3dSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_, - scan3dSub_, - odomSub_); - stereoScan3dSync_->registerCallback(boost::bind(&CoreWrapper::stereoScan3dCallback, this, _1, _2, _3, _4, _5, _6)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str(), - scan3dSub_.getTopic().c_str()); - } - else //!subscribeLaserScan - { - if(approxSync) - { - stereoApproxSync_ = new message_filters::Synchronizer( - MyStereoApproxSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_, - odomSub_); - stereoApproxSync_->registerCallback(boost::bind(&CoreWrapper::stereoCallback, this, _1, _2, _3, _4, _5)); - } - else - { - stereoExactSync_ = new message_filters::Synchronizer( - MyStereoExactSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_, - odomSub_); - stereoExactSync_->registerCallback(boost::bind(&CoreWrapper::stereoCallback, this, _1, _2, _3, _4, _5)); - } - - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str()); - } - } - else - { - // use odom from TF, so subscribe to sensors only - if(subscribeScan2d) - { - scanSub_.subscribe(nh, "scan", 1); - stereoScanTFSync_ = new message_filters::Synchronizer( - MyStereoScanTFSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_, - scanSub_); - stereoScanTFSync_->registerCallback(boost::bind(&CoreWrapper::stereoScanTFCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - scanSub_.getTopic().c_str()); - } - else if(subscribeScan3d) - { - scan3dSub_.subscribe(nh, "scan_cloud", 1); - stereoScan3dTFSync_ = new message_filters::Synchronizer( - MyStereoScan3dTFSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_, - scan3dSub_); - stereoScan3dTFSync_->registerCallback(boost::bind(&CoreWrapper::stereoScan3dTFCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - scan3dSub_.getTopic().c_str()); - } - else //!subscribeLaserScan - { - if(approxSync) - { - stereoApproxTFSync_ = new message_filters::Synchronizer( - MyStereoApproxTFSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoApproxTFSync_->registerCallback(boost::bind(&CoreWrapper::stereoTFCallback, this, _1, _2, _3, _4)); - } - else - { - stereoExactTFSync_ = new message_filters::Synchronizer( - MyStereoExactTFSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoExactTFSync_->registerCallback(boost::bind(&CoreWrapper::stereoTFCallback, this, _1, _2, _3, _4)); - } - - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str()); - } - } - } - else - { - ros::NodeHandle rgb_nh(nh, "rgb"); - ros::NodeHandle rgb_pnh(pnh, "rgb"); - image_transport::ImageTransport rgb_it(rgb_nh); - image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); - defaultSub_ = rgb_it.subscribe("image", 1, &CoreWrapper::defaultCallback, this); - - ROS_INFO("\n%s subscribed to:\n %s", ros::this_node::getName().c_str(), defaultSub_.getTopic().c_str()); - } } - - - diff --git a/src/GuiNode.cpp b/src/GuiNode.cpp index 6fd74d2f..99a8c41b 100644 --- a/src/GuiNode.cpp +++ b/src/GuiNode.cpp @@ -54,7 +54,7 @@ int main(int argc, char** argv) app = new QApplication(argc, argv); app->connect( app, SIGNAL( lastWindowClosed() ), app, SLOT( quit() ) ); - GuiWrapper * gui = new GuiWrapper(argc, argv); + rtabmap_ros::GuiWrapper * gui = new rtabmap_ros::GuiWrapper(argc, argv); // Catch ctrl-c to close the gui // (Place this after QApplication's constructor) diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index 799b6fa9..cfb9d852 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -60,22 +60,17 @@ float max3( const float& a, const float& b, const float& c) return m>c?m:c; } +namespace rtabmap_ros { + GuiWrapper::GuiWrapper(int & argc, char** argv) : mainWindow_(0), frameId_("base_link"), + odomFrameId_(""), waitForTransform_(true), waitForTransformDuration_(0.2), // 200 ms odomSensorSync_(false), cameraNodeName_(""), - lastOdomInfoUpdateTime_(0), - depthScanSync_(0), - depthSync_(0), - depthOdomInfoSync_(0), - stereoSync_(0), - stereoScanSync_(0), - stereoOdomInfoSync_(0), - depth2Sync_(0), - depthOdomInfo2Sync_(0) + lastOdomInfoUpdateTime_(0) { ros::NodeHandle nh; @@ -107,28 +102,10 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : ros::NodeHandle pnh("~"); // To receive odometry events - bool subscribeLaserScan2d = false; - bool subscribeLaserScan3d = false; - bool subscribeDepth = false; - bool subscribeOdomInfo = false; - bool subscribeStereo = false; - int queueSize = 10; - int depthCameras = 1; std::string tfPrefix; std::string initCachePath; pnh.param("frame_id", frameId_, frameId_); pnh.param("odom_frame_id", odomFrameId_, odomFrameId_); // set to use odom from TF - pnh.param("subscribe_depth", subscribeDepth, subscribeDepth); - if(pnh.getParam("subscribe_laserScan", subscribeLaserScan2d) && subscribeLaserScan2d) - { - ROS_WARN("rtabmapviz: \"subscribe_laserScan\" parameter is deprecated, use \"subscribe_scan\" instead. The scan topic is still subscribed."); - } - pnh.param("subscribe_scan", subscribeLaserScan2d, subscribeLaserScan2d); - pnh.param("subscribe_scan_cloud", subscribeLaserScan3d, subscribeLaserScan3d); - pnh.param("subscribe_odom_info", subscribeOdomInfo, subscribeOdomInfo); - pnh.param("subscribe_stereo", subscribeStereo, subscribeStereo); - pnh.param("depth_cameras", depthCameras, depthCameras); - pnh.param("queue_size", queueSize, queueSize); pnh.param("tf_prefix", tfPrefix, tfPrefix); pnh.param("wait_for_transform", waitForTransform_, waitForTransform_); pnh.param("wait_for_transform_duration", waitForTransformDuration_, waitForTransformDuration_); @@ -174,22 +151,13 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : } } - this->setupCallbacks( - subscribeDepth, - subscribeLaserScan2d, - subscribeLaserScan3d, - subscribeOdomInfo, - subscribeStereo, - queueSize, - depthCameras); - UEventsManager::addHandler(this); UEventsManager::addHandler(mainWindow_); infoTopic_.subscribe(nh, "info", 1); mapDataTopic_.subscribe(nh, "mapData", 1); infoMapSync_ = new message_filters::Synchronizer( - MyInfoMapSyncPolicy(queueSize), + MyInfoMapSyncPolicy(this->getQueueSize()), infoTopic_, mapDataTopic_); infoMapSync_->registerCallback(boost::bind(&GuiWrapper::infoMapCallback, this, _1, _2)); @@ -197,48 +165,25 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : goalTopic_.subscribe(nh, "goal_node", 1); pathTopic_.subscribe(nh, "global_path", 1); goalPathSync_ = new message_filters::Synchronizer( - MyGoalPathSyncPolicy(queueSize), + MyGoalPathSyncPolicy(this->getQueueSize()), goalTopic_, pathTopic_); goalPathSync_->registerCallback(boost::bind(&GuiWrapper::goalPathCallback, this, _1, _2)); goalReachedTopic_ = nh.subscribe("goal_reached", 1, &GuiWrapper::goalReachedCallback, this); + + if(!this->isDataSubscribed()) + { + defaultSub_ = nh.subscribe("odom", 1, &GuiWrapper::defaultCallback, this); + + ROS_INFO("\n%s subscribed to:\n %s", + ros::this_node::getName().c_str(), + defaultSub_.getTopic().c_str()); + } } GuiWrapper::~GuiWrapper() { UDEBUG(""); - if(depthSync_) - delete depthSync_; - if(depth2Sync_) - delete depth2Sync_; - if(depthScanSync_) - delete depthScanSync_; - if(depthOdomInfoSync_) - delete depthOdomInfoSync_; - if(depthOdomInfo2Sync_) - delete depthOdomInfo2Sync_; - if(stereoSync_) - delete stereoSync_; - if(stereoScanSync_) - delete stereoScanSync_; - if(stereoOdomInfoSync_) - delete stereoOdomInfoSync_; - - for(unsigned int i=0; i poses; std::map signatures; - std::multimap links; + std::multimap links; rtabmap_ros::mapDataFromROS(*mapMsg, poses, links, signatures, mapToOdom); @@ -471,34 +416,15 @@ void GuiWrapper::handleEvent(UEvent * anEvent) void GuiWrapper::commonDepthCallback( const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, const sensor_msgs::LaserScanConstPtr& scan2dMsg, const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) { - std::vector imageMsgs; - std::vector depthMsgs; - std::vector cameraInfoMsgs; - imageMsgs.push_back(imageMsg); - depthMsgs.push_back(depthMsg); - cameraInfoMsgs.push_back(cameraInfoMsg); - commonDepthCallback(odomMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scan2dMsg, scan3dMsg, odomInfoMsg); -} - -void GuiWrapper::commonDepthCallback( - const nav_msgs::OdometryConstPtr & odomMsg, - const std::vector & imageMsgs, - const std::vector & depthMsgs, - const std::vector & cameraInfoMsgs, - const sensor_msgs::LaserScanConstPtr& scan2dMsg, - const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, - const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) -{ - UASSERT(imageMsgs.size()>0 && - imageMsgs.size() == depthMsgs.size() && - imageMsgs.size() == cameraInfoMsgs.size()); + UASSERT(imageMsgs.size() == 0 || (imageMsgs.size() == depthMsgs.size() && imageMsgs.size() == cameraInfoMsgs.size())); std_msgs::Header odomHeader; if(odomMsg.get()) @@ -515,9 +441,9 @@ void GuiWrapper::commonDepthCallback( { odomHeader = scan3dMsg->header; } - else if(cameraInfoMsgs.size() && cameraInfoMsgs[0].get()) + else if(cameraInfoMsgs.size()) { - odomHeader = cameraInfoMsgs[0]->header; + odomHeader = cameraInfoMsgs[0].header; } else if(depthMsgs.size() && depthMsgs[0].get()) { @@ -565,7 +491,7 @@ void GuiWrapper::commonDepthCallback( { lastOdomInfoUpdateTime_ = UTimer::now(); - if(imageMsgs[0].get() && depthMsgs[0].get()) + if(imageMsgs.size() && imageMsgs[0].get() && depthMsgs[0].get()) { if(!rtabmap_ros::convertRGBDMsgs( imageMsgs, @@ -814,1001 +740,15 @@ void GuiWrapper::commonStereoCallback( // With odom msg void GuiWrapper::defaultCallback(const nav_msgs::OdometryConstPtr & odomMsg) { - commonDepthCallback( + this->commonSingleDepthCallback( odomMsg, - sensor_msgs::ImageConstPtr(), - sensor_msgs::ImageConstPtr(), - sensor_msgs::CameraInfoConstPtr(), + rtabmap_ros::UserDataConstPtr(), + cv_bridge::CvImageConstPtr(), + cv_bridge::CvImageConstPtr(), + sensor_msgs::CameraInfo(), sensor_msgs::LaserScanConstPtr(), sensor_msgs::PointCloud2ConstPtr(), rtabmap_ros::OdomInfoConstPtr()); } -void GuiWrapper::depthCallback( - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - odomMsg, - imageMsg, - depthMsg, - cameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); } - -void GuiWrapper::depth2Callback( - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& image1Msg, - const sensor_msgs::ImageConstPtr& depth1Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo1Msg, - const sensor_msgs::ImageConstPtr& image2Msg, - const sensor_msgs::ImageConstPtr& depth2Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo2Msg) -{ - std::vector imageMsgs; - std::vector depthMsgs; - std::vector cameraInfoMsgs; - imageMsgs.push_back(image1Msg); - imageMsgs.push_back(image2Msg); - depthMsgs.push_back(depth1Msg); - depthMsgs.push_back(depth2Msg); - cameraInfoMsgs.push_back(cameraInfo1Msg); - cameraInfoMsgs.push_back(cameraInfo2Msg); - - commonDepthCallback( - odomMsg, - imageMsgs, - depthMsgs, - cameraInfoMsgs, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::depthOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - odomMsg, - imageMsg, - depthMsg, - cameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - odomInfoMsg); -} - -void GuiWrapper::depthOdomInfo2Callback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& image1Msg, - const sensor_msgs::ImageConstPtr& depth1Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo1Msg, - const sensor_msgs::ImageConstPtr& image2Msg, - const sensor_msgs::ImageConstPtr& depth2Msg, - const sensor_msgs::CameraInfoConstPtr& cameraInfo2Msg) -{ - std::vector imageMsgs; - std::vector depthMsgs; - std::vector cameraInfoMsgs; - imageMsgs.push_back(image1Msg); - imageMsgs.push_back(image2Msg); - depthMsgs.push_back(depth1Msg); - depthMsgs.push_back(depth2Msg); - cameraInfoMsgs.push_back(cameraInfo1Msg); - cameraInfoMsgs.push_back(cameraInfo2Msg); - - commonDepthCallback( - odomMsg, - imageMsgs, - depthMsgs, - cameraInfoMsgs, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - odomInfoMsg); -} - -void GuiWrapper::depthScanCallback( - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - odomMsg, - imageMsg, - depthMsg, - cameraInfoMsg, - scanMsg, - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::depthScanOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - odomMsg, - imageMsg, - depthMsg, - cameraInfoMsg, - scanMsg, - sensor_msgs::PointCloud2ConstPtr(), - odomInfoMsg); -} - -void GuiWrapper::depthScan3dCallback( - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - odomMsg, - imageMsg, - depthMsg, - cameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - scanMsg, - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::depthScan3dOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - odomMsg, - imageMsg, - depthMsg, - cameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - scanMsg, - odomInfoMsg); -} - -void GuiWrapper::stereoScanCallback( - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - odomMsg, - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - scanMsg, - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::stereoScanOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::LaserScanConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - odomMsg, - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - scanMsg, - sensor_msgs::PointCloud2ConstPtr(), - odomInfoMsg); -} - -void GuiWrapper::stereoScan3dCallback( - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - odomMsg, - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - scanMsg, - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::stereoScan3dOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - odomMsg, - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - scanMsg, - odomInfoMsg); -} - -void GuiWrapper::stereoOdomInfoCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - odomMsg, - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - odomInfoMsg); -} - -void GuiWrapper::stereoCallback( - const nav_msgs::OdometryConstPtr & odomMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - odomMsg, - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); -} - -// With odom TF -void GuiWrapper::depthTFCallback( - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - nav_msgs::OdometryConstPtr(), - imageMsg, - depthMsg, - cameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::depthOdomInfoTFCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - nav_msgs::OdometryConstPtr(), - imageMsg, - depthMsg, - cameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - odomInfoMsg); -} - -void GuiWrapper::depthScanTFCallback( - const sensor_msgs::LaserScanConstPtr& scanMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - nav_msgs::OdometryConstPtr(), - imageMsg, - depthMsg, - cameraInfoMsg, - scanMsg, - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::depthScan3dTFCallback( - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const sensor_msgs::ImageConstPtr& imageMsg, - const sensor_msgs::ImageConstPtr& depthMsg, - const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) -{ - commonDepthCallback( - nav_msgs::OdometryConstPtr(), - imageMsg, - depthMsg, - cameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - scanMsg, - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::stereoScanTFCallback( - const sensor_msgs::LaserScanConstPtr& scanMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - nav_msgs::OdometryConstPtr(), - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - scanMsg, - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::stereoScan3dTFCallback( - const sensor_msgs::PointCloud2ConstPtr& scanMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - nav_msgs::OdometryConstPtr(), - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - scanMsg, - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::stereoOdomInfoTFCallback( - const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg, - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - nav_msgs::OdometryConstPtr(), - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - odomInfoMsg); -} - -void GuiWrapper::stereoTFCallback( - const sensor_msgs::ImageConstPtr& leftImageMsg, - const sensor_msgs::ImageConstPtr& rightImageMsg, - const sensor_msgs::CameraInfoConstPtr& leftCameraInfoMsg, - const sensor_msgs::CameraInfoConstPtr& rightCameraInfoMsg) -{ - commonStereoCallback( - nav_msgs::OdometryConstPtr(), - leftImageMsg, - rightImageMsg, - leftCameraInfoMsg, - rightCameraInfoMsg, - sensor_msgs::LaserScanConstPtr(), - sensor_msgs::PointCloud2ConstPtr(), - rtabmap_ros::OdomInfoConstPtr()); -} - -void GuiWrapper::setupCallbacks( - bool subscribeDepth, - bool subscribeLaserScan2d, - bool subscribeLaserScan3d, - bool subscribeOdomInfo, - bool subscribeStereo, - int queueSize, - int depthCameras) -{ - ros::NodeHandle nh; // public - ros::NodeHandle pnh("~"); // private - - if(subscribeDepth && subscribeStereo) - { - ROS_WARN("rtabmapviz: Parameters subscribe_depth and subscribe_stereo cannot be true at the " - "same time. Parameter subscribe_depth is set to false."); - subscribeDepth = false; - } - if(!subscribeDepth && !subscribeStereo && (subscribeLaserScan2d || subscribeLaserScan3d)) - { - ROS_WARN("Cannot subscribe to laser scan without depth or stereo subscription..."); - } - if(depthCameras <= 0) - { - depthCameras = 1; - } - if(depthCameras > 2) - { - ROS_WARN("Cannot subscribe to more than 2 cameras yet..."); - depthCameras = 2; - } - - if(subscribeDepth) - { - UASSERT(depthCameras >= 1 && depthCameras <= 2); - UASSERT_MSG(depthCameras == 1 || !(subscribeLaserScan2d || subscribeLaserScan3d || !odomFrameId_.empty()), "Not yet supported!"); - - imageSubs_.resize(depthCameras); - imageDepthSubs_.resize(depthCameras); - cameraInfoSubs_.resize(depthCameras); - for(int i=0; i1) - { - rgbPrefix += uNumber2Str(i); - depthPrefix += uNumber2Str(i); - } - ros::NodeHandle rgb_nh(nh, rgbPrefix); - ros::NodeHandle depth_nh(nh, depthPrefix); - ros::NodeHandle rgb_pnh(pnh, rgbPrefix); - ros::NodeHandle depth_pnh(pnh, depthPrefix); - image_transport::ImageTransport rgb_it(rgb_nh); - image_transport::ImageTransport depth_it(depth_nh); - image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); - image_transport::TransportHints hintsDepth("raw", ros::TransportHints(), depth_pnh); - - imageSubs_[i] = new image_transport::SubscriberFilter; - imageDepthSubs_[i] = new image_transport::SubscriberFilter; - cameraInfoSubs_[i] = new message_filters::Subscriber; - imageSubs_[i]->subscribe(rgb_it, rgb_nh.resolveName("image"), 1, hintsRgb); - imageDepthSubs_[i]->subscribe(depth_it, depth_nh.resolveName("image"), 1, hintsDepth); - cameraInfoSubs_[i]->subscribe(rgb_nh, "camera_info", 1); - } - - if(odomFrameId_.empty()) - { - odomSub_.subscribe(nh, "odom", 1); - if(subscribeLaserScan2d) - { - scanSub_.subscribe(nh, "scan", 1); - if(subscribeOdomInfo) - { - odomInfoSub_.subscribe(nh, "odom_info", 1); - depthScanOdomInfoSync_ = new message_filters::Synchronizer( - MyDepthScanOdomInfoSyncPolicy(queueSize), - odomInfoSub_, - scanSub_, - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthScanOdomInfoSync_->registerCallback(boost::bind(&GuiWrapper::depthScanOdomInfoCallback, this, _1, _2, _3, _4, _5, _6)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str(), - scanSub_.getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - else - { - depthScanSync_ = new message_filters::Synchronizer( - MyDepthScanSyncPolicy(queueSize), - scanSub_, - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthScanSync_->registerCallback(boost::bind(&GuiWrapper::depthScanCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str(), - scanSub_.getTopic().c_str()); - } - } - else if(subscribeLaserScan3d) - { - scan3dSub_.subscribe(nh, "scan_cloud", 1); - if(subscribeOdomInfo) - { - odomInfoSub_.subscribe(nh, "odom_info", 1); - depthScan3dOdomInfoSync_ = new message_filters::Synchronizer( - MyDepthScan3dOdomInfoSyncPolicy(queueSize), - odomInfoSub_, - scan3dSub_, - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthScan3dOdomInfoSync_->registerCallback(boost::bind(&GuiWrapper::depthScan3dOdomInfoCallback, this, _1, _2, _3, _4, _5, _6)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str(), - scan3dSub_.getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - else - { - depthScan3dSync_ = new message_filters::Synchronizer( - MyDepthScan3dSyncPolicy(queueSize), - scan3dSub_, - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthScan3dSync_->registerCallback(boost::bind(&GuiWrapper::depthScan3dCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str(), - scan3dSub_.getTopic().c_str()); - } - } - else if(subscribeOdomInfo) - { - odomInfoSub_.subscribe(nh, "odom_info", 1); - if(depthCameras > 1) - { - depthOdomInfo2Sync_ = new message_filters::Synchronizer( - MyDepthOdomInfo2SyncPolicy(queueSize), - odomInfoSub_, - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - *imageSubs_[1], - *imageDepthSubs_[1], - *cameraInfoSubs_[1]); - depthOdomInfo2Sync_->registerCallback(boost::bind(&GuiWrapper::depthOdomInfo2Callback, this, _1, _2, _3, _4, _5, _6, _7, _8)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - imageSubs_[1]->getTopic().c_str(), - imageDepthSubs_[1]->getTopic().c_str(), - cameraInfoSubs_[1]->getTopic().c_str(), - odomSub_.getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - else - { - depthOdomInfoSync_ = new message_filters::Synchronizer( - MyDepthOdomInfoSyncPolicy(queueSize), - odomInfoSub_, - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthOdomInfoSync_->registerCallback(boost::bind(&GuiWrapper::depthOdomInfoCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - } - else - { - if(depthCameras > 1) - { - depth2Sync_ = new message_filters::Synchronizer( - MyDepth2SyncPolicy(queueSize), - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0], - *imageSubs_[1], - *imageDepthSubs_[1], - *cameraInfoSubs_[1]); - depth2Sync_->registerCallback(boost::bind(&GuiWrapper::depth2Callback, this, _1, _2, _3, _4, _5, _6, _7)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - imageSubs_[1]->getTopic().c_str(), - imageDepthSubs_[1]->getTopic().c_str(), - cameraInfoSubs_[1]->getTopic().c_str(), - odomSub_.getTopic().c_str()); - } - else - { - depthSync_ = new message_filters::Synchronizer( - MyDepthSyncPolicy(queueSize), - odomSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthSync_->registerCallback(boost::bind(&GuiWrapper::depthCallback, this, _1, _2, _3, _4)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomSub_.getTopic().c_str()); - } - } - } - else - { - // use TF as odom - if(subscribeLaserScan2d) - { - scanSub_.subscribe(nh, "scan", 1); - depthScanTFSync_ = new message_filters::Synchronizer( - MyDepthScanTFSyncPolicy(queueSize), - scanSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthScanTFSync_->registerCallback(boost::bind(&GuiWrapper::depthScanTFCallback, this, _1, _2, _3, _4)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - scanSub_.getTopic().c_str()); - } - else if(subscribeLaserScan3d) - { - scan3dSub_.subscribe(nh, "scan_cloud", 1); - depthScan3dTFSync_ = new message_filters::Synchronizer( - MyDepthScan3dTFSyncPolicy(queueSize), - scan3dSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthScan3dTFSync_->registerCallback(boost::bind(&GuiWrapper::depthScan3dTFCallback, this, _1, _2, _3, _4)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - scan3dSub_.getTopic().c_str()); - } - else if(subscribeOdomInfo) - { - odomInfoSub_.subscribe(nh, "odom_info", 1); - depthOdomInfoTFSync_ = new message_filters::Synchronizer( - MyDepthOdomInfoTFSyncPolicy(queueSize), - odomInfoSub_, - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthOdomInfoTFSync_->registerCallback(boost::bind(&GuiWrapper::depthOdomInfoTFCallback, this, _1, _2, _3, _4)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - else - { - depthTFSync_ = new message_filters::Synchronizer( - MyDepthTFSyncPolicy(queueSize), - *imageSubs_[0], - *imageDepthSubs_[0], - *cameraInfoSubs_[0]); - depthTFSync_->registerCallback(boost::bind(&GuiWrapper::depthTFCallback, this, _1, _2, _3)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageSubs_[0]->getTopic().c_str(), - imageDepthSubs_[0]->getTopic().c_str(), - cameraInfoSubs_[0]->getTopic().c_str()); - } - } - } - else if(subscribeStereo) - { - ros::NodeHandle left_nh(nh, "left"); - ros::NodeHandle right_nh(nh, "right"); - ros::NodeHandle left_pnh(pnh, "left"); - ros::NodeHandle right_pnh(pnh, "right"); - image_transport::ImageTransport left_it(left_nh); - image_transport::ImageTransport right_it(right_nh); - image_transport::TransportHints hintsLeft("raw", ros::TransportHints(), left_pnh); - image_transport::TransportHints hintsRight("raw", ros::TransportHints(), right_pnh); - - imageRectLeft_.subscribe(left_it, left_nh.resolveName("image_rect"), 1, hintsLeft); - imageRectRight_.subscribe(right_it, right_nh.resolveName("image_rect"), 1, hintsRight); - cameraInfoLeft_.subscribe(left_nh, "camera_info", 1); - cameraInfoRight_.subscribe(right_nh, "camera_info", 1); - - if(odomFrameId_.empty()) - { - odomSub_.subscribe(nh, "odom", 1); - if(subscribeLaserScan2d) - { - scanSub_.subscribe(nh, "scan", 1); - if(subscribeOdomInfo) - { - odomInfoSub_.subscribe(nh, "odom_info", 1); - stereoScanOdomInfoSync_ = new message_filters::Synchronizer( - MyStereoScanOdomInfoSyncPolicy(queueSize), - odomInfoSub_, - scanSub_, - odomSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoScanOdomInfoSync_->registerCallback(boost::bind(&GuiWrapper::stereoScanOdomInfoCallback, this, _1, _2, _3, _4, _5, _6, _7)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str(), - scanSub_.getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - else - { - stereoScanSync_ = new message_filters::Synchronizer( - MyStereoScanSyncPolicy(queueSize), - scanSub_, - odomSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoScanSync_->registerCallback(boost::bind(&GuiWrapper::stereoScanCallback, this, _1, _2, _3, _4, _5, _6)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str(), - scanSub_.getTopic().c_str()); - } - } - else if(subscribeLaserScan3d) - { - scan3dSub_.subscribe(nh, "scan_cloud", 1); - if(subscribeOdomInfo) - { - odomInfoSub_.subscribe(nh, "odom_info", 1); - stereoScan3dOdomInfoSync_ = new message_filters::Synchronizer( - MyStereoScan3dOdomInfoSyncPolicy(queueSize), - odomInfoSub_, - scan3dSub_, - odomSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoScan3dOdomInfoSync_->registerCallback(boost::bind(&GuiWrapper::stereoScan3dOdomInfoCallback, this, _1, _2, _3, _4, _5, _6, _7)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str(), - scan3dSub_.getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - else - { - stereoScan3dSync_ = new message_filters::Synchronizer( - MyStereoScan3dSyncPolicy(queueSize), - scan3dSub_, - odomSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoScan3dSync_->registerCallback(boost::bind(&GuiWrapper::stereoScan3dCallback, this, _1, _2, _3, _4, _5, _6)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str(), - scan3dSub_.getTopic().c_str()); - } - } - else if(subscribeOdomInfo) - { - odomInfoSub_.subscribe(nh, "odom_info", 1); - stereoOdomInfoSync_ = new message_filters::Synchronizer( - MyStereoOdomInfoSyncPolicy(queueSize), - odomInfoSub_, - odomSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoOdomInfoSync_->registerCallback(boost::bind(&GuiWrapper::stereoOdomInfoCallback, this, _1, _2, _3, _4, _5, _6)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - else - { - stereoSync_ = new message_filters::Synchronizer( - MyStereoSyncPolicy(queueSize), - odomSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoSync_->registerCallback(boost::bind(&GuiWrapper::stereoCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomSub_.getTopic().c_str()); - } - } - else - { - //use odom TF - if(subscribeLaserScan2d) - { - scanSub_.subscribe(nh, "scan", 1); - stereoScanTFSync_ = new message_filters::Synchronizer( - MyStereoScanTFSyncPolicy(queueSize), - scanSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoScanTFSync_->registerCallback(boost::bind(&GuiWrapper::stereoScanTFCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - scanSub_.getTopic().c_str()); - } - else if(subscribeLaserScan3d) - { - scan3dSub_.subscribe(nh, "scan_cloud", 1); - stereoScan3dTFSync_ = new message_filters::Synchronizer( - MyStereoScan3dTFSyncPolicy(queueSize), - scan3dSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoScan3dTFSync_->registerCallback(boost::bind(&GuiWrapper::stereoScan3dTFCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - scan3dSub_.getTopic().c_str()); - } - else if(subscribeOdomInfo) - { - odomInfoSub_.subscribe(nh, "odom_info", 1); - stereoOdomInfoTFSync_ = new message_filters::Synchronizer( - MyStereoOdomInfoTFSyncPolicy(queueSize), - odomInfoSub_, - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoOdomInfoTFSync_->registerCallback(boost::bind(&GuiWrapper::stereoOdomInfoTFCallback, this, _1, _2, _3, _4, _5)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str(), - odomInfoSub_.getTopic().c_str()); - } - else - { - stereoTFSync_ = new message_filters::Synchronizer( - MyStereoTFSyncPolicy(queueSize), - imageRectLeft_, - imageRectRight_, - cameraInfoLeft_, - cameraInfoRight_); - stereoTFSync_->registerCallback(boost::bind(&GuiWrapper::stereoTFCallback, this, _1, _2, _3, _4)); - - ROS_INFO("\n%s subscribed to:\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), - imageRectLeft_.getTopic().c_str(), - imageRectRight_.getTopic().c_str(), - cameraInfoLeft_.getTopic().c_str(), - cameraInfoRight_.getTopic().c_str()); - } - } - } - else // default odom only - { - defaultSub_ = nh.subscribe("odom", 1, &GuiWrapper::defaultCallback, this); - - ROS_INFO("\n%s subscribed to:\n %s", - ros::this_node::getName().c_str(), - defaultSub_.getTopic().c_str()); - } -} - diff --git a/src/MsgConversion.cpp b/src/MsgConversion.cpp index befcfdf0..8cd5bbdf 100644 --- a/src/MsgConversion.cpp +++ b/src/MsgConversion.cpp @@ -40,7 +40,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #include @@ -119,6 +118,78 @@ rtabmap::Transform transformFromPoseMsg(const geometry_msgs::Pose & msg) return rtabmap::Transform::fromEigen3d(tfPose); } +void toCvCopy(const rtabmap_ros::RGBDImage & image, cv_bridge::CvImagePtr & rgb, cv_bridge::CvImagePtr & depth) +{ + if(!image.rgb.data.empty()) + { + rgb = cv_bridge::toCvCopy(image.rgb); + } + else if(!image.rgbCompressed.data.empty()) + { + rgb = cv_bridge::toCvCopy(image.rgbCompressed); + } + else + { + // empty + rgb = boost::make_shared(); + } + + if(!image.depth.data.empty()) + { + depth = cv_bridge::toCvCopy(image.depth); + } + else if(!image.depthCompressed.data.empty()) + { + cv_bridge::CvImagePtr ptr = boost::make_shared(); + ptr->header = image.depthCompressed.header; + ptr->image = rtabmap::uncompressImage(image.depthCompressed.data); + ROS_ASSERT(ptr->image.empty() || ptr->image.type() == CV_32FC1 || ptr->image.type() == CV_16UC1); + ptr->encoding = ptr->image.empty()?"":ptr->image.type() == CV_32FC1?sensor_msgs::image_encodings::TYPE_32FC1:sensor_msgs::image_encodings::TYPE_16UC1; + depth = ptr; + } + else + { + // empty + depth = boost::make_shared(); + } +} + +void toCvShare(const rtabmap_ros::RGBDImageConstPtr & image, cv_bridge::CvImageConstPtr & rgb, cv_bridge::CvImageConstPtr & depth) +{ + if(!image->rgb.data.empty()) + { + rgb = cv_bridge::toCvShare(image->rgb, image); + } + else if(!image->rgbCompressed.data.empty()) + { + rgb = cv_bridge::toCvCopy(image->rgbCompressed); + } + else + { + // empty + rgb = boost::make_shared(); + } + + if(!image->depth.data.empty()) + { + depth = cv_bridge::toCvShare(image->depth, image); + } + else if(!image->depthCompressed.data.empty()) + { + cv_bridge::CvImagePtr ptr = boost::make_shared(); + ptr->header = image->depthCompressed.header; + ptr->image = rtabmap::uncompressImage(image->depthCompressed.data); + ROS_ASSERT(ptr->image.empty() || ptr->image.type() == CV_32FC1 || ptr->image.type() == CV_16UC1); + ptr->encoding = ptr->image.empty()?"":ptr->image.type() == CV_32FC1?sensor_msgs::image_encodings::TYPE_32FC1:sensor_msgs::image_encodings::TYPE_16UC1; + depth = ptr; + } + else + { + // empty + depth = boost::make_shared(); + } +} + void compressedMatToBytes(const cv::Mat & compressed, std::vector & bytes) { UASSERT(compressed.empty() || compressed.type() == CV_8UC1); @@ -566,9 +637,11 @@ rtabmap::Signature nodeDataFromROS(const rtabmap_ros::NodeData & msg) { // stereo model if(msg.fx.size() == 1 && - msg.fy.size() == 1, - msg.cx.size() == 1, - msg.cy.size() == 1, + msg.fy.size() == 1 && + msg.cx.size() == 1 && + msg.cy.size() == 1 && + msg.width.size() == 1 && + msg.height.size() == 1 && msg.localTransform.size() == 1) { stereoModel = rtabmap::StereoCameraModel( @@ -577,7 +650,8 @@ rtabmap::Signature nodeDataFromROS(const rtabmap_ros::NodeData & msg) msg.cx[0], msg.cy[0], msg.baseline, - transformFromGeometryMsg(msg.localTransform[0])); + transformFromGeometryMsg(msg.localTransform[0]), + cv::Size(msg.width[0], msg.height[0])); } } else @@ -596,7 +670,9 @@ rtabmap::Signature nodeDataFromROS(const rtabmap_ros::NodeData & msg) msg.fy[i], msg.cx[i], msg.cy[i], - transformFromGeometryMsg(msg.localTransform[i]))); + transformFromGeometryMsg(msg.localTransform[i]), + 0.0, + cv::Size(msg.width[i], msg.height[i]))); } } } @@ -672,6 +748,8 @@ void nodeDataToROS(const rtabmap::Signature & signature, rtabmap_ros::NodeData & msg.fy.resize(signature.sensorData().cameraModels().size()); msg.cx.resize(signature.sensorData().cameraModels().size()); msg.cy.resize(signature.sensorData().cameraModels().size()); + msg.width.resize(signature.sensorData().cameraModels().size()); + msg.height.resize(signature.sensorData().cameraModels().size()); msg.localTransform.resize(signature.sensorData().cameraModels().size()); for(unsigned int i=0; i 0 && dataMsg.rows > 0 && dataMsg.type >= 0) + { + data = cv::Mat(dataMsg.rows, dataMsg.cols, dataMsg.type, (void*)dataMsg.data.data()).clone(); + } + else + { + if(dataMsg.cols != (int)dataMsg.data.size() || dataMsg.rows != 1 || dataMsg.type != CV_8UC1) + { + ROS_ERROR("cols, rows and type fields of the UserData msg " + "are not correctly set (cols=%d, rows=%d, type=%d)! We assume that the data " + "is compressed (cols=%d, rows=1, type=%d(CV_8UC1)).", + dataMsg.cols, dataMsg.rows, dataMsg.type, (int)dataMsg.data.size(), CV_8UC1); + + } + data = cv::Mat(1, dataMsg.data.size(), CV_8UC1, (void*)dataMsg.data.data()).clone(); + } + } + return data; +} +void userDataToROS(const cv::Mat & data, rtabmap_ros::UserData & dataMsg, bool compress) +{ + if(!data.empty()) + { + if(compress) + { + dataMsg.data = rtabmap::compressData(data); + dataMsg.rows = 1; + dataMsg.cols = dataMsg.data.size(); + dataMsg.type = CV_8UC1; + } + else + { + dataMsg.data.resize(data.step[0] * data.rows); // use step for non-contiguous matrices + memcpy(dataMsg.data.data(), data.data, dataMsg.data.size()); + dataMsg.rows = data.rows; + dataMsg.cols = data.cols; + dataMsg.type = data.type(); + } + } +} + rtabmap::Transform getTransform( const std::string & fromFrameId, const std::string & toFrameId, @@ -940,9 +1068,9 @@ rtabmap::Transform getTransform( } bool convertRGBDMsgs( - const std::vector & imageMsgs, - const std::vector & depthMsgs, - const std::vector & cameraInfoMsgs, + const std::vector & imageMsgs, + const std::vector & depthMsgs, + const std::vector & cameraInfoMsgs, const std::string & frameId, const std::string & odomFrameId, const ros::Time & odomStamp, @@ -956,8 +1084,8 @@ bool convertRGBDMsgs( imageMsgs.size() == depthMsgs.size() && imageMsgs.size() == cameraInfoMsgs.size()); - int imageWidth = imageMsgs[0]->width; - int imageHeight = imageMsgs[0]->height; + int imageWidth = imageMsgs[0]->image.cols; + int imageHeight = imageMsgs[0]->image.rows; int cameraCount = imageMsgs.size(); for(unsigned int i=0; iwidth == imageWidth && imageMsgs[i]->height == imageHeight, + UASSERT_MSG(imageMsgs[i]->image.cols == imageWidth && imageMsgs[i]->image.rows == imageHeight, uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", imageWidth, - imageMsgs[i]->width, + imageMsgs[i]->image.cols, imageHeight, - imageMsgs[i]->height).c_str()); - UASSERT_MSG(depthMsgs[i]->width == imageWidth && depthMsgs[i]->height == imageHeight, + imageMsgs[i]->image.rows).c_str()); + UASSERT_MSG(depthMsgs[i]->image.cols == imageWidth && depthMsgs[i]->image.rows == imageHeight, uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", imageWidth, - depthMsgs[i]->width, + depthMsgs[i]->image.cols, imageHeight, - depthMsgs[i]->height).c_str()); + depthMsgs[i]->image.rows).c_str()); rtabmap::Transform localTransform = rtabmap_ros::getTransform(frameId, depthMsgs[i]->header.frame_id, depthMsgs[i]->header.stamp, listener, waitForTransform); if(localTransform.isNull()) @@ -1015,21 +1143,22 @@ bool convertRGBDMsgs( } } - cv_bridge::CvImageConstPtr ptrImage; - if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1)==0) + cv_bridge::CvImageConstPtr ptrImage = imageMsgs[i]; + if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1)==0 || + imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || + imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0) { - ptrImage = cv_bridge::toCvShare(imageMsgs[i]); + // do nothing } - else if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) + else if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) { - ptrImage = cv_bridge::toCvShare(imageMsgs[i], "mono8"); + ptrImage = cv_bridge::cvtColor(imageMsgs[i], "mono8"); } else { - ptrImage = cv_bridge::toCvShare(imageMsgs[i], "bgr8"); + ptrImage = cv_bridge::cvtColor(imageMsgs[i], "bgr8"); } - cv_bridge::CvImageConstPtr ptrDepth = cv_bridge::toCvShare(depthMsgs[i]); + cv_bridge::CvImageConstPtr ptrDepth = depthMsgs[i]; cv::Mat subDepth = ptrDepth->image; // initialize @@ -1062,7 +1191,7 @@ bool convertRGBDMsgs( return false; } - cameraModels.push_back(rtabmap_ros::cameraModelFromROS(*cameraInfoMsgs[i], localTransform)); + cameraModels.push_back(rtabmap_ros::cameraModelFromROS(cameraInfoMsgs[i], localTransform)); } return true; } diff --git a/src/impl/CommonDataSubscriberDepth.cpp b/src/impl/CommonDataSubscriberDepth.cpp new file mode 100644 index 00000000..6464d2f5 --- /dev/null +++ b/src/impl/CommonDataSubscriberDepth.cpp @@ -0,0 +1,369 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +namespace rtabmap_ros { + +// RGB + Depth +void CommonDataSubscriber::depthCallback( + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) +{ + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthScan2dCallback( + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthScan3dCallback( + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const sensor_msgs::PointCloud2ConstPtr& scanMsg) +{ + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scan2dMsg, scanMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthInfoCallback( + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scan2dMsg, scan3dMsg, odomInfoMsg); +} + +// RGB + Depth + Odom +void CommonDataSubscriber::depthOdomCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) +{ + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthOdomScan2dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthOdomScan3dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const sensor_msgs::PointCloud2ConstPtr& scanMsg) +{ + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scan2dMsg, scanMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthOdomInfoCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scan2dMsg, scan3dMsg, odomInfoMsg); +} + +// RGB + Depth + User Data +void CommonDataSubscriber::depthDataCallback( + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) +{ + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthDataScan2dCallback( + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + nav_msgs::OdometryConstPtr odomMsg; // null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthDataScan3dCallback( + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const sensor_msgs::PointCloud2ConstPtr& scanMsg) +{ + nav_msgs::OdometryConstPtr odomMsg; // null + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scan2dMsg, scanMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthDataInfoCallback( + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + nav_msgs::OdometryConstPtr odomMsg; // null + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scan2dMsg, scan3dMsg, odomInfoMsg); +} + +// RGB + Depth + Odom + User Data +void CommonDataSubscriber::depthOdomDataCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg) +{ + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthOdomDataScan2dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthOdomDataScan3dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const sensor_msgs::PointCloud2ConstPtr& scanMsg) +{ + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scan2dMsg, scanMsg, odomInfoMsg); +} +void CommonDataSubscriber::depthOdomDataInfoCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const sensor_msgs::ImageConstPtr& imageMsg, + const sensor_msgs::ImageConstPtr& depthMsg, + const sensor_msgs::CameraInfoConstPtr& cameraInfoMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonSingleDepthCallback(odomMsg, userDataMsg, cv_bridge::toCvShare(imageMsg), cv_bridge::toCvShare(depthMsg), *cameraInfoMsg, scan2dMsg, scan3dMsg, odomInfoMsg); +} + +void CommonDataSubscriber::setupDepthCallbacks( + bool subscribeOdom, + bool subscribeUserData, + bool subscribeScan2d, + bool subscribeScan3d, + bool subscribeOdomInfo, + int queueSize, + bool approxSync) +{ + ROS_INFO("Setup depth callback"); + ros::NodeHandle nh; // public + ros::NodeHandle pnh("~"); // private + + std::string rgbPrefix = "rgb"; + std::string depthPrefix = "depth"; + ros::NodeHandle rgb_nh(nh, rgbPrefix); + ros::NodeHandle depth_nh(nh, depthPrefix); + ros::NodeHandle rgb_pnh(pnh, rgbPrefix); + ros::NodeHandle depth_pnh(pnh, depthPrefix); + image_transport::ImageTransport rgb_it(rgb_nh); + image_transport::ImageTransport depth_it(depth_nh); + image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); + image_transport::TransportHints hintsDepth("raw", ros::TransportHints(), depth_pnh); + + imageSub_.subscribe(rgb_it, rgb_nh.resolveName("image"), 1, hintsRgb); + imageDepthSub_.subscribe(depth_it, depth_nh.resolveName("image"), 1, hintsDepth); + cameraInfoSub_.subscribe(rgb_nh, "camera_info", 1); + + if(subscribeOdom && subscribeUserData) + { + odomSub_.subscribe(nh, "odom", 1); + userDataSub_.subscribe(nh, "user_data", 1); + + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL6(depthOdomDataScan2d, approxSync, queueSize, odomSub_, userDataSub_, imageSub_, imageDepthSub_, cameraInfoSub_, scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL6(depthOdomDataScan3d, approxSync, queueSize, odomSub_, userDataSub_, imageSub_, imageDepthSub_, cameraInfoSub_, scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL6(depthOdomDataInfo, approxSync, queueSize, odomSub_, userDataSub_, imageSub_, imageDepthSub_, cameraInfoSub_, odomInfoSub_); + } + else + { + SYNC_DECL5(depthOdomData, approxSync, queueSize, odomSub_, userDataSub_, imageSub_, imageDepthSub_, cameraInfoSub_); + } + } + else if(subscribeOdom) + { + odomSub_.subscribe(nh, "odom", 1); + + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL5(depthOdomScan2d, approxSync, queueSize, odomSub_, imageSub_, imageDepthSub_, cameraInfoSub_, scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL5(depthOdomScan3d, approxSync, queueSize, odomSub_, imageSub_, imageDepthSub_, cameraInfoSub_, scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL5(depthOdomInfo, approxSync, queueSize, odomSub_, imageSub_, imageDepthSub_, cameraInfoSub_, odomInfoSub_); + } + else + { + SYNC_DECL4(depthOdom, approxSync, queueSize, odomSub_, imageSub_, imageDepthSub_, cameraInfoSub_); + } + } + else if(subscribeUserData) + { + userDataSub_.subscribe(nh, "user_data", 1); + + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL5(depthDataScan2d, approxSync, queueSize, userDataSub_, imageSub_, imageDepthSub_, cameraInfoSub_, scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL5(depthDataScan3d, approxSync, queueSize, userDataSub_, imageSub_, imageDepthSub_, cameraInfoSub_, scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL5(depthDataInfo, approxSync, queueSize, userDataSub_, imageSub_, imageDepthSub_, cameraInfoSub_, odomInfoSub_); + } + else + { + SYNC_DECL4(depthData, approxSync, queueSize, userDataSub_, imageSub_, imageDepthSub_, cameraInfoSub_); + } + } + else + { + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL4(depthScan2d, approxSync, queueSize, imageSub_, imageDepthSub_, cameraInfoSub_, scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL4(depthScan3d, approxSync, queueSize, imageSub_, imageDepthSub_, cameraInfoSub_, scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL4(depthInfo, approxSync, queueSize, imageSub_, imageDepthSub_, cameraInfoSub_, odomInfoSub_); + } + else + { + SYNC_DECL3(depth, approxSync, queueSize, imageSub_, imageDepthSub_, cameraInfoSub_); + } + } +} + +} /* namespace rtabmap_ros */ diff --git a/src/impl/CommonDataSubscriberRGBD.cpp b/src/impl/CommonDataSubscriberRGBD.cpp new file mode 100644 index 00000000..091fb504 --- /dev/null +++ b/src/impl/CommonDataSubscriberRGBD.cpp @@ -0,0 +1,386 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +namespace rtabmap_ros { + +// 1 RGBD camera +void CommonDataSubscriber::rgbdCallback( + const rtabmap_ros::RGBDImageConstPtr& image1Msg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdScan2dCallback( + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdScan3dCallback( + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdInfoCallback( + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} + +// 1 RGBD camera + Odom +void CommonDataSubscriber::rgbdOdomCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdOdomScan2dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdOdomScan3dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdOdomInfoCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} + +// 1 RGBD camera + User Data +void CommonDataSubscriber::rgbdDataCallback( + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg, rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdDataScan2dCallback( + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg,rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdDataScan3dCallback( + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg,rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdDataInfoCallback( + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonSingleDepthCallback(odomMsg, userDataMsg,rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} + +// 1 RGBD camera + Odom + User Data +void CommonDataSubscriber::rgbdOdomDataCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg,rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdOdomDataScan2dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg,rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdOdomDataScan3dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + sensor_msgs::LaserScanConstPtr scanMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonSingleDepthCallback(odomMsg, userDataMsg,rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbdOdomDataInfoCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::UserDataConstPtr & userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + cv_bridge::CvImageConstPtr rgb, depth; + rtabmap_ros::toCvShare(image1Msg, rgb, depth); + + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonSingleDepthCallback(odomMsg, userDataMsg,rgb, depth, image1Msg->cameraInfo, scanMsg, scan3dMsg, odomInfoMsg); +} + +void CommonDataSubscriber::setupRGBDCallbacks( + bool subscribeOdom, + bool subscribeUserData, + bool subscribeScan2d, + bool subscribeScan3d, + bool subscribeOdomInfo, + int queueSize, + bool approxSync) +{ + ROS_INFO("Setup rgbd callback"); + ros::NodeHandle nh; // public + ros::NodeHandle pnh("~"); // private + + if(subscribeOdom || subscribeUserData || subscribeScan2d || subscribeScan3d || subscribeOdomInfo) + { + rgbdSubs_.resize(1); + rgbdSubs_[0] = new message_filters::Subscriber; + rgbdSubs_[0]->subscribe(nh, "rgbd_image", 1); + + + if(subscribeOdom && subscribeUserData) + { + odomSub_.subscribe(nh, "odom", 1); + userDataSub_.subscribe(nh, "user_data", 1); + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL4(rgbdOdomDataScan2d, approxSync, queueSize, odomSub_, userDataSub_, (*rgbdSubs_[0]), scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL4(rgbdOdomDataScan3d, approxSync, queueSize, odomSub_, userDataSub_, (*rgbdSubs_[0]), scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL4(rgbdOdomDataInfo, approxSync, queueSize, odomSub_, userDataSub_, (*rgbdSubs_[0]), odomInfoSub_); + } + else + { + SYNC_DECL3(rgbdOdomData, approxSync, queueSize, odomSub_, userDataSub_, (*rgbdSubs_[0])); + } + } + else if(subscribeOdom) + { + odomSub_.subscribe(nh, "odom", 1); + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL3(rgbdOdomScan2d, approxSync, queueSize, odomSub_, (*rgbdSubs_[0]), scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL3(rgbdOdomScan3d, approxSync, queueSize, odomSub_, (*rgbdSubs_[0]), scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL3(rgbdOdomInfo, approxSync, queueSize, odomSub_, (*rgbdSubs_[0]), odomInfoSub_); + } + else + { + SYNC_DECL2(rgbdOdom, approxSync, queueSize, odomSub_, (*rgbdSubs_[0])); + } + } + else if(subscribeUserData) + { + userDataSub_.subscribe(nh, "user_data", 1); + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL3(rgbdDataScan2d, approxSync, queueSize, userDataSub_, (*rgbdSubs_[0]), scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL3(rgbdDataScan3d, approxSync, queueSize, userDataSub_, (*rgbdSubs_[0]), scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL3(rgbdDataInfo, approxSync, queueSize, userDataSub_, (*rgbdSubs_[0]), odomInfoSub_); + } + else + { + SYNC_DECL2(rgbdData, approxSync, queueSize, userDataSub_, (*rgbdSubs_[0])); + } + } + else + { + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL2(rgbdScan2d, approxSync, queueSize, (*rgbdSubs_[0]), scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL2(rgbdScan3d, approxSync, queueSize, (*rgbdSubs_[0]), scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL2(rgbdInfo, approxSync, queueSize, (*rgbdSubs_[0]), odomInfoSub_); + } + else + { + ROS_FATAL("Not supposed to be here!"); + } + } + } + else + { + rgbdSub_ = nh.subscribe("rgbd_image", 1, &CommonDataSubscriber::rgbdCallback, this); + + ROS_INFO("\n%s subscribed to:\n %s", + ros::this_node::getName().c_str(), + rgbdSub_.getTopic().c_str()); + } +} + +} /* namespace rtabmap_ros */ diff --git a/src/impl/CommonDataSubscriberRGBD2.cpp b/src/impl/CommonDataSubscriberRGBD2.cpp new file mode 100644 index 00000000..ed044aed --- /dev/null +++ b/src/impl/CommonDataSubscriberRGBD2.cpp @@ -0,0 +1,385 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +namespace rtabmap_ros { + +#define IMAGE_CONVERSION() \ + std::vector imageMsgs(2); \ + std::vector depthMsgs(2); \ + rtabmap_ros::toCvShare(image1Msg, imageMsgs[0], depthMsgs[0]); \ + rtabmap_ros::toCvShare(image2Msg, imageMsgs[1], depthMsgs[1]); \ + std::vector cameraInfoMsgs; \ + cameraInfoMsgs.push_back(image1Msg->cameraInfo); \ + cameraInfoMsgs.push_back(image2Msg->cameraInfo); + +// 2 RGBD +void CommonDataSubscriber::rgbd2Callback( + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg) +{ + IMAGE_CONVERSION(); + + nav_msgs::OdometryConstPtr odomMsg; // Null + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2Scan2dCallback( + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + IMAGE_CONVERSION(); + + nav_msgs::OdometryConstPtr odomMsg; // Null + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2Scan3dCallback( + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) +{ + IMAGE_CONVERSION(); + + nav_msgs::OdometryConstPtr odomMsg; // Null + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2InfoCallback( + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + IMAGE_CONVERSION(); + + nav_msgs::OdometryConstPtr odomMsg; // Null + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} + +// 2 RGBD + Odom +void CommonDataSubscriber::rgbd2OdomCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg) +{ + IMAGE_CONVERSION(); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2OdomScan2dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + IMAGE_CONVERSION(); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2OdomScan3dCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) +{ + IMAGE_CONVERSION(); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2OdomInfoCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + IMAGE_CONVERSION(); + + rtabmap_ros::UserDataConstPtr userDataMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} + +// 2 RGBD + User Data +void CommonDataSubscriber::rgbd2DataCallback( + const rtabmap_ros::UserDataConstPtr& userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg) +{ + IMAGE_CONVERSION(); + + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2DataScan2dCallback( + const rtabmap_ros::UserDataConstPtr& userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + IMAGE_CONVERSION(); + + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2DataScan3dCallback( + const rtabmap_ros::UserDataConstPtr& userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) +{ + IMAGE_CONVERSION(); + + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2DataInfoCallback( + const rtabmap_ros::UserDataConstPtr& userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + IMAGE_CONVERSION(); + + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} + +// 2 RGBD + Odom + User Data +void CommonDataSubscriber::rgbd2OdomDataCallback( + const nav_msgs::OdometryConstPtr& odomMsg, + const rtabmap_ros::UserDataConstPtr& userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg) +{ + IMAGE_CONVERSION(); + + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2OdomDataScan2dCallback( + const nav_msgs::OdometryConstPtr& odomMsg, + const rtabmap_ros::UserDataConstPtr& userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const sensor_msgs::LaserScanConstPtr& scanMsg) +{ + IMAGE_CONVERSION(); + + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2OdomDataScan3dCallback( + const nav_msgs::OdometryConstPtr& odomMsg, + const rtabmap_ros::UserDataConstPtr& userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const sensor_msgs::PointCloud2ConstPtr& scan3dMsg) +{ + IMAGE_CONVERSION(); + + sensor_msgs::LaserScanConstPtr scanMsg; // Null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::rgbd2OdomDataInfoCallback( + const nav_msgs::OdometryConstPtr& odomMsg, + const rtabmap_ros::UserDataConstPtr& userDataMsg, + const rtabmap_ros::RGBDImageConstPtr& image1Msg, + const rtabmap_ros::RGBDImageConstPtr& image2Msg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + IMAGE_CONVERSION(); + + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonDepthCallback(odomMsg, userDataMsg, imageMsgs, depthMsgs, cameraInfoMsgs, scanMsg, scan3dMsg, odomInfoMsg); +} + +void CommonDataSubscriber::setupRGBD2Callbacks( + bool subscribeOdom, + bool subscribeUserData, + bool subscribeScan2d, + bool subscribeScan3d, + bool subscribeOdomInfo, + int queueSize, + bool approxSync) +{ + ROS_INFO("Setup rgbd2 callback"); + ros::NodeHandle nh; // public + ros::NodeHandle pnh("~"); // private + + rgbdSubs_.resize(2); + for(int i=0; i<2; ++i) + { + rgbdSubs_[i] = new message_filters::Subscriber; + rgbdSubs_[i]->subscribe(nh, uFormat("rgbd_image%d", i), 1); + } + if(subscribeOdom && subscribeUserData) + { + odomSub_.subscribe(nh, "odom", 1); + userDataSub_.subscribe(nh, "user_data", 1); + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL5(rgbd2OdomDataScan2d, approxSync, queueSize, odomSub_, userDataSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL5(rgbd2OdomDataScan3d, approxSync, queueSize, odomSub_, userDataSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL5(rgbd2OdomDataInfo, approxSync, queueSize, odomSub_, userDataSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), odomInfoSub_); + } + else + { + SYNC_DECL4(rgbd2OdomData, approxSync, queueSize, odomSub_, userDataSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1])); + } + } + else if(subscribeOdom) + { + odomSub_.subscribe(nh, "odom", 1); + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL4(rgbd2OdomScan2d, approxSync, queueSize, odomSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL4(rgbd2OdomScan3d, approxSync, queueSize, odomSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL4(rgbd2OdomInfo, approxSync, queueSize, odomSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), odomInfoSub_); + } + else + { + SYNC_DECL3(rgbd2Odom, approxSync, queueSize, odomSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1])); + } + } + else if(subscribeUserData) + { + userDataSub_.subscribe(nh, "user_data", 1); + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL4(rgbd2DataScan2d, approxSync, queueSize, userDataSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL4(rgbd2DataScan3d, approxSync, queueSize, userDataSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL4(rgbd2DataInfo, approxSync, queueSize, userDataSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1]), odomInfoSub_); + } + else + { + SYNC_DECL3(rgbd2Data, approxSync, queueSize, userDataSub_, (*rgbdSubs_[0]), (*rgbdSubs_[1])); + } + } + else + { + if(subscribeScan2d) + { + subscribedToScan2d_ = true; + scanSub_.subscribe(nh, "scan", 1); + SYNC_DECL3(rgbd2Scan2d, approxSync, queueSize, (*rgbdSubs_[0]), (*rgbdSubs_[1]), scanSub_); + } + else if(subscribeScan3d) + { + subscribedToScan3d_ = true; + scan3dSub_.subscribe(nh, "scan_cloud", 1); + SYNC_DECL3(rgbd2Scan3d, approxSync, queueSize, (*rgbdSubs_[0]), (*rgbdSubs_[1]), scan3dSub_); + } + else if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL3(rgbd2Info, approxSync, queueSize, (*rgbdSubs_[0]), (*rgbdSubs_[1]), odomInfoSub_); + } + else + { + SYNC_DECL2(rgbd2, approxSync, queueSize, (*rgbdSubs_[0]), (*rgbdSubs_[1])); + } + } +} + +} /* namespace rtabmap_ros */ diff --git a/src/impl/CommonDataSubscriberStereo.cpp b/src/impl/CommonDataSubscriberStereo.cpp new file mode 100644 index 00000000..4aa00a3f --- /dev/null +++ b/src/impl/CommonDataSubscriberStereo.cpp @@ -0,0 +1,138 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +namespace rtabmap_ros { + +// Stereo +void CommonDataSubscriber::stereoCallback( + const sensor_msgs::ImageConstPtr& leftImageMsg, + const sensor_msgs::ImageConstPtr& rightImageMsg, + const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, + const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg) +{ + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scanMsg; // null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonStereoCallback(odomMsg, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::stereoInfoCallback( + const sensor_msgs::ImageConstPtr& leftImageMsg, + const sensor_msgs::ImageConstPtr& rightImageMsg, + const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, + const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, + const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) +{ + nav_msgs::OdometryConstPtr odomMsg; // Null + sensor_msgs::LaserScanConstPtr scan2dMsg; // null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null + commonStereoCallback(odomMsg, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scan2dMsg, scan3dMsg, odomInfoMsg); +} + +// Stereo + Odom +void CommonDataSubscriber::stereoOdomCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const sensor_msgs::ImageConstPtr& leftImageMsg, + const sensor_msgs::ImageConstPtr& rightImageMsg, + const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, + const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg) +{ + sensor_msgs::LaserScanConstPtr scanMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null + rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null + commonStereoCallback(odomMsg, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scanMsg, scan3dMsg, odomInfoMsg); +} +void CommonDataSubscriber::stereoOdomInfoCallback( + const nav_msgs::OdometryConstPtr & odomMsg, + const sensor_msgs::ImageConstPtr& leftImageMsg, + const sensor_msgs::ImageConstPtr& rightImageMsg, + const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, + const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, + const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg) +{ + sensor_msgs::LaserScanConstPtr scan2dMsg; // Null + sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null + commonStereoCallback(odomMsg, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scan2dMsg, scan3dMsg, odomInfoMsg); +} + +void CommonDataSubscriber::setupStereoCallbacks( + bool subscribeOdom, + bool subscribeOdomInfo, + int queueSize, + bool approxSync) +{ + ROS_INFO("Setup stereo callback"); + ros::NodeHandle nh; // public + ros::NodeHandle pnh("~"); // private + + ros::NodeHandle left_nh(nh, "left"); + ros::NodeHandle right_nh(nh, "right"); + ros::NodeHandle left_pnh(pnh, "left"); + ros::NodeHandle right_pnh(pnh, "right"); + image_transport::ImageTransport left_it(left_nh); + image_transport::ImageTransport right_it(right_nh); + image_transport::TransportHints hintsLeft("raw", ros::TransportHints(), left_pnh); + image_transport::TransportHints hintsRight("raw", ros::TransportHints(), right_pnh); + + imageRectLeft_.subscribe(left_it, left_nh.resolveName("image_rect"), 1, hintsLeft); + imageRectRight_.subscribe(right_it, right_nh.resolveName("image_rect"), 1, hintsRight); + cameraInfoLeft_.subscribe(left_nh, "camera_info", 1); + cameraInfoRight_.subscribe(right_nh, "camera_info", 1); + + if(subscribeOdom) + { + odomSub_.subscribe(nh, "odom", 1); + + if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL6(stereoOdomInfo, approxSync, queueSize, odomSub_, imageRectLeft_, imageRectRight_, cameraInfoLeft_, cameraInfoRight_, odomInfoSub_); + } + else + { + SYNC_DECL5(stereoOdom, approxSync, queueSize, odomSub_, imageRectLeft_, imageRectRight_, cameraInfoLeft_, cameraInfoRight_); + } + } + else + { + if(subscribeOdomInfo) + { + subscribedToOdomInfo_ = true; + odomInfoSub_.subscribe(nh, "odom_info", 1); + SYNC_DECL5(stereoInfo, approxSync, queueSize, imageRectLeft_, imageRectRight_, cameraInfoLeft_, cameraInfoRight_, odomInfoSub_); + } + else + { + SYNC_DECL4(stereo, approxSync, queueSize, imageRectLeft_, imageRectRight_, cameraInfoLeft_, cameraInfoRight_); + } + } +} + +} /* namespace rtabmap_ros */ diff --git a/src/nodelets/rgbd_odometry.cpp b/src/nodelets/rgbd_odometry.cpp index f74dd76c..675fed7f 100644 --- a/src/nodelets/rgbd_odometry.cpp +++ b/src/nodelets/rgbd_odometry.cpp @@ -63,7 +63,8 @@ public: OdometryROS(false, true, false), approxSync_(0), exactSync_(0), - sync2_(0), + approxSync2_(0), + exactSync2_(0), queueSize_(5) { } @@ -78,9 +79,13 @@ public: { delete exactSync_; } - if(sync2_) + if(approxSync2_) { - delete sync2_; + delete approxSync2_; + } + if(exactSync2_) + { + delete exactSync2_; } } @@ -91,65 +96,50 @@ private: ros::NodeHandle & nh = getNodeHandle(); ros::NodeHandle & pnh = getPrivateNodeHandle(); - int depthCameras = 1; + int rgbdCameras = 1; bool approxSync = true; pnh.param("approx_sync", approxSync, approxSync); pnh.param("queue_size", queueSize_, queueSize_); - pnh.param("depth_cameras", depthCameras, depthCameras); - if(depthCameras <= 0) + if(pnh.hasParam("depth_cameras")) { - depthCameras = 1; + ROS_ERROR("\"depth_cameras\" parameter doesn't exist anymore. It is replaced by \"rgbd_cameras\" with the \"rgbd_image\" input topics."); } - if(depthCameras > 2) + pnh.param("rgbd_cameras", rgbdCameras, rgbdCameras); + if(rgbdCameras <= 0) + { + rgbdCameras = 1; + } + if(rgbdCameras > 2) { NODELET_FATAL("Only 2 cameras maximum supported yet."); } - if(depthCameras == 2) + if(rgbdCameras == 2) { - ros::NodeHandle rgb0_nh(nh, "rgb0"); - ros::NodeHandle depth0_nh(nh, "depth0"); - ros::NodeHandle rgb0_pnh(pnh, "rgb0"); - ros::NodeHandle depth0_pnh(pnh, "depth0"); - image_transport::ImageTransport rgb0_it(rgb0_nh); - image_transport::ImageTransport depth0_it(depth0_nh); - image_transport::TransportHints hintsRgb0("raw", ros::TransportHints(), rgb0_pnh); - image_transport::TransportHints hintsDepth0("raw", ros::TransportHints(), depth0_pnh); + rgbd_image1_sub_.subscribe(nh, "rgbd_image0", 1); + rgbd_image2_sub_.subscribe(nh, "rgbd_image1", 1); - image_mono_sub_.subscribe(rgb0_it, rgb0_nh.resolveName("image"), 1, hintsRgb0); - image_depth_sub_.subscribe(depth0_it, depth0_nh.resolveName("image"), 1, hintsDepth0); - info_sub_.subscribe(rgb0_nh, "camera_info", 1); - - ros::NodeHandle rgb1_nh(nh, "rgb1"); - ros::NodeHandle depth1_nh(nh, "depth1"); - ros::NodeHandle rgb1_pnh(pnh, "rgb1"); - ros::NodeHandle depth1_pnh(pnh, "depth1"); - image_transport::ImageTransport rgb1_it(rgb1_nh); - image_transport::ImageTransport depth1_it(depth1_nh); - image_transport::TransportHints hintsRgb1("raw", ros::TransportHints(), rgb1_pnh); - image_transport::TransportHints hintsDepth1("raw", ros::TransportHints(), depth1_pnh); - - image_mono2_sub_.subscribe(rgb1_it, rgb1_nh.resolveName("image"), 1, hintsRgb1); - image_depth2_sub_.subscribe(depth1_it, depth1_nh.resolveName("image"), 1, hintsDepth1); - info2_sub_.subscribe(rgb1_nh, "camera_info", 1); - - sync2_ = new message_filters::Synchronizer( - MySync2Policy(queueSize_), - image_mono_sub_, - image_depth_sub_, - info_sub_, - image_mono2_sub_, - image_depth2_sub_, - info2_sub_); - sync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2, _3, _4, _5, _6)); - NODELET_INFO("\n%s subscribed to (approx sync):\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", + if(approxSync) + { + approxSync2_ = new message_filters::Synchronizer( + MyApproxSync2Policy(queueSize_), + rgbd_image1_sub_, + rgbd_image2_sub_); + approxSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); + } + else + { + exactSync2_ = new message_filters::Synchronizer( + MyExactSync2Policy(queueSize_), + rgbd_image1_sub_, + rgbd_image2_sub_); + exactSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); + } + NODELET_INFO("\n%s subscribed to (%s sync):\n %s,\n %s", ros::this_node::getName().c_str(), - image_mono_sub_.getTopic().c_str(), - image_depth_sub_.getTopic().c_str(), - info_sub_.getTopic().c_str(), - image_mono2_sub_.getTopic().c_str(), - image_depth2_sub_.getTopic().c_str(), - info2_sub_.getTopic().c_str()); + approxSync?"approx":"exact", + rgbd_image1_sub_.getTopic().c_str(), + rgbd_image2_sub_.getTopic().c_str()); } else { @@ -197,6 +187,117 @@ private: uInsert(parameters, ParametersPair(Parameters::kRegStrategy(), "0")); } + void commonCallback( + const std::vector & rgbImages, + const std::vector & depthImages, + const std::vector& cameraInfos) + { + ROS_ASSERT(rgbImages.size() > 0 && rgbImages.size() == depthImages.size() && rgbImages.size() == cameraInfos.size()); + ros::Time higherStamp; + int imageWidth = rgbImages[0]->image.cols; + int imageHeight = rgbImages[0]->image.rows; + int cameraCount = rgbImages.size(); + cv::Mat rgb; + cv::Mat depth; + pcl::PointCloud scanCloud; + std::vector cameraModels; + for(unsigned int i=0; iencoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) ==0 || + rgbImages[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) ==0 || + rgbImages[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) ==0 || + rgbImages[i]->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || + rgbImages[i]->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || + !(depthImages[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_16UC1) == 0 || + depthImages[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_32FC1) == 0 || + depthImages[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0)) + { + NODELET_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8 and image_depth=32FC1,16UC1,mono16"); + return; + } + UASSERT_MSG(rgbImages[i]->image.cols == imageWidth && rgbImages[i]->image.rows == imageHeight, + uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", + imageWidth, + rgbImages[i]->image.cols, + imageHeight, + rgbImages[i]->image.rows).c_str()); + UASSERT_MSG(depthImages[i]->image.cols == imageWidth && depthImages[i]->image.rows == imageHeight, + uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", + imageWidth, + depthImages[i]->image.cols, + imageHeight, + depthImages[i]->image.rows).c_str()); + + ros::Time stamp = rgbImages[i]->header.stamp>depthImages[i]->header.stamp?rgbImages[i]->header.stamp:depthImages[i]->header.stamp; + + if(i == 0) + { + higherStamp = stamp; + } + else if(stamp > higherStamp) + { + higherStamp = stamp; + } + + Transform localTransform = getTransform(this->frameId(), rgbImages[i]->header.frame_id, stamp); + if(localTransform.isNull()) + { + return; + } + + cv_bridge::CvImageConstPtr ptrImage = rgbImages[i]; + if(rgbImages[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) !=0 && + rgbImages[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) != 0) + { + ptrImage = cv_bridge::cvtColor(rgbImages[i], "mono8"); + } + + cv_bridge::CvImageConstPtr ptrDepth = depthImages[i]; + cv::Mat subDepth = ptrDepth->image; + + // initialize + if(rgb.empty()) + { + rgb = cv::Mat(imageHeight, imageWidth*cameraCount, ptrImage->image.type()); + } + if(depth.empty()) + { + depth = cv::Mat(imageHeight, imageWidth*cameraCount, subDepth.type()); + } + + if(ptrImage->image.type() == rgb.type()) + { + ptrImage->image.copyTo(cv::Mat(rgb, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); + } + else + { + NODELET_ERROR("Some RGB images are not the same type!"); + return; + } + + if(subDepth.type() == depth.type()) + { + subDepth.copyTo(cv::Mat(depth, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); + } + else + { + NODELET_ERROR("Some Depth images are not the same type!"); + return; + } + + cameraModels.push_back(rtabmap_ros::cameraModelFromROS(cameraInfos[i], localTransform)); + } + + rtabmap::SensorData data( + rgb, + depth, + cameraModels, + 0, + rtabmap_ros::timestampFromROS(higherStamp)); + + this->processData(data, higherStamp); + } + void callback( const sensor_msgs::ImageConstPtr& image, const sensor_msgs::ImageConstPtr& depth, @@ -204,178 +305,32 @@ private: { if(!this->isPaused()) { - if(!(image->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) ==0 || - image->encoding.compare(sensor_msgs::image_encodings::MONO8) ==0 || - image->encoding.compare(sensor_msgs::image_encodings::MONO16) ==0 || - image->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || - image->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || - !(depth->encoding.compare(sensor_msgs::image_encodings::TYPE_16UC1)==0 || - depth->encoding.compare(sensor_msgs::image_encodings::TYPE_32FC1)==0 || - depth->encoding.compare(sensor_msgs::image_encodings::MONO16)==0)) - { - NODELET_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8 (mono8 " - "recommended) and image_depth=16UC1,32FC1,mono16. Types detected: %s %s", - image->encoding.c_str(), depth->encoding.c_str()); - return; - } + std::vector imageMsgs(1); + std::vector depthMsgs(1); + std::vector infoMsgs; + imageMsgs[0] = cv_bridge::toCvShare(image); + depthMsgs[0] = cv_bridge::toCvShare(depth); + infoMsgs.push_back(*cameraInfo); - // use the highest stamp to make sure that there will be no future interpolation required when synchronized with another node - ros::Time stamp = image->header.stamp > depth->header.stamp? image->header.stamp : depth->header.stamp; - - Transform localTransform = getTransform(this->frameId(), image->header.frame_id, stamp); - if(localTransform.isNull()) - { - return; - } - - if(image->data.size() && depth->data.size() && cameraInfo->K[4] != 0) - { - rtabmap::CameraModel rtabmapModel = rtabmap_ros::cameraModelFromROS(*cameraInfo, localTransform); - cv_bridge::CvImagePtr ptrImage = cv_bridge::toCvCopy(image, image->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1)==0?"":"mono8"); - cv_bridge::CvImagePtr ptrDepth = cv_bridge::toCvCopy(depth); - - rtabmap::SensorData data( - ptrImage->image, - ptrDepth->image, - rtabmapModel, - 0, - rtabmap_ros::timestampFromROS(stamp)); - - this->processData(data, stamp); - } + this->commonCallback(imageMsgs, depthMsgs, infoMsgs); } } void callback2( - const sensor_msgs::ImageConstPtr& image, - const sensor_msgs::ImageConstPtr& depth, - const sensor_msgs::CameraInfoConstPtr& cameraInfo, - const sensor_msgs::ImageConstPtr& image2, - const sensor_msgs::ImageConstPtr& depth2, - const sensor_msgs::CameraInfoConstPtr& cameraInfo2) + const rtabmap_ros::RGBDImageConstPtr& image, + const rtabmap_ros::RGBDImageConstPtr& image2) { if(!this->isPaused()) { - std::vector imageMsgs; - std::vector depthMsgs; - std::vector infoMsgs; - imageMsgs.push_back(image); - imageMsgs.push_back(image2); - depthMsgs.push_back(depth); - depthMsgs.push_back(depth2); - infoMsgs.push_back(cameraInfo); - infoMsgs.push_back(cameraInfo2); + std::vector imageMsgs(2); + std::vector depthMsgs(2); + std::vector infoMsgs; + rtabmap_ros::toCvShare(image, imageMsgs[0], depthMsgs[0]); + rtabmap_ros::toCvShare(image2, imageMsgs[1], depthMsgs[1]); + infoMsgs.push_back(image->cameraInfo); + infoMsgs.push_back(image2->cameraInfo); - ros::Time higherStamp; - int imageWidth = imageMsgs[0]->width; - int imageHeight = imageMsgs[0]->height; - int cameraCount = imageMsgs.size(); - cv::Mat rgb; - cv::Mat depth; - pcl::PointCloud scanCloud; - std::vector cameraModels; - for(unsigned int i=0; iencoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) ==0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) ==0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) ==0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0) || - !(depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_16UC1) == 0 || - depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_32FC1) == 0 || - depthMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0)) - { - NODELET_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8 and image_depth=32FC1,16UC1,mono16"); - return; - } - UASSERT_MSG(imageMsgs[i]->width == imageWidth && imageMsgs[i]->height == imageHeight, - uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", - imageWidth, - imageMsgs[i]->width, - imageHeight, - imageMsgs[i]->height).c_str()); - UASSERT_MSG(depthMsgs[i]->width == imageWidth && depthMsgs[i]->height == imageHeight, - uFormat("imageWidth=%d vs %d imageHeight=%d vs %d", - imageWidth, - depthMsgs[i]->width, - imageHeight, - depthMsgs[i]->height).c_str()); - - ros::Time stamp = imageMsgs[i]->header.stamp>depthMsgs[i]->header.stamp?imageMsgs[i]->header.stamp:depthMsgs[i]->header.stamp; - - if(i == 0) - { - higherStamp = stamp; - } - else if(stamp > higherStamp) - { - higherStamp = stamp; - } - - Transform localTransform = getTransform(this->frameId(), imageMsgs[i]->header.frame_id, stamp); - if(localTransform.isNull()) - { - return; - } - - cv_bridge::CvImageConstPtr ptrImage; - if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1)==0) - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i]); - } - else if(imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 || - imageMsgs[i]->encoding.compare(sensor_msgs::image_encodings::MONO16) == 0) - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i], "mono8"); - } - else - { - ptrImage = cv_bridge::toCvShare(imageMsgs[i], "bgr8"); - } - cv_bridge::CvImageConstPtr ptrDepth = cv_bridge::toCvShare(depthMsgs[i]); - cv::Mat subDepth = ptrDepth->image; - - // initialize - if(rgb.empty()) - { - rgb = cv::Mat(imageHeight, imageWidth*cameraCount, ptrImage->image.type()); - } - if(depth.empty()) - { - depth = cv::Mat(imageHeight, imageWidth*cameraCount, subDepth.type()); - } - - if(ptrImage->image.type() == rgb.type()) - { - ptrImage->image.copyTo(cv::Mat(rgb, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); - } - else - { - NODELET_ERROR("Some RGB images are not the same type!"); - return; - } - - if(subDepth.type() == depth.type()) - { - subDepth.copyTo(cv::Mat(depth, cv::Rect(i*imageWidth, 0, imageWidth, imageHeight))); - } - else - { - NODELET_ERROR("Some Depth images are not the same type!"); - return; - } - - cameraModels.push_back(rtabmap_ros::cameraModelFromROS(*infoMsgs[i], localTransform)); - } - - rtabmap::SensorData data( - rgb, - depth, - cameraModels, - 0, - rtabmap_ros::timestampFromROS(higherStamp)); - - this->processData(data, higherStamp); + this->commonCallback(imageMsgs, depthMsgs, infoMsgs); } } @@ -395,18 +350,23 @@ protected: exactSync_ = new message_filters::Synchronizer(MyExactSyncPolicy(queueSize_), image_mono_sub_, image_depth_sub_, info_sub_); exactSync_->registerCallback(boost::bind(&RGBDOdometry::callback, this, _1, _2, _3)); } - if(sync2_) + if(approxSync2_) { - delete sync2_; - sync2_ = new message_filters::Synchronizer( - MySync2Policy(queueSize_), - image_mono_sub_, - image_depth_sub_, - info_sub_, - image_mono2_sub_, - image_depth2_sub_, - info2_sub_); - sync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2, _3, _4, _5, _6)); + delete approxSync2_; + approxSync2_ = new message_filters::Synchronizer( + MyApproxSync2Policy(queueSize_), + rgbd_image1_sub_, + rgbd_image2_sub_); + approxSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); + } + if(exactSync2_) + { + delete exactSync2_; + exactSync2_ = new message_filters::Synchronizer( + MyExactSync2Policy(queueSize_), + rgbd_image1_sub_, + rgbd_image2_sub_); + exactSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); } } @@ -414,15 +374,18 @@ private: image_transport::SubscriberFilter image_mono_sub_; image_transport::SubscriberFilter image_depth_sub_; message_filters::Subscriber info_sub_; - image_transport::SubscriberFilter image_mono2_sub_; - image_transport::SubscriberFilter image_depth2_sub_; - message_filters::Subscriber info2_sub_; + + message_filters::Subscriber rgbd_image1_sub_; + message_filters::Subscriber rgbd_image2_sub_; + typedef message_filters::sync_policies::ApproximateTime MyApproxSyncPolicy; message_filters::Synchronizer * approxSync_; - typedef message_filters::sync_policies::ApproximateTime MyExactSyncPolicy; + typedef message_filters::sync_policies::ExactTime MyExactSyncPolicy; message_filters::Synchronizer * exactSync_; - typedef message_filters::sync_policies::ApproximateTime MySync2Policy; - message_filters::Synchronizer * sync2_; + typedef message_filters::sync_policies::ApproximateTime MyApproxSync2Policy; + message_filters::Synchronizer * approxSync2_; + typedef message_filters::sync_policies::ExactTime MyExactSync2Policy; + message_filters::Synchronizer * exactSync2_; int queueSize_; }; diff --git a/src/nodelets/rgbd_sync.cpp b/src/nodelets/rgbd_sync.cpp new file mode 100644 index 00000000..37c7851e --- /dev/null +++ b/src/nodelets/rgbd_sync.cpp @@ -0,0 +1,165 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include "rtabmap_ros/RGBDImage.h" + +#include "rtabmap/core/Compression.h" + +namespace rtabmap_ros +{ + +class RGBDSync : public nodelet::Nodelet +{ +public: + RGBDSync() : + approxSyncDepth_(0), + exactSyncDepth_(0) + {} + + virtual ~RGBDSync() + { + if(approxSyncDepth_) + delete approxSyncDepth_; + if(exactSyncDepth_) + delete exactSyncDepth_; + } + +private: + virtual void onInit() + { + ros::NodeHandle & nh = getNodeHandle(); + ros::NodeHandle & pnh = getPrivateNodeHandle(); + + int queueSize = 10; + bool approxSync = true; + pnh.param("approx_sync", approxSync, approxSync); + pnh.param("queue_size", queueSize, queueSize); + + NODELET_INFO("Approximate time sync = %s", approxSync?"true":"false"); + + rgbdImagePub_ = nh.advertise("rgbd_image", 1); + rgbdImageCompressedPub_ = nh.advertise("rgbd_image/compressed", 1); + + if(approxSync) + { + approxSyncDepth_ = new message_filters::Synchronizer(MyApproxSyncDepthPolicy(queueSize), imageSub_, imageDepthSub_, cameraInfoSub_); + approxSyncDepth_->registerCallback(boost::bind(&RGBDSync::callback, this, _1, _2, _3)); + } + else + { + exactSyncDepth_ = new message_filters::Synchronizer(MyExactSyncDepthPolicy(queueSize), imageSub_, imageDepthSub_, cameraInfoSub_); + exactSyncDepth_->registerCallback(boost::bind(&RGBDSync::callback, this, _1, _2, _3)); + } + + ros::NodeHandle rgb_nh(nh, "rgb"); + ros::NodeHandle depth_nh(nh, "depth"); + ros::NodeHandle rgb_pnh(pnh, "rgb"); + ros::NodeHandle depth_pnh(pnh, "depth"); + image_transport::ImageTransport rgb_it(rgb_nh); + image_transport::ImageTransport depth_it(depth_nh); + image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); + image_transport::TransportHints hintsDepth("raw", ros::TransportHints(), depth_pnh); + + imageSub_.subscribe(rgb_it, rgb_nh.resolveName("image"), 1, hintsRgb); + imageDepthSub_.subscribe(depth_it, depth_nh.resolveName("image"), 1, hintsDepth); + cameraInfoSub_.subscribe(rgb_nh, "camera_info", 1); + } + + void callback( + const sensor_msgs::ImageConstPtr& image, + const sensor_msgs::ImageConstPtr& depth, + const sensor_msgs::CameraInfoConstPtr& cameraInfo) + { + if(rgbdImagePub_.getNumSubscribers() || rgbdImageCompressedPub_.getNumSubscribers()) + { + rtabmap_ros::RGBDImage msg; + msg.header.frame_id = cameraInfo->header.frame_id; + msg.header.stamp = image->header.stamp>depth->header.stamp?image->header.stamp:depth->header.stamp; + msg.cameraInfo = *cameraInfo; + + if(rgbdImageCompressedPub_.getNumSubscribers()) + { + rtabmap_ros::RGBDImage msgCompressed = msg; + + cv_bridge::CvImageConstPtr imagePtr = cv_bridge::toCvShare(image); + imagePtr->toCompressedImageMsg(msgCompressed.rgbCompressed, cv_bridge::JPG); + + cv_bridge::CvImageConstPtr imageDepthPtr = cv_bridge::toCvShare(depth); + ROS_ASSERT(imageDepthPtr->image.type() == CV_32FC1 || imageDepthPtr->image.type() == CV_16UC1); + msgCompressed.depthCompressed.header = imageDepthPtr->header; + msgCompressed.depthCompressed.data = rtabmap::compressImage(imageDepthPtr->image, ".png"); + msgCompressed.depthCompressed.format = "png"; + + rgbdImageCompressedPub_.publish(msgCompressed); + } + + if(rgbdImagePub_.getNumSubscribers()) + { + msg.rgb = *image; + msg.depth = *depth; + rgbdImagePub_.publish(msg); + } + } + } + +private: + ros::Publisher rgbdImagePub_; + ros::Publisher rgbdImageCompressedPub_; + + image_transport::SubscriberFilter imageSub_; + image_transport::SubscriberFilter imageDepthSub_; + message_filters::Subscriber cameraInfoSub_; + + typedef message_filters::sync_policies::ApproximateTime MyApproxSyncDepthPolicy; + message_filters::Synchronizer * approxSyncDepth_; + + typedef message_filters::sync_policies::ExactTime MyExactSyncDepthPolicy; + message_filters::Synchronizer * exactSyncDepth_; +}; + +PLUGINLIB_EXPORT_CLASS(rtabmap_ros::RGBDSync, nodelet::Nodelet); +} + From 24afa89477897f9ce0948e0938d1a02ace4a4d32 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 26 Sep 2016 17:04:31 -0400 Subject: [PATCH 38/78] Updated test_rgbd_image.launch to use exact sync --- launch/tests/test_rgbd_image.launch | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launch/tests/test_rgbd_image.launch b/launch/tests/test_rgbd_image.launch index 8d423622..72a2eb83 100644 --- a/launch/tests/test_rgbd_image.launch +++ b/launch/tests/test_rgbd_image.launch @@ -28,6 +28,7 @@ + @@ -36,6 +37,7 @@ + From 5119b3ab90a2f9704ad4ae0f9890f71f18db1a2a Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 28 Sep 2016 12:08:11 -0400 Subject: [PATCH 39/78] Added warning after 10 seconds if any callback has not been called since the start (rtabmap, rtabmapviz and odometry nodes). --- include/rtabmap_ros/CommonDataSubscriber.h | 10 ++++ .../rtabmap_ros/CommonDataSubscriberDefines.h | 11 ++-- include/rtabmap_ros/OdometryROS.h | 8 +++ launch/rtabmap.launch | 6 ++- src/CommonDataSubscriber.cpp | 50 +++++++++++++++---- src/OdometryROS.cpp | 31 ++++++++++++ src/impl/CommonDataSubscriberRGBD.cpp | 3 +- src/impl/CommonDataSubscriberRGBD2.cpp | 1 + src/impl/CommonDataSubscriberStereo.cpp | 4 ++ src/nodelets/rgbd_odometry.cpp | 8 ++- src/nodelets/rgbdicp_odometry.cpp | 7 ++- src/nodelets/stereo_odometry.cpp | 4 +- 12 files changed, 122 insertions(+), 21 deletions(-) diff --git a/include/rtabmap_ros/CommonDataSubscriber.h b/include/rtabmap_ros/CommonDataSubscriber.h index f84a2152..bbc4b867 100644 --- a/include/rtabmap_ros/CommonDataSubscriber.h +++ b/include/rtabmap_ros/CommonDataSubscriber.h @@ -50,6 +50,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + namespace rtabmap_ros { class CommonDataSubscriber { @@ -97,6 +99,8 @@ protected: const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg); private: + void warningLoop(); + void callbackCalled() {callbackCalled_ = true;} void setupDepthCallbacks( bool subscribeOdom, bool subscribeUserData, @@ -127,8 +131,14 @@ private: int queueSize, bool approxSync); +protected: + std::string subscribedTopicsMsg_; + private: int queueSize_; + bool approxSync_; + boost::thread* warningThread_; + bool callbackCalled_; bool subscribedToDepth_; bool subscribedToStereo_; bool subscribedToRGBD_; diff --git a/include/rtabmap_ros/CommonDataSubscriberDefines.h b/include/rtabmap_ros/CommonDataSubscriberDefines.h index 8d5da016..ac55479d 100644 --- a/include/rtabmap_ros/CommonDataSubscriberDefines.h +++ b/include/rtabmap_ros/CommonDataSubscriberDefines.h @@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef INCLUDE_RTABMAP_ROS_COMMONDATASUBSCRIBERIMPL_H_ #define INCLUDE_RTABMAP_ROS_COMMONDATASUBSCRIBERIMPL_H_ +#include #define DATA_SYNC2(PREFIX, SYNC_NAME, MSG0, MSG1) \ typedef message_filters::sync_policies::SYNC_NAME##Time PREFIX##SYNC_NAME##SyncPolicy; \ @@ -98,7 +99,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1); \ PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2)); \ } \ - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s", \ + subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s", \ ros::this_node::getName().c_str(), \ APPROX?"approx":"exact", \ SUB0.getTopic().c_str(), \ @@ -117,7 +118,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2); \ PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3)); \ } \ - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s", \ + subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s", \ ros::this_node::getName().c_str(), \ APPROX?"approx":"exact", \ SUB0.getTopic().c_str(), \ @@ -137,7 +138,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3); \ PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4)); \ } \ - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", \ + subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", \ ros::this_node::getName().c_str(), \ APPROX?"approx":"exact", \ SUB0.getTopic().c_str(), \ @@ -158,7 +159,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3, SUB4); \ PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4, _5)); \ } \ - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", \ + subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", \ ros::this_node::getName().c_str(), \ approxSync?"approx":"exact", \ SUB0.getTopic().c_str(), \ @@ -180,7 +181,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSyncPolicy(QUEUE_SIZE), SUB0, SUB1, SUB2, SUB3, SUB4, SUB5); \ PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4, _5, _6)); \ } \ - ROS_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", \ + subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", \ ros::this_node::getName().c_str(), \ APPROX?"approx":"exact", \ SUB0.getTopic().c_str(), \ diff --git a/include/rtabmap_ros/OdometryROS.h b/include/rtabmap_ros/OdometryROS.h index 768ab57c..f674f0c8 100644 --- a/include/rtabmap_ros/OdometryROS.h +++ b/include/rtabmap_ros/OdometryROS.h @@ -41,6 +41,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + namespace rtabmap { class Odometry; } @@ -72,16 +74,22 @@ public: rtabmap::Transform getTransform(const std::string & fromFrameId, const std::string & toFrameId, const ros::Time & stamp) const; protected: + void startWarningThread(const std::string & subscribedTopicsMsg, bool approxSync); + void callbackCalled() {callbackCalled_ = true;} + virtual void flushCallbacks() = 0; tf::TransformListener & tfListener() {return tfListener_;} private: + void warningLoop(const std::string & subscribedTopicsMsg, bool approxSync); virtual void onInit(); virtual void onOdomInit() = 0; virtual void updateParameters(rtabmap::ParametersMap & parameters) {} private: rtabmap::Odometry * odometry_; + boost::thread * warningThread_; + bool callbackCalled_; // parameters std::string frameId_; diff --git a/launch/rtabmap.launch b/launch/rtabmap.launch index 33f6b5e1..a6e1158d 100644 --- a/launch/rtabmap.launch +++ b/launch/rtabmap.launch @@ -36,7 +36,10 @@ - + + + + @@ -163,6 +166,7 @@ + diff --git a/src/CommonDataSubscriber.cpp b/src/CommonDataSubscriber.cpp index c3c054aa..e8343e1a 100644 --- a/src/CommonDataSubscriber.cpp +++ b/src/CommonDataSubscriber.cpp @@ -32,6 +32,9 @@ namespace rtabmap_ros { CommonDataSubscriber::CommonDataSubscriber() : queueSize_(10), + approxSync_(true), + warningThread_(0), + callbackCalled_(false), subscribedToDepth_(true), subscribedToStereo_(false), subscribedToRGBD_(false), @@ -126,7 +129,6 @@ CommonDataSubscriber::CommonDataSubscriber() : bool subscribeOdomInfo = false; bool subscribeUserData = false; int rgbdCameras = 1; - bool approxSync = true; // ROS related parameters (private) pnh.param("subscribe_depth", subscribedToDepth_, subscribedToDepth_); @@ -170,7 +172,7 @@ CommonDataSubscriber::CommonDataSubscriber() : } if(subscribedToStereo_) { - approxSync = false; // default for stereo: exact sync + approxSync_ = false; // default for stereo: exact sync } std::string odomFrameId; @@ -186,11 +188,11 @@ CommonDataSubscriber::CommonDataSubscriber() : ROS_WARN("Parameter \"stereo_approx_sync\" has been renamed " "to \"approx_sync\"! Your value is still copied to " "corresponding parameter."); - pnh.param("stereo_approx_sync", approxSync, approxSync); + pnh.param("stereo_approx_sync", approxSync_, approxSync_); } else { - pnh.param("approx_sync", approxSync, approxSync); + pnh.param("approx_sync", approxSync_, approxSync_); } if(rgbdCameras <= 0 && subscribedToRGBD_) @@ -200,7 +202,7 @@ CommonDataSubscriber::CommonDataSubscriber() : ROS_INFO("%s: queue_size = %d", ros::this_node::getName().c_str(), queueSize_); ROS_INFO("%s: rgbd_cameras = %d", ros::this_node::getName().c_str(), rgbdCameras); - ROS_INFO("%s: approx_sync = %s", ros::this_node::getName().c_str(), approxSync?"true":"false"); + ROS_INFO("%s: approx_sync = %s", ros::this_node::getName().c_str(), approxSync_?"true":"false"); bool subscribeOdom = odomFrameId.empty(); if(subscribedToDepth_) @@ -212,7 +214,7 @@ CommonDataSubscriber::CommonDataSubscriber() : subscribeScan3d, subscribeOdomInfo, queueSize_, - approxSync); + approxSync_); } else if(subscribedToStereo_) { @@ -220,7 +222,7 @@ CommonDataSubscriber::CommonDataSubscriber() : subscribeOdom, subscribeOdomInfo, queueSize_, - approxSync); + approxSync_); } else if(subscribedToRGBD_) { @@ -233,7 +235,7 @@ CommonDataSubscriber::CommonDataSubscriber() : subscribeScan3d, subscribeOdomInfo, queueSize_, - approxSync); + approxSync_); } else { @@ -244,13 +246,23 @@ CommonDataSubscriber::CommonDataSubscriber() : subscribeScan3d, subscribeOdomInfo, queueSize_, - approxSync); + approxSync_); } } + if(subscribedToDepth_ || subscribedToStereo_ || subscribedToRGBD_) + { + warningThread_ = new boost::thread(boost::bind(&CommonDataSubscriber::warningLoop, this)); + ROS_INFO(subscribedTopicsMsg_.c_str()); + } } CommonDataSubscriber::~CommonDataSubscriber() { + if(warningThread_) + { + delete warningThread_; + } + // RGB + Depth SYNC_DEL(depth); SYNC_DEL(depthScan2d); @@ -337,6 +349,25 @@ CommonDataSubscriber::~CommonDataSubscriber() rgbdSubs_.clear(); } +void CommonDataSubscriber::warningLoop() +{ + ros::Duration r(10.0); + while(ros::ok() && !callbackCalled_) + { + r.sleep(); + if(ros::ok() && !callbackCalled_) + { + ROS_WARN("%s: Did not receive data since 10 seconds! Make sure the input topics are " + "published (\"$ rostopic hz my_topic\") and the timestamps in their " + "header are set. %s%s", + ros::this_node::getName().c_str(), + approxSync_?"":"Parameter \"approx_sync\" is false, which means that input " + "topics should have all the exact timestamp for the callback to be called.", + subscribedTopicsMsg_.c_str()); + } + } +} + void CommonDataSubscriber::commonSingleDepthCallback( const nav_msgs::OdometryConstPtr & odomMsg, const rtabmap_ros::UserDataConstPtr & userDataMsg, @@ -347,6 +378,7 @@ void CommonDataSubscriber::commonSingleDepthCallback( const sensor_msgs::PointCloud2ConstPtr& scan3dMsg, const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) { + callbackCalled(); std::vector imageMsgs; std::vector depthMsgs; std::vector cameraInfoMsgs; diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index 2e0db238..3f1f40e3 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -58,6 +58,8 @@ namespace rtabmap_ros { OdometryROS::OdometryROS(bool stereoParams, bool visParams, bool icpParams) : odometry_(0), + warningThread_(0), + callbackCalled_(false), frameId_("base_link"), odomFrameId_("odom"), groundTruthFrameId_(""), @@ -79,6 +81,10 @@ OdometryROS::OdometryROS(bool stereoParams, bool visParams, bool icpParams) : OdometryROS::~OdometryROS() { + if(warningThread_) + { + delete warningThread_; + } ros::NodeHandle & pnh = getPrivateNodeHandle(); if(pnh.ok()) { @@ -297,6 +303,31 @@ void OdometryROS::onInit() onOdomInit(); } +void OdometryROS::startWarningThread(const std::string & subscribedTopicsMsg, bool approxSync) +{ + warningThread_ = new boost::thread(boost::bind(&OdometryROS::warningLoop, this, subscribedTopicsMsg, approxSync)); + NODELET_INFO(subscribedTopicsMsg.c_str()); +} + +void OdometryROS::warningLoop(const std::string & subscribedTopicsMsg, bool approxSync) +{ + ros::Duration r(10.0); + while(ros::ok() && !callbackCalled_) + { + r.sleep(); + if(ros::ok() && !callbackCalled_) + { + ROS_WARN("%s: Did not receive data since 10 seconds! Make sure the input topics are " + "published (\"$ rostopic hz my_topic\") and the timestamps in their " + "header are set. %s%s", + ros::this_node::getName().c_str(), + approxSync?"":"Parameter \"approx_sync\" is false, which means that input " + "topics should have all the exact timestamp for the callback to be called.", + subscribedTopicsMsg.c_str()); + } + } +} + Transform OdometryROS::getTransform(const std::string & fromFrameId, const std::string & toFrameId, const ros::Time & stamp) const { // TF ready? diff --git a/src/impl/CommonDataSubscriberRGBD.cpp b/src/impl/CommonDataSubscriberRGBD.cpp index 091fb504..4ed8aeb9 100644 --- a/src/impl/CommonDataSubscriberRGBD.cpp +++ b/src/impl/CommonDataSubscriberRGBD.cpp @@ -377,7 +377,8 @@ void CommonDataSubscriber::setupRGBDCallbacks( { rgbdSub_ = nh.subscribe("rgbd_image", 1, &CommonDataSubscriber::rgbdCallback, this); - ROS_INFO("\n%s subscribed to:\n %s", + subscribedTopicsMsg_ = + uFormat("\n%s subscribed to:\n %s", ros::this_node::getName().c_str(), rgbdSub_.getTopic().c_str()); } diff --git a/src/impl/CommonDataSubscriberRGBD2.cpp b/src/impl/CommonDataSubscriberRGBD2.cpp index ed044aed..2816f507 100644 --- a/src/impl/CommonDataSubscriberRGBD2.cpp +++ b/src/impl/CommonDataSubscriberRGBD2.cpp @@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace rtabmap_ros { #define IMAGE_CONVERSION() \ + callbackCalled(); \ std::vector imageMsgs(2); \ std::vector depthMsgs(2); \ rtabmap_ros::toCvShare(image1Msg, imageMsgs[0], depthMsgs[0]); \ diff --git a/src/impl/CommonDataSubscriberStereo.cpp b/src/impl/CommonDataSubscriberStereo.cpp index 4aa00a3f..bcf98be5 100644 --- a/src/impl/CommonDataSubscriberStereo.cpp +++ b/src/impl/CommonDataSubscriberStereo.cpp @@ -36,6 +36,7 @@ void CommonDataSubscriber::stereoCallback( const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg) { + callbackCalled(); nav_msgs::OdometryConstPtr odomMsg; // Null sensor_msgs::LaserScanConstPtr scanMsg; // null sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null @@ -49,6 +50,7 @@ void CommonDataSubscriber::stereoInfoCallback( const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, const rtabmap_ros::OdomInfoConstPtr& odomInfoMsg) { + callbackCalled(); nav_msgs::OdometryConstPtr odomMsg; // Null sensor_msgs::LaserScanConstPtr scan2dMsg; // null sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null @@ -63,6 +65,7 @@ void CommonDataSubscriber::stereoOdomCallback( const sensor_msgs::CameraInfoConstPtr& leftCamInfoMsg, const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg) { + callbackCalled(); sensor_msgs::LaserScanConstPtr scanMsg; // Null sensor_msgs::PointCloud2ConstPtr scan3dMsg; // null rtabmap_ros::OdomInfoConstPtr odomInfoMsg; // null @@ -76,6 +79,7 @@ void CommonDataSubscriber::stereoOdomInfoCallback( const sensor_msgs::CameraInfoConstPtr& rightCamInfoMsg, const rtabmap_ros::OdomInfoConstPtr & odomInfoMsg) { + callbackCalled(); sensor_msgs::LaserScanConstPtr scan2dMsg; // Null sensor_msgs::PointCloud2ConstPtr scan3dMsg; // Null commonStereoCallback(odomMsg, leftImageMsg, rightImageMsg, leftCamInfoMsg, rightCamInfoMsg, scan2dMsg, scan3dMsg, odomInfoMsg); diff --git a/src/nodelets/rgbd_odometry.cpp b/src/nodelets/rgbd_odometry.cpp index 675fed7f..4d4d4a22 100644 --- a/src/nodelets/rgbd_odometry.cpp +++ b/src/nodelets/rgbd_odometry.cpp @@ -114,6 +114,7 @@ private: NODELET_FATAL("Only 2 cameras maximum supported yet."); } + std::string subscribedTopicsMsg; if(rgbdCameras == 2) { rgbd_image1_sub_.subscribe(nh, "rgbd_image0", 1); @@ -135,7 +136,7 @@ private: rgbd_image2_sub_); exactSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); } - NODELET_INFO("\n%s subscribed to (%s sync):\n %s,\n %s", + subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s", ros::this_node::getName().c_str(), approxSync?"approx":"exact", rgbd_image1_sub_.getTopic().c_str(), @@ -167,13 +168,14 @@ private: exactSync_->registerCallback(boost::bind(&RGBDOdometry::callback, this, _1, _2, _3)); } - NODELET_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s", + subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s", ros::this_node::getName().c_str(), approxSync?"approx":"exact", image_mono_sub_.getTopic().c_str(), image_depth_sub_.getTopic().c_str(), info_sub_.getTopic().c_str()); } + this->startWarningThread(subscribedTopicsMsg, approxSync); } virtual void updateParameters(ParametersMap & parameters) @@ -303,6 +305,7 @@ private: const sensor_msgs::ImageConstPtr& depth, const sensor_msgs::CameraInfoConstPtr& cameraInfo) { + callbackCalled(); if(!this->isPaused()) { std::vector imageMsgs(1); @@ -320,6 +323,7 @@ private: const rtabmap_ros::RGBDImageConstPtr& image, const rtabmap_ros::RGBDImageConstPtr& image2) { + callbackCalled(); if(!this->isPaused()) { std::vector imageMsgs(2); diff --git a/src/nodelets/rgbdicp_odometry.cpp b/src/nodelets/rgbdicp_odometry.cpp index 80f0de44..e717918c 100644 --- a/src/nodelets/rgbdicp_odometry.cpp +++ b/src/nodelets/rgbdicp_odometry.cpp @@ -125,6 +125,7 @@ private: image_depth_sub_.subscribe(depth_it, depth_nh.resolveName("image"), 1, hintsDepth); info_sub_.subscribe(rgb_nh, "camera_info", 1); + std::string subscribedTopicsMsg; if(subscribeScanCloud) { cloud_sub_.subscribe(nh, "scan_cloud", 1); @@ -139,7 +140,7 @@ private: exactCloudSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackCloud, this, _1, _2, _3, _4)); } - NODELET_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s, \n %s", + subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s, \n %s", ros::this_node::getName().c_str(), approxSync?"approx":"exact", image_mono_sub_.getTopic().c_str(), @@ -161,7 +162,7 @@ private: exactScanSync_->registerCallback(boost::bind(&RGBDICPOdometry::callbackScan, this, _1, _2, _3, _4)); } - NODELET_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s, \n %s", + subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s, \n %s", ros::this_node::getName().c_str(), approxSync?"approx":"exact", image_mono_sub_.getTopic().c_str(), @@ -169,6 +170,7 @@ private: info_sub_.getTopic().c_str(), scan_sub_.getTopic().c_str()); } + this->startWarningThread(subscribedTopicsMsg, approxSync); } virtual void updateParameters(ParametersMap & parameters) @@ -219,6 +221,7 @@ private: const sensor_msgs::LaserScanConstPtr& scanMsg, const sensor_msgs::PointCloud2ConstPtr& cloudMsg) { + callbackCalled(); if(!this->isPaused()) { if(!(image->encoding.compare(sensor_msgs::image_encodings::TYPE_8UC1) ==0 || diff --git a/src/nodelets/stereo_odometry.cpp b/src/nodelets/stereo_odometry.cpp index 3ff9b57e..d92b38a9 100644 --- a/src/nodelets/stereo_odometry.cpp +++ b/src/nodelets/stereo_odometry.cpp @@ -113,13 +113,14 @@ private: } - NODELET_INFO("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", + std::string subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", ros::this_node::getName().c_str(), approxSync?"approx":"exact", imageRectLeft_.getTopic().c_str(), imageRectRight_.getTopic().c_str(), cameraInfoLeft_.getTopic().c_str(), cameraInfoRight_.getTopic().c_str()); + this->startWarningThread(subscribedTopicsMsg, approxSync); } virtual void updateParameters(ParametersMap & parameters) @@ -139,6 +140,7 @@ private: const sensor_msgs::CameraInfoConstPtr& cameraInfoLeft, const sensor_msgs::CameraInfoConstPtr& cameraInfoRight) { + callbackCalled(); if(!this->isPaused()) { if(!(imageRectLeft->encoding.compare(sensor_msgs::image_encodings::MONO8) ==0 || From 5167492771c709e4f24637fc1bd0b6ad4f509838 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 29 Sep 2016 12:28:48 -0400 Subject: [PATCH 40/78] Fixed build error when rtabmap is not built with octomap --- src/CommonDataSubscriber.cpp | 2 +- src/MapsManager.cpp | 2 +- src/OdometryROS.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CommonDataSubscriber.cpp b/src/CommonDataSubscriber.cpp index e8343e1a..74152339 100644 --- a/src/CommonDataSubscriber.cpp +++ b/src/CommonDataSubscriber.cpp @@ -252,7 +252,7 @@ CommonDataSubscriber::CommonDataSubscriber() : if(subscribedToDepth_ || subscribedToStereo_ || subscribedToRGBD_) { warningThread_ = new boost::thread(boost::bind(&CommonDataSubscriber::warningLoop, this)); - ROS_INFO(subscribedTopicsMsg_.c_str()); + ROS_INFO("%s", subscribedTopicsMsg_.c_str()); } } diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index 2c38c7f5..35365ea4 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -417,7 +417,7 @@ std::map MapsManager::updateMapCaches( { if(updateGrid && gridMaps_.size() < 5) { - ROS_WARN("Many occupancy grids should be loaded (~%d), this may take a while to update the map(s)...", int(filteredPoses.size()-octomap_->addedNodes().size())); + ROS_WARN("Many occupancy grids should be loaded (~%d), this may take a while to update the map(s)...", int(filteredPoses.size()-gridMaps_.size())); longUpdate = true; } #ifdef WITH_OCTOMAP_ROS diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index 3f1f40e3..b3105987 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -306,7 +306,7 @@ void OdometryROS::onInit() void OdometryROS::startWarningThread(const std::string & subscribedTopicsMsg, bool approxSync) { warningThread_ = new boost::thread(boost::bind(&OdometryROS::warningLoop, this, subscribedTopicsMsg, approxSync)); - NODELET_INFO(subscribedTopicsMsg.c_str()); + NODELET_INFO("%s", subscribedTopicsMsg.c_str()); } void OdometryROS::warningLoop(const std::string & subscribedTopicsMsg, bool approxSync) From db91736443505cff7dacb7a4d25ab2922db35c9e Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 30 Sep 2016 16:21:06 -0400 Subject: [PATCH 41/78] Added rtabmap_ros/rtabmap nodelet #96 and test_rtabmap_nodelets.launch file as an example --- CMakeLists.txt | 5 +- include/rtabmap_ros/CommonDataSubscriber.h | 13 +- include/rtabmap_ros/CoreWrapper.h | 8 +- launch/tests/test_rtabmap_nodelets.launch | 49 ++++ nodelet_plugins.xml | 8 + src/CommonDataSubscriber.cpp | 27 ++- src/CoreNode.cpp | 19 +- src/CoreWrapper.cpp | 257 +++++++++++---------- src/GuiWrapper.cpp | 8 +- src/OdometryROS.cpp | 10 +- src/impl/CommonDataSubscriberDepth.cpp | 4 +- src/impl/CommonDataSubscriberRGBD.cpp | 4 +- src/impl/CommonDataSubscriberRGBD2.cpp | 4 +- src/impl/CommonDataSubscriberStereo.cpp | 4 +- src/nodelets/rgbd_odometry.cpp | 80 +++++-- 15 files changed, 320 insertions(+), 180 deletions(-) create mode 100644 launch/tests/test_rtabmap_nodelets.launch diff --git a/CMakeLists.txt b/CMakeLists.txt index b2998e1f..01e26c1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ SET(rtabmap_ros_lib_src src/MsgConversion.cpp src/MapsManager.cpp src/CommonDataSubscriber.cpp + src/CoreWrapper.cpp src/impl/CommonDataSubscriberDepth.cpp src/impl/CommonDataSubscriberStereo.cpp src/impl/CommonDataSubscriberRGBD.cpp @@ -270,8 +271,8 @@ IF(Qt5_FOUND) ENDIF(Qt5_FOUND) add_dependencies(rtabmap_ros ${${PROJECT_NAME}_EXPORTED_TARGETS}) -add_executable(rtabmap src/CoreNode.cpp src/CoreWrapper.cpp) -target_link_libraries(rtabmap rtabmap_ros ${Libraries}) +add_executable(rtabmap src/CoreNode.cpp) +target_link_libraries(rtabmap ${Libraries}) add_executable(rgbd_odometry src/RGBDOdometryNode.cpp) target_link_libraries(rgbd_odometry ${Libraries}) diff --git a/include/rtabmap_ros/CommonDataSubscriber.h b/include/rtabmap_ros/CommonDataSubscriber.h index bbc4b867..c915bccc 100644 --- a/include/rtabmap_ros/CommonDataSubscriber.h +++ b/include/rtabmap_ros/CommonDataSubscriber.h @@ -56,7 +56,7 @@ namespace rtabmap_ros { class CommonDataSubscriber { public: - CommonDataSubscriber(); + CommonDataSubscriber(bool gui); virtual ~CommonDataSubscriber(); bool isSubscribedToDepth() const {return subscribedToDepth_;} @@ -69,6 +69,7 @@ public: int getQueueSize() const {return queueSize_;} protected: + void setupCallbacks(ros::NodeHandle & nh, ros::NodeHandle & pnh); virtual void commonDepthCallback( const nav_msgs::OdometryConstPtr & odomMsg, const rtabmap_ros::UserDataConstPtr & userDataMsg, @@ -102,6 +103,8 @@ private: void warningLoop(); void callbackCalled() {callbackCalled_ = true;} void setupDepthCallbacks( + ros::NodeHandle & nh, + ros::NodeHandle & pnh, bool subscribeOdom, bool subscribeUserData, bool subscribeScan2d, @@ -110,11 +113,15 @@ private: int queueSize, bool approxSync); void setupStereoCallbacks( + ros::NodeHandle & nh, + ros::NodeHandle & pnh, bool subscribeOdom, bool subscribeOdomInfo, int queueSize, bool approxSync); void setupRGBDCallbacks( + ros::NodeHandle & nh, + ros::NodeHandle & pnh, bool subscribeOdom, bool subscribeUserData, bool subscribeScan2d, @@ -123,6 +130,8 @@ private: int queueSize, bool approxSync); void setupRGBD2Callbacks( + ros::NodeHandle & nh, + ros::NodeHandle & pnh, bool subscribeOdom, bool subscribeUserData, bool subscribeScan2d, @@ -133,9 +142,9 @@ private: protected: std::string subscribedTopicsMsg_; + int queueSize_; private: - int queueSize_; bool approxSync_; boost::thread* warningThread_; bool callbackCalled_; diff --git a/include/rtabmap_ros/CoreWrapper.h b/include/rtabmap_ros/CoreWrapper.h index 153cdbc4..6855aaf7 100644 --- a/include/rtabmap_ros/CoreWrapper.h +++ b/include/rtabmap_ros/CoreWrapper.h @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include #include @@ -71,14 +72,16 @@ class StereoDense; namespace rtabmap_ros { -class CoreWrapper : public CommonDataSubscriber +class CoreWrapper : public CommonDataSubscriber, public nodelet::Nodelet { public: - CoreWrapper(bool deleteDbOnStart, const rtabmap::ParametersMap & parameters); + CoreWrapper(); virtual ~CoreWrapper(); private: + virtual void onInit(); + bool odomUpdate(const nav_msgs::OdometryConstPtr & odomMsg); bool odomTFUpdate(const ros::Time & stamp); // TF odom @@ -242,6 +245,7 @@ private: MoveBaseClient mbClient_; boost::thread* transformThread_; + bool tfThreadRunning_; // for loop closure detection only image_transport::Subscriber defaultSub_; diff --git a/launch/tests/test_rtabmap_nodelets.launch b/launch/tests/test_rtabmap_nodelets.launch new file mode 100644 index 00000000..c5a31fc6 --- /dev/null +++ b/launch/tests/test_rtabmap_nodelets.launch @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nodelet_plugins.xml b/nodelet_plugins.xml index 3fad43f3..e440f931 100644 --- a/nodelet_plugins.xml +++ b/nodelet_plugins.xml @@ -1,5 +1,13 @@ + + + This is my nodelet. + + + diff --git a/src/CommonDataSubscriber.cpp b/src/CommonDataSubscriber.cpp index 74152339..e8cb84a3 100644 --- a/src/CommonDataSubscriber.cpp +++ b/src/CommonDataSubscriber.cpp @@ -29,13 +29,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace rtabmap_ros { - -CommonDataSubscriber::CommonDataSubscriber() : +CommonDataSubscriber::CommonDataSubscriber(bool gui) : queueSize_(10), approxSync_(true), warningThread_(0), callbackCalled_(false), - subscribedToDepth_(true), + subscribedToDepth_(!gui), subscribedToStereo_(false), subscribedToRGBD_(false), subscribedToScan2d_(false), @@ -122,8 +121,10 @@ CommonDataSubscriber::CommonDataSubscriber() : SYNC_INIT(rgbd2OdomDataInfo) { - ros::NodeHandle pnh("~"); +} +void CommonDataSubscriber::setupCallbacks(ros::NodeHandle & nh, ros::NodeHandle & pnh) +{ bool subscribeScan2d = false; bool subscribeScan3d = false; bool subscribeOdomInfo = false; @@ -208,6 +209,8 @@ CommonDataSubscriber::CommonDataSubscriber() : if(subscribedToDepth_) { setupDepthCallbacks( + nh, + pnh, subscribeOdom, subscribeUserData, subscribeScan2d, @@ -219,6 +222,8 @@ CommonDataSubscriber::CommonDataSubscriber() : else if(subscribedToStereo_) { setupStereoCallbacks( + nh, + pnh, subscribeOdom, subscribeOdomInfo, queueSize_, @@ -229,6 +234,8 @@ CommonDataSubscriber::CommonDataSubscriber() : if(rgbdCameras == 2) { setupRGBD2Callbacks( + nh, + pnh, subscribeOdom, subscribeUserData, subscribeScan2d, @@ -240,6 +247,8 @@ CommonDataSubscriber::CommonDataSubscriber() : else { setupRGBDCallbacks( + nh, + pnh, subscribeOdom, subscribeUserData, subscribeScan2d, @@ -260,6 +269,8 @@ CommonDataSubscriber::~CommonDataSubscriber() { if(warningThread_) { + callbackCalled(); + warningThread_->join(); delete warningThread_; } @@ -351,13 +362,13 @@ CommonDataSubscriber::~CommonDataSubscriber() void CommonDataSubscriber::warningLoop() { - ros::Duration r(10.0); - while(ros::ok() && !callbackCalled_) + ros::Duration r(5.0); + while(!callbackCalled_) { r.sleep(); - if(ros::ok() && !callbackCalled_) + if(!callbackCalled_) { - ROS_WARN("%s: Did not receive data since 10 seconds! Make sure the input topics are " + ROS_WARN("%s: Did not receive data since 5 seconds! Make sure the input topics are " "published (\"$ rostopic hz my_topic\") and the timestamps in their " "header are set. %s%s", ros::this_node::getName().c_str(), diff --git a/src/CoreNode.cpp b/src/CoreNode.cpp index 03dd7c3c..c3dc79a6 100644 --- a/src/CoreNode.cpp +++ b/src/CoreNode.cpp @@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include "nodelet/loader.h" int main(int argc, char** argv) { @@ -42,13 +43,10 @@ int main(int argc, char** argv) ros::init(argc, argv, "rtabmap"); bool deleteDbOnStart = false; + nodelet::V_string nargv; for(int i=1;i #include +#include "pluginlib/class_list_macros.h" #include #include @@ -75,7 +76,8 @@ using namespace rtabmap; namespace rtabmap_ros { -CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) : +CoreWrapper::CoreWrapper() : + CommonDataSubscriber(false), paused_(false), lastPose_(Transform::getIdentity()), lastPoseIntermediate_(false), @@ -100,6 +102,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) mapToOdom_(rtabmap::Transform::getIdentity()), mapsManager_(true), transformThread_(0), + tfThreadRunning_(false), stereoToDepth_(false), odomSensorSync_(false), rate_(Parameters::defaultRtabmapDetectionRate()), @@ -108,8 +111,14 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) previousStamp_(0), mbClient_("move_base", true) { - ros::NodeHandle nh; - ros::NodeHandle pnh("~"); +} + +void CoreWrapper::onInit() +{ + ros::NodeHandle & nh = getNodeHandle(); + ros::NodeHandle & pnh = getPrivateNodeHandle(); + + setupCallbacks(nh, pnh); bool publishTf = true; double tfDelay = 0.05; // 20 Hz @@ -126,7 +135,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) pnh.param("ground_truth_base_frame_id", groundTruthBaseFrameId_, frameId_); if(pnh.hasParam("depth_cameras") && !pnh.hasParam("depth_cameras")) { - ROS_ERROR("\"depth_cameras\" parameter doesn't exist " + NODELET_ERROR("\"depth_cameras\" parameter doesn't exist " "anymore! It is replaced by \"rgbd_cameras\" parameter " "used when \"subscribe_rgbd\" is true"); } @@ -147,7 +156,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) pnh.param("odom_sensor_sync", odomSensorSync_, odomSensorSync_); if(pnh.hasParam("flip_scan")) { - ROS_WARN("Parameter \"flip_scan\" doesn't exist anymore. Rtabmap now " + NODELET_WARN("Parameter \"flip_scan\" doesn't exist anymore. Rtabmap now " "detects automatically if the laser is upside down with /tf, then if so, it " "switches scan values."); } @@ -177,21 +186,21 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) // keep worldFrameId_ without prefix as it should be global } - ROS_INFO("rtabmap: frame_id = %s", frameId_.c_str()); + NODELET_INFO("rtabmap: frame_id = %s", frameId_.c_str()); if(!odomFrameId_.empty()) { - ROS_INFO("rtabmap: odom_frame_id = %s", odomFrameId_.c_str()); + NODELET_INFO("rtabmap: odom_frame_id = %s", odomFrameId_.c_str()); } if(!groundTruthFrameId_.empty()) { - ROS_INFO("rtabmap: ground_truth_frame_id = %s -> ground_truth_base_frame_id = %s", + NODELET_INFO("rtabmap: ground_truth_frame_id = %s -> ground_truth_base_frame_id = %s", groundTruthFrameId_.c_str(), groundTruthBaseFrameId_.c_str()); } - ROS_INFO("rtabmap: map_frame_id = %s", mapFrameId_.c_str()); - ROS_INFO("rtabmap: tf_delay = %f", tfDelay); - ROS_INFO("rtabmap: tf_tolerance = %f", tfTolerance); - ROS_INFO("rtabmap: odom_sensor_sync = %s", odomSensorSync_?"true":"false"); + NODELET_INFO("rtabmap: map_frame_id = %s", mapFrameId_.c_str()); + NODELET_INFO("rtabmap: tf_delay = %f", tfDelay); + NODELET_INFO("rtabmap: tf_tolerance = %f", tfTolerance); + NODELET_INFO("rtabmap: odom_sensor_sync = %s", odomSensorSync_?"true":"false"); infoPub_ = nh.advertise("info", 1); mapDataPub_ = nh.advertise("mapData", 1); @@ -237,7 +246,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) double vDouble; if(pnh.getParam(iter->first, vStr)) { - ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), vStr.c_str()); + NODELET_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), vStr.c_str()); if(iter->first.compare(Parameters::kRtabmapWorkingDirectory()) == 0) { @@ -251,26 +260,38 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) } else if(pnh.getParam(iter->first, vBool)) { - ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uBool2Str(vBool).c_str()); + NODELET_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uBool2Str(vBool).c_str()); uInsert(parameters_, ParametersPair(iter->first, uBool2Str(vBool))); } else if(pnh.getParam(iter->first, vDouble)) { - ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vDouble).c_str()); + NODELET_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vDouble).c_str()); uInsert(parameters_, ParametersPair(iter->first, uNumber2Str(vDouble))); } else if(pnh.getParam(iter->first, vInt)) { - ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vInt).c_str()); + NODELET_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vInt).c_str()); uInsert(parameters_, ParametersPair(iter->first, uNumber2Str(vInt))); } } - //update with input arguments + //parse input arguments + std::vector argList = getMyArgv(); + char * argv[argList.size()]; + bool deleteDbOnStart; + for(unsigned int i=0; ifirst, iter->second)); - ROS_INFO("Update RTAB-Map parameter \"%s\"=\"%s\" from arguments", iter->first.c_str(), iter->second.c_str()); + NODELET_INFO("Update RTAB-Map parameter \"%s\"=\"%s\" from arguments", iter->first.c_str(), iter->second.c_str()); } // Backward compatibility @@ -285,19 +306,19 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) { // can be migrated uInsert(parameters_, ParametersPair(iter->second.second, vStr)); - ROS_WARN("Rtabmap: Parameter name changed: \"%s\" -> \"%s\". Please update your launch file accordingly. Value \"%s\" is still set to the new parameter name.", + NODELET_WARN("Rtabmap: Parameter name changed: \"%s\" -> \"%s\". Please update your launch file accordingly. Value \"%s\" is still set to the new parameter name.", iter->first.c_str(), iter->second.second.c_str(), vStr.c_str()); } else { if(iter->second.second.empty()) { - ROS_ERROR("Rtabmap: Parameter \"%s\" doesn't exist anymore!", + NODELET_ERROR("Rtabmap: Parameter \"%s\" doesn't exist anymore!", iter->first.c_str()); } else { - ROS_ERROR("Rtabmap: Parameter \"%s\" doesn't exist anymore! You may look at this similar parameter: \"%s\"", + NODELET_ERROR("Rtabmap: Parameter \"%s\" doesn't exist anymore! You may look at this similar parameter: \"%s\"", iter->first.c_str(), iter->second.second.c_str()); } } @@ -309,7 +330,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) if((this->isSubscribedToScan2d() || this->isSubscribedToScan3d()) && parameters_.find(Parameters::kGridFromDepth()) == parameters_.end()) { - ROS_WARN("Setting \"%s\" parameter to false (default true) as \"subscribe_scan\" or \"subscribe_scan_cloud\" is " + NODELET_WARN("Setting \"%s\" parameter to false (default true) as \"subscribe_scan\" or \"subscribe_scan_cloud\" is " "true. The occupancy grid map will be constructed from " "laser scans. To get occupancy grid map from cloud projection, set \"%s\" " "to true. To suppress this warning, " @@ -332,14 +353,14 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) if(parameters_.find(Parameters::kRtabmapDetectionRate()) != parameters_.end()) { Parameters::parse(parameters_, Parameters::kRtabmapDetectionRate(), rate_); - ROS_INFO("RTAB-Map detection rate = %f Hz", rate_); + NODELET_INFO("RTAB-Map detection rate = %f Hz", rate_); } if(parameters_.find(Parameters::kRtabmapCreateIntermediateNodes()) != parameters_.end()) { Parameters::parse(parameters_, Parameters::kRtabmapCreateIntermediateNodes(), createIntermediateNodes_); if(createIntermediateNodes_) { - ROS_INFO("Create intermediate nodes"); + NODELET_INFO("Create intermediate nodes"); } } bool isRGBD = uStr2Bool(parameters_.at(Parameters::kRGBDEnabled()).c_str()); @@ -348,7 +369,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) // RGBD SLAM if(!this->isSubscribedToDepth() && !this->isSubscribedToStereo() && !this->isSubscribedToRGBD()) { - ROS_WARN("ROS param subscribe_depth, subscribe_stereo and subscribe_rgbd are false, but RTAB-Map " + NODELET_WARN("ROS param subscribe_depth, subscribe_stereo and subscribe_rgbd are false, but RTAB-Map " "parameter \"%s\" is true! Please set subscribe_depth, subscribe_stereo or subscribe_rgbd " "to true to use rtabmap node for RGB-D SLAM, or set \"%s\" to false for loop closure " "detection on images-only.", Parameters::kRGBDEnabled().c_str(), Parameters::kRGBDEnabled().c_str()); @@ -357,30 +378,30 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) if(paused_) { - ROS_WARN("Node paused... don't forget to call service \"resume\" to start rtabmap."); + NODELET_WARN("Node paused... don't forget to call service \"resume\" to start rtabmap."); } if(deleteDbOnStart) { if(UFile::erase(databasePath_) == 0) { - ROS_INFO("rtabmap: Deleted database \"%s\" (--delete_db_on_start is set).", databasePath_.c_str()); + NODELET_INFO("rtabmap: Deleted database \"%s\" (--delete_db_on_start is set).", databasePath_.c_str()); } } if(databasePath_.size()) { - ROS_INFO("rtabmap: Using database from \"%s\".", databasePath_.c_str()); + NODELET_INFO("rtabmap: Using database from \"%s\".", databasePath_.c_str()); } else { - ROS_INFO("rtabmap: database_path parameter not set, the map will not be saved."); + NODELET_INFO("rtabmap: database_path parameter not set, the map will not be saved."); } mapsManager_.setParameters(parameters_); if(this->isSubscribedToStereo()) { - ROS_INFO("rtabmap: stereo_to_depth = %s", stereoToDepth_?"true":"false"); + NODELET_INFO("rtabmap: stereo_to_depth = %s", stereoToDepth_?"true":"false"); } // Init RTAB-Map @@ -388,7 +409,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) if(databasePath_.size() && rtabmap_.getMemory()) { - ROS_INFO("rtabmap: Database version = \"%s\".", rtabmap_.getMemory()->getDatabaseVersion().c_str()); + NODELET_INFO("rtabmap: Database version = \"%s\".", rtabmap_.getMemory()->getDatabaseVersion().c_str()); } // setup services @@ -425,6 +446,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) Parameters::parse(parameters_, Parameters::kOptimizerIterations(), optimizeIterations); if(publishTf && optimizeIterations != 0) { + tfThreadRunning_ = true; transformThread_ = new boost::thread(boost::bind(&CoreWrapper::publishLoop, this, tfDelay, tfTolerance)); } else if(publishTf) @@ -441,7 +463,7 @@ CoreWrapper::CoreWrapper(bool deleteDbOnStart, const ParametersMap & parameters) image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); defaultSub_ = rgb_it.subscribe("image", 1, &CoreWrapper::defaultCallback, this); - ROS_INFO("\n%s subscribed to:\n %s", ros::this_node::getName().c_str(), defaultSub_.getTopic().c_str()); + NODELET_INFO("\n%s subscribed to:\n %s", ros::this_node::getName().c_str(), defaultSub_.getTopic().c_str()); } } @@ -449,6 +471,7 @@ CoreWrapper::~CoreWrapper() { if(transformThread_) { + tfThreadRunning_ = false; transformThread_->join(); delete transformThread_; } @@ -469,10 +492,10 @@ void CoreWrapper::loadParameters(const std::string & configFile, ParametersMap & { if(!configFile.empty()) { - ROS_INFO("Loading parameters from %s", configFile.c_str()); + NODELET_INFO("Loading parameters from %s", configFile.c_str()); if(!UFile::exists(configFile.c_str())) { - ROS_WARN("Config file doesn't exist! It will be generated..."); + NODELET_WARN("Config file doesn't exist! It will be generated..."); } Parameters::readINI(configFile.c_str(), parameters); } @@ -492,7 +515,7 @@ void CoreWrapper::saveParameters(const std::string & configFile) } else { - ROS_INFO("Parameters are not saved! (No configuration file provided...)"); + NODELET_INFO("Parameters are not saved! (No configuration file provided...)"); } } @@ -501,7 +524,7 @@ void CoreWrapper::publishLoop(double tfDelay, double tfTolerance) if(tfDelay == 0) return; ros::Rate r(1.0 / tfDelay); - while(ros::ok()) + while(tfThreadRunning_) { if(!odomFrameId_.empty()) { @@ -537,7 +560,7 @@ void CoreWrapper::defaultCallback(const sensor_msgs::ImageConstPtr & imageMsg) imageMsg->encoding.compare(sensor_msgs::image_encodings::BGR8) == 0 || imageMsg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0)) { - ROS_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8"); + NODELET_ERROR("Input type must be image=mono8,mono16,rgb8,bgr8"); return; } @@ -558,7 +581,7 @@ void CoreWrapper::defaultCallback(const sensor_msgs::ImageConstPtr & imageMsg) { if(!rtabmap_.process(ptrImage->image.clone(), ptrImage->header.seq)) { - ROS_WARN("RTAB-Map could not process the data received! (ROS id = %d)", ptrImage->header.seq); + NODELET_WARN("RTAB-Map could not process the data received! (ROS id = %d)", ptrImage->header.seq); } else { @@ -567,13 +590,13 @@ void CoreWrapper::defaultCallback(const sensor_msgs::ImageConstPtr & imageMsg) } else if(!rtabmap_.isIDsGenerated()) { - ROS_WARN("Ignoring received image because its sequence ID=0. Please " + NODELET_WARN("Ignoring received image because its sequence ID=0. Please " "set \"Mem/GenerateIds\"=\"true\" to ignore ros generated sequence id. " "Use only \"Mem/GenerateIds\"=\"false\" for once-time run of RTAB-Map and " "when you need to have IDs output of RTAB-map synchronised with the source " "image sequence ID."); } - ROS_INFO("rtabmap: Update rate=%fs, Limit=%fs, Processing time = %fs (%d local nodes)", + NODELET_INFO("rtabmap: Update rate=%fs, Limit=%fs, Processing time = %fs (%d local nodes)", 1.0f/rate_, rtabmap_.getTimeThreshold()/1000.0f, timer.ticks(), @@ -776,7 +799,7 @@ void CoreWrapper::commonDepthCallbackImpl( tfListener_, waitForTransform_?waitForTransformDuration_:0.0)) { - ROS_ERROR("Could not convert rgb/depth msgs! Aborting rtabmap update..."); + NODELET_ERROR("Could not convert rgb/depth msgs! Aborting rtabmap update..."); return; } @@ -787,7 +810,7 @@ void CoreWrapper::commonDepthCallbackImpl( static bool shown = false; if(!shown) { - ROS_WARN("Save depth data to 16 bits format: depth type detected is " + NODELET_WARN("Save depth data to 16 bits format: depth type detected is " "32FC1, use 16UC1 depth format to avoid this conversion " "(or set parameter \"Mem/SaveDepth16Format=false\" to use " "32bits format). This message is only printed once..."); @@ -821,7 +844,7 @@ void CoreWrapper::commonDepthCallbackImpl( tfListener_, waitForTransform_?waitForTransformDuration_:0)) { - ROS_ERROR("Could not convert laser scan msg! Aborting rtabmap update..."); + NODELET_ERROR("Could not convert laser scan msg! Aborting rtabmap update..."); return; } Transform zAxis(0,0,1,0,0,0); @@ -845,7 +868,7 @@ void CoreWrapper::commonDepthCallbackImpl( tfListener_, waitForTransform_?waitForTransformDuration_:0)) { - ROS_ERROR("Could not convert 3d laser scan msg! Aborting rtabmap update..."); + NODELET_ERROR("Could not convert 3d laser scan msg! Aborting rtabmap update..."); return; } } @@ -942,7 +965,7 @@ void CoreWrapper::commonStereoCallback( tfListener_, waitForTransform_?waitForTransformDuration_:0.0)) { - ROS_ERROR("Could not convert stereo msgs! Aborting rtabmap update..."); + NODELET_ERROR("Could not convert stereo msgs! Aborting rtabmap update..."); return; } @@ -955,7 +978,7 @@ void CoreWrapper::commonStereoCallback( parameters_); if(disparity.empty()) { - ROS_ERROR("Could not compute disparity image (\"stereo_to_depth\" is true)!"); + NODELET_ERROR("Could not compute disparity image (\"stereo_to_depth\" is true)!"); return; } cv::Mat depth = rtabmap::util2d::depthFromDisparity( @@ -965,7 +988,7 @@ void CoreWrapper::commonStereoCallback( if(depth.empty()) { - ROS_ERROR("Could not compute depth image (\"stereo_to_depth\" is true)!"); + NODELET_ERROR("Could not compute depth image (\"stereo_to_depth\" is true)!"); return; } UASSERT(depth.type() == CV_16UC1 || depth.type() == CV_32FC1); @@ -1007,7 +1030,7 @@ void CoreWrapper::commonStereoCallback( tfListener_, waitForTransform_?waitForTransformDuration_:0)) { - ROS_ERROR("Could not convert laser scan msg! Aborting rtabmap update..."); + NODELET_ERROR("Could not convert laser scan msg! Aborting rtabmap update..."); return; } Transform zAxis(0,0,1,0,0,0); @@ -1031,7 +1054,7 @@ void CoreWrapper::commonStereoCallback( tfListener_, waitForTransform_?waitForTransformDuration_:0)) { - ROS_ERROR("Could not convert 3d laser scan msg! Aborting rtabmap update..."); + NODELET_ERROR("Could not convert 3d laser scan msg! Aborting rtabmap update..."); return; } } @@ -1089,7 +1112,7 @@ void CoreWrapper::process( if(data.id() < 0) { - ROS_INFO("Intermediate node added"); + NODELET_INFO("Intermediate node added"); } else { @@ -1124,11 +1147,11 @@ void CoreWrapper::process( if(rtabmap_.getPathStatus() > 0) { // Goal reached - ROS_INFO("Planning: Publishing goal reached!"); + NODELET_INFO("Planning: Publishing goal reached!"); } else { - ROS_WARN("Planning: Plan failed!"); + NODELET_WARN("Planning: Plan failed!"); if(mbClient_.isServerConnected()) { mbClient_.cancelGoal(); @@ -1170,7 +1193,7 @@ void CoreWrapper::process( } else { - ROS_ERROR("Planning: Local map broken, current goal id=%d (the robot may have moved to far from planned nodes)", + NODELET_ERROR("Planning: Local map broken, current goal id=%d (the robot may have moved to far from planned nodes)", rtabmap_.getPathCurrentGoalId()); rtabmap_.clearPath(-1); if(goalReachedPub_.getNumSubscribers()) @@ -1192,7 +1215,7 @@ void CoreWrapper::process( { timeRtabmap = timer.ticks(); } - ROS_INFO("rtabmap (%d): Rate=%.2fs, Limit=%.3fs, RTAB-Map=%.4fs, Maps update=%.4fs pub=%.4fs (local map=%d, WM=%d)", + NODELET_INFO("rtabmap (%d): Rate=%.2fs, Limit=%.3fs, RTAB-Map=%.4fs, Maps update=%.4fs pub=%.4fs (local map=%d, WM=%d)", rtabmap_.getLastLocationId(), rate_>0?1.0f/rate_:0, rtabmap_.getTimeThreshold()/1000.0f, @@ -1204,7 +1227,7 @@ void CoreWrapper::process( } else if(!rtabmap_.isIDsGenerated()) { - ROS_WARN("Ignoring received image because its sequence ID=0. Please " + NODELET_WARN("Ignoring received image because its sequence ID=0. Please " "set \"Mem/GenerateIds\"=\"true\" to ignore ros generated sequence id. " "Use only \"Mem/GenerateIds\"=\"false\" for once-time run of RTAB-Map and " "when you need to have IDs output of RTAB-map synchronised with the source " @@ -1228,11 +1251,11 @@ void CoreWrapper::goalCommonCallback( if(id > 0) { - ROS_INFO("Planning: set goal %d", id); + NODELET_INFO("Planning: set goal %d", id); } else if(!pose.isNull()) { - ROS_INFO("Planning: set goal %s", pose.prettyPrint().c_str()); + NODELET_INFO("Planning: set goal %s", pose.prettyPrint().c_str()); } if(planningTime) @@ -1248,14 +1271,14 @@ void CoreWrapper::goalCommonCallback( { *planningTime = timer.elapsed(); } - ROS_INFO("Planning: Time computing path = %f s", timer.ticks()); + NODELET_INFO("Planning: Time computing path = %f s", timer.ticks()); const std::vector > & poses = rtabmap_.getPath(); currentMetricGoal_.setNull(); latestNodeWasReached_ = false; if(poses.size() == 0) { - ROS_WARN("Planning: Goal already reached (RGBD/GoalReachedRadius=%fm).", + NODELET_WARN("Planning: Goal already reached (RGBD/GoalReachedRadius=%fm).", rtabmap_.getGoalReachedRadius()); rtabmap_.clearPath(1); if(goalReachedPub_.getNumSubscribers()) @@ -1271,7 +1294,7 @@ void CoreWrapper::goalCommonCallback( currentMetricGoal_ = rtabmap_.getPose(rtabmap_.getPathCurrentGoalId()); if(!currentMetricGoal_.isNull()) { - ROS_INFO("Planning: Path successfully created (size=%d)", (int)poses.size()); + NODELET_INFO("Planning: Path successfully created (size=%d)", (int)poses.size()); // Adjust the target pose relative to last node if(rtabmap_.getPathCurrentGoalId() == rtabmap_.getPath().back().first && rtabmap_.getLocalOptimizedPoses().size()) @@ -1297,33 +1320,33 @@ void CoreWrapper::goalCommonCallback( } stream << iter->first; } - ROS_INFO("Global path: [%s]", stream.str().c_str()); + NODELET_INFO("Global path: [%s]", stream.str().c_str()); success=true; } else { - ROS_ERROR("Pose of node %d not found!? Cannot send a metric goal...", rtabmap_.getPathCurrentGoalId()); + NODELET_ERROR("Pose of node %d not found!? Cannot send a metric goal...", rtabmap_.getPathCurrentGoalId()); } } } else if(!label.empty()) { - ROS_ERROR("Planning: Node with label \"%s\" not found!", label.c_str()); + NODELET_ERROR("Planning: Node with label \"%s\" not found!", label.c_str()); } else if(pose.isNull()) { if(id > 0) { - ROS_ERROR("Planning: Could not plan to node %d! The node is not in map's graph (look for warnings before this message for more details).", id); + NODELET_ERROR("Planning: Could not plan to node %d! The node is not in map's graph (look for warnings before this message for more details).", id); } else { - ROS_ERROR("Planning: Node id should be > 0 !"); + NODELET_ERROR("Planning: Node id should be > 0 !"); } } else { - ROS_ERROR("Planning: A node near the goal's pose not found! The pose may be to far from the graph (RGBD/LocalRadius=%f m)", rtabmap_.getLocalRadius()); + NODELET_ERROR("Planning: A node near the goal's pose not found! The pose may be to far from the graph (RGBD/LocalRadius=%f m)", rtabmap_.getLocalRadius()); } if(!success) @@ -1343,7 +1366,7 @@ void CoreWrapper::goalCallback(const geometry_msgs::PoseStampedConstPtr & msg) Transform targetPose = rtabmap_ros::transformFromPoseMsg(msg->pose); if(targetPose.isNull()) { - ROS_ERROR("Pose received is null!"); + NODELET_ERROR("Pose received is null!"); return; } @@ -1353,7 +1376,7 @@ void CoreWrapper::goalCallback(const geometry_msgs::PoseStampedConstPtr & msg) Transform t = rtabmap_ros::getTransform(mapFrameId_, msg->header.frame_id, msg->header.stamp, tfListener_, waitForTransform_?waitForTransformDuration_:0.0); if(t.isNull()) { - ROS_ERROR("Cannot transform goal pose from \"%s\" frame to \"%s\" frame!", + NODELET_ERROR("Cannot transform goal pose from \"%s\" frame to \"%s\" frame!", msg->header.frame_id.c_str(), mapFrameId_.c_str()); return; } @@ -1367,7 +1390,7 @@ void CoreWrapper::goalNodeCallback(const rtabmap_ros::GoalConstPtr & msg) { if(msg->node_id <= 0 && msg->node_label.empty()) { - ROS_ERROR("Node id or label should be set!"); + NODELET_ERROR("Node id or label should be set!"); return; } goalCommonCallback(msg->node_id, msg->node_label, Transform(), msg->header.stamp); @@ -1384,30 +1407,30 @@ bool CoreWrapper::updateRtabmapCallback(std_srvs::Empty::Request&, std_srvs::Emp double vDouble; if(nh.getParam(iter->first, vStr)) { - ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), vStr.c_str()); + NODELET_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), vStr.c_str()); iter->second = vStr; } else if(nh.getParam(iter->first, vBool)) { - ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uBool2Str(vBool).c_str()); + NODELET_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uBool2Str(vBool).c_str()); iter->second = uBool2Str(vBool); } else if(nh.getParam(iter->first, vInt)) { - ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vInt).c_str()); + NODELET_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vInt).c_str()); iter->second = uNumber2Str(vInt).c_str(); } else if(nh.getParam(iter->first, vDouble)) { - ROS_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vDouble).c_str()); + NODELET_INFO("Setting RTAB-Map parameter \"%s\"=\"%s\"", iter->first.c_str(), uNumber2Str(vDouble).c_str()); iter->second = uNumber2Str(vDouble).c_str(); } } - ROS_INFO("rtabmap: Updating parameters"); + NODELET_INFO("rtabmap: Updating parameters"); if(parameters_.find(Parameters::kRtabmapDetectionRate()) != parameters_.end()) { rate_ = uStr2Float(parameters_.at(Parameters::kRtabmapDetectionRate())); - ROS_INFO("RTAB-Map rate detection = %f Hz", rate_); + NODELET_INFO("RTAB-Map rate detection = %f Hz", rate_); } rtabmap_.parseParameters(parameters_); mapsManager_.setParameters(parameters_); @@ -1416,7 +1439,7 @@ bool CoreWrapper::updateRtabmapCallback(std_srvs::Empty::Request&, std_srvs::Emp bool CoreWrapper::resetRtabmapCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("rtabmap: Reset"); + NODELET_INFO("rtabmap: Reset"); rtabmap_.resetMemory(); rotVariance_ = 0; transVariance_ = 0; @@ -1433,12 +1456,12 @@ bool CoreWrapper::pauseRtabmapCallback(std_srvs::Empty::Request&, std_srvs::Empt { if(paused_) { - ROS_WARN("rtabmap: Already paused!"); + NODELET_WARN("rtabmap: Already paused!"); } else { paused_ = true; - ROS_INFO("rtabmap: paused!"); + NODELET_INFO("rtabmap: paused!"); ros::NodeHandle nh; nh.setParam("is_rtabmap_paused", true); } @@ -1449,12 +1472,12 @@ bool CoreWrapper::resumeRtabmapCallback(std_srvs::Empty::Request&, std_srvs::Emp { if(!paused_) { - ROS_WARN("rtabmap: Already running!"); + NODELET_WARN("rtabmap: Already running!"); } else { paused_ = false; - ROS_INFO("rtabmap: resumed!"); + NODELET_INFO("rtabmap: resumed!"); ros::NodeHandle nh; nh.setParam("is_rtabmap_paused", false); } @@ -1463,16 +1486,16 @@ bool CoreWrapper::resumeRtabmapCallback(std_srvs::Empty::Request&, std_srvs::Emp bool CoreWrapper::triggerNewMapCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("rtabmap: Trigger new map"); + NODELET_INFO("rtabmap: Trigger new map"); rtabmap_.triggerNewMap(); return true; } bool CoreWrapper::backupDatabaseCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("Backup: Saving memory..."); + NODELET_INFO("Backup: Saving memory..."); rtabmap_.close(); - ROS_INFO("Backup: Saving memory... done!"); + NODELET_INFO("Backup: Saving memory... done!"); rotVariance_ = 0; transVariance_ = 0; @@ -1480,20 +1503,20 @@ bool CoreWrapper::backupDatabaseCallback(std_srvs::Empty::Request&, std_srvs::Em currentMetricGoal_.setNull(); latestNodeWasReached_ = false; - ROS_INFO("Backup: Saving \"%s\" to \"%s\"...", databasePath_.c_str(), (databasePath_+".back").c_str()); + NODELET_INFO("Backup: Saving \"%s\" to \"%s\"...", databasePath_.c_str(), (databasePath_+".back").c_str()); UFile::copy(databasePath_, databasePath_+".back"); - ROS_INFO("Backup: Saving \"%s\" to \"%s\"... done!", databasePath_.c_str(), (databasePath_+".back").c_str()); + NODELET_INFO("Backup: Saving \"%s\" to \"%s\"... done!", databasePath_.c_str(), (databasePath_+".back").c_str()); - ROS_INFO("Backup: Reloading memory..."); + NODELET_INFO("Backup: Reloading memory..."); rtabmap_.init(parameters_, databasePath_); - ROS_INFO("Backup: Reloading memory... done!"); + NODELET_INFO("Backup: Reloading memory... done!"); return true; } bool CoreWrapper::setModeLocalizationCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("rtabmap: Set localization mode"); + NODELET_INFO("rtabmap: Set localization mode"); rtabmap::ParametersMap parameters; parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemIncrementalMemory(), "false")); rtabmap_.parseParameters(parameters); @@ -1502,7 +1525,7 @@ bool CoreWrapper::setModeLocalizationCallback(std_srvs::Empty::Request&, std_srv bool CoreWrapper::setModeMappingCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("rtabmap: Set mapping mode"); + NODELET_INFO("rtabmap: Set mapping mode"); rtabmap::ParametersMap parameters; parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemIncrementalMemory(), "true")); rtabmap_.parseParameters(parameters); @@ -1511,32 +1534,32 @@ bool CoreWrapper::setModeMappingCallback(std_srvs::Empty::Request&, std_srvs::Em bool CoreWrapper::setLogDebug(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("rtabmap: Set log level to Debug"); + NODELET_INFO("rtabmap: Set log level to Debug"); ULogger::setLevel(ULogger::kDebug); return true; } bool CoreWrapper::setLogInfo(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("rtabmap: Set log level to Info"); + NODELET_INFO("rtabmap: Set log level to Info"); ULogger::setLevel(ULogger::kInfo); return true; } bool CoreWrapper::setLogWarn(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("rtabmap: Set log level to Warning"); + NODELET_INFO("rtabmap: Set log level to Warning"); ULogger::setLevel(ULogger::kWarning); return true; } bool CoreWrapper::setLogError(std_srvs::Empty::Request&, std_srvs::Empty::Response&) { - ROS_INFO("rtabmap: Set log level to Error"); + NODELET_INFO("rtabmap: Set log level to Error"); ULogger::setLevel(ULogger::kError); return true; } bool CoreWrapper::getMapDataCallback(rtabmap_ros::GetMap::Request& req, rtabmap_ros::GetMap::Response& res) { - ROS_INFO("rtabmap: Getting map (global=%s optimized=%s graphOnly=%s)...", + NODELET_INFO("rtabmap: Getting map (global=%s optimized=%s graphOnly=%s)...", req.global?"true":"false", req.optimized?"true":"false", req.graphOnly?"true":"false"); @@ -1581,7 +1604,7 @@ bool CoreWrapper::getProjMapCallback(nav_msgs::GetMap::Request &req, nav_msgs:: if(parameters_.find(Parameters::kGridFromDepth()) != parameters_.end() && !uStr2Bool(parameters_.at(Parameters::kGridFromDepth()))) { - ROS_WARN("/get_proj_map service is deprecated! Call /get_grid_map service " + NODELET_WARN("/get_proj_map service is deprecated! Call /get_grid_map service " "instead with . " "Do \"$ rosrun rtabmap_ros rtabmap --params | grep Grid\" to see " "all occupancy grid parameters.", @@ -1589,14 +1612,14 @@ bool CoreWrapper::getProjMapCallback(nav_msgs::GetMap::Request &req, nav_msgs:: } else { - ROS_WARN("/get_proj_map service is deprecated! Call /get_grid_map service instead."); + NODELET_WARN("/get_proj_map service is deprecated! Call /get_grid_map service instead."); } return getGridMapCallback(req, res); } bool CoreWrapper::getGridMapCallback(nav_msgs::GetMap::Request &req, nav_msgs::GetMap::Response &res) { - ROS_WARN("/get_grid_map service is deprecated! Call /get_map service instead."); + NODELET_WARN("/get_grid_map service is deprecated! Call /get_map service instead."); return getMapCallback(req, res); } @@ -1644,7 +1667,7 @@ bool CoreWrapper::getMapCallback(nav_msgs::GetMap::Request &req, nav_msgs::GetM bool CoreWrapper::publishMapCallback(rtabmap_ros::PublishMap::Request& req, rtabmap_ros::PublishMap::Response& res) { - ROS_INFO("rtabmap: Publishing map..."); + NODELET_INFO("rtabmap: Publishing map..."); if(mapDataPub_.getNumSubscribers() || (!req.graphOnly && mapsManager_.hasSubscribers()) || @@ -1675,7 +1698,7 @@ bool CoreWrapper::publishMapCallback(rtabmap_ros::PublishMap::Request& req, rtab if(poses.size() && poses.size() != signatures.size()) { - ROS_WARN("poses and signatures are not the same size!? %d vs %d", (int)poses.size(), (int)signatures.size()); + NODELET_WARN("poses and signatures are not the same size!? %d vs %d", (int)poses.size(), (int)signatures.size()); } ros::Time now = ros::Time::now(); @@ -1833,7 +1856,7 @@ bool CoreWrapper::cancelGoalCallback(std_srvs::Empty::Request& req, std_srvs::Em { if(rtabmap_.getPath().size()) { - ROS_WARN("Goal cancelled!"); + NODELET_WARN("Goal cancelled!"); rtabmap_.clearPath(0); currentMetricGoal_.setNull(); latestNodeWasReached_ = false; @@ -1858,22 +1881,22 @@ bool CoreWrapper::setLabelCallback(rtabmap_ros::SetLabel::Request& req, rtabmap_ { if(req.node_id > 0) { - ROS_INFO("Set label \"%s\" to node %d", req.node_label.c_str(), req.node_id); + NODELET_INFO("Set label \"%s\" to node %d", req.node_label.c_str(), req.node_id); } else { - ROS_INFO("Set label \"%s\" to last node", req.node_label.c_str()); + NODELET_INFO("Set label \"%s\" to last node", req.node_label.c_str()); } } else { if(req.node_id > 0) { - ROS_ERROR("Could not set label \"%s\" to node %d", req.node_label.c_str(), req.node_id); + NODELET_ERROR("Could not set label \"%s\" to node %d", req.node_label.c_str(), req.node_id); } else { - ROS_ERROR("Could not set label \"%s\" to last node", req.node_label.c_str()); + NODELET_ERROR("Could not set label \"%s\" to last node", req.node_label.c_str()); } } return true; @@ -1885,7 +1908,7 @@ bool CoreWrapper::listLabelsCallback(rtabmap_ros::ListLabels::Request& req, rtab { std::map labels = rtabmap_.getMemory()->getAllLabels(); res.labels = uValues(labels); - ROS_INFO("List labels service: %d labels found.", (int)res.labels.size()); + NODELET_INFO("List labels service: %d labels found.", (int)res.labels.size()); } return true; } @@ -1897,7 +1920,7 @@ void CoreWrapper::publishStats(const ros::Time & stamp) if(infoPub_.getNumSubscribers()) { - //ROS_INFO("Sending RtabmapInfo msg (last_id=%d)...", stat.refImageId()); + //NODELET_INFO("Sending RtabmapInfo msg (last_id=%d)...", stat.refImageId()); rtabmap_ros::InfoPtr msg(new rtabmap_ros::Info); msg->header.stamp = stamp; msg->header.frame_id = mapFrameId_; @@ -2020,7 +2043,7 @@ void CoreWrapper::publishCurrentGoal(const ros::Time & stamp) { if(!currentMetricGoal_.isNull()) { - ROS_INFO("Publishing next goal: %d -> %s", + NODELET_INFO("Publishing next goal: %d -> %s", rtabmap_.getPathCurrentGoalId(), currentMetricGoal_.prettyPrint().c_str()); geometry_msgs::PoseStamped poseMsg; @@ -2032,7 +2055,7 @@ void CoreWrapper::publishCurrentGoal(const ros::Time & stamp) { if(!mbClient_.isServerConnected()) { - ROS_INFO("Connecting to move_base action server..."); + NODELET_INFO("Connecting to move_base action server..."); mbClient_.waitForServer(ros::Duration(5.0)); } if(mbClient_.isServerConnected()) @@ -2047,7 +2070,7 @@ void CoreWrapper::publishCurrentGoal(const ros::Time & stamp) } else { - ROS_ERROR("Cannot connect to move_base action server!"); + NODELET_ERROR("Cannot connect to move_base action server!"); } } if(nextMetricGoalPub_.getNumSubscribers()) @@ -2070,19 +2093,19 @@ void CoreWrapper::goalDoneCb(const actionlib::SimpleClientGoalState& state, rtabmap_.getPathCurrentGoalId() != rtabmap_.getPath().back().first && (!uContains(rtabmap_.getLocalOptimizedPoses(), rtabmap_.getPath().back().first) || !latestNodeWasReached_)) { - ROS_WARN("Planning: move_base reached current goal but it is not " + NODELET_WARN("Planning: move_base reached current goal but it is not " "the last one planned by rtabmap. A new goal should be sent when " "rtabmap will be able to retrieve next locations on the path."); ignore = true; } else { - ROS_INFO("Planning: move_base success!"); + NODELET_INFO("Planning: move_base success!"); } } else { - ROS_ERROR("Planning: move_base failed for some reason. Aborting the plan..."); + NODELET_ERROR("Planning: move_base failed for some reason. Aborting the plan..."); } if(!ignore && goalReachedPub_.getNumSubscribers()) @@ -2104,14 +2127,14 @@ void CoreWrapper::goalDoneCb(const actionlib::SimpleClientGoalState& state, // Called once when the goal becomes active void CoreWrapper::goalActiveCb() { - //ROS_INFO("Planning: Goal just went active"); + //NODELET_INFO("Planning: Goal just went active"); } // Called every time feedback is received for the goal void CoreWrapper::goalFeedbackCb(const move_base_msgs::MoveBaseFeedbackConstPtr& feedback) { //Transform basePosition = rtabmap_ros::transformFromPoseMsg(feedback->base_position.pose); - //ROS_INFO("Planning: feedback base_position = %s", basePosition.prettyPrint().c_str()); + //NODELET_INFO("Planning: feedback base_position = %s", basePosition.prettyPrint().c_str()); } void CoreWrapper::publishLocalPath(const ros::Time & stamp) @@ -2178,7 +2201,7 @@ bool CoreWrapper::octomapBinaryCallback( octomap_msgs::GetOctomap::Request &req, octomap_msgs::GetOctomap::Response &res) { - ROS_INFO("Sending binary map data on service request"); + NODELET_INFO("Sending binary map data on service request"); res.map.header.frame_id = mapFrameId_; res.map.header.stamp = ros::Time::now(); @@ -2194,7 +2217,7 @@ bool CoreWrapper::octomapFullCallback( octomap_msgs::GetOctomap::Request &req, octomap_msgs::GetOctomap::Response &res) { - ROS_INFO("Sending full map data on service request"); + NODELET_INFO("Sending full map data on service request"); res.map.header.frame_id = mapFrameId_; res.map.header.stamp = ros::Time::now(); @@ -2208,4 +2231,6 @@ bool CoreWrapper::octomapFullCallback( #endif #endif +PLUGINLIB_EXPORT_CLASS(rtabmap_ros::CoreWrapper, nodelet::Nodelet); + } diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index cfb9d852..56ab8734 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -63,6 +63,7 @@ float max3( const float& a, const float& b, const float& c) namespace rtabmap_ros { GuiWrapper::GuiWrapper(int & argc, char** argv) : + CommonDataSubscriber(true), mainWindow_(0), frameId_("base_link"), odomFrameId_(""), @@ -73,6 +74,9 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : lastOdomInfoUpdateTime_(0) { ros::NodeHandle nh; + ros::NodeHandle pnh("~"); + + setupCallbacks(nh, pnh); QString configFile = QDir::homePath()+"/.ros/rtabmapGUI.ini"; for(int i=1; isetMonitoringState(paused); - ros::NodeHandle pnh("~"); - // To receive odometry events std::string tfPrefix; std::string initCachePath; @@ -173,7 +175,7 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : if(!this->isDataSubscribed()) { - defaultSub_ = nh.subscribe("odom", 1, &GuiWrapper::defaultCallback, this); + defaultSub_ = nh.subscribe("odom", queueSize_, &GuiWrapper::defaultCallback, this); ROS_INFO("\n%s subscribed to:\n %s", ros::this_node::getName().c_str(), diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index b3105987..bf60de1f 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -83,6 +83,8 @@ OdometryROS::~OdometryROS() { if(warningThread_) { + callbackCalled(); + warningThread_->join(); delete warningThread_; } ros::NodeHandle & pnh = getPrivateNodeHandle(); @@ -311,13 +313,13 @@ void OdometryROS::startWarningThread(const std::string & subscribedTopicsMsg, bo void OdometryROS::warningLoop(const std::string & subscribedTopicsMsg, bool approxSync) { - ros::Duration r(10.0); - while(ros::ok() && !callbackCalled_) + ros::Duration r(5.0); + while(!callbackCalled_) { r.sleep(); - if(ros::ok() && !callbackCalled_) + if(!callbackCalled_) { - ROS_WARN("%s: Did not receive data since 10 seconds! Make sure the input topics are " + ROS_WARN("%s: Did not receive data since 5 seconds! Make sure the input topics are " "published (\"$ rostopic hz my_topic\") and the timestamps in their " "header are set. %s%s", ros::this_node::getName().c_str(), diff --git a/src/impl/CommonDataSubscriberDepth.cpp b/src/impl/CommonDataSubscriberDepth.cpp index 6464d2f5..9b0473a1 100644 --- a/src/impl/CommonDataSubscriberDepth.cpp +++ b/src/impl/CommonDataSubscriberDepth.cpp @@ -230,6 +230,8 @@ void CommonDataSubscriber::depthOdomDataInfoCallback( } void CommonDataSubscriber::setupDepthCallbacks( + ros::NodeHandle & nh, + ros::NodeHandle & pnh, bool subscribeOdom, bool subscribeUserData, bool subscribeScan2d, @@ -239,8 +241,6 @@ void CommonDataSubscriber::setupDepthCallbacks( bool approxSync) { ROS_INFO("Setup depth callback"); - ros::NodeHandle nh; // public - ros::NodeHandle pnh("~"); // private std::string rgbPrefix = "rgb"; std::string depthPrefix = "depth"; diff --git a/src/impl/CommonDataSubscriberRGBD.cpp b/src/impl/CommonDataSubscriberRGBD.cpp index 4ed8aeb9..a179b4fe 100644 --- a/src/impl/CommonDataSubscriberRGBD.cpp +++ b/src/impl/CommonDataSubscriberRGBD.cpp @@ -249,6 +249,8 @@ void CommonDataSubscriber::rgbdOdomDataInfoCallback( } void CommonDataSubscriber::setupRGBDCallbacks( + ros::NodeHandle & nh, + ros::NodeHandle & pnh, bool subscribeOdom, bool subscribeUserData, bool subscribeScan2d, @@ -258,8 +260,6 @@ void CommonDataSubscriber::setupRGBDCallbacks( bool approxSync) { ROS_INFO("Setup rgbd callback"); - ros::NodeHandle nh; // public - ros::NodeHandle pnh("~"); // private if(subscribeOdom || subscribeUserData || subscribeScan2d || subscribeScan3d || subscribeOdomInfo) { diff --git a/src/impl/CommonDataSubscriberRGBD2.cpp b/src/impl/CommonDataSubscriberRGBD2.cpp index 2816f507..ac9dde64 100644 --- a/src/impl/CommonDataSubscriberRGBD2.cpp +++ b/src/impl/CommonDataSubscriberRGBD2.cpp @@ -259,6 +259,8 @@ void CommonDataSubscriber::rgbd2OdomDataInfoCallback( } void CommonDataSubscriber::setupRGBD2Callbacks( + ros::NodeHandle & nh, + ros::NodeHandle & pnh, bool subscribeOdom, bool subscribeUserData, bool subscribeScan2d, @@ -268,8 +270,6 @@ void CommonDataSubscriber::setupRGBD2Callbacks( bool approxSync) { ROS_INFO("Setup rgbd2 callback"); - ros::NodeHandle nh; // public - ros::NodeHandle pnh("~"); // private rgbdSubs_.resize(2); for(int i=0; i<2; ++i) diff --git a/src/impl/CommonDataSubscriberStereo.cpp b/src/impl/CommonDataSubscriberStereo.cpp index bcf98be5..bf27bdf2 100644 --- a/src/impl/CommonDataSubscriberStereo.cpp +++ b/src/impl/CommonDataSubscriberStereo.cpp @@ -86,14 +86,14 @@ void CommonDataSubscriber::stereoOdomInfoCallback( } void CommonDataSubscriber::setupStereoCallbacks( + ros::NodeHandle & nh, + ros::NodeHandle & pnh, bool subscribeOdom, bool subscribeOdomInfo, int queueSize, bool approxSync) { ROS_INFO("Setup stereo callback"); - ros::NodeHandle nh; // public - ros::NodeHandle pnh("~"); // private ros::NodeHandle left_nh(nh, "left"); ros::NodeHandle right_nh(nh, "right"); diff --git a/src/nodelets/rgbd_odometry.cpp b/src/nodelets/rgbd_odometry.cpp index 4d4d4a22..2f8d041e 100644 --- a/src/nodelets/rgbd_odometry.cpp +++ b/src/nodelets/rgbd_odometry.cpp @@ -71,6 +71,7 @@ public: virtual ~RGBDOdometry() { + rgbdSub_.shutdown(); if(approxSync_) { delete approxSync_; @@ -98,11 +99,13 @@ private: int rgbdCameras = 1; bool approxSync = true; + bool subscribeRGBD = false; pnh.param("approx_sync", approxSync, approxSync); pnh.param("queue_size", queueSize_, queueSize_); + pnh.param("subscribe_rgbd", subscribeRGBD, subscribeRGBD); if(pnh.hasParam("depth_cameras")) { - ROS_ERROR("\"depth_cameras\" parameter doesn't exist anymore. It is replaced by \"rgbd_cameras\" with the \"rgbd_image\" input topics."); + ROS_ERROR("\"depth_cameras\" parameter doesn't exist anymore. It is replaced by \"rgbd_cameras\" with the \"rgbd_image\" input topics. \"subscribe_rgbd\" should be also set to true."); } pnh.param("rgbd_cameras", rgbdCameras, rgbdCameras); if(rgbdCameras <= 0) @@ -115,32 +118,44 @@ private: } std::string subscribedTopicsMsg; - if(rgbdCameras == 2) + if(subscribeRGBD) { - rgbd_image1_sub_.subscribe(nh, "rgbd_image0", 1); - rgbd_image2_sub_.subscribe(nh, "rgbd_image1", 1); - - if(approxSync) + if(rgbdCameras == 2) { - approxSync2_ = new message_filters::Synchronizer( - MyApproxSync2Policy(queueSize_), - rgbd_image1_sub_, - rgbd_image2_sub_); - approxSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); + rgbd_image1_sub_.subscribe(nh, "rgbd_image0", 1); + rgbd_image2_sub_.subscribe(nh, "rgbd_image1", 1); + + if(approxSync) + { + approxSync2_ = new message_filters::Synchronizer( + MyApproxSync2Policy(queueSize_), + rgbd_image1_sub_, + rgbd_image2_sub_); + approxSync2_->registerCallback(boost::bind(&RGBDOdometry::callbackRGBD2, this, _1, _2)); + } + else + { + exactSync2_ = new message_filters::Synchronizer( + MyExactSync2Policy(queueSize_), + rgbd_image1_sub_, + rgbd_image2_sub_); + exactSync2_->registerCallback(boost::bind(&RGBDOdometry::callbackRGBD2, this, _1, _2)); + } + subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s", + ros::this_node::getName().c_str(), + approxSync?"approx":"exact", + rgbd_image1_sub_.getTopic().c_str(), + rgbd_image2_sub_.getTopic().c_str()); } else { - exactSync2_ = new message_filters::Synchronizer( - MyExactSync2Policy(queueSize_), - rgbd_image1_sub_, - rgbd_image2_sub_); - exactSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); + rgbdSub_ = nh.subscribe("rgbd_image", queueSize_, &RGBDOdometry::callbackRGBD, this); + + subscribedTopicsMsg = + uFormat("\n%s subscribed to:\n %s", + ros::this_node::getName().c_str(), + rgbdSub_.getTopic().c_str()); } - subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s", - ros::this_node::getName().c_str(), - approxSync?"approx":"exact", - rgbd_image1_sub_.getTopic().c_str(), - rgbd_image2_sub_.getTopic().c_str()); } else { @@ -319,7 +334,23 @@ private: } } - void callback2( + void callbackRGBD( + const rtabmap_ros::RGBDImageConstPtr& image) + { + callbackCalled(); + if(!this->isPaused()) + { + std::vector imageMsgs(1); + std::vector depthMsgs(1); + std::vector infoMsgs; + rtabmap_ros::toCvShare(image, imageMsgs[0], depthMsgs[0]); + infoMsgs.push_back(image->cameraInfo); + + this->commonCallback(imageMsgs, depthMsgs, infoMsgs); + } + } + + void callbackRGBD2( const rtabmap_ros::RGBDImageConstPtr& image, const rtabmap_ros::RGBDImageConstPtr& image2) { @@ -361,7 +392,7 @@ protected: MyApproxSync2Policy(queueSize_), rgbd_image1_sub_, rgbd_image2_sub_); - approxSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); + approxSync2_->registerCallback(boost::bind(&RGBDOdometry::callbackRGBD2, this, _1, _2)); } if(exactSync2_) { @@ -370,7 +401,7 @@ protected: MyExactSync2Policy(queueSize_), rgbd_image1_sub_, rgbd_image2_sub_); - exactSync2_->registerCallback(boost::bind(&RGBDOdometry::callback2, this, _1, _2)); + exactSync2_->registerCallback(boost::bind(&RGBDOdometry::callbackRGBD2, this, _1, _2)); } } @@ -379,6 +410,7 @@ private: image_transport::SubscriberFilter image_depth_sub_; message_filters::Subscriber info_sub_; + ros::Subscriber rgbdSub_; message_filters::Subscriber rgbd_image1_sub_; message_filters::Subscriber rgbd_image2_sub_; From b2b5df31e0e2e0e5ab8b7f3180256bcc337adb16 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Sun, 2 Oct 2016 16:51:33 -0400 Subject: [PATCH 42/78] Fixed #122. Fixed assert "commonDepthCallbackImpl() Condition (uContains(parameters_, rtabmap::Parameters::kMemSaveDepth16Format())) not met!" (making sure that callbacks are initialized after rosparam) --- src/CoreWrapper.cpp | 69 +++++++++++++++++++++++++++++---------------- src/GuiWrapper.cpp | 3 +- src/MapsManager.cpp | 60 ++++++++++++++++++++++++++++----------- 3 files changed, 90 insertions(+), 42 deletions(-) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 56eb0ecf..cca821c4 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -118,8 +118,6 @@ void CoreWrapper::onInit() ros::NodeHandle & nh = getNodeHandle(); ros::NodeHandle & pnh = getPrivateNodeHandle(); - setupCallbacks(nh, pnh); - bool publishTf = true; double tfDelay = 0.05; // 20 Hz double tfTolerance = 0.1; // 100 ms @@ -201,6 +199,12 @@ void CoreWrapper::onInit() NODELET_INFO("rtabmap: tf_delay = %f", tfDelay); NODELET_INFO("rtabmap: tf_tolerance = %f", tfTolerance); NODELET_INFO("rtabmap: odom_sensor_sync = %s", odomSensorSync_?"true":"false"); + bool subscribeStereo = false; + pnh.param("subscribe_stereo", subscribeStereo, subscribeStereo); + if(subscribeStereo) + { + NODELET_INFO("rtabmap: stereo_to_depth = %s", stereoToDepth_?"true":"false"); + } infoPub_ = nh.advertise("info", 1); mapDataPub_ = nh.advertise("mapData", 1); @@ -328,7 +332,11 @@ void CoreWrapper::onInit() // Backward compatibility (MapsManager) mapsManager_.backwardCompatibilityParameters(parameters_); - if((this->isSubscribedToScan2d() || this->isSubscribedToScan3d()) && parameters_.find(Parameters::kGridFromDepth()) == parameters_.end()) + bool subscribeScan2d = false; + bool subscribeScan3d = false; + pnh.param("subscribe_scan", subscribeScan2d, subscribeScan2d); + pnh.param("subscribe_scan_cloud", subscribeScan3d, subscribeScan3d); + if((subscribeScan2d || subscribeScan3d) && parameters_.find(Parameters::kGridFromDepth()) == parameters_.end()) { NODELET_WARN("Setting \"%s\" parameter to false (default true) as \"subscribe_scan\" or \"subscribe_scan_cloud\" is " "true. The occupancy grid map will be constructed from " @@ -363,18 +371,6 @@ void CoreWrapper::onInit() NODELET_INFO("Create intermediate nodes"); } } - bool isRGBD = uStr2Bool(parameters_.at(Parameters::kRGBDEnabled()).c_str()); - if(isRGBD) - { - // RGBD SLAM - if(!this->isSubscribedToDepth() && !this->isSubscribedToStereo() && !this->isSubscribedToRGBD()) - { - NODELET_WARN("ROS param subscribe_depth, subscribe_stereo and subscribe_rgbd are false, but RTAB-Map " - "parameter \"%s\" is true! Please set subscribe_depth, subscribe_stereo or subscribe_rgbd " - "to true to use rtabmap node for RGB-D SLAM, or set \"%s\" to false for loop closure " - "detection on images-only.", Parameters::kRGBDEnabled().c_str(), Parameters::kRGBDEnabled().c_str()); - } - } if(paused_) { @@ -399,10 +395,6 @@ void CoreWrapper::onInit() } mapsManager_.setParameters(parameters_); - if(this->isSubscribedToStereo()) - { - NODELET_INFO("rtabmap: stereo_to_depth = %s", stereoToDepth_?"true":"false"); - } // Init RTAB-Map rtabmap_.init(parameters_, databasePath_); @@ -455,8 +447,18 @@ void CoreWrapper::onInit() Parameters::kOptimizerIterations().c_str(), mapFrameId_.c_str()); } + setupCallbacks(nh, pnh); // do it at the end if(!this->isDataSubscribed()) { + bool isRGBD = uStr2Bool(parameters_.at(Parameters::kRGBDEnabled()).c_str()); + if(isRGBD) + { + NODELET_WARN("ROS param subscribe_depth, subscribe_stereo and subscribe_rgbd are false, but RTAB-Map " + "parameter \"%s\" is true! Please set subscribe_depth, subscribe_stereo or subscribe_rgbd " + "to true to use rtabmap node for RGB-D SLAM, or set \"%s\" to false for loop closure " + "detection on images-only.", Parameters::kRGBDEnabled().c_str(), Parameters::kRGBDEnabled().c_str()); + } + ros::NodeHandle rgb_nh(nh, "rgb"); ros::NodeHandle rgb_pnh(pnh, "rgb"); image_transport::ImageTransport rgb_it(rgb_nh); @@ -854,6 +856,12 @@ void CoreWrapper::commonDepthCallbackImpl( cv::flip(scan, flipScan, 1); scan = flipScan; } + if(rtabmap_.getMemory() && uStrNumCmp(rtabmap_.getMemory()->getDatabaseVersion(), "0.11.10") < 0) + { + // backward compatibility, project 2D scan in /base_link frame + scan = util3d::transformLaserScan(scan, scanLocalTransform); + scanLocalTransform = Transform::getIdentity(); + } } else if(scan3dMsg.get() != 0) { @@ -1040,6 +1048,12 @@ void CoreWrapper::commonStereoCallback( cv::flip(scan, flipScan, 1); scan = flipScan; } + if(rtabmap_.getMemory() && uStrNumCmp(rtabmap_.getMemory()->getDatabaseVersion(), "0.11.10") < 0) + { + // backward compatibility, project 2D scan in /base_link frame + scan = util3d::transformLaserScan(scan, scanLocalTransform); + scanLocalTransform = Transform::getIdentity(); + } } else if(scan3dMsg.get() != 0) { @@ -1120,12 +1134,19 @@ void CoreWrapper::process( this->publishStats(stamp); std::map filteredPoses = rtabmap_.getLocalOptimizedPoses(); - // create a tmp signature with latest sensory data + // create a tmp signature with latest sensory data if latest signature was ignored std::map tmpSignature; - SensorData tmpData = data; - tmpData.setId(-1); - tmpSignature.insert(std::make_pair(-1, Signature(-1, -1, 0, data.stamp(), "", odom, Transform(), tmpData))); - filteredPoses.insert(std::make_pair(-1, rtabmap_.getMapCorrection()*odom)); + if(rtabmap_.getMemory() == 0 || + filteredPoses.size() == 0 || + rtabmap_.getMemory()->getLastSignatureId() != filteredPoses.rbegin()->first || + rtabmap_.getMemory()->getLastWorkingSignature() == 0 || + rtabmap_.getMemory()->getLastWorkingSignature()->sensorData().gridCellSize() == 0) + { + SensorData tmpData = data; + tmpData.setId(-1); + tmpSignature.insert(std::make_pair(-1, Signature(-1, -1, 0, data.stamp(), "", odom, Transform(), tmpData))); + filteredPoses.insert(std::make_pair(-1, rtabmap_.getMapCorrection()*odom)); + } // Update maps filteredPoses = mapsManager_.updateMapCaches( diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index 56ab8734..96931483 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -76,8 +76,6 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : ros::NodeHandle nh; ros::NodeHandle pnh("~"); - setupCallbacks(nh, pnh); - QString configFile = QDir::homePath()+"/.ros/rtabmapGUI.ini"; for(int i=1; iregisterCallback(boost::bind(&GuiWrapper::goalPathCallback, this, _1, _2)); goalReachedTopic_ = nh.subscribe("goal_reached", 1, &GuiWrapper::goalReachedCallback, this); + setupCallbacks(nh, pnh); // do it at the end if(!this->isDataSubscribed()) { defaultSub_ = nh.subscribe("odom", queueSize_, &GuiWrapper::defaultCallback, this); diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index 35365ea4..fb5a65a1 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -431,6 +431,8 @@ std::map MapsManager::updateMapCaches( #endif } + bool occupancySavedInDB = memory && uStrNumCmp(memory->getDatabaseVersion(), "0.11.10")>=0?true:false; + for(std::map::iterator iter=filteredPoses.begin(); iter!=filteredPoses.end(); ++iter) { if(!iter->second.isNull()) @@ -446,32 +448,58 @@ std::map MapsManager::updateMapCaches( } else if(memory) { - data = memory->getSignatureDataConst(iter->first, false, false, false, true); + data = memory->getSignatureDataConst(iter->first, occupancyGrid_->isGridFromDepth() && !occupancySavedInDB, !occupancyGrid_->isGridFromDepth() && !occupancySavedInDB, false, true); } if(data.id() != 0) { - cv::Mat ground, obstacles; - data.uncompressData( - 0, - 0, - 0, - 0, - &ground, - &obstacles); - UDEBUG("Adding grid map %d to cache...", iter->first); - cv::Point3f viewPoint; - if(iter->first > 0 || data.gridCellSize()) + cv::Mat ground, obstacles; + if(iter->first > 0) { - viewPoint = data.gridViewPoint(); - gridMaps_.insert(std::make_pair(iter->first, std::make_pair(ground, obstacles))); - gridMapsViewpoints_.insert(std::make_pair(iter->first, viewPoint)); + cv::Mat rgb, depth, scan; + bool generateGrid = data.gridCellSize() == 0.0f; + static bool warningShown = false; + if(occupancySavedInDB && generateGrid && !warningShown) + { + warningShown = true; + UWARN("Occupancy grid for location %d should be added to global map (e..g, a ROS node is subscribed to " + "any occupancy grid output) but it cannot be found " + "in memory. For convenience, the occupancy " + "grid is regenerated. Make sure parameter \"%s\" is true to " + "avoid this warning for the next locations added to map. For older " + "locations already in database without an occupancy grid map, you can use the " + "\"rtabmap-databaseViewer\" to regenerate the missing occupancy grid maps and " + "save them back in the database for next sessions. This warning is only shown once.", + data.id(), Parameters::kRGBDCreateOccupancyGrid().c_str()); + } + data.uncompressData( + occupancyGrid_->isGridFromDepth() && generateGrid?&rgb:0, + occupancyGrid_->isGridFromDepth() && generateGrid?&depth:0, + !occupancyGrid_->isGridFromDepth() && generateGrid?&scan:0, + 0, + generateGrid?0:&ground, + generateGrid?0:&obstacles); + + if(generateGrid) + { + Signature tmp(data); + tmp.setPose(iter->second); + occupancyGrid_->createLocalMap(tmp, ground, obstacles, viewPoint); + uInsert(gridMaps_, std::make_pair(iter->first, std::make_pair(ground, obstacles))); + uInsert(gridMapsViewpoints_, std::make_pair(iter->first, viewPoint)); + } + else + { + viewPoint = data.gridViewPoint(); + gridMaps_.insert(std::make_pair(iter->first, std::make_pair(ground, obstacles))); + gridMapsViewpoints_.insert(std::make_pair(iter->first, viewPoint)); + } } else { - // generate tmp occupancy grid for negative ids + // generate tmp occupancy grid for negative ids (assuming data is already uncompressed) // we need the signature std::map::const_iterator findIter = signatures.find(iter->first); if(findIter != signatures.end()) From 9338c6ef59ec013b4b20b8745d743a528d636cdc Mon Sep 17 00:00:00 2001 From: matlabbe Date: Sun, 2 Oct 2016 17:32:32 -0400 Subject: [PATCH 43/78] rtabmap.launch: added "depth_image_transport" argument for convenience --- launch/rtabmap.launch | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/launch/rtabmap.launch b/launch/rtabmap.launch index a6e1158d..81f7774f 100644 --- a/launch/rtabmap.launch +++ b/launch/rtabmap.launch @@ -54,9 +54,8 @@ - - + @@ -85,7 +84,7 @@ - + From 835cfbc131a899032a0d562474699afca7486323 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 5 Oct 2016 13:55:52 -0400 Subject: [PATCH 44/78] Added undistort_depth nodelet --- CMakeLists.txt | 1 + .../calibration/distortion_model_PS1080.bin | Bin 0 -> 947273 bytes .../calibration/distortion_model_PS1080.png | Bin 0 -> 564611 bytes launch/tests/test_undistort_depth.launch | 20 +++ nodelet_plugins.xml | 8 ++ src/nodelets/undistort_depth.cpp | 123 ++++++++++++++++++ 6 files changed, 152 insertions(+) create mode 100644 launch/calibration/distortion_model_PS1080.bin create mode 100644 launch/calibration/distortion_model_PS1080.png create mode 100644 launch/tests/test_undistort_depth.launch create mode 100644 src/nodelets/undistort_depth.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 01e26c1d..b5c0aa66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,7 @@ SET(rtabmap_ros_lib_src src/nodelets/obstacles_detection.cpp src/nodelets/obstacles_detection_old.cpp src/nodelets/point_cloud_aggregator.cpp + src/nodelets/undistort_depth.cpp src/nodelets/rgbd_sync.cpp src/OdometryROS.cpp src/MsgConversion.cpp diff --git a/launch/calibration/distortion_model_PS1080.bin b/launch/calibration/distortion_model_PS1080.bin new file mode 100644 index 0000000000000000000000000000000000000000..54ee5d35b7781ed08449928037845e835d25d01d GIT binary patch literal 947273 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

fqR{gH( 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

%c~Sv4xOfsnVyt{a7EP%bYz0?^@H# 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 Dw`0?Q literal 0 HcmV?d00001 diff --git a/launch/calibration/distortion_model_PS1080.png b/launch/calibration/distortion_model_PS1080.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9ab89cc485690fae1f0570478b7b18e4f3a31c GIT binary patch literal 564611 zcmeFa3pCYf+dn>?BNCEB4zE#pFxB28m5`z+sgUYP$!X$25=NrX!8tKgOuI%Q(awyT zNr-aDvBNIUGj>#pDdDkIDk10p=ejjhzj#eaOUdc|_vy<=Y7 zD2$t>mv-Q3-JblPfe^-~e7*#(Y^XkBN%3pptcI@~`Kb&m4a8a#Z^K@dv zx%3|#SLC>=ZK8>n6zc;@OC2)W!q`1)Xg#xY$1Am9uHPdzRBs zkMX>>-E%#ZtsO$5`vwDqZ=#gUh1UZ3)yC)9>-^O#qk8eVoW|fDm6qn_3CC?p2j11J z=Ou>@DQ0`k%t$zNA;!rxt{6%}U&C=gAu$j6ZTc_y|7ggu~g8VHJPFvz5|a zuT(^x)otu`_EB8tqTZI7f`G>0$iTqB8|M-@#)I!~vb%+M@BP7yI+>+XbfQMD#WeU%gqMhmQ*Goh|BV zw64;?esnaXE*(x{ud56Hzin$Rw3xH>{F)$BXEm~S{TelkJ-3;MkEbeqQKXMYDxCX2DP_Rl*$q-AFkzbAfXn*AXa+HUiX>58h2t^4n-eDw++kSQlOHlv}qqP!q2SF$EU!}W|c_iTo*NtTW% z-q@-!mQdEVBhKlA+K zd8Q|^>mDl$ucy4~ur52`-g`i7hk1Hf;C^vljaZ_t$0n7ka$8zrNyom0f9zI_YK`%fFCh)gIH=Bqhvi^p!-g z&`SR&1?DGJHWu4VN1{PTv3c-YG0{(#@NBPMUA?&=wOd7Qtmx%d;WQoBCmJnQuGn=u z>-qEf{riL*N4uqtCJ(K(oK%y?wtQ>S91Ak65B?rH&QekFgNbTg%_E z{HO*N*BcwOcUexvlGFHxh%q>;Q&w6&-zQjUrIz}d&7FdyTfd&4WSy1j+w!G;+R%V( zPRF+AucqPy?5x!tMT1){e>U)_=Xfn~Zc&ue@X*#27MvY=Kd$R#-ZoQ2s?YB9f2%u% zydUEq8Xq*QN~V8xl?C6%s_XzvDD}m*=lfyf;$z?(ZaD2-yI?vV6$x)9c0L`4IQX;# zE|GX>NwRf2EG?&lKh27JHqIPT!v^*jXO19fXIc>z6u3O`5P}5 zT==|@&nNQPwEm~~$bhuJ-{HPzQ-m+)B9%Ecayrj&tA2IvR^poe4XNOkNtW z6pvI`&0^2V`mimV0k~`s%uJB`wI?8BTqzm{`n1~&^B5Mk3~ z+0n1)-#T?ggZnP3mn(47rxO&v*#CEnXit95TMJLg&Zc<&k28hx4k^35t!u)>`y)>F z#OlY%W!FV1nk=t~vYUY??o>>aK_tZEjK}{AGz*8Z3m(rA6aa-K9@3rwY&IS+U|QwyM9`?N}{f4?}~~j2F#3y)I{As9N9S&DASX# zkiRMmz!&hT;rj9?jcLv+aw4|mM!8G+FHWfiWb^oDIsOemqodJp6f~n2y5h;>l%n@z z{r4RKGdP7i5>P|8T|eLvdYr~w-vGXNvKGGOz-h)t$N=KTjk|# z`=;J1=PdHi&6OT2VFS$*x>_WG+vPMe^#qq!T6zM^VoRo3X-uJi8;A`WKI1u=Yi6kx zPG{4;^aq<~N*-C)f3qtac&{O9nb=h!&#&JKh*w(V)MDfk=Th-P&pE;hcJjl7L>bY3 zvvj~vJdXGnoYEO!?1(YCd1Bd=(bBPwaQp*6*A`}VlH*W4>HEpi17f3`R{-*sO~SUI zXt;;HZ;oA}3M|YJX8dquL1FWk^%0qJz|dyMn6Lrhz(w9xJw33;ETm_O3FkpRn0|8(l#-cP< z!z5UB#*#hp^4IeGSEh$)Pe7S79%o_$Ya*)ybZm;ceex$h9uOM^aAwlw932{0I6 zvzG?&o;Oi3(uum6dbS}0#&B5qhYJB`YpuAx-`iRD#{nS}1|CKMQW51LnY~lGMhPyP zr2~t&c?kbzWSrJe+^S>}E4{%YmCmLhK0V=Tgq_Z%E43Wsp{RoV<-u}1FC^e`Y4E#PT9j>)q0uv2biPbC7c(w_nPVQA$1CMd?>WMp-K zC%`b%Q@z96>0E`pMpz_5rq25XNmE^@JP+T)$C7piSZ@pos<_O!$HwtgBq@n;uz`+sHv1}C2I4V2m zpRk1sHIlAdtg3dK-PQfPme1$%*~UKm%YUYwUM@nW^fg0ri^v75QnRb$>WT-OklgIJ zdb2FQnu$aR_Q9t-VFvfYOGM!Fvf5^XJ^cr?!jFU@Qz9Q*Fg6WIs8X6x1O@FK%YncG zQIb+JVVJCiSSdS}DvxT&E1KKx0zrpY5{C0wc|V46RCcD}2RtP44hr_f0O1e!ctJc) zd(5fDX&x&P$DSCB2F&WcLH;zyBF9L2na-V(@ua~g`j;$3{~Tw8VUsCQoA!h6p;OA0OZ_aj;T&2ccK&82urM+rJM_<^n9;kGY`klf|46UK@iGhC~O)9gB1@(0OjAK3g(}12**b+RZdKko35#!%#vuSD z;%u8^@o$FUiNQsYsrfvI&)xQdixWMCrza}|fo_h4PXoj53pmbpjQ((2af6hstG%_X z8(tP@R=BTfGzMu<4zZ^1-ErXHuJ=*eR5N6xkCCkfl->P(FbHzd8wx$M0qhp81N#qF zl`&fl*Kb@G99$zo_sFrQS?SLZ*vWv|Zi)iAk5_Mof%&flTCcSN51t`* zUkQ${mFm3W$&*y)f8Z?0d_3v6cnxu?puxB4cbqncjv^K-wueiqYk`!vj|WS>%2`dO3!kprsdxZP{=Sc*t@}T zdrQge2s(#asx<7ZQ4~-VR-WQIS4_7W@d(?o-wZh23{@^l1y6o(E68{cNyOv?oEk`e z?gGR|iUtS^oD@4#PEP~O66U{LW$LzX8Ucy1&Ss>K`$~jZ6I6h8LF-GPVB*a8`(1`0GaeOX zdKOjGs5@=rCS?B*b`sukmLX#lxdfIY$7SjfukUU`CxK9_U>y*^S5U$cQ=mq~R=`>3 zO5&px;9(YGAKu066I3YW2V|_yI9P6ttSGyBw19T0m~1{d#0EQ~s)if_Yr3+)v9ti? z;JnKn)NNP_7f8p>)>Mkg-i`zCXT72y7x7N$?~Zmt^unVK8o^C_V{a>h2dFxLWnU#v=HohkbY#%Tj1& zA$1Ebr^E%Cqn>UP>w4%mTCS;cpm4O_n0gZN@kmy%COajLlRYXS`G)~81+0@oOiqM* zQy984Xd>-c4s*IYQ*cN5YB>jq1-0am>P8({STmwQqRWk+XFK5u0 ziRYm(%=KL2oGS@)s}b`Lh(uKneDy{eybA#{Y$6>lJI1+F0FgkJfW5QF`ar~8M92q8 zVvq3Y3%VcWClZz&b`_3bqXTz7F}a$WRMek4I2v2iC6BX4uX^_x21gKu(Vx_Ug=eLj zlq7sxa;`=LxjqNqho0k{7c(h8R?Ze_cF%Rn(Sce6!8Efz^o;Z4_uI|sf=C| z)~bo8>s0J|T{22S8tR*ZLw~u|c5yWWiAdpP;{Yd#n^1SOSmO9wn=}xZ9lp=KMq0L8 zl2A*NOxN)MG>E}6*;|*DAjyE_A;=;)702^3g^7G?f^35<)ftOeSrG+rFqWXH1cL&! zrqY#+DVbyYcX`;C#d5r7FOmnc7eQaLMS#0=n-COX{=yjdrt;aPorGnm%lP{!Fd=j> z9BH+Cu67d9sV#`T=(27U$2KEpb7re>+TKM07iXnf+(744tUrRVq)&9;Edzle5*2pR z>38H8kfQ`7UDuima#L&NfQpc4ke~w15GJYX3F>3t5u79eG+NWsFV(>}0&4v{1zwU4 z5))fSYr;3*V{s0U+eQc0hW?31#TB1tGEaL6VeU6GGTc21cq<-4EP*sY?jCVMod~;K zufZiVEHasQY%1^DpcSZAYOT^8AAMUM7Hmog|LTgt=PCdL$U&QhoZUz*;laR|nR40a ze%GT(46KSAgBGu08X_c@;WXrLoFoYdXibEev;ctRY9}Dg+R%GckuqwrBiMclaVUf? zfI}7S{{71w=SPcuqF)aX?kDNNXc@>M6ab_!_=tEbajAibxabup9%78+c+1t3ESDHj zW6zUsL;c^R5(0l5j*8%TW!ZBXEb3^<7C=7Z%$jT{D^Y`rU{&WORfLs2^;Oy?Yxp(r~Q_ z1JbqNDNVB636skxk6nF-I`|Ye6H8YDPv1ccpsVdXFK{NODGFA_damTVgvcB%&Tv4` zId60>zw)XwW``K!EPBAb&@hmmq*S=r(OCys zWVWlN-S!VBkvVHq5s={y9DqwabSg@v4R6DGUn3D>U(>^adZxlV zLWh2eM%6#AqTz&Yh2?-Y0|%j+r0_UP62@Xy39i(8T;fvsNKv zlrI=N$Ixg*@G^RF3Ug|tJ-U7dfd-j;L29LRqD)5R7}>2r_F7z*-`3fi_fmp#EKx!fGaat#yh4dh?IisFg_lVGcVJ@PAPnYewy?1FQLJrKT(%} zF$tiXQES!+EcQauYCH|6N}v-+@TYPeZpb$W_n1_pSFN!5Pe%JF?P=NVUW-$cB+#NN$+9j`C`wt zC%f|cp*7Nd*U;-2doX_+3=nI+LPRrT22d1O@Kkl@#6^fyYMzCyZU+mK8Guc(Yf?Wk z1=$Hoqy|pUF>d5IQ4kIiHfI2TlBb3JBPgp`**pkzn!w1AVLJ?tp zn9$X!SxxGxS|wUg{mZ3mwBAS*1}s%@ahxZQqv}-D^V*EPW%Lu805Ta>vC(O%K~+~4 zJN*^}KM<*)^H96@CVeT~KW~ws@EZYW{O#;?y5dkAON`68$Xlqw9qtLmaZ+;<-wLzu zXv7oOgvERxnY51m30|PU=b{pDUUBpwaBnTakPbY9e?Ee|-b5@p3-BEsxE4kKYpIpX z@F(o`5@^Z<|KV8LuLiH%v3zjgmM)2>aw=&-d#+X6SQwgioR+Fo;PnJx{ivLe-Y^Xr zrbr*Syg9bby>!Kr%mCHjl&H2vP)JXqQX5~b#V;PN+-c;}@|c^x(vmbDRN*C>vb3eQ zmiL3JWh~!j@67@fbxi(7+!SjVO%%0nwpEhL?uuZWoNS|IXz)-M1z4^ESJ;K zL2O=qjMCxAfuW;Fz;6%TR;;NBi~iQO!|`B_bv~Q@%lUem3RZ;eA)$QoyNiw>MrpYXuGy6))RP*1N&^2f%{VV6+s1DQTt(-Rg~XmnNs@Su^pv5Ynb_BpepTR43qc4#AgE_Mb%d8% z;AD4-i^q7X-VTKg4p4!yE3Ct+z`x{R?QScm)x~ph<)A72{_8mjlJL5EtHLy!XX@!_ z_jD20WC#OHL=r(6MhZFiNO>Iog-iIb6H`R}0cWU>ou$V@Kz%TJrHVlv`#F^**NfA@ z=k3z*kWhWwzX*ur&J=Ao%k{EzezPv@{Ep7U4s|79u@}~ngS{FYT!ekqZH)tg8U(c^ ze8rQmJVH#lmtI-mg2yN|t(9Y72cjVC0^@?YRu08yf8+DA-0)m3+5nImJ1qY;6VW9% zHOM@@+|&YQEh4gs+T7HpL};V#Y|Yg9IQ$I`IH`H^!k&)NqFG?5IRMP#O+p33=RMxY z94F+#8lRbCc`hxeRh`Zw2QV1b3h}lcD-#d(^?PGqUGJ%YL46m8jwzLG`U}{J)r7gb zSC&Uf+Pb_sBMrTvY6xh4|ZZ1 zVl}_TM&mK0KygxOPyy_MT_85Na#`?Tj76Gr?imiZ$`5=^1J}saax(w*&?KA_2_;F5 zu%1igLk@BYJcCS&ar3oSmo6dM+|{KRmxsuO4E5&oB0itQXLI_W;}gUrUPba@%9Rhm zuJ6q_gw_B60CyAUSCa{GB0+W-J`Y-nvROh{KNCsPFb#k>-SPI*DRX86rvj2y)KfKL zW@%jPOoN83!hO@>Cxw{bZ)JODK5`x8rj$;vzfC01+__%Ms0~j3y&O*jO zQKcm$K|Ozkh>lc|fb zR#r-EjDPG*5ayE+Q^OA^jniE>pH~7LNK_;38ep8NZ!y?21Z8Yn7T-7&pMgbKAcX)Y zg+MZd>4h$x(U29OaV&uXlHm;w9Yt$#Enm$ThY*B!0&BBs9)So0=?BX(9$-EURiNOJZxL z%;7Moo_L6MP0Iaf>D*&x_IlPW*CfTkz()X=KID5Dl?nhRbPscSuYEuLqTtf?d053R~?Vz=*2&Ta)`S zeVvnV5_8zocQ}nm z6sEoX8!9`*lxe7WEyVn`e(6fIiT0HkIJ7#d$BjBt7vxZ0*#nN+z*!KJ75LzeQR)&;#Ik52E+=71l>w?=m#V7YdV&;qNXf zA%ip$L;-OlsB;6b6I^U4j<$nuC@%6{4t`}k z5|T~n;~%0Fi7*k*It!nkjFY~9+tg!aR~%zj5|CEV`+Abgzp~2XLVuT5l!AQ1BAu}rQ9f}e0!F3d)U3m21 z66L++!D5EbAZl!%aNj`GC71p47+qpTMoJ8Z>e1&k(2hTZQUcWS804e`PTp*1MwOWS z#7FiUi<8cI`yFvb#HoPu$U6`&F3iJ*+P>_8QdZbkEy(K5?tm-+ANGMj8)k*9#y z-9B=_YeC5&qQOB7)8@&m`sTyfz?e{?Vt4|UQFL~@19AUENd}oyWe;xadrfc)!iBb; zhTojw`p>dlH?GFKjZqQC+zzIy*3O#X1^`Ni7@!t9AobKAYc4ef zN9hS??uvuCe(;&P&BAu-@(~inmlExgA*TB_tW72HQ3TL{KsO^U?3TM@-()Dd!Jm_k ziK=L{F6)U5I)%kmE>oGxo1uw7kzn;Sc^G^L<68;AG2l7^8AO9bf*~TgK>0?*E+`9x z{9eIm!P(9_-y2^702DM5N*1IxG*FwS9uc%)8uu}LvM_cC<@Eda^MI4;_jm(`EmF z^gtl+14;$&o12O}h!}dBak%W9TXjc^Z)mI(0SO>7wvQyRny{dKlzzO>$`tuN1%Z!b zx_|*`1_7~WFuE>k$kLby+>40InlQz{(sywdUZy#frY(va%uAmy-~^Ay(8`FeoC$Ey zWqt{4i|3~FHFc8Z5m{FW9MS9QVP?AQAF}YV$Tws*;ChFsyvg&%`rx(1iPJfY_O~cv zOrXZjg|XgG+_Yw&N$t5e$wk64i~o2Lz6A0MDx_e+rwAQ)l72MM%zmEXb6=DxIC0{X zK41bE9pVDanu>?6I3meBPH{BWgQmOSMSQ=Zi)a-uX<|nF0ZbI#!y8F=hCz>Zf=giL zXxLZ+q@%k}X)K_O+)%F?nJ(-BG?*T%H9izWAb?R*p?yCQUdzZ_I@{CLqh(U<5oCiz zMgt{8J@@s>V&hGK16YD3QWE8i0(+=Rlv;2BaGfiO>Z*uR8b>?IZQ5HJwuxGW2P4t+ zgE6{jE6R@5t~jVPuEwmqJW4!|phv7B2Gl8XQU8{D66AoE#;3VH9~j*ah`+}GXA|5D z&KTS`x)(Q0sgxRx1!Ht0%!^dLR3U+=ZCwwiL(2nlL)5V(zJyo0;G>9bVwSmC3S7#KDF>{Xjby!U)TXI zxgrF-JhRd(5vTmBVyAIa7YQ%F{;mN9pwc+jp&~Uls_pT}2^PzdUiU)A@LV0X)Y=7=H(RRxeqF0G;Hun@C5ssMoHNqr5No=QbW}iu1R@V z0~BwjK{SLRfwiJg9!q%-t7%6!Os^V$!*pGg9ujq>z>fBlPMTp%nmUbz!HpiG5YDnv zaVC^{k0P}KfYSUfCW4>h)R8%elE6wxpUW^I#69D>+T|HSV1u2sS71L0<)??Cs!hiZ zV5uZU07b0fWM_Ie-^DD7_fJd_>|I3yuA>K83zw!9K5&xM$_9x_V;CY8Sh{bE`kg~T z=y6svKuMAqq$vZ@%cG;T+X+V!QOGpGTtsl7O$Z)A5Wr2TlbZu$z)GO5`!FX=;tWs` z_~QAu1lm9?;YPN)EKi*>trqUX5fP zYe5dcq8eE8&bdfD6zG}2o5e%nplhI5q3K%C?>IhaEZ$?2!6?xa1<^fAG?9(Vc}}n6 z8H!W$ejf=5h&ibdtBK|<1_Z$S?6?4sR#V{EU2$!VA&$@ff$s#cl=#Cw8H+-Ii;^(p zTq9DE(K7f@zXSG`+%6Y%9g{7vCquNT2?MOT!=M#b+D_aJD2?wU2ihHA(1=b5D{o6d zTr*h@HoykwMbQVrg^iF*#uYX=uUl!3L8mysQn!~H^pMV+j)MG!;!36*ib}B@R@(B0 zUQUA02j5Bb7#h+xhz|gI~}2Y0IjA-D^b^1~oSggpqM04YXU?M?yqn!Ov@WrpzYLokmK_VF!z7 zKV05$s#ZxfJZKEdCVyq63569FY>}rL)0oT}A^}pw&Yhxpiz)Nhy?xIWJsaEAPr=&W z-sc#Jdf9t&C6RXPneNfw=OCnlXpL1oACjrbk=(E$K3Wf7!D@i!*%>n2U*+KZG&*Nj z=VH=8(qTJp*HdX|4KY7X0u{QhE*BC+gi`tjiU6V|wsjDE0uW^7EskU(b`M?-{cXNO zQTg@Zp?*^J{WvSVTp#y`t@V`bsv#9^83auTjx@N)R)RsVt={Mnv9dCkse$pTJM5+~ z%$yJ(%)aElKfiYI>MZ_6sACJR;v%2S4q!EwgZ2rVXCqu`@h>*bqJbb{poCp|t_?B} z;XXx4X#rs|D&cnaos9cuV>q#hysSWm7#S;~nhrY9#XxQp)cp;8r?LySeyJa{-d?mn zI&gTf=OI)ii&lR;uJg&rDiJgba|+Q3#T#V;NIvr#kcL13kS3he+ISU$F@h@lcm(*1 zn3}-@W%(Slv$l@}IPp0M+h~NfFtpagzq1AlvazzST1``1Xvf~;w|fe%&xW+Kl(k99 zNZ(Wk+(LK)Q5KNP3mNMUT?9AMVWY7-bSdff4r+sdo( z-p3MTT5)roUY4G4y)ncoY#N06Dl{L0V}~BsyAYClRys4bkn}nzmg$Z<`gr6tX5Dqr zW6sJsTQC*d{A?_qs0eYMjR$sEpE!X9rKc3c9WRfQc%-D0`Fie|JlUP?^v#Y^1IAE1;L51=*!r0J*N z_v|}sQDxvWa+wene@)^`%E`o1K&(q6pg8Br{HE{yMs5#z@+$uA+vYWb{*K~mh%XCQ z+gK#wlDE1mwN|)g5r~0itf(&mpkar2gURB;t{Ff^G%w9)LpBQY70e=?eLRW*e-*3% z7I*ZxFb4kX$v-j$Pt>Q%aH=Pc%YHk63}}=k>ttvWh1(C!+Pq^=&G>b;Jh|(=6suXN3jRmX2I$IL>eC!O3;{hhq ztVKOK@L1xA>K95m%z0zI>_QLnF1nQL-pBt2}4~8MUZAt7Nc4Fm&SWu$LUB zcrPfxg@migwQx7g$MCSfxA_DweraFSUr%X>#Hz>meJk=qJ4-R)DQ;fr>PT~YC;f!tI*DXE%otuu&so(nf zkUk&MXHWXy>`C$t`w#ZMZ;Jmb9J;RqL}CLm01^jHUZIKa22ca^PzJd)%i`34$J3z6 zXaod@Q0TM5-huM(TK*&{yo`Y+blqu690{^%7H;l(I;(8tUR9d&wj(nC8aiD{6DVe0M}HcxoBb{h+jhb$EYzbNOFAiW-cZX>8eZ7hKilDolTbc zr>4{Mc!Ex!(dlynYuRUh- zOc@uDW#mKzb&2EU30Mvi0O@q-Q?S{9;T-Vf3=WeLhjq1r<3;-@%qo)ud4c*8-57_L zu`4qmzrm0=HW;WJ_jfJ6Mb`@uMTE_*MH}pcIBzG0$jxk%wOw_jWyfYtL0nH- zNjwbcT!8V;;GliY{8d=DgXO-9#O7`iHdQ5!+z0z&PiVq?+yU45rb_Lvc=e~v*o*Wq zEQdxosi48`6q(a?F%Y&hm_-siQlIXMLZjI(mv$Fpi-@o}QLheu;(WFQx zM3zWV?LY|tPusv+09()`vZ`^*CmHrlg*-3bc!>9x47u0_kC%9T22*qsi#I?f5=CswbJwh%yP0gx!H@h2VB zNf$4XE@2LVA1dEa(>*23K#&euf0|YL?qg1v*CMu?fB>KYCXoqoXQ(um;sQdte`V=L z!KN?gjJK5sWYRxE+!khqaX|&vMJNyen2;IpB4ID);8pSZe^j#fUjB5csMK&@w}jtf z<-a03ggPP!+i$OC_Y1t00sFcMhk%NROq*Ct_RNNuN&@~Vpb_6z6}Ib`4F=v~2SlfV zaCV)p>_DvJqT&G7G-FmoQ~r9@N#{DOrPQ2%NaCoY3rNTs9*FCsAKH(#I0R75Vx12owj+~RCgi%mA3Z$9N(}aI$0dNm?>8^lX4#8b=Ef2HZcx6vZ{&UnL z64!3?Ic{IUQx|f0s3Lp=mxn)*xe9-%8+0Vno#=tbdK43N*ORN~V1I7pQdaPAKcVgfj zlM$uXea&`+rc?b@ME}zG6W;mt&OOo7L4=+X-ukOY>cwUAY0M>T-bsfESRH=3Nwo;==tjO{`ungQ^UcvYW2a?#s_py!O*kg>irB1T5-aYlc2W18yH@~`xr=;~xv133Z6 zTl~s&2gnO_Ya_~Vs;Y==7PUki7tk(XODS9gy^H!YB|R>}ZTNz0w`xvg*}?xD@~CHk zyZ{D1E0tB%(7`z1!cbxlsAI-<5V?=0>45KJ`2h`>g2j`oOeLs)4^08Ye!5Wp&KhLX zOvF45?C8jN6l+r%hvWCLtCHWy&;A zIZ5BJU7*=u)R}4Ke`ciC?)#e^oX!~Si_lk|`kx~n9tB;R=2`5nj=*TlH|qcvP-6~{ zNS#b3={SQwVC2dklt5wURCvo16=*DH(dZm&c+%KD3UVYPr0r8 zt%n~e#a*)wX^5^;fJ#IG%d*)C^Z_}KIhrWcO~KZBAy9-LD9C?0i|qpT$_-de)#y(u z(jc$3I@x7u?{ir}XKOVVKjlz!em%Elfm6u98*{Xk_)p|#4LB<=k9{F}mjP7L5yeI^kp(-WKo}r3oUqyCQfNWVFD7o^2qsliz6HTc z4^aduf{h*Gy^CRpyS%yWGdNsd>;nXvWMcE^_6nlzSgFYHB7!~CuM=T!EN$Qp67plz&ew>xqM->oTXtw?3=!iY)fs7}*rC%aI^lqa?W5b# z-JgAhOsAx&H38|4jfvvD6zlWyc>Ys(3i=2y#9<&h$zDS9?;Pm$v&2u6yldMaSg@9-7CIp9w{PDOiYs%{%Q189w@S-r zDt-*1#?Ex`^*CZsNMcCe0M8&+0{qa+xd>d;d_jDtzyK$teP^+7{TbLQ^>SI>%7Pnq z-I3o9sfAGdMN zs~^<=gl2kw5Y*R)1~p4K=q7>K!}S5o<+bPz5b|0|=`;)_hEC`>?o{vu` zz;Q_V6Mk@@1f1oEerXa&C?ZXO+;E)<%cKd!@KX$-z{JiIVe_~q9)>OuqpX2=wR_p% z+bM`Pjkq{bZ*9`bM$8r=Hub3jmJ(vr4hPP_zGd$kBdamOfKrtM_(-FlD4U3s8bP2i zBs*(>ZBpuEYt~(+bpHt}m~Vu|b>7lUc06Mo8n;@@>CDpLn3EY|xXs$wd6&00moodg z1fa0csUL}5wUFOIHjijO@C{Cotg)I1w=j^93kQT-Lnt*TvV9*xBe!JnQo2-(fEn`N z;*^kHJS?J36Da$8XFG^gvVj2uk5Vm$QUDlx-;phO2co|pp{^TYm?+FB&svBvK5L}5 zgTrJ|84I~SK`>1erxF94=9C^kpL9V zwU3-4ei^B*aF3F+xERUdpsp$gxi5YYdxBQZMfNa=%T#^Q?}Y$~{z1(L?3%y*dWmIC zSZ{Z|H#c6@sC3)5ZH~4675h8yE}|fuUA^=JZc8@(*j>So=+Nb4@DaL@4@c!6jx#X? z7DIVJOdvu%Zqp)}E?Q$v#A(vc$RNE?XUfUWlK1*fD2lyXgR?VqrU0^PIo*CbW=;+U z8gMSPoR@FKYm~HW!7Bq+A`_v*O&u)=4oQju7|->5$uWJvjF{L-Zg^NUBf+uF5NwwK zGT~m(&Fo=7Hxt$2fm^MSWdEr04D#gXZ+FaaFWuk&GLL5-uj({!-aJlw0sA#3ipvk( zlAWeTj{8ee9qfT`QV#~^MA!JCd_X~dhQvb3h!*sV`(|UlPdPRF5~+KVz-fN{#c?~-!2}+7djiAVCFML(0&B8gmD$1G2bTcH&kzbP zXAe((Co9%a^v3Z_N+ID4D5ef1F1Y?3g^CBw9MZ$|sj^~@ew+w!_9J{fB0rx4^iVX| zy;YEA9?y}Nm*@H0a~t<}N+MMEzR3On1pk*ji=P;xpZQ^w2;KI_Xls}$G(&MP0{Ys) zz7f?tc=N@`DERvXDr?BXbkC}+w!k8bKa37Qv?MU*$-tQdhhRk_@U(bH1!4#8zERy zZd+v7s)ZCP^tB;_Otww8ASols(5*d`m+T`&ODvCw$%gbw!9w$k%fF?r zCSe8+*> z5{*V&Ziov)soJq4EgL;d5r#(@{z@Qx{0BqR+Wo9z3SkWI;=vi6|NfVTprT@etNmud zqQ?D)5zv?Rzt9yt-R=0lW@G#S6XGY`P>B#i9VIY={&bBlM0+Fbi=B1x5bpnkst>=E z6^FdTeu4<8>Y}N?N=8MPSncn&czJuO}qhAa+qT(XEcB*dJ2+=}4l?|(KbqAWp)Re&$uRthRG1OM{Fx1pwkjw&= z=${FCxc1J8`CR-!n|@1~lyNztTwA|tXT!b@CyMsHeeIXHYWrW0X~D0VKcLpjjO@Yl;qo}TP@OI{Wjqk<9i=pWb%1D@E{tz}v;skFx_z*-+!i}shN0MXACf2A$QJ?Nz#f5N@ zkn?OHPU~r&1Nbt^-I}Y>jl+LfR-tP=({Kh)dGlQJHd24IrWu}`qC zqcAe?&9ezD?U#xx_rJa|iHF~Xo32}}+w!J~=%L}cACq`T5!gN!Z%Guue2U2?=}IR0 zRSiZ|BJco)F=J$nF``r@FmS1ONU8J2BsIsSgYPy746Aun@x)&a z<$Z*sMg2ralkSw7?JS2dl_K37hP|dTwLJ!jma4!J#RJM>+#LAoTGDgYVBc{=_~Ta0 zBwwWNnp?!gY2{n)d$_M%ft@KG4*wGQ87tZ|LYy1aLBAvo}--+J3!lJLLsc-q*mGNITgU; zNFfBX2{Ryac_Id2u!?wiT-Az8ps@$9)p0MEEpdhKUlI=q@LahXy2KLvhox7#RjA_T z>Q9jQXUG4a?D&ICYQrT1U6(>Dpn@D`Ras|V@t|ZUr9tE2-u0GQa-BWJ!z!K6Z^;Up zs^Ymwj0^t~`5WolECjfx6y=DuLb9O-KVx~xEzm#8rRz0`2w}u8(>yo~0S)U$uCatD z$f@G77u_~!|1*EbjfAH%$Nz>{*B*00elu@xXqDFOiZRDU5$^W!Vt3G$0KJ1BHVGPshXMr0HP>_E)r>2z4|do8 zB@cuS+arg%|kFXTc3<#}94ZzARV z@+2WA^8Xb@%!Y_HmS<7>W$9#W-{$meQ04sJl5%rW~naMI6p&z_Ek zLhnl7d0)Rik#gnig1;|cuu-$dM#=%zsyht=lhF(V+J68IXKhF1?)cN+#e3y$3#c}yQ zO%wOwt@h5bFG?538JbWB4gc;sGuJ;)At2 zN%LcK@hVsbp!Sa*5sDLqPiHx88XRAf%U^W_z5J&ChJG zSdK;|Dz6@XuQL8~A3ySnGNV(UMWQ*fu((VUY)X-NRvij1h?gu;Eit~C z`#zd+f5D3tT73EKj`Eq)#?EOKG&B^85Oj@N5Q^uvAS`LLAS~&%1QhqHv^+&`#(3bV z6wgPQ8}RD!jq3w%@9^5|F1hS_eDy{{Lqq5Z1&wQ*IE`wp^b`Q=GPE0o`{VE*k8p_X z;;=J}Uq3DeXtFUCo6?w8>xp}f3rq^PMlGOP%=Zbke2gnq76(g)6=#l<D#}=yLIjNJ(*04r@DJ{him{#d3_2XQ2R6$IdVBR- zo<9%s`s&0Qf%C79-rgO?rm^4KD^z{ont+QBd+ByqI(j@IHQ_Qd@&A0yHbN%w@b)){ zmKfqXUZ6{+y2D?7z5H|%?DlkWnsfQLch!NfHm1V=cSgjdI&Ru5=ep1|{BfE{NiS~a zjzY7$uDo4Qyj(?YnF0rs$wBCA!|+D9x}3SJXv-8wlmLP15pE#{E7N6C6Wha&3iy7)Q{0S4cvIQm2dOlHYWv00)XYA zgugEdAwn=bmVLn$44*!7&c+j}oz-_H%3>~Jg!#^Fe_#QeQ+n<<$5RnmhWjGyzOr&! zh4hXrNAo3Z(>HG97Z3$VvpjZLRv;_8l)6Me@!ppD3jpRS!ur}Y_k2lOaRqCj$8{ta zpC}AEx@0kwN&tLqTGyBr{Ei|Q9rYBE63{j7&)A%Yq$pw<0x+TmHiW^EhrAgAtHX=>u(T3d0=Ef8!puD+vG*~T5dz7sh&PF@4B z4?3i{tgr)_+Lqdq8SW2mXS?w$#>z#Iey$Ev7*Ln2!}74_jcLTY11(Z(=92ZLq8P3D z+p2ahIa0q?3eKHWoBPq}KWD$b4t&srI~jQpT9)>ZSb>jB+Dd8l4!w-MbH*tZser^5x*y z6$%f-eUt;&X-(*Q5`dD6Vi-yh^zgTx&5S{i+=|kbWSwgdBZA@=5v_leM6Acf(D$tT zj}`>V)>MqMw1?zY?{qAp*?qkh@8QY3US+-0mlI>CQ*FVbuj2VhJ2evMr>0~wEbT1y zT!l5~ktp;~13_AW%9&;cF*W&C2D~;R@^LXypFfHepB01eWVgC2A$B{Fyn4Fu8)Pk1 z<5b(>Nl{%basIQ=tqy$mZ|N42sGdvUbQBLSZGq;yoa(w)Pp(A;(7hsfmc+q|ID76Vcz-?DXkWk&=JsoQxxrc$5r1mMFq_Gd%^3L(-g1eUBwObmV9> zEdg7GOKH|gJuv$lO{&heJb8uZA>f~1Z{4ovu@sl4qNqLzQiLRU)Z2P1qxU1KYGGG9 zxR<6Vq|)5q7T}bq4#$?<8jq^315^xHhdTT}|8L9xq(G8c!;ut|=rlHj4hIXMMUEnM z#p7!MQmh%r`j13EQm2l3R5xynab236jcj^ny3056l|Ik))29mq8j;`_Lz7Kqrf+nn zcJ9{_JJY?e6V5C2BvI~?Y|v{VI8;b@4NULriCWfL%1d#m+!TKMHQII?>v+Y38jdY~ z!f$VH(iTTes)a2})OAf#{pG0D*I*AUcwf77@AIg%uk}QRMvfFmsm!JnacjvMY=_4} zQ?Lr{b?6LLN@I3*XSzGH8<>L)ECvlTHkUgW*&Fur@2Ee?G2>S1I$tX46F+WKN^`h8 zjB7~y`S={n59m;BDeP$|V@+ty^=BNH4iETQHumriA08*zztj!2{t2y5kp&57h`V#u zg=sg-Y}~kWhr$Dkdh7(j8IzwH~M0xohx`^`%P zt^BrxEZgaf!@jRaSN#|{&P@G=%6ra^-<&hKX(LdrSD6HJFm)IQ*$}#Z*p8hM@S3_y zP+efU;(YQ0iyCG(Fo$%|B&?&ym_qwB*w4SD{{&ef_egp`QfHEfVSty5F@i5!o;@4u z*U@0a+LTO*KvXR2VWN9CY0@NK@BVw>gXVAe$a^#fLwm{K-(6Qc)}7gznZTF7B<7-} z1Rq@h@JV`KkRliu(G|UrfQ|mRh-+_HBGkiU*4)BRF4@|<40XpN8d=w9ZN2a}I5~#* z09-)`l`J&51*?ZZzg71YIn)8IM`rdo-axjVXC#1q9;Rz;xUAFIkvyu@)4Tbx7 z=-Np!Im#Ksz(c(Qoz>jnMwh8mr*b-blfV=@WPIet(6a;+o(ca(@PhhsjqS!?6P+A& zypAyD#T{{#Nl~-WduOKsLxUQNvg!Cjox{K9uK zEwmBDk|DLkv(CNxc)|JPkeG)%7xJ>Rb^i+=@h)Du_B zJu0R?(#3YozL9kLv{Or`K1#A;%a8nVaQ|G7B~Zk{P3o{d%(Bvq6H`c1MUk-qPL25l ze{iYDeV0O4qDDL6fQwEFe8&0RQqQ39mZ6ScY#SK7TwyCGdf|9DUpa%9yYjwXrU>I7 zSTIdrp^ZS2Cz?>UoxF4QY-|nvifN`cIv|3*FpYXxiO7lyH@`To5^Cnn2XsHbf?c}!>9 z`D7}`T3)=6_%9M_y*E6Zm5c>gABcS#jzXqXW;!DjsEkmDjGbuP&_Nw!!@>dN1>w<| zyfss~%!nm14c*dlVs1vJmD{A5f@V$yh~$gb8sL_td4DIO=Gno|*_- zF@rros<07&gPHzXqL9;5Ty?P-N)vOBk%XQGLuku#nMQtNI0)p)e9yWE;o4ybZs}*| z_4qCA`?rQ5Yil0o{Q2{|AK|>)gW6^b3itJJs;a7ZKpU!^U3CsF=*N(-Bm3b_j+>j? ze-$=Ej-Xbg8F>W#@pw=+RYhVw7;Y?;g(q=*ka^!hUUYn7iMi+`D`IqiB<5Uy`zRPp$F zpls)FYvwx<#3BY`KXM9_dB~cS?C-;`q}_ErtZ!gYCTR8R?(Pmb`3Ymuy%o^_&LNId zq^=`kAm$cHCQVW#1Q7t7vCwuheXzrD%&BVY`Ni;m|E7s8$4o6Lg0?m-Mp2yD9wf#R z>6m>3I8*TOq2hmmvGN)b4e<3g$ROfl@Srru1@N?(=?@V^ zkhrE1xkB3hp}x%kNI|_jV4^b@aZV^Z1CWY)DB)LT1da2zT5e3eH+3QJj#CVvyM0>b zQh+m3lVCaSXa&H^a4(2yc*!Ok6Fo`TN-XRF-Ly=rc0EWT>w#{8{lOQx(q>5PoJ|)C z(dY_o+0NPMVx?w^qt7tSs_x>ie-ak0Owyzl@_vlp@KEp`r~GZs(6+<|zW#j-ll$Q*(?&z zu|oi|FTS^l1h59*(@g~W*=Wbnd;r2VwoFs=Q}94~zDTu@lE=lN$@YH-O9x?>i>l$? z1FR;4y1VjSaC{(~fs4fI3|L*f<|CAhSHtl!wvOmZmfp2sl~B6v1aexk1Pg(o)}){^ z@CZkbdaz{?a2(F-qJ7ywp+PyYbneQHJIa5q9h3bq<)TU#@28?0wsL@oM|vr&QuRna z&)|r93XYb71G!qx_UyzmQ}{O1Jdj9yiQzXoa5QT7q6W?VI*zaKFggL%*X@(&+q3z# zw(0(Rqn7A9eS#Q%KP<&D9(X;jWoWSfMX7IoIg>H`eq$U{`jXCt3M^><;wYQ`rRyFv zpn&7cAbm55DJZ8?#f5`%Mb!=Tj)kaG2XuSdCLJ2gSX8`RMFr=DT;0;~MBR0%IlADu z6C!6S@9sqZm;9=2U6RO>_$5>U#Zp#KlA{v_!J6$@L_n0B%3M#csqmKACoq9&+MY(Lae6u$-Q<6` zGn-V6yh&23j-=@igw&g`^Y+j|e@dYbP;Ga}e5^2=s&IUOY6=VzQX*$yS{}dH!5;}@-G3=>S=D&lIhI>5S%Da*$RELQZGJLiY~( z8k^|riq<@dO%RP$RL`zk_bE1b|NA%r#GVK8Lx-1M`(@iaE-88p_rCsuRpIsC#Yag2 z3*oB^@@B`g=r4(cJa0Pu6b#>p@bByG^jJ_q~mdQ(Wi#WdF< zdiMoHp?X5jyB)P9Yn*HLa(e?2cXRMzx%j816=k(qFkWbA-NM2OQZ*96Wz3SD&~ zBjIVF^C?GSL}BmEO@QW1`q9j44brHrg9a?@F~i;9!~hrKrs%evm$ zz8@o}oKm-nsQ8v!x}EU4B4OpdmQiayvOtGUNR7pdjnJ7Fz@{6 zZN4O9P9~+Rs=Wa6(E#KY@eqidAE8OgQy| zP+Cy-U)?=s-gb8Q)G8C7JPa;gv%es_UaBNOgoyX$pBZ9H_M16{P9E`!S+Bpebn8mQ z4IMqoi_;#{IOAX$L#BfLPVqvDNd`uY*|Zh*kX|L|Bc7K7w^d?A0dA=|w;Ow9(p92L z-AV7QRxs7h&2!R0r&RtkA(&~r4-k2RujDea@N z7Zh)RbG=lw1LRQr^nxcK<@YbH9ut3b-^25xZJB#Q@^T+=xP6RRh#+vxo=b zt*B;hCO1sJe+Z`1q6-i+%Lwc&X!fPyD;I}O+DH}hXyzD#{~ z?AhUmFYKSbD*QVRdbvDFjC|0ik2M6*v4HI#`&^oq*PEcHxXqE5H2aNC(8d|}tSo|z z%A|2}N~ehXut$wSD(5QKYBXf?Xv`MPOEAkk`+~BZ5-`cR$~)Untq2pMd5K7PpA1tr zQcf8{Y!1+s4^SqNs78Q6dQ$-A-d_?o!IFUtK0_%A>f;t@M%)_?P)zf{tewm2wb2)B z@oOd?m3=bNl5QukeAvd?mHx%u2j0y}T5O;_L7y!zZ=#|amy{p>>yAHoOYajAgHztx z^|`G(SD7XCWdJ&19>-rre3)CFZ>Jm1p6gD}j}!kVDx|8V_b^N3-(Pc6Kn(xK76Wos zbNBH=E|8rGVj0oH5zR;5nK~#PadKz8(%v4YTjk`;k9k1a0CdfH<4l;Q33$No4F1F1 zs?7w=LdafUA|ME9b?OLse+Tch7TGwig#gusW5zp|KpWa-)H4sA6E*%-X=bU;$x#g zH7dB=?!-}f7TK%M&ME-l_gp{iwFV8RbS7w8}^G;#Q?FfA}b_F7KpdA?QeJBok<6WZf%+>4lMsc z6P$78H^u8b9uHpf{d@IWKkad(VAYBbgY$bfy6QJ{L9dnvhpZUAD&h5^r+TbvSiv)- z+h2RUJ9Y1tz4t#^JMqmcnP(0py#M21uc>R_63FTQAGG<%&|}FZj3=!^N!wJzFf`dP znGV=-OH^Qq{NtNyqvS+htV)B&$akkd*69;D^#YI5S@lCLlg3*&EIZ^D`;ls7RWm+eNqR+wW)0s* za|;i5RWgD0@nGlg05j<4aQ~*`w9bjs=CLgI8`HNEyx{yDORt2(yLu;2`|LHFh^nv> zvA8<=aO+(pJE?Q8eoanuGcGu?H=lue$m3b?L{HIU}wI)kP3xWDI}$*3VP5$bSx zf-1v5Jx8BBVN%IL%#u4zF|ZxuI$yjvzRDXZb0NXbJ>6DtgOgK0{peQ(M>`eIEXd3% zo-K_@I_Cp+7L2BIzRIRQt=-H}#M}~!)l-%%Sp5Oo&-qu%@~U*MN`66-L>$4f0v0iJ zSLI)J$hqDNlN{NSqW+*E{GY++8$<;+%|1Nfz^7_Vdm@A+jY&YjpColCV~!e&lqn?^ ziX_fq)qZ-ii?Pjzej5l=(B3&7NpvP^c?9i2r-@%`*pUzlmnT?=^%K#^b5d5Wafh4$ zO?Ak*yJ)nOCQq$6nH_#oJqz}dbyLA^>i5;z)}(RkRVPQ#mOJ`CHvLLdgPH`k&;mL1 z+NX>aiLTR9!UVO68J?*yY+66L17|0wJ35Nh8uo2Df;gQ0r6DWxo5>wgf4XZ49$;;_ z_LV5nO_!3ex-5;DicNh@|Cfsf6;>gQynzHG8KXx~=r@8$S zbkI+HQTVIOidZCboW&*58k`%>ukjF)AfgUxL!vc zOsBM?yfshql`3Tij*UM8&ivl?=uw%86Y(&HseIF}x!c(5{T41?ONwzd=RNFERSux|DUPz* zuQ}|2oN0#ZNJ_%Ac}pFDwO|~5TWJJobjJ+pT)D+&H>dEly!wpA?d!*BBmp^&F&%3s zl@xa{R2+5AEtLaaeKH{LEEBe!Je#-=7~MT_h7@olrJi1suVyjV()5R^oVozfZ}sAR z*eodN#+&UnUfSQis3=#4Rz2rmeZFGO5U)D5Q`0X@mWT*ooJb4vbMUD zLtfs=LJ3dFCidyWS94dU^q>j22ND!a7cd4hCc0?tf@=|BPip5sN@7FMJv?&FHv^$0aHNuiwQIo~IFP&uWR4?HA z_Vq){GW`4obIQo9?2Zo2<3ruA z&&TFE17Q#u5Ip6t4$pjHX8rF$llz-?(3VM0O`et9VtDXN$9e0lc}2f&9v+e^%%nMy zuXRKiMkA)0V=-dCc{tVeZ*DnvhGb-wEMi0K4`Z7Wm;9;kLHCm_A|1;OoL?)ixT6os zi|Sisy22;M?QWXYWOMS79a2Ga18?PC(xKBCRTczqB_qUj`>rOZT%BmZIE|xZa|=)t z;`&H1@O-Y)d3LACg_VclujV+MmBjCAw^b*P-h~57HVU$XwhiBYZcOp{{+>g-Hu-8) zP)u~g84{HaWVuv3fayP__mX-<@?0*(EtDfpPa67daKBC^5rwziuy@9=(wUvGwwHTH zQY8%@;!^`!R8MbSGpcGMm%i3b<*+jxIeRCmA$x;$5wd1n#bFqQF0!8EmMrVLcyaWc z@@&gRbiDu*GFZGd!&hW-Vq0)CX*^rjmu`QwQS?VEh^?oyIu!~oN9$xGHJgZ<|FgX(KRayfRo2d(sRFu-4grCkR zIjdjZY_h?*VLvdRBTkWHZ%S4*5eqc1mhb17xNkI~jC2zsZ=*l@*;_T*w0Y?m6HNC$ zmeg-}Vp#IO`b5$loWCfnwX*vdWa^@jFg7TgOF$EMtLTo-72#VI;)Ge088Z zq8=T;0SH(@5FB^xrVNy{&Up>f4#eAS|>I)G+;*vw5u|+(GTK(}uYW(M7U|k0BC|j?-8N8sdU{uDabr5OBfYSMNK7Z(y zdqN67Y?EThJX^pD%OpqZ%=t}P`Cw1XWNopLBH?lNVO{7puR~%pf`dj1dgg*}XN67c z=HU+F!EPgcDw#Zq$#d-)zoNK^afVVnrT3Amqd(&LD`V#o+<4&#ZyTiz2~khU&#CpV zJ`nvXjyZWVZISd@s9e%j<4Q-^Zy)-HN4w*t^U|WzI>MP4!%34{?5+4CSG5GbBl@8O zQ^R`lZQmS=-z&8S1RcPRXpRcIqzu*@>-qHuv*@36rD{vWoDzt2-mjSdcrNH1y5SCx z6FVt`L@=-tyTbQpYQBM_H7A8H=IZE=m|NtY=OClng z#~knk^?M0kjlq~KdAgfNz=H`zqr9~0s#KSKg-xI^5h8MM3buz&JrKQXjHU`x( zZj=I|S^R^EaHCu&bd;cO`jPHRZ?T@i3pf{sX2N#8PUL_dh>Nc@Pjf8a+v+=^4M1Vh zM_!G6G*Y7JNtIG$(!aHI^pS7f{R7&2?QvFjYtB^rzDZt2yb?gB@M<-40TVx)$>x~` z__=%+W2wa6LS5ZaH{?~jz?ofm(ER9cZ=Be15I0_`W;lh8<4W_{TG1)f+hwUaqyBDBgsEluGR5B z7DS-V-@9g=in`y76llSbz&{{Vdl7Pr?Ym`JG3%HlnGXNpH{mZ9p$UNXa?OK`h<>LJ zPeld85U;J(m)?(9ano`!tQ$%=TXN&yOE;mCu?P~{&snUQ^xQ?F&^^@eQ9vZ=6mYf* z)3{jxiT-kg(&Ch8Es>Ib^y+Uen^c)d-mW|uu#@obc}R~AmGA+k9!wM1g@2^u2kNw2 zCneXwD{q=s>Peh(jIbg45tyyO8%}EAveaS*FFStZRkcc#yxE_cMb)s#>MzfhFA3)5 zj(BvUVdW#R@U|L=Q#x${TM7n}DU1ku-`|%(GHmD#?R~>PNjN zoem@41X40T`djxV;*zvH$FVhG{IeLLuvn}=O$lj47nof_jP@HQWWUK!>1;sXP{uT! zRnBy`6iAHZZYY^Wwr}g{^ZV6L9qo}<9m1= zcMFI*{OPUg60F*P!@v@(v5WyE%i`!x#W1uuUwg6c15VXN&=1TN;`!pn5vo7q!5#a>PN+qw7}yu^x0@4$Rbi$} zPmBudmAOazI&}f}&`{&k7Z0Au+XTH=$>nHmbg31@QCyZnWRIB~g~L2wdqDpM^EN=J zneZFdMPW_03a?a^Y$~wkl*c}E`4;ItB6LzBP<$iM0-9WGzzyX3Yt$bmL`e^WiGokO zwEd6O^P?PEN$+-}u#ljVq$V#8Cc*2o)DCpW@ediT8uc#li1GnlKBDvbx_jOtAqb`j z_Oi=iUL;Epa8eVD-)Ld0KLjyaX>AjE*V8=I2uz6ppg4%xQUg*z3UD+)UzLU?fv*1m za4}*}Qb}V&`dKF|QAyRJo`fElY(STNx9po*aRfn$TKY%3Mc6Co<>XU!OnS#BY z^{%yz%DBnu>^sT9bsS<5DJ-=IHBR=xDyXSd-*3|vP) zYH0F3u``e?!N3zvK3rjSG~}_o4_n|NxZHSjGyEgV|4e=CqSH@b9NV5u2bMUZsCQ$n z&}kZcArO(QI5nD=MP(4(8oq^}nWDlkRmQ3UEa!3=5Rg;@u!yx9;d5{SkiYE|l7}jA zX3()1O295@7!zU*%$)E7O8#--4Cnf9c;QId*dkkw*nxM8d`V9;S%)J+_nnmUvg`&7 zW|kX`16LYNc@-`+6AL`-6#_*nP{=N~^r(Z&X#uVG6@5182K1sXZ3_lO#UUo`S|jSA zgUhvA0|tDFzzj1Se`Ts4+$G3ae$p@}$eT8_v(TR0Y4Wja=V4U>yj7rCCxJ2Y07@2geUd91nf-h0Uk$1urvO13VSYKqIBECq;sd!gDL{ZG=8A@-$dWzSg3Tl_H&& zb2Zz*K_D#ltX89_*R6_MT5UEOJ|>?=&cJrQn(eQHyn0tb0*AF={WT<<7)i4pa1BzM z1ooPAP!pYnMaZ-_HHAPXqXeYOg!`ex;WJ)J)C!TJSg-h(v@`NChnX(+H_&5zx0jfo z@o!U9P&ABnrua^Fjr`MmlQdC^SE?_Bxe#hMWo~`-u~HbcxjS1nZ&e_(1?b`RhM!Je zP=Cy_idIcvz04BZ(DKGM+e#;|j{{f>*8k=#j@Mpkor#i@sUr?G*#S8!{AOP2hu0Lt zm;?ide>aM6c>Q~Z^=E02WP<1VjZvgGV2-js1ZflJz4T&?fHN{mnnF+dMNWov1Xj1* z8eYoY0E^M(Y0_(&gghTB#YoE(cc1>l1g!>w@NHet>;Y1KOIZl;PecgGJKvyAK3#@e zTeU)@hqb^-r&k1?P6FBx3@KFr-+E7^xDxz|X}`%{@%JC?6Pj>)9RA=1MhTulv?9Sh z@nS$gziThR{>S^KKB}6Bj9cpoW4_taQ%{xJ zo#5UR_^7ozk~;dxtj~k&fd6>+mD5s|?LOL#i3o9k2S)|8gX7+d;XGj;NN;hfUR9tR z$lEUJDMYV(W@sLjoMZKReuKAz@d46Uc<+mE&S9i~G@j8u?}hGAkYuPj_mRYya0+8arAZdQCH!IgH9ar0(kh zN^EVX3x;MRNKizzoZRG>N zpQNNze=mlY|3^%%h7E$Z=kYuj8&>c0%`H0~oOR&C7ySPp+w{ja{c)TA_uMA&8>>o| zy}(L|Q@%8|<@gHB9dQxW<*EY_2ZT{_C%m9QVrd_YtVyd8qAO=jw9K8~L7Xx1?pVw8 zrPjuOcCFr3+Ul~jlaPRxS@Zuo;K;7t-IpIyeA=l5&P>#rCbem)^9MJ`0TI|(%Df~}KS)EYNE9(=lit~CM>C2Bo@M(Y$ z#TZVsVs5Hy4Ia>M(>Qp9oU~4O1GSThHEC$>qDAtrI1AH5Z_9H%%F%GPu zDM4^lxZhtC-fngwXsz|e-E!$|>JI2~rG|ANKWZP_gRPfo4hZbwl{s)=6NR6Jd3_*Q zNI0Aj$$F1aS0rab*TgU+)p>ZoZk)Sk0X=v;U8?pf(U&VeZkXn#ja|o&8EZ{wFsS;o zG@m%#;sGxKUUn70q;$|-cPML2|B8YDF;;wAo(d+KNB8Nd58cD898LnX%%9{MgV}|Z zqf+A&4nF9evh1&M-74!x&4e52%0~gp2d}K2-kQ+~q6vzh*2JTc_L{IHPw<)pY!TF9 zSR030P3u62XT%FU>!;NI82BJSC=z4X9M5^Z>aQA-I`-&u=kv#P`S-Xk28o&i2@z9R zF+VDtSB@eP!$~S2AmA$3=K{^I4%!FXl;oBGRi-oT<*?PS>XZ2-9ISq4e)iR{qM(1i z23l9zio>=#{=KulK|z*w6PL_389RI?7Z?5gS(TM;OzV=D^qxJ`Q}rFBx+-`uVN60x zCEvOH3!YWOfk*%f>JvhfB`IM%cm*!e!D(%Kq>_@;Dx^AfNYxOUD#PC8Rw+BIqLw;0 z+bhZ&<@(bPCecm}U8}UApNN-+H7b_4#K|glW2RicLZ|gC{NXPn+bp`>dDlNXZkzj~*Wdo;d8fS3`jV1P zu0D=yfBxaEB8Q8O5_)xCax7wQUfh6+KQDcCLdM)!lc7@G1Os?ehEb)z2pf`XztKv@ z?+@=e%r$!32)3Qr4j1q3DdNf^1L#H_6G0}^waa65+6E~(1*zLWj@(388=v%em ziNaWet?UQ~YV&0NDvW24;a%W>Ww+&*scpUMX@Sl-u(d2d)oA0O{(wr*V!PR%sj17pVXI@p3yO&U9I zcyy{-ieq$maZS?GYEn3Z%kaMH8v9MhjsWhI&iRX+9AM5SGcybq)MUffL6Ac^18R`I-SX8dI8av^+r0kxlmp zDPJ55frt}o%-Mo-_2>lGJgKAq!wD(N3TM_mQE@=CoG^%FHU4dhs8Z{dsN_Q){p&|h zr_Lep&kzu=_nkR2(8H&O28Es-#Nz8DhlNY=d!=%Ek#)Sn!ww$VO}L`Oe0q-~99c6X zE|(|RQ!nR9XvPf%02)@tw=1c0b7abqAG<;_M8&39zI38d^eXUqDYZKjHrr%Le_ zM4UTOcR)`0faY&eEpkhPJJp)|c58^I+La!?H-7!Y254C=v8Z0MY`LiX&)En( zYKU}_iyCmBO!{E>a7)q6vNbaoZPB2!9$xjTFTFzI0V|TYF{~#CBsZ(yx=w7MtQX&| z@`R|!x-}bBQ&@4vN$#|5{TUP~R}ag5>Ju@u@MtG~l|(vLK^$hrez--JgilV< zq8NOPLi&-ePm3RMiSq8W_a#WoWerx$FSz63Z#o8#cV0L%u+fCYm-;oXc6a-oGE22i z6!G@mK9;kGnG5cE`uH%7sl(Jz!LhQhKgEH(+6fR`cT!uhJxpEx&MICq%mC|tJ#Q%w@#}4$aS?%7FEZv(uw`M_$^UUv# zY?{62QG{u-ZR*gfT-3rJJ;Hh}caJ-|yS$g0Sw$Wu?*+VL*>*D(Dbx)59;p(Iz%C1C z8gk#Y4gveS2QG}|h9*z%ey0P*o^L)o2$QM8N8bH9MeGzgFQ^o-^0QK}O!3T|25{1H zqzXE9*0ChKh!WnIY~`4h7s8LObaB4&h?<@>Tgcgp6We1+5tbR48j@Sr^koh#7gX7+ z-jb;g7B!tRZgFrmo4OQL3_5=j9w}^k_uzV4KDxU@^P^7);HS?Lti&3CYFAso{5<|5 z9&Y}V`5dqP8~J9AWPkZ_7jCo8Af?Q;>!iMOuT+=w6vG9Zt-G&entX*kvHZDTx9QWk zzNa-z=@sQzdb=OVr4VDCHEV0lY$EqX*yYW(tV?dD$!>Gy3`bnXqklH|@cu8(na&ou zW|e-{x;2}nC}beTz?cgoGnq#Dby?EQ6w#J5#Qol(P(tO(OiUnYQ0q}Gj@R^_d^1OK zlf9_%OkbTrOiK$_Ti}&9C~{BtzA7{@=WtqoPxmglYZ^va`<6T|za^9`fqbWEjq{;c zB#ehlQ1V=?w5GvmMU+=zbF)gEaW(sgy_E{Z-hc}>z?zge$}~w2s2gpy1b4gpj}BX| z9rW1k;iHyeez``|GdLlEf#7KG_!AoT2Machn(m;=T$nSpbg5G+zmh#!e8t^n9Iq#f z_-2lzCnX)cZOjIpL0t2Yp%!eg|&b)1>r{wYdw+>0sfW7=r>t47T9$p3UTGZ)#ahx`L z+^;ZYy3XRQo(GOdO^?$z@R$SkN^mVmUi1tWjN4xjfj^N^OJtvLo3lRW3o1l6(Y%pc zwE$H6r)z?!nm(Z9Hmy&gLNj~MStj?)dx6(V=4alSJ^1UdFwk*5U&cGR;Wu307#G=} zU~2dlLthd=erAMde=@3*@k#i%@@w5q}HCyDm$B3lG|DQ(u-!5oEp~U z7uQYWBB=%$YeGtb#lrcVZ5c0au~x2A5%^mGb3^g3Dg%liiyz}<(?^b5Tv9~!FI4E> z=E6A-e{6IuzkxQhA)_*}w2q+9#??SLLT$W+&fdbAY`_gSPW@=iSN@`vE_lzHukZzL ztdGjToB|W`=M%(Yp48L%cJ2%dY7P4>DP8Fh;&kxy%^%S%VhC3u``evPGAxvBncGzU?9>`Q3?MoJ*dJHV19e zU?(DmJ4<^0c&mdpvlCNJF`iZqo2N5}4}V{o57UfLCkHm*)F=+POkPfdR=tHoU&XwU zZ`({i0r-gs zY|{Bgi~o8yRGT^DReq#DuawexxjQJ+fH!yvjgu2#W{ z?9Z$g&>|-=vkfN8G;OGBgoC`pXU5bjwm$1#xT4~1aTmwhMh=$ooBHibSXvx zO}C$z*1CQ<^=a-l`#~ES zh;@qX>HNmFTy17i1!F%_076ZtC|*+-6vMJ{u`2V{oWR75GJ`DQJHEhCx|5`?w~6_D zm1^I(A&Te~gfn?%- zOH_q~Uv>58r`4GT`Uw>*F%$w|q0Ws4kDk{8aQ;;!Ol56BUE-CXD!?F|#FIrg*M%R< zI6h3~kVBe}h$+^PA*BgW2yGfxLz7=2!B#9w623GF;G~L zI585gZyFFns$ke!m2&r2{LZeAR>1=Ks8Fwxr0bcNFvAtGm_-d(hWWQdEyMr-wK$|}K85#%v%r|9{Nn2G6P9Xg>l84$BCzmwJ$w;w zGo8KW+O%xRV*&CHJaG4=WHvv)*$Z#gk45Xm;`xo9AVTqj(DLjFdPc+TL94&$BC5z6t_F7jv^$w;V*m#74s-; z-ZrAtxHPLcWmt+`=}YAnxk8R6FcqF#IFs|G03^?iohAagslL#M4bnT6e6e|X!ZLgl z1WZl?6aO*47=SeY(~RA|g}aDjSc*WvD>MPiaHVi2$YwOJW}o)QLCs4YhL>@Twz8Hs zXE&Ecv(I@STK2_jPi7k{%`R$!rf!|IV_vs-^s!NXKE3Gpb2w+9>2FDc!a;mzf0p)x zx9(C5D+4bsRC%LUcy=knx^{_rEIDsv%3tBur4inh!*A@8@ZaDHZa50VMO}9M*MY z^?Bea)W`%py-$)&Ip?Er@Yj|-19y}L{cC_<&Z!ra<+jjX#~snm;ukfd}n z@)xyV6W8k$HxERj+p1*b3lJ%8YGcRg_!ONITD(#pUvHKTmC>Blh}m%I5*0g&VDWsa zbNcEd4vJ^R^!pKm#eu@^zmhY}3FpMWC$l?(1$e0@tWB(L9wug)9TvQIG~#vZOagYi zEm)^;zD_a-0>Kis3VF+!J$+a&=R};-@n!`oweA!dsQ&Zeo-cJIWXR0zSrm@eZ*j8)~@|Ca`>=~|r8`sFMOXu$G17^*wsL%;+ zVVfusWzk3%s;@yLk3h$stiB`m|5_TCko5Gcgw-GL1-QL=3EzqVeFCe;k_=-B#moZf zo*C^4?_v`m{{8hjSOWSMXPSFXuJInwkF{5iRsX*Ct~O|uW?S<1qqyPyBJ;T8Tna_0 za#OlO5X0J})$u2w*I?woD{BHE4Zg26HF=DOf7G2S5_U*Yx&9j79r*1;9R!@{o;KL< zQZ2%X5pgGO!nTn2T^ZL)SrIIp$TkEzfabVIHC7w!1{x8CaJqvra1S*Y+0nk>Yeg@u zhD%i&3n#VHCGw!Hfw>LZ@+Ky#JH8@e8vMs_8Dje2!E_v|?SoR! zxCNZQ)VHL#jk#v&U=990hKoy{WXja8iTnQ8nm@MYk6ZJdqUKe-(QCUHdYNAX)$;@b=^m@zfL0%(H*$7Z% z$#TS`s|6{=gtefE%!LW6Elv$Co;CQ&Y($g5am9~&62-KBGpbd_F6clCt#ZX`;sTUq zg0P_Q$=d1zn^jq&v~{K8X|(lBbC5z}Xn6VfTgQi8x+J)BDZZELNS=^oB*S9BjVS^@ zQFh|6nlLpY8)-q}F%@9qi>?y_e)zCiRLfDRyElA21>>GihQq`H{sx|dQ|_b;L%zT* zyLI@X=Ht5--5P+PsW4i;Ow?x1g4-%+y=(i}aKme3{&(YtH*%7>wMsndVhfb0yRYb&Mx zM8uPJOR4BqPSI?ETd8gn^~zWZhPVsrI|(Ct2^toEBC}rTG0t4*JcLrg)&A>&hAc_yLpgQFXCyzk9&xU9#@q6jmkgvZh))6Si7)nEj5|T? zddh|DKDNeVqZua6g*L0Z?5B{;Kflic-Uz z0;PtHW{kXjBJok|)(h9~EKInj*!qb2>vDMtfV=n>Ao2Bxy+bW$Tlpg150{AXsEj#Q zXvzEuS3YZGl3(G3lri;!R+Q&|DbXR~i7-H1(8zZ$j=eCFZWsTtd2glGXp@1C7#H$r zCq$wEoiQ7tiq;5VksT>f^s2l$XL%$c%I5KhB4+Ye(~DC}4MMX@u7aC&U!FagT7zQ0 zQPh(6IzUetp6eL?w7iL{dA=sRR^ISYcdM&WLU1sKQ=tT&xviV_`aNvxuMmCy#trZi zih|;LRUn8Xj&&r&g})DGFIgRsR>{+9IY}k;h{19?mDEg6nwEUTq7aZwV#_3WMRtOC zsJ9_+;Btf8`-Z`dYS;dos4cu?FrZOa{i1J1N^)ETJM_ z@U1;`H3wu%vw+2J0iqz=I!W8$#22s4Ody@pn(&JqqR)mXmHgb(2$roVtz>=ilcatl z_OCC_>troDG03a313?BktfKqeWVxL)P^kR&j`d%ERg&hs@Ai*N{#5x-9moIu_};jS zZihdwJGi*XfR@7eun>&B!k<-+BsR-ls=^;8b?2QYWV@n5q)uJL2a{W} zslyXV@ir~XK=R>dJoe0mS2XdMCc-P^H62txg(YXi9Tl%!t7lgfXRx<#j9V&E7^;n} zL~+=AMO9(>udx_~zm`$;3f=z*kwt~)HE@|Ncfu`ZfC~YVAp@nS$E_!sbf>EA5)1+a zZq~HJ!|Z82Q@puq8+GV&E_E0|@yMm58!pdppyAPVTp?i!2;oAhQ&!V42C(G}UPU$I zx`BtwuUm|gF#y}(CX zwDa6Fw9-5%@Hs?*Pq9avW3)TMgf(GrR4>9juwnhUYpdfI&_kwH@z`%>+^fX@rw~?w zP=l(r$Vq7}l%f@_VG?7?*_x)rT{N%VwTfj<{zqQ~oq!<~F8o=VOYC?AL70hos8>i- zxei^9ssany|1Pa|H?yYAndd9d5?LvSfhzC}q6R7KHY`y3tiS|^C?zqWaNA^!t7D3= z)D1!f1~qBfCuY5%J^7R3#H&@q%h%9CoGk{mlA~Pcu5i6 zY)q%2y%dPk7sExd5+9n=w;{$BIhs0*^UDcY70$C>7yhhEZjC#(L(iN@y5VZ_;{K*G{p8vH zo)^02u4zeLTm!fVOPj0*tzm+roU2m?0X@@SG@6M1>w~Vl$Q$P)wW2u&q=(Xb(PL;Y zsKfkwWB!$(1ai{*HpJTUEY%pX&0u|U(xvXuKkj{9R{wEJN6IjT;h#_(W4)6~8gPo> zDQaXFl+0MXVpK*;XVq$vz}?21z`3c~pPg=ETT%&_yI0kES>b7=U8Carb)KIG)c%yc z6Xh*@DzL$S&|;zLmD9(28?5w1wb)W;vV9k+jzZBzD*uvgP;D78BRjp@Z}?VfGJ*z) z_4!WnDVxSE6^2Rq#RcMe2G0rLL=)omzN8Q#oN*G3Q}arSoZZr*N4P76PV#m*ZvyA$ zZihhqEsg4Ujh4~!*s#B3ZGyd$QFmxe>k+-0jIWj$x?h!y*IRPyOcgb~b;{gX`Cdg2 zf`V}N+)CB3Y+1g_OD(79s;D}Nly{$&)t9OCmsQ7`z3ug6c3I?P2^4B+|*lw z!QAB2!_{^MZk7`|y~HEbUV>$;!kGP(U^$27PV=P%>%@rXW|dnl1f&~{vIe!{2mKIk#Y|5N%KuQ(D%9R?1rE=TC_bAcHlmE>{a) znM~B7-oi6ZU#@Ci2`kFcUMppbE}f-4iFzXf8kV$@M^jVXH_o^hN9vrMR2Wd3BXe)A z2_HNUMhvyYz|G3e6;CbI&P65(IIA<8UPR$j;U3~C^3JTrmRcy4sGeoZM`PbsYbhcs z)W)cO);&>8@b+>##T%!c4CUKjt>wFF%jhN>Iy&^mi&v{d+I{%8Q^P;I^C$yyq@p@`+87+eZOclfg%3)z|<&6-eRN}%YmO$kr zDBm+Re8va1tCBMFCRcBWE}1!fMEeHiqZWV8bBi*4VxVN}m#7;9SCO)g&AN7Kcbrc~D%fsxy_5vx~;$sebOWBTsMKnAOf1-AguQG>%X7H3>?6MEZE2!V0#L zR2*>;8lDIsYLtaZy?Eu<`$rgBjO zt3;nKvy85Uxh8=el61W^e2|+pap_@I0FN3=;9>96GzBRX-8JSxh2mJby%+=(0!(cf zOuf5bao)s3^%tu#-3gD0sv-#%^c~!qE?K^%*7I5bx;NWeroT$RkqG$nixH$@(Qr?L zy${_!4g*7&v|~NMh7HW4;3J1J(dos24gxMzA>7mIPCTV(Nbo~Ny^3!r)zd)K*s{3R z$T}H&vL=*<ZPR zI5nphJ`V(8z(+6~cIrZRgKk@f&$CNJqft11P66`lbUJd$VlI3l5g(!}#QW6o19h)1#(J5GJfti|hbxT2W$N=(=PXj=ic3O+ zrr$4LwJW7X;aJE;&rUN;B{+83l&aAOMBxC44uOx)x*@rCbF@S&#U0Um8~JLk30*Ii=9FWA4z;=lf< z#SNi|jB>9bbO59T7DPvn)q)<9o z-chaZnR{$&Uvt@tbH}HfYV=D`&MBA4{_D4_c{QxO?eKnC(~p*hfQlcs;YU?OWe^)~ z0`0p&I5fV)!(=`vzaS%&Dfk1{Eukq=u$efPz@ zTPR5Ymel#c!Ddc&TdGdV;2a2PxC~v9~w& zLm@e5Pxa(u@84^L_Om3*+9Vx}un{fLadg>%BZLtE!D17hNhAg;I|ED85wZhRZK-1L zCS3!+xLEDs2hgY8M(RASs^mgphd$_4a2oL*dJ&ggQ9Gy>;f5Dg$~S0IrfgR!trVVp#rgd$&FW4}WT8DemKqjO*8mN` z={*2JYEsA%7rs@0iqhibm}#T^#f4`O$?m>>Mqa(txfORd#|t8s2nRBmokcWP`Ed!u z(ph(1*)FceT6U~zy2-1n7I170$--l*AM@fIN$!^kr>y!QUEXL^PZ8&_H=5$sO>Y}a zXjNOUleVsQN_Frt+byvL_m-4KFBFlcRR+9jd692&Cw|vl)bAj|(Lf^(e!=7Fis>$w zVK%Z)4=-ZvO|!`HupxV>n8n#k#{5}*CHH)<^fM%zKgk9=!qU(eHEt<)iQqlssPGe! z&^^sEQP9o@>GF1FJ`RFvhGAFpEqB1oks5bkXi!T135Kpq^-LMMjfGykxKtoS_S!JX z(&}KV)e(p{e6Yxb^&erpOln88EH3=kS=+tLEZ=9n_t_&lHBQC4&6M+0S92JJtUMT0 zJeF|(bakqUAvcGO?6}xuN3JKZQ3Cv`4mV5&(~+}J!Cj!ax*>2Yh})rRZ|F}@OxdT0&~_)-L~L7SPR6`o}RFwljq0-in$wH-LH z%h@t-cEQ;`6?b9}egTeh;zT9i((IqS2y91eh*Dmd*k{|L%%sRuz#?q6lt|Q>Z#st7 zg=0BLZZLs*_(Pjxxq#p@OK(w3%8wc)c)CQemHi>ObDVG#Z~}=_O3mY`B!}VGwMG+eHwmkFEzI7*q?E3D zD&V>Qg!kE74%13T<-ZBVsbn$CxJlgWp_Au=Du3!=Xg+Vq1-APc&AhDdwh{g*A>nf> z+NA_+;YG_V{J^UMp=#1LY!!oG(vdnaq+{AYggKxy|hcreE3?_)-o6)l6b4y{Bec;ZLiRSw~XO|MFt&kCLu}X}#%(gQMzy1sks#U>zZ)rq;h!i=L-H zHy%47UO)!3W*L3KR~$J$*N&m&7)DYr*RSMBL*h0vf|J7zeB0JCsfz8V08B0Xv^pC3 zVKu&z?KuAm)mtV%n^ah<)EJLZkM_hijxllKQhN)a3CsbL%BL8tWkRh$*@K%QS-x+Q zSI*9B1?h7MnH(xneKW+qQ|y zaqf3t6smf&U%1S1)IByWyECN$v8_hdJaf;$-To;QW#uQ8NZX_!BJjKNUh^FWoc+8> zFr4r46$Wnc!)l&{N+;=H%JOJ1`}>Ge13KDmjWf6m7ChadO%t_=R<=jsRy$d5+_>lp zKn$L=_+bN=pZfXxbR1EW_E@%JSsVvK$vb&QWvHXOkUnhl{=8K~T|r&?bP>j)`T?GH zB#5-}`*ka^0wu+B2%~SfMXjM1zN*(Shp1ih*`o}y*ALg`$X%^YdaJJTqTq#XGm=Y% z)PG!<|2r=XeJQc(@YN6y;sLvUFFd*gM#!c`C~F^~-iG!J{K*K-^7UR}KMmR6t&1Z& zq4a@CJ0iX!l77WH{>^21;sG?spU=f@QJZ>V+7FaE#}{hEvwe&?K;r_9kVHk-BQ_`0 zMA74WDOspc1I_B0&?GqYGjYjn>s!^M3&zqJ3qM)v87mybgPZ!UTtFiHp>%s=X)%!v zjXTDbj^`h*V@s@w!Zyu+hSf# zh4)TKRUw7NkRZ&Mi6N)ms(Bx-Nt5kJP?%oAyt)If($i1RnrcLV&7PccNi#|$ec)KS zyFdXH?p~E_&V{k4r%RWGpSmQ5lUXz|xBr2mR}7t5g-ml!c<~$)uEs1^V_yBLrV8+k zQ_BaJ`Vm+wG=&B+b(HVW?#Yd|%lO1JOL#!b?POCZdRZWP+x|ltmuJ1d-G6rtOUa|~ z+iy!m;IOH~XFfr!swaMM2Y7Eff=Gkux;r(>YoUV+lRRgOd?KdNFm%lmAXQhtUwjO| zuSu;t7fNhqd;$};Dbtk@P?$Jq!rs^&J=dj?Hje#=JSRe%(lc|JIP$IN%N`K*bG}A{!u~W~<6n;gH(Adn{*6^aQ#Psq! zM!rH6{Tu6@QxHY^fldTp2@hrR{nYACs%X5F1_P_-ogHD03A0%`V&M#X{YsAS z1?Vb@!_BAwDG@>G4~_i1ZaFqXOV6(4-lo7_&>h$jSX3uJt&`HvRGl2jXzNo$Nn`}nC+TVTrycXb(jrrr2{GY!iRDNT*Rez&mGkD2Q zyCBjnISMZ9UQ8bMUPY;@j=T+lIW1*ONS41WDANrI9=MwN0cynt|2%SSzwNd7<0NV+ zX!Jf>v1X8O0qb+yX~$KCP!qMXs@{0ay zSlH1E=QT|*mthPfPcw0dqHMftPp(@Ick0}-M8){jV5>!3Md`eRiuFx-3!JHKZFw)X zq@6>#A(3jHtEHLWG&Mk}n$S4Zc4~97s0lk4y9<3eS$Sbq(9t}b>Z*dl)Q1l}T$KN0 z3LbH`dOnQpW{vssA@p-$WNB})OkZu;P#bSLakK*gCs_?Kw!u{W3Uz7&9TBh(+xN7N zz2YB7BB=#vKzMEfK2_Dh&HIHpTM|*e3_uwK-H1nvN^|WLtgFH^$X+T(h3*cbBgcXAEAnVlXZ|Hfe2d3=um5pL%vj9-n=T1mQWA84N@~Bs?-QC&uhOt*P?oyTRkLdfznQfziX2;Zb_4|l z6NfltGnD{FYbB?QT1eFl&`DNN%=5tWN+CL0_x#N=VC#jhH!G%7$!8L2A2v(a1$-p_ zgQ<02kKvkK1qzy3@aYw8ofXTeZgcj`VN&>`(z!;EqlL1#e2PPdo6=VOj8yk>3H-7o76%(#Id-egm#@Go&?{k~fP#_wL+z9MSbAGBxWF$r; zveK51CI?mlP>bUvSYxz9<$B5oD2JL$gubXRU!k3xe7z91KU=r z&AKn49JwH-x%HSlPov0QHvnFu6C89k8F{Kx$fPKIJ5Miag%HG^RzA#!;8JlXq_Luu z6egVJFapL+!Q8C*)h9Nq=yv_M$Fj8+m5Hm#vv2HzTfX z4{$E*$Dvha+xb_r!|*|DR_5%&UuTx;4H5TuAu^`iU$+$&N!J|}46eVlizKlBpkjV< z9p$-i9&ru1>SdGyBUNB=21vSrAPkhL2z%j{HNuNCOto0-;S!}Zj@lTJ+2DS{ehWVkvqBZWAA}iHy?A?JPUvO7u`rcHLkA@<`j1se2Zxt|8 z9T&w7sHtiodX>mJfea|%q5&pY7MC&HU_^|% zm^s27YJgRTgR5YM;R#gl^6DAsd477lW>M)^qyLTChoD-u*KUHVMI zCeubs@s!jVA?~i^sa8aW8uoSB7ksVf?fz;p86Ew)0>oH91eI~z>ffSx`pzDca|ntt z@%XsU)C|4r%x4=OA@t1XvfDp7aay1@Gg%_45f_0+*kN&y{5L&`xp;9Z*|zQXi$v+} zMjJDHEeHCoIe#LO>Z`XXUgKQfG#kQpDe5749+j_GduzRW%In1{L9A z>r?#54hP{<`EOv33f5qXM@&z4&5IbE7Gg14!@4mBNEyJTDHexK=C$e3*@4|?*3k+I z*Kj%XnWjUFLLSZffC!~JC{1NZno=FRl`Y^$nrue|Rgr8LA0ph-^ASu2)2a)Z(BN6) z&+kDfYVj;T0MKCOHM$3s_7O*2b2`y?HOHQU7jp0C9u#6DhxAq%5CjkbF^-2FagL5K)-GsKV#;HV@CeO*Y@{ATT?AE zUmb8gOq7E_GWbcI#{l7Hjak3PN+Vl}+@2fckY>B;Gh>kxWNR;-yDqU}MfRqZOcPE` zd3n^d0%vMtGOMr)`qiBATbVeOYEgr^Dk@0jtw59q6rzC}FkF@G;6p;l+zM0N3J>3~ z-*ys#wuOv4;PZzg)jp5x%JqnwZ}@d-!LCX~jgYK%4VT?t6zuy1?4_8J?|9y{{d-YH zxTN;T?y;S^i)zF&35mp=CnQo#!8QXz`0%SV=JLXG%6k>u_Xrq_f(Hzpel_FjQ0wU4 zWCJ+$>iSh=B)KGd!HCri>!k`duES)dI!)~){SnyqZP8~e^_0089lcLt?c`XF*G=zw zWR^E4OA-l$0WJRQHKuIdlw-8x3S06W&l|RXCxwINFL>NY0Gm5$%(lj&hm8V*D#wq_ zQ4GpFWEsn9)ok~#Po)ftgbJER`qiWHerHx%AfKebcN}@arlTgC`VP(6FzG78YPpcc z0cT?{j5Gx{et5qa-`3?Ad2puhlmhA&V7HdLMa`L8F_sfX$8K!~PIjf;43Hs~)FnF{2H>R`Rdx>VDzKpmb~Ap=R;?l{rbZ}+zS2i-gEBuT4R-V&+{ zLQReWAF1lV7D+>fEfPa*C_H-(DIQmLyaQZ*G+GtaFVE`AOWuP3{ zb8_9DsSi)LvE3Lod4-FNLr-p$L=}K~N0j>84ZFs!N$dk#yvVRY@Vx}pY5RQ7bo3FO z5;~qL{ZmrL`jc0KQC{Epg?ZWjvnl+>W)-#xHVi(4s0GhOl5;Ej3p#5!P7naL=no4#VWCRDVy8sK}2h*hd zH5>A}h!4qQnLMa^)p@Rx6>U<_m&fS`UU8+K`f$9xTKH%DU4An<$jV7fVe?)F&QI`PwNxhHX>Y)Nm{YpVvP(vc5HQ zP?pbH(hw_U2axH+Ylskn^aJu|!lrP4rRTxswY77klSCeL%9}m-3DN_M@k_q`MOfQV z0ZGfG)z*3QZ(4nkD|RGI;7$rL1PpHEGD-qkt>OBN`rYLS~uKM^T^K}`odWN75^N1BgD?S5-ieM05tR5sx1)a=M*TVG%0aH9d)8(E)6s%X+HkQm=V*zoH8ST z`4|kS3?&7QgMcR84w({&e`iC~yNo%wq{F8|0y7-0B(+|^9S2ll-Wgv9qUQ1iVaS6S z<5T|JH0tQAiZS27g{#nA@Y*(Vp=~u&y*Pdh=QW<g8Jp3*8+I`9;zx?w0Eco(+HM%;ON_bb3><0 zo{x2t%Tfak@Ii2#d!g(tv@xuH-pzLYj5@R!`j7vymToS*ytQiKvI`>r`xSeEJ=f zo27B1*cBGwu)~&ClaH+!CgM-{4#O@+o$BV{KnPP0D0vW4DkT029?*9g;WSafp14s( zTAW4uTr1_w4Hoj{=-{HoNCS5VD-kdf|Bo$Hn$n?Ows*OQwmwqm3%&d1t1`3y#9nW# zPCutvxv%YRnqJU`;nzy@5V=Y8xxt?E(wAW)60W5pfG6WbO^+a*3R1Y(3Qq}GRW$@x z4Dy&3b_!LRh4BR4s3@s=N268D1(0J`wF}r(RaihK-4;{QL%Y-t@EFX2(griDOl;vW zHT63)TOhRhE4tyIVk-VIm!zjzxf6ESl>Qbyq^7^=_oBZ?S#Y|zSn7#?g*Y8HAs|xq zMT&ngZKBa7_hx7Uhy%%hps@oI?C3EHrJ$4xkRN7@h?ob?pMB<=1eQ#!;Kl_SLWMFQj#t}n34Qn%^`DxR8)=8JH*4k`wKoL* zUKE~GvOrp>EVF^CZTVG{#d_j_R49h`m9!xTvI5wJoubok5kU|^S@Go(-$VJp>-4C) z6D0euGeC55)C0UV;jxA}q;MC0jG4B_z&_N@6^q6+`PPyF!I=9?S@#pKy9!`mskzDTr+@TqGJZkKpw#^nmonxjr8h}J5TP`~-)lF@ zD|b!$%}?8}`5*wNP#=;4#-f*hXL z_5OUF$<{c}AJ2N$bIpe; z2)^fZ)RAk0cnQXHzJAt6gCCGSe{bcStY0p;f1-E!Wpa;&(T*WEMh-VZVdb17LOBuazB-#NYnd$(eb3 zG7M3pM&KNcoU_qN*~T%FUuB0hhODQ%bGKFxFUtS@MX3=#pxI)u1!~9J{b^TSKo^d? z8S`M51K)iGYq^0#5okaE{%O8n%7V%c){fapb`ck6|V(j1I*m zT+4}_$8EWcpUJE~IO(%>t|=AAjrrPK;nm~ZA;)+7cdb{xC(|N7^cGTf;8Pk9X3&FD z0EX3}18W>VcX zhLuX%)41#w1qE#w#tD*w7w2iwb>p3og_8~x0OggV34YE;7py(2j&F@pldcdJ&41Vk zIWyF`14nMPoqLJ_LUTH>RgzFOal4Q!7mx{SvpxidAy+%^gW1Pz7X}$?{KMX5#;_|7PqpQO zWzXxeMF4UL!-p|JR&|_+RXFhNDwF-ocj9&0EO0;hbj~SFH6xNV-JSke0H#x#Es#h~ z8#OwY#%;>D+x!os2^2bVK|n!MhabE)sAoOX3A(pGM@e&?KxFO%@xd<7I@LqSf?=@` zqg>c!&l}K)`0thVdmn^is#G1SK#^#w93TapX~e@vjvTR}D-J-Z#4zYZFpQQC9S8I! zbT?F(Ib(;Y!V!Th${UxJYe*DPm{r-tt}qooiN398aE7=a1V&e;Qi!lg2sNLShfb)M$z@1lgc2Y(RbU(n zAZ9JAf+%Ede=22sb~oTc&%!hVhDQD_Cu37ryeCS#(PrgBj0il(UA;akovU z;Yr~(>mmUK5Xnv>a5MSKT-&&5IdOH_V@wcDoI{Hd6zT%v1PvSJ+)*XbBh@pun6zE2 zzsmv6myWdtreFh9TRnSb>IIjyFV1xp^)K?&@|7GhAw`sQq_SO>`yf9fank|T%~EM- zn{QANMleQ|9t0OAwn>xDPMQFm8Tr@`Mn<-UG#a*V7O$AhG_IUf*P_h2LMO6wD|OJ( z*&+q53w*)oQV3!_k-t9UO_He|X$ZtS@DGX)l)J^wuk<0PEVYdvKjQ`)NUVmJj9|$qDt{0pL(p_!b7LLYeoUw0NHk)Qn>|M0 z3dJq+AyQD8UQK&slf;j9MJGHF#S)<#>1w)`Bvh8h?fp*7D^?7RFhXM&_6Cb3VGWXp zxR}j-S>u$ky-@(vCNklJD3(Pt$is|V+|&`DxFZc-N)HOR|yMt?CCl03YFG_Yw3Y|bmL#Lu*g$f~Z$ zJa<|AD`6*+sF-g@_Qp=-kkVcCPF3D&n6kXhZJ4G|&v<3&fV+!eXw|cVFg)+rvEIrF zm|D~VoP)`H3L3ibjVBS<&v{Vvk{GvPg=i831~LEQC>S8X`JD|ZB2I>V5JAYxg1@7z zf(hAp)XBp}bW|YVMfNsfU`XxVfEl;MyyB;k4J6|uZB#N|V{oKkD%@o8X!x??yk+z~ za?8bQaKJ2MzB*v#vP_5WJl5s8Eg|CBrfqhwd43km-viD!fDh z#5E!(anY%+Y_a0SH=fdFge57DcY^_cLVLXIDLw7rko1pSD3$PNFm>kAiNm>Jg4xpJ zSIUZ3X|yZ;4W?XQH=Hq}V>G@TJk$!~Vu|qZsI)Mu&IRX<%GCsunWO_;WU*2{JR-s> z>>1IT@F(Y^9%CgL7+!ayvE~#ikXoUGaS4oy3VC&I=k|nH%n;I5k7U!-RnkL%f~)kJ zM%k7nobk`1?yRb%?LhY#4zk|}4J(p1nIEHFQC0XrQcR;L%6p;9YrvNmdt$;DJt`ft zq;sScLo!oBP0;iWO)7$?@4+LIqctbQms8w`eQU%3Mn+cD9C|tjCQh*jE0O`frnKpd zTb^-$QdE+TxzLyeOafOj*+9JuLT0T*_*$3l>m@>!{_sF>e{za-(VvBY^Kf2JnH%<>AXb+?j{R z?Elz1vwuRb{r74CQZ+sOu1aU*p+g|*iGU=8g%Tv(`wU;4I6m_d-dMbemnL8uF=OAM zRIAk^sek5Jw>_zC{?I2^0yQube{-TAYDaKa0g{zI$`(i}xBw#4?7T7)js^h*_>a3$qC|*1*tXu?$8uhz6|Y0365}-)S#}OS2hib_{+K z!OAt$a#pozEfF7~889xD2Tbu+QH9#mVXzfRGW-VO*ubJjJhoCNM1k%=A22OM<#C>0 zG#WNsMVtu2JcxKlbj1qLxj}3|Gs6hZna-@JK{f$qK$rjco!zCL;<$ zWrPAtPYII9J_4!%91Fc08<{uuL18^rxndK;0m36#ZL+%3V=;2*aJXp9aim;$8|p&& zKR78J2m_H>)duXw&Q+skP{Q|Gl7bxe{hKi~&`vorT)e*EF~JVhc?K|!2qg(W3_YOW zkr`1qO5tp{)DCz=u%!lgTvDm>ZD|Y);A`#Zn_y-BPY*TjpAQZrXhSQxXRj7TE}ny~Z)5wY+T zgajfj^=@n=;#&`3hpC4)cPGE_;232CRE23iPAc%qM4QM~RU<3F)X>}SDMSINL-x$L zby3wIefw(C1~KX-ngJrru7EX^uaU+B~9U zmd7$ve3WW!R1Nlkv=%FGtk%BQ{_;`-Hd9T-dRY#-DmF!6narQKTQTVKwoB4i>h~nO z2_i5^g$o&^#p_WC0BaoFXM3=8#LP3O(1>dS-rM_98mCX^K7=f*(mb3i9S~!9RBS;c zC>D;zfws|@yHZnd^ngS>XP%H%B|H6>a*c@f#+YNKQ34n?Wl5CeL&H$#e#*r@CW;dD zBpia0m_n$gnR5Ww<;+JTDb;;E9-L5uR-IlQVYc`N&v`DcyT`*-KV>SW|kb7@MG2`ihg1;Ij4BbVXxJbu-~# zMN(0uH=7y?UP?EBkJ8Ch#oYtSYOwR>LQLv&?srNLOJi216}PH_Or3X=x)ZIzn1Y4- z|0rfeRYy=9aB>VTxOI@RIASTKx9sn-`j0Nkz33UZ=KhRZ#KocMNxs{7S!q@4pqTx; z^gQIAiiIHw`WTMW*dDhTe^!O1in<=`VT~!uUik5FbC}7W$f@;!)-%a<5ofw0^0JH* zdQx)lm8q{p{MRU4zC5a0+ zhWz+@TO&XeXIq4+oRxJ&kMry#%VajqOK6(z0}f4HZ42lkyerozRE76=C$l;cBiQyN z{HSu9CjY2VQC3n3JAOs)2od#`?Dic8URg#7@Z!KC=w3)M|H>K43`={7rU&M%t$|58b;LG~(S5_^h?@%fg zHKCnTzcDy{yz_xMFKXhdDN$0%@@@o885{nh>4j1sr+T4~Xv|ej+V1f~8+D1lk?h+f zcjNA?)+nBN)-4730LVUHHB6lp9Bk|UsP~RN;@jlv5wjkICl@nEub8-Ht1F3L{`e{2 zE}GTkJ39bHO`ne%Z4f}`-6f<^8Pn0()O2Y|yYi1_ix6iJtm6iz+S1y1mv-P}s^>w; z9Rt|7URyi-xs~CSslJx&VHC+uoy@dVEqRfcEh`VsK?7+x8c2-jg|G$FgcZIYPLfmE z6LT=t&B=Oiwp=%Zwvg2`Y^f@soAo>Qkknm1_iN)@uGj8CPqBv&+oVew~bvr!8n#NT+- zKW{$6*?eVs_jq0;pl9-p4*t^z|22A)rr6T3M){AwJN0YH92v`7bGNQS)ADWh;+u?3 zP@}mV4%IW88>U?I?nV3dQqL%ncdDIeuVnQ^`%@LlG>5zTsi`gZlmtP%81HMYnDXEF z?tw5#Z1P&4h0D(PfAh=nF6we+NwnFTsDU%TdaR3S*s5j1_WnX2XuhtSXF4c=n?p!1jSL9bVAkdLrEr74vUmPcti!Q1)to&# z%od;(tmJM|Y2b0|8B-M*JL>#77L`<1Zd}m>RcxK~;!3p4@bkLpPW$es6)Uh&;99ng z`eaU%K2>{(duqz5i13*6tcLxjxx!{XrgR9K7CVK=p3Rb}fV+v3}|>~1Av)OVHdTg}tll28O9)9P8H znHWTq{_Z$Qk&3FuMA}3m-5o#~`kI~#hiP7Em*oHY)|n?ee}3c1Rl`)@#CsXLdWm_Z z_<7FSyv1@sqy(;hgbCtsU;4I-)N9NdnBFFpIoa!Td>4{@q^r<3WfxeD-rIMJU_d{_GYLmjG^_mBxzL0?@pNpf@2YvRMnGx}Olz6y%{V4% z6W}6=%iut%b`7L{satNaN>2IldyS>pCl)qI1y^BdGCCJWCqI!iZ2I!2QU0O$eyrz4 z{Kh3jh?5Dx&sHcD2ym_e+h3~84kM}Z;V1QLu z8T34RVyfz#a`tt{W@5Mo%zm+yGpsehs3LW$!au8&h>K+A;)qMYPi9b6AK8}E_bNZ) zCADAWS3Jx9wZ-IAV=&2j;Vz!R(y(1+R0;x3Ecl>id5Vv8GO|A4+^Ai|T&{i_jb+Cg z>)2xzpgaVY0%8|Zu}G>cCM*9VXO$FSJk`N|ECLI(_nCGGa9Z7Bn9Lv2DFba*AHz?b z)ye^{tCrY=TtasCHj5qPV$3;Uyj92Y&7Kc1{SRN{;f_2!TK|XK5h^7>8CL_a))cE2 zjJqqiC2(Bw$uKES%7EckH3I@CD-a)b1>2ja@Iv$fOq8fJd#i{>Ac6&jm8SNXmJ|90 z_DU_?c~J*H1E!s557;nuj9ygdFbQT#Ut26C6b3n|Q1f#)Vi-OSgCz{E&>ulI6<9Ih z<^ev#)Dy+kJP!0#>qb}sSOV|kOCRHlFfzh1FfoJ+V}L_+9ufiK`LLX-o#YoK{KgN8 z5Y*R!EhWlbks5L2nZgA3R3vO zyHF{skzbNEF%~$8!g{<`=elqIwZ{hXGX!c|oW+91Qp^u>SDf|vyygtlk6$$=jo2*fK$@LstB7V+c&@V+UqRyb$qucm~|RB2wq8~#hYWQy!+%lqOcP0vZ} zmG?!1c)d`5yo(e8L`AcXPeAMtD@fvlz0u067?)op^b)PFuv2K3*n-m~7rt|)Yf(SB zbcq~<4hd4M7q)zuVi7hpnc$ys7dxJ$;#2XA3G(>yRYD{uIc?!1^%E1YQ) zOK93D&65DA;E!f!t;+&XftDSe)1HO~j0Dh-3EdoAc)9p`&NrRJhAGG+gslo^NOUN=$%jC?> zWpA|dER5J!fNoJ+0d2GadCW| zvnM+%q%#MQcOZ1{up^w0V@Wj5N=z@ohsMv7;{Vr?nhQ{J^f1_DE(51f*8p^iHPF%; z$V6u$Yyw_%}*xRQuZqLeaDERO@uGo#kpxjxSK$HZ4m{RNL6L+WnzdlHm z^kn@&KkgE&s%(*Rc5C1>%pEmql@t+iq^w|vT6m&45Tar=Agb`>(iJ`B zo%<-y6^NbW!GD-70)*j0fl+;I>H{+%(h6!RR+VVq!F-7gYiy8^JaHj`P%MEaZzM{Q zh7vX8vmc#nzy8-2i6X1*{WKGmKnR*bWfUyEQ7w9c{g%iid-L5I6FKlO3XN8Y($;8< z3{&lgB|3mH;Jp=tWmGoIoAXWnvi7IP^$6m@cfDp2M?Gvwk55%!9(9%Q&?Z7;kFMFS z{@GdGgPbwRo~g%zsDkBG_Qvqg5-39|3x$`(o;Us_AU}!`c&3>~3v;!(5bDL@^r*g0 zw}OqP&~D8zUQ9YpoPRYA3Z#qY;CE&62q)xKjP#Nhdi~%0@*2<8a4y5%lOA{Kt++t8 zi_-=5rXW$t3x!*JzEmK8$MLiBmWD=fpYv_G;-G!_HIuyPAvuCvYq)VZ`E^Xd4y(LEMk`5*sFOD_z9!z{gA6AiBd?!Z$_XSYi5Og6kZfv8v)&Jl_%{Shy5Fe5u z9CH#n&;++?QL&%jt71zW<1^%{Nw* zDrUw_%X}JgO?<6MTj+Fh@pM0MpbLi?8I~Bi|LcQMol|_v+E2cUu4rCXTN6F> zPUgycbd2?lnWG5h@oHAjyfF&u%EbkU%i#xozWhz|Ep6O#QLe#T?k{}{z~bGgOZA}0 zit8PVYVKCV--zpRkK}yXd}CZ9j2@WlvZW^=%FOhd03eC?Szq({ZQ)CNm19SBwY!}* zT_f)It1k@EXn4nLFZXGN*WO`BR6c)bqrSZg*IEAu*-h;dS}P|2E{e^*6r9 z|F?f&EBMKwcaqC5*x%OBH>*!Z+h5c{?-AgC?;VGW&YT~5V4n4Q|0n)2)OuM>MM22? zI~O0T_5AosyPe@riJ%@GYLW$Ir!omnV$0i<)OJF~G4*b=rX358!9B)RKCWMV?sFm=5U$2En0&96F< zaw&+b8&b3wC8oNakx4FR)3sY)tu%+1={MX^WGpxG*9G(6+m|_tp@1ejqEvTvra7}V z`1P2&nhINIgoNsHiz2h$I6IOpzTs;Q`_bxhWr!fH_-oWb^-5K4ps^m}^8#wkAqf88 zv>^9#PBDFtl@Czsy=PBq_ChFdAs(st#8NKgvohRMMl%#5AOL_@nt1gBV;Yl2?TdmzIFuy@cM$ zK!IbG=rHitG^-<49<@-!vhb1@88^%?DUcQVN#m{wWn&C!fZTq`?Y0qn|BDobau$fn z81QX4gl$)}SQ4-SPeW(6+nLgO>OOs{os!M3%yC#f{ouLFkLGP|jW8_ZIOs(sUt2Yu zAfl+QYOGHB=E;~CH5mt>+YLU4c8x)&RETMYXc_ZFj#6S3uY}A~0;RFtl!r~{g?t2LXL2RNGs8Bh7fh4C=NXjX3 zWT0p*mZ7BlD{eO1&oE}>OMTo!PQH22d$m5*PMHr}@BY*5F}9Dt3aok)#7<9@xZ{&P z0ddRmi`4;jOHrrA9CT1|jejp=d4H&>8B%m;af=RrHQ=LYX528+qI+hJ}XFD)3?W7fAOAxcnX4y6H|YHi0}@a9U)0N#5j0c@rr5BQXyG*liKQ)Y+?e1TX)q{#wMy}ZMR>gAopcv9*wzkV?j5&Rrz@)oRVaf_@m6b9hH@K;U3|Gs0mtP zBBI~h_u2p|j1&uUWPs>)rs7l?i^cX6$*cgT7bZ-|YwS8npK4Dzkx*HZ?G`lS);q;{ zgpc(2YMh$?FeBwZt}L$RX{2qj$a9AOD&bL4QDJL=-u`ft^{{0kGBJ?J2_yKUl6jfK zm=6ymG0e*fEJN~PC?yT1CzR2lG!c**;dvw8T!~HW5I$osu^nlWOH264j08yA$b1cv z7gWj2m^p6s{ZdGO(Nv1=-+x`fItR0+sgcNWoTjHovvZJ~QL z+--+b4$Ysr>)kJ7y*xG@uPbZ8Ulr6P{rZST2}@o&nt1OTq6>scn3?ecV{B6c>e`!> zndqSv$_P2bWK-=%@b99%8DRvgZ(Ej_Q+S{B(_U zt@+!iQQY-BRovWeOYUAJ1L{X(F0X=HQ=K;eo5O-g4}>$WIey-~TRTnj^WVxJ?);*0 z8FK*3rZwMtfE;FOSzR^VS0xphSAK0ph3qTu-0XJGQy@xn#kJ()nLdmgE;*H{4CwV0 z!%TSy5>HJHSixRVC!mH?Sb@E-4WQ1->LoI=tCM+O*g})74F`k7<=d2G+Q+&9_%GTsSjnKTag$;iOU_{kw6$f8$PC$=2(W|GOe-!XHn6>+#6Y z7aLsu_K}JsHS6AwAN_oMgU^2V?VmkId;jfHP)_ZH>E!``h%HX|#i{q0)48xzpDa4o z>1tbN)$_m-iMuCp6pp~==U0JmlrfhNVHjR_HS(uuynMQ)b}5D3E*thWM=?{K##NmV z=)P60S?SIeAM87nF4>o#p1b#}9=6xIrL380khmjHqEa<3ilMxQ)|-@hy;xDlE@Y_iB|W2^uy_g`<1Cgay)vBzRqWh z9rBL9%1FiR^o?s9@;ttHFvE-q#201svq>=l3Q0JM?KsL|X4~8&#_QN?v*5ujE z^LaIKkhz_bpYZUVex$mz?$dqE%RTGftvazyF$t)web4AGCNdGouw!D!F)Qh))UuXF zkl{py#{Q|6W^F@Y*pF?gViC%b-e}1{Jt|4D5=MA$&!)V7Ee_KpA|L>7kuW=5cq=7v z_Hx_Q4Dvk^R{cYhse^_D|5yo4sKJwYY`aOoi2f9G|K{B4hOTPZr!;z0{-L{diy6FI ze$;cvs3k7JvyWTDFz)!}^^0k4f2YE}V%n=t5vT$&t2!aZ?(4j8Nn2|gL;P6#(1kiX1~3dk827WlCJwFZl|)TJ zkh6^JlBBmZ=r=R~cS{<rNlOx@@=t+_M{D}x$SvQ=8Ke$8}lwFrx&SP%^vBY5fGH64&-7nvr4B5 z@2TofA{|ovBix#O9;%AmP@gfsCx+sdg!+WPH{qwZi?5I6?5Yyt2ty6Ir2dwQGn*28 z*n8r8l!-~cl~}RlLTqBy_cQNwxbHr#O}RM7ZX^1};gbcOf&MTg6$}*l;GG`By&Ivc zjTSE^Vw!YN9ErhY&&=b|%+QY?(4sEbw6-DEPKa8ANPLIVz=aNS1eflBgho&jo@7cB zIdiI%WF_)2fc(F;KEz9GVBUrw+wMI4NZ$GNFayd7FOx28I49QVo3klLmaIu{OChc_ zbs=g_9Jt%5{NsWf?(vS@oUY_p~oIlN=v(A5VJl38&chzUo;wWjr zpj5n%EE;`;qf89|D`8wf$_s8ARRXb<|GB&O)TQ-P_IpChsZYdQ$g>3Hh{!_D#lE-9 z!8N=$HKGfJ#BcZhJi1~?N&n1?a~ioIuD`|_fWAI#?V{X`D-VteOS5HGtIm3Sg zceZRf^oA7ebf`_5pO^CIdQ&*Q6(5T$)?qT!6E*)bY~tLP%uBjL?o%6o{AkkgX>*Hzr0U%7tQeCq98$^7c)Vu~)8y0|9We!iHn-b0Mk(Rb zxaDzoo`mgZ1MT2)IknNt+K00ULj_hn>ggvVthc>?X;M-GYpM^@iPclhP|HmDH zVI|IIB?2jzU}|8{E50`&hzBqkuH%>h$`2&&&RU}~*_%714q{W=Z|Yy*>rAVJ&YRg{ z5Jp65`jpjWYbNbWS0l6V2Knr(T*i~Da)b3@VIv|s8cj44%1&KAWa^|dg@wjPcCK9r zvG`!)hB%rv?vc=(NkjFtaub~0A}CFO{FKE~F9#MIQm_L@%RGScfOsGc8|4tT)LQod zE^yHWg(zU(i=8w2E{4M}=0IDFH^r2n-H~0H(`V8|dDMycXGru2uIJTb)3)owb_=&u z;iU<~y&CV#>a=s~wOhB|!Bo|>s2f^yecpq857`)e4?|HY7^r0Vv~thb)()ho1D`C1 zq5?5V{o1E!=>e22MLal)%}(UQcI)Gm(O2y_2nXn;N< z&;ah6Li`vF@*e0nv90%*j+9%NA#R!yvFaUUk_4ahm3jcxX>$uSb=1hcpWQLq3%lUf zj7LL-as*$jA|)tO!@wA4sx8JA0EE&RthsaaskW?%0e_*qA5z4oJvi@mKol<5PvyMEZOkz2lVQ(MHOGn?!dx~5rqc7R%nU{fS|%lDmZ z^UB$HCLd_5%n@sRiRtQKpn=?u9@u167~tfDK?t^RSvWf8Bp4WRhaXMneQ3F!Kk~fT zKnZr?F3hM`12HfKXP5n4e6JQD)TcM)Y3z%%&dtqbCU}B|$TYons|vElI+M=klWnin zo0>EZ=^XH2Y25QQ#bZ-F>(<;4yB7t0{l(Q|_b@k9cR+0`PCp@irRH{X{bV3v4lC*P60@2B|X&XFl8$<$}3n444j# zt&~5r{LmTDhj2RXM)4KHTY`tQz?U7*s<|rXRW7%9#;qu^PV@Q&+!aY~0YA z%G~?oB)6svH-GhBkVKZE=GJO~?t$W?(`3CPj^sZVWQ31oq3sC;EJjNU)unOr4Zkr! zshM;PiHT_BRU$ZIJ$Ulny-jEZz^kzIJtIM5*=8uL5WI!Fv$?MdtN_^NIvZ*FxtivS z3cH9C4k}BePjg!RHb)87rVisS-R?(M3)^*;3MMBfMPnjLz3!l_k5>@fwo!OZIJgcF3_1dzRC(kHp>9>XjJY=X?T zy%ZQT(!c~t{El*-RJpC--`JRu7I2fR`6O3!^Bl2=INN6wGu5aYMSS?o0?n=U$4r^3 zTcV2{gnkP(iDEx47N0bi@nM`uX8m%FnCvBt0>7nC`>uN+ceP5P-<{gW^4HhK)6R3I z+hIo+_i4>dxE>Sc*f3|`)@$P|&`Mgjs<5vDr8hP0QkFIB>as222Xo|61$yimSe$Sd z3yDj?-W<(Y{2LpiLDc3MXiUdytK$tk!La)m0Oi-M9zL$d-H#Z8gH=`3h5PN7`jPrm zH8YswCl8737^#Ut4d+J2j$oCI=@o$-HqpD!BOD*@+<;HBDm*vT{_=D_oj#rJi$OLQ#lX8SR{ zrnV7m-i&RLOn5k_wyIM^CQSAb35^vq?%9~7*u)-gfns@57gxtCRKHuW^S)3$zkfl@ zyOlSOcK(OWN1M5*S;>=`nwiJJe`UPEm2JAE^i!&>T#stzw}J3}&f@ui4w?G${oFJ| zy(k1RCEtx$WWwWV45>0F8k-+!jaxXiee@{^3$dk4^J3j!{@I#8WXc^04BsWziK_*m z<0Lai89(8I8XVVb=<~Y3egz?3%@{t1OYh=oXrtPS`^{nb_x%e3=U3kxbKI%p8%G7A!lmq{kRi!PwUFA)I~8VHsnz^n64%rTX$2oLfl{vROKPVvX?Gv55>( zlx+c%s;L|gE$4CVhX{~6)%8o6u*lVXWxv6eoOW`E3KIc4s8s*h7RI;KvZ5MI$$@MM zGr@qf3Y%|z<{grJ{B~?A)7CTVhTLyBV#~hoZ|fXa{lPukLAHn-^!%HTQwPC4bDLtJ z8kbf!PPyz22T-`4OQ_l9spa{l@LmtE*njdB`&HyCE!~?A_CS8BUk{PWr7o-9X5w8rfE_bz0-#cZ z_VAN16+#(yBEn=8ra|kT}JI?I+jti!-)z8bjh8(e63Zf zgm;7>VJUm5^Hrndm?EnISa2G0l*(p{M+;6jgM#~IQeOSw$O_$7te(Y!olWSu5C)lZ z+sZjl{oCR15!^k4v&E)$=i>a>Ry3b5pv6KO_y{OL4P6uE;EkBiV~yWMw5A8|mdw2e zUy8G8{i!m`k zALBmQcxM)o33>IHh}`56C}WTvSpfb8S17b=t)!m_X$io zrZVYF_Xhk2flOYrmBGqqj%!x%57o<}-ul}E2?%73@vRo!HIpK)x!<+JI(mKW-5PKQ z;AvpRp%pAmSpq>Jd|n5RkDYF$>}xzb!V3yJK@csS#PncI;Kf!aLnF1>y%uMn5Eq+$LJ*?NLbkB0+ebGtbE(nO zVk5-whrh>)i`kCN<-h>2Nm$&Rk7p9(1Nl&{Rs|Jcz)+-SVCj~Wiz3r_SDMBKWzr?! zRo1KI;)2{RzXo%khqm`vY{xsOe&b$dD-6!~KGj!gqJof~kGPH@FyeByB%&kWD=^@h zGX}>c0B7eF4fcaYNiuNP&Qd2UhKQ{49@Zt$lHc}AO1i@Y&2)9b8qqXNoPvGd7B*_Ppbofn3n!+213FRN% zWe*DKxB-faxxn;Fv(X|#6})Ea;*9s(`91cEOIV6EIkV3-CALy!71n5V!W3nkTHbla z$|L6-emk_iUsUDWDpQ*omwmstOho4E?9W;E!dR3eW(_CCr)17cTK+vYf{hD8(oY9b z2&1de59FjHFVspvpxdocY&@FE>KLCW^QyoNFK^8;!bsHOEbO46;|h z$!Rdt4&M-)&Rm#iQZ4D%6G8*#Q%_-n7QqN3L*2mRy;+D5Gxy0Q5ZpJ}V}nX4&lcRD zH_E?ssaw(9{oC!q9x5x`Xh<&lHGa^{Hm}sX9WepD=Ysq+5Qe2|$z5jQL9DE#{D-De z^Abu0GMMu!|EyyM^s+_)n$-v&S69>59#v@r4HhHIpx?yaxWHk!orOAv=d9{Psmg+=pIfZkE<;9= z!kVn7)nI4i>fSS`%lIBmlZ{rDt9}!@M?v5B&k-PCbuvc5b&mK;<`6UVx75?$@*p9`F7BoHopo+|H3DoNY6tofN&&(k6^l+Lu^_oOvfA zhp8)`_D)FsrL1lva|$Tc|INKR@?=0@Qc}`sJ6Xwq6=RmwFLGtr3aoNTFF#n`pBDjE@KZuvWxsTAHpxe^21k|< zj~d2O*mNj5ei*m{ahR>}WE64645z*^2`6h(t0{!eSnZ!>R#kIqSTSc{k+GGhD4IOt z1AcEDiR>W;N+6nT#c^mxIIxKx)}~k#0!d{U(W6ugX~e*wqPGqnUgG6h*O?jM6dM1= zX$8dGe6*;zcj;x|;o>gfPQ>Z}()X%$n;Lr<14->D#2rn}Nfm7QDWpeQYW$CsneiXm4~LB$|q5mU9= zMWckJkOmc^0-V_+sdEs_qfi&%>N^y&E(zNKm{$RoU@NhrILl=fYCKU8yUKNH_*h%N z17x@{rp&*w>M4X-7*>uQ2D?$s(9gCz?(5H<-9#^hCQEr%FStL!kx_kXw3=(~Sw>hS zC@&JgIlW9Ar(5Nm+%Ts`I>IE+pu`P3JmGY*EQP86xT{&Ei)0^LsscEY1{3as!ccdL zBY5|8$UcSKDm=nu1}Wh!B&mR#cNB&?a#v#(6-m);lK!;!+R6Meq-dESjo=tJc?~of zSDpp~x&zwh{OB3zyGE7swKX%rUq?lNFf52waoe8EVwzbokzQ##lMC?^c0J5Au8XqoTI-4(;`g3RJEjemJ()6qr zae?>eIQnK*w<^1M=&^eiA9{>n>*6DA%8nTtL{8az%Oi6#=`rJAiWOI+?ca88s4SqW zsxZTn)xl_0oSGWEgUfXHkg8(H4N?Y#wb}{}4(aA&tPQzh`n){uHf77o z-%Kn%n?Qn70Te=2x2>KAd&vkA7=}6v<_S@QP|1SE*+blq7 z{H>IBL@}@(cR<(=gQ9JtXHTL}`1pD}kCGA)_?lwJP(uC= zKq3_e0s#qSI`ChJdDkIT856CNj8aa|=WTX=;` zzTWksjmj?FBz4Mj7NTW?qz(gtBy>W)0AyiW7&GZ&Ky%;}PcFU%f0iemk0eHvs<7*n zrL&@@v7Q=86V0_FW?1n5#m-R9y6on_xP0zM2Ly^iOz(TpPtC~3LCPdhuC3D&1J z_PO#k9R;7omy8Y!8*OXk_gypBZEFgKPFF6a0fe7M>vF2XC679uYgwhTrD?no(Xy-h zsw7^+3IFMFb_3gaT!}PG<$=()kNEjjLwx}q_}6P-hq|8xE4sVv6P%G zU3v9RF;SMXo@mq8cg$Xv6VfN8K<6u(MBQ6enq($Xo#78JFk z6OYKVW{8Q66{$kkXV$J7V*0=e?KPji-CosOMmdDj63MWVm}SzWvN>5`pJ*8CP{gR)9%#*giT3&F2VMdH%A`} zrYs4_T^*DX-QX^(*p)6Wz-VnJmFlWgzqYJ%en8!^PDVs$w!Qc1N9HJDN4|G({%^bEpL2Lyp+aXp>G zGn<-qbLy+HbCQ2zPc=dm8=%$(9FwZbF$S32Oe31h%)ZpR>c`4YW$qYd8u;vG#Wc>w zEw3!zy#Dm2-C3i$)+^)pNY=$AmP}5_xagHg!U4r|y0M^P8+9wOJ4(&>g6RNAz@}CUA?2>Js zt*hV*hLP|1{LKYAd)NND$l=4)_e6=bLaj5#np%&NZGVSKDpt7KjD$kO0lX)*wu|amEscrW<7b!;bc*A8-!j#88 z_nh~bNAq^m+>0;V@}@0dzG?_GX+m=0Wd9~DUM9BJ4U?%S;7(HB6vL2N)Ae*6&`F7& zadwM5oL@NjS>(_Wfj<@}e3vT-q~18rxViZ!HAe$Sw3SP&L4NvBjwNCl=PgeP7K3lf ztXcjWlXCa^pqv(-)nD69N?lD05=F;<#s{%FXBTl^sD;RG_n{Q(FD>zEz^2iEjN1-B zl<(&}X`(V1(`{ahD+^VK#NkJHnhQWGIsys$0=7dLOzeQEyQ4Le;Tl{cj+FaON~z@R z!v0>r_3ymba+*^;RZ^BaE-k3YUy!6z z8%F;8p~MF;3l7aF2~;$kFmwI=+r5>jt2NU z%5WIScba5^oWrX0d(|wUsU2Tts|KqLvvDTDi>VQQxA;E?Oui8m;WJ|>UKuJQc4OZ%Q#cps!3t{dD>0VR0@M@IpiOxxSb$l%RJQ%bAU@op zhgDp>_T;dmqr5mbJ6w6=Xgk)0DOcItM*(asVVkuC}2Ar=)LO z(EWi)|Ctv8Q93wk!9YT$pVfO|nlt&!H-jlUyR>9!*`uVPe0?G(@(3C))3Y9*NCH?D zo2qQj`SZ)Rlc%RR0*v|R2uz_#WK6qcARX3h+csOT7SzoWjlA`Ic_{e{r|Z`dx-1D% zrN=-_mn7E!%a9*Lmw?Z&p`(~x<_R~+N;2CPP8Wc$hD6G*P0FU9)*ofK%<6^rTks2C zMZPPUGmGTF{^FFPG|m_bY=x?OnC#Te>G+eRGl=)Xc?3uz%}4I1P)^-Eyu&(|(F240 zLsnA~P20F3X;|-d44Sgq`@jyoK*1;6t;^3GBvzc_0?JxTgTl_px6%%UTC6CkN z+7F8LDIHXEml)%EDrr8?O3E)#r=i-T*hJIa%8r|=CYlawZ#{WG;}%s`)T{_C5qmJQ z?0MK|HnmzAWvRDQ(CN0qHXy)RZK137s6R<_uDbGx_(W5=PTx+}GsW?t%O??6X@(KW zFso@G_PLi$U|~8&_THiZ9I6I#di`_x{ephF-U%H{51nW=xN?w%{RucI^dv>oRP0(K zE&`RB$qZs1p*i%b;UPKcXk}_hS4EAWORp+B;5V-6;idXdxl|c7wXBU9?jMB=*kG>Tj! z%N8u0qzFFjtn=8b?OY|N0&+sje_!)gx`n`TxNv#7e^g0i^?{sxg7Zm_wd82&4>#7& zSeHnpwlRSKqcqVUFj5_8TC1wa99KYX>wSDv!Th_ut@3fhH&Lrbh+4i3`xl!?L{2lK z>iD7AtRX&jO7avY4(oSPP*4@eMCp?jht@zqmG~YSe(c&`HRMLqwZ4{-#$borHkT)~ zYX~6bdIRewE#C5h3)F_q%5f7ahpdK^gcTKoEVP<;xQ~M@`(W4&iY>{IGvgd>l<{Ic zj3)P#7nEcw&75|>HJ_PT)3)qVBAwrcW}@2++Y~jzRF`0^Sv*atsGZs|2>V`*vS>JkSS1r6##Fn70Q*QssxD`myKrQ&B#XmlmlNXiMRI)Ho{<}zYAIkq0 zRjXk+#wK9x(za*+Ov8yr@SRSU*luX>YFP-V1@}#WdeI-ufuxnA$U z-}!@Qqpbu5+~=2C@U5_VMWI7NOlA$E_oH%*VXxCM&}Ay$09#v;dh#^DTO6qJR*YAp zcYkyPSwA(zLcwfhu4Pw@9eI@_PvZY57o1?yN_R_tLn@t}nR%r8&ZR`V9XB}`j-07K zqsxlLv-m`b2>+97R6RTC&GvBa1TLkNE|M-x^9<_hGbzmW;+i3v47W_P(f+6fKSlUc zwkyd+DK&*u;Yu#LJqv-;Qg8*o8|^p!+U)%pX|?ap4V9Ge+SA?TnoQF&wXw=pC}M&i z`{{9xA{`&kq{h`fRbEqZ%~E2!rv>rzo`PUg)X(6j50*SXWC(dyjvTJ7LhKKVXVHk( z@FhFN$W@mrIh{1SYXnKzW@f>!!r?{w=h~D@Q*&lI ze$&=ogFlv7bq6B8=E5;58w1kYMT~?bMymz5UDH@jW#(1hrxCuADCy>-97ScG@-vZZ zsg3@(u9h4aoZ(qX0T@*&bRI+uk4BC7YqG8C z&g6kTs~_1}_{b|KB^a-b?VhV!+CIQVVXM0o$PFbn|xO z2zC}O;3zhW95y1b=;Y3$TV~pJPxWrV9{oXCZo8VS;#!jk=*JPu7RASS*ph+d_toQ} zc$Od9D0n`px0dtJZUi)umjTv2Of*U|8R>$AF85e zgi*$W|9#CEsNowH?E(-=_10WHDm4dxIcj=pmzk-Ja?VBF9#GzF z=gEYn&*O{2rckwP*9pd2aF2`-q-c4tCUYF2Cz5GOqiYex<74o zAhN7s+4b{-Zd;d!_?^U3xP&y4O47&eL%fTXOYrU4Ho@aOvl zXjOloo7zY?kbY6Jb@}g0Ttge9VPPfFd;+%I?J~^Bnu)ocHfIE#Ty3-P+St{3&eb!Q^81S=s^@l?`MMMy*d>vbNLFSB45F+Tn3;*zG|sH#*q!b! zH8V+zQdw6RKu>f{>2FM1(f3c~pmuG~ZP2>RYotp-gNEU^SRNJ);j}}YohNp3o^k+W zK>D+uo{`_`ieuyQt7KhKhbl|ptH_cDp6rd?9%fBakhmdvmr|FBolb9Z%YVPWUxTSL zhD5KcS~OvL7AMj+ZubuExg~aO5|^q}p&nIvlC#aquWn1-8p2_upUq?pq#{c8pqfD@ znRTDs9~YS#6v}z+aS>h2+tguKu4LK(mf3v#`nVNKf4~GqQqM2FtQ{Lr3eremqYq>X zu}Vo^Ie8_%6hXhGEh_SBLY}MGi$xwBHI0T9MHW(Y{hg&aqp znAfe8AqR($s7erSlJcs~EEUp-vW-(dA zi(R(NU`=1?+k&orf=##l|6#~E098`vM7ifCVZa%(wYiyV_`-srx?2vZuWaGYLH42k z6O#DR<4&Utk!dn?3h|7tgzf;)%C2XuTopH!&7Hss4Vzm8!h|`H?5&MHm3Lhba zquqE>`zkht@|&$#!@`6$`yVGiO!{$GP)gd3iN80oeMHYT+&lHZ!%6;i_BWSPc~Q$8 z2-+GA19J}$mQVqN)DFRTYSG(RTRAr0v-X?&9gryzx_R>hy93fhN@xxf&|`&Fxq*Ml z^*qW3IHV3ffVm=Qn4n%8O*BuY*oQM>Qh{adS|=aAqBy+jsON9u*6hBShX;Fj*nu|py3 zesJbSjW21Gzs1k5f#?Lx|MpSYkAAp`WwV0kB+iCB5v+v)zKJ&LY2c@gi{;gz1e`_8*crg^y4$U)c39zK0NZLd$j<_ zc`Z{by`|R$cs@SOdu68&OBU@r?_7TCZPcqykzQ^V%LYNZNH}kHR?hiHTQmGQV&0F= zdE@W&UWp(rRSK(?EeoWnQTh7Yf7%;3+fPx1sW%bFWuzwzD1&4XF@YY7EBCch%!&rV zcCHJ&KQ!7#MXM1kvx8I*EEOqd0*MAB-y`O!tGDW!E4j$z<{xgWYoR0rE2x`~W7U)$ zfU2n`Xk_XTxZp`H{ShQSGo=F_jB+BadZYaGpp$t+XzHTqrKXhgrdc@@^7|g~8|bY= zz(OW2EI5cWFUCV2Mde+PHUHsjtI^r#p@6dtbJ@imuIaLst*H`k^E!XxGV_dI2 zyF&TfsU|FMKsbK2xRa}<`_$B0p}kyd&n>|{JEpjCu(*{+56%qAuAlNI0JzNp;z*fA zSql>f;0&Odj&hM1=y#r&i7asw3c`PLo&I}U#zD(d7Vl*n1`nlgTFJ2KCo&r~BHUrj7dSsVS2 zgC!ek;(B@0Remn3n&-)mB9RqxanG0F;C)~$aknrH*1@A8PpT~(di65qGez2$`86#UWc1dC62MlyoUxNL6=vKbXLwfUFNM5=yDp7`R)v!bTTBXe6 zI>Ih2U!Lf5DdD?N)N`2*?ZFw28Z~4+L!ARMQ4W!nRQF*wL|PFd3)zrG-5wtBS>h6J@1wbsrCjD*ehO>-pGP3j66vSDj#pG?w{W&HiZa)-Yvp{L;FLN8K8l zr53D)Lq^F$$b-0n$>rs^B4)!+a%piKnz`^z2H&r(qL7pS#%yWZj;{if#$qcyV$moo zF|wV0&70-DXuH7i08;jQ&SU6uSEtPj5YKH`Qb~nBwed1-8l3mu=lo+JHng9n7b{xB-8VOWW3i5)faJNVeG zcOpFypke4Gtxb|sVB&2*?`}a+P_6{7d8h9*ayxEY*N{DMOufE`_Y-#)Y<>mAu znQ#a(E^Y3lZP%XFA|1b1keug#$to&~Hd7F?SNFa4*XS$DT^g3G4&c~j%%8Ga?Y_o< zN5vlb!Y$~dbI#qIynheX`}%5hswlPDNqeayzTw(me@J*~R$$oU)L;cobjjUL(KZ(D z$P%F3_5Q}|3|Vh;OLAe3YzsWN`WVze_=8LM@Ek{$J7hy!^}uC)J$UrN__qmPeJAnX zkj3egdQ=WjY9(gH7$mC7Ikmt^P6QaZK^3VIF`K^Jsz@!3)kKEA0U2M8D*dE?mSZY4 zH>=*Ms#=8v1@v=_`dZ4{>^vDiVfLW}oAbsuQm=eC>7*hBLYXe~SRBaMF>17{(~$*R zdI!>VAAV%;(Sx0msc)owxVzt93cy&D$HoMod@Jjh6;Xp_;^;V0isZ$kyBu6X%yseq zBa7F*e%UjPTh}Avdx@_61^m@p8#=XM<0Y2E_L+I$YT&op+-S%2tX)uL7w!1x71Fqj zjF`%Uoes>q;25%DekF&5wCED%Q7(g~N^l9H>gH?7fJN0vGo* zWBz%A%;=6=g^x%7av?G#VL?@)`meVIAi%>JFc5EYfXnnTw(k9NmNagCY*CcQnJJS* zf*hx@ltsGQD|%PI5Z?~AAL~&AaC&^soM*0$)3iJnT=~MgRxxhw(^HpE^DgE5ut;hz zQTygLhZ)_f`?~Yk{DLQXz=hF znz!weMGr5~f64`#cVMzh?v)S!>40b~LU2Yhv=j|0-$hn(>GRTw3 z#M>nd<3HUhwtTBJ>OgQ(?$h^fP<1;zs>zS4*%Tlo4Ct~vzPKYc>jWVVr`j|FLmLMp z#-@v+wi2%Q;Pu#36~xb#9guvDir6vxL2r6L9sE<-ipuc)fx*K~8n@p@=&p6d{Dc6d z7_pEIFYgWR+3v% zgzNg+!)l2MO`Bzbg#%a1mqmK4zvo!Xr1WGGzUmnwm_WoX_K5Hnme^0B;nDi0gP8=( zQrKMMKWT}?XqCl4R-@DhR{-vmvGS#sD zsGbjfe))odh0C_Q?)`H8SHiyDg>{CA%zD0i`>BWH8z&AbIoqE-&jRGO$J2`Y*KF>U z0#}-WtD-1mT>ok8PF-zN7UOYdQ^oP8LYn3?8G>G<{cH}0XBt@+d|cJC(BENr9mj3i z%5hG7jQtWTyjf?O9g>jeaoclUiz<~-U_=Y9P`#kKG=@cMvMFSsL+kbPa%P@=w6#=f z+%-|K7ZD|=ayM9J;oI%&M;vSO%69-CWNSiI12y{v{A)k?i%#?M_snF^mlK8nCBYTC z#He~~&n}MFI8b!V)X|x;JIiVqsMva{S5alm4m(`Pm3*4XU~_nmL)XCql(#V;l53+` zn7DAY<0zHMnKr0^mRw<(vZbVbXq}a7@LZwA{&qCJo%10l+lAAwws-A)B$FMNDIKKAaOV1l0?B2#jGl$WEEL}x zg@z`pXn;k~oEibsB_h4#*{CpL_9}sLWEVQm|68~FHpbU0CjP>pnF%gF^l%u30ZsLV zFJV50rgjco?~>fm`*`aAXhJUi^;ql!pArF}tumSInVprt>xP4&yfx&#DZw7}IFDzp zkV>~{%;HiGCay7R3mTkf>@rXk@&fcK1#)Rliq1T;gr{71tdH6nq>E)5LZoADSFenb zw#2mj7M?d8?z=HPx_b<@kSOgM$b9O4DJxl$_cW(OWGY>8j3oKHWI;3QU=4Zt+^0Fx zut?zjkTCx{jbvTRHRhwC!Hs%6W7|%`F97i>l~FxF!y-(y<_h|g7v#x8_;{oc5PIP~ z()h2klRV|nDYWc{KLu;V^-`G#SZE#A4^BW`k2KmaSgp~0rfg-a&Ks>ePdA2y5m<6% zBAMsGp%694x&6?!=P%L&rBTdZVqI5SV{2emrgJ^qddI}e?J3Y~U!U#v2jXi)FpoB! zlXPTBRkgCH7mP>*X(7#`B?AOX(@{qxM(=^=v1F0~^BZ>{?~af>vAB5RRmxo-H^vb| z;i5*tLoH(Bf9+!?do4};A1lhC@j`8G=1Awo6JVKBe8m-m0(}|*HWAS zv@7FpZEM?l(DWq3I(sC+rxnGS;GiC!NV**axcq0?KV7sFgUn6l%qj@9l;4|(Ump(> zAU%u}ANBH|&4Ok%tRr)@rOqL*1+H*Z&(y`oJcj<-`S(peJjxz>(qkcs6mz6QhcfPm zLm@63OMf|`$pKf>Pia^?2FH2w`EQ1LF}# zwv;W9wuOdv8WXDsK+XXhASEZsQgE6Rc*<8&uk8p=4G)2+GYuP>%uT9GaKbb%!W!ME z-&yiC{|IYV`~K9;9stmGQG*FImh?E!h_}~MP4#x2+SIQDDe01IVI|FCF6qdLijr{q z$efd|Df2vrH{$h_>&1vJm_&YGlb;?1NQhOV?USWWQ z(B^cWMt_YWglF624bA%OXnzioIyATp`?qJS7g4DQ&Y?I?UI}TmQTGL$_ZeG&m_+a- zTY^aiUAk~X+vO#+tKVi6tb5fh_zfzE>+8DXP-Qdlotpg3JcvlbYYq$nljsb-b#~UdqjYKT8;w%EkL-HirTC^iss{6f zlb*TuEK^FjemY-#zvLB=8!Y=brV4#Msop8l#voDTf&mT5>KBsr{E@!`>M~+*xg0l5 zkfI1d^TC1-zcC~v>dLVAw@GEGxsOjcT{s(_keeB}A?j%Ll{f$#U$3_&JEqB}RDlRF zv%Y0wnxrP^QZXw_SOK&3ic9a;y`y6ANF?u+dndh>1HsESYjC$RIIK667ZN(Pd(;?LuWsIXlzv#wX0(`x@&9 zKMP~wQdb#3z`)mw&7he|Q)j7_kXoQfhW0FqL|AiT)f6PY>y4n`!)x-Nb4^G^Dg=Rw0eM7?!pL#zr3;WI3~2PP6R-dknon+2 zD#zHuy=@S6t+-8POYu`(HlVp86KYvv4GTEq9J1CWv-%)WjW|`~_Ea4wH6^Hhyv>(X z%BZHwIeEnGMvttz+L|LPjuUJzomhfNE*vV%%O^#RGBe;fTPJ^0}Iv@nj z4@Wl-5Q9ZEa~|3Qg>ghigQPxYhLoc8QS;)@WW^I`=@G$=5;`Yd6GXC*mZ-rs&0JI3 zm1XxON;1r&_ZS$}Azn<9AXRin5kgFt$PioQv@}b}^GW5yw ztf6SSF-$bM^&q?Ta6=w$$io}*?`{a%k7NL!08bIxiiM!CGzwh;uA3wD0@zCtwh&*~ zggY(!_9av&!N)9zebBtr0nL|&fv~@LB7gyL)E`~CP02a64HKjRj?n1t)Joa|N%RZC zq-NBB`tdNEGYHrU&f8si-06*8`_Cy>5jeBTgr$tF(ojHSBL=DV~u`SVVGft6!Lz*J$U3X z+qMAE7yVe0KABDL0uXLDR0~ixU+Jz!si6n+or4HQE|_rK_4t$Cr^nCTTOi}fJUUY0 z;Z%rHBBhF5%w|gl27miNv{~p9o{K^gs;3eFml?r8+KkK|g=c=LN}sgE zcy9?p%Ypa++|_XEy1uqA)2_|VBsvz7Ap&4Vy=n?oNNrry{OS)9TQXMVWP7XB^TOyJ zHyx~e9ugAj0}yd0xeF@k3PDm~fur-2@6`e%8D0%8j~Y-RF>QJF!NqSyenYy1&hS-6 zWd>A;o0Kq$Q2|sT1q9B~W$k)|WXvb_{lD0I_pq$%bnScPe!oLY&2p)!gO-Bm2yV-L zgrw-$NSOkPqh=|j=9Vgb$;PN7 zOMznMkKt)W5`>@yv65PwQE@dLRceX#NU9mIZ=onC4++7>PUbEesHkWCICh(X(&PC64$5lxaY-t| z?XReg&m1xp3@Bs@HN$$fXIa2WQb5XBb>*0V^=SWgAQ#!&zg_q^ZV$ZZmUg%ZpN@8s ziU^N)?(C%XHRmrVAAfKg84Vxj^jo0a=cgG60<97)ynel&!#T#ZVZC6fL7k zuS%%90cKc1c`U8H=;;a#l1v;XsfoH-g*MDX=rV3(%rA`3JnVolVTaNg4=P(+G{w}_K4f!g9YF&M7HzFtE!rtw!qt3p%rkM~D zLpVdv@nA;qK@MgWlm||}#O5r`1orhO939uiRq7KeT`cTg68DxfNpya-o47I14mOT^ zd+uM-FIH3sRq!_BY*f|Q#C4GzWML*m)Nr&pjj+Jr=zDTE1Fqcn){Eo0M)M$)`ojXa z{Aun8FhFK1AaFKaDm$@q5ce+2{}z{JBV(pLqbm!JWyQP1H5MjBZG_TtKOziO--^GV z!<9MJk-CW83bf00(wY-w^hsYbw~vJw+-qY|2@Da4(HztiZO@idr#^l2*W#InF94?U zJKiJ0{&LdqLs!4YTz~9o{tL@#Mkc3z4*~TW0S3$vG=St#R}WKHOSPL+Vl6J50WU$^ zQ?6fgR6^<(uS@;Q|5|c2W%3biBBpL6N$o3B0`^(wfdcfQoWCu zh%(0jF(G){c%7uV{gxOx;gd%$WWlk>4{Z|1D=72P^j1Hhxx%rEaEy@1uyOKGG+;> zLG*vY=65hN+dpKrQ)EC%N{oF$UJwEifLEd`Ew_cYP)W@$`htX9mKb|xxMGc|8@x_^ zGyzJB3TQF|h6Wi>-Hrl@$|k($w{P`bv@qUF`8~u>miKDJs?OV=uG`_XsK7zu9LTX@ z73fh@@Qa4baK1VB0Q9^BCTm%0&1*3m>``nTs-8`)IE0nZAff!OCcXOi*sM2lm_c=Z zD-ARBilz$H-DD4|P%?Dtq-fgUFrkV(sUTEGjKo{#C~N85Rn?2+t9l5iG)AJPdy49V zTBp$B9@*KOu3o>A$FOnx9k^c;)sw&k{`1(Lnyh&wfA{1n}TVx3w=zkP-wo2bQlGV z^h=&w@Os5N^8&K)yxcb;iHPHk9tfDy)%Dpf){_`}gBrP>ljP8c;>T;|p3VI+Ww*Ey zr6gb2I^GP?-w5DN{)xY8MMy#!n?bte7o&XJnwWi{y;lm=fcyPDKOZ@5a9L5q%ehe$ zPmqsY-Jf=FR--vx9>jbI+51k+oOtY^q+P5g&z9&3Wu)q`|NCW=It=07pqDe8A(^dS8w> zzvjb%HjRGHoK|>t%`GNyqqc*bvhS9gswZyneMFV*?N49`KUH=V?^C%)PkGL)o6z)J zV)B;qlA6VZsjOM@W0%uggWH=>uVy=F>_)4PRVET_N1xMnmWhGnKb{zi+nt+I?9+0pWX{`ZCH(^3MFrKXRHf?(fBh-F)b4D~DOYprXc2 z3MMm#ee>SUhms3XgK7ZhbswcM;!R3CsGhbOS*o()@Kz8x$1nkBQQ6vOotdC^-U15~ z;y#NGs`FAd`jdBFyM1ds)*MOqyw$A9N~|2zZjD{+RIR~T;h2csY0Og=M|s??R@d`A zL+F(2tES*tiPQ7eXih1%#FJyg61=nG?9ErF+@kd6x>(TPrKx)S%MVDm!|OhTzIo{? z=277=5aN@{dLw?Y?>XOXSNQL5=Rm$*PWUKQL9&C*{SiLpq%5SmxS>M>4F}^?rk0<0 z%$e(@yA!zMR*?U913aU9PEDeTgwENnn5gM*kVmm55*nr@QX1OLsw}?>%>=Wq{x&`* zfgc zCgnOB!yU&hr36 zPa}#rJ7D>gmORkxYUdP=D^KKZzBss9!yjXO<+3$)KfLPZF|f> zY3g!^cBMwtb><6cU$QQx#5MVpS-LBOJ73-$MZc`3b>(bXbVt+0q({!xmIiqA!8WC0 z90(z1I9N+g(v`LC4)kdsV6#qhY3NIy-s&rcFq3LfeVwHx80DSwu-CPIlvs+syHhhu z!&@!2PtdS8+L?-H?{b4O^RtXP9+K3o_?z)6FXf*))Q-OzCB*u+21<|osS zT*wXy`klT^>c$$gW4DoJam_Ur#guDl6Vpl7&aW}a5hZ*|NH{REd)VrWoA15Jy*;^i zx(L|tbN`p$lZ%^`+ldTuM54z`8IT20jAX7&@Sov5)JK#6RFFNneLvD^z@1$z)Sh;eIb860C zDfP4fDjrKnm?c^tN*3}Db?z&=qp_}Z;|Qp0qXsASJxxE^hBn8qHx8sbpt6QKU<+)P~F0!`l$?C;?o5jWE4Gh5ZVOQ z5CSuE1q>1#lKj~6=ei`0o`Yv8{`Gj{oL^s^GKzmY_S07{ZCLW{9Jao>6rfDnR)ClN zJ+!JaEd&#{?`K6cjRQxijuw(qJUfHFa{VT1Y_e25Njr*H!0><38C~^Xqy3VOwo`PaNKoBN>+G%y0$Ev`No2(lquQ;xR@OlZSNJF;|D z2xSBImcIIaGfHvRg=rz?fuZ%QF3-@-_L2sVr)$<~l9lL5(vD`#gRneq!b0_AlA2}i*Log#I$WAoliN6&~die#>0~JK_uj$Rz7*?$1^pT``63&1-={7QkFxq|Q zuuQHyg2fg`3J|BHC1nX>QVk2@fwO?rqF?o#)7lG(;pcUTH9Uc67#X-9)(E7=kfZ8S zefLj|iH84@p*al*V2Xo+wo)NU9B(q#_mTFpNe7I5%>!z1+Q57fMrI2u`$pQS3p+n^w zz~g5W7xd+}NUWPpgV4BKB^BGhTX>K`wsYmUZ2E`^*exDt^C+832&rktzM-Uzhr3C) z8*LW#obUh#sHO8JtazL+X0>z`z-baT5r5Z6Ae;Qy+tHkEfVn9o5EPEkud0SG0Ri2k zq+1#pkXhYE_Aq$CGBivB18kF0WGrF*X_3rN4S1X>98&! z==^N28OKF^C0D*O;>{&vZ@oU{+0lOt*OUxISp>{6#7TuR-h>rzvK-!VuKE--n^2S6 z!HAeh1ynVI(>>RLB^Zh{&t~2l;6PRvB)KEzG`PcB==vmC1`n?xVJnT<;2h}7ZIMcy zaVi?itnAwraqB(?Cg%$_;`S;lmh@f&^zLH0UucH~d0lzfo1FNhgz!;g5wnR00DAa6 zZ^DWP{fK7(Bcjn~%eVLAvIhv976KQ1zfN~$-n&5;un{b4%VA}yGuvjg(OrHH0%^>2EnY9(9>T{xcQKC4Ijm-k@qT0izrw#b5OEw z<<2nywQGBPTAhTQ`^$iw7~&U;AciMyKfeargDAb2Gm6}TuF=}p)O7?Fg@GF{xI34L zBPJ!8i<28nY1!a?HIneH*W0<d1r-1pH-CFqhLA+(QQ1 zN(hct#<4L&Ia^m4<56RGp<^T`X|$#0XlWak$F|#PHZwWYf0*mD$JA2oMK}VAg>tTl z9a9+Svz&^%xN}UOAg9;pFOIi{u48vJA&~vh1T;+DCNgATs{WC~vSthuEQ$?W-P-Ma zNpGz$JU}do8Aiw4O#I*D$2{@aL7#M$FlZVvFm9JCxw~uOP_DjFXd(Z(^&F z(}h9pY&L4Z;iJ=q{}O0259ddMWreF0P^S1^xT1)qkp=|`BUqs^14Jwe{3xUg;?)gh zH5IQ?CMj7zaKkbV8f&(2=)yb4-C3~y<&X1)H>qml7?BGAaxK<~xE%VsO|=T}M%II( z6+J_&{N;JaK1eupwKv9?H64rn;l_LO+xZ!7@?M{6C~%UiNs|sQ)+MZCp*6N``8KW? zY`+a7h97AIIYnqQcZx7{C~H6fn?u;k7>Z4txJsL;c5M!M5MrjFIb3}#1SnbFPRIHBfzhCfz6JXf<&RgT8kVS@!`O>Tfx zz_Ws%rXGu1<2CbHA;XA&J|-)v5t||&Pi@%%E`&mBtQ`O0%NlSRm>jOmLR8O6FNU>H zLIec9s9+6O%{nu3N4DL&_VRw*4_?tIrIE*dw|pzS_79!6FI^gwBOD$h%^aztZ3Gip%S6_UigOys;_9X8CuyL>>sar3^xfRX^rz33B92l7|TTWsdIQW&E>h z5}X9h)Nyrt&fEJXw$hs;%yE<pxe-C?zdqG|LP$5+!#zt4?4j`c*BeEHd@j^gMX zMiU;U(Fjt)g>YmcrtL}W@&f;?*;W`5g0#HXJ&!c=D1vJSRGZ5-sO62d>CR`v^nrHM zoxL_!P~4Z*$42W|NeGt%See+#R)8ml4w8>!yB3wMytg&?w&vcgA?E-8a}7AU^wm0}`3Qrx)IDj*h-od0L`h^Y%L+C-r+9UKXnWJqt)Cf<^~Omn z!lvNjP;{49&O&dR{Zi^ocU6{Kx#Vx;3UhLS)yy#4L+}p*7;b1$c9yKV3+c-wybKXDAZc6h)oewMV_~YBXXove))9h8$TwGK*~2 zsK!d9$wxmN$n?F0BXLVE=TQ}EP;!UbAGXs3v*970U*pU}ry1{BPq(hWT(nVo_>f#V zK*b4nAt^F!V!k7Cq2QbJgA5mOs3w=+dCBY9RjzoyZDQ$40F&MDjTq*MdsT`%`gc47 zA83j(PxlY0(-;Mh5%x&7zS0vU;4%Sr<5Mb^w)*DwLm#|MQElOKBZ+eeXKgC4fH1KT zTUE|P4;qnLmgX)V%Z-9ujUR)@zylXMv<>x7;b~7Yh#e=V43H$30|%9-sDAj##&7v7 zXOf1KNZRc)WTiKB97cr6oud{0*!Y&SxXsJ%NL2 zXbiDj+hA0kovSIL`d%R4&|Gn9U;<~!dE$PG>jrj&s^V#qw-4;=PGHgVf~Nnf_%>M+ZjxDKH$$50vgfYFSbM|8p z^!YW^hz+gL6aen({S9if2v%CpU6khz1<#GzNf>RfCX(f`h?Upx{}r#dK5upd~0`0oZt z*n3EK&L|I_nzgua_1MV6FMCGv*W#k%o}3i~Vk1@HvS{<~5+E0*3Y9kcff#X8eB-zu zV-O#(o^VT$ExNFBBD2MB~&|H;xHRogR}Fyw`^;8nb%k*-=VIykXO zhiInWjZSiqAwPk_-LX@)DU6ec>%d22OAe%e;1{wl&JVPUwtxZGV77Dks7Vy8C%0B# zJI{8PEb>;al)a62AW@rqSo~&&)ous^uA~lK>^hv}>2I{Gkak59EfCSETL^D>Geb zbg%VGpM1~s-5k)=frdyp&1&hmPJe@DbF^3sy{84bx=iA)Ee#O;h=T}pKevE1T7B(2 z+g*~!vYv$Y{KZV%9?A7yYA9Hp<^=Q__Er zZegbyCo5RV7y7KS5!E69x(~Viz%XkCRDo`dlV?3&GoXXm1504n)9I{|u?W z@$T{ehx=^0527Y>)t7DX=utyW^6`n{!ZX~tcricfsP?Mq0~c1Tn&J}@u>pA*)^*Nk zT`54eK)Q|U6XaDMku;Y=HD;FwBcm9NRjl|R3 zCEDA$-cD1{qP0OkHcM-MXJTf|?dzgi>QQhVU&i>y(DA|fdxo!8DHUpMApcfuU9Ope zvV!aNpl1>%_TUu|7(DQ9!JuNki>>^yAwTk~r1Y2033#XWTUmo3H>*!xJ^h?eso`+( zk)wez?;v~Aq?nRKffu(^-)EgihYa3xBB~b)7f406dEzZ0k<#0vvrLYXAAq9)Dcl-6H z;l-Rl@IYg1E7cf1&9yh)E2IJMx&MU+FXvT-=A8C;N-9w-3+jEQeYNb|^0dy-Xr$;@Y81;k&+GvEoWqeiZ1mjNC4NC@cK%h3<4xa7QHvpzxA7L3LHmOo`f@opC`9 zPg{sd@R5xCrL`k@Psv}jZp~%l1eXbkMAh*SLVV>ndp@tCampM=O7c#pgrl8fy?^~8 z-lI5AJsWzIg(XWi&`gbrH%9tHJ8?1*G_XjI#`=Z`a@uF)weaHUBsAj3g|tJdTg7F1 zt~Ec3RG$@o=vJZY_Z}T~#6VwGD+Y9YB``8ac*o|6r=I2(m^V1>u(QSKXZ%~2jsY&- z^9!V5ZvQIUxc7)copfm?%&vW3@u#WR>p*t9loILaLOtWz{`rvDLr)jPC${cS0|5IR zpZIA{dfQlod4oA3I@)qPJtVT1^Wbm1_peus&8y9v@t^^Sx4a;AA@!I5siFsShL&l& z+!wj{44yr9V~zKe7wmnlMVlY2SMgwS?uD0LV1eqHJ9BI@==FK1YTUP?( z$NPlP`3`d}j9aN-fp@$VRlDnf$!SIJ;A6#Eb z6PTwob?nTm(+wOInVze3#Z{gQHu#&=P|i#D-`it{y~}GzbChfNi8&$+l{9Fu-RqwQ*l~Ljh4C5?#Ypf#z1A%I8D(ih0f5LJF4frx<-h659ns99V z)vq|IoFH|v(Xx?(Fqf0^C!sa!h5SbQ>^&bL5u_N7^)5z1c65x?$|}!AD=! zGQR6%dd*6s!E>K(##x||SCaJnPePVkg`X?(pI2BpYZRLFJ0 z>$RDX_G#(_H7nAdjDe8F=I)Ng80pIh5Q<_5WOLAO93AT#+o{8y>*HBNv%_)KJ&p*( zzdnIEN?J_mph_MNEa={w+}o3Tr|X}7PyXYT!v}xk0{m;-et>YoPr2<%#fIc9xL?6B zY9v!vp|WQ|Y#1ztx9pt|KF-V3Y2wMFRL$$5J~^^tO0g>zBDF;X9rmC;-$Uvn!Xl1Z$wRIl7>qPr<(K838R2;k*#DzFI(YC1?6= zl5+;Wu{VArlYeP~Dtmf=w`pTx>Q-U|ot#Pu4eRbVs1o^f6bURUQDJ~nKtvjGC}ZCQ z9C|H2oO!20`kg-bFJs7UOBtJ?otn6Me%Z>tnaN?{0j`52$V4lQaMQX<=8crrpYYKe zM5bB0f}-`_-z0>DpFlFC{nHtbOgQ{E-dn#-X0B<8XuibkT)Xa%&uC1G+gKRcg6oUz zOCGUAwI1J>0IKV6+c*#jQ`GKO?tg?mY>0T%_bZ)Y^z zL_0PAyDKkUo%!KIK4FKeb+BxMqpo#T1)Y3W9WkaW+T45dso1?;>idjJ`(#VpCU0gXtFa%HgAT217Wvk*xMndN~b^viN?bO-4=cB?_M_zNa@aW*>g!y}HU8Pk= zGHYGdQcyhhddY(Dj9F7$>$`35G5QM+pV}_3Uc9${n;W9Ri0Am|srl^*!B$88>@HiR zJ_LRN*PWgqCP=a8%sWfrxse~CX)ucBHz2~Bnz)(CNuYx`^yqib9@?g?Y=Mcww3Nbd zQ!WIWT%J`eWljnEDZg`gpzvm3!wnul2@(UrWTzUx$gg0u}W|H2Wc zk(xu&dqi$_wjPycp`>ro#Tj`5I-kC#8%QpgWHmX z<#>nwlr+IIfPR*~td#n59WV&`m*X^|g;3c0)-TO`6o!UjPSN|8AMfUm#O{2G~Ctq-+f_ku}r-ke3o%sunK`viK3R>nq3U<7Q8s`xRd|#So zIZ6&GIi5hO1p_H6C0kUfZj7K=bMISYygW$)aS0Mk(xL;9n+m`QwSAE>yXKdySnt8| zds8VZ;Pm-_+K_W-eG!h`5>CylOwMTi=?cG%nrOs0s|XWrl2ix>3a~=Qi^lSo2hcpO z^M!K9e{(}T@e9qW?9X;)Z+)s$ZlfN6Geomtiz=0;X}FJd&F{>>he*;`7GFu2hJujH zKy#iX$r7@&jFbh(G8C^j$IL~7_a)@i#7t|lhXI88k;CaLSpDp=_&})xGaqR$z@*fa z$RK(EcA52kTfid;fy@NM^X~3IEb%}Ov~rje*#gBGjZecys;hx5a#XNyPM&!U-vF}{y=B}>bm4qvHWJ6cfY!(+oL9eO4H>C8JuWQl%5od zFf`2RkKcX!JAPBzR#1LgUM}*mcRs0!J2dgji!zz*8FkwhZghrMr22bauM<{pQMZeO z%F5xpA-Rl|iayBjJ5{R5)-g$%g`f~ZR8?|CN3osGdW`1UBp$B{c6#v*H1(& z`l%LN1c54%UB`PzviyKSL-@@`BQD)^1{|rc+n!0HpQ8VkOW3*bz_t#BZh` zg{r&`M9L`S6r%<8jt~GxLL3A-z$u-D>NI(ANb7uQ(<~!V;Ds;mI#1n{smFO`p z%B3Z7>=CePrbto8N`N1mV)#(7_vWG{8p2^AgGu9ph?$um{Mam{G0I9v+%vA-c!0Q1 zkSN;vO-+H8$#h&T{7@#us*n-Tu@Juv!hWJNP%RopL zIW$Yoj^QZK^5W@Yj~Y{C499XcAw}Vatn8YS)X<6=ZA)d=3ua^nQy0Hm(&r14TZGQ?Cfxp?jcNb|WS_t~8u&u6omB-A>%&W#aodW z_hdM+i~pg3D%_P;A|Avk>mu7{_R=6?9)%%%cF>47iBQNK$#(Ul1Smp)yIdTK6K*pM z#(9$u6|a&e9Dx{thvI%5x2CrUh=FG!W3|4_diM> zGb>1iy=w913L+Frz$}3|?qrq#MI(xQEWtE0RT>Qe zMO-#Rz<4!EwXViKWSahD0hlvpsA-&nu75H+x%Lb-SFfl(feZjiaO9^cgbO|)IZobM zaI||jM$#lD&PcV4xFD)E97D=Z>cSNQVy23`{{w|kR85<+s*0*?aD_@UZtq84&CC5O z#Am;$?Pl1>UU3zq{-qj5JeNefL!ZV&&Hy{bw`_CQ@m}r1iIU=!odpx~a6JpTl&bO4 z#PreblY2tSUy;NX{CoWi;xOgy0D99f3{xheo*z7tNoPXwGAjDDMPTf==|L=^?at)U zf85MMVH_B|M|7sxlUPS(Ot%45dc*9fa=H{W7HAx7IzkfR7T|$;89x?9){8~!jnObO zVyWTy0~3a10LyqPYfj#RuAl~w$Rd2re8WG4PF^?t$eruHA?nY-$(XdHutt!iZ#cMF z$C+oCyybA2sav`#77jwP>hF_-POGe|i0v=^R5#JIBFVZ!d(?;*4JdSbWuQ7B8-;~q zB+x#MrxLSS*Th~Xl10%>9jYmFvcC~K%JRWBRMQW|y1bJlEw_lEk|0RaVB|4(smtJVo(Z{f)?Ij zn9#TH5Li+HPaqhSk>IZ7=j^r4# zy=rKBnJ)>(&EY3;lx#ky;U`?RU#k0r0-g~ImuNcCD_Cv~?IAlK!zpRf8y-Qy>h#)g zsRh4`e?jbQi16g>y|c69T|*dh$SL?q_i)moJB95+L1G_f_jO5s(1K=k2{-R zLQJ$l-sUuG0W$A-HD~}lf+KfQOIwUmJC6W0VK)-mw0)Crm1S=>IsN4(_ zE#fY&VG-c4ErP}aX{kp4`U;dNGmn0AV+4WY%lZ|$%cd$y`{P$(PNbAa-TdK^p{p*8 z_~dWNX*tIOMt)WP%!ulLIuSGbk*0@kz0soai{EeVu(*f| zlB?j0+0^?(1{OvncTOanwl8GWYoiFFobm$sS}WdpxW|rw_agi~U8z4k$cSXgC<)6| z)XB?AtWy8O5bz)W#ArSuo6~Zf+7OAs(lepiL(R%_E8bhB0gpJ2pPS&Fhw%M52FibO z`m^V{tUHXamxdJR9%Gb<)>w5hB<+8wrm|vd zNK3JHf~6U(;ji!%)fc8wR-=FA6!mHV^%m=T=&4vB3BG^2!x0S5i`c7|D1;-(2&t|N zGi6p6_m~~q&yEunh?Bwz^PK4~LU+QutDJe^S{+&u~DwQR~Guw1yR3vjC(H7O;})pBnb zaUKamwx6a*Hm$2So5=3$S!oH&w3R3G^%AKlhSy1%bAb7!C$&bICC@F0n|`Fv z>Q^~QEp%Yz!NnXF?TOcTk2yN7&8w@A5`GK}sb&G7J|$k4CQ zE>7~ci5UE+If3NA+HgtjRPO1XgsntG=~}TQc0)?Yyk~m|(=YbCw-w5Y|Bu>=kW;5u z{M*0%`@cOo=n0P7y&LlX*9|Fxdq?>*u!JE$DFP@IOGMLrVWE5;bE#FR#B*8SMe7PN ziY>_dv}Zv3utjAiR&8cT9~wO%9>uw;OwD<~6>}=|ob&W~}d|O@Og> zci)~np~ihanzxSgRR=jvUi8xx5!f;lfBl?dkLU^{C=$NMA`ZtQf9Ydb{OOivd!Jjh z4s!Xt!Uar_2RA1sOs~1)PLV2TR+`eBQ(<0YTFz(SUKo+S0;PNd5{5{sf$3M z^C@NXH6k%UBBs19DZ~}0AYpq)mDOS}m~l2MC!SEZc{IM;DF7LKEvB-M*K4n~5PuV6 zCBX$Nfh&b0o}D~;!8y#DI^^(`uMiP}8g;3ndNI=bi(IS}iE0otP{*edKdGcaFyU!$L^}dDmp+S&^enhi7ge(v>+k`s zeXlz4L-759>nGy|S?g1YlOA?GaWHl<=nC1jhIp&oZy}^jLg-g$UPcibA-+1G7$CP)~Hi`5V$sI@mGa2LiS79okWZ?OG!R@4JkJa6g;p5Pc zCuetSnDOrujPgtgR88{ovI0N|IB=(%=a}DA`a&KfYBD3O(C#m=#Ooh~TvRAH^S}NO z$80Ii7iE1lQ7{D^7tXPFK0|{=L1?B?s{eJRZ2=orxrz59w73_WaYDX2)&V#0UJ`lO+MYM=$qAC4j!&V zv?7&sxFshKdorP;9~lGjQbtuD-7x6PMIIbKr8b(lLvd|C(H@5upD)^v%??(7)D)e# zO$J;1qo|%Ln2<)|FgvdrBClYU&nS%lWdT4bsjl!M6LypFi=ZzR%Y+7XVmX@owbx*qg+ssm zXbTxhjmzJ34P4(L@X6Z5WTr{%q>=w9mNfG$MuoGV(gjmKLCLuH4yJQE-6w=i64|3y zQUg7*tS8MidAKW&o(?iF)>gf0QuHa_18LUPvO~uBC~^wrC2M`S9lUVfAy4op@Yx!C zQ}jVkEE9V@3rHAj;O7{WsW&LFH`@XTby#)j_XJv0$u6fiDu*#Qh|Q0(k`BrCehI5tDF=kj_1AsbPKw zrw9Dvl^G{!g;+Gy#_+V6;jh*O%%d#-iZ@4zF~E1U>P#xaRFkqF&U!e&Lu& z7LH%h$u4M-_2W|J!a8Gz)QnF8t-%E83>MBlB{*7UjMJcki;7t|&mzvB6~Ig)hKg1; zP2B=pmFHVdIO&O`Q3JWQ^^6z54`rAnGcWR!+o0-tO>V8+E+D9sa=5pE3hD+UD$tl( zA`sPLOn#f*TB=w4imLfv`fNW|+oy)vY^BtdBQm^Y2Tvt#HjaWrFI6E@nK_r%01{aQ z$ItVQsTG=@qZS+STIAS+cPz8JpvFLcsF_=m{#WYjHXF=qh;dOgMB=GgK_Cc0KgOxY zA2*a@Qik}@1TGa2owO8yK}1j0?*_c#L1f`fPMS)GBv^o^yRwvM4nzs3cleB|zo?#t zOVk)ER#yT^7Jd=^RIC)ZYCKix@3Y&1^>G5;xDWFby5|Bo`5&=XSqPqHKvhJ^IGQV% z>wK(v)nX*ZrT{%rTDS?9OZ(Y^<3gd2&Vw7$F-)|i&S^Iu z3wS86J9U%yV}V2o&hV$5lKFMU;INbolfEOkSyGpYQO-Kz&SmGICpyz&|_O{cS7-d3FcM-dZ>e3j#@sjf+Q2{vDo9ct4imcH!h ze41*7?wM(gfPZPw3BMZ_98d73e85mYjf_%s!cwBiQ6k=m!!G5OHF-yIJ?o?l4b)97 z`KtFa=Ys_j;woOGk$q-OH9yTVC{v&-+R^w2QMuqYkS%Al1&3Z&l%?|KF5}B|sns$J zNp0~chU_q&DlcyC5Pnk$6seP{76NIqc0~J7-cf6uN|z$FaagFbh%0gIxTj)A{z9L` ziV9VOPvC1dg?1&4y;i+d!HZpg_2TW53dMIQ>-f_OqX6>o;@*JyuUsfR3{P zjqW0MzrY2G|4PX(W=o%>)&jDCeHdYR08W-}wRj*FzEFJMdhSK#ury{L z^|VU8`rMN`Z(qJN=FN4^eDnVb-TtaaHb$+)Nzyn^tcMKIT0m_Me6>JE?=eDeCHV`# z>CFmKg%`H3pgv&p{h5iP1gM`v9b0z5=}Q*HqglHhHTnF(dc2Cm}mMM>q9-^S7?I^3BAzXney~s;By2I?_Gg^>J>N@1wWcAZw^wj zDOfv-U3vIuix>or%LQPUCOuJhMPL9ef!9|0(7({gcWd#hm!yU;IeX@e6S9qRLD?1M z%jfm;o1|_O>lhTcPPL3Qol9f^L$$z?;<_ZIz8Dbm9F_wP;a|gA8l!Z4gT*q%W)WS1 zM~@zVN^SK20N$XceEHhSQ4%z;t3=ADxNBNG5-LM*E!z=ku*k?t1Mn3U!)1JyGIL0C zGTX__UsU5dwfqJoh!`$KX18-IForv>inOaDixp=jpkyhHhJ%u>0bI=+YUvoZQYpB+ zx_QnsDnzSxVNOETr>vN}m{BMgDJi-$@`}`n^hN04uf#E1n9!Z?QC9*No07(^C}N=K zS6h7AGqcv#tpk3DJzW)chzxo9Ax~idc&?lg6{W2&mt2TjNJWhdM;<}o6`ObtLu^q( zBj0?WkV5V=T1g_4*y=UsUqelLR=kZVMyw=u!lZ=tYV6Ujt0rIlBLwhbf2t#akZTDR zF~_7$r&z}%#j6%Qci?*z4q2TL{;>i#ue_USEoiN(75~?H2X7$gKmg}Ul7&Sv<;SQB z%5(8dWKxGLODW2TDlV24Tku?E$DP~Jz!b{pEsE0W@%ExfG;B6>Jf;7jba0v#-=QxD3*{VwLF?NsuKR)Ip21%tLiNF32q1l zM3(TOnmYmnlWFi$Sz#2dUZY5{l~~H88ot|yP=Z^gF}e6h(883hfFAO7989fV=paRr zqhAm?gTpJLwBwd84GM_;d=Txw|0qI*mUQl%Z$eXx5GCToX$c*aoJNppA+u)WDF)(t z1JOaBCR#ffoAco0rGFjSX2y^!_hku{$xNh_GzIj>Vz7#`hQ$jaP8=Xv<~WEkC&ZB8 zRKSWw%(dcwx$*$lT3NA$PFg+QUdh#pB+cGcA=UB-6l%OTzV6{iI_$-&~5RX zRF71OO?G*Sc=U?9D6Z52V8Q<+_rdv4n-bhn>@wqs){atbLa{R44@&j2Ve)Z9a;Osr zv{>(>(ZOB++IvSU65K#|j#A(yRTi&LktdHQWY@vJ$>{?3e*a30E;5DjI>26}PX_lk zms$p6&9@Bj(=p4Zo~b+T6M7i&mg#x!;s;8m+G)ZL;z|w3rgp&9Y==R^VveDLTA9t$ zZKi^JMR`y58Px5Z_oRuLzpLKk)aQc=<0D81eIW*_eoJC0?AXd5vnk%fIOmU=Z=8PCnTSE0)HW<6~a0t+9py?%s6q4JI<99F3fRPnFW8Peox)=4U?b$QEiExilG>bp<z~43~#0|E~(#vw+NczKbTJ-$&^`xv6L_N%&65E;)^aO ze}6 zTlivD&$|x)5i{*{3aaRd;a0x@NwCygY>ufICuUhk`DX$(y}S9(EUf=88Z`FsK6ntRl4@42++669NSJkw7W_!vgCt zF|fg$zxQf42=TmM0PSMzX?D{MbZE`q|<{$Fd zMd34?T>Kw}$hZ;jKVZ?Ivp;qyg1t}m3wb;GE{2GW6xd*?sT4@EKYr)jpMK*42*ATm zD?8_Fgii4v7GPLHe69Yh{UJkMhbV3?mOpnU%eP^9SF)#^ewF4(D=W)ni-^bIt=8G` zoz!^f+pf5J=!;cXvk%Y6*;}V;@3w#b_a}ZV%dJxOx1c_Chx;yh`rCINicVRP^JQFk zdQkViY5O$VT~t^Q48b?fG5kZFuGejT^ZQOKAqPWvkDmSO6?1Ra_QBFy=v1kk@~A@| zngDqys-mG5P~8f`7Ob;Pe}`57`#D6T{9z*|b8bEsv`lC9-|Yc?WD1U)Zae}G;i&rB z+lbmpDu!JvRh6=b8awi-tbxYN8f;Dfm38so4&u(H&;MwHfP-?4wP#R6EEihE^rEXC z;T+WeJcoCz15qAwAWEmT*4!8`;fyPUuus~OiGmenXp3!a{bc5_2>ypY$=76Sm5~*t z+2crS>Wqb3acA2jm zO`DoBLNCFy^}!Emmz!J}ML9BJ3*L5!1YUrHVBStIsznjSG`$kdk5g;krjaSTpDI+P z)-t3I`2tKuBYE;5ujzZ4`OM|jaA@T@rsYJ-r<_0R&ytI+jkjij*ELaGnJn`(QJ~0L z$|t0WD+UzifuBoJuIrmkOv@ng46{W(VtTRW*3g+UrYe9mU5o20uaeUwy4k66^0@N*MSNgm}dS)uty3}wgrAY6O+g&q?`I`6gl-c_&>wf~}5p?`(We5}(< zmoGu=!mGa=X~?+{oJ6uu@L#%&vXH?wD)iDqHt$&Wn_fy75=h0k;S$E&f0P+YoEr|d z3;}c&xmx2z;#UP+lC#OxS~*AkLl&5gAyl!8V&mzvh)4FrN$E44`=#QU&-Nuiqs-3X zx?r$zo=&9YC<5+wNXoB4QogtuM0mxU)=me@zbKY=(nY^YWJcvS^AeaP43R(^o=lBi z44CQFWV>)**(BQ~+V)k-wgguoxtfI)enK<2?i4>fiO($j6cr4M34nr;7JP1;7=B_Z zU=iW#<__7oLm?RhHbT=zdtJd$8i=dh+^uoFqs61sOWkv*e95AoY}g+NTF??UiG1do z8rpjZy0OONQ?x*g8hSX}X!GVvrGw9g^2J6G5H%e3m)+_~a*$RT6p`3AOoenwpICer zEH;)*F9i7g-0RVqS0Ql}eF8ECQZ^>TRVRELX}=6GJ48P0zUK^#%Ex7tNUiXI)!{r|{c- zvd`AcEK&llU9fjpCzm=eY7FD=pj>jDGws5Q=eP zny~P4m{LWWe*R9t( z)qm@N3Ea?>tvuh;{khi^HKcMN$=>EF0;<9h%n22PuoDwOLvJroAB1{6S=A@;vaSse z1SHAcslXpJ;yuDguZl%qSo@R;Lgo?$LS;U66idNTQw>NJPpQ%l=U;O#bqP#)g^K)7 z6&L_cd;0)!(zt=w(oor?pUkg^-fX}i+_IV(kSs-R916I38c zp7z`L%iwf7pexXE6C1#7Rgy?)eNE@o= zSgO`~Wn^qsOQeaA0HTgc%<}A)%3CWr*&1~ChKQ*GOVEv?vC3Tla&If{ZNaBj!;uS4U8U_j8!5wT^Se5Xf zsHc*fA>M#$BV5_kt)fxNwda8SAaPyd2smG9hC5RKm(xgyHSZ+06yngDr3%0lHT2kD z&um@3wD#h0!Y=R^Pz+VOJD2M=JU`i$zqOW0S--Mh6)}+v3ojq=I7qQG0px3<8n7MN zO&XI^LenA)3I;;s^^cSuBn_}8Pb2-PkzOr7Y5IFHDhV3NyFGEGXqN{m-I|A1JmyHh zW2o+a03tBC1sHlG?@)D^+5P6<+JxW-D*5I`;Jd_dx8}GtxWCYNMKr-$i{aD1T!IUx zxvK1vvXy9O$lj0!7SlXPY#6~@Y}ZHehRE}CUjOHQ%8Z498Dysx8(umSZ-A5o?(LUe_%lG3itGhk%jqaCdv2Y^z?du8RWTTXo$B^Fq#RyT$u7$1!v6^_0@PC5 z{k)+V8F99Pm=HkN3$PF}Vbin;tAn5dUhY08BkI%FFG-D2QQ2LBta~qVZ%6JOt^aH8 z2%br*Vq$lY56>qDC5n(WF>VrB8IDU`OGZZ~FgoSB_VTeC4#&npe83E@Q>mv^25D~e zWl+$Yzg{VP^^5f=SD3RkR)R^xZy4eE5qY@5)2A(NfQ&2gP#~XEptzu*b#hjW^>lJd z)yo|(>E5lEnW_e6RSY3ZE$OFFMNoPS5kpM2v$LgxBA^fsed!b4dz`n(BG^(DP=rAd znj{dbi8s3T_OcV}`q15+qkQ1h_?cZle1J4!j= z@Se~ybxiTa$O=vv>DHMedL+>nDxxYbdI}Mtd5^wpf+W#mlu=400FsSJOawtiv1bFH zL19K6(gq@mdd=>55~7GVnsDH~i15{^QJdYSj*t8A4!@r*T7gI8n=ak4k?j=^k-e zaeu;wX7$_sls|{4(+i!$6DC+ksaOjX(oWqi#=ZIAy({ofy#l*Z$rFsSEsV%US9O;R zk1Fj{H`sc|K43OeH}|nC0*h^0@$%ulnZ!JF>Exp zPugMPw*`l-vwv33BR<{$tS%5%70$l_c(Ev(3YL=MvPN*EkPB97@2DBo#48U}Y;v0- zm1_{3h|gl2BaET)7q6`nG}WxiIWUf{WgRu~O(w?dR6TOM$9x-B12{(_v;qY>oQ5@W z@Sv{79t9ephzc1Wi#BbVq-Z3rCj+fv)4IX@O!4YaHRG@y1KoO=G3_y&Bax_mMdPwos3Yx^4) zfM+Q<(BbCa%yyC7qI}_??AFo2LwiG&(^yLtSf>zZPhC0A{#hxtGo#D8ZZ&s~f&mGD zlv+_0i?y&@p*bp0;y-E&jpR@gd%JwT+YJ1+kPWfv1wCS3RaIpPOulBg9bNvH_y&Ba z8bjkLejxq0aQM}~3S3aZpUE_$?@CmVb`Gx*DPcnbT(m!tY080f!dvbq4N=pV&Z15f zeq8P(M=jWRpoz@Dp#mhq7Y(TaL)SG@Dc73nsTj8?F1zKHfKdDlE2*0e6;8+NlIja3 zdghj#YaLyaTr!@B&~HD@5%6@CZaA~mae{3e)i}ZHBCxJNE;Sw6AH7eOHz{4(*<&`6 zIqLWl5}^~t8|a!N4I*5aQUomsYME1K1E=1_E3;Tj>H zQzdyP@&AEXti8^ofY2Kr|ggt5TZawg(ZlHvK)p05jQ*G>#Z_QW%B z^P(T!0SAQ=AEia>egiM~G{Hq$JjF*>yA9{Nq|Y}Sfp0D$9FMdY`i4kOdIf?@!~vRw z!%nv1q|x$@W?gr$q?#G26|fRS&%(qI%c%gKqkmBPs}o19Dj0eJ_Cwy9<#S{ksAI7H zl7O%SUI`~9u^tKUF1j7#)<{)od!-Sr9$h|7=pA91m_fvrqT@=%?rc7#d7<|wTI?=f zdPzJmSroP7@TaseU8Q*O+N)FQugy9LMZTi^4HSsJ}l$DX0@N5nF#>{JF?YDttKU;wh)OI@z3DGe|cKln65h-Bdjla6+gun{+X z(d2b7BqV->h}UTkNbqX2QR@^JR~>*SKH56qV7)5nE(TW3ns@L{X;(f~Wjc zQIT7b4utym!LeeupCAy&f>5lh!jY!fJ(2mD5<{5P>!b8BRp(RJJ4ZCb2zO3^5)}Wf zp+io%{zs?n(Xmgy7xv=n*Gr&w8QD-kulPDLbQq~L2niU0LI<6n3g*1C9C};Is?O%XMx;9jSP`8=}!KQ8aT)M`S<`d(L)dof^_ zmYa^9s&K|4o)}C)O%ux(W9J%UHGr#(C$lRmzkN_<3!k(fUhcJ#QNW-2dwza6{;j9m zRzYQD#xSQ|Y11iWB!PDyO{_J#u+^WNSZWsU$NX0{YK_~~>Z4mn8X5Y#s@iRum zzAgSQ2gI#-a%*s8guT&Y@UQM2p)J1|Hy4qz`K0g(KbkXYT#Ay zksH5r)w0}dT@SC;x0cepL=&w3{|{m58DDuR?2uF}Ga_0}&DB_VV?0iiRp2X#E#*pVZ8xuv95~G;0Vgbf4dD*vf7^QA>M?#k>#J5dHPiw`11HC`9nlZ#s z$e4PhP=RkXZA`&B`b)k`HA2E_*eU00KHoM!-aD+G%~J2R@IT@Xch2xDd8F+K^pXht zJh8jtd*DG78jh|;kZ{0>i3!i>Jp z&Um!`ZbchRBDID*2Ggs(*Pdtv-h*Oi%jF+*?!@~~4dfjZ8@5GCR^b}oDtZPL!hQtI z(ri&-P^sGf_ZI!Sc-=6+$r;UG%eo{lklQKY=Q@#r{bFimqbKB#(9-fjr1J1MMZzY= zhB8mLnQ|+ofjEQZ&+W1gpISd_>jZ5-qd`s|T=1O-1h#4%I{p93hkTwo@`6G?e1P?I zGR$4wz4kM>2z_EW)7=%nYMq5>I~H_(JTc;&L2}R8}0AX$~~z99o+y zy>AA(E8v&C8_W?h!(+1m^VBHPk=vWUM3chZSs(fXYh@pZ9L6*nzYDYZ3^0x~Y$-kG z_T(BM?qrd0$DU_;!j{LFOppV^p+}9&!M`8v->O^34sSESYG*6}b_f4svD~=g+=?w+ zT@9*JX?r=i0J&=9FvU@NN+p?`dT|XkrJia&_pKS?u6FPtb4sDgeno6AO`msn`e_kt zJEAnR4TEU%*Pw5uR+pCCWhS%QlZy@DB#`8e^I)OH>|vY*@usk?X=mxeKOXP;;~2E@ zyHP-J{jQmh3|>^l8(7Zu8heNtY=s^dX=+9r2@cp(D!%JYIx-XBQ;+U{7g% z=5&3EdT1%H4y}%rnSG5eG#1;Jj7J6|)kt%9w*TJY|7RY4n%p&WkZ?tJNx6%2TUCo8 z6nO^UFMJiZ=n!K3whTSQpdrh`nk`=O!x{-A?Hku&*I$ZbJ*yX|R%n`?GLvSKACnll z)DJlnTywjunpz@XXAqHKo}eH345?9y?P(4Z&W%SiL-PM}I3ExN~sbtec@D#F#`Vm|tlA{xPevvRO{rc-x$}xj6h>BST z&%l~29Qu8ix1KpOFD-6~#0bE-)VQx3#XVBpOT-@)lz4P*F;T7KFh2hMv z>E(y5a*TCb-)&8ak6v8&Mdug#hWS6CE_mQwb&Lnk{j)*xxCoeSHv&lQ* z-SS)gt<#w|!Wf$p1gAtxj&c&j5@JDV#sj%lhH!+T{(Oev!)l{6f#n=FsIB&}R`%wK zSOdP?M4Y!DupQNAS-KuP(#)h4fjKMr#9Vs*1nCWan0RGvyLn9+Hlgr6j-{#9z*9#x zXVNcqo|S05L?EuIQ6QHWTu9I?mt{+?TO=z<(ymrD9X-*qfQ8%h8HUiSkyUXz>*mLw zvxn6#pNbN#xa*q{nJ(`d1p5S>2mauHzF9&1f+md<(bmw8a>z9S440QAO0Y_q! zIS)=mbAHXqXwE%4>q;K3uyWz6S4`>QHUDXrxpAv*HHeg^b1F2m3h*OCIslqtx zMxA#TDIoozffKS$!T00at?f+YdU;dd3lZlzHB3QM+At5BcXn%NaQ%#P9?X%ewmf$6 z3Tpu27!(XD&TJDY^@Y?jC5oy*`C&4-__=-;_80n2RK%qKpLA&NyIdAKbYd=2D_Tr% z3|6PG6$G!cjRQ@H)o&yn4YyB@cN?Oi8{*9VAasf*sIqP|b!jM!X|VS>Us6mlAn4vXd!Q} z)W*OtjvD8q8rNU4UV}CC)W6kNy_aXBjdXF%Ig$MXvuTYf|K$O<;Xz45en&H92rkYO z&04>u+I@&0qX%|*K*{N&wtz&&(_DQ!ZU6e<)xIJAPYj-MygBDt8h1!q1p>!)lkkG( zY>7;vUE;c8I5rMEP`YZMg;~mJZKP?HW>jX_P^vE!t5Q~=(aTuC$%j7pPKQ+e+?_UctOl zkbyc@>T~8&nef1AiHKYEO_!hMF9x_iwJqK!Job>mIy+uk8}v%FU&qaZs$E_;UcU^2 z)sb|NUyZlz)_N(BQA5%Rrb}@S6sHg;9;pYcMj4d-!XarzDUj*cHLA$gS}E~@cNbOY zkUI52+fK~x_Xh3gBudM8ZIiM?-#qgh7l7B1T|%n|R83t)wzYmzfR~ydjwD%=+79rA z06mP>b!U?xZ1c;iKQxFDCM2t>1JRa;Z3{z*wF6xv$Whp+u)Rgo0M3#24nu0ZyneRS zVLS?de{Ljw*a%!`WUsJ7{FP>y8#87<@D}n$zfsEc3X0Px9t$c|@lsoBZqO4O4O7|b ziE<_^27`{>ML**gJ;5)7i^h}X@~aB@P|pCbReXhimyoJ54@K^ZUVH#+`7&Kq5zMTI zQ@@@DYH91PO#k}g+hsbivKPFIw$@r_jfQ{~VPPcsBm$!NIz%6`Afkq73Kb6|VMu}z zD17}$AXth0?`iC`o3s~nk09H$AM*@wP=E-=DHElbMB`~4R1#M`=}_!w@AfsfuU^VU zKLk*LPKU+{sJpza4?>+v#)xURm2m|q9mfQXBb9L!I!eZl?M9Wip zM?Mio`fQe8Zsr?FuNpv5ZTTm}{fN%gsP5AdyYgiW*pLOcdp zg))&&g#3&W#ebQU93mN{71M#B}$pE;@QO;oFBjc@Ftx>L(LAzMc!uV8_nn{TR zKubWQ@ED8C;>rEUSAsPLPVl#6rs$$|7nss9LE={C#Aa%hz)UJL0m*HPnFxXhtcoqj zlk_1N!!qxAuhPt?GI&*0*`9AJm|c;oWO#T@vM0r2$ZO{)TdO*32 zWyPuVf2jeCd$H9ZDr3F+c6BYh*j^AMcwUXJZwhBu)+zRYNwI1)xpL=9%wM&3jX8Fb zR}0d$C8}tuKIRX657Y>zgkMD}q$4&Jr!+=yAppFb!KwYrkGo6OuGQz+3BXrspp#ac zjuB<(V7awe1FfCt7FcYFr6fP<+Vnuifubg=!BM4pZQXGJ?aKTS(*n{Hia?R?@+}R! zH_*R5JEH~@2S6})sV#vo(!Rl5rA11Z#Hq#$!UXjorGT#qC++D~&!ulnywXbVfK_ukfW1eY=P3=@W8#?K^*}-=iK6njGPw3n739?=3P7 z+nt0GPVvvLaNlF+fVzm{EZHb@jrim@U%W(%}>9v%72~D<@5J(N365j|9l63X1-cV z?G%Y@v{arto?y@)exaUgF5Hx<&Ck#urTGnd#Iuve>Sfh&wzA#jlMiqSEmU$)RDLuA zWSAG6&SOI2AIiSvq0z$b9y!?Mq_g}mfmdCPaJmg>oFLR$4}q6nkh`27@06`y!F+i| z^=8u@2>L8WY=5&h!h~c3S#w{b%Jkak^HS@O5ygrSphCStUb|qV@&Nj8Nf#A04@`vTVe%_vQj8 zy|+rIC*<+^Rrfzymjje12|eGBPdY6TtfQE>v=MN*?;ZPp$zyN#4!q}v{4!cw&SBx? z1j!mCZ&^lr6laE6a-r+8YLCi#buaZD47_>w%|k7bpzF@|`=)0nvJ zF?8Sr46jdJVq=`{c z31XFC10qNt6_ARe8tW5+q6~Q3?qn5Je!A z3IZ0LG~bx3H2d81Bn3NrLK{<{w zv+RJQo*zL!JeVf{R;oLt3@tnj({d8iw3Dy0RHG1&q>{aGij26!Rx(qgsfyY#Ntx#sGcyeD&Kqi+^}>=wct`^L61V(o8bYvj)xMBq|H^$dJJ(UD^yRLs=XY z4%xllXXN3l#zJCKGQQ-jlmC#MsXz=3P*WNwYP`s86bv&yP{qs|Ho|T)|K5b{P%%Xi z0DQ|0AU^beysnE1OtfT0X%odS^78!MUgwDZ1<`)!=zryjdb;L{TGnqe2_wyH4|z!1 zc39TyFm~`7E}W`V*bhJnswhQKB-MDX^bAsE^#JQ(b5h$HNj)?|ka~x*BaFufi$Z?^ zpi-wvc$S*`qdByeC{%ze0x;0T4_t?GDJ7YaXaER)9X(>H?t3K=xVUR>G5V!tQ@6N- z2l-oa9ZZLR{%aU^c<4mX!1Tl_0zQL7Lx36zK3PbEfqJFq0O37FB^Xg-0}6wh^? zBrwLU!~0WfCrJ_yiUKT;bjI)F^Z@#6cs1?-2PCC&^1DkJ`$HlR22sWntYmJTrqTm- zX2cx9EHEA!C!7xbqL+WYvYk{J4H}}F&mnHw`Hwf?)0TP<&2i-bte{3&;`>;44P`{Idmg)8NSFvWP-`&BeAh8TYLVpd(1QiMV zb4xe_%nUG-@!=~eEW!8$Sgj6*X#}{C#Duu+mw+Jc-aiT8vv7OV%t?|f!YU>=#6Vxn zRTchfXu=?1YI|S^N3YN}VKioYErE~^;Rkc;AJ*;V+*)BgZ*W(q43u&!X|a0?9$ z8LlPf5ZnAUFdN$r|2?(?N(5k*ga@<9b};4aWcCRU!-~Uo%3|qM(6KJ0vSKD5dAi%W zTkq~dHzZ;t;#{yf$(T{n45nG4Ib@(SvZAznib|=a^(1^>kj=F5WPF%75KKfoEH1OW zC{-Ob9zKzY4AFJ84uP5&6^i)60ALn)LSmFkkUJX*-b1;w{LK`izs>(rrr=lS=2MNU zTGK`?OAenL?AULG$LK+P$p9S+B@kN~li`zLqFaXX*=q?P1V733uH^1UG85(poz1%d z(_y%1X9J8#G;oBc23sJofp>(Jb>@`O!Hg0k1g8;?SJ07-T7MEUqFu#oCQx6b&3T|- z{4O596S~2k@}M)QsaI`ZC-oZmYb#5qkYjr{rO+N^D%&aA$CapA0u#Q2cSt~hO`=1* zOtwpBV%{!QlxI)~1;}Nn2}4S;Ff(z4UCC@A76UgEIvgxit6HE z?x=wXtQO^|K@u=L3@MQVw<82%I?G>2E*Kx89%kTSMpmP!y6`WtC>Q5ji@WEGimR#l zHq-Bcm_dq7F`+oFZW1s@Gr9>dA~=kpEG~D+{p?~dD$>I}p!s?Q#eI~8W?H{WB%uVk zNJBu-1J;T_Exu3U+pva+BmuEtg9XRvu*g`Gjb>;QJZ}|sF%QBiD9+P-SQa0(05h?O z5pGgwh%)k08?+$!8_R{N4tS7|gJkBJB6o@ea8>G|;G2r|fiwxxK%~dT%ZL;p{SaLD zM)eOPsjdI5|rGlNmLhuW2Md`zQ*sgT;YOPbZQuNuYKRg?<{TmOIFAW$HNON&+$9$sl7YgE^RR| z1{Avb5d544T!TRNA#RKrPvVUdk2&3l1ubzE_kfs0JpucZ^a;!)k_v&!3b1P-CLq-d z93osaEfxmr5M9{J93Ty7!kjt1o52GN41k874p=j$KKTHfv|NbD9hqWTg2R`Htzc%Bi~=?%*c1d79==svV7I8rcoahGDEBAS zY?ujVkLtDY@gH!;h$1pxREy6*7p74;NjN$>81jO7Fb1(fHc7nNT}37yL8Bn*mjV2+ z`M|(Cimb|{;EnGp0$YfNgMpI9o&!5=Y(B=;)&HIQqiK{m@B$KD*jMCVC>`JogNR`J zG9Bis&Z$bv^z}Xma9=T3G~kcOb7?%+{?4x1V0B>S8B8>bp^ec4U}-#Yq}~Wqzz2wG z-O%U+=8EVNlFAX4T6c+bW@uCNVj`kSWAtIipNwhuAqVoI$1LnQ0)&G?;5H&&aYu-b z50+XCKbPnG-TLd)f)}({!h=ny;*^5Z>kb=LVQ6VeA$*Jrf=1LGKQBH!5{pO_j|FyX z@6Y?EZ?7jtIS4^3{7l&q_$-4AJn`PI5P2gDrVh5=p>%|z!And;4_<~?q^@7kKpbJ0 z9q}@B$R6x7;x0+r1z(4Ap}L9is+V-`2pyob14Uhk?n>Wlh~&f8g!KFkS=`fCN{4um z{uroa^nEGr5FpopIHwQEjh;5Xa~%bbT_^BZAYfwWW}RWxM}o~p4YLcJKO7eE;dtU$ znhrQIu-1T((2xy~01;RLQ$s{Yyv+e0G+l@H0d>FxUre)N1`s9T>+rNpzg(aooY|lu zhV+vdW!w=+qJ~a~u>SF)x(J zc2g6n@~QZjKpY@}KN*x-O=;k(86f=$>Pwo!0386cQ49=k+Zh@)Sf=IBbog(QKSRTv ze4NokH3Y~`jgBnA9RgI?5%o&FSm0KN49U|DsbU@AvA{zj+MNfy#k7!S;83I#yfDr; zxUa`d2Vzyw@z>KshY6Yzy8NI-M9$}w3#pcYD8Zl+y@FMRoZE-Q7_oPf-oJVc!t zW33B~H~>zuNb4o>oB`#BcTzS3Tu~g;Z-_88EnwUc=)B^ljFGpKg)5r=Iw#5wdp{e< zpHn)znkeb#gH*y#8a}(^vxCX!5Xi zm<&{qO~_z4^FSA{HG|&Iq;RF)?{C7D(&1@w-aQIc&W; zEFL}6nom%4=k;{|&(MHg_0(H`8$dIalElo36J1nEL(~=C1uBe+WMC99qevYbfjvXF zr$dy;rV}_2bWaDI}?K!jO%_~$s@ zhKb^tYZ6h6FQ)1^pfh(nP~L8K#ko!J$hl(U}`X!^<_;h!U@GE5BqRZr>r{7%!!RW5y0AY7TpsXIY_|b;VDfF`%%O+fx>q_r(?v(`sGKGsreU{ zCRp_{965@hXqJOt6OY2l_ly4~oGd8KxP>`<-;{~F(zsR%N|xEGl%SNPYhe<=&Z=Yc z>g=#hj^}>saM@Ady@=~U2lI_+!mxcwwu;WlV&ZkA+@bZ}hFbthOT0J|BA6MF)fa#< z$n;@SB^88-QxL6E90{K*5e4Bpzrhz{3sH#=lSBs@gZu`0QEWt6et{pSvnlh|@Y%m+ z=m+YX7?tyICkJ;W2RyvbgoiyS)B zwP+Ff_YZQTSIRIWOyQHqnvfAVQQQL(z`Drm!j<~at*bLA0TCyiI7YGtOfAE>&^7U$ zE6FgCDZC1L6$uV70LU~^Xw66v>I)b~CbW_m86?zxFN>1rYK0A*QW&xMm`hGUBI51y z@sa@#gsKwbN*&4=g#aAS zvPH!(T3Ev9O3=F%KPkH=YJf)@JrVStnQU-wKSjxFu}nw$=gt{Q_>+8NhHSEAcQIT( zO(avE!=i(H2J|7f05K11%^WpQ(1ZgAz$ty}(Ak(V(r+WIXAIa3b7?6P+~q)5h1Qhk zVc&j~-$;fmo$(mhZ#O8Kufm5(AAnT=UjcCoLUPphD#K0Q8lH)tl!^`NlM`2%@)~co zfB-qq%wO4LL4xVX7t*7dA2Uk!%#cY*sCy#uvAO~wsR89C3(JND2Sf=*9%hUV7l5(F zPbPs^d=RY|6ve&F&Sqp2KKYcR!Z(I1q;9@%hzFVzo?XN6lX%JekTWww^p8xkR`Quk z%#y=DGFlzUXA<{4{O|sS^pJP_u6}&l&**+j^BY%59`q!o?S`HNbxIy%KEu{-eO2<| z@&0f8@VyQq!67p=; zp$>8))|Y5QnE9}nh(4-(PcEKD$8Vz&7-RhWGAvf`&EP&c{rI`9&!0=_+s$b0=#c9{ z5)5DGosbqbqUvAbN-}}ebTca*K}V9Oh0;_W(pl6d z(Cln1DT{4B5qRj(Pqx+Jg~i2XH<6ga*VQ$*rG?$ymb-;n;g6XW9(*wzrLyGjM&2zD zx{bnFP{s1C!g>dsT8)BzW?QVgZcEMppX?df^RxJMO<47YIS&^itpF4oKEsaF*1nY?J4c621)~F|qmz5W@JIPj*r;jnH$9$PJXoPa!BvL-9ff}S2e*4pUvT^P z*%1@oRGJUkin69F$6P$|>9?Dw{rIb0`tNS+J=;`Nh8Is%ocbj02PIyh%+jGR*RdY; zU$i@{eedPN68*m29rcE71%qZY-4?2BKlS^-;cG5s#$7+pP?Cu-mwIea9&0_<_Dap2 zcR{NK5mQxIK1rUZ=iUCwDRSWV7e z9WSTLX_zlNy(p)~+*>tgn{{F(-@q!rp4(`V;Gq_%qJKrtlgF}&v*wnpomkzOc}KzV zDsR&GO=mN*Hf~bNI6c8pCu6(5uzRUav_msHr{1cZ?dvl|`P+TA1zYaC?%>@~2w=H_%1bL{w=>GE~l20uO@QvkG#KhQxdPph3JTQO{+I=GR~fLsI27I7jUW4 zzYdS~67GtWyua0MQ{KtKk?Jk1+T6_0f%TeSU;0#s zy-nuRCh@n=P_E!#yx=vdY`V4`r)8;*v_qz1&M8~=#+fW9I4WVIrteD203-fF^JU5W zeX7gUGIW&To09A~SFZ4Vm6xUPPW#B?=bmS47B9)(A+VG#+Z6Ca84_dvIL0a%sn@Hp8AnBZ0!p8~H1(Q;bgr z^*Ra5F-B3%N#orcwyB+Isd_TK7yJnTi9E!ZM%JQxVN6G;&xau zuednIXL&M4qVDY6@Q+#m^XDUHvKF;IzO*fpHkN7U)L%nmyRgZ&%7Miq6>#P@{9c*8$Mx?lcKv3XI3QP+z`oy70+N-{9Fb?diup z3Fv15FqLTPtYxLNVI|y(9V}mRC_VAmr|z0<4^zr6n1rbLi$#ut#Lu^|{7Q$Hx0b88 z{rtM4!!zlDc7%C`!;hmEeWDBZw@+`4ThaK_;bC#@zwuvNnoWvP1W?tzGyENCR;!0t zIn(VOhD}6I7H^uxJN8M<*Xi9C3JsER&-y3Mvhu%!HlHL|*TjhEvn+#kIzYc+lr?H!+gGZrJ3nu&Lz7xd`~`S}q~8?CX`K3$_>5-Ve| zDK~1}blbMVE$nkkGDEwjegJ-Bren->{DtYbDSon*D;5VZkxlW3#UO( z)D;y4BaW;SOg4i(?)RfLZjZ+~BPutPno z#@D0cS>f@zXU|@Sp7vJl`=#eX;85!dzSgj`xWAcyW&y|a{n|)TAKhTnsP+6QFl{xj z1eZ2jz_`2GSSd=}sIPLKX&mdg6fBre^wTDr`BQYNI5j%H#?~+&&T)k=4dFCemipJ# znMKUOS6o`;yK`!dZ^mOKmzY^mN74uxeFKy+=9*!LPQX?nJcOxD;UQAZuZLAg zT*u#|0w)Tm`er$<+hPdgj*IM`2B({8L!3*_qc!uVAQj#m`X%|3u|+YqD8?4Wn5FoC zVJUhZuHxQyKXUK7tiDB1#tE0P5Po&c*TCWZd%pc-$18+IPp9u`B+zVZ6ITJI z4#=x4inD$_|Mn*UqyXUeWdOdWjtg8z>5>3aIz$;?||f8E_Rz zJlvP#6{6dKHsd)POintxwS6#{W5dAsjj>@E8-}q>F=iY7!bDy$^q=HE)W28m&fU9{ zwtIV1=nWrX3w|lMvm~nU@W9g6GI`UlG(Z3RbMArS2WG!~;3FEtwDNDogG~T&RP$QT zm$k;_<_W|a0IyyZ2n*l}V3iY<1^l98Yws-5v12=3a6k4)L3sjy;hlFjidjuI@Ex}a z5i|K0c!gO!p7b(x1RUThuFD3zl=fe{5tKqswHbhVy;Y4bKVl;O32(p369otl#X4`Z zg#UQDE1}R*QPgS&{>Qg~6BwUU<4|AE@0vw9b6ntjIy}nb=>YSWrUOw&%mD?~Vkl}j zc)k)ReFTXX$(b|E6+#I?S967-^z7NQxrvF&Ok7c>f8LhWS<elI2{NxqH3N~0HrrB(<+bzz_i@BV*i_&j=b2%)_NijF7Mf3s!ny{1rE8&KcxM~1Sbl_Pzr)&N z2&(hdd_AP_PxH*M?}9Y29A>IuW#lZc_tN8p7X{7$gnPOh59zy1*t;A93T+r&K%1Do zAAr)Z^nlVlPJk5+j1TH_Y3)YIQEE{1aR-LX95WPShT=aPim6L(9=TlO%bjZz@9r8l z93arIGPYp$$ml)tC;t93dfKzoJX1pleQb*x?rtkS5O@5aABdT4MYP@)*r)SMLyru0 zuNNl!@%A)XYpjZN-f96JcA>eIBJ3JMfZjgxGND?aY+ua6US2mbZ`EZ_g~@Xm1$hI||BilLhlD z9agQvmj*OXf@`?6D5uuq&Fl072ar4Q-7y(t^080gweC#-xf5vt3Y~Z==q6Eu08VSL zCijEY92<(U=ZM%(f=4ZX1&8_lp?MetsPdz*x7-#1Dt^6xn2>H0$04~{R zSp^jHqcqLo4e&esN~o4TU+cC|M^)6}g5<}GS^57uqI$|5*z-yR5VbnV!*Vlqlv^LS zQyTGgr~jufX2JCUMk9UhAq60&>!pVrIPg_qJkZi!3PcuvyWUT5)c6B&h)DNWO2C)f61+%-l^2G#HB-10(x~PXDCgO z>N(VsxP|rV)hq6d1@`V|&z@yft?pBogl7vVLWxmX;%SdGXkqBEQEO&`$(@Tn_y3H} zM!MvMXEyL4YV!=68X%_B1){Cz3gSO9aU9 zhAc@L`Pi5ke#EX=U|zE$2*^q_nW#(jP(z|KROChAV9!hueP4mZtt42)tL^Ex z`p02JxFavrvPRCCW;{%7ks$Hho82bOiF@9jDF?iHQvAL zg_Yp)#%##y+$s*gdt}2kurrkYm<^*s?e4XR51n88Wn6Q$LYM%Atj^O6QKAG*kTils z{EkHUV2B_+bi&3fU-63%ayX>iyEAUP)d?nQ*jwfv_%5J^V|B34-Rt zEv~n}%gyz&zp(PCle6<3sPW8QL@Gck8*BJXK+WyKkZ_i6Nsg6wd$nDfSE2 zunAO`$dD1VLM)mnAu^w;c zbvB|9u*MD6AcUEqbk)HRx%?!a^WsefF;DBvuz4#qEx`LSu{2)IK0f$jDn`V8FlHxr z&}}5#DEO=-P`TNC|8pjFdUD1B*8279xoxSz@x*Er`*|A8miIr z$VSWKu|dnHUpd`f-EF)>bF_c5=-vYCPT%iKBZ`8Q5k-M%_~t~=I}UCn=#M#wq47I} z0HQwj@j*q~tEV$M8m+7p@%U4xauuh5*1)cw2bCxcfY>PKOAkFrOsdXTj+jjTEFOyh zI}r*Cgzp|F?k{cK*U!T80Hl4c0Emjv)qJ@%bUdG~L5k2-9?%og(>6IQgNCArbsJ%z z5asGws z-WLxgL!4|RRk3c(HcRc)@9$>1MzZIiP89nWixu30@W54&@5 zZ-K*>T%u+WpUZI)Ormik@~gwio|!}=?>lU=-NE$OWyht$hGHOhta=YA{m*W{dyl;i z&SE73dDvq|9l@n)vlc`Dx~8@@KhwlxCzhZuk-Q9-VDV9XHI<_Xv@{cE+f4K`f z1>pO@Y-CEAIKAW92hod9Gz?UO9=t7W-e6fsgcy1OucpX}VwTW|v@f~lAqpFqA$sj3 zA$htUuIhA84e50fAYgBO-&wkY)F>b9NT(l)3tN-@Zr#2)(3;FG9TDepO^2S#b4`ca zH~f20Dwl9Z#cniXR{XCXTMhSxWp3V#bTHTo1nTfKm^ikSvwaz5-M!GWnv$!##Ea zx6#2Z0xi^O3eq6D|F;uvImaIzT!Q@qN3yh?H}bxe^ZDoCE|ArM#sJ3T>2BepOl%LZ zGApf-HfIrcgQ<^Hy=!MXi?;h&IApvXvb6otPaCXYL;d6tsX?uMH4>SmU6ELLf$6M4AF;c zVec8+Tv~(?0%zEX`o0k(-=za#uWvtyaXI+BzpF3s$G+6}2Kp;}c&#eaXN0RoFWld9 zXHU2MAEuuc{cQwp+8;d+n@8d}&V7XY(Eje{M7g`C|L&^M(3- z&!71}Y#Z9__REWD`)-AI@-Ea)4R|$ERMK}yk+(A5=Y>jX|HM`s(?P}7-@f#NYYq<|{RWT4DWVTCBc~hO(j8J50-lM*2!C7-?O7)5uwsIpII~i;A*NQq4x0 z7H{TMq{Oa^WvOBT^doGlt2`F!j0Fy8^mc2&4g-9DI+j%+Udd$ zJ)H?Kau_a{^o2COMb;YX>2URjY0s}crn4TypR;q56YuPL;oV7bkVwU2>hKC|^lxk8 zCQ0?QG|%d}dQV0?&<2Md3W9>c^&9Kl2e#Noq^86q|D z%PFEqbA=niqMdWeDK-WtsLY9O=4yv_#kjeLzRT3|hzBtc`0fuyyP=^r)9f1GeCMm9 zT~Ve(QvRKds|8h|z50UAu+d;fP*ytp#xGFOT-rGM+RmhYREL-j&GG$y4W@Z(6_uV4 z2bUX#C*_12?Hxz$QIeZ1A)?fUFeE3(p~ny-aXQ`nC*7j0+<9h5WSXx5h=f7z%#0qF zUz(7!Q+Qhni}wK;ZkV_0L^BpyC(idRwA{Vx*Dq)hgNiG%#;4+@3BOigxtYYXo07hB zNDGU{b7v}w-jaY)(|8S>L8`GCrU_>7%mRLc%O+`Jcx&-tN9CMroDHeznyb;!ESoP@ z8(T7COXh!P$%tyMaKY<)*{7Mr5hC2a^t}7SG}8@NXg7^M=J7NY|{5;z`^h>%}wq1L|;9x)#tx8rxjm49A}T4@k@;k=0@1?~*7csGP4e zL0CG1P>kfA&{1{I!%o9qsI^G>O$Q-~vmWP)GIl9+r!oMg&bkg8(s-#RR=7fn23fIq zj@414mDuf~E4feti-rO>aneV6w1fvs$<+?O=ev!ZmYA1ot|ifdL;dX5ckR{OjoJ1d z+GnP;4h{7DK$chBw9C)W3(~qM)r1OeUsIS?&)}=v9@MEFvUjO9ppa8<+Zqi`Ui|#Y z7=eIM6L=A1X|67YalsIQKJ=fVJPzYsC^@@0PGC&1(&MCKRzA<4A}<;{9&4n@~XyR-d%{dvDp`GwWNX8~w^^GA}CSNM5e zb1RKE4DQu!eR6dxx1cd7=1(jeCC}M$e=`4^t2wOei&^4(3vMcaD-tf+iNK5#o-Udyr;&b7^U#ao3VCIDfa@EjcgLdH9haCr9~ zlvyBxf-73FhY<3J2^bO6ektF4a}AA*(C!(+H}(9cp-JNBQ8%SI>JHq4Nb z(DdHe!Iv{!Pu!aT_l+B42)a}n6LK?i(w`8VCIN-V6OQ~wC16I!vv9%ru9^3z2kiRN zEA@>PH+utcHv;#vIrxHC%i`2EdA+@q0V^K|y5r#m0;7;5oJAN9^>P3AY8$^*%-WcX zxB7-@0hlf{cT@%yh7Yt%r;2d6{pv;Fs}`T5QW^NJFf9ZeT0@_}C&kA*m<~)4_3jpu zApNa{Yxu%=ucUse=%sk(X4l=>2J94j5_MmaK>F6!2gwW>f$@nc1mbPITG05yN_x)Q z3q{v_UOcxDp6`|tT{`C=(U*5mS@QE&$BY7GaAOSXQiU;*kpjo4Kr0~!?2}F<>lHt0 zk&G}X4iOAgBe@*!lSa!gv#WYP6I5pdXoFpPu7EEPCs|{N!Q)}~dmaU)x8q@Y&}v`| zo%KwaaKO+%;uhVPuD*=7sxe^zUWVJ9M<3XCaNk`{kk)rDh@hbC#GZRj}<2zr8(~8{F5y)Iue3?X$UgJE|U2c=II8zo*rX zp}R@+{tVS%Jx{j!b^QrI-p&!(4tyehyq;fX0piwVAY8?BsTnw7L{+CHsnU675l0aC zOVdhlWV8d^0AZQZL}q(n?PK>`x70`$a(4tPwzhA?_5k1a%?SiJL3VR7#E42B${}>Z z2Gif+04lf1CUHL2`dSD?zq_mC9o{a)TjL;-uUUqd9SOGK1BI4U)BnH@K|EGk<$Nj@ zazBE1St*Jpo2~)+%S5yndaNj9wm7*d-DK#W5pAU=*2$**G&Q$jKbo&%@ z+!ztJz?TT=C8_vNe+XFh0m6kQj zd`q%Um02K&B{cRA$^@L(mxP1&r(>=nKA&VCWzY7LdFNDVLNVE;(q8_F(( zt*G9!!b)qIPI+e58oY}qEk3T5zWHp);#c^q!oQ5h@2TXnmJm}Pa8*&bN?j2*4e zUe;RY8Xh>Pgza%}0(d!*TE1)bFVE)?r<->i;JcYE5~xx%-G^-j-sg0Byw(-Am8ypk z_K0gH7_5LeyzB_w7JWdEgRTs~#n63WrR_V3s=PP^Y( zzkm(@P-4wQ`T5@;H8L_s?84X;T|5*xkrX*ZC2&!o)*ia$>p8;P-zNFQ4hW4mL_PW*qhn0uOT#RXDIeM#xi(pDgG?o<`MUm2J+nn;S) z#=tfW!G5DlCl$t3U4E6MZN3Kj+0pV4I0o}Iz_z*;$`fJjFD;7uc0QD--X{?=z+379 z^E`cK>8LE~-wP*q;|5E4iuMFJHYS(OQ0)N0vu6WcD~|&n-HTde(9VNH{zxJsjp2O~ ze>+A19`22W3x#YU=j)5;K=TUZFFT{Q;^!8%jg~I9JHjTzIU(rScQR;DiE7v52u~_F zq=kTh7dBHG4VUj$7Y}!40?wO`utl9=Hp5*BXkT@AKJ*w1NV7Hc{AKiU$;ko2$e_#2&GkMB=E{i& zfXqop*1?fHmSY6!h5$6LF1EEgHbn}|6;B%Y?nj=(*t17`v}n~bW1 zIE3SL0vfbfQ~cw;2Zwh`lp7!LJG zj}(FEcBD}jp`TLCxV$m?=)WgNn-CT|_`V6p+RsHZE8a;8K7RCXlWXmLQ_rQ$XisSzmd%{>92pGFy$OH>RimbyX_j=N?5+X_aO2Wq-H+H zM`VZK1(fOja2Y9a-428{t3yeqfq<=w(==*=(GfRmk#do`>!A`)SoXS!&d__JDU=sL_uX+eyP6tab=peMTt{(t9G zY?n~2L~rg;|03Jm87%J;KA&CPXRFx9LN=qx2ANjaJ!D#Cu`R&brxle15C_A|V{eYb^T2uPf{;RRi%d`zXV?TZ z%s|&huImE#`wG>T&Mn-}Iy_+ROB0Z)LC#`;5Zd&pBD7QIYebe z`P&|Ukpy2O{FfFhK*@{OLY8*r-IwhO)yZ!VYQccqpANT(xE@*54W?Gu_XzKhmNlSG zE+7BfQ{>J&SsU^|Mcygtdl?7 zXInih`))H9L%=sj;PPR;S!-bSc1#Xv-4AQoT3xk@O9~Oh?v(s0ym-pM3ZbNjsk><5 zUCYXe42rl{Jtu2LJ=h&gCTt|*_7-e#NDGY~e60@#2hhSN8un0L3vn$n+`eApu@dal ztYKSMHJOEol|@x(zuG^?&T_AM_0L2>QUf~12Zp#%;){9}owv_6P?YeHzu=Iy0d=TV zbYwt|5lz}`urv+SBVto5cxhVPZSeFFM0FtxW^995)h&BjvB4uK<;Wrn+ZjHIUeP~B zIb9`n*YBTTceMJ06oSO=p`1w24pbn>PCJM`MR)~i#+Pe_`_|6&i3Zpx3;jvRi9LCi zK*oz%XRh#e(7LeX!Hjd!;d?Vs`=Fwm(TRpeq1sxTfN{7G|# z&sQd3piz{D1+ReT!3t;Gpfdm<0j56aBJL%m-FasuwK0;b{aB|$X|H^qGvtL34q$fx z#6Z_nLA>GlYAHuY?grCN_v^BF1vC@Vo-L=Xp$mMK5jlM1a^|cVi&YrUsJ$VI+qi2o zI2*6=uqzDWgLoihaFHmlimIfR1D*m3tUi1KvLnnFv8L6= zX#+*#msa>S{(N&#bVUOH0=_RPiKB))+l^8lTS~INbDr zK*|$J$aqq*>1U76$zi31Hr{5GBw!m+Dpol^?jluSOnWc2(T1(;xGNG*ygxdqpPH9oz5uwND+CN0fRk(*kfTH}^)M}y z_hA6fHJ9Q@Qoj8cs~!Bk{bVd9%-Aj*$b%BP(zwC9D}a81ydeXX0%yZWW3b(oNW|Gr zvJV)juZ|(XTWNgvU70#Kvm(g(xB-doMFH`P&S-95PJ$d1Z)pIyZn|!K$M~rh6}@pJ znF*xLzc~St6viw^Cg8;c9i6G5DiKH!nS|k$lcR{^DnZ%wa3k9cs;E*Kd>(K=RxQN_1w<@J*> zERY=`49Zm0ankhAe3QUpBt}dI0$R(JmLNmnyYO6l$0;Wg=f?1IbJDS!CQruYVN7!< z7ewlZj>o?owx27nnGWG4*jzhUb{Q!|5qK~7`uo$xXl))kyi>Bfch+9>fB^Q3zxQSP z-YgzQ!hQ|xw~ z*3&|B<%uw6PiDdq;_@{zKr^uXc+>_L;0d3Xkjym{W0IK#J_jq2-V2`Hi8s=_LYNz7 zCxL2jo-QLnvm`_E$}>24wtxHM`Tv2&um+C~(i)0VlE(@wv@&lE?-s7Obrh3AjcGCR zVl^Ny;8yDIgoR}vWqn;1t_SS=p;)I@c9vEvtQc}2te?KSGNJmazwP;VEDU0;g|{k# zS!}%1EsCv=Mx@bLH=1a0WLlHSB#)Son{p)SA;*a_lS&!U;6Q+c1HL(e%lk$EtijRa zQ9JYi9}T*Hm!gskvSLjB4)Gt}+bRo#Pp5v=mS#x;_ycd$T3?jMd~jQP2fDo_4Hroq z_z|Gu7?J34_!b6@E`())&q8KG12+vf23ns*^v*R7tcwCxG=AfRV>>~Z*T2_xRxGQ_ z*K95&S8Y=cu&^4hLmlY*&ui>CZ#-Y{tuePdCiggYkFtECVU8ik$&tY<>RMoC4XnIV zWHwez(RN;pa2M+Wiq$^0TSp(e$=L)j4~q{UFR_NjMlJ;CjM4cb(Yzpy;)b&sh$mni za~zu9lUW7AY);^Gme~B&e7_*Vrl2WwcF!M==6<9qUgGE^EG|0<9Ob~10I?uLwTc=Q zpGKIC)eE2Hk_m4z2Nn#!q3m9bn3S$&E^C&@i<)EBlw{``y>W;2A(}_{sf(BS)AFA= zahSJwiMoCjlr zj|asS1*?EB06c@ZcR7vO1``S#(0Z_g2WRM*ajTEqC?F=5E0myrxxZT~&#{@lfsZn6 z{)DJF(+9?cx-ny2^MIu>*NDpw$l_+^nL!|S`x9VPM%HRhDa|7I8SGk^l;#Sy2kXwy zS0={ILn#A0;OT9o#qUqY=gdGe{19_8Qw=kmq9NFFZ?x>yCT)$t_dW)}s2!OZ3BW$RogWRXQBsA2-EEWroc% z%QgBYV~(UmB3J#O9SI4D7>yPr3=|F|PjA zGHh)kF9FcnurM%YCIb{YVEBq-fR`O31{?cO5QG_nI|}Mt{g?jx+!D9DOcza(NPh&W zIA9Am2sA>7G@1g#d9-h!@ug-F2vC93Mt8_}n9VDc505hy=U(d&wWqcZoSm9Dk1acOc?C%93Al!ln&RIvS-OoI)1zUh3QDMDT`?gAK3L`V#t1% zirJpA@481vv|t)xh>fiSH1OMQ3Cx69VCo4tB#EhCtYeK+HgaCUVe@Ap6b5SOF@$TV zej{LK=>CD$6?5EQEG&E0l$kUMlb^zgXt(yV{o*rxpXtbn0h16deKm;E1A?M`x565M zGjSx)v}3V$q|%vhXK$*9jl{#Dv1$3MMXe1FD9zeV328v5w9MtRg(&ZOx(6c!tBK>& zNafJRzw$Z40pG2{C@mfgolo>&+NjxZZP+85>E`JPP`0Y|+*^>w`tbB%JMI`C?pNq} zQ_%F-rZ(W_oV#Bv2^*M-NkN`Bfuo{=aCX@=(QDZn16&$E!1UJQVHwY1brdJC!-8Tg zT%xek;4h4Az)cs);f;(7p8!|tQ_a@CWL18m{f_jEBpKzozWH~aB_y!eyr(n4TS%ts z5gFpUdt-3l`=g^3Hx@{pSvDI;P)?qGCE(ckuu;;Fe!xL=GDH1VI!X1qrOUST8(XpA z_w2!w;yjhz1%xJwJ9!u!V2XAn@hi9Lu_qs!b7rrPt@g2f{y*2kK`Q6*@Zm$ovj4uj z3&>b-&Mow0;K-2SpKk5#96)Pu3gzPf@F1SAOOH?Bz-&WS8I};O2of4tIhJ3%3U(Xj z6k7RrN;LtnqXGMp={tvl0=O!CEdsFfF!AvEMI}}^Uk=A*f!hMLDJJEb z-SMJZS?5}&<2|7855yD|ngJM6)YKWaL2Iq_QIk|q`i84kc`@Fspd6@>5H*aX964KfXggX#z8dKK1cP5>BIHHLPFnHTqQgOo34ko$c zCYgR!^@~NVlf;d|#0cQjM+Uzf>V2)+`ucV3ZFf9#c|-w3Vn zaJ~_6$cRq#ILXsQ{gg}qgMlLxXAuDjlKljx7lA-A-L3rwj!P7P)q(|>D#Wnn>E|Gw zEspV3Lr4Mtn(CEfbUQZh1VW5z%%fd4H@QU`J<}B&0^$%+nREbmV|65B0c@)G6m`j- zDVGTdCO8IIkP9s#2z@{sIPDk}I^^)o0&?AMM4=n-U+oNX(j&d0krljm@@!#nCzXc{ zkMzAe6|V3{Gjx93ygYpvaCg?Bt;W!g^|YLG^Gr1jv)6K|?TVZ=w*;y`JREXO-xD%N zCE9X+Gg0YS>o~98(-KW>Krzq7^dgyznMRe&YO4SZTruSWP1UYQnWL`IHiGW;0geEq z3XH!jnxOyiGr(&*%;T~Y&pl`7A}eFQhJmRAKT?Jll`YFL2D@V}SLoZ${9O2EF@qKn z8h|%}Fx4?*j~^Y$hlA8W(*x&)#32~3PQRAh?pSNQNo{$*#Sys%Htlb{oD03|3Z%O{G8I}rm&SdLN~d?A>>a@rz9RVp}CRFLFzxVM1W)0sIs zsJ5s0%l++*eiWXw+zxk5Y{lsrf0}Ka>MJ9qJg(&CsEO#sID|FxP(2Sjh9EPGp6xnq zavns@U@>4^q1{2`89h$?yA*;es*oOsJQ43FgUJiHoZ|BRbrY%s_}3(|jQKS)vF&bb z73|x0$JD|l6VVGe*6Ev;M|1VGxi=80zJ76>ye#X zg187rBqK7UvI{z*vVll#yjmwqEWG&|4XsQxoDkO|s-n0H=^me?r5}PWDzrb0LsC$X zkoNDOtA9MKr}Vq-k@x(Vso*3r1#K{62yT)l;n|SntwW3t7X+D0a7<7e$v!aNh;oWZ zcn$yN1ea8;e!<=P&CF`=8Md{E!{E-~>NjnL`+T~GU3qWHsBAzSlI-9v);iQJ^ep1K zKb@&O7gu)TwYCrgjSLfS&4caS?MusIi-?^)w*&!Z$+m6jII0^y8!3revf=o`CL4rb zFrPT}Rz@n|a4ecA2>rs=Gp;!&huX4`mzv}k<9BOdQV))~a8ES}S@+RsqrU(a#++X( z-*;x}>0voz3eY$*0dcgi8a*5NYKGJTLHA~{D23!c7;hv6AjM|NAH7fN$k_USb4)dV ztL-^EmP?e#2;K}M@#St#{bJwZa@_?BhV+HYKw4tl=9zhCo@nIB4w-;SOj}} z2?2>yWRFvmQ{bUjL~?iObp8aEsQn z9j%t+)y#*L!l|mjHmEcZQPq%E*hQdghBU-k?~)RyEHkv15@nlO4R@cDz%Q`OT%6#F zrs@phY-?-dqF07&&&zu=2LC9EU-9#-_7)tMSrQaM`P%~inHdtUy+K3tJN38)tO_EK zc$yL+PH-&1muSI&0+kJLEr4a?@IHn=RGyo)%Nkt=fUvA3EdAPzNJ0W=<=y_lCY#$b zz2*kB2{@aPGzm(ZYn+YCxvxB<@A=aOmI`m*w03c&%RN%e>iE*u@loZjfRl(QKv3S; z3OEd+sb-m|LW{5$!;hN|QC$uam_80#A>o7V8#tvy^Cc)736<971k>#w0*iqG!`8ei zlQQGL?r@W$Yt7Tx)qCXEHl_grtqv=9WOF|U zV!=#>fy{Gk^ug7d#^h;h35#s!s#}28+IyQUN*C>FS*lJWM5W zE_ffql(IeVZx09O?gDI%IRQ&+unaDUlfHbmZSekR-0SWb{{0OCJBTC{*H;G>!+tQ6Q|F786U)P_NSg->LTBdgoj|{YNdwTr;v|vz#tqzj_ zYAi><9GI~r9>O=KkpsYk!XKBeA)1X=T&DJDy$fk-0hEBw&XO_HtX^O5d4GA?Cdvqc zBeA9c{$GSTXE5>xIXTwBSBj7`&Ov4h2KO4}F&JB21qmf=*AH8X%?GZ&z6gc9Y#x%* zg9&Ze^7)JLjZ56XJs?tM=o0K`m#CtfmH`?#aTv?{x-4Q^lOY9E6aLCT%^SNv?BKrs zE-wL&4URW7HO>BW2M;l)!85oL+tDQo+ZbZ8-kCsDa2QaO9Ca0`VE|?!`B4Jsgle~f zC`20Aj8s!v^26e+UHZ0;3}(vkq$qX@1HLkJ2?_uHiGP8n2U&IOf5!;mF=^7yxl(R} z^2Bz<%aFDKywpziL{5Az6g8Jd55>ca)dReWyXRxa+Z~s11|k{UM&)`yFxL{QysadL zrc9nwIxvl~(qmKl!6=VS>DVs)Z@WwXGh<2Ccd$MCe~Ep;STYGbsx#F9X&3UAqWIlZ zAAtf019RvXU^L(OB_m6V^V0kLb(u;uLF;_Hn{Q1F6#Kx$Q& zn%LkT&cXiDfuOSpK(|f;o!C7cC>yp4s}CXsag3Y90RJcYj;wGt)XDz~pMwJSR-%(L z=fb>!t(~j|m;-o=za2@C;&~J%KH>;a8u;l6K~B)xEFhS7=)|Jt31^4{!N34}HB#fC zE6OFi3V@7-o=YCDuSX59!1srQ%cMpG1oZ|o!f)sqPW{hd%c0vr8##MU##IVwm)=$i zh{V@tR|f-~Be32E@dtn-?qMH28+zs;E#RDi`C;?)mJr%xIsgndbUL7Q+h3B>V^d5D zg0U$c+r|GU?c#sKDQ_pwc!+qso&HTuqKt5mP$AlQ!0^3Of@Fot3r*jv=IlnY1dJjQ zWfcjKA;ILdQIvifWF=@z`wi^9r@dTD2|w-zq(qok4MGzjIL90^?UFvkr?7G;Wn8p^ zdqwcbUhn`@sS^q#Qzt5RQ36 z@&i#!^tgafk^o6Ctux>;a3^HBhvRWxfrII&;eUdTW6wG^uVZU?Y`^~ZvrQwpMypnp z-8|HF>`%%O1zh{12cJ(CmJUq?OonZP0y!w)FW(n3Lbos+!G>%igilChgCc99 zd@;6B*ywC=+UT!V3XyYM(0(?z_LVrO^9r3_(1S*-)mGI8H#2u{H5w=#rK6&-y-@9F zKn>L2_i@_Vr~nuen5CFD5acPTJammmt~dG*RI52gAYJTv6k-*@o+uQnq2pIPLP4Zh z6-Y#@gho-M(l4<_-OfEeDE!vf2f}^SC`q+bvbE}22o`{r$H>TgzoM^`A`kwsd;NmZzA(yq2cASBGxjjf8H6fe zNL}2XO_ZWGmJ5_j)fstW*MGjy5YMus5@+GoC=au7{o)xE!JX{gKRuHBW=?r*_4w^2 zv)#9!nS}2AMb-_^&`T^}B^f4c*A;$fvK@hL?U-axs&&}8!q5cd#W8-=3N38JLd9%W zJY1mlax6_xA9i16UEXZ9GfLHFeZkFnbU^ z@=+fFlaC6$)atcoskzrYKI><7Z5yOVP&qxMjy1$&^=e6Ym7+w&t4PF9d7JNrd~Vh> zRNaGuMVX;4buTO+5g60OyFm_%{gJe|cd_VrAgZJ`xp;}{G;C9Wuo-ZD$s+=4Z&FpP zad9CbTb(Lmf&l;HXl0R#`@_z`D8mH4CJ9;S$oYToUCDm!t=qwM3mZ=SbH65-Qdwh4 zN(L%ou;z3vU`m+g_+(nq93SNnSU_0i#@T0dEl`-Y7H3Bo+Qzd`n&*WA0BmSR`kw2C zo+?JLfv0I>QuU&6v#o%a4Om9Pg95HZE6d!KkFE&mBs z^$#Xt{;pdh``AD-H}v&O&VW%}1CNM(_jECIr% zfC$h%J9a>i2k(Z5r#c608vF*%2P1%D;DH_gd8^j#Nb?9C7H|1;tD>#}Qnr8tk*Ie; zNgRq?{z3g1vk;;ogTb<-+S(W65Yu&Eut%DJS_nqWX=NGSd1;7>85pn|)Xycyyd)Hz zI{&!15bhQ;#?&4+e^+j>`v|xy=nSJtzG2TP)_|3RIBB`Xn*%5<#8N5n<7f${ViAbc zHdb}GGbI(e7APc{_POvCwS0u6RV0Ii1U48nm&vp7@CaEMCeDcnE{p&?ZBNyH#c+== zRM3VL_P3<*J?hL&%7)Mdd;cSYX`P6b?L&tYp*F&57eiFLlFH=1N0l2q3p|Yv7Aloa zLjh%A-eL%YggZuiP78s@zVLhg*v}8E5XeHD2?-TSD9-=7mV?v~W(+|y3L%_ge4c)S zs`=%!F@KoJ6y5<+EKV8fpG&G+>~}OF{RTn>co7t}B_TvdFn>ou+*Lu2?Q3{WBh<<&0S_ui za?4&ypJ~H>Mo|9$u=nP1Ip=Nv|JWOoJqpPz)+`kvM3QnNOV-Jf64}EfloorGa@|av zmPCz&N!LgkQrWw3(q*dY%GNMsL>ii;{rf!LC*~g4_wl&@`8|HW@9+Nlr!g((d4Ar@ zalDS#aU8FMMi14ZQ{HDmuY)_Hn7zP`k=LgIEumHB$y#*D;{>}v2mtxmNk;MPWET?e zVBT1c`>FQ+=4KoM6p;ocuV?)v?S-AQ{C*sF|5|gdFkLUcp@sSYBK%ITo%Qz?m%q61yp-$3eP>IJrDeS? z_5_o_@2biOW_vN||EbpMzul2oregAYZha)+Rw-@Q^sZcslxkv0$bhC)MwRjbPij@No zsmHHzie|5)HpSRI*lq)tkBO?MZ80)d4!W9V5vBk)TS4%!1i!k4c5U3uBW*bQz%;R) zxL5Ah=Tt0mcGxx=3p?<=mW6%!@My*HX+UABsf_{L za8H{e6~KYUFQ9aU$V-UfhgVc8g)=iJ{Ar7-MAVo)6u&PTD7jLt#&qG(-#X}tHtSNk zuzI=?GfoFM%_6%p!T3w|<5FL@5(z4itUBYs?_Zbe*JPpWdc`0-ATIfY zxGzh4RxCprTj`12iCyD+^O9`&@UoU%&JpW!4ZrX>ne0$liiySq-EIYfCSqD3>P$}+ zV7&|Q?y?yEC!R_6TAqxj!Xi|DcQe_V54cJrsP}8GpeZs(L`+ zAQ(&aA(-D5>yA%YAYk!6e+h`ilNgr51Wb#4WoiRW$JL4lPbyw(OOBSBn%(L z60G`Kc0IR9%F~&h+z~qmn$%-kFz@{CiD6u2v_z8|DJ2(vB1I`E&K;w)K^N~|M=tE_i$mX4TeQwD$F|& zACsZf1RTB^@M&#cWq`5BQ`F|5kYqQ|ae`J?41)A9q(ED*^*D@WTLIi49+BGumLrZU z_Ug*THnQsh+s>rQxTfwh%xyF)#7P zS|oJ}m{OCUW!FL9C4IO?bGC5hN%+@hTR>p6Gk2#njqly^T(88K_=%LyB$y4=&RZFJ zyGYz^iUR@R>sI)3l{f=*yRN|?)T_lY02W?oils-HC1sVZ0=hp;jT7N0b#kk7{H5rX z4vmfP_jJlksr6?=o=WFC?a)Ge?Tdz-{uJO++T1 zK{!K&Qy7`#$%p5ZgsQauR)|U^i z(rAqvU>Gpyw4$UsP{u~J)?w@ss0=$vopd~S2vfvQq)kI&%hO_4@&ZPEe9n*i`SIL- zyr2I^*r7j(1^T1He9B&Rd>@0Fr41Y8A1p{tCqD)e{9p~;RA`3++{nybhXcjPtG@4L zTM0t%P))17y!VS4-wQM&2q8j2yk|9*Sc#i<846H5We?TllXN+2^ctU%hLC0$ZWsx{ zkc?HhIaPbGvHAKlqB(MO+-SSbkj=)J>@$j%y(oPa7i$6ffVT)hdE#NHr3JkJ`GJhj z^+CP>n{T$@iZUgxZ({QyGe+l|yGyFyi1+*nQwo`SArMF82#gLOC}ky+#1I4p(GaBc z;=y=`8PS|sVUV8k8Av|B&aO}RqZdE3DQ&BHvb%N@^H4v}eUu@ys!j*C~ z{D2kBH+a#YIyex*7_Znm+5^(Y05r(_=ZHN~jT_)sU&zz|aHjeK8X`0|#-ZW4sJp|h z6RzS{2HF7$Z`8-vt@{Me3JG(u$%o*Uo<LXwF8P=S2s zAZ$8OW+6?45^{r;5@cS+=X8q_{z315i2CSO%}3(oJse7{Y!2_u;Y2@J6x29IL3Lzg z9?HKe;+N>2hak^kE$}>;Q;ZIo;gU_ue?WwPHE0M_z~L%F=!QcrJI;Z)-AZ_}d#xt) ztCgSKO(;R>w1JYf5}_FAqo4uj^b4f%=;+WsjUvA5$|iCmt3?%=os};-_(61-EHUej z7R2Vh6|{#6SrV`f*Z}jcM>Qj{x=rPf4|upMaq|NRDSx%LuK9=4_aS;$}hZdQwoIO!NsRhfk8=QHvJ?qDrUe(hQ(Hm$y|uwwIoUF1$d@r(Zh0DQ}JifJ@1v7%VZ* z{dQnL@Tac_W<>@@K?))6m~2n$#+j8(O71M7s;uf|1O9Z%6fq>;bgN=7$>V*%`g!~L zZSCK`|G|)$`KyNCIko)Njef#;Jc-1j>L?S!&hB#UZ?=Oxz;^GEzKjKghEnKuGxV{T5t87LNRg=R2Atyp2wO6&w2 zz-akNqKpt)-(=h*`_2iiJH@+0DPR#MZG^2+v5F`_vo;b(9uRYVYlL$Y35ksrJv=6b zT1?sq)q@J1Bj!UfSx~hb`{QlMWHjUj=C9Lhk(w&=Szta(31bRNHLoKhXRV_Q@kyjm zxJb2nI?M(`Zi(r{_2J3ekjD%AfGKL%o*9W_#{Bk<`SUAoM6f4A zP3dS6_Jl|b^SEf!87~<=Ob8kyKbcCDSaQAr0@XWm^>^f0==i*&LkX@_Q@53Hv?6AW z#}V%Gx9_?;A?J6Ys#U_#E1IL?naG`W;`}Lsa&gGD3&zkYXGCvoz-3F@4WG`O$v%S% z@Z0+_>W&o|V=c}H1eF($C6>ki;g9iFr7sQ!R5Zea+Emjv1kYRYTQj13g4)n=dH%yc zFu`Hlwpn!U-1$#WQ&t~7M)c{K@V|AwCEQah~K(;r#7ZfD*-=D&F~&{T#P2 zCWOxr4Y`O}dp^IgA>NFAhIp(P1fNtfD5mPkVU25=T~%3U=qA!<;D=*VVpo^ad$5MM?AX!bJvWx;w;m?Mzq&tOQxPfLLh#z&gCq>u ziO;38mWH0myq;9!9{p{0-;0mDw<`7>b7U(zObQ&p!%Ngm^QL4P26P=I@_kFwE`NX2 zBIc<0Vtq-yiu@A^={r#;%!k9U9EXtGGsa?cQ@0B_yVz%tP?ZU{y2>Q8x_E-)-}4Su zSGJ&C9*<9V75!rb=O|K`ssVFGFq~FbAHb6#xZcHe(sj zN&acoRxWx`nshFc+%ITI@fvnvMTl>4eJCXOCuA?M_x7^ebW&aZ?7uU`59@JiVfbGf zalzr?>&jm$sHgHuGQpS!0dDW;46;Fy!}x{;`4sUUoOw>1jIv@?R28!88a}A=Ch}&{ zZXvmab+k&fm}ihdqbh|(_DUN2vaE9jr!!c}ixuFH+%}VVR~b9-vH6e`e-VRN98zAj zQnS^=@wo?;UKXGJ3a_l`jNGobk|*L6Tr`}OZ?0aPxA0>OH< zM3d1Zn#AaK43oUd0N5Ty*RrHCxd6Zh(+Guh>hibI{XG(N)&{y?ATWno>H$O6-|4}ZFD>#PJ=*Y|F$*!|_)^1y zLq%$|IR;t&QeqZGz|TP{T~zf7MHyOrrsf;V^hJGD-V%3(vrtp)q#6LD$RS)Rk{Q+x zp__VmBsDQt^d`?>C8k&MS=?OJu39`>5G^z1GMqx{@kblKAl0nWcSIfMKOt+(i$D#Q z<_XtcZh$>v#W4He3!n8cXGk6<+vCoGF7c zXT=aCNRs?NX&GyAN0@w4B`ZZN_unb}53MmGAspG|8|0=*n%j1b{G|lkq~29t?6f2p zlVpTAmWXDvTgC~wQfnI{abq4Ik7M1PaTj114jwVt9AyGdY3>a^r!Of8opP6A8u)`4JgVVFF>a|#$Bd8UV_NC+Mnp&M!CEH?^;EIi-FldiEg5#(WC|M{u zr!YyM0x$r^D%V5R$zeXJf6bu(4lR~1#Na9HZ(<|k=6;Y$X3hqI@&ND=##)u%an?>> zvfRhHhvXxEl@NjmUItR4W~ksFyDt!oC7|SC%N}BIO~|C+5fgZqheo(> zM2a>v02No4f5ttbC3$too(v1`UKfVl=_8peBhY5%{lGP!-uINJiIFT7>YP4X05|hC zkc;NxvTipTiPz1RlmK~;>*7K5#^nW${qG=DwSaL2h$#kVxQ~hguy3l?NTr}uRu+5N z)8fYLO`jIGi!q+9QR!$-4_lKagJyym1-MyZA{8AdEsv`k#ze&sis#P?pY@Xg?_wpW zfrI=&j%`}Cj2u)G{3iqaBjprq zIg=K=*87$;Up_p~Qlh?wBAQG+u4Uj*+EMDH>H+ zus?d|D97Hr-7feAb#2f(MpaaxxV2j7HW?i#T8(5&bMVM2A0cS@;EvcMp#RffAJSMd zbAcmB6%WREm6+c6G_lIv<$RPGPw4!W>3Si4g+}=Gv7{rsSG&BvQ5%5m5HhoAreZ0E=gRkzt5l}VNh8SIQv`=*9h}9)# z9AR#y>zeX(o8P`$>_kGem(Kbak>gJA*mOinh44zMS_`1pOUiQ1hvD!c+6?dq+zEBj zm_N!luXY|qj#a@cqAGja^&}3VDi*_VsCy3tgCS;|gq_d?TnGdBQe4F6Qbb7H1^Un% z$6==OYSmV#Hh!c$6E_+_1a>Fn{x^hNQd#DuML77+B9GEE{GFa913FFJ>EtXdRqN`F^HKI#QNu|x|r5SD0{kwQp?B5mP zzk5c{5nBxGTYI=x{A2!jy>%hy)~v2`IBbT!`Jy^C-{r(OMTX&0ufeviMl0qT zjrrxvjqCO5+xw-sSHnW4G{`sP_!3 zg*_wS%Osp)g@B?2pLM4aq^Su(iYG%e!xPL!%*di=@Gk%;sE1@chSt-mrreOF$B)w} z`79ps3E!Asfl?$NCZP#6gbOy7 z7cSwI*&(Lt{Jv%~N+eP=$rl7BjET&(jRw+difyI4y*7Ub%-$wcdl+0B+XfY}4fKx| zI)1nNZ5sWxzJ7*K2)MLKBE8kTp7^QbL>T;{VgtdFP3O%?uvKGmz;05)29$;&8$28j zD2n}nG6F|pFC1vHebuv>{1QeUftdKTFx**WjbmE|9GxVXiWtMAR!$uxGJ2(HH4%M> z>Yat{nOcTQFf|M~hm;T^V_IC3(&Rm?k-4V$YwG+q9CczV15rF-PLroazvr$T7ks7+ zF|g8&(ghXamPM=!Zw#@JB(o)`0-PrxVgkR;(>~oV^rP@ULPo386EjSOpBq%>^lOV(;0fR1W~AFzRHnO!aY? zk+XaNk6OI2L}W1YYN}V>W}sbg%=~$e1FIVlo8$DUbsivcA$isE9%v`WptLye(0pBa zozn)yVaTl{U%vbI^%y#Kr3?BEU}%{pjmrdX$q*n=77W-u^xFkGLdKxNcj2d!hJ%yJ(-{9kue%#ZKr}yJM{Xf?t z-C2?rTK4pCu7v2E>|idL7Xpol2g`pYU_UD z#|zWV#;e|P zU=H<1_${X&F5T_hKVNm9!Lh3n*EQ0Xfww6EJe>SywT$Ew(isHu;BS*<+=S`q`G-W4 z6KAbtQQ@hj$etJ*q?4=8`y`8`-@5-4(RQ0UH^z?BUe z0H39OY=k^31B@ntLh$R9DJF-oRS8$GXu2Oa&j~Tew2aN&I+^!VmFUJ&2Lfs{KM;=I zl&qZ|iw6e=wU?#_mNYpCxxtK8UEMj%HYUnqDevn%%L0ar}_%QoJ!RgB%^9HrFdOxTV3hM0uq2M zvRlU8z0XmEx(DIaUSRcIxJ>vOIbmv78JWWdixenFRyDxD`yu8=4LwVG&yLneT6|_! z5B&h$rc}MYqlpYC-|`;aUThDARd8RXv4OEv$0&#h5+CtJE7I@4W>wY6=Df&DUv$Ev zQ%E;$$(NTc8!hM?9nSknmk7X=Iv09T^yeKqDoS=;;G-ax$#aeva?dq|Nb^2w@Py1O;fUJm`>tr zYiW0KX@d5dn6>!fm=J*I$`@>dcAt>IgnQQ9Jk^`a7mxM)c=+&><8$N??mobHzU>EI zxrwN^Bz1fOcK_sHAQ`^p)3JtpVpcz-9t)#Je?vW#8N<>O!bmwa{D4bfJB%gvP6Z`8 zIb=0;hYEp8V}3!J{#Jz0IF(iWM4Gh`5GE^q+3-V1OSV#K-QSt{q!hvX++_s@(u+00&EyPenz?mYuJXE9Zjx8+j8wd1wg5tXX^1IdA$nZq$BSOkeP z@n0g6D^sP2tqHUFCw;3Nuz>2;C6)c9d6`1$GeiAf*89N3dVllHy8eR(9eNjF)7Od_ zV@fTC&s=h5jBi;kqfb$J<7$-n$}wbMtQ1$A*Y1Y;CEnu`$Y4nL5vD`-l^-u3){S=P zMI22QiL77y`dF8TMzG^gc6D7mOwvE2(`A#Xc)`JajT$>|%dv%K&ybENrk6?o3c0P4 z9ErPo*(&M)3y=P>nwmx=Mo`8ttinkzr;r$NN^{YB=#Kt1 zNfbJ$Fb(<=|NPz*h`?em2@t`%6q7jWdGdy+iL+;|EiX-zOy!eEr;NyG6t-E6W8Jb0XMVhX6YN&_`Z}_Ui56?GqKe&cdj5YM~BTQT|?P6=43B=;bNuhDA?RE!NeoQ0l$v* zRfV#DZ!a{F^jz?&-l60?_+OgJ=HP0$u_MNX?&)g%t`-2M1J4x%7Q$|v$d##3_G`6=Uc`~;F>-eB4hLrio*iR+zKYBeyZ3x1h87iD1eUmtNN=OC^Zm^QJ zot$V`Gt44yR+=YyAk{jjycL!XV9vU9upQ)&d4L3bNo%M3egD!KIzV`6VE_K-rJc+{ zZ|xMz(RSUa8-OxhF6A}OYV7QD=po&xTlO~$1g+|zKzHYBd1PL<_=5a4Xh7-r(m#uv zk_YlF?4y924mW~y^n+q8j{@C;ztmp-KFB#<#*kQv)IedYKF=Ef1KG>b>&e#6kPXUi z$)l0FmjR2Qsh3iD!O+~Je2%z9eWe}H=|F0Y3Eqd#fwuG6VYo23tEFqyPy}+-y6{EK zJA08_^phgV%_F|3T^lPASjO6ej|7ZR>}UBW)CMV}fU z^b4uD!gY*M50teq9#qL{sd$bEWDad|PN6KIfm=_HL0ZbKbD?i}BK(FMVaRdzk0;F( zy^^oxe^riElwg4)DByz>8xyn?lnC#O7oa)_;lZ~}bUNpZVF#(dwBW-qL_pytzp9cP z&B{FgT{?vLltMmfEPEfX?dS{^!l_d*xl?A!umL=|nry3{REZ3jAd<`~=Kk@GqaM{L ze9(p>J5I`Fd9XatL6_By6q3^c$@wF@@!3Ncuz*o|UKY>XNS&it7)I6@NX;l~2x5v< zl;l^AY>@Xx9?f|Zq_K=BWM}|~mKnLG0^ZO}p~v1Jwg}Pgx1DqxC_FZ07{;}gmvN6 zXR>#~@#ayLnvV|q+h_g3CEjAm*_ZY6nSc9!a~T-=&v#VU z;@q9GI39TPde#wv$ptdDVFohoqzRZ-RN0>b6jk*D@v+hnnfAQl45xtY*e>n6G^bK4 z^t?5tBc$J}%f>)bV?o z1uYEjRbYE*#D7>DmO)}2fvucymFQ9hG^AA~iQ31;HMxyfikVHY#dQ8a3j$V!#|sk$ z&!{0>P6o?o%0zNwDI+`CRbsdN`n}x^6TZeK;*d!9NEh(qY>{?9!&q=kA*B*!Uer#o z&?`b%WAyv$LTr;g;00AD2~*h>-7P)7^gI+t;-Dzq9Xy<{o&N7t@+yblU1GeiMrM5K z4Ay1elq+XnUTLdgs`WqLS!i@Iz<1Syr78b1Bc%K#iXDLD9f8W**bEoJ^@jGRe!5md zyBDt9OAm1nv({pG-VXRClcREG2m+}(5Gx0Y1m;Z;OnRe{$CaKQKhW}k)grwsU8L8J zNe0WCIRs=%Xfq|Exg|mvg{|<$M2dOD)J{bSTr1&AEh6} znVeHzWvtC171qANM4OX*i&DvqnZYH#p+3Kc_EQ>3?<0k3X=6cp9{-ElKvjiABPtk^ zS{VEc2a(Fg6%u(+GFjVW;NA1zOPt5izAU4c6R$c9iAJ^kti4~+&kvohp3B^Wn969rJ zsub!Pt!V?#1EeEK~vUAHGs$Gb9O;3kcOhoO8~%)aF+UJHK_Ru z2``x|lq?OkR>PRXhKXUv$BdPtsmL0ECC9~dpGI?nbIPj>u6k>_!C4m>(8 ztWSPuzx(Gz>Fj;{`$mj3=aWWYlyzX1*kKjBsB&Y)lu_iEEAcedMS7aTr46vgq}0^- zmrSf0kEaYGXC5`+JJcxfIWZQNL+$rAha>g$H1EB_BBUNsGr(d#ON4uKxxv}xzk0-j z_}%@qZ#b~fTx2z)dFT*E6|3O~Sm&jikrcoSvg7D1QHN|(BV&MPNOv98re1PRE$tDX z`fCI{s+7SJv+f7h_-k`>TfZma{^%-6+~pgD-EiU9y%SvrHVS3Ly&^`WMpsQswz^`O zIm6Llf%dxdI_I9!+aOF7cofSkuqn>C-9eTOTM&3yn%9N+7&$>}x_1^Vi4X_suJ$pO zX|9}WHMF@quMWBv0Z({I!LEeUSO#X4eJzv#g~&gOiBtpPDEHqMcmSlyIXmuzKD|&C zNpj$G=l+%2FuTX_kj*|dRizel=kl|++%dJVgqXLQNj&TeQo`Q|B9~%E#dLDCWWX{m zgz@bUjWQiqS~$g8n9co^ffz8=ia>Q~g7J}1;N&;UH}K)O4pk8%y&$2o>1c~%i-Nfc ziV;%Bb?HB)a86HXP7gMfSO0;D&~OP#u%tF#fy)6CI%_SLg1GmgSP43I76}4n(ymUb z>x@H+pA2*6XYBdh$XkEqtd`qUzxgV^sx&WsUqr;McNUu}QMdzW28a&0>Eli;tQsCb zS?Lo#FZWxi(_uv=CFADc-O$k4ec<&<5e`ut zh~?$)Pjn@8;@;&!zd>_dv|X?E6Lx+tX>u%v+H5I|P(X)-dy*8vVb|i z3yKPuPgQsj_@*k*_k$HM!5qXrGQp;5Fhj?0EH`Yiu}$$eJ6(5<65=x$%gi1qfdXfT zZmb(0ZuYv{c}Mu3BDa1rNVpaQfPs<-Xs=L4qXvz1`zeFx;j#8asm+%12nE=oOpucu zU=J>HX~O1lKLBlNz+(qBN19qt4~evxE6gtOGrECZxBhr}|L0#`H9?c+811j=+jPu_ z&5bED{Wto&dh}OT_IEo$dn?oIBKH0BnkXzXV^XHOKlp+pOZ%N5D>79~(g@{$K_Onr zBasH88SI9~$?bD+2i&s`2=d`?L}F4j*(`^aB-Bs%G1Pp=_dpQM3J%*vKx|B7`YQH&Se9J$AB@(4ICAbu3LK9 z8Ev!=`(AK7fh%@vb=|0UWac&1nym*S?l{MmJQx0sOPB~!2Fj3mWBgN5Cbaj9dlwh& zG3i8=*ijh_AcNd!peFo>16j4Xoc26!u$0Ue+~_B2Ue?3ua#Gj$ij2dhaq+x$K&3{Z zkLpanlI$s(&SUrgg{p00Bjr;8r30A(8wZ9&y3s_s^Z1IvmL~yAnF|=8M`lmWfaeI(Y>E$7Rnh(OMC}Ts zS@uNdh}A$`c)Vif5a<}6vBlX_X1eW%%=-%wU)huhPh|Z&cRqQ-MFjm;ZRQB`755`g z0**fa3GSFD*~4mFzI(hm^<11vvbnQcbIgFmBh)n@C#&nE2_0AU{rT>mUuipZBbM^` zVXH-P|G2FVxM1aY$G193136*nJ%$#5oFG+}ieORIV7PFyb=sJxZq$zPTC+%O;MwV{ zhSpvrgXWi{+Tw^w#W)?f@7DssIS|*ANL@kS#|tfH?Eiub-M;3{aBWdw&1x~-7Jr(u zv>DTmrn(?Zd6-kA_<#xi9Lx?pcNh(C44G_pSb9I>+nNkLXkMWF6}2Uh;t-Xg80sz@IH%Ac04X2r5)Ys;R^tO;gPUG;|D8Q6oj;2WLX zg1{P?DOXdygxH)bOjTcrpnW(+B}utcXqh&m#WsmdXUWW(jk|Z%aom0Y6ssV=bS+>7 zh$N(a8Kaq(jQDw7FVxMSjBsfWpin=TY|2|+M#16;<^ z&B5;4PTy&wNW~X;rVb_5$Z&F(XsFQy(b$9z1g}y4T;txv#iiRIy`XJ@k=Oe_>7R79 z&~n-B4L7%*>e?&K_WQ>AU!R}V@we_@T{LL@+sJ#%GZSlu&FgpQ$M(&pb^rGG^phby zy(hIB=W=L7r-u4het~8HNEjP`_avqv|EPFv>P2$CicG^e=;%Vaps}^dXKJ7Zm!2W#iYbU(PRU{Ai|RUvu@d=N;I&b6m!tAdvZ-EerZWffOv4wwmj-FZn+0sD0Y~{IF)CYv(70 zyiBC7O$0o(2V0IN$O1MYdr{Px8I)|R_{^aW6(^+b?)5ayIPzVlZQ|6}fbHWiTP=1S zFhcsgRjjNkjw2dVz+KqaRNtcb%PU*cF};n>v|y7ZU^*pn<0e6#3XFB56CaDNDY2K+iaGWdZAuf2L{TvaIKaHBg8@$0x`OP zFkL;t$72xu@faM&vj6X_0Sd70E`BfVwXa|I^;ozNP>wmM&KdmXmpa-PFM4{+o!dY% zrY-HAe&2ec`;pmREr3n!D?cg8jEved8j}e=hgEKyH>6LS*vX$I{vGmHXGo2s_l)MA z_*VO9&n|LRAZyH6NExgFJ;T{U6YubmxEY>exE<uo4CzraUZxpCKs|0K z9VTxqguCA)oe~#n^X=?sPFF9~hV*3O(66S-NpT(U!cQkQv;B;jWDt=kt z#5_4USrZY_?k^_s#%MlR!0&ak#s&ss6zF*zXb0iC#7~}MY7gE9{5U@t)r05<-%}*7?PyUR1Ovg=RG`4W(!8VO1Mz39vjZ#)iv6Mj$dG? zy?lFpH44KTpJwiR)YxWf_#B1a(6@M6@R`P;?|JKpQx+q{5(2edR@F+(8s$W)6gMB< zP5O?1O&40*NN9@BKI7Zus5BRy047R?sXvq@yQ>RYF;u)qE1{SVw zGWgu{7cXY5Sg}GSc7fzI|lhe#Bzs=bMbPsp@((7YYg#4;PvGQawlFI9}b7 zXNAJS@Z}1@`y?`5d%eDgimm9kjlnQdn1WwOh84nB6s&+&!4!tpKYGuh&)(QBQQJxC z?PrZRB^`)U_;#c6-ee;4?YGXqB61dFW@yy%KsEfkV`i>xa_bf^zkQ{{r;C$E7C=Kk+0lwJax_d*)6o0^V@>> za1fLVY&0Aj(|BYPb;K;aTGwx98rZJ@=>iku~Qn zJ5U-R`Mt@L$&(i(n?*)5#U=6N2JmFnV>8-dY1ms5Xa_O}B?&<{T^bkDybOBOhMXLbRW^Bm+2jxMa0C ztxGqTzqtM+Ha>M|j~*iZM~yw<$Fi;Eck)2+yd81%og}BuojGPD36W09eS9AuuS@O! z1yfB+ee>`$wF03Ib>xz0=UGMtM|D}{;JkCLUr*SSG&lV~Cg)iA0=4UmcW5)i?!N%1q@o_JxA5CW^V zQW=h$w8pr{P7WEV;s_v@vrb!bRWC?gaS-JLJS@Y^8L4@LccZ?mrm{1qm8d?o;#GeF zrg5W2NtiXW8WlxWu=;gBiBy)$0E*X(As9JkRnO<@@We+m@zG3tU?!G5`?-g9VA0w2 zr&pKu%)i*g=F-`cpQmLB;9ij{_)>y%tfe+_Va|{GZ^I7c?TNd|((Z^nKo%WCnb-%6 zee}+u?Fvr8Z}vO9El8sk?a2bn>0@iq`6#g+%m_G5YzfPj)K5jSY57vf>bV2 zgy2vIo31Lj&=_3+q{80fTyQg+M<5m>b$s8=jIDg}^7~5pMz9Mo`25$w0X-rKvKm#isI$U0*Pr!qoHLnTp|<*lH!2v2LfKTkXi-r zmWih}P5pX^ck;xa7Rl_2B)XEFuu9oOvA;jH$p8L_b*E31PRZIelXNns7Ezp%lAPEgzXq&v`TbHc$U!!bdMPj! zH!aD$=xlN0{1^92(ZHbf6QMc#Z0b^UQ`u!6ign7>K8Nc>=$3`J`mLe0u z#s-i;9S(Z<`DckUJa>&$@g@NT@R{&(D)!s7>nI9oe0sd=ATt%q zB-tx`BEH6a2xExwJy#=QOR85)FA>?4oZ5bRN%07R+`ei4X+k&(J zyRovjDR|l7)X*+KNe^1DfTHU53qDa;_ufl`|sH)$Crf*^p z!qFeSpS8-5Sb6{a-IUda#Seno&$>@%vkE|y7Y!UZP}87^iK|0&v;5nM3#V^->o1zu zm7EpAeOJdyvORlJ*6lV6+-hYPjvs-u5FQO5#u5;bpBUOvN_5S~jxk~1@U{`mH%}9=G|jt4#hgg zbxb5Ld7-JI?bN*oc6GM{fYAoBY%Sgvx2~$PuM1nJAXKI-OK@xl5}62?PP85jK%vZg zTjvms$Xa1z*bQtGQP$5rCcgQ@$m{2?&ri>-=vHvuOzNyMZ3(r?%NJ&?OkK|!`xmXI zr?KQ>`)^t^81Bf!pW~)!R??Z^RZ8A52+<&)XL01uBnUrI`24x6iS?>AtwS9&3v-Sy zYyB2PF+_n>9#SElZEPt-m^?);gvlSXyqRQIy=P6pWVc(4-J8is${ae?_c>p_hy zXdeSgHa+lAGm?vHL;#A_Q}7>c2OQ zm6cDVm+5gs#Xpz7+$Ic1_9Oyi$X_=pTHQ}O9?ucplJE8HE9q)VT3Qa$`h`)0JCYWC zNoQjvM@yMBtns)J(#9#PNNu{csFe*6CL>$YA1m5o47=O0vS_JA2pLsA;Anr7D1-px zj?ZfBFau#hNN9U1a3viveHh5frAhX_eNj}6pxaSU6gZrfAyGh<21LbP47VA|%iS3L zaS1*y!N*hZ@gjV*1^>kyPEQMHCrp!1@r`~K@n>H(jLBW3^9X^jP$`%NQ*B<9Mo+$F z8@CsE^mPC0U%$M^iz$P$iUNWFCse!O14o%1JU(+c&g_mo?{I9&k=X<6G0!KYUfRkb z?t6G(NRR#l7Q2EjsKV$q(P;ctN{#e{niWo;{DmuhP(%*^p{9Thsig!9tC>E=w15*7 zE3L1nApo$?B`dkHUe?1eAt>Ng{xC>9yA{t5$uR#@YEdXy%&^&3@U5ZrIxm%;bQlHN zGk%cfY9$axlgNwyS=q_sAZVICH@eAyoSNb>|m1wO5b3UHF}ZK`_|T0Fa}nfSwBM1la=NKc)69nb+y zOEL#bvaQFaoYt32_OhySZ%JF_-{_Zj>Fld(-83EvBNSq+xq3J)5SF~8xlLzl|iK~KFwLSo`ZRkIv@ zOV6tYfsX%*ot_sSl+rNzFMe;zXLIrW)VoT=r|$LSj?73I*%9gfzLa9aD^hPhryd)8 zsH67Q{5mb_N}glq!3%#INfyv{4Yb<)U;@Pb(G(3j9Uwg@%EhcF@@$06h|-zB*QymI z_K1SC15S~Yq=>bELU-Se)5~hSUg3)4a zZRwN9^2#`P+~u(@s4RJwqq2DY^88v9U+Wg{y5~kG$>tra>WqX!SCX8c6<1djxl2e@ zT~(n84Q1COWfzC%ti;2Fk#`2;UbW6%L*hoDoh~%On#;>UNO9$2}6Qq ze(hut_M=e$?`y!$h_+b`qa$JGE}>aF*h0iA^#C%eP7$xrn2#dlq;lex9ubQ|_N*;H zeuPw~uxAoq{$tM9)23bYt9+TRIq#|>sJ;D`=sZZ8zs&I+JGiWFaoLkdch9`k(9;-; z{F@O2->t>;VB~K~D)M9;1b}^4+A!i=m4e_)5uKK@+ux2PkpORn4Ih%V0lZk%! zUeUGolT@8ZPf8`NgazvH@gC@?8^T0mEU?U~=a@*%lTsyW9+<1VkVlcJuk z|NYddO=VQMTdY5?$d!Ie?&};#V1>;2jr4of`)g77zG(u;vh>_jS* z1CFL(UCIA<;GS^kS;#_ZV^YnkkT~}oIBdCFN2>fd-AIxrhV@(U!!EZm6u!W(QRPC% z<*2OEv~3u1>VrnZf8kYHj*-j&n);kJx?ye&>Cr<-tm8B6h>%mubRotrx5bMa6~9bx z?_Pg>ErAZ6_s_od9~_BDx4~tPT4mc9+Uzv){2dxNZ^z-^cy zHg9Hia{^w)yz_;~aTE#dkrPaJX9!tI>{sOe%A+j?lQjGy5uoR@28%~@luBbO(#HS{ zil0Wn+H>Yv$UD;7#F3HdR=gHxB%yWqFcC>q`3W{2R58o!;y$V(h^B!SZ)Dy`QDA#) zIDfK7z|N~)ru)J;VW=aL1fUTxdhOJyQQNJD&q&?`BJwZZt58MHi??sL$)}I!iawm7 zAcwO)Z{0_s%n9~;b+xl5!=}dAtK#)yWhqv^xclHO!g@4blv?s?Ot3)WRVzEfg|W>J zb@-wxqgJ#HkD1sjQm6^?a(U^GZuG@o4j+#F;BWY>QWHi}TuBaM2?bPP^O*1Q<7O5z zN!pz_GD_Bi40s`_Leni$EP&-#w6A~)mreasHd8Yg4YHuUy1P?e&0tJ81Q4TDrs7j3 zTD6m=XeMYta|KCm<)pA#XI3-<5S8cmQ=nq{ro_ZP+H^V+TkF?Xz`>{Zt@pU2<_JI9 zG1fdXu)2T#>%u#l3Up^)+&z8qEx@{Z=nc9&X*Gi|Kx|$>*(g{!m6zg6)nGo8K*%Gr z2McQnPE6Z&b?;)#CE0wc01}uvV~)KAabz^Q*|JxwP2a!vDL5N)dDnQqdg6! z-Ng}D0qI-Vm-BMNV`6iWIpjJi!bk8$3WIW+xg*uh6VssM9F=ous5~3#7MqVH>bf$&DonU!2xoxNxC{#wVlRM*1Z1^t_7m-#U_{jKLN9 z#aBB=x(=?X^wJjINwO#{%1fuZF6bTB8ub!IrhuB&*m*Xx>eVg?GPayluYt5~IY$Ya z9DkKY${CXM3TVHh-yMyb2|%;bDtSDo4R{FzLTCaKG^yYf{l8wIY8Dl)bBV~(D}ftc z3mW`JYzL{L;5sQTSm4EKhOd4}J*jjzQxtPzn14+LW)q`Aes#e?yvu{-VoIWiONE9p znP>nX6j#CRJ_3#Q=IJ{J=+~r>4ZaTz*ebr490X(^MF1;cam-v#>1r zDW&)SWUr~E#5&tlH$$y88a4=77~X*+57)*qmdabU5>kofaU%VUPvcsIchh7ptJ6XZ zC~W$~Ub}(9fNyhv1m#Tl-_M7za_GsOcz6tysrB;yFHkRlS5^MmomaH5n&f;a2O zZtT`^(!TxmXcUkI>LTaBd>Dt458EcWi*t1>#Tmo#ZV_>#a2$!X-@*emYW zrzoZLokKbD2o0ngoeH)E4nqP|L^4s2;Y#8cNJUabC2x%F16(=Ca3gbJsL{uH+%SLn z-E)^LFo*tCeZn0(v2tX7^?`#_uaN84RsOD@C!Bh{RzwbU+M+^|Cr^^2RQQsV)?J_oe+P;ri&I7ev6H!aDnPNd1C-VZfm?6UPE8Pjyx&j>EEuO zxiSVb14nnng^DX7;sA|_@E(u@WrPd?lp=4&sbMwC_TgI<9tT1Y+fm^g09GNso%-YW z@t2=EuR&0P8$y3E=)w+o!-v4CdmbJZvCq{brKRJ-ni^fnx2l(BBx6ctul(niYc77* zl}y|yRc%`p&|az_;!|F?$rsVR=_$S~6HcFg=W2g{U?(o;IrKITc#GKG()+8oy|W%8 zQB=ZrRlgaI?C(~9k4Q!>%)tmyzre*-Zww+`Ays2QD?2_thGNQb*D>jh9Mx>doS74@ z&L+&{Vo=XfR}Oi z+~LMACTx+o;ONA0yyoq=b@Ppv*U^$^q-yR-tb4j>moKXg05@$p(LH8JVWHbk`L#Rc zo>_ZzvSc_X3udUPnweErta(vd-NeJ%p-1Vu9721(NfC;PN{>FFpw?o+L7ta`L<@tro0kmA$~Nas6g7Q-$Y=XczO% zs?98+L`&RMFhQaeIN^#z$EAmfaPS+NAHLr{!xxwe@p!f5@Tn{XC0CfLsf~DaW?F#= z8}jk#NLwlsHTFbp-*CjTjSeX7Zk_eK`-0VN0YF%?XRQjJJM#;~KA6aR2sL36RUVJr z&;C;B-?DMJpslHrk>41-0#kOUO)cUwCAJ^sMdHHycaBv^( z%m3SUH`=^+@AW=qk9S%4?04TEP(EJ^Q6WzHOdy3Ijl^fiygc8az3bre-@elR1VjJR zp-~zMNtOSiuB!iA$7I!9S`^0HQg#ztdf#r)Yomf!bFxl*IUdT98q+SgasXn&)rkxG zK$6f=^{SVLK|c#Z1Ei-+@ZQi4N@E6*46Qzl4TV%;1g6jxiNPQgFE0^WiVrev7&HFJ z0T4-22=O%t#aez-fA=*!69eUR$pU2A2suIVbfdgkhC74w?U=0(lp*TumnZax5L%jR zi3_FVpYoL{P9Y)28z~nGyP(zT=hN(d<&jUAPx;fs7KG;C0$iR}FDty=N4xPeL(P>2 z?fur2{T98=d=%{Ts9inX23I`qt(J%KqWmt8d~Ob+c;r7S=~ft7kLr`C`>UIj{L4M| z`Yxv@H!Z?J95ImAL{U;sJmE+-RUqU8u3y(U-Myq=U+D2wlFR_v$k0AeNAKS76*zM% zyew;(ZOTgDxN#6V2*Rg}fI60ld_p$$XJi@pP4n5N2?@~F?}&0Vt*9bhA^D+S)2ZEo z@eH3R0~J8^%=Y9bz?tk>rjVJ0_{TQF74+!&jR9z`f5YCrduwQgZ@uG5|9sMcSH6#n za}vx_kxmVvxna-gu5H@;{a!0SdfVU&-Fx(K>pA1hhBDzgWqGKw(M134N&ixkWM93c ztI4!wR+GTZzD0ww+_xRFg8$lIa(L@nPP$&G}y9}bqKrf2*OL3Ou=_!l?gC$L15Lyk;O-bCUB+LL;~AOGP5 z@&BlcR;zV&2`HQ6xc`ZUR9KsYQ>PpNkl`=dQ%ZMn`j1Q3&iDKMd;`rkf-AWxrcR>>_-x`7j<)$#B8W_ZD?3H#%bpsiFDfWTo#K!`%>w1+No) zJF*_Pj2a}PC>VY>@Lf!=fih_GZRQnyB$zyiFIrRrj6v5hTqF9xx5EvTFteH5T@e&< z%r}K?z-Tdjr zrW4|G{`4rc+O29-0yX4x1wo zl;I>6Z8+wIUA*o`hS=7#f6^h`U*9EdOOM}9s@an845 z#Iv7CqnFWvuy*LEVi>p!miGMQ^C1z|Tgo4PCN^w=(2sR;Y z+nz|Va!BwU6P<2ZG(xuEaj2gYppuWhg+MN$H5TP`e?m5lo=Oyz44}vnRuDpf?4l(G zlE{;v=#A?tIp=cHgd8+i0kXmNK`_DN}#H0{cgI?+BEXCi?p500T z$vKrRL^h{oan^*f%Afq?)V6Vu#$4Z`#5dx-%lJ>a>I|KlSc}CYEokD`l=cmtuUhsQ zwv9wwk)-)X2mzIV)PZB_@`jmmM8sr`5rJG443iiyiq3x`uFrC4_5!w1EgI#OUy3aH zOCu>%uoR}GR2Xm1#_T%XH%k9AC5%J>RowrwfaQd8NZUqyEQ9K^uI`96OC18*IofKD zWbB@I7kwWDN`)f96U!*{67Q@^YoV&PSzpk(52?WH+qbWI`@l*IkqEeKU6M6HK*o{f zS(`3?(MjLVF~#M{dmJIXmiAe7nur^u_OwC!VYGK~1=K#j>IGQhSW@*Nry45nmGKU< zhzozn5fML@Da|V`B1{LTQ4w3_!zTw)B(`B2)uPErI?+qFcA$cXr`su614#b8qD)BZ zqcH!I;zdyp1^pHJkOg!T7eVXu6KZ%~btnii6VG+jzO^I@28N&l1HpfE61CmHGnpCg z->#3%HsQCw+O|}ODL$Hl3;)-ff+Z6S>@|`P6rK>3>BC|MYkU>|MN6MB)~ZsSn8ahw zoO;D{y3IP#-Mx*D3VSoTilSt471h?4$8#Uv3ny|ze%a}IubBVTLF*;i8G&J#t|uAMI50!AsGieBj8%BZgcz!Jo)ixd^{Te-yMw|&W)D5s|EOIK|lfjKeQl4 z+rz_KYQdKZU03@IDtoj;l?)u8nHHwv@q+AQnz{RZ>PxCv^}?(_zh?eh?q*;|y>V&f zEv0QFVxaVn6Cz|!ifLZgv11t7Gm>9~zn`BosH+*%0N_YEg;i2@s@J8=K&glZYew$Z z!EgTF?H>~X>xb8ayyjn<2(X;Dfv_L;#)Wz#$RE`(86N~u1&6huCH16ad7Ky>1cxS= z4Loiiq*<02wk5b3c$^prdg}*waBIo+nNef*4_mzWAT-9}CDGO8Q)0*q|6}FVYu6g) zAB#_oQf~F^e!o{Ev^Vzlv&e~`nLho@@=sObymyB6dXd`@ZG4Ic&RM>)equsbXapT_ zGpibwM7KY9GeX!H;Cz888Kaoy>3lw72zZ31t_^EDDv($Bf^JAi71i4Wks^X3E$tmU zW{BsplpcBrDIx>a&}})#w6HJVon%V#4L_|?%&I{yN7~EapAv)7Fd%;^ijf6(`-7kH zWQkFB+8?K;54$V_l!3zcdIHE{%E}*`IT4D`>rWc}EiujCJ$L_kK^ijQLhEYo`VL);p{y=GU}O2MZR4KYl%9F2hP}g& z)U72+Qz*%7$#vP)#$*~TVHrgk8_WjwSjPi z5Rk0=b0c88l>F@I>@;nfPzxo}f!g5O5(5l~t$9g@HKDFP;$6uScZq1QP z7JNQJC@CY=totPMsf?Qf_%N(3hD$&z7v!Q;!q_*PKvQQ5d1qlbA!(OxR*meYqj!%? z4&}SBbUAj!*h&yjx@AdIbA6DMh?D&knI3M&fR&92A3#uXzcuZB{AUX%i=hOvOdd!I zAs&G}mP#-pF*FWd{u*1jIBRCjH3g}9Iy*a`7sXCR;6~=`7&~~26ouqVIk)h($6Zo_ zeK-QDo`_KxhSXna9CXxN>V$QllpLFRFyN1`c>!pJ>4Sl|t) zV&sE#N5WomeaQPymi_c-0ZU4~cz3`f~+pj10j6eua+ zpIZst{km^KeBhim6c73B`u_XxT~krqP+&6S8Fgfrs6A}qC8hm`M(r|}>MCoFfD&Lr zXg!4T5XY}Fyk4L{l-JdEO?h_wIGgfj+(BttwWfxtSNP|yrZ6j$C9N#wU=%~!6bsJw8rs;H)4SI6U6bz zoNyv5&W0tk>iHKwVoU?hml77h2Gf>Oe%WkX-ZT?;&y`azKRDTHI^<>T_Z%PPd=!tSJ0c|7p{vX(&rJ@p}2t-ZA;!mFcN|t<{_b=^NLg zMNfZVUl8lVT@J*Mr)75vIX|kYFTRqsEGXIdCqL zHG$zASr0p{+NiLzi?BTSB0TLenH$TXg#yF0A^uBzww}JX#XKZ1K$tF(ei&FJG6j6~ z@DSIJumEQot43+Q!W993o6}lI>+;u&X$IRLhyWjxbu0>3wDl@`HAmZa$=S8iR&Flh zERdPYjNNbU9~6SZu1@!U>C$%~Yw65YsHbHFNPdYtVC!vC5lLYY_3qFb1qMfRE^ghE zZVnsmGMwwntlYG1_)o;{es9WyL1sE{;>Hl6Fd{f^6S2A4dt2$Aix0w@(s!mtQ-MT* z999mB@xRPM#7s5u%#V3g@0n|vz)5EbdD{GgeJhsSR(fLC6>N25)MHvE> z6`(y|_w2UCM~m(NXWG)f>Om95VpN=$GG+lG(@%d>Z^E%`1;qZ?zxqXgE!n^j9~x#X z#N0?7YcUDsSprCM@7`7VfEZeHYCF9+Fi!s{-nSZo4*v&#%+HDs43$dm3Tb5n?>wHP z{mxr|LWI6Zr{XRIN+b;2bNP}aOd0B9EkOaO&7hu6A@HS(oaJVtiFY8bxuvb~V+VLC^I76thY5eCqA>bE4yq^u%RRO)?N zOVQr3De?h0SwC);wYk=?pkLa9F3Pa1Doo5V@74|30xC%tB({+YwdcMUEk!;whgeVx zZ>2iPWWutE{~u1;w`I?&3-A2-S}}P=?kv!|y0{+Q=>e)Mq3VK3AF0AZ4xV(~ z5ojoUf*W>{RwJe8ut=xvMAdWZf>L#>GDI&LNFA@xonDWxwRDV|tUPt*>KCVbk&3^ z^L)v;2TK&=0ZS+iyja|G)JJPspy6NWP+aLog7>uQKDcHa4Sj9|NjrC0?D*Nj(Yx0$ zq+T$u5`So4%xgsfy!kL@192QuLL1hOVQRFJ{~>8E=0&RiEJa;c3NuK-+!roW4KjQy z_oGS&DO}jk&Bco;?YIX`SyvvDc*V*=c_fq>Bo%;WrundYXSeu1Dhd>lf4UMoM;4=ASzP%i10cX^f6rRA0dIcW~m<CxjfJ3`Ft{+yW+5N_mGt`$pRLZ zvNX@)g0Ffha45D)gGmQKE*4234uz^)fK_<~$QapiZh~VN2dX+4&cYW7AhrdxuvJLU zjHpByjm-md$OyblMtjVtItZXJ?#M92T#nAJ0cT;;cOJ2y%ZAb{aE?DcY{Z5#d{$v* zjPFnM6@j}=`D&PwW~I3B0(Y$Y_6N=3v1=q{4}O*Qcx$F3J7+r9ZOqk%s_auhNoZFf z(8q-fgVlELKmUA_%Z>pL=qvU1cGDr-29$leAxmbA7st9-IK_<%DNX^cC*U*!kv%IyLx0 znx#0H%KVoCPI_7Y`CV62!y^SdPmaS~_9aKOS2}ZAQAUvh1~M$K0F)$~a|2R8Q*u*R zSK{jzsUYa`U5Fh#G)iJo6@+?^_I4~xCFy<5D{EfFu@HSJJlB|iY{p%ttR+rZM{te{ z8#=H{+#ku1(S$*zirt5M+!=px1DV!P`JPjZ9Efj+!KU%cfm5cRA zGtqQEs2E8hQ$C-Hg6$rv!`l-(6+2*&Lgkf*EF%jH?#qbL$QY<`CLF^4Qa@F^&ye(cwIdZ$E{_6&BhS>^pMz zwBzfO)jG(Bp2eNJnQEo}@zzj-peKK|)PvteJ)L(mzy~#pzK^RuVtt#h!6G!rs(gtjrv1gH_}8ClCjz#&E7`AK{!T1rDPCaL^!MqRDxPTfSuzCWIbMk!|JP#`_NdC1m@ zmgI8FMZbg&G|9FriR(I;MBsh>`gM&m_63ts3FLFW+7J~lGSwA0Z1F{Ap;G7#kz{X6 z_c-r!2OoMFyZqB>kAovG{riZCjND`AG(^7Ap z3>Tpl$g!foKwk}M8l2{d5e)%<2Kl!JM&OflP^pn1TOQ#!YmsK@u-4OFV!78&(rE|$ zzA36{sEmc)+zOBgaVfaq)D_OUf}((S*RGI{id)hH$mBx-+}fQZ^l3ql)r6zq?3(8K*6zmI zG3##+a;bfL#~$04ZA5h8bMS2>chy@Zg8Q(&PF5jvLNWvdkaBfT2fO+6n(K_DfXf$+ zUv}@+6l`OXfrT_MNG>!J(00Gb?e7!ePcVciVOQ6!LO(^>FJ6X}bsFrr5M`l+76uj| zC*~wac(#;!oT|&1Owue&iu|;#!d5eBvv1s+Z{~jNg22O;K7!kMggXSs1&>dU3VG-8 zvONy%w+CJ6nq}g;RLCRB`xoHl;BS1q4hxlvU)i)t#s73pRL}V@%?q%R%|bnVd9bzn zvi9fWLfvaGJ?1$wEB&EiSYLM5O3zQIe5=^b;%Bw!v4kVa?CzS2Do)q6DY!VJTBE8w z6yAA}UXZpKEzc2G6Z=u@^Jgv<7PxcyB;64;zL%S8Lopp|xt;C_>CYezMp9ock5ZRb z5~1#*&JZO)a=shYuo@W}(Hd}KDvBo5NQoYy?Si-KgaH&`G_>>#QScAD!~!wI_Bq$P z4aHTc`^FSqQag?8(u;Pw;I~cWqkN{S?(?d8zfF!xhezQ9v{=w1+5Y+HEQ zEp2E2K=Yu^?zvJX`HK>VSOs_TI{v3BuPR>1s<3ji+xx?Q-;~_;1!xM09eh)v8x^dz zu9MPX(91S0tG^sRvT;cq3M~+=UUm)>DkzQ?|J34Vo%_!>99OUp_WUiuZAY%x^*Yu7 zBY5@>UWrmyMr`Grbg)N+fjHV_$P^F9+#6gfDo4=!5DRKjyxFjiy$d|R6=*4Dyv>ja zqg)5YO(DWE9BC(hCOBw`hcr>zMKn(MF)oEQ)^!tvP*JVa45VjJLH&AsRJf~Umv8qU zUzSs3Zf|ZNvOT&1$xELWhdhsv@{_R!W&ETuzEMfuPT@v>3>rC7|7fQZZx}}cQku@1 zw`c~xE#41)BK+8A0`;@>lGsm|)8SOtD@!o`HSsx{+sxi9kvZokQP<}$nbgIZS4nXV zzyFR*Sb2`hw#6h7;D$CZ#se)PLaGo@37vGy%6VK2$p|j%*XstY7Zw1)DM{*XywlTP ziBvL*HX+jaY#g&fRDm+eI!a7!G*slTa^jyy2tSvN9fT4*wxeM2-jyszPs7QJhzB#>B}F@?no&0Dtg--Yyk-|xt+ zEPmClpQJn3fxPvbRb|{Qc*`~jvNO=&%7d_%IcNNxc@pde5=h8%J5fWDPPmXJXySTr zGq$W7qXhe4N;DoXbTBx?Fgv#`K&t^Z#ksm%RYo1OW(%STS8OyyiM|yhwrr8IdM|N> z7=8y;D4wEn-m4AJI=}kSUjc^5$TcCRQF$2!13gg4*APl@~GS3_MNb=dvP5$OAv_RK%~0y95gl$+&c4)%_m+mSfiY#8^4Se8e?ID#4?7I;Grr z<|ETco?^Bdzt~T{sEUAXNPzS?t@QgGFWF_@GeWX(>6ET!9L(&+Pbv>+&cXWB$JA2q z`&HHg;}?IDQWYc)TB*CS=a~Ea7H>d`8hHa3TL^B_&%z-*L2s;W-GH_p|0u^bc0m95 zE2N1t#K^4i6tBkw+$Lq+3Cfu3PH1}DtM#kFWy`$!M0AQo}_%QS>V4#nZe`Ldp z)f8D+K$eDs>F4z2fO6H_pEo9%l(q~%`Qx5FzMa^Bq_%~~`q(Mkn#v`R-|i2CYpdSC z48~Q|!r&NM|2ipaYbvKi`>kg&DFU6kd0PZB`AaBy?g)knkNIUMawH`MbbB$eVyeO8 z4~)L~w{#bj7#l^|2akNlmQac0DBxK9r|M4YQ6Y#|ZN9CZ%kW;(& z+Gzzk*K69zd&ssv7b*jq!c-Yh6#cmVaZmpq=3h%H-T57Zh#$o-j)*i-YNSK3apS5& z_a#3QQ8l42N;NNXZ17+oIk`?F4<9^J>=VfGAXBk2Rid6$l3!b8>0p!ePU{m1)v9pf zm@)i^f@I->fFYqCV%-goO`D#t26wJX6eqGNiU7K!$D>tEMrXy+3r53-!pX^?Rix)y zhG2u&0^V%tZbl=g>T3N1;er%BzNE-vGR5O9m$`Qe8WUcjFVr%qX6Mjt{?dXv<;X-& zgx@}PI=0Ct5BtAvWsL-!Ia=pNOlKZcl1> zqd1YL9(Cdq+;GzI_2K%D3a+U&MGBR9U! z`Y9a1Q)HGZStFAo$9l=iURM!g1p`DbWh~BT3{)l20%M9^)rTR?*HC6umP~=~pO4Xh zEPAY%Vls9iP74(xH{fuk*39V@^>n#Aho(xWvF)LZU}U9d;f6=50{Gfp70iBW%ultb zv?H=*+Bt4qiYE&xjUn}; zqjffk@LAbUjkMcv5hEqEXY`!jc=E8B>)nT(9P zx}tK(Jt;IHWnMkCT*A-Tg*z#+{o_@OUDPA=eTRb2NSg>vO!)RhTw%vdIXqS;Lj_rl z)<4bNIi@mPu4v+`G< zC2jms*NaVc>u{fNTbE4TA&JHM!^5_A^?;u-Z z3h1XzdMwsH)P)`Tf!LGA=Du#hC-ybTpF&BP_i(GR6eL2|=owb-sFvLst4ZZ5W00|k z?#K+1{oX1;pWaPTy=A-_u`#dKy*jA!>(5{N>G7R@6j)$phAVq(48>q58i^<_&6&qg z$EA5w^CPAV6>r1c4uf>7>e`#R>CmVY$KM6fM}x&5fEYgp@)x-lL)aMrN(4XEK~wiq zfk8b6fN^l0K21oa=`qYLCQ#@ZNwbb(kPtg4*-++F%{hitLACg+sPNV&Le4^(7{s1W zj9UHb5KYxI1*-U4@)pKfpZaA|q*Se~-D14Ah7`7!M<9A@q7k5_p6AP=UZav_r%+)~ zims*2;1TkTveL7_QR-jQ*le}%`Q|~kUCZhPf#S-vz|sGIO%oCUtkJ-nl?BliXDEtSjVK+l0cr-d1Ihvq zEj1DpNew8JS|tS>*7o$&5GKf}dxjmEnapGcz@a)t3AhY|j}lI%fz<%O4$8KR1`rrv zL^#ZiO0%w0_{78W^P|VI*8?S-^Hl60PE7YQ`A6AxK?JAvuFMExP2p>w_x)7oR79OA z{j+vw?kV%0v#ZOBqYv>@xR*tYar~rU>o7<^sB`)mNuBfagoh17eW+&2Ms}PsWy|O6 z13N*tc#^UbB;{V6mYP@q3va&izoKJ9=j0i;rS&ne?9Z5vpB@SdgJf!t#0br}Nlk

dPr3r4mQ(oDhjAHRu9wP&!js_6uUpyw$|<38d8_q zUNN|3=U)0zdT=k4bLAqQIKMTxz*G;%c^Q}o-zb=ey=APF$2>1-6r`Kg)?ON{obxPW z^S<^}X{_8os0q`DXdpYD=E9eRr9c8P>>ZwGdrSx36X@v^H6}da0)dk~d6KB4C~zIv zwroaG+t!x`-5p#A)CLR!9B3MftCi&nQ+qFw>jvlmB$!eMsufeg7q_H~J+d0)XD``^ z@8|5QvjhZ7ZJfHC|BLC`kS)HQFv$opX&m;kXQAFyc7og}O2$iDRT9h42|m&+ZmueoEF%C6tT0lw;YuTp6ZO!xmi`)cJs9@gLs{8C-N( zwLocnqd;kI7DUti%aarw1mAQ&m)iOVz^Hshzqq!VMe5FjLN)(RIwML6l(p_RgEJ=- zm|3erp0w1D%F8n6W+m4hp>L(fHm{3ZvF`m=EU8lO<};mp>_^g-^% zQUQNB&w0DJeQ~k)xKl0gCUAL`+GSoUZ)$|0h=knBd>)~2qsMgF0v5{vw&(1JwF`Fd z+0BMe0hUcNTeVkuMWyei4yDtmE{`+m5t8!qaT$gDSd=Uoi%dtwp6x- z*?#|Pxd1m=qXYP{a9kxKW7)z5gkMGF-p|nI_j8>JgCyAd1@ib0AJI-wB2O&BPaf%aJ8hVH35x3s%7xi7eabneQZ z2Om{AOIMdxqa}lm!~X?wW5iC~pt$Wc_*oOMz7DD>tFTweZBgfX|eu2|x* zP+%(qD}N`|caas<+~JJ0^lMwYX=LjkWJ4{ZFvsmGXxzDjXO6$SGDr%Fg&TScBV8n; z<5SwpH_8I2qosfYJV~`JDp;OT)uKNb?(mh=uZYs@k6jASHXz7T)yQ^C4&m{B6+H z1bbPr;edx&@FvLR6;a-~?>kTekX_g~_@F6m39MB1b4vRn^izV5!;RFrSq6s(f0gGE z1rnTe)!gINK~C712@|rVT9?6(d+sm6^3Z4XI0FEuo;_?s{eyGponi;}bD5DE1Rdxs zv{(2=`4wB!!k17?sqp3z{;_+nrBk|lY&i}IB1Pw_OX~Y*%2Lw}?F%ki0F8j7aVo5x z#mZ4>khGKDqn#L^Mag=(oJ>cyn@9-oB`Sk~!obNA>O_}R-o zWUIUTzTd_aJrXMgZ%)_i>}?tRuwlVh1s@GHj+k;+<_*aXLW7~bmGKBgOu=OWCP<0k z0}?mk+u6kmQBl^BzuwmqA7Fqol|y;Az__Y(A5xlN({$^3^%QuuZ;cyg%UwleaAytw z@hQ4W<^>n(1r~V4IDx9hJ*hS~2=`@i!Q=i(R#BdKq#T~EO60^lCQ0gPKm8@`>NC*- zs!C5qRbyx_m0{<{3`e*hO>(vxV9p{BW(C?yJbpNfAWC!drDbotjhG!TQ$Aj+qP& zx(-foqu2q)6!1=J9vEj?jzZP@_&U?@YpyN5kBioi=~17`UL_2wE$uos<5j>YXQnpN z5xjE-d7Ar?XCX^%ZK4ZI@MV*NO(>Ef{8VExl2xc0gF_j$@{)LbdsRh9ixjt=+s4y{ z6UF0DgIV>oQ$`4dg_fW;AOuwl=_k`L*>Z6+!UEJr)L@IYiFIIQ1;7pNIMb^i+$Xu# z?#syboZ&wkQkC;mWgUt=0%RTd*<-`>iv^Bz`gO`VM}`5G!g4QI>t^X=`9%k)ifATb z_$_1Vd&i~`#DoAX9aYfdi#}JnPO=1lk{JWYdeFl^8;WH>(I2ys-(}Z(;+H!4`Dzvz zpo}66KeO&Y*8A+Fs5C`*QrC>LgTbhRyt$QX zAgP?y;B!`?3KX9;G9^#9GD@oPrFz;Mha?PQbIX7N4(57iw zfI9RY+%alIMz7|SZiGGRw`T&JRFuhb%|!zm-I9Df_NX4EhP*t=%KpN7O>y%Hi7W)s z_d~cw{|+-nk!K|eQgg}}8H@U~NIGBDLo8l^jG2f7in($DS`#+mOPGqL9z7bRmKQo_ zeC|T>+#;oKnA;Ek9e+y1+eOKrCM@XwnL4sBRmxz(SM+Z7jjD$QBjv2NjK&9@ZI^lo^<%_D;MBHUD*o(NbD@O7a%-T-X;R`P#6v0Yy|$ z`mjcAhj^1U@)*6EsM!q=q?lNWMb*JCmGAqktkwK5khb*?fDI2MH>R>GdohzEr7yS{ z<_Cb9LYaAO;91?BR+)!xF&_s1VN69gfRSdL?9Z^jsYu8GM75=n3V@wVar)GRQwEgy zlMIUiWNFDv1ZqoPLuP~=drjUjBPmvl1b=iKa3{=3wDMAy)8dwuaMX+c>c^9wd+1uW zHnokm?CcM+y9fy!cPeZUrc1$%O!_ogdqBU;_c{QCNz<%YgItA!M8dUkrXwWS*IpF6j);=PN0q1;Zzd5DohO@@M^?Mc^F za3->m)OYWh!)6KmB-p2z=!#19*TL$dz&EYQH|)m#WTrR!rP`oA$f*txqLwAVM7V@`La1qn&nuuNC+jjaZkZex z+3*z>{k&w8iQ5?{V;oEqs>=pSbt*yX5h5K+QS_=bv;@GUz5s|kU5n|(cDd8)jL3M? zxG;ApSSyGMqg3E%24n3+VgT5YIT-ICEy#$Se;Y!mdLDtmr;$`{t2>{dbJz;vq2to- z-Md%qQWU+#rhyx&D&hHaWKRdG!$R>nf|%EK$mWxhOswG4sPQ!O5@o`SjoR0WuHwAq z?G@eUHLGh`@yy(P{lxQce|qk*FEsParX1a&yrn*x3%@x2`rAFPs%?Jx>+x^z7Cn7X z(8jRA3XRc|^CopS89eXzQ|;<530+%$_w#U9apMJbwPn z@$;)1OZ5vneJazfYC56+quIMEio%*@LjgFk8T%{QsCaFZyWsv5HaKSY^z0M6u|cTr z;%`e5ko}4f&>DSFh!wPX^@aAQL=I+)5(5=wRx7p_*5xQ6ASA$0o@4=z2MU^XU=?TJ zqoXXIzCv7xEi+Z7vjq*r@>s&nw(>fnXb@&aL<;-VRuH1+c7)FLlMOY?PdnOV1U4;x zlf7%afzT~j0Rk!q%1EfcXS9%}SV`F_OLrA7GVJOkw5Y8MpPd#hvpKO-YUJ9mtx~-Z zp(JJ)>v!`JF$TO^6dsb=oj!DHx*YieS&iA;YraA5wC;9NsrN^EY5e#zT{jJufTFjy zc0av{;=C1kc6DdzJv%Qx75SE;A0Ms*t6;kQ6#mrXq+TzI@+wbf#8Pd8E$8(|SAx?? zBw+*bqoeI6SJvi`L$H>?e>>=bC>a29rXfzI3>2z8m3@wutA;j|U5d5zvUCiI!pJvNoG{=GTh1 z`*m7JdHzkBCWLE^s%u@+#-9~TK+LxylUBtmVGMD?X3}x~Dj%8u0fG?`2oDnaRuS2I!zPPO#wP7sTydS#L1tH<4;+o1j*& zPOv6pTYFfVd83AM9zgcku$k0FR-|{!Lq_Y0no616q(wV8;L0v@pWWeOH@*^lpBXPI zA^do-P8byJ-Z_j^EBn^de6NIk{6TADDZX8+S8H8tYFD*M9N)=L=6pwH=AZ5FamHO~ zXVAIIM)RbAfPg}u>j2K;JJT*o!>+GV=9kR`p%Ti$5iMLEUz4Uy?JoE?k{DrM$31S= zHag=~Ar{5=R|74vhCM53Assg^e;p`nx|#Wen4rPT;ZW(8mq_zRCf}yX z6SYXsG(;-inju%QK;(FZsC)u*aLW{5)OX7k>Im)t? zG`n(euvnMJw|ezzyd)!~>`4f>cK>7O#uRP;^q7!0t^n!m@AvTNQ(-QqkWGl#xc&65 zt4lB?Yw(_9pHm_nh&w{77_>naEva{Io7--4o0Q5zp>ix?;8V8-MGYI$^r^>Qp)9~{ z>y+J}o!AY+UuL;4p+2GvBW>j&e=On&kVhf(c1p%9ilsx`p==Y^7nDj7$Gn(*tMnL_ z7KuF!!>&Gv6}>k!)ZR@fZyX=#CIY4RTgCreXW0_>b{o#?WED@GnmkMb-sxtbuSfheKshULwfHOPYnNFfZ7jW0*R_U>&>B zW7rcDSznReSRwX51ROYucjhBW!EM{%*NT<9^Fmm?b8MHkCeg}vojl~v!Ig*$Jd6z< zZf-(SoVAKC4Juj$IKWg{R zwEpG%Bn2Fa$5*?k9`17QtQLQ0DdHR2!Gj0))-xaT$CaLdF0vGYnW#NaL7F%?Y4cu~ ziNDDG`>k-Q^_5D-rlq^_OA41p9{ZEM7B&PFTKuNokY6r2xe*njTRp__mMNc6H70UHm<^L7mbqt88( zb#(TMZI%4cl-Q#`U)u4pkJM#IY=&9Uf3PpjT%KuG@rMU; z0Wa9tlx+yhns~pVsJ#C1r}e~TE&l0oEufo-Or)px9ukMXcd*dlNL79+6uoCT%10I# z^A{F1K%nF78zCS_KPxjDapsiFwr*ezR{UgS?e`^G&;HNivZgg`D|3}&L z>@GYv)$R_r4Ht)$qd#gLd&TD6l+dB+&-9uJ9tXkdvq<}($6~XbbHlUYIyNJI5xSW? z^FKF{{a#q4g#E>qv~D5>crod%1Zt8uqgu@6E+vx;(#*+*h!>zcSZHD!@67xW76?Ny zgbbLVGE8mRjJiS&B!*PGrPGB2F-I7mZkEpL2I_V#FdJcO*O1iTV~Ff9X50DCoEaPc z>+s#)io!)0)~Rd8E?ubF2j+1sk9?pDRD*hHxkPSKJZ*{E+8x7Bh5M`uW*c4iB@&tM zWx@Neg z{Yh&vBleBTJQEn5ck9*zYqo+bg-r-CDSTM^vv6ps*6ev4-T;~AvSKbBg7lfn4Vs9A z?xhCF24OsXy=B?5H_aj6Re{aAaQo1s9VreA^7pU1F@a2p-Y|) z<5{%8{A&`Th+-^g*B*xW4*8DBP1~*Fk~FXeohoCf#>=YkK&i7R77@Del3kr4vIiRS zupy^1GHR#Y4&z2^3E2-k_g<5w%=ho#_i#rzzEURW*t|8Wddv%bl^!8h#|tsXM&ZOX zTXO`WXS zVZ6u403Wr&d3JA}JNi$pN9Jc6+)Nad$=&QC@#;BURJ=+KjZ5Q0$?4f$^cwh|N6TI` zF@y_=E*ygtq>6j3CT{VyTds6Sj1`<>d9sxJNJ)v|k#O9!HD$&GeHohov{TG>rB3!( z0rKM~H3aw)0x~y*YL+W_yJc|ok+wcbGzYuS^#3;;{Tq({4}~L}mi7DLoq!WnKdEtg z!~`4FT~YPH2O++QJl_aD441=7K#Z%1^(|k?{1n?nF-1d^21E(|bn_pv6 zqc=D{ifL?eEWB8J=-;%U+|ygj*2wupJdEka;!mAfF-D64gt1&4-~r1dCKBAmInvW2 z(v>3#{*sVu)1tXjtO3k0pi)N(4loz!Rgxa@8d;R{6yA{TNOV*QNTNXXUMD3%wvzyh z%1060r`KxArN@?o>`GtMPf>Qlu!u%(6gqUXBx=-75qqv_UM#O~6Yos2&8VQY@FBB*O|QzB0(MHtGWQLKYxqpe01VF;Nrf(*&S8t~%MA zmL7q-H4P{b&`>(Ciu^?cZ_)QrQDx0cp5M8gCqXURqOYKitX)XzBWh~|d1%oWiW=z- zF|WXeM7n`(APaqZ6Dy=dWw3a;FV(_1{9>|)>lgQ0{k3XG6otUquymp4CEa zX^Z7K6Is2K1@#|K=p{6H8}kIjkAq)m<4N?j5{I?Z#3;vU&Q)b!B0P5EP_>MEX^uOn`LUL9$pveUcQ22prz?WP3_Koo zmS{!GJ;VC)yvH`k?enKMSrOK8X}-sX&h}C->B4ye;^o3GG^#96I=3^a8~uRebjlI# znX0_&Q+abcmF|GFZy+Yx!(+1)|ND!h%HL6xDk zEjL0;CSKThog&b@T3gpI0S0@wY|aACLbzn%FNLqEwR< zmIqeZ)?B2&(92$jy;7f8{?^#VXRd~|0w~JfJ$9cjmGqYuzAaX?nd37STgXPPG#*3Z znC8f2CPoTt(MBU6Cwz)+qhQxZqalb8=;#{Jeu}qtALe{yc~AW>c0q0>l>B~?t+YX- z&sG=bFhU5tOTJO*2*PM1W}x>UCmLQ^twb%(BoRPoMOB(kU^QA%w#-pdjfkEE_|tB? zz)Yy#-81C#L=eJ|vV6YpdiKgwDgjz9h|)YK00g@yelzYSoHDnRuGU0UVN=Q*sV7u?)wuTfxTFZ8bPhk$2TI?Ht?Yl0|KWHb>CQc(Rl z;lwribLxvDt9gbW9Rqqy>_A)TQbjl`P^BOT@p;upka=oj70_ipE-BL1(veGZ-XST< z*(#{$V*P^GMkKhT+R!)binj@J6S!y8UU3@}#b^OZhtoMJIcYZUrCW-B$izgZA4*k8 z#=+!|lybjH9JJ>s=u*UGD6{CmS+iF5*9&h0`$_uSmoa8NH#(_yuK9X9XK{L=Fk=CV z8Ml%WWnFi* z>N3);SXUoiu=c?DPLyFY7UXT+OVsOE+fhx!66&B+oIWuc@^p0F->BU_|ooIAlPPgx0*|(3| zFYoEtpBvv!w4JqOZM!%d&Dy-@nk~k9N(fp|%c}w1luEQHYzX3H{SosGrD!P%Ycghi zF>llEiqVz8WXv_lg9Bvy@zaHwJ>2nDn$t^{sWr8qKgV$7zAo$Ao$eYjNoyo)~o-&f^Kwzs*UK2ZPG2k?#3C>0n0>Y#FH(uVSpkRxs{$5LNJb$a3lW(Du^!v3E zz4rSJ++i%yi4vxn8;yHfT2HEH@DInL`s$9d_G2fvcQZuvc8Bv#08S#g zTxeAxU$56xDs0px&hy+v79QR%`zFj*(L+wUetj6o<~D{fGAjP$AhR`mv3x|U)^2NP z2`Iy!QNg-NfRE0*ZamWs-yJU3oDS=3LxjR+%rKBwo-KXlEjr3M;yXsg)_r(TME{*{e7dj&^16k5vLkR;WxVeZY*h;u(W>BC1 z$fi5>J_D%LrYJ1*D11ZjWj*x_i+d z8NxTq7V%0hOrS~K8UpIxoAqbrm-ugZ1~NZGY4*_|;hw!L8`mS-X|~cmwDZ0)%^*H+ zrnF0)BJ$FjdV}6?tPG&MV7de3Z&yng>=Z%v1^xGQ;jO2CV=;ad;f`?$Yu=oPf$~io zD;4_L%jWAxXH}tc<`|_#lN6B;=Z%q-L?SKQ}27u8rJS07)J-Dn)edwIbK?(~B3M4!PDq(S{WDwgwBQ;K7)PrWq`K-@} zf2y4gEG`_WF5L>3Dgxv7Fn_E05<2fl8@-cGp{}m$rp$@& z$8iMKo%fmdvW9M26QD2^eR#pj&xh3iq&6L6l9(cLraM79uou%bh3kQlZ?Bz(^< zczLHE?bp~xKI{LFg_wWCiuj}bUmjL^xC30A3DjE?P5>|5d&uIUXtPvzUINO@=(+IT zoYMExwVgI(@+oN>7WTQ+Lpjc3MyuI>{B2!q8!B~77%OJC2g0DlS(T&JU@AzMK|`?#NQPfe2bZ$h&<+cTW`qGLfFc0L4?sR%IT3FZTH$Wh1JCNWqBo3&8)%`k3G1}kw z;x?z)V39O(Hu7fJ#W*#j#~pYd@#i0D_!O15&9+PSF~VU|`?;dwf}w%Z-oWK0Gq5fW zla#ERHf^HRRS`$K{wkG37mBtv;Q>X*Azp`ZbKyn)YrCPtNs+#@DG!mV%N0XzF zU;*C86j4N14o7`@VC6Biw;(z24Vl2^Y>)cbtf)L=g$^P^!Ly0voDTHE$jg2So#EllX?NyM{+bKAC6d9@wPOoL+D-NXb#I`gH$Q zELEKngD|LYL|gMpvZiW574?M)A4?6A%(fpKL#-4?u6rF>~9ugLk(l|z9<~VU!$xBupNJK!P z$}WJipR>Xe^Jrky=toa#i_`_v#n!RgA)PWJbSiHsqz%v-u5663q;#=%uPVl^%FA<> z`CR!ge!|ITwd_&R=62Gq{u^%o4LAQU!j1bbI)7R07I8tPG$%aT@tq>zlL_Fi#0bUj z8#|TNT(F9Xm9E%Yb*oDi-Nk;you3IknOd_8%g$8l3h&G?oaH zYp(ZU@r1hrUAA85*WT83{Qc}teh%t(+Ioet4Wv2Espq%rY4SQ0-kNDTh;Bk%w1G7g zWWj8ix|U|srOL?t_UpTfF)tE8H3cX#m*r+v*^=>SnH3?10FO=zRWB`Q@3O9}+PH~0 z$>-p!4<_H_=8m-06^C8&rY7}~@dqOw)?HUrrKL!JT{T-&Brp}ZHgd=>if;#UARF9D zkW$nMg*hvM*u>SbHr1{OSdKxl_&DAf^iX9t4yL6=?!NI%dwNat?-U70v^T^^f?isg zuJkE;HzH;Pq5cta|L_;ShG?6lYtly+0Hc`6-VmzTZEr)H_P=Og ztE{N=rK`$ce10^TeB5@v8R~$7JOJ}>QgZB1APU)X;MevH#%RdV(|RG%NMw41ib)0i z6Y|?US-Mftdz#h+z^L#k>hcx6MbiK}o~2W+C?yU*^`DB+nyJS{vKolunhSfa;8HZ_ zE1LEgbz^@r4ifwRJ6mG4w=u|rlBdI{%SRL~g)3{b;XHi9`!b(UhwjQ1W06Ysr5MUs zk8nBmxRZ@%-2Q{@#7)YF;g!9p(-+N+K|~580NC@ zUjh>uV~;uhFIp8#j-;?wPr>Yc&=1b!M;55`}4U2CG7Dk~yTKplQ zJ5?4aX<5F#4wZ&xGA&~6b+!QwF{iil@skHcq5`t)+;xrGC;}dHP}#|5j#XEwpg!I* zUn5z}wV|O);;@r)=+>`auMztmy3mTlRDr&5!TiW01-fU!39`sdX5#`?4VOFljh2zRgBMwr7s%PJew zt4H#?tY^|{E#Hilsz5Ga3xHJ=1Kbm@$sK6R4PXJ&dI5AnjNin6;QMo$U?V8z9t}ML zPZSOSRw19JTzwT5i^!$$^Rh1;0ub2asd-kh^;Cz>BA*YB7M|^l?MzXRtPZ(XBj#H1 zGcmE*yH_PT9SqSZ2~0-EbXEe$kbU5UFxAj;lX&dz!>_pe-b7N_>U)q zi^PJk6-E9>ge1{&P$oYMKRw;4YJo52#Y3pCE4hn%SDbsKxp2zdzpD#HA{Z?}!EivF zclYkq^iDst)(&syLs3_+k^W-(H}MyePL!=bKu@bGN_bBUrlSOm1c6;3bE#qnKlc$* zXBrFOd#fK*c#$C&*ar6-^g)GDOlcuPb%Z|vuiT-@tf9)}8{Tm5#VKnjQFkmGQ&Cdi zKiHIW$Z!pUTVOTHFivm1&a?jW-wAGTd&mCxsDF+y@?>l*fl4$o-kY7p+B0LAYk(`=i815p8&tQPHO$cc{~&9xHrSjq&ml z{R(ClUpUqD-<=|+7!+t2<023M7`&qC0!>`?)B~p8SELrtHH$Xci>H6aRQBaBYjf)sFy6>b^$D1U;`1oD#=Xtu-^8fGcW$l}I z>>ZVuk|N9{ND>d@;jhV{DYa3zZYu`vWRM$B9N^h%={f%?-|rjHPx$~-%y#S~^mrb3 zjDhF4<}!5HD|{}d5=WX|yBQTQ(~XvF*Ih5q`QJ4FBZu5I(RkgEWbm8BB>@$g8Hv~b zbL#W6M=zM%uG1!+&Gx{|s=>z#GiA9n_E7&W3BNWZD#l-WY?tNRaD?qT=?>+Ub=PQ? zpkff z*Dfzj`1Xi5^Nu|uckOO3q|+9Cdw;Zs>&JYn@Ol5igA=bzp%osRZno}w9wM+f+?ONN_J`IBnv0y$Qrd&!rzOBC_G#y5m}{&6NHG!-qw|+5@A{{4%hO zbky?78q}}v4T(9zeP^z&UxdNsRrl`s=p#T7B8? z7G&o5hx@B#;GoP6c8}WIwN>q^kOv*lQSn#?n&+;{ald-=)kqIu?zzu2yxr5*yWK*? zWVZ3OyD^1NPU_c<)4*1`W_$Iji7=3jwr^c%EIT+`T<6Y9;x0bz3huDaYt2Aw9&v-+ z*M9nb$Fj=#4?LyY=0LqX?SQG`waRJ>!xFd??~}%k^LX(BRZc6;R^iK(mW<0#TF@r( z)DK6Im0pB>`0nb)Zo07a;}-Eb+vD7?e(1q?Be5m(Em%(lYuA5o6W_HJl^e};%IFTh zj99>M)GMo5v!{fo?BbEoJN`$hb8=Jjh$!RvdOUAuMbeS3hLo8A1Ew@*#WJ6OZM zd-s(hvm!mYC^YDA-!@mdmjq?juzD4!DVCEMP{sC$nR4c#b-;^~JrZ$_&eidDcR!yz zJ=nBc&q(6O+&SI_=Q`>jlm;{o2&fA|m+27pp9C|;Y7V_TxQlay<~wTtfHuZF;+0ne zy8fz6+c|@{^2{(mvZL@x-@}I`G9fd?g|tl!3H8{wkMKw9AIsUAJ3sg57JcC`bGvxz zV*#mmy?6TDJm7+vKEvy73}mGo>wtyk#qrzz`Z@n|O=K2|z8_>A?{2mh$m-)l%Ml%# z*zFizhuTiPvTD_;=@yl~_qp=4Yr$WM{kr-tSkNr!!)qT1&6YEglatYqcJ7H)N&QgYBaJa5K;D>O0%`@cYn_5^?T0=<%lA^yu>M z$D=oH?oa%1aL@!jYN9=`YV6^cVM-l!|-*n?_9X&SXd!y~5 z3=l6*eV8IKP`RyFCrBc`Q)G24n@ZG!HYZHfU-~Dud7P@?P>FMb@KBX2! z6Si(WE2=w1vhx&`)hZ=9xtfgZX-^0II=sEM&$etK@b30JEq`)zt5v|_(|>;b^86m+ zta>-SYndT|iwt$F`MU4DljijDpH?3-Ke>llEw9zBRzO*Z=#vnuc&?CeyRW3+cy=AK zAWgy3DV{?O=~RVzv`%oJ;Q}WT&KE6`C$cvY7!o|^L?1d+R=GHs^PVFnvS)9-;ssM1 z78UPv^yjaak;D;$!}E8AZ`7UqCA7kTi@5l`z30?#K~HzIL z8MW>gburad?7HX(4@>{@&M~`2jT-3^6I)7^x0!)@B_LR} zefR#ouY5|adWVFVDNX1ItHrC@H(9NVi7^mT%hhLd&DIXwp-wcFHESy=2Ue&zw#NfAN7gP{C( zGg<;l5JtV`$tgQMdiR$7M5ZmCqL?zUhO}p+`V|*Tg4nl`Jq`}or%$YjhI|erW86GY zjk*4RJ6(OS&sBfp$XgwMy-d9BI9$D(XdK{Mb;QfEUSvoyGUalEnXNQ;2t2oWX zZ{x=Dv77rWGRwVnYc-JdX<6yfJ$*VwFRFO5_9DU3fs4q#2K~pB{k^X2H#VX;S=*iFbk=O=$X1J$|DhJ{89+!a|zn^y8llTcszeEyyUFl#pZ zi4m!`>`~`k`DF$od9Z(H%)lL^-6JP#mR7=?{nujH#a}?{BC-4dpoNlc)YJi#vWFX!Eyre93XV$NAV(s0v?dWa`{-QS1$G)ksx(>O>?;?*>{{+} zfCIXD#tiM$C`wp#{pT!GXn&-M#iz^WY>twk?cYAbZO+jYKe{tcM_ z2F(8-2Fycm)t1Z;KQtAF5xAV=8&r=6Z|d%I_Uu_r==o*)H`)t6G|H#^!LzPx0#a~r(sT%PFTYSCjT&y^db@Ru-^OyQg~g5i-#neJ z`+Zk*+8Dj}*3rEqs0)j%ChUYbPCTR^2idD!!5&l()XI5@>;j}R-$Dn4nuk`cP!_pn zoAO%+KO`b*=e_aZ;%>Ru~`4)Njo)l z7khY;aIn$v-6sCeuJ6<3CX<qM5dITjv}yF@@?6Rs{aVPpWKcey>HBNMvHgu{AZT{BU2uwBjKU@?|(ae-F>`|0{f=!(@#gQ zS>yBmd0ysn8JpIwO3MLX(<|#;Nr|6mAoX1IsjL63->2)!N=y8tXH%v+Vh0Wmxcq18 zy>UloWNC*C@ude50AGOpfPkoVCY5kfN<}Rh9W4xLp3>3QZ$=Y)UE5KY5=ZSj{W<%_ z6n28BTdv(%1HPv=vpE(BM;OTpH>|ZN!HQTRy_m#7q6Qx?zpb3bY;h%rQrx|NvFm^x zSOVWWeY#XJx&8WepVs&Aq3tzmdF&nQr=J}5z4!J1x}uRQMunOsNRWsx@hQUvE$)WY zlMT+j`-f$Gs6p>0YpyT-T_v9m{`Qruiq4rk*9StgY(_K^?dC&M8c9pf-n*rxtW`K0 z{dM)-Z%47>P07fxQo={lgz&gBpXX;-*#j*p;sSiiGZf2~;hwfD@{Z2^)_?YFV_9yZ zc9dVh?ElwxlsSFC&g_2fEzNWf)1Xw{DaQjL26VFpPA7)=(TafPP(jP8j)biBO(@OK z+i()801#3yu~GnFx+a`DO1F7UXlEJ6P|%DYFn4a^?=#&fVnD9&o|@W^x@4=R*+HLb zdv?_5)??M!Yi53ZO8Zq!o!1dPToGqpT19F@-~+?OC#$`=K2C5f-c5G(fta>K4w?Vm zx3+?lj18W7;-gK<-o5pt!GgE3bpaypDT6m@{QUfMy+hx>ZUNB+{z6Jh1gy~??8?f@ zl)@1%0&DVqpWfWRqP$FZ`Lc(y@l9Lsrd5j9{-PH2W%}#;#@WTk$LqlL1;*zWs%hUg zU(lte6{i>=HQ9D|<&@Rubepp37ernQ0(8?H>^+ zN1;TnUW{D5|8^1vEv93s691be5%l2=?yYxtQLlh$^??>Rl;fI9xAE?;pBpqE*0JNR zQ_ffSTyIvCXJxEAJXN%B5n^soU*>C|oNRMit~zqpMV4O+zZGxwv8gp-cN{%7#)BF- zr|f6a){fzk8Ers~#lBB^Q2|1f7~rGx8|*?$USWBl1HK-$+lI6~ zJKB{09^MkJsQrj3nTHN-rxTmL+EqXP=j;w06LGNyqfPMGvxqShlfv`9ok zg~2IduAu0WQ?~xXg*O^^iVO&jPhY*t7P{x=&1V3jz9!6<5!Xk-@eLctRP<1I!tO&p z1bH6i?^&(a3k67`lB=7LGA*}JroW+#~mh~L!Kt3afzSZkZrLj z=4Ye_P6_<=tB4XSaf%jw!A|I0!C?SD4QNad&E zUDx)KNE(ds8ZiBDS$amVnwHOD4P_a~f|=z2e7hVy(GL~h>j`Cb4^L^t!R=k)sKQ#B z)og~Lp0;m}{0h~9$w9ba<{iiV?j2*^bD4O8_`kZFuAA5;vgf?#&1~Xxyax;zpy@U5 zxxi6({`sec|C~7{3OJF%qqO8zwo>vHZmh(;kN83fa_*?5>LA|vNd-*otSoKUea)by ziH93w@RGltVQEJnf7|IgqW;ux_e?*qDxuz69V}Hm)zK^khhuaiJ8~~q^Bp$J1?%mY zXj})xmH;p2^B2r0L`28TW;qcJc-TvLRnY2DA66n36v7~vKu(3%1RQ1LN-_lqK(Sw6 zpH7wtM~B+LP-GG;ZBbQ5SVDBkDN{MPTf?@h;_SAeBsSZ1AI1%5h$tSu02})C9mgqI zU2zjho&U;9+a=+Qd9Qi(<8vlzTDNYkv* zwh&PaLDDU4=rRxp{e!Vizc-NxOOqZ8voJ(nP)7h62UnQnQp#A42rBFQJjg zz~2y9A>6b61Z;%pCICC+8?d-PR`KecmvH^)J+}-fE=eY563$xmt!x>Hf+{UiaSR?x z;HM!{yh`pUMs`udSjOPkz^@qAcdqpy;hXTnIy;n`0t~aimKRxg;Rj7ben9rsXS?R# zy|)(O7!>-0Tc}2}bmz{UwNj_v=%iSX?0t7RqpHn?Qyj%5KEiyDkrw6M27CiUrOe)OE&HpNq7Jkmpvr;Qz_&biD9ZsAVCoNR4$}h0wqRMB(R~<7UN>#VCTAP@>XZg z3XHIJ+a2QCaBSHP(KofIh-OUVyae37xZqn|=FG!>h|ctS_4<1Z$fSngBSx?&R}+K>OCMmcYQ@DdWTg z*_N3LkyVAq9h-MzQ7`|Jmv^LZ6%=#Mym`JD1TN=NI-XPb=%34tzVuI}JBK?T&Ofno zcb^lMsdO%0_d5ZRBTGYIr-Xox^}?($r!5bKBSP(oHky%KG>>rZPg$7&Olo;nO!tD7pr)y+;}miAq{hUfj}Q>|df z2Cvg%q$2_aJ~Bh?IqRQq4C5m~k-5Ty2Ul!Tj){N!#rnv$h^mY8PguwdNJ5p=ybis3 zi8G)~A1o(TPWYWyRU+iMWg~WPzd3V4rl-|}*fHb#FX|lFl-tmL9noAiF1xRWTP$=1 z_<|RbXq%}`YfF>aPNwnYi%E$@tI7s}N`oM$vL1aBgB*1f0X;Nw9(!v=9!gUugeA?f?RS|;@pHAcdSn}~na;zGpn)jHsBqE1d81Q#QN=n5A zWtI5+K(@we;p-}@7@nCQlb0U)owTe{=x*7vMTaGzH&{^Y!OAgz3)np}n15Qn+jH29 zBl~^!#O}}T&?)J}hCi6NGv_9RQ!H?rkj#X$AoLr5nYB}pR+s+BL?$j_PUJymBnsP* z@&Z2zmFk!+zw26g6x9j==h0lYN%i(rYurI*fNlqc%kv{IEa|~EJEfGs$noyN8bKfS z&C{X?_t?U63i>+^3x2NEO4`e#VZ><7v3V~}PvbBM7O0$^g?v-LK?9%e)Bau}zC?<~ zErJeenrdL)<7DPUU*D=+;eI1>D=Iw_-~a8WW2x^Oselo*%a#d*plwp!Uc5L&j~Ac} z2;Ih1&iakM46)`^c-roIf9rhCO=;qY@J~Dt24MchVVu7YbY!Z4%#JXP;{bsG(m@k>}#4Qrri9WaCdkpKlCMN0k zY0hOIFX_%Wm9;KlNspQgVY@|dMqDkpu&c$%&yVlSz##-&f`* zJpFF8>|YQf=`fS64kRru9d~u_{ozz(jmZ!_idN zUIvM>O__omr5gF2Q{da9*=V#=lsrZ{+O?C-FJ0=KA-pddSBP;dR?#A z$L_Pse8#r$tSXz=zlc8GXie@2I4r!$K5Hm{&eal7(O8tTV7h&$#nA!#2;f#(uwcH* zSc_Csg*9z`DsQQ@D5QW$`sYz`GJQw$P(-k2?XimpkLx$zzVhuRiJi1kZ)}?ACN9G_ zC)D)uM;7!{`p+?|R@GL3oWQ_986+PVP})_v9{#WHhXz2eR8)S1q$=6Gc-#9dT^bW| zMQjKq(zEu|L_2?JvkFz(VUzAfnW+TZR_rLHt13E*Ha0dfShBSn162=d$qd6ng}U<( zwQcWff9XYP_8%C^-bzf|-*bv9QZGhtM^|siU^Fs!HOFGG$Cp;e1Ns;@G_4Lu@3;^Z z;(cP{0y+c*JQnF|unHr7_5CPEpyI4l`L3}0M?%Kn7rV@ z(6@`k$0^Za(@Y4lMq8b0Y%Sltvrs%eBx9Bt*6v7JDt-JKV$VOsx4b2%f;!PH-uaVI z$8WghZ|fLLuX~*VQeW;V0>_oDHnKLLX~)Rf1sq&MTop#646V3^+cx{+Rs01*zm9EoeK;jxg1@PEPQ{mzNOAFDaHgLL&-{(uYC`9D{0=Q^;ryJYQ&A zoI)hg2nH)91|&^1tFA~oSfjvrFn)KiE;Sh?7Z`DgPX`ie+V1HE377+vzew93)l%Xx z^F=oc1Y*W4rbh|h#AhmuYY3Lbr<7d2MIWJ5f@JSKd{|Q;-)^IKD|2KpXw1aiNik6b zCl?xeQAGcT=bCD2mYFK{irhw7d%5}V3P!>B&R|fa=3F>_wE6wYI*r15N*^x7PUI=-2{V@OI@pq^~~D#lBOId!Rw8C z5(rcEY8MTA?T?&?Ed&Ve2x>!3rE(Y`s0OtwVoZ#w{kjUE5<6U2Pe=+YwFeZ=85_76 z*JG2u-w>ajxlsTWhG8`M+}EQeg-?RoYMAeWy`|)N<|yY3d2Be|xWK2`xE2E=X~a zw(!{~m*C)GJ$&VTA_)5G;hBvAG7-RAl8PU(>B9%E_-GpOCq2WW4@_x5URC;vLeg9` zC?bIVqnY?N%8}Jv*Rn?4Nf@&xZE6s&GiN$UL-n7-r@8?kXzvCwH@8j!Fh;0g*{gva z7L*&qe4G2w#RgTva?R_#W}eM>xIT6lbNOsxj%?7n<6)wkOScNH53mT2L*d1VF73=Z z5)ojZ4Uy&#&7Cyi?`SpZaMV|ubAQMOodbva+tRE^Xjt}Jrlx0=_r2ROC1b98;^|m^ zXV(7IDQ+GsR^9!zrRW>}W%X(!<^n{QU4y@#JP|!OW~ZI<7gC*sI6>w-f<75W{3+cA zU*ATe0@mTkf+zMlX9GVgF%;M`En40u`j|zKr4+w~G~Tj9cjz}N>;{?{od{wr{s}>a>j= zIx=Bmkp%dnu%S^OH!^aCCdGoKxKkuXDMFCTjEd9W*-yp`7haFASY{*CsxVW%FQ01_ zd7KE0NEuq*l-{!|_8oUmwl2Tec7H@i#ivpC>DiC(#t4@xW6Qsb_20$%?_&L*CbGWu zc=4OZ_tO@it*(AH`G@KeJ9brGy|$;ruDqBnclGlsZh9T*v!i>PF0IC@Zuu>HtNrM{ zZP?hxc5ibZwtl*@@!i8=OAYJ2y_B-Q)94Z2FP5y%?7nWU*Bqw@77s?wiSGA#wExkg zTPi1I-kW;bXMMTBUjL=TbRE~MaqQE_(ZJx)#_CUcNy|F<77Tqgd-vPzQDkVNBvaeFN7hWVcZjNb^B=9OO}8hz7CnnvU5jI=d)Q`-d>h*& z1y?UgdN@64X3Lb+h>2Q_x)wN)(G^!LPD<&XwEVT5^S&sbSy80{Nt2ru2UqsyH!lHw{@LAzRtAy^wuE>HS6Nw>iJ34pM#_7 zH;D4_*=%n=v2AtPFvs#ixn~3K-?S|FVD=W|T+{pd?jb4wa#fZT0Hsx*UnioL{rGRP z9Qw&N^xsufaLVmyK+{aV1`jACqf>D{s(>;b-UjLwa#J0oho1%MUwInlz<(JFp%c5J z`R*#cFS+~i5H=r6mXak9s$|$|7y$C$<39l@vS?LgiM_(}%Qd(6aTA-QMIEl38XDSa zy<3ExLNay_do9<=VT;1&8C-UhSH7^PuCsbnWaTUXDF`Ivze7HpxL*r^>p68lna@ zZ&gV0Z?CL9W(pTTB#Ec998VusP4%XxZ=^h51t^Bt@019u$0}91_ zq5R2DUJ*Djyj`d7J}Bbpv^};gxYwBuF!{5e zwt2RB^~#ASPqDC)-uoxHH%b|6O`|VmUxSo1ZQmf>;@|5fxzzV9^O@Owd(_>#o5zpe zJiBj_Z5>`>VW%9{Ou1uw^7YW9RkdXsY@<}Gc=CFJ>+e2Vg(c&XUfv5W{L?IHTbrZ_ z9v2T8T>lX0vO=y@sBT$6>0zaBFY6`Bw5sZ=&z1oY<6P-6yV8+$rgF>r8-j+BgpUX|LCh3u80?)>eWvW-_-atsOg6#3U;8{>b!0M z^rtnYfUWTYReRI3CdCPtkci^P+&lw zx*+FTm&HpAfHU)Ntn zt;xR=YB6k+tXrcs#{&8sQD-~Oo$J`6hvV@M?p;>#5+kblw?p#hba}8uFZW!n-1C~+ z+35vcD&KzSp_ z;upILNVVh|<40N8e`Y~+auacQwR5ty4s=c{L10}4za}<^De)Qfz*gdh7&rJ-4tO@v z0P~9#Gd8G483f$!7WI3J;?J+6b}x?{KxAW?QT74}Fnm*T<#59N(_4pkZxecPU{dGK z+g{C+xOk=$3($D$W}1K0!8C3t!C1H5w^-dh9Xm10JLyEfq^+$9klmw(^Ab5VEcV&` zqF$ZS^!2P&{3s-}@ZS8Ws*j&rtAB~L%~;mj_p|@Zf&NWXlIs;y5IhXhvrj4rre&$qPei)9oD)@k# z$(My{T1E-NxbWr|s|27LJPQuHx3_n_LWr9x!3>;}-Ss0WE8@foSp@*=Kp)qR{|qBR z4iY48i0^M&&`%2wwL}&Atf?)h!RhB)9A|8JSNXy5`Va@5v@}PSS=(NniDZixbuNE8 zfs@HOuGPj1k9FKzv>36Zzx$C5`41ymfLom%clOgSf7f}jf0wJ*W!-XL1_~Ai_h3U$7Z_ZyReVOhU{H3z7^PesWQ_qAQ3mkkz zXYl&`+|}<&4#q5g`swVZ(D`zu%7OricN1(_{h=UOFZs?KEeF`Z64sr2ZMP|Y{@ez7 zj+Hck#_ObNU>1EOH{o6!rD>}caB3ID={hm$@q_H77Q7(Y zJ*CBEIG?UESgQtnO$?8WOyEDQ%OneK5;uMZUz#-R+Q3^I5*liIwCj?b=lHPPiDR^) zYI)ML-3F0&CMB`VnzY?aBpWnn^Rm)|s{Zp0UBm2VE@9aARSGGfL8bT&*H zxpVWfWt#^Lj^Z3=-DZ8ax!-)FZGPWkE0_unv}zG2Ub=;`^e#npFALMj zQ1hWfN9nLxy#YlE5!@UueIAK<&J0FmNh0HJ(#GxV=U0Q(UhvU$hOTI5xaWFetBrPQ zB9A+OrF7JkBNo6^y$>rAZ=t%D3qwT>Xw#|?aX(q3DgZ5RY20bNG*mG4R3iPH1;Xme zaNhzU&l=Kb27&!2ECzcnZtZ<`+wdzsQCewvs_akG)r)paC)z0U(O27 zjg+bOSCyS0YW}+qHJSu6u9NHrXx(EoU z0CB82#H~U9Idke2;!|P+zsx~;d#;kv@_;sY!5mdV_e&OKl}!sH`{KU!rS8Flsj|gc=8n5|IkJI=ZhCiK(=nx0ZmGHZ@i1QD zxUuNFj&o$%*>LrBgMlFg0d{xgmp<-iA~}e73u(_)Blji!bIEJU2SvX&fU%x`Z{ygM z&YcFVv=|t===u=H;HAGr`jw8}=?a5%$1cNdk7mpact0sAbxIUCb6TWikii};G*tLz zTLl!+7!^`oDX2n1_@+(T4(xBac##uz^?&^^Lc(M&gHfd%)5;2cefD$nLP&cm#Wf+6tJ}@vWYcdEaDLV^l1sftFFYBVx!Swgpc{!DU! zJwX-Svyw5};@o$oQNS@}6~Mi9g{I~!r%>;uF%x$Ow()&FJ_$%#Njh^1+G^C!io&yc zcR*2lHQc;7F}qYfdGh1})4IyViwmwAB2XeAb|Qn336|AgloeWd3b#7p{$(-!?M9!( z`N%|D2wZY|3sEo}E(bYdsP+NkLw91{gRn`%q)7VojxMBh4y7=HBPV%$mN?Gj!qhD# zb&D5D4iBj1i19Q>M-SdlSR ze40hoO>)m$4&0Pr%yD>d0!vv+(Zx+t(%YuETj3h+>!($`d->+7H=dmepYG`wKc^tl zV(^Ksb_L}dfP%uA7zCWV04rP{n@p--q7&q>(AVn;B_Vel-nz82c^<^_s$|~%;+KO zq}0@bO5(@eKaH>QHA-Mo#qVt zMV*<0s4Lg;IG^QeGcsaPzt2kRkHJsp|>GQZJkdV<~?cnwvVs z@yr>=t&MY6)pgvm#qq=aHSa@jbWO75av(wsKFALh*rsPIkdIc2kWqDo~KZg%Mc+l(aIRxksOPVZ&QcHrdF zH4^fyJOe}=zdZwa^N08M9I^O)SGZkD?2Q{8@0jGidT^oiU7q8V6P{O9d+O`OO7JahF?oJdGqU+=~2^z+81Z{j+#7qbDOqN1pWu7 zLJN!cML|m#gl({Ll3i-2EoT`fSccoPcM=9`q=)pPvV5ZaUmI=Px5xhO(R#(WY%A#^7iQKmy9OfR z-o-=|l7*Y`aQbbl$slUpKykwxFU>Kj2_iLbsCne!cP7>(#x-xKd0_3*j?~l}{3hN| zbGyxayap;|9KVS-$g$7MIhXvD>RA4ekC*KHn$J-KIw)_Dy!QY052`7D=e{qYyG0B34wBh`~kjI~gEP_Zm zIJJ?h`EQMLb0jTsJ`-zoNA24MYk1of3+KdD#QBk<4M<6dNYhd`s?yJoSFbp_o&>p- zhT~1+=x6#pO(gFa7-G?9&9Prr9y=LEE`u$4?vqUhb}e%+ZgdRM^m?T$SmEG(bPl~2 zEQlI_ajctM;BRXhxY0RuChkbZC6v)iU-}5(cV|wULij@$9{ptVHgL~?ZQHlI{cdU- z8xu1;gTXUr;xe;6$;_V>ir(P;AvL?U=o+(#CB>1hMcKN}4h`=9+0MOPg6*akwr|IG z;M0a>FNyznys>l4wfL@Dj-f^7piWvpYp7y9>-qXfotFGCCvHxbMHHz?a#oGSK_*<)*m9&%F5U<*0;) z#gFZh&WD&S(PW+E?8*abKlcl5KZrk% zf4mm|@$6_FMA%d_+9kl-juTmaLryz^?j>)!Hu$yYE8Ut%w(wa_PUrGP4yrP5&E&+o zbIFOCx+OUHCoJvJj8A*9@RaM9$@L6fp1D$I+AeHQ0@P?i9kt6Zt9f{w?(Xw_T>E+N zaHRHfGmgmpzH_p>V_u%rNh6xLPk7!X>*X)44fh<<=XuwwO{MiTz_CdaIliyyU!BnX zQu#M7Ku%{CMe5I8NDuHTfA{f;qubg=Z@c6^Iv;Z?gy>sztr&0}7wWE&SidS4`{pkz zHN!_*oR?C>2*uN^ItSRzx!+E^fv|TL40z_cqM%zOqL6CSrf#sZU4Nc_CQMo*EDR|s z6*|BY&6-G`u@J<6*+~QSr=uacH&!uC+-kk*hQVOB(_8EF$sdU8GJIzNX-5jS@ZMFBckX*9Zm$C=kEal+hmWBR*hDDFQ zCC`3!t(3`jrItzS>QUQJ#`A|+6yqmIz4ZCD1Hv3hD(jO=TNb~Wk@Mg@C(^=af{YUa zv@R11lnY$?rnB>z=?;y^LS#*s-}bm16D+z9hC8n&AXySN;HQP)liHU{OU<3ztyjUb zRxz+@=VY`+76wiH#M2^DdMc#m^t(Thw$hLXEwfhKu_^w0c*l-5jcwF_N9|HQbgs*% zUGS+_tIY9Mjq(PnZR>QuG1VRt((hYq8EPNud^HD%EFphqd-cdzqudf>XSH*pMF%Oa z-&siMk-Iwo=E)6ygXh>?>Gp#>d#jaf_yFD9-|OT)?Mm{;A7)V;Jji~_t^8mxq-ubB zEB$GwH!S*STJ-7>r*y_bfwc(%p6HS|9FB7V`L zIO?u-`p)nE7}aWi@~W|^3BRsr8|HB^Zf&uSbC*@>?|V^XsUA76jBYeu9vT?wOHZ;3>O}8DiqTRv|Zv^NnmC8oYE2(mX>HPR78+nf4PQA0PqM7mj z)q{=Z47T{eMh(Bi^Y!Z{P^jS7g=kvaeB%DNk-sbL05Gu)|3v@#43BrcZUcW=0Vx98 z`c?<=anzaPgF+~@-A_$m9VA6IS8r9Vie8j0I~$m^g}|QP=BggCCeBAKH$EP8%I(X| z`tB_{e;S&Ve~jbD@k&?sSj+o(~S#XemviNo2e|Kyvin~eD9@<4R z;PoNW8Nrwo2>9QN{`X1$yS)G1)BnE-WRkbSe^s8S-{T97)bQ;T#FlYIX%8q+qRiV#Ryt$3CI&R~`*v{L9pXM>QQWZ; zUFwZ!!r|-}_;bC&qLBoB=@ee=)J)J@+d3%+n-pJaCZ`oWh8x2n*a7xJz<9QqzPI$Q zb(NF_jCW3(;`5qjnL{Om_n0o(81()uMyK6m4*w`4bj!+z@qxDm(k?{gWq9nKJq`mU?LlSoOU2jfr9Ltca(41zSLph#(DA+9i`*$R zsBFFpn4e~HS||Yh;w6K4698StIH$O-TLfR);Vn&2`?^=kTxW+iSKk_+T}Du&HVZueyd09f%9|b-B&7r|8`MWMkYWs($^=y33Thg{~Ap zk>)U-&QidQpa3b!pe(CUX|NLwo-DeH(i>B7WJyP)E0=gngy+%?cX#igAl*W#eOM1=&%d%XIwCM5Ni!dop2OP(Nz+bkPH1RYaWyj@ngEC* zxk~ct{0GJIrWJaH;|*6uy9j9RyDbV1Mu%qPJt>BRTqjS7TAf^vv@B!j?bUu|M#7Jw z!CMqrKR3S>&p7|)x?;6yjoJqw}@8qmL&1fYJ-v zqvLX}Ur$sLUX<{H0&D74pSq|Kk!;I{9lFV4XymaU+Pb%jx6S<2Url-i2jGtFfw7xU z4(`O8qF*Q8KhV`R+DZB}SIwn&ow(|0fai@A8aKo)qF0B@SRWX)U+ePpUxR@QJq4s{LC(X=B?cEHpsfhJI~X zs6TxQP(TmXL(bu^&AvG+5j&!C=EIU#Ub-B`FAwXY{=0wmdSNN03;eLPvHOyJHoU1c zP(5XDnJ-p;e_LC=B&9>NVshPa(@8Q04?HX@uvzcCJ#ndKb(vF=v3BP3L;0}UM zQ|C=+n;X$l3KY61go2@wz}fUyuIa;;62{XIrAh_G26#n1Gbm7?zUQ-qW4Cp}bSu#u z$znXm4cs=10d!XpCAswF2PRKci~`t-oW}EbS5=(MS3h1@!<|{o-=G?%lIAm%50d_* zV`sc@Sd>}OBNEllj;X1X$7+`@b6(Xe8i>Bjm3%l~A2=b06(`mr*7mU*z7s8|EkDy(?-E(X>byC_uHa5ALN$ zRlV;oYz45~Yoo{zQs%d}Me6_VPbr;IF*aGJ>pbFa{Gma5hK~l52U~6$@Kf6s4r!t4 zLl3QMs5c0jWVO~b4BJc^mTV|A1{%(jzj#XyO$xOy%E*Y*2TPXCP@obT`uasrWV}`w z6%F)kE3!TKUh(x{qc#N&S%sS!^{GD?#p|Q z!Jj)y(hPVD;h;raetOdc6O+z!uZ@Lj-b%pqTh5P{n_oHbFImS#5=am zdVb_u`1LT1i{qal{DDFpGgsCJe&n z{@E^fjb;~waQWW5XT!8NzBVZIb7bgpsH9RA!haJfC0E_$FU-_qu&DsP)P}1u*!0qu zK`>{8uda3Z#S0UKeKqvcQ*HH$6a5sH!m$%45@cjndru9{c_?wjc5XRsdf}aCRyLDd z34}IHo8-S_N_{tw*nYE&$sbiC;zM$y{Z$9AIXHc6eq~_S>jurA)w^(GP8Y+2>6>df zDywR%{Vv*oVL8`>%PakUC^2n<+e2ufI)aY{slD@Xfx3A5D(f9Tb=}6uD%6AG-x@?! zmA2)36=i!uFMf#f8<>RA=N_rltEyyFX+FhE4Epw!CJ4z zHfEJ)x&Ix1EU8o0figF(vo?Osuas>#A5eRN#ZJd7B|U4%9PnY2r@nPfXb7bPp6tnI zy28%8=poDxI3sYIX4dK6aC1;aqnZ%~qi&&vjDa$6w(O0S3>1LmGJihvZD7*W<|(e{ zE<7VSo-|D6Bt&v3OO#2aB*u=PaRApD6~sEq8jefgj-(4Ix`s88g}mva^8vQHnF zu276U#Bo4K0ca3jiRe9YjP4fi_8D~-G3D(#98D!`cLJ-2pvlnJY}JmPHrUQqCg;V> zYkS;vF5)s~m8x?tW8HI1o0(lS#lP9OVLOKot=e72G4l8l#>4ME5DO#U6Lnm+#p8E_ zq3~2ucMmnS$DrOv8x4qCx5dqR-yT=Yp|f52z-HU3%QHp8v+8=aRbXXb`R<2mbF{vg zk`DT~>i);orcJLz9EK2h_DksqMS9@H3r`_+`(@mJ^rKjW9=`3RgZ9z+6=hfZD`L4g z7KZvBpBw`bc1uT(9eXW}vd#Ufn^~##Z9Ci%WX8>C5dCOx@OU z$ykYDMJ%%5Om8-hu5IL;jc*noJ*;W_5jr?o<`)_He9U#qxm3SK`NY%~i6Wylis|L+ zM8%S)s%iO7r$_wL0&-ku@p_NO-hzJ2=iQDkTL?71vd?wC>g zY+h|o_RaD-YGSZtW!1ZGs4*Z~iQKN~a0H5{2Y15Db-j()&3`)f*Qmd~aRD->om+GY z=_&kj(RR3?aX_mnM$CJ!YZ#4m!;slsjBZVAP>8$H5c@^lp|+g{t_D$6WPHapYrwbp zN#xz0$9HIkMV#XEUUIDy9%yJmpd>V_L3!S2Oe>L;Agp7}1$Z@N*;+iBC)vN+UT;Rb z=)nUDf`DvYY6Sj-ZjLa{%H99-c8^VF8T3n<@+f)43I%G%vdmI{(P@_P}zkt zQFI>kHYX>?Q`D)!T*WMM=Ad28Gd#BK?Y`*>zoB+T-X3HgAg-Vy@LMf(%$HRz#;=%9 zo%y*_(KG+rc`p&9!=De07;WwS&Zfg7>|=&%$5`0m{Hr|FqfI#`xzw~!j=sfD8-7#x z9@|%kZ}XF=9UcyuZGmqj=9tIt?H|0v)oC>dB=>7qr~J{w;&WSZAks=kHyCnm`QI%M z6gZHdc(j4i`0B3}cwmd|C0s|pCx1SCc=3j_$D{!CBC|Eg^Ur5EyV5hQH!nKvO+t=?--eXK4d>!_itkA2Oe=Jh*hXdEE_U5aTP9|scjBC zuz!%T1{=zprrfWWFXAWzw(iB)4oNPY$3#TeVUv=<@*z7nweib5gqD+VtVMZx6jwOJ z>EOYGIBuE>iMvQ9P{qKO(rNF_*)yWM&d`yU(ZemXbp9G#GJizHYPth?GD?rV60obx z7Q0UZM6?Db*`WnXn~Z5xKgQvzxexN@bHwkAT{PZ?T~ry!r{`;{mF%x*xeYrK26f`@ zV5W-i0Fh{0JQjl78?;H>^jM}>N8%ki*~5Wcmv^6vC@B}S?Hj`}xIb}%dxM0GJ@@Uw zv?>RN+wR_f8qi8M;P#so4#Lux;Zg}P-Fz1>ZjA|R5n}-7l@UEWjm6>j*yNtr(uRBS z^1|w>z}*hMJ4$0rvj3>WXB-1roYDCdXl~(P`bo5W5b%kPd(RMd$N4i@oMrnbUCOzZ zz|+ca#hF5Q@bAb?Ku|8f73&Z!Z7nVjv(J5N2u}}sv8||ASi;A{*ldWHcP>d>UF zD0krbb8%AsaRVEY^*h~x;941&f2$3_iGr17ar{(v^#*A6J9k=cl3JtksF?aO=V2<- zcNtTAedwC{^UpJ(#mXQ_%n!GJo&g0Bx#kfu;}5RGym`SPUVc@OdA&^aHQH38vx-{a z&0@qz5hCJKz~x313pG?N+~AFZp7eAvZ0D)lH(P{F(!>mrb&Yucyu|k(&_(6zdbPPw zTw|N#|5~z0cJ4hn*9c9VA>*+rB80WJl_%e?zubYqPRGwg@a~uWI6%0=_I0^ockhZR z1=>{X4*w;e^cK8mH+$ihXKu11J;p<}R)k)doa;u+*`0Lr<~XdHV`LO*q0Ib3FcYDe zyxGT1ouW-6c{v9y;sD*RNB;*8bS#mo8q}#qn+2caLJvuR9OtLY*YMjC9-8oOp%7Df{e#26Z5QLE~-6Csyy5 zCtqa59Y{T#cp@F|VRmTwGP%RwvufO?Od(p8KN}_@vafno8!+lKcV4MwV~)fR*)(X1 zm^C{u2&u4D0JJqMU?Wl9TIay;hR*(> z3DE%1fk1utL18d33LAO1gH^lr1X(dygf$j(eOUERQvm^@$4{7G1~FX5A#+4TK+Jow zvUNFl^LH$12gu7GUKczIP=7uDgC8MAcqWpyhA!_^`4o&bC}v}P@L)ad%(rd!YBe?8 zw8J}xeC3W9Q*zLyu4i9;{>zkyaae_Sg zojbV$$InJ$8O*!=JCf3IhZ9$E!{*LK&P$ZQIIm8f`m$+&f79a`SIShJk>`^Ta*|O& zMGv&lFIFzJjUwYHhspOAGj%Xorw#2hYgU7_vu7iPAN=h(>KJ`}J|LEwJP%(uhmc~k=>Rb}O{_#^6zx%z|5O`uo&#F-(Y zK*)e+`DaTvtR%q#wt6c8XrK!@nj%Ce!#YU$Nu-_i8-r-3lB8(fO&+6Yzl8UFtM zrq7>0kAePJ`t=~W6kl3?TjIM;A`;$g4A?0*jc3o_bR-97Ls3j4K%%{UVEp+DiCy;I z_A=q=%pP#=-Foj!QQmALo}hca)i~pr0X@7pgx*<{R0WdG5LvKJ+a>sglZaer3?yJ1 z*-s;Qv0{|OK!JGJkmAT(`B_wn7lZ>RL9Sr3YHa3p?ed)kakp-{PP+8(!~FMQ{<|~( zzq>QPL!3d;7)Ihkg3q%X>zcGR?LYtFg+qr9c}f#TDY7t?(pd}7t9^vMWs_WsLbFNR z7VJNiD)j_OHm>dz0v8&|$rYpv+1_(PN9r#2E!P>I7P8SNaS1MQ7;HDa7&;&NLN zX2})5UyFYCrTRotR9vr)l(Sl~+@?21($_%Kl~fR-7Ut+qn-Oi9*@mqL#n2|NNO~_Lxd5MhfSJOx!fLW*C(?yt`gaT03AYVGT|8 zM>Zt1Qrtb(;KW2_60b?B2a;PQFIB>(i}MEG$n)Y00fY=lWka7Pzu7vl#?SAY`>odRO{5K{Iu?9DtG-=XrBD5pE zIiNqU0v3@6AVA#T4Be*~X~^^e43UGh0dy8m`^1*lSJxy)I>tZzK>AEEOiDz-&h#KD zC%PjXjZ}nGSW0GtJ;!|=Bf|^FChwowvhTPTUb$HsMmmNZfH{etwY4Q-K;F6W`F2}y zY4Gm|(VwjKp$%&9)iNTnAn)_hre;fMc>Kt!$y017dlzbx zWY^eX(>LX!%4i%e9hK1u4q0d-qk{du6MlnfRa4p!o z&y6Y{N6}OPw{o>`Qr@C9lP<`S4LgkAoB4Xz(T>#0f2dpJ_RGpfQm28Kt(y^kC9Ac| zsR$B&@*e*sZp)X;UFy4T*~`B9wtdfESW|0EBW>zAiqo`E>TtKUcmEuonkrC6Mkr|- z+W~hf{7>xbmj6|oDuA^|A!pc#@}^mRoei`>%P)1bY(il(&_&ji45?9|AN5_+QT$kv}@j+w7&0{FJyXmFx3LEcz zU{A_Oxq0doYeDYP4r{bvW+&{J@z`o*%GyR$1mqQWY{6Dk)^q1b>OhC-zAe?UVpWZM z3D0-gn_A?NtUWT1Xu;F!*KK}*X6Ujf&34x#HP$hbbu7LGsBY06%O0{x<}5M-%8Yyn zbBbVP(X(e}Owbf|7DbQmEgnzWT$2;qc+YJ{d~WRT8sH3oNOJ@+Y!%#BuA1^R%L^HNF zyBvR!;yKuPal zQK@AiUhKk7XD3k-PP(LxiZVDrTHm*gi~fAO^2m4X8|X{T@Koe>5o%EE6ePk;>Ibs7 zYgB`Ul@*I~u59UMCeCd>^XG5s<_ijy_tIZ1h+rv;J~t{Tm#rKXh=$C1Q*azEE~>c6 zJ@Y-P!%*7cu8>ZH4tmw)ld}gFgb(s>+GhnYWmkl<0WUl4dH2WT*2@JwEp|a-!$}g1_uTm3Z#E zt;R;}nwdS=|HlAa`*aeBnpWoLk}jIl0{HNX9|W19^oL&>spkN84<5I0;r_NheWV~Z zm37aGf7y>F>ZJ7)RYTzkGygb5*3{V`tVM?(Gbkh^!8zo%c3e?CRU{H-@jiPEggXe^V;H&AZoz^ zCO~)BS^Mn>tp3;lhXXdmP3y|?!}}4Ty%O<*H%B6Bj3@&LpIvO2-XTR5BYjb=;d%`Mpu=%=1j~lXKfsu}EXzzNd zX$VIL5D}}}Z7t~>4wTXFX$x{BIn182DjYPDzQek^+V$G?W&E zxKPNnZP`tHHHAmWEtD`zjHBQAdlS+FG9)}fe8UKh5kg9V!84k3U~Hp0 z|J~?I3If}voji%~1{n}owsguw;}kE^-&Y`r9wVlFT;Av{k`C(AxEJGG0MYV`_Wn(1!RRzM{X? z!is8UOBz(hzIt?@cAPhWSF`w8bMi`D`jp4`xLtWxkNw40S}M<2Zswc>;wVAvj@6kr zbpTxxTZ-kxjs#*~;wn5W3WBd|6X?gS{$CyV0J|4~NMNsSqZ3$N(3KiW?rLH~st+x5+`*U^ zf3z~%V8GI4z2ba1ynP>?1fu&36D}Q-yot8-QJwEP&?GH-)6mDlnwAofCPkp~7zfy& zDQ!G^nP1H_=4OHyMl>Utp>*nfdL^~o_iM$`V6Wsr-(wn-|Dm9a9^~k)o&S5H{|~lR zyMA<<@Qn-b@3#Fvw{0H=G!TDMNz?A1TB4bD^r*FL;gM`%>F*Ey{GyNaY%E5aiE$AL z{%)iBXUf|YRg^!E>FP9~;WvZ=U*5`(1_VQ#EJ}(-(#iB{-&_i;+crIWnQwD%w79~8 zES_H6(eNX-Ee~K~THnqk)%^EkC+p=r?Mkyce`9a+Z3M6pr}kT1oO3xZMg{Uf99Arw zlnyxyep#n+k#_}urulToiar#Iag}9z#9EXXLB`gxBQ^Yk)1eiQ8nD=J-45XI?Q?En zy3OQCXQeb(vanYCvISI@yCdcVp+6!1JXUCG_( zZ2S|n{GPWdxvVF+`^^b%SOaX^-aeaPR1R7_dqLC@wex-iT+xZdAjCV|FpLi9-u|kY zv$wX~d$Xq}F`b-i`iiI{O=-;CB_Q|=-hIioCCMHT`fb88dzmOc)t+ng%eSXOWA9TG zPIo}atz=KNZDq*>u~}APb#iyP<(k6;*E)Oce;2SswnR)pB?HA2?>y)P{>Xl#w-CI9 zjB|bH=caU7jYf{i5l0|MDf_NWnSW+oMo{G-T2{@izd;jI(p`sDZQvL{llf$T_VLp> z6Y1|B4&kCVZVXQzYrSh|1O*E4n2DE(+u8%S&CXc09CI+-G<9C>J_AExlaxQ+817BS zHMf`XNV9uyEsVlAi^(zZl=ULLCmwPtPj}dw*bmf&qmF2W5nhO%YxS3I;^#?p1c|#; zpW>Zcj>;t`a{1!j`%RvtXlaBSW)Enn)a0*Emo+aEyW&y%)=0HUDZWlBk{zhK-GSJ# z;{Baj(mv3DslhR6HU*ZD3@fi|SeKml@B0;@=5U^?cgkxePL%BTN4}-Erp%x5vqxqmP3AS< zFp0ujJ@YpW;{e2}D}k}Tp1u2Qqto_09zw+9E?(WYx}xk%2Nd_9>~VfZo3ukmwp+C zJP6O3NH%Q#uhRKGl`ZQYX zeL*#A&gUI+%h}gx`hRJrmew0aYs_XM# zE(d>UP*hO$vcNQT{6?K5g-G`#(=- zmnTvegA>q-0|oH!Ncv3rcqa1Br;L?6dZ*P)BnWe4`cI)|lQLE}y4!PXwC9QF04|s; z&1_X;n@i@yHqo*JXbjRTubAJ5<{n?xEbZg+e{uQRO;?ip3$VskNDBpOs>BTdB#S_q z{f8A#_j;l@VSn>}Q`{TZt*e)#SM{cWg0bBC-okV_z;^Y=P1WbV+{>)&2=gUsO-zis_x|I3A<_YVP(V+2fC9k<8OMXMF+N{Z)GnKPmM#g&%3m_d3hB#7y5YdN3D zKjeL@FweTHTRF#-Z?3<(>pts+uC}wCtnZ$?baeKGmnFZxdJ)+ttt{v8v$8YGmU`45 zpYXhFS)pHVL``1_J3+}?AtQL+ec5l|9~_%&Tl~2tsH|iD;ot~FoWYeilaw9;a6-4y zB&8hga0lPNo@E@OnTTHj&GR2kiaU%6*1$?$@wcAC7T;9%e5zM zZICzt`dj1L0B?_DQcjmPhPpt*8+h+=bsaKv8ouR=Ip~3&V7lOFHh3ecJ|Otbab)O# zat9CYN5Te^<5ekZhc{0c5P4^#Ovit^2R^Hi66FLC`RcN4kE&>i|51pHY6{;(tkuOP zHE&+}6{~IDmWFH3?S4o9SnRex6bNf6c{PB??{z3fh6JppTI5vssee3p*qDJU+AaR8 z7VtZvY`3Kqc#L&z>al>+TRp*z$*LDNAXt>#46N&-+G=)VOFzhBmhH~0b{aFbp@P@A zFyYJM0McO>XjiudZ{o_SW;x-(B;hV84SyxWK^!f&6^IPe$RsuFmWD2d8h$Mgs+h0Kl2(!#r(NGmpSFe3Gg$#smmi3Q3qv zhhKH{v`z^R-F=Muspz-ATf<1T9!~ri;A2b`&7KFaFL2oPF>@sqCp#pt6B`B`jVC<7 zo-$QQZDucSO*I0SszeeV0~q!WkA{_@d*tr=R~)x;?Wi z5r6u3lGVToQyk2pfSo&bZ?4~FikU|GzjUiC>aVz3SAL*|=9wwIKN5}6yW`mLheP0p zzAU&twz-}?YRu3p0G?UgL=0DIPfy+XhB0(ZI>f_AWPWAY;(}V*&fYHXk|IEPHSG^W zaco^j!KlXs6t)&B#&MW?B^Rpe3GZ!@^aP260IYf9YzVbZ$!S=*7IvV}h`*7>a8%S8 z(`c2B?E;;$E>obliB&)u<{)0;okwyKW1nb6R{qlN%yi8%1YJGPR+RL}k)|n~qi0uW zqne5J)^+!qmE7bdnG(q|dg5HhZ5yghn42lm$efqR`OFn&3}mH(EmLyx06{q6Vk`}3 z#oyQQnR;Lb3C^hq@{&pG2U+A<@g(6B=_Yc6XMd>=;Hn?TW(O*s?g0KV6}SXE@a4|f zIEnz$RV6#;L9eMT6x8E#mZTnn{+NHagESJ4uBHJucYazA>%TIz?Dn@7g_bNMu|RZwP$)K4JRd5Rta7DX*n%O7E!bDUH;a-kD#bXLZju->qf+*ShyO zm&13?Sjd(n=CX5sSxL+Sxk#6Eo7i+;hlVD6C>*`X+CV;`wprSfU(9RXr`JtW>XirL zp$JyKqc{$jIelY?O>U`eeEJ5}-O_b`3s)>fU?Crt5uRRTGxX@*Uv-i3B93$AWal&m zP={DN*^Y&c84Gb^ZZl~mPO_pMaU%UrYyCGM^4P)vlWdMzi_%o0iw#Ev)v=;N}5_(;%}@!lQb2Ym)svn!+_1} z8%Cd(hiqZTr-4aI@iebFOPIbP=G=dp3685gQ0T+`L)vej9Yd}&Ph1bT#W%mYNQiOw z>>@6MzNdA_Tm`J$P@(|5z}uv-tP1dUf49#*M*FD;NFkK-jxM_AN)mj6IV`mvJ>+;l zv0j>qhvGO3@7;S5)?(?;6gV2YliAYKzY~6=G?8S|N@DyY^0TvO=b|mm=$h=9s`iF4% zKxV{eo0$A@LCph&6u$F(_=vtPu*K8Gr!d{`t3YneIKR>{Sl(xY#X;6|!Y;7%t!4w-$OmhS#(XkZ61 zEG#wg24A#pc=|s<z{gzrdG`Z zh3bEG#--#Adv6P`*RMT&FPlD{*rGhoCuRq3{(k7vd{268bxO|FW-U;n?2yD&N20^o z*hVF{dXfs(#NIVgPTtj2^H5=+1|9(`(0x+$l+XzcDGXY;J?VNYV(-hrH4 zB&{;Bs!&XRK^!bPG$Wgv*Sn;ASxoPpx$7BSu9xtI)A(NUI@xmWTwXkl&D8a*e}Xm5u9^oLO}1zH#JySGbHM>K!og1C zW1F)@d7f|EL75)^q`#@t>c+J>*jWo84kKk%L`rlh$kBi&PpDH$aKa*R9I=E>5=fkP zJxT3uxq>=uZB1)w_VS(6TGqL~Rb{>ITv{tm>5cy|x2dd!>!KrMURVxFb&A=Qmq5Ty zdFp3Z;5cs#c=K0nfmU9uH_2Y=+;COfyRm(!IfY=+LN?${Ia}DkQ|8CZ3}B`p9>+Tv zYjDY2EZ*lOSB0%h^#FD-*$!a(Q47+hV()P-XLiIJX|t1w2Mi=?t&Vt(P?Twudy2m*)K3aMd&CR%TmU{Mk$i3wZpO`^Rte%C^jRW^oh2b?~#~Qpjl1(qg8MznFip{cLlq zbxJTcNw7X=*-Pg_Qg^|No!KW;xtzq$9qNv`VxFTKVR|uQF}J3opju`~0Ejcfd7>nI z5T5D05)8M+lO&cy`eXsV-)!nW$eFc39TlA58G`#M6|r^)(O_yCx9F)d7NkGTBx0g5s-@1J}V4JAZO@iis|8#)1^6I2* zZ`Cd8EYD>^k$lN%HF3siHL|Sb8$IIa8diSD6oxa5tLAQ)9*=53FAlyqOcmp>m@Lng zcQf_a?0{t^RDVo(@iyv3rRP4mS)bT@o1pmVn{I6(hrW|*pPP+*1~{1}&uB}I7iQ9j%8`RZ`fn{z;R{jfJ{cr^KV$dRpeSg@zitQ&cl2tUWhx!{Y%yy$2!f zp%`lG4ERxDZ^W*^UMj{X?aI9G(`ztdDDT(O6tIHH4Ta4nul|D+d>BmhfDd;#giuW7 z=<%fvRDqJrYCDc)P}?6j5a1~JVC=y<0C;h`FqTMKRY|m?Pu`X?%JjU`>h8@>F|A3jbJ>lid#m2`qjqdvN7!i}O=&@JS{Pj3@IJT$SV{{=r#g!GeGhJ^VE}PnD)hy$%KU?en(oum#MAJiz z>dbJIlxk|ak+@Cmy))-%M}Qs$7d~|OaG8mKi)qYP33W|*^&hODvN;hlT7y&vg49RT zlD3({g?0Zr*o}h%vZRiT!&27!HWIGY-)>_$6@RM*RzgIlHk`;*3fKv6W0EI*BS9H2 z3iEV5*tt^TOUBNG?E&qa9~rJV--`l`&hB)JH7UbR3+}N_sA!#rhSPSS#wi&`8A)QE zS+(wD(5`3B4K2sGUEnBILi6-R%or(LQRyfF%o*J}WC29#O)E6k0fy zpCu2A6s?Dm;&~2YUR*<*I&jl;OGYK+J*n}f3g^fkv&(b+7L73-{J~2OOuXLDHtPAzTi}ea9BJS_v{+5GHqm)hC zuZSO$Z@--{*887|`1B&}x+&8emLrf5Jcp(~#_XpfqGcNP)|GKK!|C9`;%w!!? zGJ6V!iNo4ymv1W<{X7DRyA@aI-;o9+!4g6hz}wbr7ej?BiWecM*Q{YSl)JY*9C#x+ z5P<)Rn3S%Kd}d8Nmmg*zMYYVJ7(2yW`tcPbZ1KQ;i%+?DE5oUzNQr}lH|Kh8&T+4t zvw{16{Zy-lc0j$~T-SD_k$_rCg+vKaRi-D?D|mI1jy>8;wa|Nu@-z`AnxRnN5qyfxZMez9Bt zW$*F%dZfdZFJ_U}$VjqZd$0)7_z=xLTf6@DqG^+u$qUv}PI126X0j-+-L*4epYy{7$-N>;8Iiz+wDZDm}8YD;k1OPLg#5Ek3wv&B3sF@WvAx$@b4JD$ z{Q24*2QohAdPE?K`r`EX^XzG!UwGZI5I*jt_Qn*c%68*vJEB0dK)(9X%{8xD&hvng_$DaDihb! zjrz~Kk37LNQN}SNdjRX8J<~+mIn#~4G&E;yv_@JV6VKTfTY`pg%t{1v@fz^Km>w%w=gDb% z&6Qbk3VpXuiJ(|2&l7jedgNXZ0sdMzr$N3mBr! z|M3q(?RFjej$OV8`+)Y=8=HK^W-HAd#M!3|U&cxza=V(v=_4Qm* zp*l6L8Gegi>)Z6%^ozDF1-MSn9$TZ+U}@j?fD|sUt;Y|(^x|Gf*iOx_^sZZNO8&CS z|4vwoHJD_PQOU4sE5_z3-cJ!Ge{;d-%tMuoNOTQ^wT>7omZaO4$KL;SL0uv6Lgu~* z#0)%nPEcp2t(L}qr(iQLobbkkFq^!Z_zfEK&X=>T>a8Z2lJRyfKv?;#Dbj=ML9*8H zp5TqZ2M{DanvQYO%DMK-%9Pd)0$>&^*@bDuqx+0{ThoOo@sG_?M#c$N_KQI=h)j28 zUydP&EhwukN)-@f?^;t4Qw#ks?7ewh&iVWIJ;qM>nGnsBxZp`^6PM6wMAZDeFG zTb8mGr5c*XnJv{Q#u#H7V`A)*IO#iL42FgX*`=Yh+|T2EV&-@Mb3g9;zOKjP`dt@) zd>>y7&hztmzmMg0yk4&ZX0Jk{fNLu6I_-G<(uWPpT>KLsQ3g%uOw0Pv4O@<$MaXQ_ z>zinA7Vx8$g3;LsYQ5sv2Q6}}<$5AyIGyEWLsRMK+|?awQ+aDgvQQe>XHu#F5*<470_@3hnnF}01g8UTsO{L!{kf$26$(vzNV zoWAH)Bb^`2HV`KTI{~tMvC?u?jUeMhYXbQ-H^%>_hoU4#8*%}7#oU4g;&(u|4ERiZ zC1G+OsWyK%5>#TRATjYDfj}L)heNIcs(9j|MXM5`Bh2otj=|IqItN-SdVK8gnNekJrO5e%|GkPar-Q|K}q~ zRLND+fN-joyzWNWpa8E(JKL1==@^k@Jo%E&9{(|tq{Nz+Z2)HfznGlNF#sX>_hm9qiv?#og zLvccI$)S9QUl-IRHp%^KM*Sn>=K+HaPx2DDe{$V?$BH^Z8Xyf$yuuNY$lAFi9{<30 z?HRlzh=2@^V{Z%)B1<%XkjGMJ4XTF92}qe~0&$o2g*yxDbZU%1ihKr_zjD;z?*&F4 zqN35)L6Iju!hK9t2274^jSV!j%>oZx?=CZQhfmQfcTTA9^af6JypKc}#Fnk`T$1qe z;W$vBe3M)~q%H}JubQ%y!`6DHj9Z=J0sJ8_p^r%4@!5o955KX_KW)PYDgLc-utSh# zLVZexM-TlDL=_yR1h$e|Uk!uCO20D9*e}w@1Wy;R;|hmwe_OPgIF6J|utWZb5C)I8 zvLBeTsLG}{B92gUPM=QACuQ!4dKJbhk=_V@JzcrUshVh({mF@ABBe?Y$xWRVH9E%y zinxTS`MSwn37->fZsxoz=Br+U>Ce5_R1%nXgw(2(WaMf>E26oSrPMuS|H4MZ8(s)R zBM53{%ywXjYPZ-0nQ}MVk8r%|9Gl}c$wo?sEp5DBK5eZ%22+6(Pqk?8&f}yLZBSFE zu|&{Jep~q1nx-Ui1nl z$QbB0>5?IX(3w|s3AXWn8?|^34c_)}GTy4&cJWf#iyjmfmZS#v+owcxixWE5S58BS zBXt}FhwutxDWJA)$MsxbM;Wd_e)^-)+rW*^AD$ri19dE>GeLSznRd{|RL~WRZ3I$& z6~C647+w?wtxOZNxepyW*8+#&Pr^Wq9@@}fuvaLuXh#NHCuCo?CI$mb?_EtO^AvKx zc9&CjqUPP-!`A2uHQ$zE=drOr}8PK2#SlIrNWT)Ac;DX}He#kX}jB|X99otV5c zSP(~z&?h$>=M8v^RbX5JjgD4*Y$GvEx}v9CrLRxt^pd*ZTimXP-w^CB;!t7(`X$(i zHhtsk*PvzRPYPk}qRSrw#lm#hw+d3%#AkDL*AMa+f>0?OMCJ%zTE|wkJ2j$V2i>Us ztCP_3KX~b*^hM-D1sy}!1Rk8QrRzfa@5Xmx)P!32MRp+RR~b%JgGjmFIPk3B;ENy7m?1uPZK`ZzNRv4 zp|l~0Trjj9v$#ztmwJ>)h81KWV}*%cX-=!HFCDL!d8u0fgV0<~m{JxoL&T3}*DAfR zlPaZ2+fEg<4lB_OLB{O-TuLY77lW2!HrIr5;1 zAeP-z>8R-;i23;P#m;Cse3$6M@xwD5t{XDy*tBUP$RMj+P8tL>i93_ep13iei>n-M zy2x$PfBfl43`DxmuQdw__l>+?8t7%15mGVJ26K&~~ohR*#;f7SGlFK5u1G z!3O_@mBuaHpnAKI_rZfEiWZ-s0J{~Q2>l9UMJNN*e$-26gs=gi9c~Wams(e4$2S#1 zR_NV##!)t~d8J9itL1Q6QETPHqo~iq&KIAv;Z{c0@@UAHQa=2bDj%y_#pW0j3Gtae zi+TWyKvkIys?BVzPa85qa6vyocwB1G1YEf{%9{8$+7%+DyLI-%i7oM{C1sFV7H--3 zRpj^)hW_TI#aaaHY0EfHBGU1*{Pgufy1&eQZTyQ~PSQ?rg5>_~*FTH1z@pY*DfGj7 zf;a35obQ-3GDxSn!M^85p)sslN<9T3mS*l>CGZ@U&cF3SJLGl2F8k!{wd}3dBDJlf z&c5OXGgDSheX;;VEQ-jO6cGQVjjej+q31Y!odPqMyFk4DLi!I-l3`-&z-;E1)nF3L z)`|S*bbdV#jP#-)GJ@Xpt&f*=-!Zi__hJnuxn;s+ONKBl-LxKu`UFjH#53eLiGD|W z=0a6>BoEW3duYp+n518h2X*>(dpGIaZny&wD*IkxKr8Db89Yv^Evy3^slJ1|00vBb z>Qr&``+6!vzUlGahXnyK-iHNg9Au&uQFhi>)39Mf#U;XbE8m!s_~85}0cY;m6tNio zWW~)U5l2PVBfP@0d~6TPULL8A4Xb!%oo_E_m-y^GeE3ik`*uWtCwb-VE6nK{`^Xu6*65r}!f(&M1j_qKluY$`vxXt+>-8;us8hJ(X@_x>q1w(&HP4V!CcP ztL;^=xJ5~nQ$>wToG^QbJ?~iL9Z}w*sGz#4SJAV=9Irw##uTSIc0Zi3dz(M8;4e)` z7M4L}#a;4&-eL?8M3DkPLg=S$oZ*pD^N-x+)j?+`iGsAa6NUq|Hi5q|2UwxBc$cJJ zvsB2*iED@HipSO-E}#l&k@OjIx3TaYfcl0xgF@gScNbtha$>3cvAn1tol61Ft1F}rPIHTg%W9teut;abstQ4PemO#@d( zYC_GouU1+uXPh4`--{@-G*Pm z*RIcwCx>N3cS%q~Xt?`lru|NhQg6q+O_MEar8jqDu$taXjUr@B2vNF~?)dQ!8Aa@G zh2@}~u(HsRXhX8rhT-&X9r5Z!TcwW@R%R6jul`k)Pa*=%ox*i?3f_mjr0LtYuUCgR z;CA_=nLbWs^+n5;7ZFE_U`WdwuW%qqqVSpE0Z0nC4yUksa+4Vg3oQ+@W1a5ml3L|? zIV9{Y-2dm)frUjsu0Hc&{mN#e8>HU*IdOFRj<;(U*X_TfVA;q^ZFb#j-*aHS#Mlkj5XOefVclC6m_?7MWaHg*0Q6`x4P6<>qMZ^u2@l1%}T0E zoT^s{577GH!GmiHk>_Rqd1z59>Yl=Z>sNXpZ}GmcX=WY5MlPN zhOAQIK!CCx7@}1|HvoTZitywWEm9as_PKPULC+?iP zyPf~ZZju?&#R|i2`bj-onsY1q3eXvnrJG`rIB0EQ<;#K`VUJE&^ym}))(x32ioLON z0G5X!U@2|U+76!0&DW)Ob@Ieoo45n5j)?^>XV^pUTSZk(gQ7$YWE?Bu5IV6*%)k3{2WEQAfDvsi~3ro7);o6yGO6V)v*%_WkdK0JK8= z1^~@kyHPh^EIScf>=EKHw%h|q+Kl@M$V3dEmF4AA`GxXp1x$*VkwDL$8e`jSu!#r4 zFDExK62%p5eeOh?>LHBuNl&4sbi0KML>VAVQYGmT`vf&F7jN#Yd5=uhLy$qhe~An* zu*gH8JrGbnS%tSb;0*;e@a1T|^AnE(T67)=mGz2_QF_htI3apZXo8MajD*)tT4+k% zfvR+T92V9Z--t~);N1ikgiFS}`^JrjW{FRc(n&CX?p!inT1LP9D z!7h#H_dqwHtays#GTTkpF4te_d4V+y63D|s0T1_j&9CSZJfj=1v+n1?ZR zP)iAZ{@FhNZJPKW9s-WWTmA8t3-CT1{I?1Rla+2j1nj?7JU`2o@G8oIVX3seMM;yl z9{cL-B-z;`ORt1Kq8R4lvv>Xac(JjK$6U+y5mPVNkQk$tIY<+KE@kV&23$a8aD=CM z+Oji@2g|sh`j#>dFl$ z(c2%o)R?9KQRIsiC* zvX+dgW}Qy@Ivqc=6JKUd1Ibs&-h}Y7NqqCpC56zplv=+;XPf6oW)u%G*?%QW1X1`a zv%SZUyT7fBlKy^OAR;qz-(qtLZX%zz<0>#WzU_5)mO=d1UwwFfi}?3CC(Nr~JZ!ry zjQ&O#f10_5L67rN;yaP!jh=aA*9>jPMjC<$m)8yk0ZLMrXIP>xh2w zbWfmE8CwN>Q=V^Z`S@a+`tbZ#OpLa&(4lj(2i+}vqmhb%( z+-rRut#F=dkoh@F$_SCYR^}p?hpDhZW(kXbUD2mEQIpq8E>-0xLzIFgQM4oli-qm! zyLcIs{@q40owHuX-1!r;ko4_c3q=fUCHg;wWTNnrZ=PGRVB@*Yil*tnYnSds{L)%Y zSWrVKM56l?+HvN=gJ<6a{gCuZ2A_067Y!|wJvfJm{f2vsiC5Y(vaqvQoT(4bZxQsX zW}7GE(JA@BT(N9<>cTV`Z8hCSQsdNExG>b6;4$({*Up|bb;6$cfZ_LIbDkbGkD<8l#{ppey5q&pZrgZby&9vddyrLu4hmlQYgYZQT1! z;1Bs)B!D-Of2G7c&LOB=@y-l-WL_0bOgph^QXih*y460Y+CDz_u5@V-1rr+t zPElf-dUB~bHE%2nEbka5d{ZN6YC|FbWxb)(1 znRtFd%ghTU+NlrEZ_RF&8L>I%c8PemLOC0>YOmcUAz|0zQp>4%V?vS`676~6gG^{| z_7Gb*OFX@fB6vPAfTP3_#;ap!|HSzDL%Ve0*{YgJ&5_~q2sFXOZ_`Yu+{H0HBqm;x z1`XXjjb-G3w*~L!Ng~A-VwFG>^3|(K+`k->)fKymdCTm48Ay zz!XzNn5pA`UCnhPK&h&Za*|SPgbPIp+k6~jd0ALLH1-(jCHDH1K~z0HE2---TpG+3 zrmV+uJX&@-ES7F@cQ;C1WsbxfRy-z3d71}B;Mj6D8&>4rIenx;4wkadfAxVmN+<(u zg_$|(*1gMQEt}lxC*CXbT;=(aal&eArsPu_)lx@D_#GBmTNcQq^Ic)x8zP z`>6}}g^RdLPQa>1WN#o0B}P>3u#(Zzjncp zuL)_Ry9hclRdBC4x5}Yt)(F<{aP;_w0nMl@;UPbPzro#S^1dEGjR_OC0fVorE>y6} z%8WQAOc$xpA@J@N8GF_^6s;zoW~?BBb7+Zikvl%!prmd%Z<+}y2^fzFK4$i>Uz(h~ zYFa%E-8Y6a`le;_6&7e}gy1P~Beypx!cl(q>x&DYZjQXWby4F`76^FtOP*}D;@HxE zG8A|bV_;C0Ry|UQ9>iN!nbQ(@hR2?w1N5#_5FqA`u=ZuVSk|LQuTA`W#1g}h?0Yx z`9vU5(G#$f$NH^`&tb@$eA{Be;%23HHV_a?(cXVQ!3{zK=UD`M#`^h}A)*nk6SSb+ zk-fjKu8S`-^Fw+`e2&E1HsjXQrm7a>>=9+uNcCcF{o)&r`Ws3Rr;%JG=6u{f3zv~A z8Z$|t%mAQ3$$M8Ut+A!;9>oPn@TS!+&2bXW8HUoAq};O?dxA^YU(fFRP1pG0Kn#RF-|0;`x!{@G#SOl&UBs_P)Cv$1$BLCe zAp!syclxk=hr0yngGBvHRa-F|Hx|?Y*DkR$-W;vcCZ8C^!CzuU3@}0BT=Y_ZJ&E8z z24q)_Ts@GPMty{v6CwmkNQB8A__PCC1G7A%IDnYm3NUq?i*TD(8v`x8$7OuVn@ze0z4en3cRy;2tf6;j6l(o7t^=e8ble z3Xxlj-UfQ!l=>pJg&Hrj9hx9daq-y#bfqmUj}Qla)|nZIf>f+&XIZMMkWWmSprDY+ zsJyVp*9Yj6Dd8J%$U;JtwRk^&a!-g@xk4wTj->h8AH`Ud+y?8ptRpWzqN0P0LU+s3 z*UU9kVP<$y0Od_~+4$U^y?Y@>R+Ns?1%y?WUKVG0m2e&&l<5Xc^nTS9p}M#F>iFSr z^6JV~zOGBkIuQrOM*zhbytM6|IHrC2iDGV0Z4ydHXZ+vi8I9cKANQbo*%UFgpk$zq z5jW!FJMC4|vsg@2s0L&Z-i8-;V15&QCM9-*r=*D6;>lXPwoBVqFlFbRvvsFCy%QGQ zVkCT)=Bo5|0x!PuS-ecXe-|Tf4fT>>t3@& zIkDjc5QbH%gk9Z{WF?>%4{_?>1)KGFUPxz$N;{4Wr-#6c5)Ek8P&$w!d84VUm3JQH zTSuG`wcU!WaWlPFuch1C?c?03@4(+43tFxB%V^Ukg#&oI1^mfHnIBTp9iDH9)8gVt${2BNK+dI?+ERmvVray zT|=>@u!0{^VV!#Z@<^I{hj7EUXZJt!tI9l53{pSyU6QEU+t+FrnmA)q5XR&{;uYw6 z^6`9c*~NwAk9dSgRwK3SIE=&AP73&t?+>Ucp45QsR|3p!O;h6HhWeZw&7)9$26xX( zdCvb*=>67=P{eMtZxan$%&)1#rUfN!10i=?a`8S{625ZHqTZ)`;^zB!cu-5aHA^2^ z<_(Q>$DL0L#P(-=*781m#H(psV_`~DB`J?u5G7O#nCuVrvx=zh8*F#WW8K@h`~H=( z-tPS?eSb^;SH7i@z==izBTk(@44CRT~iaAx*FRdt%X7$o8p)9iI^sW?WO7g@SA-|=HiWdwKTM>(Hs{iRv5et{Z!tuI$ z6?UEH{}j(G5jtAcljH;p%1SS;sCL7W(^L2$2w7g_3)BV!eMW(3bZGS%)T7(3ywgKu zC27r^qV4X?W`p5vJBQZ~Gi58sn9n~yA(nWVzx=W~42wE(5S22jbIAZ%>>{d!x24k% zH`+mLfBx=S-0O(5tS!Eq?19vs`k#M{lBZw%nLB(lS=J+K{A9K2gq*eKFIiKpFd_e9CDXY(Az}|ZUTcrGAZVs$4FV~i=Y|2F1b6V-pL}A@L7oA5 z!0btY``rxdth6OkLIu?+iKyOu(lBO6hIvH~>P1gsc+>M{B)bd3zDNz8!p)g|f`5vl zTULC4J;uKBdD9bQ+!S7cpo>?pnku)vY@pGPk#C8sr~qJ*$69OD;X;M{`>D5G19Z@-aTLf#&&O;hun%UWrGp&jCT#g@McU%=NX~+C_guI zC?u-lvb#RW<0+{5$JmXVmlo2SZw7-AfP=;lPqxd}X`ypAsGWy%8V zfvxwAZIt=*&yiVTKQ?=Ivyw9(@A2az2yex!C`xTAd-YdM^9()LU~@FS>7a=4eP)KQ zkX_e#4Ap@r4@L8j(Fg%Ao|SLL)-JV;RZ%`ow(JVz)qy~(Y6N_EkIYeNGFa-FBM@fq zHD~S^V=a3?GaGLW-3`Cy%-(sP-J^mpx@%`RMco(jF-=tO8`*2mdk2Piyq4XMI)kF> zs@`k58u@rTzVf9l#lB%$D$b|gPO;^;8p_T6TbN0L70pNsf7bA#Z`E>{M3_EH8#vrN zgXJAVPGA`vBeRBm`#w=-gE0(BKAR9$`)$i?<0PBhM5pquO&)xdaQ5#y38KL|o5F)g>v??6>)!02<{3~5KkFVeC)_ll?^S7+S*lR|GS3qW z(uKvxs=)@pKXDeuQnY-E;PKpCI0+m~##va1Va_6pUQj*$wz{p`o7U1){6B<~ptfc1 zxon*rUfxyoqDDqWZ_a}3w^18i(4%F1q_1By+SjvhN!f}noeRJ}#M$Vwwf|smTRhJB z7+sQOw-)}ukurQ}*ih@gcX{uAg2)Z2OXvW&6N_k?Vi{$eP2rG0Ne?jrq7QF7QrCzG z9<=1(>aKAxD-!P}|Z~hnT8pAYWS+ON+0B>~0cDxhM?ZAi87*5gjP99JprwmwE(& zfUr3l(=rs!q(j0EnWBaFw)70KNQP|#N&|p-D2}d};RAM}Ka4(R)|QT5SSAYU5z}`) z(9LZpWuTnNTC0ceiz2xdnTN-pgg(l!QCxDkBR~RsqTqM6oIZV9_X)Dk3J3yzxYvcNvW$AULEs0I^_*3jZ$GqHVeHmm*CVB7am#_ujvAI zEg24fC5)B$31>^{6 z6FYv|i^0K~`WK&Bvl9acc>tu%ktzhpisrB|lak*&+WNDzO3~?=!@QkekPbv}q1dB- zn!oJc%wCBC83AD?VpBkm2{~|{QR&GRtROmt2M&sVunxphd)Pk?h>se?)@qc7(+7-o zUo3G_WYC7tR*iK+dP|CjKiXZs1K4-gN05-do7u{mL6-9Jw0Ebu!;AAl3d_IUK5xAv zrjZyED2na;>ZL5m11cikN}7OgBJj913&e;n(3*K6SLAp-yj zGyM75Vuzy)WJ(vdDY>7KEP~S@fB$7~ibGj-rj~nYjAxm?Y0#+&hVT7#ZTXcvW~@Sn z?HjZ3R(-HNNoAhP7e4oddIRw1zZ!l4snEpeV=V}xGRelX4_d3tTS3bx4Z;7Z8sYwK zknix)C;Lyf&iMbnKSqllL+m_>5u{1%I7^4JLppVLBN>_?3%@D69bqV|p&ZAWyrCI(DvSl4RD`f!1elDdd!Vlo>gwDq4 zwg^mwLQOQVZ}bKWZs~k<=jaLU;xz(>cPJKs(D7NV;DsBduYfQU%Vb93fyyFYKL;7tm|PBt8az-x-I;-lwC2GzH+aF6Ov-DAqS;>7bT@1@ z^ta#Z6yI_KsVoLezP~NJDwKgIATY<;-kpcZI72=PMy#1DC;@%Mq#5iHppQ+EzhCa% z;=)(?O@Y^@jOJ@QR9BQ}`_6ythqsr0UC+XWro;DKmLgCxKNP$h3kn+{fSms?vLOY4 z4AJ1TK1UfJ+*0h7iQ4$hFg8PDa=2Q20jQ9P)o{@qV{x~CVb^*02y4AJ`k~n3QsYmz zBMb7g;vkH6ZPPZ5f>G`1Bq0pijD2tB61!#fKjfNH5mP6@DEUwCQ`5{xS+{CfAoeJv z*!cq2a8_VGV0m}e7X^|myC&8GXjr%~qfP6ee@+0fU?#K+9D&z8Cy(98qA8j8`5Gq#Dtq5* zQ{G~Ej?>T$-JyB0AxQ#Ssn8}NbHgg!$bL5KDchrAZv(G@;_t~BjY;qE4`p`<6&#oH z2eFK%4B&v=VtJwVQCfZWw-P709Kj8kHZMPK9aQo|bk^us<{WBr`9b-LjK)w2a5#Lc zn)BFYtGRJ105rYBH8-FyeQ1QRGe=C>iTc;}zvCgOj1M;MP{e{mEk0oSMMhhn=*!3B|3$bY>jR$^-(I=**JpL8>MP4HOW1(Y3dFdkN>tct&W z_}p9Sk%|CeEVQ?t|M<^ixPt{MpQ#rw>{wE#nju}2G2_Q)|N87yYb7wjF6He6x23aS zh+Vp@y%XDuioW>r5qByvEKn|P|CBK_yQ7G2RK?_D#1aclc`Em(c#Yi|z$<~Kxv2G} z2i9M+o$809vRh>DYVIgSB8QuL(cU%DUc||-zxa^Fu7u6*Q6xvSCfh>SUUB#?pu5pw z4`?0~myhKuk}cpq9WpQ8a75ycQ?&`z7ULEO0P_Uv(??49J8i_CjwJB+^mnubmGqee z#iO@tjAvLI{feiI4C~T`Ti(Qfp<>4Nx^eXXMn@;{)7bUt?Qb>n*JV`mtV1V`i_Hyl zR$MYLPNlplmK}yL3mAq}=b8Vy^3+`825qa8ZB5S&?-1MR?2jFtE?k{qnDKq1xu%+{ zEBn_!xvT$2V>-6_aLmFM8?V=%9@1(^i!Xlp>dV2o=^uTWefie+KmOA3%b~heKYd^D zwc+LXb)8~8z7DeUKG(b8_n7MKI=A%w=gKOnm8_SoY3Qb-#AVEIAi8*t@xYX&8@C3v zqXzP-!yFE>GphZ?W(3MJ7Z{xd&HXrvb+VAWwht{Vgd@;$S})DZUNPFFn{&z0;#9ah z6?lO&#p0}t6$=@Y{b5{uq^cp$`s2@r4j7QSZYYHp}pE`eK9jnWR|x(67Q(2 z*3|bJXAuAkhlt2Nfs7OVsia=m)x2`Wd6JC~dmLlQ(tKkoq_5crX}#<7-UJ>XsqDpuUUWfzltdqsAq?6ylEYVoB!g_8v(jA!-tF(WxV z`u@w;2JNHUn?rg&NbtFDhjgP|kHf1mkIFB9S$*_&Rf!|K!}45jQVFpyHa?AywdzBC zB{|~wi=f7VFbS|s9^=f_S=WqU!1q&mmu;9_Lt6y1 zo)+{Aaezu&b|AdXr3cTXF=E~D#o@Tm)e%bc3@yu&#S>qA4Rj|@)=~s$hsqcET9=gm z?d5btD!t1=3%$|$8 zILViWquno#ug6PO9oDe=`I~yLy+22c!?D4dk>>6$d2+FtszJrfe}v1V%pEBo$!K(% zL1OMU_vLj&^;1=;P26YLInkw2qZFR9f5s}V|Du;Nj#e&R6yJCT5fq`#Jk?kxDis;3 z*X#2ILqtrwMJ!-|D5!`)X`t)vlXcVOb4iRXVCq8zfyl?ADOW1ggZ{EEyhvxF*8Q|- z(L&FQf06GU*kIL%Yg~CQ6^_0Um7geA&&B2UGqmAH^4g_Z+@fm9{CN2Xi9^yBI@Wo9 zD~3f?*1NfGUXPs$!~`R`q84A85Bh`M+G0dzd~`jAs{*TL zSbc6Ic01Gy5NuAI*fFAk^ebFd$a7E$_)q4T;+`Uz;l6N|m_akxH{J2zGNIj@3<1Xr zQtQ|WK~3q_9!8dxg(-t|*$Lt0<>ja368YdBT@4#>fn3`^d%2h?u{PeR%2*Zk<(2(b ztWI5>18x3$LU$i2#7=X4ha{i+sjAXRlU2RA30F1zc?-TYADW=R*JD&BWw~Xd=1|fJ zE%rIPQqXTxu1yrA;GW{2Dfv?i?X#k9e@CBz&@Z)R8wT zBteBSOZ!(6e(EYVxRbX!A1doyjJYTi0r8mnyC1Ns&;H%{dr}$UW+Dk5^TjLHuW)l zhB3_*df*mNSnCzItGnS$qeT?}YPW@qMp zUCcCyy$GYrYy{qYsu6JGo`o37Hr*B(0R4MPn`v5ZGAEOwtaCvksO7QCaGfSE@ zi_1_nq@+Z4w-5LlS@u1WY((f}WzaT{R-r`F4Nu#2MZ;`hl4xP^8x8kd^$$ENHTF;oMIn1gJ}g4`Q7sR)}o$(2B^8`;xJO+KKaF zJdIU8$6a4fW_-EBaS;&D8P{%gF3cdOOCbi#owbJYVM7lY0g^z#{$k@&v8t|JBY1(a z6fqM-@4)-G-I0HcaTqWl?xM^zfdi_W`mZKu@h5)&-S}O#@jfU>aq)kAP}mw#6{b`e z_u1PLyf3=9th&ch`%QI)8(DqeGPR&kL5Pe}-`xryD_ApiE<8#N$KmzU9x1G`00S1t z(|#Fu5qHQ$v6BG(BuZkzL;3rcv)3LJj^muS50k2xHHx^Ho{-;!#J@!lz;TWBh39S~ z%S~=A^V?7uX}6s4(9mK~h;cK5eD2#>KKzNl&2r1BIU#{s-O#pk?=+yYCOZ*t35-M; zuKbKvTsUg2GYyKLOVLXpV2FccBp9j?VHF5*q%7YQxqu8G-n4D+pUC?s^8SpxzcugU z$p7fbs+j8!A6h9}%cp-i>skYY;Bg) zQ7~Nupr&Sbmvupq{GDY;sT4zGp+xb3PT3Iy6ft}4nEP>o$SKJt8rL@3$ARVFXm-Y0 zQqTjOfJF_HdK8~KOstQ@11BI(94Bh{w$;WsAPKYVW)sl^Gde~?=TzQ|VGo@YW|3@d zX+bxGlBZuhklJT^Z1mvi16;(Dwrt6^f&|~#++lTCVQeo=XlN+;_~gsn^71}KVjbb- z?H$NKBj-pe9__+o(%?creD^5p_Uw~N~$7r~?%TOK)e$UTX%WOPw@9F-0 zaSFrMvi23$4!+OyK_;<&0Avs;s5}A0DAd0IF%mUWJ)QnUJBzT zs%G}dfG8GwILUw<1TD5Ts8AS-gn5uq4|W=Yvg=Fu1)>YU%1JrIvWCifCIgz;W8yz~ z%LNFUF(Z!V?p41rzKrr^7vuf=Q*?m;uRgnob+oYGL`m@NB}-)qj~e-1Nz<&BtOD6T z!}?2FUzzyuGo%wWyBe9C<{=pP1P01GV_Z0S433 z+9E4f!YumX5e)#|B2&AASQb8@Wd>VETK(%0DQ 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 + + + + + + + + + + + + + + + + + + diff --git a/nodelet_plugins.xml b/nodelet_plugins.xml index e440f931..98b339b2 100644 --- a/nodelet_plugins.xml +++ b/nodelet_plugins.xml @@ -119,6 +119,14 @@ This is my nodelet. + + + + This is my nodelet. + + diff --git a/src/nodelets/undistort_depth.cpp b/src/nodelets/undistort_depth.cpp new file mode 100644 index 00000000..c64c7e1a --- /dev/null +++ b/src/nodelets/undistort_depth.cpp @@ -0,0 +1,123 @@ +/* +Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Universite de Sherbrooke nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include "rtabmap/core/clams/discrete_depth_distortion_model.h" +#include "rtabmap/utilite/UConversion.h" + +namespace rtabmap_ros +{ + +class UndistortDepth : public nodelet::Nodelet +{ +public: + UndistortDepth() + {} + + virtual ~UndistortDepth() + { + } + +private: + virtual void onInit() + { + ros::NodeHandle & nh = getNodeHandle(); + ros::NodeHandle & pnh = getPrivateNodeHandle(); + + int queueSize = 10; + std::string modelPath; + pnh.param("queue_size", queueSize, queueSize); + pnh.param("model", modelPath, modelPath); + + if(modelPath.empty()) + { + NODELET_ERROR("undistort_depth: \"model\" parameter should be set!"); + } + + model_.load(modelPath); + if(!model_.isValid()) + { + NODELET_ERROR("Loaded distortion model from \"%s\" is not valid!", modelPath.c_str()); + } + else + { + image_transport::ImageTransport it(nh); + sub_ = it.subscribe("depth", queueSize, &UndistortDepth::callback, this); + pub_ = it.advertise(uFormat("%s_undistorted", nh.resolveName("depth").c_str()), 1); + } + } + + void callback(const sensor_msgs::ImageConstPtr& depth) + { + if(depth->encoding.compare(sensor_msgs::image_encodings::TYPE_16UC1)!=0 && + depth->encoding.compare(sensor_msgs::image_encodings::TYPE_32FC1)!=0 && + depth->encoding.compare(sensor_msgs::image_encodings::MONO16)!=0) + { + NODELET_ERROR("Input type depth=32FC1,16UC1,MONO16"); + return; + } + + if(pub_.getNumSubscribers()) + { + if(depth->width == model_.getWidth() && depth->width == model_.getWidth()) + { + cv_bridge::CvImagePtr imageDepthPtr = cv_bridge::toCvCopy(depth); + model_.undistort(imageDepthPtr->image); + pub_.publish(imageDepthPtr->toImageMsg()); + } + else + { + NODELET_ERROR("Input depth image size (%dx%d) and distortion model " + "size (%dx%d) don't match! Cannot undistort image.", + depth->width, depth->height, + model_.getWidth(), model_.getHeight()); + } + } + } + +private: + clams::DiscreteDepthDistortionModel model_; + image_transport::Publisher pub_; + image_transport::Subscriber sub_; +}; + +PLUGINLIB_EXPORT_CLASS(rtabmap_ros::UndistortDepth, nodelet::Nodelet); +} + From 40aef97ea3dc4efeca8de5452b5445daee021022 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 5 Oct 2016 18:13:03 -0400 Subject: [PATCH 45/78] added default color for ground/obstacle map clouds --- src/MapsManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index fb5a65a1..fe4f1565 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -827,7 +827,7 @@ void MapsManager::publishMaps( assembledGroundPoses_.insert(*iter); if(jter!=gridMaps_.end() && jter->second.first.cols) { - pcl::PointCloud::Ptr transformed = util3d::laserScanToPointCloudRGB(jter->second.first, iter->second); + pcl::PointCloud::Ptr transformed = util3d::laserScanToPointCloudRGB(jter->second.first, iter->second, 0, 255, 0); pcl::PointCloud::Ptr subtractedCloud = transformed; if(cloudSubtractFiltering_) { @@ -868,7 +868,7 @@ void MapsManager::publishMaps( assembledObstaclePoses_.insert(*iter); if(jter!=gridMaps_.end() && jter->second.second.cols) { - pcl::PointCloud::Ptr transformed = util3d::laserScanToPointCloudRGB(jter->second.second, iter->second); + pcl::PointCloud::Ptr transformed = util3d::laserScanToPointCloudRGB(jter->second.second, iter->second, 255, 0, 0); pcl::PointCloud::Ptr subtractedCloud = transformed; if(cloudSubtractFiltering_) { From 0464da6a0bf8265538b1e7c3177dfc81bc6cc892 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 11 Oct 2016 12:20:13 -0400 Subject: [PATCH 46/78] fixed az3 stereo nav global costmap namespace --- launch/azimut3/az3_mapping_robot_stereo_nav.launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch/azimut3/az3_mapping_robot_stereo_nav.launch b/launch/azimut3/az3_mapping_robot_stereo_nav.launch index 63e9f486..9deae37a 100644 --- a/launch/azimut3/az3_mapping_robot_stereo_nav.launch +++ b/launch/azimut3/az3_mapping_robot_stereo_nav.launch @@ -28,7 +28,7 @@ - + From 9a8538711ddcaf8895ab6dcdd54f2d92c9c6c674 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 11 Oct 2016 12:52:24 -0400 Subject: [PATCH 47/78] fixed deleted database even if --delete_db_on_start is set --- src/CoreNode.cpp | 1 - src/CoreWrapper.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CoreNode.cpp b/src/CoreNode.cpp index c3dc79a6..c89c8443 100644 --- a/src/CoreNode.cpp +++ b/src/CoreNode.cpp @@ -42,7 +42,6 @@ int main(int argc, char** argv) ros::init(argc, argv, "rtabmap"); - bool deleteDbOnStart = false; nodelet::V_string nargv; for(int i=1;i argList = getMyArgv(); char * argv[argList.size()]; - bool deleteDbOnStart; + bool deleteDbOnStart = false; for(unsigned int i=0; i Date: Tue, 11 Oct 2016 14:39:15 -0400 Subject: [PATCH 48/78] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0eb1e7ba..dad6d8d2 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ $ sudo apt-get remove ros-kinetic-rtabmap $ sudo apt-get install libqt4-dev libpcl-1.7-all-dev libdc1394-dev ros-kinetic-openni-launch ros-kinetic-openni2-launch ros-kinetic-freenect-launch ros-kinetic-costmap-2d ros-kinetic-octomap-ros ros-kinetic-g2o ros-kinetic-rviz ros-kinetic-cv-bridge ``` - * [GTSAM](https://collab.cc.gatech.edu/borg/gtsam): Follow installation instructions from [here](https://collab.cc.gatech.edu/borg/gtsam/#quickstart). RTAB-Map needs latest version from source (`git clone https://bitbucket.org/gtborg/gtsam.git`), it will not build with 3.2.1. + * [GTSAM](https://collab.cc.gatech.edu/borg/gtsam): Follow installation instructions from [here](https://collab.cc.gatech.edu/borg/gtsam/#quickstart). RTAB-Map needs latest version from source (`git clone https://bitbucket.org/gtborg/gtsam.git`), it will **not build** with 3.2.1. * [cvsba](http://www.uco.es/investiga/grupos/ava/node/39): Follow installation instructions from [here](http://www.uco.es/investiga/grupos/ava/node/39). Their installation is not standard CMake, you need these extra steps so RTAB-Map can find it: ```bash @@ -94,7 +94,7 @@ $ git pull origin master $ cd build $ make $ make install -// Do "sudo make install" if you installed rtabmap in "/usr/local" +# Do "sudo make install" if you installed rtabmap in "/usr/local" $ roscd rtabmap_ros $ git pull origin master From cf11f38cf8867fcc6cbe7a3295edc59668da399a Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 11 Oct 2016 17:33:04 -0400 Subject: [PATCH 49/78] Fixed msg headers not existing error when building CoreNode.cpp (#126) --- src/CoreNode.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CoreNode.cpp b/src/CoreNode.cpp index c89c8443..99d3ad95 100644 --- a/src/CoreNode.cpp +++ b/src/CoreNode.cpp @@ -25,7 +25,8 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "rtabmap_ros/CoreWrapper.h" +#include "ros/ros.h" +#include #include #include #include From 24d113fb8f5de94769941801380bfdb43b2a9925 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 11 Oct 2016 18:18:53 -0400 Subject: [PATCH 50/78] MapsManager: Don't ignore latest signature (-1) if the grid is created from 2D laser scans (so that empty space can be filled for the latest data) --- include/rtabmap_ros/MapsManager.h | 3 ++- src/CoreWrapper.cpp | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/rtabmap_ros/MapsManager.h b/include/rtabmap_ros/MapsManager.h index d4af081f..536b038d 100644 --- a/include/rtabmap_ros/MapsManager.h +++ b/include/rtabmap_ros/MapsManager.h @@ -73,7 +73,8 @@ public: float & yMin, float & gridCellSize); - rtabmap::OctoMap * getOctomap() const {return octomap_;} + const rtabmap::OctoMap * getOctomap() const {return octomap_;} + const rtabmap::OccupancyGrid * getOccupancyGrid() const {return occupancyGrid_;} private: // mapping stuff diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index d968b8e3..e0143544 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -53,6 +53,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP @@ -1140,7 +1141,8 @@ void CoreWrapper::process( filteredPoses.size() == 0 || rtabmap_.getMemory()->getLastSignatureId() != filteredPoses.rbegin()->first || rtabmap_.getMemory()->getLastWorkingSignature() == 0 || - rtabmap_.getMemory()->getLastWorkingSignature()->sensorData().gridCellSize() == 0) + rtabmap_.getMemory()->getLastWorkingSignature()->sensorData().gridCellSize() == 0 || + (!mapsManager_.getOccupancyGrid()->isGridFromDepth() && data.laserScanRaw().channels() == 2)) // 2d laser scan would fill empty space for latest data { SensorData tmpData = data; tmpData.setId(-1); From faa9e7d089c86683f2920e6eed0b5ad1c49b826d Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 12 Oct 2016 16:23:54 -0400 Subject: [PATCH 51/78] Updated warning message when not receiving data --- src/CommonDataSubscriber.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/CommonDataSubscriber.cpp b/src/CommonDataSubscriber.cpp index e8cb84a3..4234da96 100644 --- a/src/CommonDataSubscriber.cpp +++ b/src/CommonDataSubscriber.cpp @@ -370,10 +370,12 @@ void CommonDataSubscriber::warningLoop() { ROS_WARN("%s: Did not receive data since 5 seconds! Make sure the input topics are " "published (\"$ rostopic hz my_topic\") and the timestamps in their " - "header are set. %s%s", + "header are set. If topics are coming from different computers, make sure " + "the clocks of the computers are synchronized (\"ntpdate\"). %s%s", ros::this_node::getName().c_str(), - approxSync_?"":"Parameter \"approx_sync\" is false, which means that input " - "topics should have all the exact timestamp for the callback to be called.", + approxSync_? + uFormat("If topics are not published at the same rate, you could increase \"queue_size\" parameter (current=%d).", queueSize_).c_str(): + "Parameter \"approx_sync\" is false, which means that input topics should have all the exact timestamp for the callback to be called.", subscribedTopicsMsg_.c_str()); } } From 37d805daf25e259eaa4dea0dc5ae1d1e9ee7c94e Mon Sep 17 00:00:00 2001 From: matlabbe Date: Sun, 16 Oct 2016 22:15:56 -0400 Subject: [PATCH 52/78] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dad6d8d2..28faa00b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/ros/kinetic/lib/x86_64-linux-gnu ``` ### Build from source -This section shows how to install RTAB-Map ros-pkg on **ROS Hydro/Indigo/Jade/Kinetic** (Catkin build). RTAB-Map works only with the PCL 1.7, which is the default version installed with ROS Hydro/Indigo/Jade/Kinetic (**Fuerte and Groovy are not supported**). +This section shows how to install RTAB-Map ros-pkg on **ROS Hydro/Indigo/Jade/Kinetic** (Catkin build). RTAB-Map works only with the PCL >=1.7, which is the default version installed with ROS Hydro/Indigo/Jade/Kinetic (**Fuerte and Groovy are not supported**). * The next instructions assume that you have set up your ROS workspace using this [tutorial](http://wiki.ros.org/catkin/Tutorials/create_a_workspace). I will use kinetic prefix for convenience, but it should work with Hydro, Indigo and Jade. The workspace path is `~/catkin_ws` and your `~/.bashrc` contains: From ea859291ed8b690946265d2f9fcec436f58a02bf Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 18 Oct 2016 11:49:56 -0400 Subject: [PATCH 53/78] fixed MapsManager's topics not remapped when using rtabmap nodelet --- include/rtabmap_ros/CommonDataSubscriber.h | 3 +- .../rtabmap_ros/CommonDataSubscriberDefines.h | 10 ++--- include/rtabmap_ros/MapsManager.h | 5 ++- src/CommonDataSubscriber.cpp | 11 ++--- src/CoreWrapper.cpp | 9 ++-- src/GuiWrapper.cpp | 2 +- src/MapAssemblerNode.cpp | 7 +-- src/MapsManager.cpp | 45 ++++++++++--------- src/OdometryROS.cpp | 2 +- src/nodelets/rgbd_odometry.cpp | 6 +-- src/nodelets/rgbdicp_odometry.cpp | 4 +- src/nodelets/stereo_odometry.cpp | 2 +- 12 files changed, 58 insertions(+), 48 deletions(-) diff --git a/include/rtabmap_ros/CommonDataSubscriber.h b/include/rtabmap_ros/CommonDataSubscriber.h index c915bccc..9b50bc0d 100644 --- a/include/rtabmap_ros/CommonDataSubscriber.h +++ b/include/rtabmap_ros/CommonDataSubscriber.h @@ -69,7 +69,7 @@ public: int getQueueSize() const {return queueSize_;} protected: - void setupCallbacks(ros::NodeHandle & nh, ros::NodeHandle & pnh); + void setupCallbacks(ros::NodeHandle & nh, ros::NodeHandle & pnh, const std::string & name); virtual void commonDepthCallback( const nav_msgs::OdometryConstPtr & odomMsg, const rtabmap_ros::UserDataConstPtr & userDataMsg, @@ -154,6 +154,7 @@ private: bool subscribedToScan2d_; bool subscribedToScan3d_; bool subscribedToOdomInfo_; + std::string name_; //for depth callback image_transport::SubscriberFilter imageSub_; diff --git a/include/rtabmap_ros/CommonDataSubscriberDefines.h b/include/rtabmap_ros/CommonDataSubscriberDefines.h index ac55479d..a88aa5ae 100644 --- a/include/rtabmap_ros/CommonDataSubscriberDefines.h +++ b/include/rtabmap_ros/CommonDataSubscriberDefines.h @@ -100,7 +100,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2)); \ } \ subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s", \ - ros::this_node::getName().c_str(), \ + name_.c_str(), \ APPROX?"approx":"exact", \ SUB0.getTopic().c_str(), \ SUB1.getTopic().c_str()); @@ -119,7 +119,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3)); \ } \ subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s", \ - ros::this_node::getName().c_str(), \ + name_.c_str(), \ APPROX?"approx":"exact", \ SUB0.getTopic().c_str(), \ SUB1.getTopic().c_str(), \ @@ -139,7 +139,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4)); \ } \ subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", \ - ros::this_node::getName().c_str(), \ + name_.c_str(), \ APPROX?"approx":"exact", \ SUB0.getTopic().c_str(), \ SUB1.getTopic().c_str(), \ @@ -160,7 +160,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4, _5)); \ } \ subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s", \ - ros::this_node::getName().c_str(), \ + name_.c_str(), \ approxSync?"approx":"exact", \ SUB0.getTopic().c_str(), \ SUB1.getTopic().c_str(), \ @@ -182,7 +182,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PREFIX##ExactSync_->registerCallback(boost::bind(&CommonDataSubscriber::PREFIX##Callback, this, _1, _2, _3, _4, _5, _6)); \ } \ subscribedTopicsMsg_ = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s,\n %s,\n %s", \ - ros::this_node::getName().c_str(), \ + name_.c_str(), \ APPROX?"approx":"exact", \ SUB0.getTopic().c_str(), \ SUB1.getTopic().c_str(), \ diff --git a/include/rtabmap_ros/MapsManager.h b/include/rtabmap_ros/MapsManager.h index 536b038d..5ca0c9d2 100644 --- a/include/rtabmap_ros/MapsManager.h +++ b/include/rtabmap_ros/MapsManager.h @@ -45,11 +45,12 @@ class OccupancyGrid; class MapsManager { public: - MapsManager(bool usePublicNamespace); + MapsManager(); virtual ~MapsManager(); + void init(ros::NodeHandle & nh, ros::NodeHandle & pnh, const std::string & name, bool usePublicNamespace); void clear(); bool hasSubscribers() const; - void backwardCompatibilityParameters(rtabmap::ParametersMap & parameters) const; + void backwardCompatibilityParameters(ros::NodeHandle & pnh, rtabmap::ParametersMap & parameters) const; void setParameters(const rtabmap::ParametersMap & parameters); std::map getFilteredPoses( diff --git a/src/CommonDataSubscriber.cpp b/src/CommonDataSubscriber.cpp index 4234da96..fc01376d 100644 --- a/src/CommonDataSubscriber.cpp +++ b/src/CommonDataSubscriber.cpp @@ -123,13 +123,14 @@ CommonDataSubscriber::CommonDataSubscriber(bool gui) : { } -void CommonDataSubscriber::setupCallbacks(ros::NodeHandle & nh, ros::NodeHandle & pnh) +void CommonDataSubscriber::setupCallbacks(ros::NodeHandle & nh, ros::NodeHandle & pnh, const std::string & name) { bool subscribeScan2d = false; bool subscribeScan3d = false; bool subscribeOdomInfo = false; bool subscribeUserData = false; int rgbdCameras = 1; + name_ = name; // ROS related parameters (private) pnh.param("subscribe_depth", subscribedToDepth_, subscribedToDepth_); @@ -201,9 +202,9 @@ void CommonDataSubscriber::setupCallbacks(ros::NodeHandle & nh, ros::NodeHandle rgbdCameras = 1; } - ROS_INFO("%s: queue_size = %d", ros::this_node::getName().c_str(), queueSize_); - ROS_INFO("%s: rgbd_cameras = %d", ros::this_node::getName().c_str(), rgbdCameras); - ROS_INFO("%s: approx_sync = %s", ros::this_node::getName().c_str(), approxSync_?"true":"false"); + ROS_INFO("%s: queue_size = %d", name.c_str(), queueSize_); + ROS_INFO("%s: rgbd_cameras = %d", name.c_str(), rgbdCameras); + ROS_INFO("%s: approx_sync = %s", name.c_str(), approxSync_?"true":"false"); bool subscribeOdom = odomFrameId.empty(); if(subscribedToDepth_) @@ -372,7 +373,7 @@ void CommonDataSubscriber::warningLoop() "published (\"$ rostopic hz my_topic\") and the timestamps in their " "header are set. If topics are coming from different computers, make sure " "the clocks of the computers are synchronized (\"ntpdate\"). %s%s", - ros::this_node::getName().c_str(), + name_.c_str(), approxSync_? uFormat("If topics are not published at the same rate, you could increase \"queue_size\" parameter (current=%d).", queueSize_).c_str(): "Parameter \"approx_sync\" is false, which means that input topics should have all the exact timestamp for the callback to be called.", diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index e0143544..6dcc94e6 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -101,7 +101,6 @@ CoreWrapper::CoreWrapper() : scanCloudMaxPoints_(0), scanCloudNormalK_(0), mapToOdom_(rtabmap::Transform::getIdentity()), - mapsManager_(true), transformThread_(0), tfThreadRunning_(false), stereoToDepth_(false), @@ -119,6 +118,8 @@ void CoreWrapper::onInit() ros::NodeHandle & nh = getNodeHandle(); ros::NodeHandle & pnh = getPrivateNodeHandle(); + mapsManager_.init(nh, pnh, getName(), true); + bool publishTf = true; double tfDelay = 0.05; // 20 Hz double tfTolerance = 0.1; // 100 ms @@ -331,7 +332,7 @@ void CoreWrapper::onInit() } // Backward compatibility (MapsManager) - mapsManager_.backwardCompatibilityParameters(parameters_); + mapsManager_.backwardCompatibilityParameters(pnh, parameters_); bool subscribeScan2d = false; bool subscribeScan3d = false; @@ -448,7 +449,7 @@ void CoreWrapper::onInit() Parameters::kOptimizerIterations().c_str(), mapFrameId_.c_str()); } - setupCallbacks(nh, pnh); // do it at the end + setupCallbacks(nh, pnh, getName()); // do it at the end if(!this->isDataSubscribed()) { bool isRGBD = uStr2Bool(parameters_.at(Parameters::kRGBDEnabled()).c_str()); @@ -466,7 +467,7 @@ void CoreWrapper::onInit() image_transport::TransportHints hintsRgb("raw", ros::TransportHints(), rgb_pnh); defaultSub_ = rgb_it.subscribe("image", 1, &CoreWrapper::defaultCallback, this); - NODELET_INFO("\n%s subscribed to:\n %s", ros::this_node::getName().c_str(), defaultSub_.getTopic().c_str()); + NODELET_INFO("\n%s subscribed to:\n %s", getName().c_str(), defaultSub_.getTopic().c_str()); } } diff --git a/src/GuiWrapper.cpp b/src/GuiWrapper.cpp index 96931483..48af6efa 100644 --- a/src/GuiWrapper.cpp +++ b/src/GuiWrapper.cpp @@ -171,7 +171,7 @@ GuiWrapper::GuiWrapper(int & argc, char** argv) : goalPathSync_->registerCallback(boost::bind(&GuiWrapper::goalPathCallback, this, _1, _2)); goalReachedTopic_ = nh.subscribe("goal_reached", 1, &GuiWrapper::goalReachedCallback, this); - setupCallbacks(nh, pnh); // do it at the end + setupCallbacks(nh, pnh, ros::this_node::getName()); // do it at the end if(!this->isDataSubscribed()) { defaultSub_ = nh.subscribe("odom", queueSize_, &GuiWrapper::defaultCallback, this); diff --git a/src/MapAssemblerNode.cpp b/src/MapAssemblerNode.cpp index ce63ff4a..1b8729f3 100644 --- a/src/MapAssemblerNode.cpp +++ b/src/MapAssemblerNode.cpp @@ -49,12 +49,13 @@ class MapAssembler { public: - MapAssembler() : - mapsManager_(false) + MapAssembler() { ros::NodeHandle pnh("~"); - ros::NodeHandle nh; + + mapsManager_.init(nh, pnh, ros::this_node::getName(), false); + mapDataTopic_ = nh.subscribe("mapData", 1, &MapAssembler::mapDataReceivedCallback, this); // private service diff --git a/src/MapsManager.cpp b/src/MapsManager.cpp index fe4f1565..d32f8d59 100644 --- a/src/MapsManager.cpp +++ b/src/MapsManager.cpp @@ -57,7 +57,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace rtabmap; -MapsManager::MapsManager(bool usePublicNamespace) : +MapsManager::MapsManager() : cloudOutputVoxelized_(true), cloudSubtractFiltering_(false), cloudSubtractFilteringMinNeighbors_(2), @@ -77,10 +77,10 @@ MapsManager::MapsManager(bool usePublicNamespace) : octomapTreeDepth_(16), octomapOccupancyThr_(0.5) { +} - ros::NodeHandle nh; - ros::NodeHandle pnh("~"); - +void MapsManager::init(ros::NodeHandle & nh, ros::NodeHandle & pnh, const std::string & name, bool usePublicNamespace) +{ // common grid map stuff pnh.param("grid_cell_size", gridCellSize_, gridCellSize_); // m if(gridCellSize_ <= 0) @@ -113,7 +113,6 @@ MapsManager::MapsManager(bool usePublicNamespace) : pnh.param("cloud_subtract_filtering", cloudSubtractFiltering_, cloudSubtractFiltering_); pnh.param("cloud_subtract_filtering_min_neighbors", cloudSubtractFilteringMinNeighbors_, cloudSubtractFilteringMinNeighbors_); - std::string name = ros::this_node::getName(); ROS_INFO("%s(maps): grid_cell_size = %f", name.c_str(), gridCellSize_); ROS_INFO("%s(maps): grid_incremental = %s", name.c_str(), gridIncremental_?"true":"false"); ROS_INFO("%s(maps): grid_size = %f", name.c_str(), gridSize_); @@ -155,23 +154,31 @@ MapsManager::MapsManager(bool usePublicNamespace) : pnh.param("latch", latch, latch); // mapping topics - ros::NodeHandle nht(usePublicNamespace?"":"~"); - gridMapPub_ = nht.advertise("grid_map", 1, latch); - cloudMapPub_ = nht.advertise("cloud_map", 1, latch); - cloudObstaclesPub_ = nht.advertise("cloud_obstacles", 1, latch); - cloudGroundPub_ = nht.advertise("cloud_ground", 1, latch); + ros::NodeHandle * nht; + if(usePublicNamespace) + { + nht = &nh; + } + else + { + nht = &pnh; + } + gridMapPub_ = nht->advertise("grid_map", 1, latch); + cloudMapPub_ = nht->advertise("cloud_map", 1, latch); + cloudObstaclesPub_ = nht->advertise("cloud_obstacles", 1, latch); + cloudGroundPub_ = nht->advertise("cloud_ground", 1, latch); // deprecated - projMapPub_ = nht.advertise("proj_map", 1, latch); - scanMapPub_ = nht.advertise("scan_map", 1, latch); + projMapPub_ = nht->advertise("proj_map", 1, latch); + scanMapPub_ = nht->advertise("scan_map", 1, latch); #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP - octoMapPubBin_ = nht.advertise("octomap_binary", 1, latch); - octoMapPubFull_ = nht.advertise("octomap_full", 1, latch); - octoMapCloud_ = nht.advertise("octomap_occupied_space", 1, latch); - octoMapEmptySpace_ = nht.advertise("octomap_empty_space", 1, latch); - octoMapProj_ = nht.advertise("octomap_grid", 1, latch); + octoMapPubBin_ = nht->advertise("octomap_binary", 1, latch); + octoMapPubFull_ = nht->advertise("octomap_full", 1, latch); + octoMapCloud_ = nht->advertise("octomap_occupied_space", 1, latch); + octoMapEmptySpace_ = nht->advertise("octomap_empty_space", 1, latch); + octoMapProj_ = nht->advertise("octomap_grid", 1, latch); #endif #endif } @@ -234,10 +241,8 @@ void parameterMoved( } } -void MapsManager::backwardCompatibilityParameters(ParametersMap & parameters) const +void MapsManager::backwardCompatibilityParameters(ros::NodeHandle & pnh, ParametersMap & parameters) const { - ros::NodeHandle pnh("~"); - // removed if(pnh.hasParam("cloud_frustum_culling")) { diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index bf60de1f..84a67f7e 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -322,7 +322,7 @@ void OdometryROS::warningLoop(const std::string & subscribedTopicsMsg, bool appr ROS_WARN("%s: Did not receive data since 5 seconds! Make sure the input topics are " "published (\"$ rostopic hz my_topic\") and the timestamps in their " "header are set. %s%s", - ros::this_node::getName().c_str(), + getName().c_str(), approxSync?"":"Parameter \"approx_sync\" is false, which means that input " "topics should have all the exact timestamp for the callback to be called.", subscribedTopicsMsg.c_str()); diff --git a/src/nodelets/rgbd_odometry.cpp b/src/nodelets/rgbd_odometry.cpp index 2f8d041e..d00a35cb 100644 --- a/src/nodelets/rgbd_odometry.cpp +++ b/src/nodelets/rgbd_odometry.cpp @@ -142,7 +142,7 @@ private: exactSync2_->registerCallback(boost::bind(&RGBDOdometry::callbackRGBD2, this, _1, _2)); } subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s", - ros::this_node::getName().c_str(), + getName().c_str(), approxSync?"approx":"exact", rgbd_image1_sub_.getTopic().c_str(), rgbd_image2_sub_.getTopic().c_str()); @@ -153,7 +153,7 @@ private: subscribedTopicsMsg = uFormat("\n%s subscribed to:\n %s", - ros::this_node::getName().c_str(), + getName().c_str(), rgbdSub_.getTopic().c_str()); } } @@ -184,7 +184,7 @@ private: } subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), + getName().c_str(), approxSync?"approx":"exact", image_mono_sub_.getTopic().c_str(), image_depth_sub_.getTopic().c_str(), diff --git a/src/nodelets/rgbdicp_odometry.cpp b/src/nodelets/rgbdicp_odometry.cpp index e717918c..01aee2c4 100644 --- a/src/nodelets/rgbdicp_odometry.cpp +++ b/src/nodelets/rgbdicp_odometry.cpp @@ -141,7 +141,7 @@ private: } subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s, \n %s", - ros::this_node::getName().c_str(), + getName().c_str(), approxSync?"approx":"exact", image_mono_sub_.getTopic().c_str(), image_depth_sub_.getTopic().c_str(), @@ -163,7 +163,7 @@ private: } subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s, \n %s", - ros::this_node::getName().c_str(), + getName().c_str(), approxSync?"approx":"exact", image_mono_sub_.getTopic().c_str(), image_depth_sub_.getTopic().c_str(), diff --git a/src/nodelets/stereo_odometry.cpp b/src/nodelets/stereo_odometry.cpp index d92b38a9..2b40a107 100644 --- a/src/nodelets/stereo_odometry.cpp +++ b/src/nodelets/stereo_odometry.cpp @@ -114,7 +114,7 @@ private: std::string subscribedTopicsMsg = uFormat("\n%s subscribed to (%s sync):\n %s,\n %s,\n %s,\n %s", - ros::this_node::getName().c_str(), + getName().c_str(), approxSync?"approx":"exact", imageRectLeft_.getTopic().c_str(), imageRectRight_.getTopic().c_str(), From ba1e64418c9c10366165dfaaa751260d045c4d2c Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 11:43:57 -0400 Subject: [PATCH 54/78] Added .travis.yml --- .travis.yml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..a62460e8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,44 @@ +sudo: true +dist: trusty +language: cpp + +compiler: + - gcc + - clang + +addons: + apt: + packages: + - cmake + - libopencv-dev + - libqt4-dev + - libsqlite3-dev + +install: + - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' + - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - + - sudo apt-get update + - sudo apt-get -y install libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-g2o ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs + +script: + - source /opt/ros/indigo/setup.bash + - cd .. + - mkdir -p catkin_ws/src + - mv rtabmap_ros catkin_ws/src/. + - cd catkin_ws/src + - catkin_init_workspace + - cd .. + - catkin_make + - git clone https://github.com/introlab/rtabmap.git + - cd rtabmap + - mkdir -p build && cd build + - cmake -DCMAKE_INSTALL_PREFIX=~/introlab/catkin_ws/devel .. + - make + - make install + - cd ../catkin_ws + - source devel/setup.bash + - catkin_make + +notifications: + email: + - matlabbe@gmail.com From 72b4fe04e895ae629dc6475edc63ee73adf37a69 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 11:56:33 -0400 Subject: [PATCH 55/78] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28faa00b..9dcdec52 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -rtabmap_ros +rtabmap_ros [![Build Status](https://travis-ci.org/introlab/rtabmap_ros.svg?branch=master)](https://travis-ci.org/introlab/rtabmap_ros) =========== RTAB-Map's ROS package. From 28abc0aace002546b51635f2ac8edc9dd9b0b352 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 12:00:52 -0400 Subject: [PATCH 56/78] Update .travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a62460e8..e227359e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - sudo apt-get update - - sudo apt-get -y install libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-g2o ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs + - sudo apt-get -y install libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs script: - source /opt/ros/indigo/setup.bash @@ -29,13 +29,14 @@ script: - catkin_init_workspace - cd .. - catkin_make + - cd .. - git clone https://github.com/introlab/rtabmap.git - cd rtabmap - mkdir -p build && cd build - cmake -DCMAKE_INSTALL_PREFIX=~/introlab/catkin_ws/devel .. - make - make install - - cd ../catkin_ws + - cd ../../catkin_ws - source devel/setup.bash - catkin_make From de8ef5f810c166661213a7f9b41dc94bd825709c Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 12:06:25 -0400 Subject: [PATCH 57/78] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e227359e..3304d213 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - sudo apt-get update - - sudo apt-get -y install libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs + - sudo apt-get -y install ros-indigo-ros-base libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs script: - source /opt/ros/indigo/setup.bash From e42a8b8a1545adca79146d3813352b8e8c9adeb7 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 12:11:20 -0400 Subject: [PATCH 58/78] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3304d213..5482708a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - sudo apt-get update - - sudo apt-get -y install ros-indigo-ros-base libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs + - sudo apt-get -y install ros-indigo-ros-base python-catkin-pkg libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs script: - source /opt/ros/indigo/setup.bash From 15ff53e0d7a8966e86298ea3ca2dbebd2374fbbc Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 12:29:01 -0400 Subject: [PATCH 59/78] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5482708a..69bf314c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,10 +18,11 @@ install: - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - sudo apt-get update - - sudo apt-get -y install ros-indigo-ros-base python-catkin-pkg libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs + - sudo apt-get -y install ros-indigo-ros-base libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs script: - source /opt/ros/indigo/setup.bash + - ls /usr/lib/python2.7/dist-packages | grep catkin - cd .. - mkdir -p catkin_ws/src - mv rtabmap_ros catkin_ws/src/. From e2563d4963414aed2583470161dd81025eabe065 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 12:36:57 -0400 Subject: [PATCH 60/78] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 69bf314c..76342192 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ addons: - libsqlite3-dev install: + - sudo pip install -U catkin_pkg - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - sudo apt-get update @@ -22,7 +23,6 @@ install: script: - source /opt/ros/indigo/setup.bash - - ls /usr/lib/python2.7/dist-packages | grep catkin - cd .. - mkdir -p catkin_ws/src - mv rtabmap_ros catkin_ws/src/. From c0b41e34d1251471a04c69eb7da116482511df4b Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 12:44:09 -0400 Subject: [PATCH 61/78] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 76342192..a8476998 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ addons: - libsqlite3-dev install: - - sudo pip install -U catkin_pkg + - pip install catkin_pkg - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - sudo apt-get update From d1ee48cc9e73f30b572d9fbf961f16abd7d0877f Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 12:51:49 -0400 Subject: [PATCH 62/78] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a8476998..3c528e0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,18 +19,18 @@ install: - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - sudo apt-get update - - sudo apt-get -y install ros-indigo-ros-base libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs + - sudo apt-get -y install ros-indigo-ros-base libpcl-1.7-all libfreenect-dev ros-indigo-libg2o ros-indigo-octomap libopenni2-dev ros-indigo-costmap-2d ros-indigo-octomap-ros ros-indigo-rviz ros-indigo-cv-bridge ros-indigo-move-base-msgs ros-indigo-costmap-2d ros-indigo-image-geometry ros-indigo-message-filters ros-indigo-image-transport ros-indigo-eigen-conversions ros-indigo-stereo-msgs ros-indigo-nav-msgs ros-indigo-sensor-msgs ros-indigo-tf-conversions ros-indigo-laser-geometry ros-indigo-pcl-conversions ros-indigo-pcl-ros ros-indigo-dynamic-reconfigure ros-indigo-nodelet script: - source /opt/ros/indigo/setup.bash - cd .. - mkdir -p catkin_ws/src - - mv rtabmap_ros catkin_ws/src/. - cd catkin_ws/src - catkin_init_workspace - cd .. - catkin_make - cd .. + - mv rtabmap_ros catkin_ws/src/. - git clone https://github.com/introlab/rtabmap.git - cd rtabmap - mkdir -p build && cd build From aa1685c50b5c413517bac306da19637b9a3acc7b Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 13:08:51 -0400 Subject: [PATCH 63/78] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3c528e0f..ef5f45ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ script: - git clone https://github.com/introlab/rtabmap.git - cd rtabmap - mkdir -p build && cd build - - cmake -DCMAKE_INSTALL_PREFIX=~/introlab/catkin_ws/devel .. + - cmake -DCMAKE_INSTALL_PREFIX=~/build/introlab/catkin_ws/devel .. - make - make install - cd ../../catkin_ws From c15f81e71abae5a144e82182652e6f5c2631d898 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 3 Nov 2016 13:53:18 -0400 Subject: [PATCH 64/78] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ef5f45ed..28abe882 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,6 @@ addons: - libsqlite3-dev install: - - pip install catkin_pkg - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - - sudo apt-get update @@ -23,6 +22,7 @@ install: script: - source /opt/ros/indigo/setup.bash + - export PYTHONPATH=$PYTHONPATH:/usr/lib/python2.7/dist-packages - cd .. - mkdir -p catkin_ws/src - cd catkin_ws/src From 2c326714eccddd749abb8d10e526f3bb4e9d52fa Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 9 Nov 2016 15:04:22 -0500 Subject: [PATCH 65/78] Update rtabmap.launch --- launch/rtabmap.launch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launch/rtabmap.launch b/launch/rtabmap.launch index 81f7774f..b421cd23 100644 --- a/launch/rtabmap.launch +++ b/launch/rtabmap.launch @@ -27,7 +27,7 @@ - + @@ -184,7 +184,7 @@ - + From 8eb164f047d51a08fb346e69c6ea9f2de9d328df Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 9 Nov 2016 16:21:06 -0500 Subject: [PATCH 66/78] MapCloud: auto refresh clouds when rtabmap is reset --- src/rviz/MapCloudDisplay.cpp | 85 ++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/src/rviz/MapCloudDisplay.cpp b/src/rviz/MapCloudDisplay.cpp index 8cf7cd4b..fd207953 100644 --- a/src/rviz/MapCloudDisplay.cpp +++ b/src/rviz/MapCloudDisplay.cpp @@ -272,58 +272,56 @@ void MapCloudDisplay::processMapData(const rtabmap_ros::MapData& map) for(unsigned int i=0; i::Ptr cloud; + pcl::IndicesPtr validIndices(new std::vector); + cloud = rtabmap::util3d::cloudRGBFromSensorData( + s.sensorData(), + cloud_decimation_->getInt(), + cloud_max_depth_->getFloat(), + cloud_min_depth_->getFloat(), + validIndices.get()); - - if(!s.sensorData().imageRaw().empty() && !s.sensorData().depthOrRightRaw().empty()) + if(cloud_voxel_size_->getFloat()) { - pcl::PointCloud::Ptr cloud; - pcl::IndicesPtr validIndices(new std::vector); - cloud = rtabmap::util3d::cloudRGBFromSensorData( - s.sensorData(), - cloud_decimation_->getInt(), - cloud_max_depth_->getFloat(), - cloud_min_depth_->getFloat(), - validIndices.get()); + cloud = rtabmap::util3d::voxelize(cloud, validIndices, cloud_voxel_size_->getFloat()); + } - if(cloud_voxel_size_->getFloat()) + if(cloud->size()) + { + if(cloud_filter_floor_height_->getFloat() > 0.0f || cloud_filter_ceiling_height_->getFloat() > 0.0f) { - cloud = rtabmap::util3d::voxelize(cloud, validIndices, cloud_voxel_size_->getFloat()); + cloud = rtabmap::util3d::passThrough(cloud, "z", + cloud_filter_floor_height_->getFloat()>0.0f?cloud_filter_floor_height_->getFloat():-999.0f, + cloud_filter_ceiling_height_->getFloat()>0.0f && (cloud_filter_floor_height_->getFloat()<=0.0f || cloud_filter_ceiling_height_->getFloat()>cloud_filter_floor_height_->getFloat())?cloud_filter_ceiling_height_->getFloat():999.0f); } - if(cloud->size()) + sensor_msgs::PointCloud2::Ptr cloudMsg(new sensor_msgs::PointCloud2); + pcl::toROSMsg(*cloud, *cloudMsg); + cloudMsg->header = map.header; + + CloudInfoPtr info(new CloudInfo); + info->message_ = cloudMsg; + info->pose_ = rtabmap::Transform::getIdentity(); + info->id_ = id; + + if (transformCloud(info, true)) { - if(cloud_filter_floor_height_->getFloat() > 0.0f || cloud_filter_ceiling_height_->getFloat() > 0.0f) - { - cloud = rtabmap::util3d::passThrough(cloud, "z", - cloud_filter_floor_height_->getFloat()>0.0f?cloud_filter_floor_height_->getFloat():-999.0f, - cloud_filter_ceiling_height_->getFloat()>0.0f && (cloud_filter_floor_height_->getFloat()<=0.0f || cloud_filter_ceiling_height_->getFloat()>cloud_filter_floor_height_->getFloat())?cloud_filter_ceiling_height_->getFloat():999.0f); - } - - sensor_msgs::PointCloud2::Ptr cloudMsg(new sensor_msgs::PointCloud2); - pcl::toROSMsg(*cloud, *cloudMsg); - cloudMsg->header = map.header; - - CloudInfoPtr info(new CloudInfo); - info->message_ = cloudMsg; - info->pose_ = rtabmap::Transform::getIdentity(); - info->id_ = id; - - if (transformCloud(info, true)) - { - boost::mutex::scoped_lock lock(new_clouds_mutex_); - new_cloud_infos_.insert(std::make_pair(id, info)); - } + boost::mutex::scoped_lock lock(new_clouds_mutex_); + new_cloud_infos_.erase(id); + new_cloud_infos_.insert(std::make_pair(id, info)); } } } @@ -633,6 +631,7 @@ void MapCloudDisplay::update( float wall_dt, float ros_dt ) cloud_info->scene_node_->attachObject( cloud_info->cloud_.get() ); cloud_info->scene_node_->setVisible(false); + cloud_infos_.erase(it->first); cloud_infos_.insert(*it); } From 2fd3675440a1e3890746510c420c199407b82e4b Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 15 Nov 2016 10:39:03 -0500 Subject: [PATCH 67/78] rtabmap 0.11.12 version now required. MapCloudDisplay: doing z filtering in /odom frame instead of /base_link frame --- CMakeLists.txt | 2 +- src/MapOptimizerNode.cpp | 2 +- src/rviz/MapCloudDisplay.cpp | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5c0aa66..f61d6458 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ find_package(rviz) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) -find_package(RTABMap 0.11.10 REQUIRED) +find_package(RTABMap 0.11.12 REQUIRED) find_package(OpenCV REQUIRED) diff --git a/src/MapOptimizerNode.cpp b/src/MapOptimizerNode.cpp index 18d3b531..7d188ec5 100644 --- a/src/MapOptimizerNode.cpp +++ b/src/MapOptimizerNode.cpp @@ -84,7 +84,7 @@ public: parameters.insert(ParametersPair(Parameters::kOptimizerEpsilon(), uNumber2Str(epsilon))); parameters.insert(ParametersPair(Parameters::kOptimizerIterations(), uNumber2Str(iterations))); parameters.insert(ParametersPair(Parameters::kOptimizerRobust(), uBool2Str(robust))); - parameters.insert(ParametersPair(Parameters::kOptimizerSlam2D(), uBool2Str(slam2d))); + parameters.insert(ParametersPair(Parameters::kRegForce3DoF(), uBool2Str(slam2d))); parameters.insert(ParametersPair(Parameters::kOptimizerVarianceIgnored(), uBool2Str(ignoreVariance))); optimizer_ = Optimizer::create(parameters); diff --git a/src/rviz/MapCloudDisplay.cpp b/src/rviz/MapCloudDisplay.cpp index fd207953..8110ce9c 100644 --- a/src/rviz/MapCloudDisplay.cpp +++ b/src/rviz/MapCloudDisplay.cpp @@ -303,9 +303,13 @@ void MapCloudDisplay::processMapData(const rtabmap_ros::MapData& map) { if(cloud_filter_floor_height_->getFloat() > 0.0f || cloud_filter_ceiling_height_->getFloat() > 0.0f) { + // convert in /odom frame + cloud = rtabmap::util3d::transformPointCloud(cloud, s.getPose()); cloud = rtabmap::util3d::passThrough(cloud, "z", cloud_filter_floor_height_->getFloat()>0.0f?cloud_filter_floor_height_->getFloat():-999.0f, cloud_filter_ceiling_height_->getFloat()>0.0f && (cloud_filter_floor_height_->getFloat()<=0.0f || cloud_filter_ceiling_height_->getFloat()>cloud_filter_floor_height_->getFloat())?cloud_filter_ceiling_height_->getFloat():999.0f); + // convert back in /base_link frame + cloud = rtabmap::util3d::transformPointCloud(cloud, s.getPose().inverse()); } sensor_msgs::PointCloud2::Ptr cloudMsg(new sensor_msgs::PointCloud2); From 827f5d02d66c1661f6b9f805e22c4a3acc656aa4 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 15 Nov 2016 12:18:00 -0500 Subject: [PATCH 68/78] rtabmap node: loading modified parameters in loaded database if not set already (in config.ini, ros params or arguments) --- src/CoreWrapper.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 6dcc94e6..e1060f20 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -54,6 +54,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef WITH_OCTOMAP_ROS #ifdef RTABMAP_OCTOMAP @@ -351,6 +352,33 @@ void CoreWrapper::onInit() parameters_.insert(ParametersPair(Parameters::kGridFromDepth(), "false")); } + // modify default parameters with those in the database + if(!deleteDbOnStart) + { + ParametersMap dbParameters; + rtabmap::DBDriver * driver = rtabmap::DBDriver::create(); + if(driver->openConnection(databasePath_)) + { + dbParameters = driver->getLastParameters(); // parameter migration is already done + } + delete driver; + for(ParametersMap::iterator iter=dbParameters.begin(); iter!=dbParameters.end(); ++iter) + { + if(iter->first.compare(Parameters::kRtabmapWorkingDirectory()) == 0) + { + // ignore working directory + continue; + } + if(parameters_.find(iter->first) == parameters_.end() && + allParameters.find(iter->first) != allParameters.end() && + allParameters.find(iter->first)->second.compare(iter->second) !=0) + { + NODELET_INFO("Update RTAB-Map parameter \"%s\"=\"%s\" from database", iter->first.c_str(), iter->second.c_str()); + parameters_.insert(*iter); + } + } + } + // Add all other parameters (not copied if already exists) parameters_.insert(allParameters.begin(), allParameters.end()); From 571f225786a8ad3c2ec65151f79dc90e999701a2 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 24 Nov 2016 15:10:46 -0500 Subject: [PATCH 69/78] multi-session demo: decreased RGBD/OptimizeMaxError to 25 cm --- launch/demo/demo_multi-session_mapping.launch | 1 + 1 file changed, 1 insertion(+) diff --git a/launch/demo/demo_multi-session_mapping.launch b/launch/demo/demo_multi-session_mapping.launch index cbf3fa24..3dc06b6b 100644 --- a/launch/demo/demo_multi-session_mapping.launch +++ b/launch/demo/demo_multi-session_mapping.launch @@ -51,6 +51,7 @@ + From 295ae0cee0e74dd76a9e192bf06eb927e9d8455c Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 24 Nov 2016 16:38:31 -0500 Subject: [PATCH 70/78] multi-session demo: use TORO by default and disabled incremental flann like in the paper --- launch/demo/demo_multi-session_mapping.launch | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/launch/demo/demo_multi-session_mapping.launch b/launch/demo/demo_multi-session_mapping.launch index 3dc06b6b..19e6cdbc 100644 --- a/launch/demo/demo_multi-session_mapping.launch +++ b/launch/demo/demo_multi-session_mapping.launch @@ -33,7 +33,7 @@ - + @@ -51,7 +51,10 @@ - + + + + From 19eb9cb836c029f3319b48310cd04ac119d58903 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 28 Nov 2016 13:14:35 -0500 Subject: [PATCH 71/78] demo_two_kinects.launch: fixed rgbd_odomerty not correctly subscribed to rgbd_image topics --- launch/demo/demo_two_kinects.launch | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launch/demo/demo_two_kinects.launch b/launch/demo/demo_two_kinects.launch index ee29615e..0f582860 100644 --- a/launch/demo/demo_two_kinects.launch +++ b/launch/demo/demo_two_kinects.launch @@ -15,7 +15,7 @@ - + + @@ -92,6 +93,7 @@ + From a03d0ae4e1fcfa4acaec5b3c29a54f7e2d4dbe96 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Mon, 28 Nov 2016 16:25:01 -0500 Subject: [PATCH 72/78] updating param server when calling set_mode_localization or set_mode_mapping services --- src/CoreWrapper.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index e1060f20..8d87376a 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -1571,6 +1571,8 @@ bool CoreWrapper::setModeLocalizationCallback(std_srvs::Empty::Request&, std_srv NODELET_INFO("rtabmap: Set localization mode"); rtabmap::ParametersMap parameters; parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemIncrementalMemory(), "false")); + ros::NodeHandle & nh = getNodeHandle(); + nh.setParam(rtabmap::Parameters::kMemIncrementalMemory(), "false"); rtabmap_.parseParameters(parameters); return true; } @@ -1580,6 +1582,8 @@ bool CoreWrapper::setModeMappingCallback(std_srvs::Empty::Request&, std_srvs::Em NODELET_INFO("rtabmap: Set mapping mode"); rtabmap::ParametersMap parameters; parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kMemIncrementalMemory(), "true")); + ros::NodeHandle & nh = getNodeHandle(); + nh.setParam(rtabmap::Parameters::kMemIncrementalMemory(), "true"); rtabmap_.parseParameters(parameters); return true; } From f6a29a3e64501b8fea8db0d85caec068e633b2ba Mon Sep 17 00:00:00 2001 From: matlabbe Date: Wed, 30 Nov 2016 18:28:48 -0500 Subject: [PATCH 73/78] Fixed high variance bug when odometry recovers from being lost --- src/CoreWrapper.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 8d87376a..5985744b 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -659,6 +659,18 @@ bool CoreWrapper::odomUpdate(const nav_msgs::OdometryConstPtr & odomMsg) // using MIN in case of 3DoF mapping (maybe no parameters are set, except x and yaw for the twist) float transVariance = uMax3(odomMsg->twist.covariance[0], MIN(odomMsg->twist.covariance[7], BAD_COVARIANCE), MIN(odomMsg->twist.covariance[14], BAD_COVARIANCE)); float rotVariance = uMax3(MIN(odomMsg->twist.covariance[21],BAD_COVARIANCE), MIN(odomMsg->twist.covariance[28], BAD_COVARIANCE), odomMsg->twist.covariance[35]); + + if(transVariance == BAD_COVARIANCE) + { + //use the one of the pose + transVariance = uMax3(odomMsg->pose.covariance[0]/2.0, MIN(odomMsg->pose.covariance[7]/2.0, BAD_COVARIANCE), MIN(odomMsg->pose.covariance[14]/2.0, BAD_COVARIANCE)); + } + if(rotVariance == BAD_COVARIANCE) + { + //use the one of the pose + rotVariance = uMax3(MIN(odomMsg->pose.covariance[21]/2.0,BAD_COVARIANCE), MIN(odomMsg->pose.covariance[28]/2.0, BAD_COVARIANCE), odomMsg->pose.covariance[35]/2.0); + } + if(uIsFinite(rotVariance) && rotVariance > rotVariance_) { rotVariance_ = rotVariance; From 3fb6a828252a35590b2b7d33a441d79812b1f6c8 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Tue, 3 Jan 2017 23:26:26 -0500 Subject: [PATCH 74/78] Fixed build with changes from 0.11.13 --- CMakeLists.txt | 2 +- msg/OdomInfo.msg | 6 ++++-- package.xml | 2 +- src/MsgConversion.cpp | 6 ++++-- src/OdometryROS.cpp | 30 +++++++++++++++--------------- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f61d6458..fecf15b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ find_package(rviz) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) -find_package(RTABMap 0.11.12 REQUIRED) +find_package(RTABMap 0.11.13 REQUIRED) find_package(OpenCV REQUIRED) diff --git a/msg/OdomInfo.msg b/msg/OdomInfo.msg index 0162088a..4cace8f1 100644 --- a/msg/OdomInfo.msg +++ b/msg/OdomInfo.msg @@ -6,7 +6,8 @@ Header header # bool lost; # int matches; # int inliers; -# float variance; +# float varianceLin; +# float varianceAng; # int features; # int localMapSize; # float time; @@ -27,7 +28,8 @@ Header header bool lost int32 matches int32 inliers -float32 variance +float32 varianceLin +float32 varianceAng int32 features int32 localMapSize int32 localScanMapSize diff --git a/package.xml b/package.xml index 6eab9a07..1fa8a3e8 100644 --- a/package.xml +++ b/package.xml @@ -1,7 +1,7 @@ rtabmap_ros - 0.11.10 + 0.11.13 RTAB-Map's ros-pkg. RTAB-Map is a RGB-D SLAM approach with real-time constraints. Mathieu Labbe Mathieu Labbe diff --git a/src/MsgConversion.cpp b/src/MsgConversion.cpp index 8cd5bbdf..8160a099 100644 --- a/src/MsgConversion.cpp +++ b/src/MsgConversion.cpp @@ -879,7 +879,8 @@ rtabmap::OdometryInfo odomInfoFromROS(const rtabmap_ros::OdomInfo & msg) info.localMapSize = msg.localMapSize; info.localScanMapSize = msg.localScanMapSize; info.timeEstimation = msg.timeEstimation; - info.variance = msg.variance; + info.varianceLin = msg.varianceLin; + info.varianceAng = msg.varianceAng; info.timeParticleFiltering = msg.timeParticleFiltering; info.stamp = msg.stamp; info.interval = msg.interval; @@ -923,7 +924,8 @@ void odomInfoToROS(const rtabmap::OdometryInfo & info, rtabmap_ros::OdomInfo & m msg.localMapSize = info.localMapSize; msg.localScanMapSize = info.localScanMapSize; msg.timeEstimation = info.timeEstimation; - msg.variance = info.variance; + msg.varianceLin = info.varianceLin; + msg.varianceAng = info.varianceAng; msg.timeParticleFiltering = info.timeParticleFiltering; msg.stamp = info.stamp; msg.interval = info.interval; diff --git a/src/OdometryROS.cpp b/src/OdometryROS.cpp index 84a67f7e..169dc991 100644 --- a/src/OdometryROS.cpp +++ b/src/OdometryROS.cpp @@ -443,12 +443,12 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) //set covariance // libviso2 uses approximately vel variance * 2 - odom.pose.covariance.at(0) = info.variance*2; // xx - odom.pose.covariance.at(7) = info.variance*2; // yy - odom.pose.covariance.at(14) = info.variance*2; // zz - odom.pose.covariance.at(21) = info.variance*2; // rr - odom.pose.covariance.at(28) = info.variance*2; // pp - odom.pose.covariance.at(35) = info.variance*2; // yawyaw + odom.pose.covariance.at(0) = info.varianceLin*2; // xx + odom.pose.covariance.at(7) = info.varianceLin*2; // yy + odom.pose.covariance.at(14) = info.varianceLin*2; // zz + odom.pose.covariance.at(21) = info.varianceAng*2; // rr + odom.pose.covariance.at(28) = info.varianceAng*2; // pp + odom.pose.covariance.at(35) = info.varianceAng*2; // yawyaw //set velocity bool setTwist = !odometry_->previousVelocityTransform().isNull(); @@ -464,12 +464,12 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) odom.twist.twist.angular.z = yaw; } - odom.twist.covariance.at(0) = setTwist?info.variance:BAD_COVARIANCE; // xx - odom.twist.covariance.at(7) = setTwist?info.variance:BAD_COVARIANCE; // yy - odom.twist.covariance.at(14) = setTwist?info.variance:BAD_COVARIANCE; // zz - odom.twist.covariance.at(21) = setTwist?info.variance:BAD_COVARIANCE; // rr - odom.twist.covariance.at(28) = setTwist?info.variance:BAD_COVARIANCE; // pp - odom.twist.covariance.at(35) = setTwist?info.variance:BAD_COVARIANCE; // yawyaw + odom.twist.covariance.at(0) = setTwist?info.varianceLin:BAD_COVARIANCE; // xx + odom.twist.covariance.at(7) = setTwist?info.varianceLin:BAD_COVARIANCE; // yy + odom.twist.covariance.at(14) = setTwist?info.varianceLin:BAD_COVARIANCE; // zz + odom.twist.covariance.at(21) = setTwist?info.varianceAng:BAD_COVARIANCE; // rr + odom.twist.covariance.at(28) = setTwist?info.varianceAng:BAD_COVARIANCE; // pp + odom.twist.covariance.at(35) = setTwist?info.varianceAng:BAD_COVARIANCE; // yawyaw //publish the message odomPub_.publish(odom); @@ -620,16 +620,16 @@ void OdometryROS::processData(const SensorData & data, const ros::Time & stamp) { if(icpParams_) { - NODELET_INFO( "Odom: quality=%d, ratio=%f, std dev=%fm, update time=%fs", info.inliers, info.icpInliersRatio, pose.isNull()?0.0f:std::sqrt(info.variance), (ros::WallTime::now()-time).toSec()); + NODELET_INFO( "Odom: quality=%d, ratio=%f, std dev=%fm|%frad, update time=%fs", info.inliers, info.icpInliersRatio, pose.isNull()?0.0f:std::sqrt(info.varianceLin), pose.isNull()?0.0f:std::sqrt(info.varianceAng), (ros::WallTime::now()-time).toSec()); } else { - NODELET_INFO( "Odom: quality=%d, std dev=%fm, update time=%fs", info.inliers, pose.isNull()?0.0f:std::sqrt(info.variance), (ros::WallTime::now()-time).toSec()); + NODELET_INFO( "Odom: quality=%d, std dev=%fm|%frad, update time=%fs", info.inliers, pose.isNull()?0.0f:std::sqrt(info.varianceLin), pose.isNull()?0.0f:std::sqrt(info.varianceAng), (ros::WallTime::now()-time).toSec()); } } else { - NODELET_INFO( "Odom: ratio=%f, std dev=%fm, update time=%fs", info.icpInliersRatio, pose.isNull()?0.0f:std::sqrt(info.variance), (ros::WallTime::now()-time).toSec()); + NODELET_INFO( "Odom: ratio=%f, std dev=%fm|%frad, update time=%fs", info.icpInliersRatio, pose.isNull()?0.0f:std::sqrt(info.varianceLin), pose.isNull()?0.0f:std::sqrt(info.varianceAng), (ros::WallTime::now()-time).toSec()); } } From d343e8cb710cbb0973fe5f4e27dc31d7bdb0be7d Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 5 Jan 2017 17:32:12 -0500 Subject: [PATCH 75/78] rtabmap: odometry variance summation instead of taking largest one --- src/CoreWrapper.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CoreWrapper.cpp b/src/CoreWrapper.cpp index 5985744b..98583494 100644 --- a/src/CoreWrapper.cpp +++ b/src/CoreWrapper.cpp @@ -671,13 +671,13 @@ bool CoreWrapper::odomUpdate(const nav_msgs::OdometryConstPtr & odomMsg) rotVariance = uMax3(MIN(odomMsg->pose.covariance[21]/2.0,BAD_COVARIANCE), MIN(odomMsg->pose.covariance[28]/2.0, BAD_COVARIANCE), odomMsg->pose.covariance[35]/2.0); } - if(uIsFinite(rotVariance) && rotVariance > rotVariance_) + if(uIsFinite(rotVariance) && rotVariance != 1.0f) { - rotVariance_ = rotVariance; + rotVariance_ += rotVariance; } - if(uIsFinite(transVariance) && transVariance > transVariance_) + if(uIsFinite(transVariance) && transVariance != 1.0f) { - transVariance_ = transVariance; + transVariance_ += transVariance; } } From b26b70c54f59c6f896099f4bef2dcc7184076866 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 5 Jan 2017 17:45:21 -0500 Subject: [PATCH 76/78] rgbd_mapping.launch: creating/using rtabmap_gui.ini in ~/.ros --- launch/rgbd_mapping.launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch/rgbd_mapping.launch b/launch/rgbd_mapping.launch index 60630b19..ade39606 100644 --- a/launch/rgbd_mapping.launch +++ b/launch/rgbd_mapping.launch @@ -15,7 +15,7 @@ - + From 4819d3d56165a87783fde9a8211eb0184fdabf95 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Thu, 5 Jan 2017 19:29:13 -0500 Subject: [PATCH 77/78] fixed some warnings on demo launch files --- launch/config/demo_stereo_outdoor.rviz | 2 +- launch/demo/demo_multi-session_mapping.launch | 2 +- launch/demo/demo_robot_mapping.launch | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/launch/config/demo_stereo_outdoor.rviz b/launch/config/demo_stereo_outdoor.rviz index ce7efce9..c9eb5966 100644 --- a/launch/config/demo_stereo_outdoor.rviz +++ b/launch/config/demo_stereo_outdoor.rviz @@ -180,7 +180,7 @@ Visualization Manager: Draw Behind: false Enabled: true Name: Map - Topic: /rtabmap/proj_map + Topic: /rtabmap/grid_map Value: true - Class: rtabmap_ros/Info Enabled: true diff --git a/launch/demo/demo_multi-session_mapping.launch b/launch/demo/demo_multi-session_mapping.launch index 19e6cdbc..0c8c9d65 100644 --- a/launch/demo/demo_multi-session_mapping.launch +++ b/launch/demo/demo_multi-session_mapping.launch @@ -49,12 +49,12 @@ - + diff --git a/launch/demo/demo_robot_mapping.launch b/launch/demo/demo_robot_mapping.launch index 2955d5ab..efbf952c 100644 --- a/launch/demo/demo_robot_mapping.launch +++ b/launch/demo/demo_robot_mapping.launch @@ -33,8 +33,8 @@ - - + + @@ -43,8 +43,8 @@ - + From 5bd1ccaf5d649d1c92a2ce39b1a83e81f8efa0fb Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 6 Jan 2017 08:21:09 -0500 Subject: [PATCH 78/78] demo_multi-session_mapping.launch: added rtabmap_args for convenience --- launch/demo/demo_multi-session_mapping.launch | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launch/demo/demo_multi-session_mapping.launch b/launch/demo/demo_multi-session_mapping.launch index 0c8c9d65..710172ce 100644 --- a/launch/demo/demo_multi-session_mapping.launch +++ b/launch/demo/demo_multi-session_mapping.launch @@ -6,6 +6,7 @@ + @@ -13,7 +14,7 @@ - +