xb3_full_speed.cpp
Go to the documentation of this file.
00001 #include <iostream>
00002 #include <chrono>
00003 #include <cstring>
00004 #include <csignal>
00005 
00006 #include <dc1394/log.h>
00007 #include <dc1394/control.h>
00008 #include <dc1394/conversions.h>
00009 
00010 #include <opencv2/imgproc/imgproc.hpp>
00011 #include <opencv2/highgui/highgui.hpp>
00012 
00013 #include <boost/thread.hpp>
00014 
00015 #include <ros/ros.h>
00016 
00017 #include <image_transport/image_transport.h>
00018 #include <camera_info_manager/camera_info_manager.h>
00019 
00020 #include <sensor_msgs/image_encodings.h>
00021 
00022 #include <diagnostic_updater/diagnostic_updater.h>
00023 #include <diagnostic_updater/publisher.h>
00024 
00025 #define BAYER_TILE_MAPPING_REGISTER     (0x1040)
00026 #define SENSOR_BOARD_INFO_REGISTER      (0x1f28)
00027 #define IMAGE_DATA_FORMAT_REGISTER  (0x1048)
00028 
00029 using namespace std;
00030 
00031 class xb3CameraDriver
00032 {
00033     public:
00034         dc1394_t* d;
00035         unsigned int nThisCam;
00036         dc1394camera_t* camera;
00037         dc1394color_filter_t bayerTile;
00038         
00039         unsigned int nBufferSize;
00040         unsigned char* pucRightRGB;
00041         unsigned char* pucLeftRGB;
00042         unsigned char* pucCenterRGB;
00043         unsigned char* pucGreenBuffer;
00044         unsigned char* pucRGBBuffer;
00045         unsigned char* pucDeInterlacedBuffer;
00046         unsigned char* pucGrabBuffer;
00047         
00048         const unsigned int hardware_width;
00049         const unsigned int hardware_height;
00050         const unsigned int nBytesPerPixel;
00051         
00052         unsigned int output_width;
00053         unsigned int output_height;
00054         
00055         //Load camera info from file
00056         string left_calibration_file;
00057         string center_calibration_file;
00058         string right_calibration_file;
00059             
00060         image_transport::CameraPublisher left_publisher;
00061         image_transport::CameraPublisher center_publisher;
00062         image_transport::CameraPublisher right_publisher;
00063         
00064         camera_info_manager::CameraInfoManager left_info_manager;
00065         camera_info_manager::CameraInfoManager center_info_manager;
00066         camera_info_manager::CameraInfoManager right_info_manager;
00067         
00068         sensor_msgs::CameraInfo left_camera_info;
00069         sensor_msgs::CameraInfo center_camera_info;
00070         sensor_msgs::CameraInfo right_camera_info;
00071         
00072         sensor_msgs::Image left;                                                                                                                                                                   
00073         sensor_msgs::Image center;
00074         sensor_msgs::Image right;
00075             
00076         cv::Mat opencv_left;
00077         cv::Mat opencv_center;
00078         cv::Mat opencv_right;
00079         
00080         cv::Mat opencv_output_left;
00081         cv::Mat opencv_output_center;
00082         cv::Mat opencv_output_right;
00083         
00084         boost::thread* buffer_processing_thread;
00085         boost::thread_group threads;
00086 
00087         ros::NodeHandle nh;
00088         image_transport::ImageTransport it;
00089         
00090         //Diagnostics class
00091         diagnostic_updater::Updater updater;
00092         
00093         //Maximum admissible frequency
00094         double status_max_frequency;
00095         
00096         //Minimum admissible frequency
00097         double status_min_frequency;
00098         
00099         diagnostic_updater::HeaderlessTopicDiagnostic frequency_diagnostics;
00100     
00101         xb3CameraDriver(ros::NodeHandle nh_):
00102         nh(nh_),
00103         it(nh_),
00104         hardware_width(1280),
00105         hardware_height(960),
00106         nBytesPerPixel(3),
00107         left_info_manager(ros::NodeHandle(nh_, "left"),"left"),
00108         center_info_manager(ros::NodeHandle(nh_, "center"),"center"),
00109         right_info_manager(ros::NodeHandle(nh_, "right"),"right"),
00110         status_max_frequency(18),
00111         status_min_frequency(14),
00112         frequency_diagnostics("Xb3",updater,diagnostic_updater::FrequencyStatusParam(&status_min_frequency,&status_max_frequency, 0.01, 10)),
00113         buffer_processing_thread(NULL)
00114         {
00115             updater.setHardwareID("Xb3");
00116             
00117             init();
00118             
00119             //Reduce shutter time
00120 //             dc1394_feature_set_mode(camera,DC1394_FEATURE_SHUTTER,DC1394_FEATURE_MODE_MANUAL);
00121 //             dc1394_feature_set_value(camera,DC1394_FEATURE_SHUTTER,100);
00122             
00123             nBufferSize = hardware_height*hardware_width*nBytesPerPixel;
00124             pucDeInterlacedBuffer = new unsigned char[nBufferSize];
00125             pucRGBBuffer = new unsigned char[3*nBufferSize];
00126             pucGreenBuffer = new unsigned char[nBufferSize];
00127             pucGrabBuffer = new unsigned char[3*nBufferSize];
00128             
00129             opencv_left.create(cvSize(hardware_width,hardware_height),CV_8UC3);
00130             opencv_center.create(cvSize(hardware_width,hardware_height),CV_8UC3);
00131             opencv_right.create(cvSize(hardware_width,hardware_height),CV_8UC3);
00132             
00133             //Create camera publishers
00134             left_publisher = it.advertiseCamera("left/image_raw", 100);
00135             center_publisher = it.advertiseCamera("center/image_raw", 100);
00136             right_publisher = it.advertiseCamera("right/image_raw", 100);
00137             
00138             //Load calibration paths
00139             nh.param("left_calibration_file",left_calibration_file,string("package://xb3/calibrations/left_full_resolution.yaml"));
00140             nh.param("center_calibration_file",center_calibration_file,string("package://xb3/calibrations/center_full_resolution.yaml"));
00141             nh.param("right_calibration_file",right_calibration_file,string("package://xb3/calibrations/right_full_resolution.yaml"));
00142             
00143             //Load camera calibrations
00144             left_info_manager.loadCameraInfo(left_calibration_file);
00145             center_info_manager.loadCameraInfo(center_calibration_file);
00146             right_info_manager.loadCameraInfo(right_calibration_file);
00147             
00148             //Create camera info messages
00149             left_camera_info = left_info_manager.getCameraInfo();
00150             center_camera_info = center_info_manager.getCameraInfo();
00151             right_camera_info = right_info_manager.getCameraInfo();
00152             
00153             //Fill the remaining parameters
00154             left_camera_info.header.frame_id = ros::names::remap("/xb3_optical_frame"); 
00155             left_camera_info.roi.do_rectify = true;
00156             
00157             center_camera_info.header.frame_id = ros::names::remap("/xb3_optical_frame");
00158             center_camera_info.roi.do_rectify = true;
00159             
00160             right_camera_info.header.frame_id = ros::names::remap("/xb3_optical_frame");
00161             right_camera_info.roi.do_rectify = true;
00162             
00163             output_height = left_camera_info.height;
00164             output_width = left_camera_info.width;
00165             
00166             opencv_output_left.create(cvSize(output_width,output_height),CV_8UC3);
00167             opencv_output_center.create(cvSize(output_width,output_height),CV_8UC3);
00168             opencv_output_right.create(cvSize(output_width,output_height),CV_8UC3);
00169             
00170             //Fill image static parameters
00171             left.height = left_camera_info.height; //set the height.
00172             left.width = left_camera_info.width; //set the width
00173             left.encoding = sensor_msgs::image_encodings::RGB8; //Set the encoding
00174             left.is_bigendian = 0;
00175             left.step = left.width*3;
00176             left.data.resize(left.width*left.height*3);
00177             left.header.frame_id = ros::names::remap( "/xb3_optical_frame");
00178             
00179             center.height = center_camera_info.height; //set the height.
00180             center.width = center_camera_info.width; //set the width
00181             center.encoding = sensor_msgs::image_encodings::RGB8; //Set the encoding
00182             center.is_bigendian = 0;
00183             center.step = center.width*3;
00184             center.data.resize(center.width*center.height*3);
00185             center.header.frame_id = ros::names::remap( "/xb3_optical_frame");
00186             
00187             right.height = right_camera_info.height; //set the height.
00188             right.width = right_camera_info.width; //set the width
00189             right.encoding = sensor_msgs::image_encodings::RGB8; //Set the encoding
00190             right.is_bigendian = 0;
00191             right.step = right.width*3;
00192             right.data.resize(right.width*right.height*3);
00193             right.header.frame_id = ros::names::remap( "/xb3_optical_frame");
00194         }
00195         
00196         ~xb3CameraDriver()
00197         {
00198             cout<<"[ xb3CameraDriver]: Stopping transmission"<<endl;
00199             buffer_processing_thread->join();
00200             clean_dc1394();
00201             cout<<"[ xb3CameraDriver]: Camera closed"<<endl;
00202         }
00203         
00204         void clean_dc1394(void)
00205         {
00206             dc1394_capture_stop(camera);
00207             dc1394_video_set_transmission(camera, DC1394_OFF);
00208             dc1394_camera_free(camera);
00209         }
00210         
00211         dc1394error_t init()
00212         {
00213             dc1394camera_list_t* list;
00214             dc1394error_t err;
00215 
00216             // Find cameras on the 1394 buses
00217             d = dc1394_new();
00218 
00219             // Enumerate cameras connected to the PC
00220             err = dc1394_camera_enumerate (d, &list);
00221             if (err != DC1394_SUCCESS )
00222             {
00223                 ROS_ERROR("Unable to look for cameras\n\nPlease check \n"
00224                                 "  - if the kernel modules `ieee1394',`raw1394' and `ohci1394' are loaded \n"
00225                                 "  - if you have read/write access to /dev/raw1394\n\n");
00226                 return err;
00227             }
00228 
00229             //  get the camera nodes and describe them as we find them
00230             if (list->num == 0)
00231             {
00232                 ROS_ERROR("No cameras found!");
00233                 clean_dc1394();
00234                 return DC1394_FAILURE;
00235             }
00236 
00237             ROS_INFO("There were %d camera(s) found attached to your PC", list->num);
00238 
00239             // Identify cameras. Use the first stereo camera that is found
00240             for ( nThisCam = 0; nThisCam < list->num; nThisCam++ )
00241             {
00242                 camera = dc1394_camera_new(d, list->ids[nThisCam].guid);
00243 
00244                 if(!camera)
00245                 {
00246                     ROS_ERROR("Failed to initialize camera with guid %lx", list->ids[nThisCam].guid);
00247                     continue;
00248                 }
00249 
00250                 ROS_INFO("Camera %d model = '%s'", nThisCam, camera->model );
00251 
00252                 if(strncmp(camera->model, "Bumblebee XB3", strlen("Bumblebee XB3")) == 0)
00253                 {
00254                     ROS_INFO("Found Bumblebee XB3 camera");
00255                     break;
00256                 }
00257 
00258                 dc1394_camera_free(camera);
00259             }
00260 
00261             // set the capture mode
00262             ROS_INFO("Setting stereo video capture mode");
00263 
00264             err = setStereoVideoCapture();
00265             if ( err != DC1394_SUCCESS )
00266             {
00267                 ROS_ERROR("Could not set up video capture mode");
00268                 clean_dc1394();
00269                 return DC1394_FAILURE;
00270             }
00271 
00272             // have the camera start sending us data
00273             ROS_INFO("Start transmission");
00274             err = startTransmission();
00275             if ( err != DC1394_SUCCESS )
00276             {
00277                 ROS_ERROR("Unable to start camera iso transmission" );
00278                 clean_dc1394();
00279             }
00280             
00281             err = getBayerTile(camera,&bayerTile);
00282             if ( err != DC1394_SUCCESS )
00283             {
00284                 ROS_ERROR("Could not query the Bayer Tile Register!" );
00285                 return DC1394_FAILURE;
00286             }
00287       
00288             return DC1394_SUCCESS;
00289         }
00290         
00291         dc1394error_t getBayerTile( dc1394camera_t* camera,dc1394color_filter_t* bayerPattern)
00292         {
00293             uint32_t value;
00294             dc1394error_t err;
00295 
00296             // query register 0x1040
00297             // This register is an advanced PGR register called BAYER_TILE_MAPPING
00298             // For more information check the PGR IEEE-1394 Digital Camera Register Reference
00299             err = dc1394_get_control_register( camera, BAYER_TILE_MAPPING_REGISTER, &value );
00300             if ( err != DC1394_SUCCESS )
00301             {
00302                 return err;
00303             }
00304 
00305             // Ascii R = 52 G = 47 B = 42 Y = 59
00306             switch( value )
00307             {
00308                 default:
00309                 case 0x59595959:  // YYYY
00310                 // no bayer
00311                 *bayerPattern = (dc1394color_filter_t) 0;
00312                 break;
00313                 case 0x52474742:  // RGGB
00314                 *bayerPattern = DC1394_COLOR_FILTER_RGGB;
00315                 break;
00316                 case 0x47425247:  // GBRG
00317                 *bayerPattern = DC1394_COLOR_FILTER_GBRG;
00318                 break;
00319                 case 0x47524247:  // GRBG
00320                 *bayerPattern = DC1394_COLOR_FILTER_GRBG;
00321                 break;
00322                 case 0x42474752:  // BGGR
00323                 *bayerPattern = DC1394_COLOR_FILTER_BGGR;
00324                 break;
00325             }
00326 
00327             return err;
00328         }
00329 
00330         dc1394error_t setStereoVideoCapture()
00331         {
00332             dc1394error_t    err;
00333 
00334             // Bumblebee3 transmits stereo images in Format 7
00335             // load the factory defaults - this is auto-everything
00336             err = dc1394_memory_load(camera, 0 );
00337             if ( err != DC1394_SUCCESS ) 
00338             {
00339                 ROS_ERROR("Can't load default memory channel" );
00340                 return err;
00341             }
00342 
00343             // assume the XB is plugged into a 1394B network
00344             // XB3 can work with a 1394A bus but code changes will be required
00345             dc1394_video_set_operation_mode(camera, DC1394_OPERATION_MODE_1394B);
00346             
00347             dc1394_video_set_iso_speed(camera, DC1394_ISO_SPEED_800 );
00348             dc1394_video_set_mode(camera, DC1394_VIDEO_MODE_FORMAT7_3 );
00349             err = dc1394_format7_set_roi(camera,
00350                             DC1394_VIDEO_MODE_FORMAT7_3,
00351                             DC1394_COLOR_CODING_RGB8,
00352                                             // bytes per packet - sets frame rate
00353                             7680, //DC1394_USE_MAX_AVAIL
00354                             0, 
00355                             0,
00356                             hardware_width,
00357                             hardware_height);
00358             if ( err != DC1394_SUCCESS )
00359             {
00360                 ROS_ERROR("Can't setup Bumblebee XB3 capture" );
00361                 return err;
00362             }
00363 
00364             err = dc1394_capture_setup(camera, 1, DC1394_CAPTURE_FLAGS_DEFAULT );
00365             if ( err != DC1394_SUCCESS ) 
00366             {
00367                 ROS_ERROR("Can't setup Bumblebee capture" );
00368                 return err;
00369             }
00370 
00371             return DC1394_SUCCESS;
00372         }
00373         
00374         dc1394error_t startTransmission()
00375         {
00376             dc1394error_t err;
00377             
00378             // have the camera start sending us data
00379             err = dc1394_video_set_transmission(camera, DC1394_ON );
00380             if ( err != DC1394_SUCCESS )
00381             {
00382                 ROS_ERROR("Unable to start camera iso transmission" );
00383                 return err;
00384             }
00385 
00386             // printf( "Waiting for transmission... \n" );
00387             //  Sleep untill the camera has a transmission
00388             dc1394switch_t status = DC1394_OFF;
00389 
00390             for ( int i = 0; i <= 5; i++ )
00391             {
00392                 usleep(50000);
00393                 
00394                 
00395                 err = dc1394_video_get_transmission(camera, &status );
00396                 if ( err != DC1394_SUCCESS ) 
00397                 {
00398                     ROS_ERROR("Unable to get transmision status" );
00399                     return err;
00400                 }
00401                 
00402                 if ( status != DC1394_OFF )
00403                     break;
00404 
00405                 if( i == 5 ) 
00406                 {
00407                     ROS_ERROR("Camera doesn't seem to want to turn on!");
00408                     return DC1394_FAILURE;
00409                 }
00410             }
00411             
00412             return DC1394_SUCCESS;
00413         }
00414         
00415         void dc1394_deinterlace_rgb(unsigned char* src,unsigned char* dest, unsigned int width_, unsigned int height_)
00416         {
00417             register int i = (width_*height_)-1;
00418             register int r = ((width_*height_)/3)-1;
00419             register int g = ((width_*height_)*2/3)-1;
00420             register int b = (width_*height_)-1;
00421 
00422             while (i >= 0)
00423             {
00424                 dest[r--] = src[i--];
00425                 dest[g--] = src[i--];
00426                 dest[b--] = src[i--];
00427             }
00428         }
00429 
00430         void getFrameAndPublish()
00431         {
00432             dc1394error_t err;
00433             dc1394video_frame_t* frame;
00434 
00435             err = dc1394_capture_dequeue(camera,DC1394_CAPTURE_POLICY_WAIT,&frame);
00436             if ( err != DC1394_SUCCESS )
00437             {
00438                 ROS_ERROR("extractImagesColor - cannot dequeue image!" );
00439                 return;
00440             }
00441 
00442             dc1394_capture_enqueue(camera,frame);
00443             
00444             memcpy(pucGrabBuffer,frame->image,nBufferSize);
00445             
00446 //             if(buffer_processing_thread)
00447 //                 buffer_processing_thread->join();
00448             
00449             processBuffer();
00450 //             buffer_processing_thread = new boost::thread(boost::bind(&xb3CameraDriver::processBuffer,this)); 
00451 //             publish();
00452          
00453             return;
00454         }
00455         
00456         void processBuffer()
00457         {
00458             dc1394_deinterlace_rgb(pucGrabBuffer,pucDeInterlacedBuffer,hardware_width,3*hardware_height);
00459 
00460             // extract color from the bayer tile image
00461             // note: this will alias colors on the top and bottom rows
00462             dc1394_bayer_decoding_8bit(pucDeInterlacedBuffer,pucRGBBuffer,hardware_width,3*hardware_height,bayerTile,DC1394_BAYER_METHOD_HQLINEAR);
00463 
00464             if(output_height == hardware_height && output_width == hardware_width)
00465             {
00466                 threads.join_all();
00467                 
00468                 ros::Time timestamp = ros::Time::now();
00469                 threads.create_thread(boost::bind(&xb3CameraDriver::processLeftImage,this,timestamp));
00470                 threads.create_thread(boost::bind(&xb3CameraDriver::processCenterImage,this,timestamp));
00471                 threads.create_thread(boost::bind(&xb3CameraDriver::processRightImage,this,timestamp));
00472             }else
00473             {
00475                 copyToMat();
00476                 resizeToOutputMat();
00477                 fillRosImagesFromMat();
00478                 publish();
00479             }
00480         }
00481         
00482         void processLeftImage(ros::Time timestamp)
00483         {
00484             //Copy to ros image
00485             unsigned char* ptr_left_buffers = pucRGBBuffer;
00486             memcpy(left.data.data(),ptr_left_buffers,left.data.size());
00487             
00488             left.header.stamp = timestamp;
00489             left_camera_info.header.stamp = timestamp;
00490             
00491             left_publisher.publish(left,left_camera_info,timestamp);
00492         }
00493         
00494         void processCenterImage(ros::Time timestamp)
00495         {
00496             //Copy to ros image
00497             unsigned char* ptr_center_buffers = pucRGBBuffer + 3 * hardware_width*hardware_height; 
00498             memcpy(center.data.data(),ptr_center_buffers,center.data.size());
00499             
00500             center.header.stamp = timestamp;
00501             center_camera_info.header.stamp = timestamp;
00502             
00503             center_publisher.publish(center,center_camera_info,timestamp);
00504         }
00505         
00506         void processRightImage(ros::Time timestamp)
00507         {
00508             //Copy to ros image
00509             unsigned char* ptr_right_buffers = pucRGBBuffer + 6 * hardware_width*hardware_height; 
00510             memcpy(right.data.data(),ptr_right_buffers,right.data.size());
00511             
00512             right.header.stamp = timestamp;
00513             right_camera_info.header.stamp = timestamp;
00514             
00515             right_publisher.publish(right,right_camera_info,timestamp);
00516         }
00517         
00518         void fillRosImagesFromMat()
00519         {
00520             unsigned char* ptr_left_buffers = opencv_output_left.ptr<unsigned char>(0); 
00521             unsigned char* ptr_center_buffers = opencv_output_center.ptr<unsigned char>(0); 
00522             unsigned char* ptr_right_buffers = opencv_output_right.ptr<unsigned char>(0); 
00523 
00524             memcpy(left.data.data(),ptr_left_buffers,left.data.size());
00525             memcpy(center.data.data(),ptr_center_buffers,center.data.size());
00526             memcpy(right.data.data(),ptr_right_buffers,right.data.size());
00527         }
00528         
00529         void resizeToOutputMat()
00530         {
00531             cv::resize(opencv_left,opencv_output_left,opencv_output_left.size(),0,0,CV_INTER_AREA);
00532             cv::resize(opencv_center,opencv_output_center,opencv_output_center.size(),0,0,CV_INTER_AREA);
00533             cv::resize(opencv_right,opencv_output_right,opencv_output_right.size(),0,0,CV_INTER_AREA);
00534         }
00535         
00536         void copyToMat()
00537         {
00538             //Get pointers to the three image buffers
00539             unsigned char* ptr_left_buffers = pucRGBBuffer; 
00540             unsigned char* ptr_center_buffer = pucRGBBuffer + 3 * hardware_width*hardware_height; 
00541             unsigned char* ptr_right_buffers = pucRGBBuffer + 6 * hardware_width*hardware_height; 
00542 
00543             //Get pointers to the three cv::Mat
00544             unsigned char* ptr_left_cvMat = opencv_left.ptr<unsigned char>(0);
00545             unsigned char* ptr_center_cvMat = opencv_center.ptr<unsigned char>(0);
00546             unsigned char* ptr_right_cvMat = opencv_right.ptr<unsigned char>(0);
00547 
00548             memcpy(ptr_left_cvMat,ptr_left_buffers,nBufferSize);
00549             memcpy(ptr_center_cvMat,ptr_center_buffer,nBufferSize);
00550             memcpy(ptr_right_cvMat,ptr_right_buffers,nBufferSize);
00551             
00552             return;
00553         }
00554         
00555         void fillRosImages()
00556         {
00557             unsigned char* ptr_left_buffers = pucRGBBuffer; 
00558             unsigned char* ptr_center_buffers = pucRGBBuffer + 3 * hardware_width*hardware_height; 
00559             unsigned char* ptr_right_buffers = pucRGBBuffer + 6 * hardware_width*hardware_height; 
00560 
00561             memcpy(left.data.data(),ptr_left_buffers,left.data.size());
00562             memcpy(center.data.data(),ptr_center_buffers,center.data.size());
00563             memcpy(right.data.data(),ptr_right_buffers,right.data.size());
00564         }
00565         
00566         void publish()
00567         {
00568             ros::Time timestamp = ros::Time::now();
00569             
00570             left.header.stamp = timestamp;
00571             center.header.stamp = timestamp;
00572             right.header.stamp = timestamp;
00573             
00574             left_camera_info.header.stamp = timestamp;
00575             center_camera_info.header.stamp = timestamp;
00576             right_camera_info.header.stamp = timestamp;
00577             
00578             left_publisher.publish(left,left_camera_info,timestamp);
00579             center_publisher.publish(center,center_camera_info,timestamp);
00580             right_publisher.publish(right,right_camera_info,timestamp);            
00581         }
00582 };
00583 
00584 int main(int argc,char** argv)
00585 {
00586     ros::init(argc, argv, "xb3");
00587     ros::NodeHandle nh("~"); 
00588     
00589     //Start camera driver
00590     xb3CameraDriver xb3(nh);
00591     
00592 //     ros::Rate loop_rate(15);
00593 //     ros::Time start = ros::Time::now();
00594 //     boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
00595 //     boost::posix_time::ptime end;
00596     while(ros::ok())
00597     {
00598         xb3.getFrameAndPublish();
00599 //         end = boost::posix_time::microsec_clock::local_time();
00600 //         cout<<"Freq: "<<1e6/((end-start).total_microseconds())<<endl;
00601 //         start = boost::posix_time::microsec_clock::local_time();
00602 //         cout<<"frame rate: "<<1./((ros::Time::now()-start).toSec())<<endl;
00603 //         start = ros::Time::now();
00604     }
00605     
00606     return 0;
00607 }
00608     


xb3
Author(s): Miguel Oliveira, Tiago Talhada
autogenerated on Thu Nov 20 2014 11:36:02