tof_camera.cpp
Go to the documentation of this file.
00001 /****************************************************************
00002  *
00003  * Copyright (c) 2010
00004  *
00005  * Fraunhofer Institute for Manufacturing Engineering   
00006  * and Automation (IPA)
00007  *
00008  * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
00009  *
00010  * Project name: care-o-bot
00011  * ROS stack name: cob_driver
00012  * ROS package name: cob_camera_sensors
00013  *                                                              
00014  * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
00015  *                      
00016  * Author: Jan Fischer, email:jan.fischer@ipa.fhg.de
00017  * Supervised by: Jan Fischer, email:jan.fischer@ipa.fhg.de
00018  *
00019  * Date of creation: Jan 2010
00020  * ToDo:
00021  *
00022  * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
00023  *
00024  * Redistribution and use in source and binary forms, with or without
00025  * modification, are permitted provided that the following conditions are met:
00026  *
00027  *     * Redistributions of source code must retain the above copyright
00028  *       notice, this list of conditions and the following disclaimer.
00029  *     * Redistributions in binary form must reproduce the above copyright
00030  *       notice, this list of conditions and the following disclaimer in the
00031  *       documentation and/or other materials provided with the distribution.
00032  *     * Neither the name of the Fraunhofer Institute for Manufacturing 
00033  *       Engineering and Automation (IPA) nor the names of its
00034  *       contributors may be used to endorse or promote products derived from
00035  *       this software without specific prior written permission.
00036  *
00037  * This program is free software: you can redistribute it and/or modify
00038  * it under the terms of the GNU Lesser General Public License LGPL as 
00039  * published by the Free Software Foundation, either version 3 of the 
00040  * License, or (at your option) any later version.
00041  * 
00042  * This program is distributed in the hope that it will be useful,
00043  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00044  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00045  * GNU Lesser General Public License LGPL for more details.
00046  * 
00047  * You should have received a copy of the GNU Lesser General Public 
00048  * License LGPL along with this program. 
00049  * If not, see <http://www.gnu.org/licenses/>.
00050  *
00051  ****************************************************************/
00052 
00053 //##################
00054 //#### includes ####
00055 
00056 // standard includes
00057 //--
00058 
00059 // ROS includes
00060 #include <ros/ros.h>
00061 #include <image_transport/image_transport.h>
00062 #include <cv_bridge/CvBridge.h>
00063 
00064 // ROS message includes
00065 #include <sensor_msgs/Image.h>
00066 #include <sensor_msgs/CameraInfo.h>
00067 #include <sensor_msgs/fill_image.h>
00068 #include <sensor_msgs/SetCameraInfo.h>
00069 #include <sensor_msgs/PointCloud.h>
00070 #include <sensor_msgs/PointCloud2.h>
00071 
00072 #include <cob_camera_sensors/GetTOFImages.h>
00073 
00074 // external includes
00075 #include <cob_camera_sensors/AbstractRangeImagingSensor.h>
00076 #include <cob_vision_utils/CameraSensorToolbox.h>
00077 #include <cob_vision_utils/GlobalDefines.h>
00078 #include <cob_vision_utils/VisionUtils.h>
00079 
00080 #include <boost/thread/mutex.hpp>
00081 
00082 using namespace ipa_CameraSensors;
00083 
00084 class CobTofCameraNode
00085 {
00086         enum t_Mode
00087         {
00088                 MODE_TOPIC = 0,
00089                 MODE_SERVICE
00090         };
00091 
00092 private:
00093         ros::NodeHandle node_handle_;   
00094 
00095         image_transport::ImageTransport image_transport_;       
00096         image_transport::CameraPublisher xyz_image_publisher_;  
00097         image_transport::CameraPublisher grey_image_publisher_; 
00098         ros::Publisher topicPub_pointCloud_;
00099         ros::Publisher topicPub_pointCloud2_;
00100 
00101         sensor_msgs::CameraInfo camera_info_msg_;    
00102 
00103         ros::ServiceServer camera_info_service_;                
00104         ros::ServiceServer image_service_;
00105 
00106         AbstractRangeImagingSensorPtr tof_camera_;     
00107         
00108         std::string config_directory_; 
00109         int tof_camera_index_;  
00110         ipa_CameraSensors::t_cameraType tof_camera_type_; 
00111         bool filter_xyz_by_amplitude_;
00112         bool filter_xyz_tearoff_edges_;
00113         int lower_amplitude_threshold_;
00114         int upper_amplitude_threshold_;
00115         double tearoff_tear_half_fraction_;
00116 
00117         cv::Mat xyz_image_32F3_;        
00118         cv::Mat grey_image_32F1_;       
00119 
00120         CobTofCameraNode::t_Mode ros_node_mode_;        
00121         boost::mutex service_mutex_;
00122 
00123         bool publish_point_cloud_;
00124         bool publish_point_cloud_2_;
00125 
00126 public:
00128     CobTofCameraNode(const ros::NodeHandle& node_handle)
00129     : node_handle_(node_handle),
00130           image_transport_(node_handle),
00131       tof_camera_(AbstractRangeImagingSensorPtr()),
00132       xyz_image_32F3_(cv::Mat()),
00133       grey_image_32F1_(cv::Mat()),
00134       publish_point_cloud_(false),
00135       publish_point_cloud_2_(false)
00136     {
00138     }
00139         
00141         ~CobTofCameraNode()
00142     {
00143         tof_camera_->Close();
00144     }
00145 
00148     bool init()
00149     {
00150                 if (loadParameters() == false)
00151                 {
00152                         ROS_ERROR("[color_camera] Could not read all parameters from launch file");
00153                         return false;
00154                 }               
00155                 
00156 
00157                 if (tof_camera_->Init(config_directory_, tof_camera_index_) & ipa_CameraSensors::RET_FAILED)
00158                 {
00159 
00160                         std::stringstream ss;
00161                         ss << "Initialization of tof camera ";
00162                         ss << tof_camera_index_;
00163                         ss << " failed";
00164                         ROS_ERROR("[tof_camera] %s", ss.str().c_str());
00165                         tof_camera_ = AbstractRangeImagingSensorPtr();
00166                         return false;
00167                 }
00168         
00169                 if (tof_camera_->Open() & ipa_CameraSensors::RET_FAILED)
00170                 {
00171                         std::stringstream ss;
00172                         ss << "Could not open tof camera ";
00173                         ss << tof_camera_index_;
00174                         ROS_ERROR("[tof_camera] %s", ss.str().c_str());
00175                         tof_camera_ = AbstractRangeImagingSensorPtr();
00176                         return false;
00177                 }
00178 
00180                 ipa_CameraSensors::t_cameraProperty cameraProperty;
00181                 cameraProperty.propertyID = ipa_CameraSensors::PROP_CAMERA_RESOLUTION;
00182                 tof_camera_->GetProperty(&cameraProperty);
00183                 int range_sensor_width = cameraProperty.cameraResolution.xResolution;
00184                 int range_sensor_height = cameraProperty.cameraResolution.yResolution;
00185                 cv::Size range_image_size(range_sensor_width, range_sensor_height);
00186 
00188                 ipa_CameraSensors::CameraSensorToolboxPtr tof_sensor_toolbox = ipa_CameraSensors::CreateCameraSensorToolbox();
00189                 tof_sensor_toolbox->Init(config_directory_, tof_camera_->GetCameraType(), tof_camera_index_, range_image_size);
00190 
00191                 cv::Mat intrinsic_mat = tof_sensor_toolbox->GetIntrinsicMatrix(tof_camera_type_, tof_camera_index_);
00192                 cv::Mat distortion_map_X = tof_sensor_toolbox->GetDistortionMapX(tof_camera_type_, tof_camera_index_);
00193                 cv::Mat distortion_map_Y = tof_sensor_toolbox->GetDistortionMapY(tof_camera_type_, tof_camera_index_);
00194                 tof_camera_->SetIntrinsics(intrinsic_mat, distortion_map_X, distortion_map_Y);
00195 
00197                 camera_info_service_ = node_handle_.advertiseService("set_camera_info", &CobTofCameraNode::setCameraInfo, this);
00198                 image_service_ = node_handle_.advertiseService("get_images", &CobTofCameraNode::imageSrvCallback, this);
00199                 xyz_image_publisher_ = image_transport_.advertiseCamera("image_xyz", 1);
00200                 grey_image_publisher_ = image_transport_.advertiseCamera("image_grey", 1);
00201                 if(publish_point_cloud_2_) topicPub_pointCloud2_ = node_handle_.advertise<sensor_msgs::PointCloud2>("point_cloud2", 1);
00202                 if(publish_point_cloud_) topicPub_pointCloud_ = node_handle_.advertise<sensor_msgs::PointCloud>("point_cloud", 1);
00203 
00204                 cv::Mat d = tof_sensor_toolbox->GetDistortionParameters(tof_camera_type_, tof_camera_index_);
00205                 camera_info_msg_.header.stamp = ros::Time::now();
00206                 camera_info_msg_.header.frame_id = "head_tof_link";
00207                 /*camera_info_msg_.D[0] = d.at<double>(0, 0);
00208                 camera_info_msg_.D[1] = d.at<double>(0, 1);
00209                 camera_info_msg_.D[2] = d.at<double>(0, 2);
00210                 camera_info_msg_.D[3] = d.at<double>(0, 3);
00211                 camera_info_msg_.D[4] = 0;*/
00212         
00213                 cv::Mat k = tof_sensor_toolbox->GetIntrinsicMatrix(tof_camera_type_, tof_camera_index_);
00214                 /*camera_info_msg_.K[0] = k.at<double>(0, 0);
00215                 camera_info_msg_.K[1] = k.at<double>(0, 1);
00216                 camera_info_msg_.K[2] = k.at<double>(0, 2);
00217                 camera_info_msg_.K[3] = k.at<double>(1, 0);
00218                 camera_info_msg_.K[4] = k.at<double>(1, 1);
00219                 camera_info_msg_.K[5] = k.at<double>(1, 2);
00220                 camera_info_msg_.K[6] = k.at<double>(2, 0);
00221                 camera_info_msg_.K[7] = k.at<double>(2, 1);
00222                 camera_info_msg_.K[8] = k.at<double>(2, 2);*/
00223 
00224                 camera_info_msg_.width = range_sensor_width;            
00225                 camera_info_msg_.height = range_sensor_height;
00226 
00227                 return true;
00228         }
00229 
00234         bool setCameraInfo(sensor_msgs::SetCameraInfo::Request& req,
00235                     sensor_msgs::SetCameraInfo::Response& rsp)
00236         {
00238                 camera_info_msg_ = req.camera_info;
00239 
00240                 rsp.success = false;
00241                 rsp.status_message = "[tof_camera] Setting camera parameters through ROS not implemented";
00242 
00243                 return true;
00244         }
00245 
00247         bool spin()
00248         {
00249                 boost::mutex::scoped_lock lock(service_mutex_);
00250                 sensor_msgs::Image::Ptr xyz_image_msg_ptr;
00251                 sensor_msgs::Image::Ptr grey_image_msg_ptr;
00252                 sensor_msgs::CameraInfo tof_image_info;
00253         
00254                 if(tof_camera_->AcquireImages(0, &grey_image_32F1_, &xyz_image_32F3_, false, false, ipa_CameraSensors::INTENSITY_32F1) & ipa_Utils::RET_FAILED)
00255                 {
00256                         ROS_ERROR("[tof_camera] Tof image acquisition failed");
00257                         return false;
00258                 }
00259 
00261                 //if(filter_xyz_tearoff_edges_ || filter_xyz_by_amplitude_)
00262                 //      ROS_ERROR("[tof_camera] FUNCTION UNCOMMENT BY JSF");
00263                 if(filter_xyz_tearoff_edges_) ipa_Utils::FilterTearOffEdges(xyz_image_32F3_, 0, (float)tearoff_tear_half_fraction_);
00264                 if(filter_xyz_by_amplitude_) ipa_Utils::FilterByAmplitude(xyz_image_32F3_, grey_image_32F1_, 0, 0, lower_amplitude_threshold_, upper_amplitude_threshold_);
00265 
00266                 try
00267                 {
00268                         IplImage img = xyz_image_32F3_;
00269                         xyz_image_msg_ptr = sensor_msgs::CvBridge::cvToImgMsg(&img, "passthrough");
00270                 }
00271                 catch (sensor_msgs::CvBridgeException error)
00272                 {
00273                         ROS_ERROR("[tof_camera] Could not convert 32bit xyz IplImage to ROS message");
00274                         return false;
00275                 }
00276 
00277                 try
00278                 {
00279                         IplImage img = grey_image_32F1_;
00280                         grey_image_msg_ptr = sensor_msgs::CvBridge::cvToImgMsg(&img, "passthrough");
00281                 }
00282                 catch (sensor_msgs::CvBridgeException error)
00283                 {
00284                         ROS_ERROR("[tof_camera] Could not convert 32bit grey IplImage to ROS message");
00285                         return false;
00286                 }
00287 
00289                 ros::Time now = ros::Time::now();
00290                 xyz_image_msg_ptr->header.stamp = now;
00291                 xyz_image_msg_ptr->header.frame_id = "head_tof_link";
00292                 grey_image_msg_ptr->header.stamp = now;
00293                 grey_image_msg_ptr->header.frame_id = "head_tof_link";
00294 
00295                 tof_image_info = camera_info_msg_;
00296                 tof_image_info.width = grey_image_32F1_.cols;
00297                 tof_image_info.height = grey_image_32F1_.rows;
00298                 tof_image_info.header.stamp = now;
00299                 tof_image_info.header.frame_id = "head_tof_link";
00300 
00302                 xyz_image_publisher_.publish(*xyz_image_msg_ptr, tof_image_info);
00303                 grey_image_publisher_.publish(*grey_image_msg_ptr, tof_image_info);
00304 
00305                 if(publish_point_cloud_) publishPointCloud(now);
00306                 if(publish_point_cloud_2_) publishPointCloud2(now);
00307 
00308                 return true;
00309         }
00310 
00311     void publishPointCloud(ros::Time now)
00312     {
00313         ROS_DEBUG("convert xyz_image to point_cloud");
00314         sensor_msgs::PointCloud pc_msg;
00315                 // create point_cloud message
00316                 pc_msg.header.stamp = now;
00317                 pc_msg.header.frame_id = "head_tof_link";
00318 
00319                 cv::Mat cpp_xyz_image_32F3 = xyz_image_32F3_;
00320                 cv::Mat cpp_grey_image_32F1 = grey_image_32F1_;
00321 
00322                 float* f_ptr = 0;
00323                 for (int row = 0; row < cpp_xyz_image_32F3.rows; row++)
00324                 {
00325                         f_ptr = cpp_xyz_image_32F3.ptr<float>(row);
00326                         for (int col = 0; col < cpp_xyz_image_32F3.cols; col++)
00327                         {
00328                                 geometry_msgs::Point32 pt;
00329                                 pt.x = f_ptr[3*col + 0];
00330                                 pt.y = f_ptr[3*col + 1];
00331                                 pt.z = f_ptr[3*col + 2];
00332                                 pc_msg.points.push_back(pt);
00333                         }
00334                 }
00335         topicPub_pointCloud_.publish(pc_msg);
00336     }
00337 
00338         void publishPointCloud2(ros::Time now)
00339         {
00340                 cv::Mat cpp_xyz_image_32F3 = xyz_image_32F3_;
00341                 cv::Mat cpp_confidence_mask_32F1 = grey_image_32F1_;
00342 
00343                 sensor_msgs::PointCloud2 pc_msg;
00344                 // create point_cloud message
00345                 pc_msg.header.stamp = now;
00346                 pc_msg.header.frame_id = "head_tof_link";
00347                 pc_msg.width = cpp_xyz_image_32F3.cols;
00348                 pc_msg.height = cpp_xyz_image_32F3.rows;
00349                 pc_msg.fields.resize(4);
00350                 pc_msg.fields[0].name = "x";
00351                 pc_msg.fields[0].datatype = sensor_msgs::PointField::FLOAT32;
00352                 pc_msg.fields[1].name = "y";
00353                 pc_msg.fields[1].datatype = sensor_msgs::PointField::FLOAT32;
00354                 pc_msg.fields[2].name = "z";
00355                 pc_msg.fields[2].datatype = sensor_msgs::PointField::FLOAT32;
00356                 pc_msg.fields[3].name = "confidence";
00357                 pc_msg.fields[3].datatype = sensor_msgs::PointField::FLOAT32;
00358                 int offset = 0;
00359                 for (size_t d = 0; d < pc_msg.fields.size(); ++d, offset += 4)
00360                 {
00361                         pc_msg.fields[d].offset = offset;
00362                 }
00363                 pc_msg.point_step = 16;
00364                 pc_msg.row_step = pc_msg.point_step * pc_msg.width;
00365                 pc_msg.data.resize (pc_msg.width*pc_msg.height*pc_msg.point_step);
00366                 pc_msg.is_dense = true;
00367                 pc_msg.is_bigendian = false;
00368 
00369                 float* f_ptr = 0;
00370                 float* g_ptr = 0;
00371                 int pc_msg_idx=0;
00372                 for (int row = 0; row < cpp_xyz_image_32F3.rows; row++)
00373                 {
00374                         f_ptr = cpp_xyz_image_32F3.ptr<float>(row);
00375                         g_ptr = cpp_confidence_mask_32F1.ptr<float>(row);
00376                         for (int col = 0; col < cpp_xyz_image_32F3.cols; col++, pc_msg_idx++)
00377                         {
00378                                 memcpy(&pc_msg.data[pc_msg_idx * pc_msg.point_step], &f_ptr[3*col], 3*sizeof(float));
00379                                 memcpy(&pc_msg.data[pc_msg_idx * pc_msg.point_step + pc_msg.fields[3].offset], &g_ptr[col], sizeof(float));
00380                         }
00381                 }
00382                 topicPub_pointCloud2_.publish(pc_msg);
00383         }
00384 
00385         bool imageSrvCallback(cob_camera_sensors::GetTOFImages::Request &req,
00386                         cob_camera_sensors::GetTOFImages::Response &res)
00387         {
00388                 boost::mutex::scoped_lock lock(service_mutex_);
00389                 // Convert openCV IplImages to ROS messages
00390                 try
00391                 {
00392                         IplImage grey_img = grey_image_32F1_;
00393                         IplImage xyz_img = xyz_image_32F3_;
00394                         res.greyImage = *(sensor_msgs::CvBridge::cvToImgMsg(&grey_img, "passthrough"));
00395                         res.xyzImage = *(sensor_msgs::CvBridge::cvToImgMsg(&xyz_img, "passthrough"));
00396                 }
00397                 catch (sensor_msgs::CvBridgeException error)
00398                 {
00399                         ROS_ERROR("[tof_camera_type_node] Could not convert IplImage to ROS message");
00400                 }
00401 
00402                 // Set time stamp
00403                 ros::Time now = ros::Time::now();
00404                 res.greyImage.header.stamp = now;
00405                 res.greyImage.header.frame_id = "head_tof_link";
00406                 res.xyzImage.header.stamp = now;
00407                 res.xyzImage.header.frame_id = "head_tof_link";
00408                 return true;
00409         }
00410 
00411         bool loadParameters()
00412         {
00413                 std::string tmp_string = "NULL";
00414                
00416                 if (node_handle_.getParam("tof_camera/configuration_files", config_directory_) == false)
00417                 {
00418                                 ROS_ERROR("[tof_camera] Path to xml configuration for tof camera not specified");
00419                                 return false;
00420                 }
00421 
00422                 ROS_INFO("Configuration directory: %s", config_directory_.c_str());
00423 
00425                 if (node_handle_.getParam("tof_camera/tof_camera_index", tof_camera_index_) == false)
00426                 {
00427                         ROS_ERROR("[tof_camera] 'tof_camera_index' (0 or 1) not specified");
00428                         return false;
00429                 }
00430 
00432                 if (node_handle_.getParam("tof_camera/tof_camera_type", tmp_string) == false)
00433                 {
00434                         ROS_ERROR("[tof_camera] 'tof_camera_type' not specified");
00435                         return false;
00436                 }
00437                 if (tmp_string == "CAM_SWISSRANGER") 
00438                 {
00439                         tof_camera_ = ipa_CameraSensors::CreateRangeImagingSensor_Swissranger();
00440                         tof_camera_type_ = ipa_CameraSensors::CAM_SWISSRANGER;
00441                 }
00442                 else if (tmp_string == "CAM_VIRTUAL") 
00443                 {
00444                         tof_camera_ = ipa_CameraSensors::CreateRangeImagingSensor_VirtualCam();
00445                         tof_camera_type_ = ipa_CameraSensors::CAM_VIRTUALRANGE;
00446                 }
00447                 else
00448                 {
00449                         std::string str = "[tof_camera] Camera type '" + tmp_string + "' unknown, try 'CAM_SWISSRANGER'";
00450                         ROS_ERROR("%s", str.c_str());
00451                         return false;
00452                 }
00453 
00454                 ROS_INFO("Camera type: %s_%d", tmp_string.c_str(), tof_camera_index_);
00455 
00457                 if (node_handle_.getParam("tof_camera/filter_xyz_by_amplitude", filter_xyz_by_amplitude_) == false)
00458                 {
00459                         ROS_ERROR("[tof_camera] 'filter_xyz_by_amplitude not specified");
00460                         return false;
00461                 }
00463                 if (node_handle_.getParam("tof_camera/filter_xyz_tearoff_edges", filter_xyz_tearoff_edges_) == false)
00464                 {
00465                         ROS_ERROR("[tof_camera] 'filter_xyz_tearoff_edges_' not specified");
00466                         return false;
00467                 }
00469                 if (node_handle_.getParam("tof_camera/lower_amplitude_threshold", lower_amplitude_threshold_) == false)
00470                 {
00471                         ROS_ERROR("[tof_camera] 'lower_amplitude_threshold' not specified");
00472                         return false;
00473                 }
00475                 if (node_handle_.getParam("tof_camera/upper_amplitude_threshold", upper_amplitude_threshold_) == false)
00476                 {
00477                         ROS_ERROR("[tof_camera] 'upper_amplitude_threshold' not specified");
00478                         return false;
00479                 }
00481                 if (node_handle_.getParam("tof_camera/tearoff_pi_half_fraction", tearoff_tear_half_fraction_) == false)
00482                 {
00483                         ROS_ERROR("[tof_camera] 'tearoff_pi_half_fraction' not specified");
00484                         return false;
00485                 }
00487                 if (node_handle_.getParam("tof_camera/ros_node_mode", tmp_string) == false)
00488                 {
00489                         ROS_ERROR("[tof_camera] Mode for tof camera node not specified");
00490                         return false;
00491                 }
00492                 if (tmp_string == "MODE_SERVICE")
00493                 {
00494                         ros_node_mode_ = CobTofCameraNode::MODE_SERVICE;
00495                 }
00496                 else if (tmp_string == "MODE_TOPIC")
00497                 {
00498                         ros_node_mode_ = CobTofCameraNode::MODE_TOPIC;
00499                 }
00500                 else
00501                 {
00502                         std::string str = "[tof_camera] Mode '" + tmp_string + "' unknown, try 'MODE_SERVICE' or 'MODE_TOPIC'";
00503                         ROS_ERROR("%s", str.c_str());
00504                         return false;
00505                 }
00506 
00507                 if(node_handle_.getParam("tof_camera/publish_point_cloud", publish_point_cloud_) == false)
00508                 {
00509                         ROS_WARN("[tof_camera] Flag for publishing PointCloud not set, falling back to default (false)");
00510                 }
00511                 if(node_handle_.getParam("tof_camera/publish_point_cloud_2", publish_point_cloud_2_) == false)
00512                 {
00513                         ROS_WARN("[tof_camera] Flag for publishing PointCloud2 not set, falling back to default (false)");
00514                 }
00515 
00516 
00517                 ROS_INFO("ROS node mode: %s", tmp_string.c_str());
00518 
00519                 return true;
00520         }
00521 };
00522 
00523 
00524 //#######################
00525 //#### main programm ####
00526 int main(int argc, char** argv)
00527 {
00529     ros::init(argc, argv, "tof_camera");
00530 
00532     ros::NodeHandle nh;
00533 
00535     CobTofCameraNode camera_node(nh);
00536 
00538     if (!camera_node.init()) return 0;
00539 
00540     //ros::spin();
00541         ros::Rate rate(100);
00542         while(nh.ok())
00543         {
00544                 camera_node.spin();
00545                 ros::spinOnce();
00546                 rate.sleep();
00547         }
00548 
00549         return 0;
00550 }


cob_camera_sensors
Author(s): Jan Fischer
autogenerated on Thu Aug 27 2015 12:45:58