ROSInterface.cpp
Go to the documentation of this file.
00001 /* 
00002  * Copyright (c) 2013 University of Jaume-I.
00003  * All rights reserved. This program and the accompanying materials
00004  * are made available under the terms of the GNU Public License v3.0
00005  * which accompanies this distribution, and is available at
00006  * http://www.gnu.org/licenses/gpl.html
00007  * 
00008  * Contributors:
00009  *     Mario Prats
00010  *     Javier Perez
00011  */
00012 
00013 #include <uwsim/ROSInterface.h>
00014 #include <uwsim/UWSimUtils.h>
00015 #include <osg/LineWidth>
00016 #include <osg/Material>
00017 #include <osgOcean/ShaderManager>
00018 #include <uwsim/osgPCDLoader.h>
00019 
00020 #include <sensor_msgs/Imu.h>
00021 #include <sensor_msgs/NavSatFix.h>
00022 #include <sensor_msgs/LaserScan.h>
00023 #include <underwater_sensor_msgs/Pressure.h>
00024 #include <underwater_sensor_msgs/DVL.h>
00025 #include <std_msgs/Bool.h>
00026 #include <osg/LineStipple>
00027 #include <robot_state_publisher/robot_state_publisher.h>
00028 #include <kdl_parser/kdl_parser.hpp>
00029 
00030 // static member
00031 ros::Time ROSInterface::current_time_;
00032 
00033 ROSSubscriberInterface::ROSSubscriberInterface(std::string topic) :
00034     ROSInterface(topic)
00035 {
00036   startThread();
00037 }
00038 
00039 /* Thread code */
00040 void ROSSubscriberInterface::run()
00041 {
00042   ros::Duration(2).sleep();
00043   createSubscriber (nh_);
00044 }
00045 
00046 ROSSubscriberInterface::~ROSSubscriberInterface()
00047 {
00048   join();
00049 }
00050 
00051 ROSOdomToPAT::ROSOdomToPAT(osg::Group *rootNode, std::string topic, std::string vehicleName) :
00052     ROSSubscriberInterface(topic)
00053 {
00054   findNodeVisitor findNode(vehicleName);
00055   rootNode->accept(findNode);
00056   osg::Node *first = findNode.getFirst();
00057   if (first == NULL)
00058   {
00059     transform = NULL;
00060   }
00061   else
00062   {
00063     transform = dynamic_cast<osg::MatrixTransform*>(first);
00064   }
00065   started = 0; //Used in time
00066 }
00067 
00068 void ROSOdomToPAT::createSubscriber(ros::NodeHandle &nh)
00069 {
00070   ROS_INFO("ROSOdomToPAT subscriber on topic %s", topic.c_str());
00071   sub_ = nh.subscribe < nav_msgs::Odometry > (topic, 10, &ROSOdomToPAT::processData, this);
00072   if (sub_ == ros::Subscriber())
00073   {
00074     ROS_ERROR("ROSOdomToPAT::createSubscriber cannot subscribe to topic %s", topic.c_str());
00075   }
00076 }
00077 
00078 void ROSOdomToPAT::processData(const nav_msgs::Odometry::ConstPtr& odom)
00079 {
00080   if (transform != NULL)
00081   {
00082     //vpHomogeneousMatrix sMsv;
00083     osg::Matrixd sMsv_osg;
00084     //If velocity is zero, use the position reference. If not, use velocity
00085     //If position is not zero, use the position reference. If not, use velocity
00086     if (odom->pose.pose.orientation.x != 0 || odom->pose.pose.orientation.y != 0 || odom->pose.pose.orientation.z != 0
00087         || odom->pose.pose.position.x != 0 || odom->pose.pose.position.y != 0 || odom->pose.pose.position.z != 0)
00088     {
00089       sMsv_osg.setTrans(odom->pose.pose.position.x, odom->pose.pose.position.y, odom->pose.pose.position.z);
00090       sMsv_osg.setRotate(
00091           osg::Quat(odom->pose.pose.orientation.x, odom->pose.pose.orientation.y, odom->pose.pose.orientation.z,
00092                     odom->pose.pose.orientation.w));
00093       sMsv_osg.preMultScale(transform->getMatrix().getScale());
00094     }
00095     else
00096     {
00097       //Get the current vehicle position and attitude in an homogeneous matrix
00098       sMsv_osg = transform->getMatrix();
00099 
00100       //Time issues
00101       double elapsed = 0;
00102       if (started != 0)
00103       {
00104         ros::WallDuration t_diff = ros::WallTime::now() - last;
00105         elapsed = t_diff.toSec();
00106         //If elapsed>MAX_ELAPSED, consider this is sent by a different publisher, so that the counter has to restart
00107         if (elapsed > 1)
00108           elapsed = 0;
00109       }
00110       started = 1;
00111       last = ros::WallTime::now();
00112       //std::cout<<elapsed<<std::endl;
00113 
00114       osg::Matrixd vMnv;
00115       osg::Matrixd T, Rx, Ry, Rz;
00116       T.makeTranslate(odom->twist.twist.linear.x * elapsed, odom->twist.twist.linear.y * elapsed,
00117                       odom->twist.twist.linear.z * elapsed);
00118       Rx.makeRotate(odom->twist.twist.angular.x * elapsed, 1, 0, 0);
00119       Ry.makeRotate(odom->twist.twist.angular.y * elapsed, 0, 1, 0);
00120       Rz.makeRotate(odom->twist.twist.angular.z * elapsed, 0, 0, 1);
00121       vMnv = Rz * Ry * Rx * T;
00122 
00123       sMsv_osg = vMnv * sMsv_osg;
00124     }
00125     transform->setMatrix(sMsv_osg);
00126   }
00127 }
00128 
00129 ROSOdomToPAT::~ROSOdomToPAT()
00130 {
00131 }
00132 
00133 ROSTwistToPAT::ROSTwistToPAT(osg::Group *rootNode, std::string topic, std::string vehicleName) :
00134     ROSSubscriberInterface(topic)
00135 {
00136   findNodeVisitor findNode(vehicleName);
00137   rootNode->accept(findNode);
00138   osg::Node *first = findNode.getFirst();
00139   if (first == NULL)
00140   {
00141     transform = NULL;
00142   }
00143   else
00144   {
00145     transform = dynamic_cast<osg::MatrixTransform*>(first);
00146   }
00147   started = 0; //used in time
00148 }
00149 
00150 void ROSTwistToPAT::createSubscriber(ros::NodeHandle &nh)
00151 {
00152   ROS_INFO("ROSTwistToPAT subscriber on topic %s", topic.c_str());
00153   sub_ = nh.subscribe < geometry_msgs::TwistStamped > (topic, 10, &ROSTwistToPAT::processData, this);
00154 }
00155 
00156 void ROSTwistToPAT::processData(const geometry_msgs::TwistStamped::ConstPtr& twist)
00157 {
00158   if (transform != NULL)
00159   {
00160     //vpHomogeneousMatrix sMsv;
00161     osg::Matrixd sMsv_osg;
00162     //Get the current vehicle position and attitude in an homogeneous matrix
00163     sMsv_osg = transform->getMatrix();
00164 
00165     //Time issues
00166     double elapsed = 0;
00167     if (started != 0)
00168     {
00169       ros::WallDuration t_diff = ros::WallTime::now() - last;
00170       elapsed = t_diff.toSec();
00171       //If elapsed>MAX_ELAPSED, consider this is sent by a different publisher, so that the counter has to restart
00172       if (elapsed > 1)
00173         elapsed = 0;
00174     }
00175     started = 1;
00176     last = ros::WallTime::now();
00177 
00178     osg::Matrixd vMnv;
00179     osg::Matrixd T, Rx, Ry, Rz;
00180     T.makeTranslate(twist->twist.linear.x * elapsed, twist->twist.linear.y * elapsed, twist->twist.linear.z * elapsed);
00181     Rx.makeRotate(twist->twist.angular.x * elapsed, 1, 0, 0);
00182     Ry.makeRotate(twist->twist.angular.y * elapsed, 0, 1, 0);
00183     Rz.makeRotate(twist->twist.angular.z * elapsed, 0, 0, 1);
00184     vMnv = Rz * Ry * Rx * T;
00185 
00186     sMsv_osg = vMnv * sMsv_osg;
00187 
00188     transform->setMatrix(sMsv_osg);
00189   }
00190 }
00191 
00192 ROSTwistToPAT::~ROSTwistToPAT()
00193 {
00194 }
00195 
00196 ROSPoseToPAT::ROSPoseToPAT(osg::Group *rootNode, std::string topic, std::string vehicleName) :
00197     ROSSubscriberInterface(topic)
00198 {
00199   findNodeVisitor findNode(vehicleName);
00200   rootNode->accept(findNode);
00201   osg::Node *first = findNode.getFirst();
00202   if (first == NULL)
00203   {
00204     transform = NULL;
00205   }
00206   else
00207   {
00208     transform = dynamic_cast<osg::MatrixTransform*>(first);
00209   }
00210 }
00211 
00212 void ROSPoseToPAT::createSubscriber(ros::NodeHandle &nh)
00213 {
00214   ROS_INFO("ROSPoseToPAT subscriber on topic %s", topic.c_str());
00215   sub_ = nh.subscribe < geometry_msgs::Pose > (topic, 10, &ROSPoseToPAT::processData, this);
00216 }
00217 
00218 void ROSPoseToPAT::processData(const geometry_msgs::Pose::ConstPtr& pose)
00219 {
00220   if (transform != NULL)
00221   {
00222     //vpHomogeneousMatrix sMsv;
00223     osg::Matrixd sMsv_osg;
00224 
00225     sMsv_osg.setTrans(pose->position.x, pose->position.y, pose->position.z);
00226     sMsv_osg.setRotate(osg::Quat(pose->orientation.x, pose->orientation.y, pose->orientation.z, pose->orientation.w));
00227     sMsv_osg.preMultScale(transform->getMatrix().getScale());
00228 
00229     transform->setMatrix(sMsv_osg);
00230 
00231   }
00232 }
00233 
00234 ROSPoseToPAT::~ROSPoseToPAT()
00235 {
00236 }
00237 
00238 ROSPointCloudLoader::ROSPointCloudLoader(std::string topic, osg::ref_ptr<osg::Group> root,unsigned int mask,bool del)
00239 : ROSSubscriberInterface(topic), scene_root(root), nodeMask(mask), deleteLastPCD(del)
00240 {
00241   
00242 }
00243 
00244 void ROSPointCloudLoader::createSubscriber(ros::NodeHandle &nh)
00245 {
00246   ROS_INFO("ROSPointCloudLoader subscriber on topic %s", topic.c_str());
00247   sub_ = nh.subscribe<pcl::PointCloud<pcl::PointXYZ> >(topic, 10, &ROSPointCloudLoader::processData, this);
00248 }
00249 
00250 void ROSPointCloudLoader::processData(const pcl::PointCloud<pcl::PointXYZ>::ConstPtr& msg)
00251 {
00252    osgPCDLoader<pcl::PointXYZ> pcdLoader(*msg.get());
00253 
00254    osg::ref_ptr < osg::Node > frame_id=findRN(msg->header.frame_id,scene_root);
00255 
00256    if(frame_id)
00257    {
00258      osg::ref_ptr < osg::Node > LWNode=findRN("localizedWorld",scene_root);
00259      boost::shared_ptr<osg::Matrix> LWMat=getWorldCoords(LWNode);
00260      LWMat->invert(*LWMat);
00261 
00262      boost::shared_ptr<osg::Matrix> WorldToBase=getWorldCoords(frame_id);
00263 
00264      osg::Matrixd  res=*WorldToBase * *LWMat;
00265      osg::ref_ptr < osg::MatrixTransform > WorldToBaseTransform= new osg::MatrixTransform(res);
00266      WorldToBaseTransform->addChild(pcdLoader.getGeode());
00267 
00268      pcdLoader.getGeode()->setNodeMask(nodeMask);
00269      LWNode->asGroup()->addChild(WorldToBaseTransform);
00270      if(deleteLastPCD)
00271      {
00272        LWNode->asGroup()->removeChild(lastPCD);
00273        lastPCD=WorldToBaseTransform;
00274      }
00275   }
00276   else
00277   {
00278     ROS_WARN ("%s is not a valid frame id for PointCloudLoader.",msg->header.frame_id.c_str());
00279   }
00280 }
00281 
00282 ROSPointCloudLoader::~ROSPointCloudLoader(){}
00283 
00284 /*
00285  class ROSNavigationDataToPAT: public ROSSubscriberInterface {
00286  osg::PositionAttitudeTransform *transform;
00287  public:
00288  ROSNavigationDataToPAT(std::string topic, osg::PositionAttitudeTransform *t): ROSSubscriberInterface(topic) {
00289  transform=t;
00290  }
00291 
00292  virtual void createSubscriber(ros::NodeHandle &nh) {
00293  sub_ = nh.subscribe<cola2_common::NavigationData>(topic, 10, &ROSNavigationDataToPAT::processData, this);
00294  }
00295 
00296  virtual void processData(const cola2_common::NavigationData::ConstPtr& odom) {
00297  //Simulated vehicle frame wrt real vehicle frame
00298  vpHomogeneousMatrix vMsv(0.8,0,0.8,0,M_PI,0);
00299 
00300  vpHomogeneousMatrix sMsv;
00301 
00302 
00303  //Set a position reference
00304  //Pose of the real vehicle wrt to the localization origin
00305  vpRotationMatrix pRv(vpRxyzVector(odom->pose[3],odom->pose[4],odom->pose[5]));
00306  vpTranslationVector pTv(odom->pose[0],odom->pose[1],odom->pose[2]);
00307  vpHomogeneousMatrix pMv;
00308  pMv.buildFrom(pTv, pRv);
00309 
00310  //Localization origin wrt simulator origin
00311  vpRxyzVector sRVp(M_PI,0,-M_PI_2);
00312  vpRotationMatrix sRp(sRVp);
00313  vpTranslationVector sTp(-514921,-4677958,3.4);
00314 
00315  vpHomogeneousMatrix sMp;
00316  sMp.buildFrom(sTp,sRp);
00317 
00318  sMsv=sMp*pMv*vMsv;
00319 
00320  if (transform!=NULL) {
00321  //OSG_DEBUG << "SimulatedVehicle::processData baseTransform not null" << std::endl;
00322  transform->setPosition(osg::Vec3d(sMsv[0][3],sMsv[1][3],sMsv[2][3]));
00323  vpRotationMatrix mr;
00324  sMsv.extract(mr);
00325  vpRxyzVector vr(mr);
00326  osg::Quat sQsv(vr[0],osg::Vec3d(1,0,0), vr[1],osg::Vec3d(0,1,0), vr[2],osg::Vec3d(0,0,1));
00327  transform->setAttitude(sQsv);
00328  //baseTransform->setAttitude(osg::Quat(js->pose.pose.orientation.w,osg::Vec3d(0,0,1)));
00329  }
00330  }
00331 
00332  ~ROSNavigationDataToPAT(){}
00333  };
00334  */
00335 
00336 ROSJointStateToArm::ROSJointStateToArm(std::string topic, boost::shared_ptr<SimulatedIAUV> arm) :
00337     ROSSubscriberInterface(topic)
00338 {
00339   this->arm = arm;
00340 }
00341 
00342 void ROSJointStateToArm::createSubscriber(ros::NodeHandle &nh)
00343 {
00344   ROS_INFO("ROSJointStateToArm subscriber on topic %s", topic.c_str());
00345   sub_ = nh.subscribe < sensor_msgs::JointState > (topic, 10, &ROSJointStateToArm::processData, this);
00346 }
00347 
00348 void ROSJointStateToArm::processData(const sensor_msgs::JointState::ConstPtr& js)
00349 {
00350   //Receive request from client
00351   if (js->position.size() != 0)
00352   {
00353     //position command
00354     std::vector < std::string > names = js->name;
00355     std::vector<double> position = js->position;
00356     arm->urdf->setJointPosition(position, names);
00357   }
00358   else if (js->velocity.size() != 0)
00359   {
00360     //velocity command
00361     std::vector < std::string > names = js->name;
00362     std::vector<double> velocity = js->velocity;
00363     arm->urdf->setJointVelocity(velocity, names);
00364   }
00365 }
00366 
00367 ROSJointStateToArm::~ROSJointStateToArm()
00368 {
00369 }
00370 
00371 ROSImageToHUDCamera::ROSImageToHUDCamera(std::string topic, std::string info_topic, boost::shared_ptr<HUDCamera> camera) :
00372     ROSSubscriberInterface(info_topic), cam(camera), image_topic(topic)
00373 {
00374 }
00375 
00376 void ROSImageToHUDCamera::createSubscriber(ros::NodeHandle &nh)
00377 {
00378   ROS_INFO("ROSImageToHUDCamera subscriber on topic %s", topic.c_str());
00379   it.reset(new image_transport::ImageTransport(nh));
00380   OSG_DEBUG << "ROSImageToHUDCamera::createSubscriber Subscribing to image topic " << image_topic << std::endl;
00381   image_sub = it->subscribe(image_topic, 1, &ROSImageToHUDCamera::processData, this);
00382   //OSG_INFO << "ROSCamera::ROSCamera Subscribing to camera info topic " << info_topic << std::endl;
00383   //sub_=nh_.subscribe<sensor_msgs::CameraInfo>(info_topic, 1, &ROSImageToHUDCamera::imageInfoCallback, this);
00384 }
00385 
00386 void ROSImageToHUDCamera::processData(const sensor_msgs::ImageConstPtr& msg)
00387 {
00388   OSG_DEBUG << "ROSImageToHUDCamera::imageCallback start: " << msg->width << "x" << msg->height << " step:" << msg->step
00389       << std::endl;
00390 
00391   //unsigned char *msgdata=(unsigned char*)&(msg->data[0]);
00392   //osg_image->setImage(width,height,1,0,GL_RGB,GL_UNSIGNED_BYTE,msgdata,osg::Image::NO_DELETE);
00393   char *osgimage_data = (char*)cam->osg_image->data();
00394   //Memory cannot be directly copied, since the image frame used in OpenSceneGraph (OpenGL glReadPixels) is on
00395   //the bottom-left looking towards up-right, whereas ROS sensor_msgs::Image::data expects origin on top-left
00396   //looking towards bottom-right. Therefore it must be manually arranged, although this could be much improved:
00397   for (unsigned int i = 0; i < msg->height; i++)
00398   {
00399     for (unsigned int j = 0; j < msg->step; j++)
00400     {
00401       osgimage_data[i * msg->step + j] = msg->data[(msg->height - i - 1) * msg->step + j];
00402     }
00403   }
00404   cam->ready_ = true;
00405   OSG_DEBUG << "ROSImageToHUDCamera::imageCallback exit" << std::endl;
00406 }
00407 
00408 ROSImageToHUDCamera::~ROSImageToHUDCamera()
00409 {
00410 }
00411 
00412 ROSPublisherInterface::ROSPublisherInterface(std::string topic, int publish_rate) :
00413     ROSInterface(topic)
00414 {
00415   this->publish_rate = publish_rate;
00416   OSG_DEBUG << "ROSPublisherInterface Thread starting..." << topic << std::endl;
00417   startThread();
00418   OSG_DEBUG << "ROSPublisherInterface Thread created" << std::endl;
00419 }
00420 
00421 /* Thread code */
00422 void ROSPublisherInterface::run()
00423 {
00424   ros::Duration(2).sleep();
00425   createPublisher (nh_);
00426 
00427   ros::Rate rate(publish_rate);
00428   while (ros::ok())
00429   {
00430     publish();
00431 
00432     rate.sleep();
00433   }
00434 }
00435 
00436 ROSPublisherInterface::~ROSPublisherInterface()
00437 {
00438   join();
00439 }
00440 
00441 PATToROSOdom::PATToROSOdom(osg::Group *rootNode, std::string vehicleName, std::string topic, int rate) :
00442     ROSPublisherInterface(topic, rate)
00443 {
00444 
00445   findNodeVisitor findNode(vehicleName);
00446   rootNode->accept(findNode);
00447   osg::Node *first = findNode.getFirst();
00448   if (first == NULL)
00449   {
00450     transform = NULL;
00451   }
00452   else
00453   {
00454     transform = dynamic_cast<osg::MatrixTransform*>(first);
00455   }
00456 }
00457 
00458 void PATToROSOdom::createPublisher(ros::NodeHandle &nh)
00459 {
00460   ROS_INFO("PATToROSOdom publisher on topic %s", topic.c_str());
00461   pub_ = nh.advertise < nav_msgs::Odometry > (topic, 1);
00462 }
00463 
00464 void PATToROSOdom::publish()
00465 {
00466   if (transform != NULL)
00467   {
00468     nav_msgs::Odometry odom;
00469     odom.header.stamp = getROSTime();
00470 
00471     osg::Matrixd mat = transform->getMatrix();
00472     osg::Vec3d pos = mat.getTrans();
00473     osg::Quat rot = mat.getRotate();
00474 
00475     odom.pose.pose.position.x = pos.x();
00476     odom.pose.pose.position.y = pos.y();
00477     odom.pose.pose.position.z = pos.z();
00478     odom.pose.pose.orientation.x = rot.x();
00479     odom.pose.pose.orientation.y = rot.y();
00480     odom.pose.pose.orientation.z = rot.z();
00481     odom.pose.pose.orientation.w = rot.w();
00482 
00483     //twist and covariances not implemented at the moment
00484     odom.twist.twist.linear.x = 0;
00485     odom.twist.twist.linear.y = 0;
00486     odom.twist.twist.linear.z = 0;
00487     odom.twist.twist.angular.x = 0;
00488     odom.twist.twist.angular.y = 0;
00489     odom.twist.twist.angular.z = 0;
00490     for (int i = 0; i < 36; i++)
00491     {
00492       odom.twist.covariance[i] = 0;
00493       odom.pose.covariance[i] = 0;
00494     }
00495 
00496     pub_.publish(odom);
00497   }
00498 }
00499 
00500 PATToROSOdom::~PATToROSOdom()
00501 {
00502 }
00503 
00504 ImuToROSImu::ImuToROSImu(InertialMeasurementUnit *imu, std::string topic, int rate) :
00505     ROSPublisherInterface(topic, rate), imu_(imu)
00506 {
00507 }
00508 
00509 void ImuToROSImu::createPublisher(ros::NodeHandle &nh)
00510 {
00511   ROS_INFO("Imu publisher on topic %s", topic.c_str());
00512   pub_ = nh.advertise < sensor_msgs::Imu > (topic, 1);
00513 }
00514 
00515 void ImuToROSImu::publish()
00516 {
00517   if (imu_ != NULL)
00518   {
00519     osg::Quat rot = imu_->getMeasurement();
00520 
00521     sensor_msgs::Imu imu;
00522     imu.header.stamp = getROSTime();
00523     imu.header.frame_id = "world";
00524     imu.orientation.x = rot.x();
00525     imu.orientation.y = rot.y();
00526     imu.orientation.z = rot.z();
00527     imu.orientation.w = rot.w();
00528 
00529     imu.orientation_covariance[0] = imu.orientation_covariance[4] = imu.orientation_covariance[8] = std::pow(
00530         imu_->getStandardDeviation(), 2);
00531 
00532     pub_.publish(imu);
00533   }
00534 }
00535 
00536 ImuToROSImu::~ImuToROSImu()
00537 {
00538 }
00539 
00540 PressureSensorToROS::PressureSensorToROS(PressureSensor *sensor, std::string topic, int rate) :
00541     ROSPublisherInterface(topic, rate), sensor_(sensor)
00542 {
00543 }
00544 
00545 void PressureSensorToROS::createPublisher(ros::NodeHandle &nh)
00546 {
00547   ROS_INFO("PressureSensor publisher on topic %s", topic.c_str());
00548   pub_ = nh.advertise < underwater_sensor_msgs::Pressure > (topic, 1);
00549 }
00550 
00551 void PressureSensorToROS::publish()
00552 {
00553   if (sensor_ != NULL)
00554   {
00555     double pressure = sensor_->getMeasurement();
00556 
00557     underwater_sensor_msgs::Pressure v;
00558     v.pressure = pressure;
00559     v.header.stamp = getROSTime();
00560     v.header.frame_id = "world";
00561 
00562     pub_.publish(v);
00563   }
00564 }
00565 
00566 PressureSensorToROS::~PressureSensorToROS()
00567 {
00568 }
00569 
00570 void GPSSensorToROS::createPublisher(ros::NodeHandle &nh)
00571 {
00572   ROS_INFO("GPSSensor publisher on topic %s", topic.c_str());
00573   pub_ = nh.advertise < sensor_msgs::NavSatFix > (topic, 1);
00574 }
00575 
00576 void GPSSensorToROS::publish()
00577 {
00578   if (sensor_ != NULL)
00579   {
00580     osg::Vec3d wTgps = sensor_->getMeasurement();
00581 
00582     //publish only if near to the ocean surface
00583     if (sensor_->depthBelowWater() < 0.5)
00584     {
00585       sensor_msgs::NavSatFix m;
00586       m.latitude = wTgps[0];
00587       m.longitude = wTgps[1];
00588       m.position_covariance[0] = m.position_covariance[4] = m.position_covariance[8] = std::pow(
00589           sensor_->getStandardDeviation(), 2);
00590       m.position_covariance_type = m.COVARIANCE_TYPE_DIAGONAL_KNOWN;
00591 
00592       pub_.publish(m);
00593     }
00594   }
00595 }
00596 
00597 void DVLSensorToROS::createPublisher(ros::NodeHandle &nh)
00598 {
00599   ROS_INFO("DVLSensor publisher on topic %s", topic.c_str());
00600   pub_ = nh.advertise < underwater_sensor_msgs::DVL > (topic, 1);
00601 }
00602 
00603 void DVLSensorToROS::publish()
00604 {
00605   if (sensor_ != NULL)
00606   {
00607     osg::Vec3d vdvl = sensor_->getMeasurement();
00608 
00609     underwater_sensor_msgs::DVL m;
00610     m.bi_x_axis = vdvl.x();
00611     m.bi_y_axis = vdvl.y();
00612     m.bi_z_axis = vdvl.z();
00613 
00614     pub_.publish(m);
00615   }
00616 }
00617 
00618 ArmToROSJointState::ArmToROSJointState(SimulatedIAUV *arm, std::string topic, int rate) :
00619     ROSPublisherInterface(topic, rate)
00620 {
00621   this->arm = arm->urdf;
00622 }
00623 
00624 void ArmToROSJointState::createPublisher(ros::NodeHandle &nh)
00625 {
00626   ROS_INFO("ArmToROSJointState publisher on topic %s", topic.c_str());
00627   pub_ = nh.advertise < sensor_msgs::JointState > (topic, 1);
00628 }
00629 
00630 void ArmToROSJointState::publish()
00631 {
00632   if (arm != NULL)
00633   {
00634     sensor_msgs::JointState js;
00635     js.header.stamp = getROSTime();
00636     std::vector<double> q = arm->getJointPosition();
00637     std::vector<std::string> names=arm->getJointName();
00638     for (size_t i = 0; i < q.size(); i++)
00639     {
00640       js.name.push_back(names[i]);
00641       js.position.push_back(q[i]);
00642       js.effort.push_back(0);
00643     }
00644 
00645     pub_.publish(js);
00646   }
00647 }
00648 
00649 ArmToROSJointState::~ArmToROSJointState()
00650 {
00651 }
00652 
00653 void VirtualCameraToROSImage::CameraBufferCallback::operator () (const osg::Camera& camera) const
00654 {
00655   if(pub)
00656   {
00657     pub->mutex.lock();
00658     if (depth)
00659     {
00660       pub->osgimage = new osg::Image(*cam->depthTexture.get());
00661     }
00662     else
00663     {
00664      pub->osgimage = new osg::Image(*cam->renderTexture.get());
00665     }
00666     pub->mutex.unlock();
00667   }
00668 }
00669 
00670 VirtualCameraToROSImage::CameraBufferCallback::CameraBufferCallback(VirtualCameraToROSImage * publisher,VirtualCamera *camera,int depth)
00671 {
00672   pub = publisher;
00673   cam=camera; //We could read data from cameracb, but it is already on virtualCamera.
00674   this->depth=depth;
00675 }
00676 
00677 VirtualCameraToROSImage::VirtualCameraToROSImage(VirtualCamera *camera, std::string topic, std::string info_topic,
00678                                                  int rate, int depth) :
00679     ROSPublisherInterface(info_topic, rate), cam(camera), image_topic(topic)
00680 {
00681   it.reset(new image_transport::ImageTransport(nh_));
00682   this->depth = depth;
00683   this->bw = camera->bw;
00684   CameraBufferCallback * buffercb = new  CameraBufferCallback(this,cam,depth); 
00685   cam->textureCamera->setPostDrawCallback(buffercb); 
00686 }
00687 
00688 void VirtualCameraToROSImage::createPublisher(ros::NodeHandle &nh)
00689 {
00690   ROS_INFO("VirtualCameraToROSImage publisher on topic %s", topic.c_str());
00691   while (!it)
00692   {
00693     ROS_INFO("VirtualCameraToROSImage Waiting for transport to be initialized...");
00694   }
00695   img_pub_ = it->advertise(image_topic, 1);
00696   pub_ = nh.advertise < sensor_msgs::CameraInfo > (topic, 1);
00697 }
00698 
00699 void VirtualCameraToROSImage::publish()
00700 {
00701   //OSG_DEBUG << "OSGImageToROSImage::publish entering" << std::endl;
00702   if (osgimage != NULL && osgimage->getTotalSizeInBytes() != 0)
00703   {
00704     //OSG_DEBUG << "\t image size: " << cam->renderTexture->s() << " " << cam->renderTexture->t() << " " << cam->renderTexture->getTotalSizeInBytes() << std::endl;
00705     int w, h, d;
00706     w = osgimage->s();
00707     h = osgimage->t();
00708     d = osgimage->getTotalSizeInBytes();
00709 
00710     if (d != 0)
00711     {
00712       sensor_msgs::Image img;
00713       sensor_msgs::CameraInfo img_info;
00714       img_info.header.stamp = img.header.stamp = getROSTime();
00715       img_info.header.frame_id = img.header.frame_id = cam->frameId;
00716       if (depth)
00717         img.encoding = std::string("32FC1");
00718       else if (bw)
00719         img.encoding = std::string("mono8");
00720       else
00721         img.encoding = std::string("rgb8");
00722 
00723       img.is_bigendian = 0;
00724       img.height = h;
00725       img.width = w;
00726       if (bw)
00727         d /=3;
00728       img.step = d / h;
00729       img.data.resize(d);
00730       img_info.width = w;
00731       img_info.height = h;
00732 
00733       img_info.D.resize(4, 0.0);
00734 
00735       img_info.R[0] = 1.0;
00736       img_info.R[4] = 1.0;
00737       img_info.R[8] = 1.0;
00738 
00739       img_info.K[0] = cam->fx;
00740       img_info.K[2] = cam->cx;
00741       img_info.K[4] = cam->fy;
00742       img_info.K[5] = cam->cy;
00743       img_info.K[8] = 1;
00744 
00745       img_info.P[0] = cam->fx;
00746       img_info.P[2] = cam->cx;
00747       img_info.P[3] = cam->Tx;
00748       img_info.P[5] = cam->fy;
00749       img_info.P[6] = cam->cy;
00750       img_info.P[7] = cam->Ty;
00751       img_info.P[10] = 1;
00752 
00753       img_info.roi.x_offset = 0;
00754       img_info.roi.y_offset = 0;
00755       img_info.roi.height = img_info.height;
00756       img_info.roi.width = img_info.width;
00757       img_info.roi.do_rectify = false;
00758 
00759       img_info.binning_x = 0;
00760       img_info.binning_y = 0;
00761 
00762       double fov, aspect, near, far;
00763       cam->textureCamera->getProjectionMatrixAsPerspective(fov, aspect, near, far);
00764       double a = far / (far - near);
00765       double b = (far * near) / (near - far);
00766 
00767       //img_info.distortion_model = sensor_msgs::distortion_models::PLUMB_BOB;
00768 
00769       mutex.lock();
00770       unsigned char *virtualdata = (unsigned char*)osgimage->data();
00771       //memcpy(&(img.data.front()),virtualdata,d*sizeof(char));
00772       //Memory cannot be directly copied, since the image frame used in OpenSceneGraph (OpenGL glReadPixels) is on
00773       //the bottom-left looking towards up-right, whereas ROS sensor_msgs::Image::data expects origin on top-left
00774       //looking towards bottom-right. Therefore it must be manually arranged.
00775       if (virtualdata != NULL)
00776         for (int i = 0; i < h; i++)
00777         {
00778           unsigned char *srcRow = virtualdata + i * img.step*(bw?3:1);
00779           unsigned char *dstRow = img.data.data() + (h - i - 1) * img.step;
00780           for (unsigned int j = 0;
00781               (!depth && j < img.step) || (depth && j < w); j++)
00782           {
00783             if (bw)
00784             {
00785               dstRow[j] = srcRow[j * 3] * 0.2989;
00786               dstRow[j] += srcRow[j * 3 + 1] * 0.5870;
00787               dstRow[j] += srcRow[j * 3 + 2] * 0.1140;
00788             }
00789             else if (depth)
00790             {
00791               float D = b / (((float*)srcRow)[j] - a);
00792               if (D == far)
00793                 D = INFINITY;
00794               else if (D == near)
00795                 D = -INFINITY;
00796               else if (!isfinite(D) || D < near || D > far)
00797                 D = NAN;
00798               ((float*)dstRow)[j] = D;
00799             }
00800             else
00801               dstRow[j] = srcRow[j];
00802           }
00803         }
00804       else
00805         memset(&(img.data.front()), 0, d);
00806       mutex.unlock();
00807 
00808       img_pub_.publish(img);
00809       pub_.publish(img_info);
00810     }
00811   }
00812   //OSG_DEBUG << "OSGImageToROSImage::publish exit" << std::endl;
00813 }
00814 
00815 VirtualCameraToROSImage::~VirtualCameraToROSImage()
00816 {
00817 }
00818 
00819 RangeSensorToROSRange::RangeSensorToROSRange(VirtualRangeSensor *rangesensor, std::string topic, int rate) :
00820     ROSPublisherInterface(topic, rate), rs(rangesensor)
00821 {
00822 }
00823 
00824 void RangeSensorToROSRange::createPublisher(ros::NodeHandle &nh)
00825 {
00826   ROS_INFO("RangeSensorToROSRange publisher on topic %s", topic.c_str());
00827   pub_ = nh.advertise < sensor_msgs::Range > (topic, 1);
00828 }
00829 
00830 void RangeSensorToROSRange::publish()
00831 {
00832   if (rs != NULL)
00833   {
00834     sensor_msgs::Range r;
00835     r.header.stamp = getROSTime();
00836     r.radiation_type = sensor_msgs::Range::ULTRASOUND;
00837     r.field_of_view = 0; //X axis of the sensor
00838     r.min_range = 0;
00839     r.max_range = rs->range;
00840     r.range = (rs->node_tracker != NULL) ? rs->node_tracker->distance_to_obstacle : r.max_range;
00841 
00842     pub_.publish(r);
00843   }
00844 }
00845 
00846 RangeSensorToROSRange::~RangeSensorToROSRange()
00847 {
00848 }
00849 
00850 MultibeamSensorToROS::MultibeamSensorToROS(MultibeamSensor *multibeamSensor, std::string topic, int rate) :
00851     ROSPublisherInterface(topic, rate), MB(multibeamSensor)
00852 {
00853 }
00854 
00855 void MultibeamSensorToROS::createPublisher(ros::NodeHandle &nh)
00856 {
00857   ROS_INFO(" MultibeamSensorToROS publisher on topic %s", topic.c_str());
00858   pub_ = nh.advertise < sensor_msgs::LaserScan > (topic, 1);
00859 }
00860 
00861 void MultibeamSensorToROS::publish()
00862 {
00863   if (MB != NULL)
00864   {
00865     sensor_msgs::LaserScan ls;
00866     ls.header.stamp = getROSTime();
00867     ls.header.frame_id = MB->name;
00868 
00869     double fov, aspect, near, far;
00870 
00871     ls.range_min = near;
00872     ls.range_max = MB->range; //far plane should be higher (z-buffer resolution)
00873     ls.angle_min = MB->initAngle * M_PI / 180;
00874     ls.angle_max = MB->finalAngle * M_PI / 180;
00875     ls.angle_increment = MB->angleIncr * M_PI / 180;
00876 
00877     std::vector<double> tmp;
00878     tmp.resize(MB->camPixels*MB->nCams);
00879     for(unsigned int j=0; j<MB->nCams ;j++)
00880     {
00881       MB->vcams[j].textureCamera->getProjectionMatrixAsPerspective(fov, aspect, near, far);
00882 
00883       float * data = (float *)MB->vcams[j].depthTexture->data();
00884       double a = far / (far - near);
00885       double b = (far * near) / (near - far);
00886 
00887       for (int i = 0; i < MB->camPixels; i++)
00888       {
00889         double Z = (data[i]); 
00890         tmp[i+MB->camPixels*j] = b / (Z - a);
00891       }
00892     }
00893 
00894     ls.ranges.resize(MB->numpixels);
00895     for (int i = 0; i < MB->numpixels; i++)
00896     {
00897       ls.ranges[i] = (tmp[MB->remapVector[i].pixel1] * MB->remapVector[i].weight1
00898           + tmp[MB->remapVector[i].pixel2] * MB->remapVector[i].weight2) * MB->remapVector[i].distort;
00899       if (ls.ranges[i] > MB->range)
00900         ls.ranges[i] = MB->range;
00901     }
00902 
00903     pub_.publish(ls);
00904   }
00905 }
00906 
00907 MultibeamSensorToROS::~MultibeamSensorToROS()
00908 {
00909 }
00910 
00911 contactSensorToROS::contactSensorToROS(osg::Group *rootNode, BulletPhysics * physics, std::string target,
00912                                        std::string topic, int rate) :
00913     ROSPublisherInterface(topic, rate)
00914 {
00915   this->rootNode = rootNode;
00916   this->physics = physics;
00917   this->target = target;
00918 }
00919 
00920 void contactSensorToROS::createPublisher(ros::NodeHandle &nh)
00921 {
00922   ROS_INFO("contactSensorToROS publisher on topic %s", topic.c_str());
00923   pub_ = nh.advertise < std_msgs::Bool > (topic, 1);
00924 }
00925 
00926 void contactSensorToROS::publish()
00927 {
00928   int colliding = 0;
00929 
00930   for (int i = 0; i < physics->getNumCollisions(); i++)
00931   {
00932     btPersistentManifold * col = physics->getCollision(i);
00933 
00934     #if BT_BULLET_VERSION <= 279
00935     //Get objects colliding
00936     btRigidBody* obA = static_cast<btRigidBody*>(col->getBody0());
00937     btRigidBody* obB = static_cast<btRigidBody*>(col->getBody1());
00938 
00939     //Check if target is involved in collision
00940     CollisionDataType * data = (CollisionDataType *)obA->getUserPointer();
00941     CollisionDataType * data2 = (CollisionDataType *)obB->getUserPointer();
00942 
00943     #else
00944     CollisionDataType * data = (CollisionDataType *)col->getBody0()->getUserPointer();
00945     CollisionDataType * data2 = (CollisionDataType *)col->getBody0()->getUserPointer();
00946     #endif
00947 
00948     int numContacts = col->getNumContacts();
00949 
00950     if (data2->getObjectName() == target || data->getObjectName() == target)
00951     {
00952       for (int j = 0; j < numContacts; j++)
00953       {
00954         btManifoldPoint pt = col->getContactPoint(j);
00955         if (pt.getDistance() < 0.f)
00956           colliding = 1;
00957       }
00958     }
00959 
00960   }
00961   std_msgs::Bool msg;
00962   msg.data = colliding;
00963   pub_.publish(msg);
00964 }
00965 
00966 contactSensorToROS::~contactSensorToROS()
00967 {
00968 }
00969 
00970 
00971 WorldToROSTF::WorldToROSTF(osg::Group *rootNode,  std::vector< boost::shared_ptr<SimulatedIAUV> > iauvFile,   std::vector<osg::ref_ptr<osg::Node> > objects, std::string worldRootName, unsigned int enableObjects, int rate ) :
00972     ROSPublisherInterface(worldRootName, rate)
00973 {
00974    iauvFile_ = iauvFile;
00975    objects_ = objects;
00976    rootNode_ = rootNode;
00977    for(int i = 0; i < iauvFile_.size(); i++)
00978    {
00979       KDL::Tree tree;
00980       if (!kdl_parser::treeFromFile(iauvFile[i].get()->urdf->URDFFile, tree))
00981       {
00982          ROS_ERROR("Failed to construct kdl tree");
00983       }
00984       else
00985       {
00986          ROS_INFO("Loaded tree, %d segments, %d joints", tree.getNrOfSegments(), tree.getNrOfJoints());
00987       }
00988       
00989       osg::ref_ptr<osg::MatrixTransform> transform;
00990       robot_pubs_.push_back(boost::shared_ptr<robot_state_publisher::RobotStatePublisher>(
00991        new robot_state_publisher::RobotStatePublisher(tree)));
00992   
00993       findNodeVisitor findNode(iauvFile[i].get()->name);
00994       rootNode->accept(findNode);
00995       osg::Node *first = findNode.getFirst();
00996       if (first == NULL)
00997       {
00998          transform = NULL;
00999       }
01000       else
01001       {
01002          transform = dynamic_cast<osg::MatrixTransform*>(first);
01003       }
01004       transforms_.push_back(transform);
01005    }
01006    worldRootName_ = worldRootName;
01007    enableObjects_ = enableObjects;//TODO Implement enableObjects feature
01008 }
01009 
01010 void WorldToROSTF::createPublisher(ros::NodeHandle &nh)
01011 {   
01012    tfpub_ = boost::shared_ptr<tf::TransformBroadcaster>(new tf::TransformBroadcaster());
01013 }
01014 
01015 void WorldToROSTF::publish()
01016 {
01017 
01018    //Publish vehicle frames
01019    for( int i = 0; i < iauvFile_.size(); i++ )
01020    {
01021       // Publish moving joints
01022       robot_pubs_[i]->RobotStatePublisher::publishTransforms(iauvFile_[i].get()->urdf->getFullJointMap(), getROSTime(), iauvFile_[i].get()->name);
01023       // Publish fixed joints
01024       robot_pubs_[i]->RobotStatePublisher::publishFixedTransforms(iauvFile_[i].get()->name);
01025       //Publish odometry
01026       if (transforms_[i] != NULL)
01027       {
01028          osg::Matrixd mat = transforms_[i]->getMatrix();
01029          osg::Vec3d pos = mat.getTrans();
01030          osg::Quat rot = mat.getRotate();
01031 
01032          tf::Vector3 p(pos.x(), pos.y(), pos.z());
01033          tf::Quaternion q(rot.x(), rot.y(), rot.z(), rot.w());
01034          tf::Pose pose(q,p);
01035          tf::StampedTransform t(pose, getROSTime(), "/" + worldRootName_, "/"+iauvFile_[i].get()->name);
01036       
01037          tf::Vector3 p2(0, 0, 0);
01038          tf::Quaternion q2(0, 0, 0, 1);
01039          tf::Pose pose2(q2,p2);
01040          tf::StampedTransform t2(pose2, getROSTime(), "/"+iauvFile_[i].get()->name, "/"+iauvFile_[i].get()->name + "/base_link");
01041 
01042          tfpub_->sendTransform(t2);
01043          tfpub_->sendTransform(t);  
01044       }
01045 
01046       //publish Cameras
01047       for(int j=0; j< iauvFile_[i].get()->camview.size();j++)
01048       {
01049         tf::Pose pose;
01050         std::string parent;
01051         tf::Pose OSGToTFconvention;
01052         if(iauvFile_[i].get()->camview[j].getTFTransform(pose,parent))
01053         {
01054           OSGToTFconvention.setOrigin(tf::Vector3(0,0,0));
01055           OSGToTFconvention.setRotation(tf::Quaternion(tf::Vector3(1,0,0),M_PI));//OSG convention is different to tf:
01056           //Remember that in opengl/osg, the camera frame is a right-handed system with Z going backwards (opposite to the viewing direction) and Y up.
01057           //While in tf convention, the camera frame is a right-handed system with Z going forward (in the viewing direction) and Y down.
01058 
01059           int multibeam=false;
01060           for(int k=0;k<iauvFile_[i].get()->multibeam_sensors.size();k++) //check if camera comes from multibeam
01061             if(iauvFile_[i].get()->multibeam_sensors[k].name==iauvFile_[i].get()->camview[j].name)
01062               multibeam=true;
01063               //OSGToTFconvention.setRotation(tf::Quaternion(tf::Vector3(0,1,0),M_PI/2));  //As we are using camera to simulate it, we need to rotate it
01064 
01065           if(!multibeam){
01066             pose=pose*OSGToTFconvention;
01067             tf::StampedTransform t(pose, getROSTime(),   "/"+iauvFile_[i].get()->name + "/" +parent, iauvFile_[i].get()->camview[j].name);
01068             tfpub_->sendTransform(t);
01069           }
01070         }  
01071       }
01072 
01073       //publish multibeams
01074       for(int j=0; j< iauvFile_[i].get()->multibeam_sensors.size();j++)
01075       {
01076         tf::Pose pose;
01077         std::string parent;
01078         if(iauvFile_[i].get()->multibeam_sensors[j].getTFTransform(pose,parent))
01079         {
01080           tf::StampedTransform t(pose, getROSTime(),   "/"+iauvFile_[i].get()->name + "/" +parent, iauvFile_[i].get()->multibeam_sensors[i].name);
01081           tfpub_->sendTransform(t);
01082         }  
01083       }
01084 
01085 
01086       //publish imus
01087       for(int j=0; j< iauvFile_[i].get()->imus.size();j++)
01088       {
01089         tf::Pose pose;
01090         std::string parent;
01091         if(iauvFile_[i].get()->imus[j].getTFTransform(pose,parent))
01092         {
01093           tf::StampedTransform t(pose, getROSTime(),   "/"+iauvFile_[i].get()->name + "/" +parent, iauvFile_[i].get()->imus[i].name);
01094           tfpub_->sendTransform(t);
01095         }  
01096       }
01097 
01098       //publish RangeSensor
01099       for(int j=0; j< iauvFile_[i].get()->range_sensors.size();j++)
01100       {
01101         tf::Pose pose;
01102         std::string parent;
01103         if(iauvFile_[i].get()->range_sensors[j].getTFTransform(pose,parent))
01104         {
01105           tf::StampedTransform t(pose, getROSTime(),   "/"+iauvFile_[i].get()->name + "/" +parent, iauvFile_[i].get()->range_sensors[i].name);
01106           tfpub_->sendTransform(t);
01107         }  
01108       }
01109 
01110       //publish PressureSensor
01111       for(int j=0; j< iauvFile_[i].get()->pressure_sensors.size();j++)
01112       {
01113         tf::Pose pose;
01114         std::string parent;
01115         if(iauvFile_[i].get()->pressure_sensors[j].getTFTransform(pose,parent))
01116         {
01117           tf::StampedTransform t(pose, getROSTime(),   "/"+iauvFile_[i].get()->name + "/" +parent, iauvFile_[i].get()->pressure_sensors[i].name);
01118           tfpub_->sendTransform(t);
01119         }  
01120       }
01121 
01122       //publish GPSSensor
01123       for(int j=0; j< iauvFile_[i].get()->gps_sensors.size();j++)
01124       {
01125         tf::Pose pose;
01126         std::string parent;
01127         if(iauvFile_[i].get()->gps_sensors[j].getTFTransform(pose,parent))
01128         {
01129           tf::StampedTransform t(pose, getROSTime(),   "/"+iauvFile_[i].get()->name + "/" +parent, iauvFile_[i].get()->gps_sensors[i].name);
01130           tfpub_->sendTransform(t);
01131         }  
01132       }
01133 
01134       //publish DVLSensor
01135       for(int j=0; j< iauvFile_[i].get()->dvl_sensors.size();j++)
01136       {
01137         tf::Pose pose;
01138         std::string parent;
01139         if(iauvFile_[i].get()->dvl_sensors[j].getTFTransform(pose,parent))
01140         {
01141           tf::StampedTransform t(pose, getROSTime(),   "/"+iauvFile_[i].get()->name + "/" +parent, iauvFile_[i].get()->dvl_sensors[i].name);
01142           tfpub_->sendTransform(t);
01143         }  
01144       }
01145 
01146    }
01147 
01148    //Publish object frames
01149    if(enableObjects_)
01150    {
01151 
01152      boost::shared_ptr<osg::Matrix> LWMat=getWorldCoords(findRN("localizedWorld",rootNode_));
01153      LWMat->invert(*LWMat);
01154 
01155      for(unsigned int i=0;i<objects_.size();i++)
01156      {
01157        boost::shared_ptr<osg::Matrix> objectMat= getWorldCoords(objects_[i]);
01158 
01159        osg::Matrixd  res=*objectMat * *LWMat;
01160 
01161        tf::Vector3 p2(res.getTrans().x(), res.getTrans().y(), res.getTrans().z());
01162        tf::Quaternion q2(res.getRotate().x(), res.getRotate().y(), res.getRotate().z(), res.getRotate().w());
01163        tf::Pose pose2(q2,p2);
01164        tf::StampedTransform t(pose2, getROSTime(),  "/" + worldRootName_, objects_[i]->getName());
01165 
01166        tfpub_->sendTransform(t);
01167      }
01168 
01169    }
01170 
01171 
01172 
01173 }
01174 
01175 WorldToROSTF::~WorldToROSTF()
01176 {
01177 }


uwsim
Author(s): Mario Prats , Javier Perez
autogenerated on Fri Aug 28 2015 13:28:58